Ensure that node exists when using node auth

Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
This commit is contained in:
Brad Davidson 2022-12-24 01:58:55 +00:00 committed by Brad Davidson
parent 992e64993d
commit 87f9c4ab11

View File

@ -32,13 +32,19 @@ import (
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/apiserver/pkg/endpoints/request"
bootstrapapi "k8s.io/cluster-bootstrap/token/api" bootstrapapi "k8s.io/cluster-bootstrap/token/api"
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
) )
const ( const (
staticURL = "/static/" staticURL = "/static/"
) )
var (
identifier = nodeidentifier.NewDefaultNodeIdentifier()
)
func router(ctx context.Context, config *Config, cfg *cmds.Server) http.Handler { func router(ctx context.Context, config *Config, cfg *cmds.Server) http.Handler {
serverConfig := &config.ControlConfig serverConfig := &config.ControlConfig
nodeAuth := passwordBootstrap(ctx, config) nodeAuth := passwordBootstrap(ctx, config)
@ -143,18 +149,27 @@ func cacerts(serverCA string) http.Handler {
}) })
} }
func getNodeInfo(req *http.Request) (string, string, error) { func getNodeInfo(req *http.Request) (*nodeInfo, error) {
user, ok := request.UserFrom(req.Context())
if !ok {
return nil, errors.New("auth user not set")
}
nodeName := req.Header.Get(version.Program + "-Node-Name") nodeName := req.Header.Get(version.Program + "-Node-Name")
if nodeName == "" { if nodeName == "" {
return "", "", errors.New("node name not set") return nil, errors.New("node name not set")
} }
nodePassword := req.Header.Get(version.Program + "-Node-Password") nodePassword := req.Header.Get(version.Program + "-Node-Password")
if nodePassword == "" { if nodePassword == "" {
return "", "", errors.New("node password not set") return nil, errors.New("node password not set")
} }
return strings.ToLower(nodeName), nodePassword, nil return &nodeInfo{
Name: strings.ToLower(nodeName),
Password: nodePassword,
User: user,
}, nil
} }
func getCACertAndKeys(caCertFile, caKeyFile, signingKeyFile string) ([]*x509.Certificate, crypto.Signer, crypto.Signer, error) { func getCACertAndKeys(caCertFile, caKeyFile, signingKeyFile string) ([]*x509.Certificate, crypto.Signer, crypto.Signer, error) {
@ -398,43 +413,63 @@ func sendError(err error, resp http.ResponseWriter, status ...int) {
// nodePassBootstrapper returns a node name, or http error code and error // nodePassBootstrapper returns a node name, or http error code and error
type nodePassBootstrapper func(req *http.Request) (string, int, error) type nodePassBootstrapper func(req *http.Request) (string, int, error)
// nodeInfo contains information on the requesting node, derived from auth creds
// and request headers.
type nodeInfo struct {
Name string
Password string
User user.Info
}
func passwordBootstrap(ctx context.Context, config *Config) nodePassBootstrapper { func passwordBootstrap(ctx context.Context, config *Config) nodePassBootstrapper {
runtime := config.ControlConfig.Runtime runtime := config.ControlConfig.Runtime
deferredNodes := map[string]bool{} deferredNodes := map[string]bool{}
var secretClient coreclient.SecretClient var secretClient coreclient.SecretClient
var nodeClient coreclient.NodeClient
var mu sync.Mutex var mu sync.Mutex
return nodePassBootstrapper(func(req *http.Request) (string, int, error) { return nodePassBootstrapper(func(req *http.Request) (string, int, error) {
nodeName, nodePassword, err := getNodeInfo(req) node, err := getNodeInfo(req)
if err != nil { if err != nil {
return "", http.StatusBadRequest, err return "", http.StatusBadRequest, err
} }
if secretClient == nil { nodeName, isNodeAuth := identifier.NodeIdentity(node.User)
if isNodeAuth && nodeName != node.Name {
return "", http.StatusBadRequest, errors.New("header node name does not match auth node name")
}
if secretClient == nil || nodeClient == nil {
if runtime.Core != nil { if runtime.Core != nil {
// initialize the client if we can // initialize the client if we can
secretClient = runtime.Core.Core().V1().Secret() secretClient = runtime.Core.Core().V1().Secret()
} else if nodeName == os.Getenv("NODE_NAME") { nodeClient = runtime.Core.Core().V1().Node()
// or verify the password locally and ensure a secret later } else if node.Name == os.Getenv("NODE_NAME") {
return verifyLocalPassword(ctx, config, &mu, deferredNodes, nodeName, nodePassword) // If we're verifying our own password, verify it locally and ensure a secret later.
} else if config.ControlConfig.DisableAPIServer { return verifyLocalPassword(ctx, config, &mu, deferredNodes, node)
// or defer node password verification until an apiserver joins the cluster } else if config.ControlConfig.DisableAPIServer && !isNodeAuth {
return verifyRemotePassword(ctx, config, &mu, deferredNodes, nodeName, nodePassword) // If we're running on an etcd-only node, and the request didn't use Node Identity auth,
// defer node password verification until an apiserver joins the cluster.
return verifyRemotePassword(ctx, config, &mu, deferredNodes, node)
} else { } else {
// or reject the request until the core is ready // Otherwise, reject the request until the core is ready.
return "", http.StatusServiceUnavailable, errors.New("runtime core not ready") return "", http.StatusServiceUnavailable, errors.New("runtime core not ready")
} }
} }
if err := nodepassword.Ensure(secretClient, nodeName, nodePassword); err != nil { if err := verifyNode(ctx, nodeClient, node); err != nil {
return "", http.StatusUnauthorized, err
}
if err := nodepassword.Ensure(secretClient, node.Name, node.Password); err != nil {
return "", http.StatusForbidden, err return "", http.StatusForbidden, err
} }
return nodeName, http.StatusOK, nil return node.Name, http.StatusOK, nil
}) })
} }
func verifyLocalPassword(ctx context.Context, config *Config, mu *sync.Mutex, deferredNodes map[string]bool, nodeName, nodePassword string) (string, int, error) { func verifyLocalPassword(ctx context.Context, config *Config, mu *sync.Mutex, deferredNodes map[string]bool, node *nodeInfo) (string, int, error) {
// use same password file location that the agent creates // use same password file location that the agent creates
nodePasswordRoot := "/" nodePasswordRoot := "/"
if config.ControlConfig.Rootless { if config.ControlConfig.Rootless {
@ -449,36 +484,45 @@ func verifyLocalPassword(ctx context.Context, config *Config, mu *sync.Mutex, de
} }
password := strings.TrimSpace(string(passBytes)) password := strings.TrimSpace(string(passBytes))
if password != nodePassword { if password != node.Password {
return "", http.StatusForbidden, errors.Wrapf(err, "unable to verify local password for node '%s'", nodeName) return "", http.StatusForbidden, errors.Wrapf(err, "unable to verify local password for node '%s'", node.Name)
} }
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
if _, ok := deferredNodes[nodeName]; !ok { if _, ok := deferredNodes[node.Name]; !ok {
deferredNodes[nodeName] = true deferredNodes[node.Name] = true
go ensureSecret(ctx, config, nodeName, nodePassword) go ensureSecret(ctx, config, node)
logrus.Debugf("Password verified locally for node '%s'", nodeName) logrus.Debugf("Password verified locally for node '%s'", node.Name)
} }
return nodeName, http.StatusOK, nil return node.Name, http.StatusOK, nil
} }
func verifyRemotePassword(ctx context.Context, config *Config, mu *sync.Mutex, deferredNodes map[string]bool, nodeName, nodePassword string) (string, int, error) { func verifyRemotePassword(ctx context.Context, config *Config, mu *sync.Mutex, deferredNodes map[string]bool, node *nodeInfo) (string, int, error) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
if _, ok := deferredNodes[nodeName]; !ok { if _, ok := deferredNodes[node.Name]; !ok {
deferredNodes[nodeName] = true deferredNodes[node.Name] = true
go ensureSecret(ctx, config, nodeName, nodePassword) go ensureSecret(ctx, config, node)
logrus.Debugf("Password verification deferred for node '%s'", nodeName) logrus.Debugf("Password verification deferred for node '%s'", node.Name)
} }
return nodeName, http.StatusOK, nil return node.Name, http.StatusOK, nil
} }
func ensureSecret(ctx context.Context, config *Config, nodeName, nodePassword string) { func verifyNode(ctx context.Context, nodeClient coreclient.NodeClient, node *nodeInfo) error {
if nodeName, isNodeAuth := identifier.NodeIdentity(node.User); isNodeAuth {
if _, err := nodeClient.Get(nodeName, metav1.GetOptions{}); err != nil {
return errors.Wrap(err, "unable to verify node identity")
}
}
return nil
}
func ensureSecret(ctx context.Context, config *Config, node *nodeInfo) {
runtime := config.ControlConfig.Runtime runtime := config.ControlConfig.Runtime
for { for {
select { select {
@ -486,10 +530,10 @@ func ensureSecret(ctx context.Context, config *Config, nodeName, nodePassword st
return return
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
if runtime.Core != nil { if runtime.Core != nil {
logrus.Debugf("Runtime core has become available, ensuring password secret for node '%s'", nodeName) logrus.Debugf("Runtime core has become available, ensuring password secret for node '%s'", node.Name)
secretClient := runtime.Core.Core().V1().Secret() secretClient := runtime.Core.Core().V1().Secret()
if err := nodepassword.Ensure(secretClient, nodeName, nodePassword); err != nil { if err := nodepassword.Ensure(secretClient, node.Name, node.Password); err != nil {
logrus.Warnf("Error ensuring node password secret for pre-validated node '%s': %v", nodeName, err) logrus.Warnf("Error ensuring node password secret for pre-validated node '%s': %v", node.Name, err)
} }
return return
} }