mirror of
https://github.com/k3s-io/k3s.git
synced 2024-06-07 19:41:36 +00:00
Ensure that node exists when using node auth
Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
This commit is contained in:
parent
992e64993d
commit
87f9c4ab11
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user