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/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/apiserver/pkg/endpoints/request"
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
)
const (
staticURL = "/static/"
)
var (
identifier = nodeidentifier.NewDefaultNodeIdentifier()
)
func router(ctx context.Context, config *Config, cfg *cmds.Server) http.Handler {
serverConfig := &config.ControlConfig
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")
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")
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) {
@ -398,43 +413,63 @@ func sendError(err error, resp http.ResponseWriter, status ...int) {
// nodePassBootstrapper returns a node name, or http error code and 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 {
runtime := config.ControlConfig.Runtime
deferredNodes := map[string]bool{}
var secretClient coreclient.SecretClient
var nodeClient coreclient.NodeClient
var mu sync.Mutex
return nodePassBootstrapper(func(req *http.Request) (string, int, error) {
nodeName, nodePassword, err := getNodeInfo(req)
node, err := getNodeInfo(req)
if err != nil {
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 {
// initialize the client if we can
secretClient = runtime.Core.Core().V1().Secret()
} else if nodeName == os.Getenv("NODE_NAME") {
// or verify the password locally and ensure a secret later
return verifyLocalPassword(ctx, config, &mu, deferredNodes, nodeName, nodePassword)
} else if config.ControlConfig.DisableAPIServer {
// or defer node password verification until an apiserver joins the cluster
return verifyRemotePassword(ctx, config, &mu, deferredNodes, nodeName, nodePassword)
nodeClient = runtime.Core.Core().V1().Node()
} else if node.Name == os.Getenv("NODE_NAME") {
// If we're verifying our own password, verify it locally and ensure a secret later.
return verifyLocalPassword(ctx, config, &mu, deferredNodes, node)
} else if config.ControlConfig.DisableAPIServer && !isNodeAuth {
// 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 {
// 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")
}
}
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 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
nodePasswordRoot := "/"
if config.ControlConfig.Rootless {
@ -449,36 +484,45 @@ func verifyLocalPassword(ctx context.Context, config *Config, mu *sync.Mutex, de
}
password := strings.TrimSpace(string(passBytes))
if password != nodePassword {
return "", http.StatusForbidden, errors.Wrapf(err, "unable to verify local password for node '%s'", nodeName)
if password != node.Password {
return "", http.StatusForbidden, errors.Wrapf(err, "unable to verify local password for node '%s'", node.Name)
}
mu.Lock()
defer mu.Unlock()
if _, ok := deferredNodes[nodeName]; !ok {
deferredNodes[nodeName] = true
go ensureSecret(ctx, config, nodeName, nodePassword)
logrus.Debugf("Password verified locally for node '%s'", nodeName)
if _, ok := deferredNodes[node.Name]; !ok {
deferredNodes[node.Name] = true
go ensureSecret(ctx, config, node)
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()
defer mu.Unlock()
if _, ok := deferredNodes[nodeName]; !ok {
deferredNodes[nodeName] = true
go ensureSecret(ctx, config, nodeName, nodePassword)
logrus.Debugf("Password verification deferred for node '%s'", nodeName)
if _, ok := deferredNodes[node.Name]; !ok {
deferredNodes[node.Name] = true
go ensureSecret(ctx, config, node)
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
for {
select {
@ -486,10 +530,10 @@ func ensureSecret(ctx context.Context, config *Config, nodeName, nodePassword st
return
case <-time.After(1 * time.Second):
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()
if err := nodepassword.Ensure(secretClient, nodeName, nodePassword); err != nil {
logrus.Warnf("Error ensuring node password secret for pre-validated node '%s': %v", nodeName, err)
if err := nodepassword.Ensure(secretClient, node.Name, node.Password); err != nil {
logrus.Warnf("Error ensuring node password secret for pre-validated node '%s': %v", node.Name, err)
}
return
}