...

Source file src/k8s.io/cluster-bootstrap/token/util/helpers.go

Documentation: k8s.io/cluster-bootstrap/token/util

     1  /*
     2  Copyright 2017 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 util
    18  
    19  import (
    20  	"crypto/rand"
    21  	"fmt"
    22  	"math/big"
    23  	"regexp"
    24  	"strings"
    25  
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	"k8s.io/cluster-bootstrap/token/api"
    28  )
    29  
    30  // TODO(dixudx): refactor this to util/secrets and util/tokens
    31  
    32  // validBootstrapTokenChars defines the characters a bootstrap token can consist of
    33  const validBootstrapTokenChars = "0123456789abcdefghijklmnopqrstuvwxyz"
    34  
    35  var (
    36  	// BootstrapTokenRegexp is a compiled regular expression of TokenRegexpString
    37  	BootstrapTokenRegexp = regexp.MustCompile(api.BootstrapTokenPattern)
    38  	// BootstrapTokenIDRegexp is a compiled regular expression of TokenIDRegexpString
    39  	BootstrapTokenIDRegexp = regexp.MustCompile(api.BootstrapTokenIDPattern)
    40  	// BootstrapGroupRegexp is a compiled regular expression of BootstrapGroupPattern
    41  	BootstrapGroupRegexp = regexp.MustCompile(api.BootstrapGroupPattern)
    42  )
    43  
    44  // GenerateBootstrapToken generates a new, random Bootstrap Token.
    45  func GenerateBootstrapToken() (string, error) {
    46  	tokenID, err := randBytes(api.BootstrapTokenIDBytes)
    47  	if err != nil {
    48  		return "", err
    49  	}
    50  
    51  	tokenSecret, err := randBytes(api.BootstrapTokenSecretBytes)
    52  	if err != nil {
    53  		return "", err
    54  	}
    55  
    56  	return TokenFromIDAndSecret(tokenID, tokenSecret), nil
    57  }
    58  
    59  // randBytes returns a random string consisting of the characters in
    60  // validBootstrapTokenChars, with the length customized by the parameter
    61  func randBytes(length int) (string, error) {
    62  	var (
    63  		token = make([]byte, length)
    64  		max   = new(big.Int).SetUint64(uint64(len(validBootstrapTokenChars)))
    65  	)
    66  
    67  	for i := range token {
    68  		val, err := rand.Int(rand.Reader, max)
    69  		if err != nil {
    70  			return "", fmt.Errorf("could not generate random integer: %w", err)
    71  		}
    72  		// Use simple operations in constant-time to obtain a byte in the a-z,0-9
    73  		// character range
    74  		x := val.Uint64()
    75  		res := x + 48 + (39 & ((9 - x) >> 8))
    76  		token[i] = byte(res)
    77  	}
    78  
    79  	return string(token), nil
    80  }
    81  
    82  // TokenFromIDAndSecret returns the full token which is of the form "{id}.{secret}"
    83  func TokenFromIDAndSecret(id, secret string) string {
    84  	return fmt.Sprintf("%s.%s", id, secret)
    85  }
    86  
    87  // IsValidBootstrapToken returns whether the given string is valid as a Bootstrap Token.
    88  // Avoid using BootstrapTokenRegexp.MatchString(token) and instead perform constant-time
    89  // comparisons on the secret.
    90  func IsValidBootstrapToken(token string) bool {
    91  	// Must be exactly two strings separated by "."
    92  	t := strings.Split(token, ".")
    93  	if len(t) != 2 {
    94  		return false
    95  	}
    96  
    97  	// Validate the ID: t[0]
    98  	// Using a Regexp for it is safe because the ID is public already
    99  	if !BootstrapTokenIDRegexp.MatchString(t[0]) {
   100  		return false
   101  	}
   102  
   103  	// Validate the secret with constant-time: t[1]
   104  	secret := t[1]
   105  	if len(secret) != api.BootstrapTokenSecretBytes { // Must be an exact size
   106  		return false
   107  	}
   108  	for i := range secret {
   109  		c := int(secret[i])
   110  		notDigit := (c < 48 || c > 57)   // Character is not in the 0-9 range
   111  		notLetter := (c < 97 || c > 122) // Character is not in the a-z range
   112  		if notDigit && notLetter {
   113  			return false
   114  		}
   115  	}
   116  	return true
   117  }
   118  
   119  // IsValidBootstrapTokenID returns whether the given string is valid as a Bootstrap Token ID and
   120  // in other words satisfies the BootstrapTokenIDRegexp
   121  func IsValidBootstrapTokenID(tokenID string) bool {
   122  	return BootstrapTokenIDRegexp.MatchString(tokenID)
   123  }
   124  
   125  // BootstrapTokenSecretName returns the expected name for the Secret storing the
   126  // Bootstrap Token in the Kubernetes API.
   127  func BootstrapTokenSecretName(tokenID string) string {
   128  	return fmt.Sprintf("%s%s", api.BootstrapTokenSecretPrefix, tokenID)
   129  }
   130  
   131  // ValidateBootstrapGroupName checks if the provided group name is a valid
   132  // bootstrap group name. Returns nil if valid or a validation error if invalid.
   133  // TODO(dixudx): should be moved to util/secrets
   134  func ValidateBootstrapGroupName(name string) error {
   135  	if BootstrapGroupRegexp.Match([]byte(name)) {
   136  		return nil
   137  	}
   138  	return fmt.Errorf("bootstrap group %q is invalid (must match %s)", name, api.BootstrapGroupPattern)
   139  }
   140  
   141  // ValidateUsages validates that the passed in string are valid usage strings for bootstrap tokens.
   142  func ValidateUsages(usages []string) error {
   143  	validUsages := sets.NewString(api.KnownTokenUsages...)
   144  	invalidUsages := sets.NewString()
   145  	for _, usage := range usages {
   146  		if !validUsages.Has(usage) {
   147  			invalidUsages.Insert(usage)
   148  		}
   149  	}
   150  	if len(invalidUsages) > 0 {
   151  		return fmt.Errorf("invalid bootstrap token usage string: %s, valid usage options: %s", strings.Join(invalidUsages.List(), ","), strings.Join(api.KnownTokenUsages, ","))
   152  	}
   153  	return nil
   154  }
   155  

View as plain text