Implement auth-extra-groups in bootstrap token authenticator.
				
					
				
			This implements support for the new `auth-extra-groups` key in `bootstrap.kubernetes.io/token` secrets by adding extra groups to the user info returned for valid bootstrap tokens.
This commit is contained in:
		| @@ -30,6 +30,7 @@ go_library( | ||||
|         "//pkg/client/listers/core/internalversion:go_default_library", | ||||
|         "//vendor/github.com/golang/glog:go_default_library", | ||||
|         "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", | ||||
|         "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", | ||||
|         "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", | ||||
|     ], | ||||
| ) | ||||
|   | ||||
| @@ -23,11 +23,13 @@ import ( | ||||
| 	"crypto/subtle" | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/apiserver/pkg/authentication/user" | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" | ||||
| @@ -79,6 +81,7 @@ func tokenErrorf(s *api.Secret, format string, i ...interface{}) { | ||||
| //       token-id: ( token id ) | ||||
| //       # Required key usage. | ||||
| //       usage-bootstrap-authentication: true | ||||
| //       auth-extra-groups: "system:bootstrappers:custom-group1,system:bootstrappers:custom-group2" | ||||
| //       # May also contain an expiry. | ||||
| // | ||||
| // Tokens are expected to be of the form: | ||||
| @@ -134,9 +137,15 @@ func (t *TokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, e | ||||
| 		return nil, false, nil | ||||
| 	} | ||||
|  | ||||
| 	groups, err := getGroups(secret) | ||||
| 	if err != nil { | ||||
| 		tokenErrorf(secret, "has invalid value for key %s: %v.", bootstrapapi.BootstrapTokenExtraGroupsKey, err) | ||||
| 		return nil, false, nil | ||||
| 	} | ||||
|  | ||||
| 	return &user.DefaultInfo{ | ||||
| 		Name:   bootstrapapi.BootstrapUserPrefix + string(id), | ||||
| 		Groups: []string{bootstrapapi.BootstrapDefaultGroup}, | ||||
| 		Groups: groups, | ||||
| 	}, true, nil | ||||
| } | ||||
|  | ||||
| @@ -184,3 +193,28 @@ func parseToken(s string) (string, string, error) { | ||||
| 	} | ||||
| 	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 *api.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 := bootstrapapi.ValidateBootstrapGroupName(group); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		groups.Insert(group) | ||||
| 	} | ||||
|  | ||||
| 	// return the result as a deduplicated, sorted list | ||||
| 	return groups.List(), nil | ||||
| } | ||||
|   | ||||
| @@ -84,6 +84,47 @@ func TestTokenAuthenticator(t *testing.T) { | ||||
| 				Groups: []string{"system:bootstrappers"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "valid token with extra group", | ||||
| 			secrets: []*api.Secret{ | ||||
| 				{ | ||||
| 					ObjectMeta: metav1.ObjectMeta{ | ||||
| 						Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID, | ||||
| 					}, | ||||
| 					Data: map[string][]byte{ | ||||
| 						bootstrapapi.BootstrapTokenIDKey:               []byte(tokenID), | ||||
| 						bootstrapapi.BootstrapTokenSecretKey:           []byte(tokenSecret), | ||||
| 						bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"), | ||||
| 						bootstrapapi.BootstrapTokenExtraGroupsKey:      []byte("system:bootstrappers:foo"), | ||||
| 					}, | ||||
| 					Type: "bootstrap.kubernetes.io/token", | ||||
| 				}, | ||||
| 			}, | ||||
| 			token: tokenID + "." + tokenSecret, | ||||
| 			wantUser: &user.DefaultInfo{ | ||||
| 				Name:   "system:bootstrap:" + tokenID, | ||||
| 				Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "invalid group", | ||||
| 			secrets: []*api.Secret{ | ||||
| 				{ | ||||
| 					ObjectMeta: metav1.ObjectMeta{ | ||||
| 						Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID, | ||||
| 					}, | ||||
| 					Data: map[string][]byte{ | ||||
| 						bootstrapapi.BootstrapTokenIDKey:               []byte(tokenID), | ||||
| 						bootstrapapi.BootstrapTokenSecretKey:           []byte(tokenSecret), | ||||
| 						bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"), | ||||
| 						bootstrapapi.BootstrapTokenExtraGroupsKey:      []byte("foo"), | ||||
| 					}, | ||||
| 					Type: "bootstrap.kubernetes.io/token", | ||||
| 				}, | ||||
| 			}, | ||||
| 			token:        tokenID + "." + tokenSecret, | ||||
| 			wantNotFound: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "invalid secret name", | ||||
| 			secrets: []*api.Secret{ | ||||
| @@ -247,3 +288,72 @@ func TestTokenAuthenticator(t *testing.T) { | ||||
| 		}() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGetGroups(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name         string | ||||
| 		secret       *api.Secret | ||||
| 		expectResult []string | ||||
| 		expectError  bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "not set", | ||||
| 			secret: &api.Secret{ | ||||
| 				ObjectMeta: metav1.ObjectMeta{Name: "test"}, | ||||
| 				Data:       map[string][]byte{}, | ||||
| 			}, | ||||
| 			expectResult: []string{"system:bootstrappers"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "set to empty value", | ||||
| 			secret: &api.Secret{ | ||||
| 				ObjectMeta: metav1.ObjectMeta{Name: "test"}, | ||||
| 				Data: map[string][]byte{ | ||||
| 					bootstrapapi.BootstrapTokenExtraGroupsKey: []byte(""), | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectResult: []string{"system:bootstrappers"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "invalid prefix", | ||||
| 			secret: &api.Secret{ | ||||
| 				ObjectMeta: metav1.ObjectMeta{Name: "test"}, | ||||
| 				Data: map[string][]byte{ | ||||
| 					bootstrapapi.BootstrapTokenExtraGroupsKey: []byte("foo"), | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectError: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "valid", | ||||
| 			secret: &api.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) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Matt Moyer
					Matt Moyer