1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package externalaccount
16
17 import (
18 "context"
19 "encoding/json"
20 "net/http"
21 "net/http/httptest"
22 "strings"
23 "testing"
24 "time"
25
26 iexacc "cloud.google.com/go/auth/credentials/internal/externalaccount"
27 )
28
29 const (
30 accessKeyID = "accessKeyID"
31 secretAccessKey = "secret"
32 sessionToken = "sessionTok"
33 subjectTok = `%7B%22url%22%3A%22https%3A%2F%2Fsts.us-east-2.amazonaws.com%3FAction%3DGetCallerIdentity%5Cu0026Version%3D2011-06-15%22%2C%22method%22%3A%22POST%22%2C%22headers%22%3A%5B%7B%22key%22%3A%22Authorization%22%2C%22value%22%3A%22AWS4-HMAC-SHA256+Credential%3DaccessKeyID%2F20110909%2Fus-east-2%2Fsts%2Faws4_request%2C+SignedHeaders%3Dhost%3Bx-amz-date%3Bx-amz-security-token%3Bx-goog-cloud-target-resource%2C+Signature%3D19e8a661c61d39d19a9c82e272deef7784908176b82b0eb42f328d2c640f369b%22%7D%2C%7B%22key%22%3A%22Host%22%2C%22value%22%3A%22sts.us-east-2.amazonaws.com%22%7D%2C%7B%22key%22%3A%22X-Amz-Date%22%2C%22value%22%3A%2220110909T233600Z%22%7D%2C%7B%22key%22%3A%22X-Amz-Security-Token%22%2C%22value%22%3A%22sessionTok%22%7D%2C%7B%22key%22%3A%22X-Goog-Cloud-Target-Resource%22%2C%22value%22%3A%2232555940559.apps.googleusercontent.com%22%7D%5D%7D`
34 )
35
36 var (
37 defaultTime = time.Date(2011, 9, 9, 23, 36, 0, 0, time.UTC)
38 )
39
40 func TestNewCredentials_AwsSecurityCredentials(t *testing.T) {
41 opts := &Options{
42 Audience: "32555940559.apps.googleusercontent.com",
43 SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
44 ClientSecret: "notsosecret",
45 ClientID: "rbrgnognrhongo3bi4gb9ghg9g",
46 }
47 opts.AwsSecurityCredentialsProvider = &fakeAwsCredsProvider{
48 awsRegion: "us-east-2",
49 creds: &AwsSecurityCredentials{
50 AccessKeyID: accessKeyID,
51 SecretAccessKey: secretAccessKey,
52 SessionToken: sessionToken,
53 },
54 }
55 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56 defer r.Body.Close()
57 if r.URL.Path == "/sts" {
58 r.ParseForm()
59 if got, want := r.Form.Get("subject_token"), subjectTok; got != want {
60 t.Errorf("got %q, want %q", got, want)
61 }
62
63 resp := &struct {
64 AccessToken string `json:"access_token"`
65 ExpiresIn int `json:"expires_in"`
66 }{
67 AccessToken: "a_fake_token_sts",
68 ExpiresIn: 60,
69 }
70 if err := json.NewEncoder(w).Encode(&resp); err != nil {
71 t.Error(err)
72 }
73 } else if r.URL.Path == "/impersonate" {
74 if want := "a_fake_token_sts"; !strings.Contains(r.Header.Get("Authorization"), want) {
75 t.Errorf("missing sts token: got %q, want %q", r.Header.Get("Authorization"), want)
76 }
77
78 resp := &struct {
79 AccessToken string `json:"accessToken"`
80 ExpireTime string `json:"expireTime"`
81 }{
82 AccessToken: "a_fake_token",
83 ExpireTime: "2006-01-02T15:04:05Z",
84 }
85 if err := json.NewEncoder(w).Encode(&resp); err != nil {
86 t.Error(err)
87 }
88 } else {
89 t.Errorf("unexpected call to %q", r.URL.Path)
90 }
91 }))
92 opts.ServiceAccountImpersonationURL = ts.URL + "/impersonate"
93 opts.TokenURL = ts.URL + "/sts"
94
95 oldNow := iexacc.Now
96 defer func() {
97 iexacc.Now = oldNow
98 }()
99 iexacc.Now = func() time.Time {
100 return defaultTime
101 }
102
103 creds, err := NewCredentials(opts)
104 if err != nil {
105 t.Fatalf("NewCredentials() = %v", err)
106 }
107 if _, err := creds.Token(context.Background()); err != nil {
108 t.Fatalf("creds.Token() = %v", err)
109 }
110 }
111
112 func TestNewCredentials_SubjectTokenProvider(t *testing.T) {
113 opts := &Options{
114 Audience: "32555940559.apps.googleusercontent.com",
115 SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
116 ClientSecret: "notsosecret",
117 ClientID: "rbrgnognrhongo3bi4gb9ghg9g",
118 }
119 opts.SubjectTokenProvider = &fakeSubjectTokenProvider{
120 subjectToken: "fake_token",
121 }
122 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
123 defer r.Body.Close()
124 if r.URL.Path == "/sts" {
125 r.ParseForm()
126 if got, want := r.Form.Get("subject_token"), "fake_token"; got != want {
127 t.Errorf("got %q, want %q", got, want)
128 }
129
130 resp := &struct {
131 AccessToken string `json:"access_token"`
132 ExpiresIn int `json:"expires_in"`
133 }{
134 AccessToken: "a_fake_token_sts",
135 ExpiresIn: 60,
136 }
137 if err := json.NewEncoder(w).Encode(&resp); err != nil {
138 t.Error(err)
139 }
140 } else if r.URL.Path == "/impersonate" {
141 if want := "a_fake_token_sts"; !strings.Contains(r.Header.Get("Authorization"), want) {
142 t.Errorf("missing sts token: got %q, want %q", r.Header.Get("Authorization"), want)
143 }
144
145 resp := &struct {
146 AccessToken string `json:"accessToken"`
147 ExpireTime string `json:"expireTime"`
148 }{
149 AccessToken: "a_fake_token",
150 ExpireTime: "2006-01-02T15:04:05Z",
151 }
152 if err := json.NewEncoder(w).Encode(&resp); err != nil {
153 t.Error(err)
154 }
155 } else {
156 t.Errorf("unexpected call to %q", r.URL.Path)
157 }
158 }))
159 opts.ServiceAccountImpersonationURL = ts.URL + "/impersonate"
160 opts.TokenURL = ts.URL + "/sts"
161
162 oldNow := iexacc.Now
163 defer func() {
164 iexacc.Now = oldNow
165 }()
166 iexacc.Now = func() time.Time {
167 return defaultTime
168 }
169
170 creds, err := NewCredentials(opts)
171 if err != nil {
172 t.Fatalf("NewCredentials() = %v", err)
173 }
174 if _, err := creds.Token(context.Background()); err != nil {
175 t.Fatalf("creds.Token() = %v", err)
176 }
177 }
178
179 func TestNewCredentials_CredentialSourceURL(t *testing.T) {
180 opts := &Options{
181 Audience: "//iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID",
182 SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
183 CredentialSource: &CredentialSource{
184 Format: &Format{
185 Type: "json",
186 SubjectTokenFieldName: "id_token",
187 },
188 },
189 }
190 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
191 defer r.Body.Close()
192 if r.URL.Path == "/token" {
193 resp := &struct {
194 Token string `json:"id_token"`
195 }{
196 Token: "a_fake_token_base",
197 }
198 if err := json.NewEncoder(w).Encode(&resp); err != nil {
199 t.Error(err)
200 }
201 } else if r.URL.Path == "/sts" {
202 r.ParseForm()
203 if got, want := r.Form.Get("subject_token"), "a_fake_token_base"; got != want {
204 t.Errorf("got %q, want %q", got, want)
205 }
206
207 resp := &struct {
208 AccessToken string `json:"access_token"`
209 ExpiresIn int `json:"expires_in"`
210 }{
211 AccessToken: "a_fake_token_sts",
212 ExpiresIn: 60,
213 }
214 if err := json.NewEncoder(w).Encode(&resp); err != nil {
215 t.Error(err)
216 }
217 } else if r.URL.Path == "/impersonate" {
218 if want := "a_fake_token_sts"; !strings.Contains(r.Header.Get("Authorization"), want) {
219 t.Errorf("missing sts token: got %q, want %q", r.Header.Get("Authorization"), want)
220 }
221
222 resp := &struct {
223 AccessToken string `json:"accessToken"`
224 ExpireTime string `json:"expireTime"`
225 }{
226 AccessToken: "a_fake_token",
227 ExpireTime: "2006-01-02T15:04:05Z",
228 }
229 if err := json.NewEncoder(w).Encode(&resp); err != nil {
230 t.Error(err)
231 }
232 } else {
233 t.Errorf("unexpected call to %q", r.URL.Path)
234 }
235 }))
236 opts.ServiceAccountImpersonationURL = ts.URL + "/impersonate"
237 opts.TokenURL = ts.URL + "/sts"
238 opts.CredentialSource.URL = ts.URL + "/token"
239
240 creds, err := NewCredentials(opts)
241 if err != nil {
242 t.Fatalf("NewCredentials() = %v", err)
243 }
244 if _, err := creds.Token(context.Background()); err != nil {
245 t.Fatalf("creds.Token() = %v", err)
246 }
247 }
248
249 type fakeAwsCredsProvider struct {
250 credsErr error
251 regionErr error
252 awsRegion string
253 creds *AwsSecurityCredentials
254 }
255
256 func (acp fakeAwsCredsProvider) AwsRegion(ctx context.Context, opts *RequestOptions) (string, error) {
257 if acp.regionErr != nil {
258 return "", acp.regionErr
259 }
260 return acp.awsRegion, nil
261 }
262
263 func (acp fakeAwsCredsProvider) AwsSecurityCredentials(ctx context.Context, opts *RequestOptions) (*AwsSecurityCredentials, error) {
264 if acp.credsErr != nil {
265 return nil, acp.credsErr
266 }
267 return acp.creds, nil
268 }
269
270 type fakeSubjectTokenProvider struct {
271 err error
272 subjectToken string
273 }
274
275 func (p fakeSubjectTokenProvider) SubjectToken(ctx context.Context, options *RequestOptions) (string, error) {
276 if p.err != nil {
277 return "", p.err
278 }
279 return p.subjectToken, nil
280 }
281
View as plain text