Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pkg/kubeapiserver/authenticator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func (config Config) New(serverLifecycle context.Context) (authenticator.Request
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
}
if len(config.ServiceAccountIssuers) > 0 && config.ServiceAccountPublicKeysGetter != nil {
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuers, config.ServiceAccountPublicKeysGetter, config.APIAudiences, config.ServiceAccountTokenGetter)
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuers, config.ServiceAccountPublicKeysGetter, config.APIAudiences, config.ServiceAccountTokenGetter, config.ServiceAccountLookup)
if err != nil {
return nil, nil, nil, nil, err
}
Expand Down Expand Up @@ -351,11 +351,11 @@ func newLegacyServiceAccountAuthenticator(publicKeysGetter serviceaccount.Public
}

// newServiceAccountAuthenticator returns an authenticator.Token or an error
func newServiceAccountAuthenticator(issuers []string, publicKeysGetter serviceaccount.PublicKeysGetter, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenClusterGetter) (authenticator.Token, error) {
func newServiceAccountAuthenticator(issuers []string, publicKeysGetter serviceaccount.PublicKeysGetter, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter, lookup bool) (authenticator.Token, error) {
if publicKeysGetter == nil {
return nil, fmt.Errorf("no public key getter provided")
}
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(issuers, publicKeysGetter, apiAudiences, serviceaccount.NewValidator(serviceAccountGetter))
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(issuers, publicKeysGetter, apiAudiences, serviceaccount.NewValidator(lookup, serviceAccountGetter))
return tokenAuthenticator, nil
}

Expand Down
28 changes: 16 additions & 12 deletions pkg/serviceaccount/claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,16 @@ func Claims(sa core.ServiceAccount, pod *core.Pod, secret *core.Secret, node *co
return sc, pc, nil
}

func NewValidator(getter ServiceAccountTokenClusterGetter) Validator[privateClaims] {
func NewValidator(lookup bool, getter ServiceAccountTokenGetter) Validator[privateClaims] {
return &validator{
lookup: lookup,
getter: getter,
}
}

type validator struct {
getter ServiceAccountTokenClusterGetter
lookup bool
getter ServiceAccountTokenGetter
}

var _ = Validator[privateClaims](&validator{})
Expand Down Expand Up @@ -181,7 +183,8 @@ func (v *validator) Validate(ctx context.Context, _ string, public *jwt.Claims,
podref := private.Kubernetes.Pod
noderef := private.Kubernetes.Node
secref := private.Kubernetes.Secret
// Make sure service account still exists (name and UID)
if v.lookup {
// Make sure service account still exists (name and UID)
serviceAccount, err := v.getter.Cluster(clusterName).GetServiceAccount(namespace, saref.Name)
if err != nil {
klog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, saref.Name, err)
Expand All @@ -197,20 +200,21 @@ func (v *validator) Validate(ctx context.Context, _ string, public *jwt.Claims,
return nil, fmt.Errorf("service account %s/%s has been deleted", namespace, saref.Name)
}

if secref != nil {
if v.lookup && secref != nil {
// Make sure token hasn't been invalidated by deletion of the secret
secret, err := v.getter.Cluster(clusterName).GetSecret(namespace, secref.Name)
if err != nil {
klog.V(4).Infof("Could not retrieve bound secret %s/%s for service account %s/%s: %v", namespace, secref.Name, namespace, saref.Name, err)
return nil, errors.New("service account token has been invalidated")
klog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, saref.Name, err)
return nil, err
}
if secref.UID != string(secret.UID) {
klog.V(4).Infof("Secret UID no longer matches %s/%s: %q != %q", namespace, secref.Name, string(secret.UID), secref.UID)
return nil, fmt.Errorf("secret UID (%s) does not match service account secret ref claim (%s)", secret.UID, secref.UID)

if string(serviceAccount.UID) != saref.UID {
klog.V(4).Infof("Service account UID no longer matches %s/%s: %q != %q", namespace, saref.Name, string(serviceAccount.UID), saref.UID)
return nil, fmt.Errorf("service account UID (%s) does not match claim (%s)", serviceAccount.UID, saref.UID)
}
if secret.DeletionTimestamp != nil && secret.DeletionTimestamp.Time.Before(invalidIfDeletedBefore) {
klog.V(4).Infof("Bound secret is deleted and awaiting removal: %s/%s for service account %s/%s", namespace, secref.Name, namespace, saref.Name)
return nil, errors.New("service account token has been invalidated")
if serviceAccount.DeletionTimestamp != nil && serviceAccount.DeletionTimestamp.Time.Before(invalidIfDeletedBefore) {
klog.V(4).Infof("Service account has been deleted %s/%s", namespace, saref.Name)
return nil, fmt.Errorf("service account %s/%s has been deleted", namespace, saref.Name)
}
}

Expand Down
24 changes: 17 additions & 7 deletions pkg/serviceaccount/claims_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,12 +346,13 @@ type deletionTestCase struct {
}

type claimTestCase struct {
name string
getter ServiceAccountTokenGetter
private *privateClaims
expiry jwt.NumericDate
notBefore jwt.NumericDate
expectErr string
name string
getter ServiceAccountTokenGetter
private *privateClaims
disableLookup bool
expiry jwt.NumericDate
notBefore jwt.NumericDate
expectErr string
}

func TestValidatePrivateClaims(t *testing.T) {
Expand Down Expand Up @@ -455,6 +456,12 @@ func TestValidatePrivateClaims(t *testing.T) {
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Pod: &ref{Name: "podname", UID: "poduidold"}, Namespace: "ns"}},
expectErr: "pod UID (poduid) does not match service account pod ref claim (poduidold)",
},
{
name: "disabled lookup",
getter: fakeGetter{serviceAccount, nil, nil, nil},
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "not-here"}, Namespace: "ns"}},
disableLookup: true,
},
}

for _, deletionTestCase := range deletionTestCases {
Expand Down Expand Up @@ -505,7 +512,10 @@ func TestValidatePrivateClaims(t *testing.T) {

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
v := &validator{getter: tc.getter}
v := &validator{
lookup: !tc.disableLookup,
getter: tc.getter,
}
expiry := jwt.NumericDate(nowUnix)
if tc.expiry != 0 {
expiry = tc.expiry
Expand Down