Skip to content

Commit 7dc9b7c

Browse files
committed
auth: impersonation between provider identities
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
1 parent 69cc5b5 commit 7dc9b7c

28 files changed

Lines changed: 1278 additions & 432 deletions

auth/access_token.go

Lines changed: 52 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ import (
2222
"strings"
2323

2424
authnv1 "k8s.io/api/authentication/v1"
25-
corev1 "k8s.io/api/core/v1"
26-
"k8s.io/apimachinery/pkg/api/errors"
27-
"sigs.k8s.io/controller-runtime/pkg/client"
2825

2926
"github.com/fluxcd/pkg/cache"
3027
)
@@ -45,49 +42,57 @@ func GetAccessToken(ctx context.Context, provider Provider, opts ...Option) (Tok
4542
}
4643

4744
// Update access token fetcher for a service account if specified.
48-
var serviceAccount *corev1.ServiceAccount
49-
var providerIdentity string
50-
var audiences []string
51-
if o.ShouldGetServiceAccountToken() {
45+
var saInfo *serviceAccountInfo
46+
if o.ShouldGetServiceAccount() {
47+
// Check the feature gate for object-level workload identity.
48+
if !IsObjectLevelWorkloadIdentityEnabled() {
49+
return nil, ErrObjectLevelWorkloadIdentityNotEnabled
50+
}
51+
5252
// Fetch service account details.
5353
var err error
54-
saRef := client.ObjectKey{
55-
Name: o.ServiceAccountName,
56-
Namespace: o.ServiceAccountNamespace,
57-
}
58-
serviceAccount, audiences, providerIdentity, err =
59-
getServiceAccountAndProviderInfo(ctx, provider, o.Client, saRef, opts...)
54+
saInfo, err = getServiceAccountInfo(ctx, provider, o.Client, opts...)
6055
if err != nil {
6156
return nil, err
6257
}
6358

6459
// Update the function to create an access token using the service account.
65-
newAccessToken = func() (Token, error) {
66-
// Check the feature gate for object-level workload identity.
67-
if !IsObjectLevelWorkloadIdentityEnabled() {
68-
return nil, ErrObjectLevelWorkloadIdentityNotEnabled
60+
if saInfo.useServiceAccount {
61+
newAccessToken = func() (Token, error) {
62+
// Issue Kubernetes OIDC token for the service account.
63+
tokenReq := &authnv1.TokenRequest{
64+
Spec: authnv1.TokenRequestSpec{
65+
Audiences: saInfo.audiences,
66+
},
67+
}
68+
if err := o.Client.SubResource("token").Create(ctx, saInfo.obj, tokenReq); err != nil {
69+
return nil, fmt.Errorf("failed to create kubernetes token for service account '%s/%s': %w",
70+
saInfo.obj.Namespace, saInfo.obj.Name, err)
71+
}
72+
oidcToken := tokenReq.Status.Token
73+
74+
// Exchange the Kubernetes OIDC token for a provider access token.
75+
token, err := provider.NewTokenForServiceAccount(ctx, oidcToken, *saInfo.obj, opts...)
76+
if err != nil {
77+
return nil, fmt.Errorf("failed to create provider access token for service account '%s/%s': %w",
78+
saInfo.obj.Namespace, saInfo.obj.Name, err)
79+
}
80+
81+
return token, nil
6982
}
83+
}
84+
}
7085

71-
// Issue Kubernetes OIDC token for the service account.
72-
tokenReq := &authnv1.TokenRequest{
73-
Spec: authnv1.TokenRequestSpec{
74-
Audiences: audiences,
75-
},
76-
}
77-
if err := o.Client.SubResource("token").Create(ctx, serviceAccount, tokenReq); err != nil {
78-
return nil, fmt.Errorf("failed to create kubernetes token for service account '%s/%s': %w",
79-
serviceAccount.Namespace, serviceAccount.Name, err)
80-
}
81-
oidcToken := tokenReq.Status.Token
82-
83-
// Exchange the Kubernetes OIDC token for a provider access token.
84-
token, err := provider.NewTokenForServiceAccount(ctx, oidcToken, *serviceAccount, opts...)
86+
// Update access token fetcher for impersonation if supported by the provider.
87+
if saInfo != nil && saInfo.providerIdentityForImpersonation != nil {
88+
newNonImpersonatedToken := newAccessToken
89+
newAccessToken = func() (Token, error) {
90+
token, err := newNonImpersonatedToken()
8591
if err != nil {
86-
return nil, fmt.Errorf("failed to create provider access token for service account '%s/%s': %w",
87-
serviceAccount.Namespace, serviceAccount.Name, err)
92+
return nil, err
8893
}
89-
90-
return token, nil
94+
p := provider.(ProviderWithImpersonation)
95+
return p.NewTokenForIdentity(ctx, token, saInfo.providerIdentityForImpersonation, opts...)
9196
}
9297
}
9398

@@ -97,7 +102,7 @@ func GetAccessToken(ctx context.Context, provider Provider, opts ...Option) (Tok
97102
}
98103

99104
// Build cache key.
100-
cacheKey := buildAccessTokenCacheKey(provider, audiences, providerIdentity, serviceAccount, opts...)
105+
cacheKey := buildAccessTokenCacheKey(provider, saInfo, opts...)
101106

102107
// Build involved object details.
103108
kind := o.InvolvedObject.Kind
@@ -116,55 +121,7 @@ func GetAccessToken(ctx context.Context, provider Provider, opts ...Option) (Tok
116121
return token, nil
117122
}
118123

119-
func getServiceAccountAndProviderInfo(ctx context.Context, provider Provider, client client.Client,
120-
key client.ObjectKey, opts ...Option) (*corev1.ServiceAccount, []string, string, error) {
121-
122-
var o Options
123-
o.Apply(opts...)
124-
125-
defaultSA := getDefaultServiceAccount()
126-
var setDefaultSA bool
127-
128-
// Apply multi-tenancy lockdown: use default service account when .serviceAccountName
129-
// is not explicitly specified in the object. This results in Object-Level Workload Identity.
130-
if key.Name == "" && defaultSA != "" {
131-
key.Name = defaultSA
132-
setDefaultSA = true
133-
}
134-
135-
// Get service account.
136-
var serviceAccount corev1.ServiceAccount
137-
if err := client.Get(ctx, key, &serviceAccount); err != nil {
138-
if errors.IsNotFound(err) && setDefaultSA {
139-
return nil, nil, "", fmt.Errorf("failed to get service account '%s': %w",
140-
key, ErrDefaultServiceAccountNotFound)
141-
}
142-
return nil, nil, "", fmt.Errorf("failed to get service account '%s': %w",
143-
key, err)
144-
}
145-
146-
// Get provider audience.
147-
audiences := o.Audiences
148-
if len(audiences) == 0 {
149-
var err error
150-
audiences, err = provider.GetAudiences(ctx, serviceAccount)
151-
if err != nil {
152-
return nil, nil, "", fmt.Errorf("failed to get provider audience: %w", err)
153-
}
154-
}
155-
156-
// Get provider identity.
157-
providerIdentity, err := provider.GetIdentity(serviceAccount)
158-
if err != nil {
159-
return nil, nil, "", fmt.Errorf("failed to get provider identity from service account '%s/%s' annotations: %w",
160-
key.Namespace, key.Name, err)
161-
}
162-
163-
return &serviceAccount, audiences, providerIdentity, nil
164-
}
165-
166-
func buildAccessTokenCacheKey(provider Provider, audiences []string, providerIdentity string,
167-
serviceAccount *corev1.ServiceAccount, opts ...Option) string {
124+
func buildAccessTokenCacheKey(provider Provider, saInfo *serviceAccountInfo, opts ...Option) string {
168125

169126
var o Options
170127
o.Apply(opts...)
@@ -173,11 +130,16 @@ func buildAccessTokenCacheKey(provider Provider, audiences []string, providerIde
173130

174131
parts = append(parts, fmt.Sprintf("provider=%s", provider.GetName()))
175132

176-
if serviceAccount != nil {
177-
parts = append(parts, fmt.Sprintf("providerIdentity=%s", providerIdentity))
178-
parts = append(parts, fmt.Sprintf("serviceAccountName=%s", serviceAccount.Name))
179-
parts = append(parts, fmt.Sprintf("serviceAccountNamespace=%s", serviceAccount.Namespace))
180-
parts = append(parts, fmt.Sprintf("serviceAccountTokenAudiences=%s", strings.Join(audiences, ",")))
133+
if saInfo != nil {
134+
if saInfo.useServiceAccount {
135+
parts = append(parts, fmt.Sprintf("serviceAccountName=%s", saInfo.obj.Name))
136+
parts = append(parts, fmt.Sprintf("serviceAccountNamespace=%s", saInfo.obj.Namespace))
137+
parts = append(parts, fmt.Sprintf("serviceAccountTokenAudiences=%s", strings.Join(saInfo.audiences, ",")))
138+
parts = append(parts, fmt.Sprintf("providerIdentity=%s", saInfo.providerIdentity))
139+
}
140+
if saInfo.providerIdentityForImpersonation != nil {
141+
parts = append(parts, fmt.Sprintf("providerIdentityForImpersonation=%s", saInfo.providerIdentityForImpersonation))
142+
}
181143
}
182144

183145
if len(o.Scopes) > 0 {

0 commit comments

Comments
 (0)