Skip to content

Commit bb7cb58

Browse files
authored
Merge pull request #906 from fluxcd/auth-core
[RFC-0010] Add core auth library
2 parents d18e808 + 1c55ceb commit bb7cb58

File tree

14 files changed

+943
-22
lines changed

14 files changed

+943
-22
lines changed

auth/doc.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 The Flux authors
2+
Copyright 2025 The Flux authors
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -14,6 +14,5 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
// Package auth is a Go package for OIDC-based authentication against Git SaaS providers.
18-
// Includes support for Azure DevOps and GitHub Apps.
17+
// auth is a package for handling secret-less authentication with cloud providers.
1918
package auth

auth/get_token.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
Copyright 2025 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package auth
18+
19+
import (
20+
"context"
21+
"crypto/sha256"
22+
"fmt"
23+
"strings"
24+
25+
authnv1 "k8s.io/api/authentication/v1"
26+
corev1 "k8s.io/api/core/v1"
27+
"sigs.k8s.io/controller-runtime/pkg/client"
28+
29+
"github.com/fluxcd/pkg/cache"
30+
)
31+
32+
// GetToken returns an access token for accessing resources in the given cloud provider.
33+
func GetToken(ctx context.Context, provider Provider, opts ...Option) (Token, error) {
34+
35+
var o Options
36+
o.Apply(opts...)
37+
38+
// Initialize default token fetcher.
39+
newAccessToken := func() (Token, error) {
40+
token, err := provider.NewDefaultToken(ctx, opts...)
41+
if err != nil {
42+
return nil, fmt.Errorf("failed to create default access token: %w", err)
43+
}
44+
return token, nil
45+
}
46+
47+
// Initialize service account token fetcher if service account is specified.
48+
var providerIdentity string
49+
var serviceAccountP *corev1.ServiceAccount
50+
if o.ServiceAccount != nil {
51+
// Get service account and prepare a function to create a token for it.
52+
var serviceAccount corev1.ServiceAccount
53+
if err := o.Client.Get(ctx, *o.ServiceAccount, &serviceAccount); err != nil {
54+
return nil, fmt.Errorf("failed to get service account: %w", err)
55+
}
56+
serviceAccountP = &serviceAccount
57+
58+
// Get provider audience.
59+
var err error
60+
providerAudience, err := provider.GetAudience(ctx)
61+
if err != nil {
62+
return nil, fmt.Errorf("failed to get provider audience: %w", err)
63+
}
64+
65+
// Get provider identity.
66+
providerIdentity, err = provider.GetIdentity(serviceAccount)
67+
if err != nil {
68+
return nil, fmt.Errorf("failed to get provider identity from service account '%s/%s' annotations: %w",
69+
serviceAccount.Namespace, serviceAccount.Name, err)
70+
}
71+
72+
// Initialize access token fetcher that will use the identity token.
73+
newAccessToken = func() (Token, error) {
74+
identityToken, err := newServiceAccountToken(ctx, o.Client, serviceAccount, providerAudience)
75+
if err != nil {
76+
return nil, err
77+
}
78+
79+
token, err := provider.NewTokenForServiceAccount(ctx, identityToken, serviceAccount, opts...)
80+
if err != nil {
81+
return nil, fmt.Errorf("failed to create access token: %w", err)
82+
}
83+
84+
return token, nil
85+
}
86+
}
87+
88+
// Initialize registry token fetcher if artifact repository is specified.
89+
newToken := newAccessToken
90+
if o.ArtifactRepository != "" {
91+
newToken = func() (Token, error) {
92+
accessToken, err := newAccessToken()
93+
if err != nil {
94+
return nil, err
95+
}
96+
97+
token, err := provider.NewArtifactRegistryToken(ctx, o.ArtifactRepository, accessToken, opts...)
98+
if err != nil {
99+
return nil, fmt.Errorf("failed to create artifact registry login: %w", err)
100+
}
101+
102+
return token, nil
103+
}
104+
}
105+
106+
// Bail out early if cache is disabled.
107+
if o.Cache == nil {
108+
return newToken()
109+
}
110+
111+
// Build cache key.
112+
cacheKey := buildCacheKey(provider, providerIdentity, serviceAccountP, opts...)
113+
114+
// Get involved object details.
115+
kind := o.InvolvedObject.Kind
116+
name := o.InvolvedObject.Name
117+
namespace := o.InvolvedObject.Namespace
118+
119+
// Get token from cache.
120+
token, _, err := o.Cache.GetOrSet(ctx, cacheKey, func(ctx context.Context) (cache.Token, error) {
121+
return newToken()
122+
}, cache.WithInvolvedObject(kind, name, namespace))
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
return token, nil
128+
}
129+
130+
func newServiceAccountToken(ctx context.Context, client client.Client,
131+
serviceAccount corev1.ServiceAccount, providerAudience string) (string, error) {
132+
tokenReq := &authnv1.TokenRequest{
133+
Spec: authnv1.TokenRequestSpec{
134+
Audiences: []string{providerAudience},
135+
},
136+
}
137+
if err := client.SubResource("token").Create(ctx, &serviceAccount, tokenReq); err != nil {
138+
return "", fmt.Errorf("failed to create kubernetes service account token: %w", err)
139+
}
140+
return tokenReq.Status.Token, nil
141+
}
142+
143+
func buildCacheKey(provider Provider, providerIdentity string,
144+
serviceAccount *corev1.ServiceAccount, opts ...Option) string {
145+
146+
var o Options
147+
o.Apply(opts...)
148+
149+
var keyParts []string
150+
151+
keyParts = append(keyParts, fmt.Sprintf("provider=%s", provider.GetName()))
152+
153+
if serviceAccount != nil {
154+
keyParts = append(keyParts, fmt.Sprintf("providerIdentity=%s", providerIdentity))
155+
keyParts = append(keyParts, fmt.Sprintf("serviceAccountName=%s", serviceAccount.Name))
156+
keyParts = append(keyParts, fmt.Sprintf("serviceAccountNamespace=%s", serviceAccount.Namespace))
157+
}
158+
159+
if len(o.Scopes) > 0 {
160+
keyParts = append(keyParts, fmt.Sprintf("scopes=%s", strings.Join(o.Scopes, ",")))
161+
}
162+
163+
if o.ArtifactRepository != "" {
164+
keyParts = append(keyParts, fmt.Sprintf("artifactRepositoryKey=%s", provider.GetArtifactCacheKey(o.ArtifactRepository)))
165+
}
166+
167+
if o.STSEndpoint != "" {
168+
keyParts = append(keyParts, fmt.Sprintf("stsEndpoint=%s", o.STSEndpoint))
169+
}
170+
171+
if o.ProxyURL != nil {
172+
keyParts = append(keyParts, fmt.Sprintf("proxyURL=%s", o.ProxyURL.String()))
173+
}
174+
175+
s := strings.Join(keyParts, ",")
176+
hash := sha256.Sum256([]byte(s))
177+
return fmt.Sprintf("%x", hash)
178+
}

0 commit comments

Comments
 (0)