...

Source file src/cloud.google.com/go/auth/credentials/externalaccount/externalaccount_test.go

Documentation: cloud.google.com/go/auth/credentials/externalaccount

     1  // Copyright 2024 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 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