sigs.k8s.io/cluster-api@v1.7.1/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  // shouldRotate returns true if an existing token is past half of its TTL and should to be rotated.
    91  func shouldRotate(ctx context.Context, c client.Client, token string, ttl time.Duration) (bool, error) {
    92  	secret, err := getToken(ctx, c, token)
    93  	if err != nil {
    94  		// If the secret is deleted before due to unknown reasons, machine pools cannot be scaled up.
    95  		// Since that, secret should be rotated if missing.
    96  		// Normally, it is not expected to reach this line.
    97  		if apierrors.IsNotFound(err) {
    98  			return true, nil
    99  		}
   100  		return false, err
   101  	}
   102  
   103  	expiration, err := time.Parse(time.RFC3339, string(secret.Data[bootstrapapi.BootstrapTokenExpirationKey]))
   104  	if err != nil {
   105  		return false, err
   106  	}
   107  	return expiration.Before(time.Now().UTC().Add(ttl / 2)), nil
   108  }