@@ -18,11 +18,14 @@ package gcecloudprovider
18
18
19
19
import (
20
20
"encoding/json"
21
+ "fmt"
21
22
"net/http"
23
+ "strconv"
22
24
"strings"
23
25
"time"
24
26
25
27
"k8s.io/client-go/util/flowcontrol"
28
+ "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-cloud-provider/compute/tenancy"
26
29
27
30
"golang.org/x/oauth2"
28
31
"golang.org/x/oauth2/google"
@@ -34,6 +37,8 @@ const (
34
37
tokenURLQPS = .05 // back off to once every 20 seconds when failing
35
38
// Maximum burst of requests to token URL before limiting.
36
39
tokenURLBurst = 3
40
+ // tenantAuthenticationPathFmt is the string format for the full URL needed to generate an access token for tenants
41
+ tenantAuthenticationPathFmt = "https://preprod-gkeauth.sandbox.googleapis.com/v1/projects/%s/locations/%s/tenants/%s:generateTenantToken"
37
42
)
38
43
39
44
// TODO(#276) add metrics around token requests once the driver integrates with Prometheus.
@@ -89,3 +94,84 @@ func NewAltTokenSource(tokenURL, tokenBody string) oauth2.TokenSource {
89
94
}
90
95
return oauth2 .ReuseTokenSource (nil , a )
91
96
}
97
+
98
+ func NewTenantTokenSource (tenantMeta tenancy.Metadata , region , existingTokenURL , existingTokenBody string ) (oauth2.TokenSource , error ) {
99
+ tenantTokenUrl , err := getTenantTokenURL (tenantMeta , existingTokenURL )
100
+ if err != nil {
101
+ return nil , err
102
+ }
103
+ tenantTokenBody , err := getTenantTokenBody (tenantMeta , existingTokenBody )
104
+ if err != nil {
105
+ return nil , err
106
+ }
107
+ return NewAltTokenSource (tenantTokenUrl , tenantTokenBody ), nil
108
+ }
109
+
110
+ func getTenantTokenURL (tenantMeta tenancy.Metadata , existingTokenURL string ) (string , error ) {
111
+ location := extractLocationFromTokenURL (existingTokenURL )
112
+ if location == "" {
113
+ return "" , fmt .Errorf ("could not extract location from existing token URL: %s" , existingTokenURL )
114
+ }
115
+
116
+ tokenURLParts := strings .SplitN (existingTokenURL , "/projects/" , 2 )
117
+ if len (tokenURLParts ) != 2 {
118
+ return "" , fmt .Errorf ("invalid existing token URL format: %s, cannot extract base URL" , existingTokenURL )
119
+ }
120
+ baseURL := tokenURLParts [0 ]
121
+
122
+ // Format: {BASE_URL}/projects/{TENANT_PROJECT_NUMBER}/locations/{TENANT_LOCATION}/tenants/{TENANT_ID}:generateTenantToken
123
+ formatString := "%s/projects/%s/locations/%s/tenants/%s:generateTenantToken"
124
+ tokenURL := fmt .Sprintf (formatString , baseURL , tenantMeta .ProjectNumber , location , tenantMeta .TenantName )
125
+ return tokenURL , nil
126
+ }
127
+
128
+ // extractLocationFromTokenURL extracts the location from a GKE token URL.
129
+ // Example input: https://gkeauth.googleapis.com/v1/projects/654321/locations/us-central1/clusters/example-cluster:generateToken
130
+ // Returns: us-central1
131
+ func extractLocationFromTokenURL (tokenURL string ) string {
132
+ parts := strings .Split (tokenURL , "/" )
133
+ for i , part := range parts {
134
+ if part == "locations" && i + 1 < len (parts ) {
135
+ return parts [i + 1 ]
136
+ }
137
+ }
138
+ return ""
139
+ }
140
+
141
+ func getTenantTokenBody (tenantMeta tenancy.Metadata , existingTokenBody string ) (string , error ) {
142
+ // Check if the token body is a quoted JSON string
143
+ // Quoted example: "{\"projectNumber\":12345,\"clusterId\":\"example-cluster\"}"
144
+ // Non-quoted example: {"projectNumber":12345,"clusterId":"example-cluster"}
145
+ isQuoted := len (existingTokenBody ) > 0 && existingTokenBody [0 ] == '"' && existingTokenBody [len (existingTokenBody )- 1 ] == '"'
146
+
147
+ var jsonStr string
148
+ if isQuoted {
149
+ var err error
150
+ jsonStr , err = strconv .Unquote (existingTokenBody )
151
+ if err != nil {
152
+ return "" , fmt .Errorf ("error unquoting TokenBody: %v" , err )
153
+ }
154
+ } else {
155
+ jsonStr = existingTokenBody
156
+ }
157
+
158
+ var bodyMap map [string ]any
159
+
160
+ if err := json .Unmarshal ([]byte (jsonStr ), & bodyMap ); err != nil {
161
+ return "" , fmt .Errorf ("error unmarshaling TokenBody: %v" , err )
162
+ }
163
+
164
+ bodyMap ["projectNumber" ] = tenantMeta .ProjectNumber
165
+
166
+ newTokenBodyBytes , err := json .Marshal (bodyMap )
167
+ if err != nil {
168
+ return "" , fmt .Errorf ("error marshaling TokenBody: %v" , err )
169
+ }
170
+
171
+ if isQuoted {
172
+ // Re-quote the JSON string if the original was quoted
173
+ return strconv .Quote (string (newTokenBodyBytes )), nil
174
+ }
175
+
176
+ return string (newTokenBodyBytes ), nil
177
+ }
0 commit comments