Skip to content

Commit

Permalink
implement token authenticator for new id tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
mikedanese committed Feb 28, 2018
1 parent 1fbf8b8 commit 024f57a
Show file tree
Hide file tree
Showing 14 changed files with 343 additions and 40 deletions.
36 changes: 26 additions & 10 deletions cmd/kube-apiserver/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,18 +326,25 @@ func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunnele
}

var issuer serviceaccount.TokenGenerator
if s.ServiceAccountSigningKeyFile != "" || s.Authentication.ServiceAccounts.Issuer != "" {
var apiAudiences []string
if s.ServiceAccountSigningKeyFile != "" ||
s.Authentication.ServiceAccounts.Issuer != "" ||
len(s.Authentication.ServiceAccounts.APIAudiences) > 0 {
if !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) {
return nil, nil, nil, nil, nil, fmt.Errorf("the TokenRequest feature is not enabled but --service-account-signing-key-file and/or --service-account-issuer-id flags were passed")
}
if s.ServiceAccountSigningKeyFile == "" || s.Authentication.ServiceAccounts.Issuer == "" {
return nil, nil, nil, nil, nil, fmt.Errorf("service-account-signing-key-file and service-account-issuer should be specified together")
if s.ServiceAccountSigningKeyFile == "" ||
s.Authentication.ServiceAccounts.Issuer == "" ||
len(s.Authentication.ServiceAccounts.APIAudiences) == 0 ||
len(s.Authentication.ServiceAccounts.KeyFiles) == 0 {
return nil, nil, nil, nil, nil, fmt.Errorf("service-account-signing-key-file, service-account-issuer, service-account-api-audiences and service-account-key-file should be specified together")
}
sk, err := certutil.PrivateKeyFromFile(s.ServiceAccountSigningKeyFile)
if err != nil {
return nil, nil, nil, nil, nil, fmt.Errorf("failed to parse service-account-issuer-key-file: %v", err)
}
issuer = serviceaccount.JWTTokenGenerator(s.Authentication.ServiceAccounts.Issuer, sk)
apiAudiences = s.Authentication.ServiceAccounts.APIAudiences
}

config := &master.Config{
Expand Down Expand Up @@ -371,7 +378,9 @@ func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunnele

EndpointReconcilerType: reconcilers.Type(s.EndpointReconcilerType),
MasterCount: s.MasterCount,
ServiceAccountIssuer: issuer,

ServiceAccountIssuer: issuer,
ServiceAccountAPIAudiences: apiAudiences,
},
}

Expand Down Expand Up @@ -672,12 +681,19 @@ func defaultOptions(s *options.ServerRunOptions) error {

s.Authentication.ApplyAuthorization(s.Authorization)

// Default to the private server key for service account token signing
if len(s.Authentication.ServiceAccounts.KeyFiles) == 0 && s.SecureServing.ServerCert.CertKey.KeyFile != "" {
if kubeauthenticator.IsValidServiceAccountKeyFile(s.SecureServing.ServerCert.CertKey.KeyFile) {
s.Authentication.ServiceAccounts.KeyFiles = []string{s.SecureServing.ServerCert.CertKey.KeyFile}
} else {
glog.Warning("No TLS key provided, service account token authentication disabled")
// Use (ServiceAccountSigningKeyFile != "") as a proxy to the user enabling
// TokenRequest functionality. This defaulting was convenient, but messed up
// a lot of people when they rotated their serving cert with no idea it was
// connected to their service account keys. We are taking this oppurtunity to
// remove this problematic defaulting.
if s.ServiceAccountSigningKeyFile == "" {
// Default to the private server key for service account token signing
if len(s.Authentication.ServiceAccounts.KeyFiles) == 0 && s.SecureServing.ServerCert.CertKey.KeyFile != "" {
if kubeauthenticator.IsValidServiceAccountKeyFile(s.SecureServing.ServerCert.CertKey.KeyFile) {
s.Authentication.ServiceAccounts.KeyFiles = []string{s.SecureServing.ServerCert.CertKey.KeyFile}
} else {
glog.Warning("No TLS key provided, service account token authentication disabled")
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/controller/serviceaccount/tokengetter.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,15 @@ type clientGetter struct {
func NewGetterFromClient(c clientset.Interface) serviceaccount.ServiceAccountTokenGetter {
return clientGetter{c}
}

func (c clientGetter) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) {
return c.client.CoreV1().ServiceAccounts(namespace).Get(name, metav1.GetOptions{})
}

func (c clientGetter) GetPod(namespace, name string) (*v1.Pod, error) {
return c.client.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{})
}

func (c clientGetter) GetSecret(namespace, name string) (*v1.Secret, error) {
return c.client.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
}
2 changes: 2 additions & 0 deletions pkg/kubeapiserver/authenticator/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ go_library(
srcs = ["config.go"],
importpath = "k8s.io/kubernetes/pkg/kubeapiserver/authenticator",
deps = [
"//pkg/features:go_default_library",
"//pkg/serviceaccount:go_default_library",
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
Expand All @@ -24,6 +25,7 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/authentication/token/cache:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/token/tokenfile:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/token/union:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc:go_default_library",
Expand Down
32 changes: 29 additions & 3 deletions pkg/kubeapiserver/authenticator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ import (
tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
"k8s.io/apiserver/pkg/authentication/token/tokenfile"
tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile"
"k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth"
"k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
certutil "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/serviceaccount"

_ "k8s.io/client-go/plugin/pkg/client/auth"
Expand All @@ -59,6 +61,8 @@ type AuthenticatorConfig struct {
OIDCSigningAlgs []string
ServiceAccountKeyFiles []string
ServiceAccountLookup bool
ServiceAccountIssuer string
ServiceAccountAPIAudiences []string
WebhookTokenAuthnConfigFile string
WebhookTokenAuthnCacheTTL time.Duration

Expand Down Expand Up @@ -123,7 +127,14 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
tokenAuthenticators = append(tokenAuthenticators, tokenAuth)
}
if len(config.ServiceAccountKeyFiles) > 0 {
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
if err != nil {
return nil, nil, err
}
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
}
if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" {
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountAPIAudiences, config.ServiceAccountKeyFiles, config.ServiceAccountTokenGetter)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -267,8 +278,8 @@ func newAuthenticatorFromOIDCIssuerURL(issuerURL, clientID, caFile, usernameClai
return tokenAuthenticator, nil
}

// newServiceAccountAuthenticator returns an authenticator.Token or an error
func newServiceAccountAuthenticator(keyfiles []string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
// newLegacyServiceAccountAuthenticator returns an authenticator.Token or an error
func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
allPublicKeys := []interface{}{}
for _, keyfile := range keyfiles {
publicKeys, err := certutil.PublicKeysFromFile(keyfile)
Expand All @@ -282,6 +293,21 @@ func newServiceAccountAuthenticator(keyfiles []string, lookup bool, serviceAccou
return tokenAuthenticator, nil
}

// newServiceAccountAuthenticator returns an authenticator.Token or an error
func newServiceAccountAuthenticator(iss string, audiences []string, keyfiles []string, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
allPublicKeys := []interface{}{}
for _, keyfile := range keyfiles {
publicKeys, err := certutil.PublicKeysFromFile(keyfile)
if err != nil {
return nil, err
}
allPublicKeys = append(allPublicKeys, publicKeys...)
}

tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(iss, allPublicKeys, serviceaccount.NewValidator(audiences, serviceAccountGetter))
return tokenAuthenticator, nil
}

// newAuthenticatorFromClientCAFile returns an authenticator.Request or an error
func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) {
roots, err := certutil.NewPool(clientCAFile)
Expand Down
19 changes: 14 additions & 5 deletions pkg/kubeapiserver/options/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ type PasswordFileAuthenticationOptions struct {
}

type ServiceAccountAuthenticationOptions struct {
KeyFiles []string
Lookup bool
Issuer string
KeyFiles []string
Lookup bool
Issuer string
APIAudiences []string
}

type TokenFileAuthenticationOptions struct {
Expand Down Expand Up @@ -236,15 +237,21 @@ func (s *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
if s.ServiceAccounts != nil {
fs.StringArrayVar(&s.ServiceAccounts.KeyFiles, "service-account-key-file", s.ServiceAccounts.KeyFiles, ""+
"File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify "+
"ServiceAccount tokens. If unspecified, --tls-private-key-file is used. "+
"The specified file can contain multiple keys, and the flag can be specified multiple times with different files.")
"ServiceAccount tokens. The specified file can contain multiple keys, and the flag can "+
"be specified multiple times with different files. If unspecified, "+
"--tls-private-key-file is used. Must be specified when "+
"--service-account-signing-key is provided")

fs.BoolVar(&s.ServiceAccounts.Lookup, "service-account-lookup", s.ServiceAccounts.Lookup,
"If true, validate ServiceAccount tokens exist in etcd as part of authentication.")

fs.StringVar(&s.ServiceAccounts.Issuer, "service-account-issuer", s.ServiceAccounts.Issuer, ""+
"Identifier of the service account token issuer. The issuer will assert this identifier "+
"in \"iss\" claim of issued tokens. This value is a string or URI.")

fs.StringSliceVar(&s.ServiceAccounts.APIAudiences, "service-account-api-audiences", s.ServiceAccounts.APIAudiences, ""+
"Identifiers of the API. The service account token authenticator will validate that "+
"tokens used against the API are bound to at least one of these audiences.")
}

if s.TokenFile != nil {
Expand Down Expand Up @@ -303,6 +310,8 @@ func (s *BuiltInAuthenticationOptions) ToAuthenticationConfig() authenticator.Au
if s.ServiceAccounts != nil {
ret.ServiceAccountKeyFiles = s.ServiceAccounts.KeyFiles
ret.ServiceAccountLookup = s.ServiceAccounts.Lookup
ret.ServiceAccountIssuer = s.ServiceAccounts.Issuer
ret.ServiceAccountAPIAudiences = s.ServiceAccounts.APIAudiences
}

if s.TokenFile != nil {
Expand Down
20 changes: 11 additions & 9 deletions pkg/master/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ type ExtraConfig struct {
// Selects which reconciler to use
EndpointReconcilerType reconcilers.Type

ServiceAccountIssuer serviceaccount.TokenGenerator
ServiceAccountIssuer serviceaccount.TokenGenerator
ServiceAccountAPIAudiences []string
}

type Config struct {
Expand Down Expand Up @@ -314,14 +315,15 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
// install legacy rest storage
if c.ExtraConfig.APIResourceConfigSource.VersionEnabled(apiv1.SchemeGroupVersion) {
legacyRESTStorageProvider := corerest.LegacyRESTStorageProvider{
StorageFactory: c.ExtraConfig.StorageFactory,
ProxyTransport: c.ExtraConfig.ProxyTransport,
KubeletClientConfig: c.ExtraConfig.KubeletClientConfig,
EventTTL: c.ExtraConfig.EventTTL,
ServiceIPRange: c.ExtraConfig.ServiceIPRange,
ServiceNodePortRange: c.ExtraConfig.ServiceNodePortRange,
LoopbackClientConfig: c.GenericConfig.LoopbackClientConfig,
ServiceAccountIssuer: c.ExtraConfig.ServiceAccountIssuer,
StorageFactory: c.ExtraConfig.StorageFactory,
ProxyTransport: c.ExtraConfig.ProxyTransport,
KubeletClientConfig: c.ExtraConfig.KubeletClientConfig,
EventTTL: c.ExtraConfig.EventTTL,
ServiceIPRange: c.ExtraConfig.ServiceIPRange,
ServiceNodePortRange: c.ExtraConfig.ServiceNodePortRange,
LoopbackClientConfig: c.GenericConfig.LoopbackClientConfig,
ServiceAccountIssuer: c.ExtraConfig.ServiceAccountIssuer,
ServiceAccountAPIAudiences: c.ExtraConfig.ServiceAccountAPIAudiences,
}
m.InstallLegacyAPI(&c, c.GenericConfig.RESTOptionsGetter, legacyRESTStorageProvider)
}
Expand Down
7 changes: 4 additions & 3 deletions pkg/registry/core/rest/storage_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ type LegacyRESTStorageProvider struct {
ServiceIPRange net.IPNet
ServiceNodePortRange utilnet.PortRange

ServiceAccountIssuer serviceaccount.TokenGenerator
ServiceAccountIssuer serviceaccount.TokenGenerator
ServiceAccountAPIAudiences []string

LoopbackClientConfig *restclient.Config
}
Expand Down Expand Up @@ -140,9 +141,9 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi

var serviceAccountStorage *serviceaccountstore.REST
if c.ServiceAccountIssuer != nil && utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) {
serviceAccountStorage = serviceaccountstore.NewREST(restOptionsGetter, c.ServiceAccountIssuer, podStorage.Pod.Store, secretStorage.Store)
serviceAccountStorage = serviceaccountstore.NewREST(restOptionsGetter, c.ServiceAccountIssuer, c.ServiceAccountAPIAudiences, podStorage.Pod.Store, secretStorage.Store)
} else {
serviceAccountStorage = serviceaccountstore.NewREST(restOptionsGetter, nil, nil, nil)
serviceAccountStorage = serviceaccountstore.NewREST(restOptionsGetter, nil, nil, nil, nil)
}

serviceRESTStorage, serviceStatusStorage := servicestore.NewGenericREST(restOptionsGetter)
Expand Down
5 changes: 3 additions & 2 deletions pkg/registry/core/serviceaccount/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type REST struct {
}

// NewREST returns a RESTStorage object that will work against service accounts.
func NewREST(optsGetter generic.RESTOptionsGetter, issuer token.TokenGenerator, podStorage, secretStorage *genericregistry.Store) *REST {
func NewREST(optsGetter generic.RESTOptionsGetter, issuer token.TokenGenerator, auds []string, podStorage, secretStorage *genericregistry.Store) *REST {
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &api.ServiceAccount{} },
NewListFunc: func() runtime.Object { return &api.ServiceAccountList{} },
Expand All @@ -52,9 +52,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter, issuer token.TokenGenerator,
if issuer != nil && podStorage != nil && secretStorage != nil {
trest = &TokenREST{
svcaccts: store,
issuer: issuer,
pods: podStorage,
secrets: secretStorage,
issuer: issuer,
auds: auds,
}
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/registry/core/serviceaccount/storage/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
DeleteCollectionWorkers: 1,
ResourcePrefix: "serviceaccounts",
}
return NewREST(restOptions, nil, nil, nil), server
return NewREST(restOptions, nil, nil, nil, nil), server
}

func validNewServiceAccount(name string) *api.ServiceAccount {
Expand Down
4 changes: 4 additions & 0 deletions pkg/registry/core/serviceaccount/storage/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type TokenREST struct {
pods getter
secrets getter
issuer token.TokenGenerator
auds []string
}

var _ = rest.NamedCreater(&TokenREST{})
Expand Down Expand Up @@ -92,6 +93,9 @@ func (r *TokenREST) Create(ctx genericapirequest.Context, name string, obj runti
return nil, errors.NewConflict(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, ref.Name, fmt.Errorf("the UID in the bound object reference (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", ref.UID, uid))
}
}
if len(out.Spec.Audiences) == 0 {
out.Spec.Audiences = r.auds
}
sc, pc := token.Claims(*svcacct, pod, secret, out.Spec.ExpirationSeconds, out.Spec.Audiences)
tokdata, err := r.issuer.GenerateToken(sc, pc)
if err != nil {
Expand Down
Loading

0 comments on commit 024f57a

Please sign in to comment.