sigs.k8s.io/cluster-api@v1.6.3/bootstrap/kubeadm/internal/controllers/token.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package controllers
    18  
    19  import (
    20  	"context"
    21  	"time"
    22  
    23  	"github.com/pkg/errors"
    24  	corev1 "k8s.io/api/core/v1"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
    28  	bootstraputil "k8s.io/cluster-bootstrap/token/util"
    29  	"sigs.k8s.io/controller-runtime/pkg/client"
    30  )
    31  
    32  // createToken attempts to create a token with the given ID.
    33  func createToken(ctx context.Context, c client.Client, ttl time.Duration) (string, error) {
    34  	token, err := bootstraputil.GenerateBootstrapToken()
    35  	if err != nil {
    36  		return "", errors.Wrap(err, "unable to generate bootstrap token")
    37  	}
    38  
    39  	substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(token)
    40  	if len(substrs) != 3 {
    41  		return "", errors.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern)
    42  	}
    43  	tokenID := substrs[1]
    44  	tokenSecret := substrs[2]
    45  
    46  	secretName := bootstraputil.BootstrapTokenSecretName(tokenID)
    47  	secretToken := &corev1.Secret{
    48  		ObjectMeta: metav1.ObjectMeta{
    49  			Name:      secretName,
    50  			Namespace: metav1.NamespaceSystem,
    51  		},
    52  		Type: bootstrapapi.SecretTypeBootstrapToken,
    53  		Data: map[string][]byte{
    54  			bootstrapapi.BootstrapTokenIDKey:               []byte(tokenID),
    55  			bootstrapapi.BootstrapTokenSecretKey:           []byte(tokenSecret),
    56  			bootstrapapi.BootstrapTokenExpirationKey:       []byte(time.Now().UTC().Add(ttl).Format(time.RFC3339)),
    57  			bootstrapapi.BootstrapTokenUsageSigningKey:     []byte("true"),
    58  			bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
    59  			bootstrapapi.BootstrapTokenExtraGroupsKey:      []byte("system:bootstrappers:kubeadm:default-node-token"),
    60  			bootstrapapi.BootstrapTokenDescriptionKey:      []byte("token generated by cluster-api-bootstrap-provider-kubeadm"),
    61  		},
    62  	}
    63  
    64  	if err := c.Create(ctx, secretToken); err != nil {
    65  		return "", err
    66  	}
    67  	return token, nil
    68  }
    69  
    70  // getToken fetches the token Secret and returns an error if it is invalid.
    71  func getToken(ctx context.Context, c client.Client, token string) (*corev1.Secret, error) {
    72  	substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(token)
    73  	if len(substrs) != 3 {
    74  		return nil, errors.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern)
    75  	}
    76  	tokenID := substrs[1]
    77  
    78  	secretName := bootstraputil.BootstrapTokenSecretName(tokenID)
    79  	secret := &corev1.Secret{}
    80  	if err := c.Get(ctx, client.ObjectKey{Name: secretName, Namespace: metav1.NamespaceSystem}, secret); err != nil {
    81  		return secret, err
    82  	}
    83  
    84  	if secret.Data == nil {
    85  		return nil, errors.Errorf("Invalid bootstrap secret %q, remove the token from the kubeadm config to re-create", secretName)
    86  	}
    87  	return secret, nil
    88  }
    89  
    90  // refreshToken extends the TTL for an existing token.
    91  func refreshToken(ctx context.Context, c client.Client, token string, ttl time.Duration) error {
    92  	secret, err := getToken(ctx, c, token)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	secret.Data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(time.Now().UTC().Add(ttl).Format(time.RFC3339))
    97  
    98  	return c.Update(ctx, secret)
    99  }
   100  
   101  // shouldRotate returns true if an existing token is past half of its TTL and should to be rotated.
   102  func shouldRotate(ctx context.Context, c client.Client, token string, ttl time.Duration) (bool, error) {
   103  	secret, err := getToken(ctx, c, token)
   104  	if err != nil {
   105  		// If the secret is deleted before due to unknown reasons, machine pools cannot be scaled up.
   106  		// Since that, secret should be rotated if missing.
   107  		// Normally, it is not expected to reach this line.
   108  		if apierrors.IsNotFound(err) {
   109  			return true, nil
   110  		}
   111  		return false, err
   112  	}
   113  
   114  	expiration, err := time.Parse(time.RFC3339, string(secret.Data[bootstrapapi.BootstrapTokenExpirationKey]))
   115  	if err != nil {
   116  		return false, err
   117  	}
   118  	return expiration.Before(time.Now().UTC().Add(ttl / 2)), nil
   119  }