kops/node-authorizer/pkg/server/admission.go

220 lines
6.1 KiB
Go

/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
"context"
"fmt"
"time"
"k8s.io/kops/node-authorizer/pkg/utils"
"go.uber.org/zap"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
// CheckRegistration indicates we should validate the node is not regestered
CheckRegistration = "verify-registration"
)
// authorizeNodeRequest is responsible for handling the incoming authorization request
func (n *NodeAuthorizer) authorizeNodeRequest(ctx context.Context, request *NodeRegistration) error {
doneCh := make(chan error)
// @step: create a context to run under
ctx, cancel := context.WithTimeout(ctx, n.config.AuthorizationTimeout)
defer cancel()
// @step: background the request and wait for either a timeout or a token
go func() {
doneCh <- func() error {
// @step: check if the node request is authorized
if err := n.safelyAuthorizeNode(ctx, request); err != nil {
return err
}
if request.IsAllowed() {
return n.safelyProvisionBootstrapToken(ctx, request)
}
return nil
}()
}()
// @step: we either wait for the context to timeout or cancel, or we receive a done signal
select {
case <-ctx.Done():
if err := ctx.Err(); err != nil {
utils.Logger.Error("operation has either timed out or been cancelled",
zap.String("client", request.Spec.RemoteAddr),
zap.String("node", request.Spec.NodeName),
zap.Error(err))
}
return nil
case err := <-doneCh:
if err != nil {
utils.Logger.Error("failed to provision a bootstrap token",
zap.String("client", request.Spec.RemoteAddr),
zap.String("node", request.Spec.NodeName),
zap.Error(err))
}
}
if !request.IsAllowed() {
utils.Logger.Error("the node has been denied authorization",
zap.String("client", request.Spec.RemoteAddr),
zap.String("node", request.Spec.NodeName),
zap.String("reason", request.Status.Reason))
nodeAuthorizationMetric.WithLabelValues("denied").Inc()
return nil
}
utils.Logger.Info("node has been authorized access",
zap.String("client", request.Spec.RemoteAddr),
zap.String("node", request.Spec.NodeName))
nodeAuthorizationMetric.WithLabelValues("allowed").Inc()
return nil
}
// safelyAuthorizeNode checks if the request is permitted
func (n *NodeAuthorizer) safelyAuthorizeNode(ctx context.Context, request *NodeRegistration) error {
// @step: attempt to authorize the request
now := time.Now()
if err := n.authorizer.Authorize(ctx, request); err != nil {
authorizerErrorMetric.Inc()
return err
}
authorizerLatencyMetric.Observe(time.Since(now).Seconds())
// @check if the node is registered already
if n.config.UseFeature(CheckRegistration) {
if found, err := isNodeRegistered(ctx, n.client, request.Spec.NodeName); err != nil {
return fmt.Errorf("unable to check node registration status: %s", err)
} else if found {
request.Deny(fmt.Sprintf("node %s already registered", request.Spec.NodeName))
}
}
return nil
}
// safelyProvisionBootstrapToken is responsible for generating a bootstrap token for us
func (n *NodeAuthorizer) safelyProvisionBootstrapToken(ctx context.Context, request *NodeRegistration) error {
maxInterval := 500 * time.Millisecond
maxTime := 10 * time.Second
usages := []string{"authentication", "signing"}
now := time.Now()
if err := utils.Retry(ctx, maxInterval, maxTime, func() error {
token, err := n.createToken(n.config.TokenDuration, usages)
if err != nil {
return err
}
request.Status.Token = token.String()
return err
}); err != nil {
return err
}
tokenLatencyMetric.Observe(time.Since(now).Seconds())
return nil
}
// createToken generates a token for the instance
func (n *NodeAuthorizer) createToken(expiration time.Duration, usages []string) (*Token, error) {
var err error
var token *Token
ctx := context.TODO()
err = utils.Retry(ctx, 2000*time.Millisecond, 10*time.Second, func() error {
// @step: generate a random token for them
if token, err = NewToken(); err != nil {
return err
}
// @step: check if the token already exist, remote but a possibility
if found, err := n.hasToken(ctx, token); err != nil {
return err
} else if found {
return fmt.Errorf("duplicate token found: %s, skipping", token.ID)
}
// @step: add the secret to the namespace
v1secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: token.Name(),
Labels: map[string]string{
"name": token.Name(),
},
},
Type: v1.SecretType(secretTypeBootstrapToken),
Data: encodeTokenSecretData(token, usages, expiration),
}
if _, err := n.client.CoreV1().Secrets(tokenNamespace).Create(ctx, v1secret, metav1.CreateOptions{}); err != nil {
return err
}
return nil
})
return token, err
}
// hasToken checks if the tokens already exists
func (n *NodeAuthorizer) hasToken(ctx context.Context, token *Token) (bool, error) {
resp, err := n.client.CoreV1().Secrets(tokenNamespace).List(ctx, metav1.ListOptions{
LabelSelector: "name=" + token.Name(),
Limit: 1,
})
if err != nil {
return false, err
}
return len(resp.Items) > 0, nil
}
// encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret
func encodeTokenSecretData(token *Token, usages []string, ttl time.Duration) map[string][]byte {
data := map[string][]byte{
bootstrapTokenIDKey: []byte(token.ID),
bootstrapTokenSecretKey: []byte(token.Secret),
}
if ttl > 0 {
expire := time.Now().Add(ttl).Format(time.RFC3339)
data[bootstrapTokenExpirationKey] = []byte(expire)
}
for _, usage := range usages {
data[bootstrapTokenUsagePrefix+usage] = []byte("true")
}
return data
}