mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 12:18:16 +00:00 
			
		
		
		
	refactor bootstrap token utils
This commit is contained in:
		@@ -27,6 +27,7 @@ import (
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
 | 
			
		||||
	bootstraputil "k8s.io/cluster-bootstrap/token/util"
 | 
			
		||||
	bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ToSecret converts the given BootstrapToken object to its Secret representation that
 | 
			
		||||
@@ -55,7 +56,7 @@ func encodeTokenSecretData(token *BootstrapToken, now time.Time) map[string][]by
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If for some strange reason both token.TTL and token.Expires would be set
 | 
			
		||||
	// (they are mutually exlusive in validation so this shouldn't be the case),
 | 
			
		||||
	// (they are mutually exclusive in validation so this shouldn't be the case),
 | 
			
		||||
	// token.Expires has higher priority, as can be seen in the logic here.
 | 
			
		||||
	if token.Expires != nil {
 | 
			
		||||
		// Format the expiration date accordingly
 | 
			
		||||
@@ -83,7 +84,7 @@ func encodeTokenSecretData(token *BootstrapToken, now time.Time) map[string][]by
 | 
			
		||||
// BootstrapTokenFromSecret returns a BootstrapToken object from the given Secret
 | 
			
		||||
func BootstrapTokenFromSecret(secret *v1.Secret) (*BootstrapToken, error) {
 | 
			
		||||
	// Get the Token ID field from the Secret data
 | 
			
		||||
	tokenID := getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
 | 
			
		||||
	tokenID := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenIDKey)
 | 
			
		||||
	if len(tokenID) == 0 {
 | 
			
		||||
		return nil, errors.Errorf("bootstrap Token Secret has no token-id data: %s", secret.Name)
 | 
			
		||||
	}
 | 
			
		||||
@@ -94,7 +95,7 @@ func BootstrapTokenFromSecret(secret *v1.Secret) (*BootstrapToken, error) {
 | 
			
		||||
			bootstrapapi.BootstrapTokenSecretPrefix, secret.Name, bootstraputil.BootstrapTokenSecretName(tokenID))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tokenSecret := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
 | 
			
		||||
	tokenSecret := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenSecretKey)
 | 
			
		||||
	if len(tokenSecret) == 0 {
 | 
			
		||||
		return nil, errors.Errorf("bootstrap Token Secret has no token-secret data: %s", secret.Name)
 | 
			
		||||
	}
 | 
			
		||||
@@ -106,11 +107,11 @@ func BootstrapTokenFromSecret(secret *v1.Secret) (*BootstrapToken, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get the description (if any) from the Secret
 | 
			
		||||
	description := getSecretString(secret, bootstrapapi.BootstrapTokenDescriptionKey)
 | 
			
		||||
	description := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenDescriptionKey)
 | 
			
		||||
 | 
			
		||||
	// Expiration time is optional, if not specified this implies the token
 | 
			
		||||
	// never expires.
 | 
			
		||||
	secretExpiration := getSecretString(secret, bootstrapapi.BootstrapTokenExpirationKey)
 | 
			
		||||
	secretExpiration := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenExpirationKey)
 | 
			
		||||
	var expires *metav1.Time
 | 
			
		||||
	if len(secretExpiration) > 0 {
 | 
			
		||||
		expTime, err := time.Parse(time.RFC3339, secretExpiration)
 | 
			
		||||
@@ -142,7 +143,7 @@ func BootstrapTokenFromSecret(secret *v1.Secret) (*BootstrapToken, error) {
 | 
			
		||||
	// It's done this way to make .Groups be nil in case there is no items, rather than an
 | 
			
		||||
	// empty slice or an empty slice with a "" string only
 | 
			
		||||
	var groups []string
 | 
			
		||||
	groupsString := getSecretString(secret, bootstrapapi.BootstrapTokenExtraGroupsKey)
 | 
			
		||||
	groupsString := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenExtraGroupsKey)
 | 
			
		||||
	g := strings.Split(groupsString, ",")
 | 
			
		||||
	if len(g) > 0 && len(g[0]) > 0 {
 | 
			
		||||
		groups = g
 | 
			
		||||
@@ -156,14 +157,3 @@ func BootstrapTokenFromSecret(secret *v1.Secret) (*BootstrapToken, error) {
 | 
			
		||||
		Groups:      groups,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getSecretString returns the string value for the given key in the specified Secret
 | 
			
		||||
func getSecretString(secret *v1.Secret, key string) string {
 | 
			
		||||
	if secret.Data == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	if val, ok := secret.Data[key]; ok {
 | 
			
		||||
		return string(val)
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -454,55 +454,3 @@ func jsonMarshal(bt *BootstrapToken) string {
 | 
			
		||||
	b, _ := json.Marshal(*bt)
 | 
			
		||||
	return string(b)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetSecretString(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		secret      *v1.Secret
 | 
			
		||||
		key         string
 | 
			
		||||
		expectedVal string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "existing key",
 | 
			
		||||
			secret: &v1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 | 
			
		||||
				Data: map[string][]byte{
 | 
			
		||||
					"foo": []byte("bar"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			key:         "foo",
 | 
			
		||||
			expectedVal: "bar",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "non-existing key",
 | 
			
		||||
			secret: &v1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 | 
			
		||||
				Data: map[string][]byte{
 | 
			
		||||
					"foo": []byte("bar"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			key:         "baz",
 | 
			
		||||
			expectedVal: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "no data",
 | 
			
		||||
			secret: &v1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 | 
			
		||||
			},
 | 
			
		||||
			key:         "foo",
 | 
			
		||||
			expectedVal: "",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, rt := range tests {
 | 
			
		||||
		t.Run(rt.name, func(t *testing.T) {
 | 
			
		||||
			actual := getSecretString(rt.secret, rt.key)
 | 
			
		||||
			if actual != rt.expectedVal {
 | 
			
		||||
				t.Errorf(
 | 
			
		||||
					"failed getSecretString:\n\texpected: %s\n\t  actual: %s",
 | 
			
		||||
					rt.expectedVal,
 | 
			
		||||
					actual,
 | 
			
		||||
				)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ import (
 | 
			
		||||
	"k8s.io/client-go/tools/cache"
 | 
			
		||||
	"k8s.io/client-go/util/workqueue"
 | 
			
		||||
	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
 | 
			
		||||
	bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets"
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
	api "k8s.io/kubernetes/pkg/apis/core"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/controller"
 | 
			
		||||
@@ -187,7 +188,7 @@ func (tc *TokenCleaner) syncFunc(key string) error {
 | 
			
		||||
 | 
			
		||||
func (tc *TokenCleaner) evalSecret(o interface{}) {
 | 
			
		||||
	secret := o.(*v1.Secret)
 | 
			
		||||
	if isSecretExpired(secret) {
 | 
			
		||||
	if bootstrapsecretutil.HasExpired(secret, time.Now()) {
 | 
			
		||||
		klog.V(3).Infof("Deleting expired secret %s/%s", secret.Namespace, secret.Name)
 | 
			
		||||
		var options *metav1.DeleteOptions
 | 
			
		||||
		if len(secret.UID) > 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,46 +17,23 @@ limitations under the License.
 | 
			
		||||
package bootstrap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
 | 
			
		||||
	bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var namePattern = `^` + regexp.QuoteMeta(bootstrapapi.BootstrapTokenSecretPrefix) + `([a-z0-9]{6})$`
 | 
			
		||||
var nameRegExp = regexp.MustCompile(namePattern)
 | 
			
		||||
 | 
			
		||||
// getSecretString gets a string value from a secret.  If there is an error or
 | 
			
		||||
// if the key doesn't exist, an empty string is returned.
 | 
			
		||||
func getSecretString(secret *v1.Secret, key string) string {
 | 
			
		||||
	data, ok := secret.Data[key]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return string(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseSecretName parses the name of the secret to extract the secret ID.
 | 
			
		||||
func parseSecretName(name string) (secretID string, ok bool) {
 | 
			
		||||
	r := nameRegExp.FindStringSubmatch(name)
 | 
			
		||||
	if r == nil {
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
	return r[1], true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func validateSecretForSigning(secret *v1.Secret) (tokenID, tokenSecret string, ok bool) {
 | 
			
		||||
	nameTokenID, ok := parseSecretName(secret.Name)
 | 
			
		||||
	nameTokenID, ok := bootstrapsecretutil.ParseName(secret.Name)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		klog.V(3).Infof("Invalid secret name: %s. Must be of form %s<secret-id>.", secret.Name, bootstrapapi.BootstrapTokenSecretPrefix)
 | 
			
		||||
		return "", "", false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tokenID = getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
 | 
			
		||||
	tokenID = bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenIDKey)
 | 
			
		||||
	if len(tokenID) == 0 {
 | 
			
		||||
		klog.V(3).Infof("No %s key in %s/%s Secret", bootstrapapi.BootstrapTokenIDKey, secret.Namespace, secret.Name)
 | 
			
		||||
		return "", "", false
 | 
			
		||||
@@ -67,7 +44,7 @@ func validateSecretForSigning(secret *v1.Secret) (tokenID, tokenSecret string, o
 | 
			
		||||
		return "", "", false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tokenSecret = getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
 | 
			
		||||
	tokenSecret = bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenSecretKey)
 | 
			
		||||
	if len(tokenSecret) == 0 {
 | 
			
		||||
		klog.V(3).Infof("No %s key in %s/%s Secret", bootstrapapi.BootstrapTokenSecretKey, secret.Namespace, secret.Name)
 | 
			
		||||
		return "", "", false
 | 
			
		||||
@@ -76,34 +53,15 @@ func validateSecretForSigning(secret *v1.Secret) (tokenID, tokenSecret string, o
 | 
			
		||||
	// Ensure this secret hasn't expired.  The TokenCleaner should remove this
 | 
			
		||||
	// but if that isn't working or it hasn't gotten there yet we should check
 | 
			
		||||
	// here.
 | 
			
		||||
	if isSecretExpired(secret) {
 | 
			
		||||
	if bootstrapsecretutil.HasExpired(secret, time.Now()) {
 | 
			
		||||
		return "", "", false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make sure this secret can be used for signing
 | 
			
		||||
	okToSign := getSecretString(secret, bootstrapapi.BootstrapTokenUsageSigningKey)
 | 
			
		||||
	okToSign := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenUsageSigningKey)
 | 
			
		||||
	if okToSign != "true" {
 | 
			
		||||
		return "", "", false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tokenID, tokenSecret, true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isSecretExpired returns true if the Secret is expired.
 | 
			
		||||
func isSecretExpired(secret *v1.Secret) bool {
 | 
			
		||||
	expiration := getSecretString(secret, bootstrapapi.BootstrapTokenExpirationKey)
 | 
			
		||||
	if len(expiration) > 0 {
 | 
			
		||||
		expTime, err2 := time.Parse(time.RFC3339, expiration)
 | 
			
		||||
		if err2 != nil {
 | 
			
		||||
			klog.V(3).Infof("Unparseable expiration time (%s) in %s/%s Secret: %v. Treating as expired.",
 | 
			
		||||
				expiration, secret.Namespace, secret.Name, err2)
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		if time.Now().After(expTime) {
 | 
			
		||||
			klog.V(3).Infof("Expired bootstrap token in %s/%s Secret: %v",
 | 
			
		||||
				secret.Namespace, secret.Name, expiration)
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,6 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
 | 
			
		||||
@@ -180,27 +178,3 @@ func TestMismatchSecretName(t *testing.T) {
 | 
			
		||||
		t.Errorf("Token validation should fail with mismatched name")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseSecretName(t *testing.T) {
 | 
			
		||||
	tokenID, ok := parseSecretName("bootstrap-token-abc123")
 | 
			
		||||
	assert.True(t, ok, "parseSecretName should accept valid name")
 | 
			
		||||
	assert.Equal(t, "abc123", tokenID, "parseSecretName should return token ID")
 | 
			
		||||
 | 
			
		||||
	_, ok = parseSecretName("")
 | 
			
		||||
	assert.False(t, ok, "parseSecretName should reject blank name")
 | 
			
		||||
 | 
			
		||||
	_, ok = parseSecretName("abc123")
 | 
			
		||||
	assert.False(t, ok, "parseSecretName should reject with no prefix")
 | 
			
		||||
 | 
			
		||||
	_, ok = parseSecretName("bootstrap-token-")
 | 
			
		||||
	assert.False(t, ok, "parseSecretName should reject no token ID")
 | 
			
		||||
 | 
			
		||||
	_, ok = parseSecretName("bootstrap-token-abc")
 | 
			
		||||
	assert.False(t, ok, "parseSecretName should reject short token ID")
 | 
			
		||||
 | 
			
		||||
	_, ok = parseSecretName("bootstrap-token-abc123ghi")
 | 
			
		||||
	assert.False(t, ok, "parseSecretName should reject long token ID")
 | 
			
		||||
 | 
			
		||||
	_, ok = parseSecretName("bootstrap-token-ABC123")
 | 
			
		||||
	assert.False(t, ok, "parseSecretName should reject invalid token ID")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,20 +23,18 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/subtle"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
 | 
			
		||||
	corev1 "k8s.io/api/core/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/errors"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
			
		||||
	"k8s.io/apiserver/pkg/authentication/authenticator"
 | 
			
		||||
	"k8s.io/apiserver/pkg/authentication/user"
 | 
			
		||||
	corev1listers "k8s.io/client-go/listers/core/v1"
 | 
			
		||||
	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
 | 
			
		||||
	bootstraputil "k8s.io/cluster-bootstrap/token/util"
 | 
			
		||||
	bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets"
 | 
			
		||||
	bootstraptokenutil "k8s.io/cluster-bootstrap/util/tokens"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TODO: A few methods in this package is copied from other sources. Either
 | 
			
		||||
@@ -92,7 +90,7 @@ func tokenErrorf(s *corev1.Secret, format string, i ...interface{}) {
 | 
			
		||||
//     ( token-id ).( token-secret )
 | 
			
		||||
//
 | 
			
		||||
func (t *TokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
 | 
			
		||||
	tokenID, tokenSecret, err := parseToken(token)
 | 
			
		||||
	tokenID, tokenSecret, err := bootstraptokenutil.ParseToken(token)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// Token isn't of the correct form, ignore it.
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
@@ -118,29 +116,29 @@ func (t *TokenAuthenticator) AuthenticateToken(ctx context.Context, token string
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ts := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
 | 
			
		||||
	ts := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenSecretKey)
 | 
			
		||||
	if subtle.ConstantTimeCompare([]byte(ts), []byte(tokenSecret)) != 1 {
 | 
			
		||||
		tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenSecretKey, tokenSecret)
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	id := getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
 | 
			
		||||
	id := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenIDKey)
 | 
			
		||||
	if id != tokenID {
 | 
			
		||||
		tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenIDKey, tokenID)
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isSecretExpired(secret) {
 | 
			
		||||
	if bootstrapsecretutil.HasExpired(secret, time.Now()) {
 | 
			
		||||
		// logging done in isSecretExpired method.
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if getSecretString(secret, bootstrapapi.BootstrapTokenUsageAuthentication) != "true" {
 | 
			
		||||
	if bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenUsageAuthentication) != "true" {
 | 
			
		||||
		tokenErrorf(secret, "not marked %s=true.", bootstrapapi.BootstrapTokenUsageAuthentication)
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	groups, err := getGroups(secret)
 | 
			
		||||
	groups, err := bootstrapsecretutil.GetGroups(secret)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		tokenErrorf(secret, "has invalid value for key %s: %v.", bootstrapapi.BootstrapTokenExtraGroupsKey, err)
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
@@ -153,76 +151,3 @@ func (t *TokenAuthenticator) AuthenticateToken(ctx context.Context, token string
 | 
			
		||||
		},
 | 
			
		||||
	}, true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Copied from k8s.io/cluster-bootstrap/token/api
 | 
			
		||||
func getSecretString(secret *corev1.Secret, key string) string {
 | 
			
		||||
	data, ok := secret.Data[key]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return string(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Copied from k8s.io/cluster-bootstrap/token/api
 | 
			
		||||
func isSecretExpired(secret *corev1.Secret) bool {
 | 
			
		||||
	expiration := getSecretString(secret, bootstrapapi.BootstrapTokenExpirationKey)
 | 
			
		||||
	if len(expiration) > 0 {
 | 
			
		||||
		expTime, err2 := time.Parse(time.RFC3339, expiration)
 | 
			
		||||
		if err2 != nil {
 | 
			
		||||
			klog.V(3).Infof("Unparseable expiration time (%s) in %s/%s Secret: %v. Treating as expired.",
 | 
			
		||||
				expiration, secret.Namespace, secret.Name, err2)
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		if time.Now().After(expTime) {
 | 
			
		||||
			klog.V(3).Infof("Expired bootstrap token in %s/%s Secret: %v",
 | 
			
		||||
				secret.Namespace, secret.Name, expiration)
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Copied from kubernetes/cmd/kubeadm/app/util/token
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// tokenRegexpString defines id.secret regular expression pattern
 | 
			
		||||
	tokenRegexpString = "^([a-z0-9]{6})\\.([a-z0-9]{16})$"
 | 
			
		||||
	// tokenRegexp is a compiled regular expression of TokenRegexpString
 | 
			
		||||
	tokenRegexp = regexp.MustCompile(tokenRegexpString)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// parseToken tries and parse a valid token from a string.
 | 
			
		||||
// A token ID and token secret are returned in case of success, an error otherwise.
 | 
			
		||||
func parseToken(s string) (string, string, error) {
 | 
			
		||||
	split := tokenRegexp.FindStringSubmatch(s)
 | 
			
		||||
	if len(split) != 3 {
 | 
			
		||||
		return "", "", fmt.Errorf("token [%q] was not of form [%q]", s, tokenRegexpString)
 | 
			
		||||
	}
 | 
			
		||||
	return split[1], split[2], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getGroups loads and validates the bootstrapapi.BootstrapTokenExtraGroupsKey
 | 
			
		||||
// key from the bootstrap token secret, returning a list of group names or an
 | 
			
		||||
// error if any of the group names are invalid.
 | 
			
		||||
func getGroups(secret *corev1.Secret) ([]string, error) {
 | 
			
		||||
	// always include the default group
 | 
			
		||||
	groups := sets.NewString(bootstrapapi.BootstrapDefaultGroup)
 | 
			
		||||
 | 
			
		||||
	// grab any extra groups and if there are none, return just the default
 | 
			
		||||
	extraGroupsString := getSecretString(secret, bootstrapapi.BootstrapTokenExtraGroupsKey)
 | 
			
		||||
	if extraGroupsString == "" {
 | 
			
		||||
		return groups.List(), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// validate the names of the extra groups
 | 
			
		||||
	for _, group := range strings.Split(extraGroupsString, ",") {
 | 
			
		||||
		if err := bootstraputil.ValidateBootstrapGroupName(group); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		groups.Insert(group)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// return the result as a deduplicated, sorted list
 | 
			
		||||
	return groups.List(), nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -288,72 +288,3 @@ func TestTokenAuthenticator(t *testing.T) {
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetGroups(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name         string
 | 
			
		||||
		secret       *corev1.Secret
 | 
			
		||||
		expectResult []string
 | 
			
		||||
		expectError  bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "not set",
 | 
			
		||||
			secret: &corev1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
 | 
			
		||||
				Data:       map[string][]byte{},
 | 
			
		||||
			},
 | 
			
		||||
			expectResult: []string{"system:bootstrappers"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "set to empty value",
 | 
			
		||||
			secret: &corev1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
 | 
			
		||||
				Data: map[string][]byte{
 | 
			
		||||
					bootstrapapi.BootstrapTokenExtraGroupsKey: []byte(""),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectResult: []string{"system:bootstrappers"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "invalid prefix",
 | 
			
		||||
			secret: &corev1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
 | 
			
		||||
				Data: map[string][]byte{
 | 
			
		||||
					bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("foo"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectError: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "valid",
 | 
			
		||||
			secret: &corev1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
 | 
			
		||||
				Data: map[string][]byte{
 | 
			
		||||
					bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("system:bootstrappers:foo,system:bootstrappers:bar,system:bootstrappers:bar"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			// expect the results in deduplicated, sorted order
 | 
			
		||||
			expectResult: []string{
 | 
			
		||||
				"system:bootstrappers",
 | 
			
		||||
				"system:bootstrappers:bar",
 | 
			
		||||
				"system:bootstrappers:foo",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		result, err := getGroups(test.secret)
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				t.Errorf("test %q expected an error, but didn't get one (result: %#v)", test.name, result)
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("test %q return an unexpected error: %v", test.name, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(result, test.expectResult) {
 | 
			
		||||
			t.Errorf("test %q expected %#v, got %#v", test.name, test.expectResult, result)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,8 @@ import (
 | 
			
		||||
	"k8s.io/cluster-bootstrap/token/api"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TODO(dixudx): refactor this to util/secrets and util/tokens
 | 
			
		||||
 | 
			
		||||
// validBootstrapTokenChars defines the characters a bootstrap token can consist of
 | 
			
		||||
const validBootstrapTokenChars = "0123456789abcdefghijklmnopqrstuvwxyz"
 | 
			
		||||
 | 
			
		||||
@@ -110,6 +112,7 @@ func BootstrapTokenSecretName(tokenID string) string {
 | 
			
		||||
 | 
			
		||||
// ValidateBootstrapGroupName checks if the provided group name is a valid
 | 
			
		||||
// bootstrap group name. Returns nil if valid or a validation error if invalid.
 | 
			
		||||
// TODO(dixudx): should be moved to util/secrets
 | 
			
		||||
func ValidateBootstrapGroupName(name string) error {
 | 
			
		||||
	if BootstrapGroupRegexp.Match([]byte(name)) {
 | 
			
		||||
		return nil
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										100
									
								
								staging/src/k8s.io/cluster-bootstrap/util/secrets/secrets.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								staging/src/k8s.io/cluster-bootstrap/util/secrets/secrets.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
/*
 | 
			
		||||
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 secrets
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
			
		||||
	"k8s.io/cluster-bootstrap/token/api"
 | 
			
		||||
	legacyutil "k8s.io/cluster-bootstrap/token/util"
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetData returns the string value for the given key in the specified Secret
 | 
			
		||||
// If there is an error or if the key doesn't exist, an empty string is returned.
 | 
			
		||||
func GetData(secret *v1.Secret, key string) string {
 | 
			
		||||
	if secret.Data == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	if val, ok := secret.Data[key]; ok {
 | 
			
		||||
		return string(val)
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HasExpired will identify whether the secret expires
 | 
			
		||||
func HasExpired(secret *v1.Secret, currentTime time.Time) bool {
 | 
			
		||||
	expiration := GetData(secret, api.BootstrapTokenExpirationKey)
 | 
			
		||||
	if len(expiration) > 0 {
 | 
			
		||||
		expTime, err2 := time.Parse(time.RFC3339, expiration)
 | 
			
		||||
		if err2 != nil {
 | 
			
		||||
			klog.V(3).Infof("Unparseable expiration time (%s) in %s/%s Secret: %v. Treating as expired.",
 | 
			
		||||
				expiration, secret.Namespace, secret.Name, err2)
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		if currentTime.After(expTime) {
 | 
			
		||||
			klog.V(3).Infof("Expired bootstrap token in %s/%s Secret: %v",
 | 
			
		||||
				secret.Namespace, secret.Name, expiration)
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseName parses the name of the secret to extract the secret ID.
 | 
			
		||||
func ParseName(name string) (secretID string, ok bool) {
 | 
			
		||||
	namePattern := `^` + regexp.QuoteMeta(api.BootstrapTokenSecretPrefix) + `([a-z0-9]{6})$`
 | 
			
		||||
	nameRegExp, err := regexp.Compile(namePattern)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		klog.Errorf("error compiling bootstrap regex %q: %v", namePattern, err)
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
	r := nameRegExp.FindStringSubmatch(name)
 | 
			
		||||
	if r == nil {
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
	return r[1], true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetGroups loads and validates the bootstrapapi.BootstrapTokenExtraGroupsKey
 | 
			
		||||
// key from the bootstrap token secret, returning a list of group names or an
 | 
			
		||||
// error if any of the group names are invalid.
 | 
			
		||||
func GetGroups(secret *v1.Secret) ([]string, error) {
 | 
			
		||||
	// always include the default group
 | 
			
		||||
	groups := sets.NewString(api.BootstrapDefaultGroup)
 | 
			
		||||
 | 
			
		||||
	// grab any extra groups and if there are none, return just the default
 | 
			
		||||
	extraGroupsString := GetData(secret, api.BootstrapTokenExtraGroupsKey)
 | 
			
		||||
	if extraGroupsString == "" {
 | 
			
		||||
		return groups.List(), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// validate the names of the extra groups
 | 
			
		||||
	for _, group := range strings.Split(extraGroupsString, ",") {
 | 
			
		||||
		if err := legacyutil.ValidateBootstrapGroupName(group); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		groups.Insert(group)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// return the result as a deduplicated, sorted list
 | 
			
		||||
	return groups.List(), nil
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,187 @@
 | 
			
		||||
/*
 | 
			
		||||
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 secrets
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetSecretString(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		secret      *v1.Secret
 | 
			
		||||
		key         string
 | 
			
		||||
		expectedVal string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "existing key",
 | 
			
		||||
			secret: &v1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 | 
			
		||||
				Data: map[string][]byte{
 | 
			
		||||
					"foo": []byte("bar"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			key:         "foo",
 | 
			
		||||
			expectedVal: "bar",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "non-existing key",
 | 
			
		||||
			secret: &v1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 | 
			
		||||
				Data: map[string][]byte{
 | 
			
		||||
					"foo": []byte("bar"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			key:         "baz",
 | 
			
		||||
			expectedVal: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "no data",
 | 
			
		||||
			secret: &v1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 | 
			
		||||
			},
 | 
			
		||||
			key:         "foo",
 | 
			
		||||
			expectedVal: "",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, rt := range tests {
 | 
			
		||||
		t.Run(rt.name, func(t *testing.T) {
 | 
			
		||||
			actual := GetData(rt.secret, rt.key)
 | 
			
		||||
			if actual != rt.expectedVal {
 | 
			
		||||
				t.Errorf(
 | 
			
		||||
					"failed getSecretString:\n\texpected: %s\n\t  actual: %s",
 | 
			
		||||
					rt.expectedVal,
 | 
			
		||||
					actual,
 | 
			
		||||
				)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseSecretName(t *testing.T) {
 | 
			
		||||
	tokenID, ok := ParseName("bootstrap-token-abc123")
 | 
			
		||||
	if !ok {
 | 
			
		||||
		t.Error("ParseName should accept valid name")
 | 
			
		||||
	}
 | 
			
		||||
	if tokenID != "abc123" {
 | 
			
		||||
		t.Error("ParseName should return token ID")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, ok = ParseName("")
 | 
			
		||||
	if ok {
 | 
			
		||||
		t.Error("ParseName should reject blank name")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, ok = ParseName("abc123")
 | 
			
		||||
	if ok {
 | 
			
		||||
		t.Error("ParseName should reject with no prefix")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, ok = ParseName("bootstrap-token-")
 | 
			
		||||
	if ok {
 | 
			
		||||
		t.Error("ParseName should reject no token ID")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, ok = ParseName("bootstrap-token-abc")
 | 
			
		||||
	if ok {
 | 
			
		||||
		t.Error("ParseName should reject short token ID")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, ok = ParseName("bootstrap-token-abc123ghi")
 | 
			
		||||
	if ok {
 | 
			
		||||
		t.Error("ParseName should reject long token ID")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, ok = ParseName("bootstrap-token-ABC123")
 | 
			
		||||
	if ok {
 | 
			
		||||
		t.Error("ParseName should reject invalid token ID")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetGroups(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name         string
 | 
			
		||||
		secret       *v1.Secret
 | 
			
		||||
		expectResult []string
 | 
			
		||||
		expectError  bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "not set",
 | 
			
		||||
			secret: &v1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
 | 
			
		||||
				Data:       map[string][]byte{},
 | 
			
		||||
			},
 | 
			
		||||
			expectResult: []string{"system:bootstrappers"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "set to empty value",
 | 
			
		||||
			secret: &v1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
 | 
			
		||||
				Data: map[string][]byte{
 | 
			
		||||
					bootstrapapi.BootstrapTokenExtraGroupsKey: []byte(""),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectResult: []string{"system:bootstrappers"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "invalid prefix",
 | 
			
		||||
			secret: &v1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
 | 
			
		||||
				Data: map[string][]byte{
 | 
			
		||||
					bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("foo"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectError: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "valid",
 | 
			
		||||
			secret: &v1.Secret{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
 | 
			
		||||
				Data: map[string][]byte{
 | 
			
		||||
					bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("system:bootstrappers:foo,system:bootstrappers:bar,system:bootstrappers:bar"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			// expect the results in deduplicated, sorted order
 | 
			
		||||
			expectResult: []string{
 | 
			
		||||
				"system:bootstrappers",
 | 
			
		||||
				"system:bootstrappers:bar",
 | 
			
		||||
				"system:bootstrappers:foo",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		result, err := GetGroups(test.secret)
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				t.Errorf("test %q expected an error, but didn't get one (result: %#v)", test.name, result)
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("test %q return an unexpected error: %v", test.name, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(result, test.expectResult) {
 | 
			
		||||
			t.Errorf("test %q expected %#v, got %#v", test.name, test.expectResult, result)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								staging/src/k8s.io/cluster-bootstrap/util/tokens/tokens.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								staging/src/k8s.io/cluster-bootstrap/util/tokens/tokens.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
/*
 | 
			
		||||
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 tokens
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/cluster-bootstrap/token/api"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ParseToken tries and parse a valid token from a string.
 | 
			
		||||
// A token ID and token secret are returned in case of success, an error otherwise.
 | 
			
		||||
func ParseToken(s string) (tokenID, tokenSecret string, err error) {
 | 
			
		||||
	bootstrapTokenRegexp, err := regexp.Compile(api.BootstrapTokenPattern)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", "", fmt.Errorf("error compiling bootstrap regex %q: %v", api.BootstrapTokenPattern, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	split := bootstrapTokenRegexp.FindStringSubmatch(s)
 | 
			
		||||
	if len(split) != 3 {
 | 
			
		||||
		return "", "", fmt.Errorf("token [%q] was not of form [%q]", s, api.BootstrapTokenPattern)
 | 
			
		||||
	}
 | 
			
		||||
	return split[1], split[2], nil
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user