external_accounts_config_providers.go raw
1 // Copyright 2025 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package trustboundary
16
17 import (
18 "context"
19 "fmt"
20 "regexp"
21 )
22
23 const (
24 workloadAllowedLocationsEndpoint = "https://iamcredentials.%s/v1/projects/%s/locations/global/workloadIdentityPools/%s/allowedLocations"
25 workforceAllowedLocationsEndpoint = "https://iamcredentials.%s/v1/locations/global/workforcePools/%s/allowedLocations"
26 )
27
28 var (
29 workforceAudiencePattern = regexp.MustCompile(`//iam\.([^/]+)/locations/global/workforcePools/([^/]+)`)
30 workloadAudiencePattern = regexp.MustCompile(`//iam\.([^/]+)/projects/([^/]+)/locations/global/workloadIdentityPools/([^/]+)`)
31 )
32
33 // NewExternalAccountConfigProvider creates a new ConfigProvider for external accounts.
34 func NewExternalAccountConfigProvider(audience, inputUniverseDomain string) (ConfigProvider, error) {
35 var audienceDomain, projectNumber, poolID string
36 var isWorkload bool
37
38 matches := workloadAudiencePattern.FindStringSubmatch(audience)
39 if len(matches) == 4 { // Expecting full match, domain, projectNumber, poolID
40 audienceDomain = matches[1]
41 projectNumber = matches[2]
42 poolID = matches[3]
43 isWorkload = true
44 } else {
45 matches = workforceAudiencePattern.FindStringSubmatch(audience)
46 if len(matches) == 3 { // Expecting full match, domain, poolID
47 audienceDomain = matches[1]
48 poolID = matches[2]
49 isWorkload = false
50 } else {
51 return nil, fmt.Errorf("trustboundary: unknown audience format: %q", audience)
52 }
53 }
54
55 effectiveUniverseDomain := inputUniverseDomain
56 if effectiveUniverseDomain == "" {
57 effectiveUniverseDomain = audienceDomain
58 } else if audienceDomain != "" && effectiveUniverseDomain != audienceDomain {
59 return nil, fmt.Errorf("trustboundary: provided universe domain (%q) does not match domain in audience (%q)", inputUniverseDomain, audienceDomain)
60 }
61
62 if isWorkload {
63 return &workloadIdentityPoolConfigProvider{
64 projectNumber: projectNumber,
65 poolID: poolID,
66 universeDomain: effectiveUniverseDomain,
67 }, nil
68 }
69 return &workforcePoolConfigProvider{
70 poolID: poolID,
71 universeDomain: effectiveUniverseDomain,
72 }, nil
73 }
74
75 type workforcePoolConfigProvider struct {
76 poolID string
77 universeDomain string
78 }
79
80 func (p *workforcePoolConfigProvider) GetTrustBoundaryEndpoint(ctx context.Context) (string, error) {
81 return fmt.Sprintf(workforceAllowedLocationsEndpoint, p.universeDomain, p.poolID), nil
82 }
83
84 func (p *workforcePoolConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
85 return p.universeDomain, nil
86 }
87
88 type workloadIdentityPoolConfigProvider struct {
89 projectNumber string
90 poolID string
91 universeDomain string
92 }
93
94 func (p *workloadIdentityPoolConfigProvider) GetTrustBoundaryEndpoint(ctx context.Context) (string, error) {
95 return fmt.Sprintf(workloadAllowedLocationsEndpoint, p.universeDomain, p.projectNumber, p.poolID), nil
96 }
97
98 func (p *workloadIdentityPoolConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
99 return p.universeDomain, nil
100 }
101