mirror of https://github.com/k3s-io/k3s
Ensure that node exists when using node auth
Signed-off-by: Brad Davidson <brad.davidson@rancher.com>pull/6922/head
parent
992e64993d
commit
87f9c4ab11
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue