Skip to content

Commit 69fd2d1

Browse files
authored
Merge pull request #928 from fluxcd/auth-audience-cache-key
[RFC-0010] Add provider audience to cache key and decouple packages
2 parents 7a72e48 + 82fd687 commit 69fd2d1

29 files changed

+150
-1598
lines changed

auth/gcp/gke_metadata_test.go

Lines changed: 13 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,51 +17,25 @@ limitations under the License.
1717
package gcp_test
1818

1919
import (
20-
"context"
21-
"errors"
2220
"fmt"
23-
"net"
2421
"net/http"
22+
"net/http/httptest"
23+
"strings"
2524
"testing"
26-
"time"
27-
28-
. "github.com/onsi/gomega"
2925
)
3026

3127
func startGKEMetadataServer(t *testing.T) {
3228
t.Helper()
33-
g := NewWithT(t)
34-
35-
lis, err := net.Listen("tcp", ":0")
36-
g.Expect(err).NotTo(HaveOccurred())
37-
38-
gkeMetadataServer := &http.Server{
39-
Addr: lis.Addr().String(),
40-
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
41-
switch r.URL.Path {
42-
case "/computeMetadata/v1/project/project-id":
43-
fmt.Fprintf(w, "%s", "project-id")
44-
case "/computeMetadata/v1/instance/attributes/cluster-location":
45-
fmt.Fprintf(w, "%s", "cluster-location")
46-
case "/computeMetadata/v1/instance/attributes/cluster-name":
47-
fmt.Fprintf(w, "%s", "cluster-name")
48-
}
49-
}),
50-
}
51-
52-
go func() {
53-
err := gkeMetadataServer.Serve(lis)
54-
if err != nil && !errors.Is(err, http.ErrServerClosed) {
55-
g.Expect(err).NotTo(HaveOccurred())
29+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
30+
switch r.URL.Path {
31+
case "/computeMetadata/v1/project/project-id":
32+
fmt.Fprintf(w, "%s", "project-id")
33+
case "/computeMetadata/v1/instance/attributes/cluster-location":
34+
fmt.Fprintf(w, "%s", "cluster-location")
35+
case "/computeMetadata/v1/instance/attributes/cluster-name":
36+
fmt.Fprintf(w, "%s", "cluster-name")
5637
}
57-
}()
58-
59-
t.Cleanup(func() {
60-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
61-
defer cancel()
62-
err := gkeMetadataServer.Shutdown(ctx)
63-
g.Expect(err).NotTo(HaveOccurred())
64-
})
65-
66-
t.Setenv("GCE_METADATA_HOST", lis.Addr().String())
38+
}))
39+
t.Cleanup(srv.Close)
40+
t.Setenv("GCE_METADATA_HOST", strings.TrimPrefix(srv.URL, "http://"))
6741
}

auth/gcp/options.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ func getServiceAccountEmail(serviceAccount corev1.ServiceAccount) (string, error
4040
return email, nil
4141
}
4242

43-
const workloadIdentityProviderPattern = `^https://iam\.googleapis\.com/projects/\d{1,30}/locations/global/workloadIdentityPools/[^/]{1,100}/providers/[^/]{1,100}$`
43+
const workloadIdentityProviderPattern = `^projects/\d{1,30}/locations/global/workloadIdentityPools/[^/]{1,100}/providers/[^/]{1,100}$`
4444

4545
var workloadIdentityProviderRegex = regexp.MustCompile(workloadIdentityProviderPattern)
4646

47-
func getWorkloadIdentityProvider(serviceAccount corev1.ServiceAccount) (string, error) {
47+
func getWorkloadIdentityProviderAudience(serviceAccount corev1.ServiceAccount) (string, error) {
4848
const key = "gcp.auth.fluxcd.io/workload-identity-provider"
4949
wip := serviceAccount.Annotations[key]
5050
if wip == "" {
@@ -54,5 +54,5 @@ func getWorkloadIdentityProvider(serviceAccount corev1.ServiceAccount) (string,
5454
return "", fmt.Errorf("invalid %s annotation: '%s'. must match %s",
5555
key, wip, workloadIdentityProviderPattern)
5656
}
57-
return wip, nil
57+
return fmt.Sprintf("https://iam.googleapis.com/%s", wip), nil
5858
}

auth/gcp/provider.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func (p Provider) NewControllerToken(ctx context.Context, opts ...auth.Option) (
7070
func (Provider) GetAudience(ctx context.Context, serviceAccount corev1.ServiceAccount) (string, error) {
7171
// Check if a workload identity provider is specified in the service account.
7272
// If so, the current cluster is not GKE and the audience is the provider itself.
73-
audience, err := getWorkloadIdentityProvider(serviceAccount)
73+
audience, err := getWorkloadIdentityProviderAudience(serviceAccount)
7474
if err != nil {
7575
return "", err
7676
}
@@ -100,7 +100,7 @@ func (p Provider) NewTokenForServiceAccount(ctx context.Context, oidcToken strin
100100

101101
// Check if a workload identity provider is specified in the service account.
102102
// If so, the current cluster is not GKE and the audience is the provider itself.
103-
audience, err := getWorkloadIdentityProvider(serviceAccount)
103+
audience, err := getWorkloadIdentityProviderAudience(serviceAccount)
104104
if err != nil {
105105
return nil, err
106106
}

auth/gcp/provider_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func TestProvider_NewTokenForServiceAccount(t *testing.T) {
109109
UniverseDomain: "googleapis.com",
110110
},
111111
annotations: map[string]string{
112-
"gcp.auth.fluxcd.io/workload-identity-provider": "https://iam.googleapis.com/projects/1234567890/locations/global/workloadIdentityPools/test-pool/providers/test-provider",
112+
"gcp.auth.fluxcd.io/workload-identity-provider": "projects/1234567890/locations/global/workloadIdentityPools/test-pool/providers/test-provider",
113113
},
114114
},
115115
{
@@ -128,7 +128,7 @@ func TestProvider_NewTokenForServiceAccount(t *testing.T) {
128128
},
129129
annotations: map[string]string{
130130
"iam.gke.io/gcp-service-account": "[email protected]",
131-
"gcp.auth.fluxcd.io/workload-identity-provider": "https://iam.googleapis.com/projects/1234567890/locations/global/workloadIdentityPools/test-pool/providers/test-provider",
131+
"gcp.auth.fluxcd.io/workload-identity-provider": "projects/1234567890/locations/global/workloadIdentityPools/test-pool/providers/test-provider",
132132
},
133133
},
134134
{
@@ -143,7 +143,7 @@ func TestProvider_NewTokenForServiceAccount(t *testing.T) {
143143
annotations: map[string]string{
144144
"gcp.auth.fluxcd.io/workload-identity-provider": "foobar",
145145
},
146-
err: `invalid gcp.auth.fluxcd.io/workload-identity-provider annotation: 'foobar'. must match ^https://iam\.googleapis\.com/projects/\d{1,30}/locations/global/workloadIdentityPools/[^/]{1,100}/providers/[^/]{1,100}$`,
146+
err: `invalid gcp.auth.fluxcd.io/workload-identity-provider annotation: 'foobar'. must match ^projects/\d{1,30}/locations/global/workloadIdentityPools/[^/]{1,100}/providers/[^/]{1,100}$`,
147147
},
148148
} {
149149
t.Run(tt.name, func(t *testing.T) {
@@ -195,7 +195,7 @@ func TestProvider_GetAudience(t *testing.T) {
195195
{
196196
name: "federation",
197197
annotations: map[string]string{
198-
"gcp.auth.fluxcd.io/workload-identity-provider": "https://iam.googleapis.com/projects/1234567890/locations/global/workloadIdentityPools/test-pool/providers/test-provider",
198+
"gcp.auth.fluxcd.io/workload-identity-provider": "projects/1234567890/locations/global/workloadIdentityPools/test-pool/providers/test-provider",
199199
},
200200
expected: "https://iam.googleapis.com/projects/1234567890/locations/global/workloadIdentityPools/test-pool/providers/test-provider",
201201
},

auth/get_token.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func GetToken(ctx context.Context, provider Provider, opts ...Option) (Token, er
4545
}
4646

4747
// Update access token fetcher for a service account if specified.
48+
var providerAudience string
4849
var providerIdentity string
4950
var serviceAccountP *corev1.ServiceAccount
5051
if o.ServiceAccount != nil {
@@ -63,7 +64,7 @@ func GetToken(ctx context.Context, provider Provider, opts ...Option) (Token, er
6364

6465
// Get provider audience.
6566
var err error
66-
providerAudience, err := provider.GetAudience(ctx, serviceAccount)
67+
providerAudience, err = provider.GetAudience(ctx, serviceAccount)
6768
if err != nil {
6869
return nil, fmt.Errorf("failed to get provider audience: %w", err)
6970
}
@@ -131,7 +132,8 @@ func GetToken(ctx context.Context, provider Provider, opts ...Option) (Token, er
131132
}
132133

133134
// Build cache key.
134-
cacheKey := buildCacheKey(provider, providerIdentity, artifactRepositoryCacheKey, serviceAccountP, opts...)
135+
cacheKey := buildCacheKey(provider, providerAudience, providerIdentity,
136+
artifactRepositoryCacheKey, serviceAccountP, opts...)
135137

136138
// Get involved object details.
137139
kind := o.InvolvedObject.Kind
@@ -163,7 +165,7 @@ func newServiceAccountToken(ctx context.Context, client client.Client,
163165
return tokenReq.Status.Token, nil
164166
}
165167

166-
func buildCacheKey(provider Provider, providerIdentity, artifactRepositoryKey string,
168+
func buildCacheKey(provider Provider, providerAudience, providerIdentity, artifactRepositoryKey string,
167169
serviceAccount *corev1.ServiceAccount, opts ...Option) string {
168170

169171
var o Options
@@ -174,6 +176,7 @@ func buildCacheKey(provider Provider, providerIdentity, artifactRepositoryKey st
174176
keyParts = append(keyParts, fmt.Sprintf("provider=%s", provider.GetName()))
175177

176178
if serviceAccount != nil {
179+
keyParts = append(keyParts, fmt.Sprintf("providerAudience=%s", providerAudience))
177180
keyParts = append(keyParts, fmt.Sprintf("providerIdentity=%s", providerIdentity))
178181
keyParts = append(keyParts, fmt.Sprintf("serviceAccountName=%s", serviceAccount.Name))
179182
keyParts = append(keyParts, fmt.Sprintf("serviceAccountNamespace=%s", serviceAccount.Namespace))

auth/get_token_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ func TestGetToken(t *testing.T) {
309309
name: "all the options are taken into account in the cache key",
310310
provider: &mockProvider{
311311
returnName: "mock-provider",
312+
returnAudience: "mock-audience",
312313
returnIdentity: "mock-identity",
313314
returnRegistryInput: "artifact-cache-key",
314315
paramServiceAccount: *defaultServiceAccount,
@@ -325,7 +326,7 @@ func TestGetToken(t *testing.T) {
325326
tokenCache, err := cache.NewTokenCache(1)
326327
g.Expect(err).NotTo(HaveOccurred())
327328

328-
const key = "da48da328aa46181e677d76c835b7ca32b5fbf64da01577463d42a2720708ecb"
329+
const key = "3e8e270134e99fda1a01d7dca77f29448eb4c7f6cc026137b85a1bcd96b276fa"
329330
token := &mockToken{token: "cached-token"}
330331
cachedToken, ok, err := tokenCache.GetOrSet(ctx, key, func(ctx context.Context) (cache.Token, error) {
331332
return token, nil

auth/go.mod

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ module github.com/fluxcd/pkg/auth
22

33
go 1.24.0
44

5-
replace (
6-
github.com/fluxcd/pkg/cache => ../cache
7-
github.com/fluxcd/pkg/ssh => ../ssh
8-
)
5+
replace github.com/fluxcd/pkg/cache => ../cache
96

107
require (
118
cloud.google.com/go/compute/metadata v0.6.0

auth/utils/doc.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
// authutils contains utility functions that import both the core
18-
// auth package and the provider packages i.e. functions that
19-
// cannot be placed in the core package because they would cause
20-
// a cyclic dependency (the provider packages also import the
21-
// core package).
17+
// authutils contains small utility functions without much logic
18+
// wrapping the major APIs of the core auth package for ease of use
19+
// in the controllers. These functions also import the provider
20+
// packages to wrap switch-case choice of provider implementations.
21+
// Because of that, these functions cannot be placed in the core
22+
// package as they would cause a cyclic dependency given that the
23+
// provider packages also import the core package.
2224
package authutils

auth/utils/git.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 authutils
18+
19+
import (
20+
"context"
21+
"errors"
22+
23+
"github.com/fluxcd/pkg/auth"
24+
"github.com/fluxcd/pkg/auth/azure"
25+
)
26+
27+
// GitCredentials contains authentication data needed in order to access a Git
28+
// repository.
29+
type GitCredentials struct {
30+
BearerToken string
31+
Username string
32+
Password string
33+
}
34+
35+
// GetGitCredentials looks up by the implemented providers that support Git
36+
// and returns the credentials for the provider.
37+
func GetGitCredentials(ctx context.Context, providerName string, opts ...auth.Option) (*GitCredentials, error) {
38+
switch providerName {
39+
case azure.ProviderName:
40+
opts = append(opts, auth.WithScopes(azure.ScopeDevOps))
41+
token, err := auth.GetToken(ctx, azure.Provider{}, opts...)
42+
if err != nil {
43+
return nil, err
44+
}
45+
return &GitCredentials{
46+
BearerToken: token.(*azure.Token).Token,
47+
}, nil
48+
default:
49+
return nil, errors.New("unsupported provider")
50+
}
51+
}

git/credentials.go

Lines changed: 0 additions & 92 deletions
This file was deleted.

0 commit comments

Comments
 (0)