Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): make package externalaccount public #9633

Merged
merged 12 commits into from
Mar 25, 2024
20 changes: 10 additions & 10 deletions auth/credentials/externalaccount/externalaccount.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"cloud.google.com/go/auth/internal/credsfile"
)

type Options struct {

Check failure on line 28 in auth/credentials/externalaccount/externalaccount.go

View workflow job for this annotation

GitHub Actions / vet

exported type Options should have comment or be unexported
// Audience is the Secure Token Service (STS) audience which contains the
// resource name for the workload identity pool or the workforce pool and
// the provider identifier in that pool. Required.
Expand Down Expand Up @@ -82,21 +82,21 @@
// This value will be used in the default STS token URL. The default value
// is "googleapis.com". It will not be used if TokenURL is set. Optional.
UniverseDomain string
// SubjectTokenSupplier is an optional token supplier for OIDC/SAML
// credentials. One of SubjectTokenSupplier, AWSSecurityCredentialSupplier
// SubjectTokenProvider is an optional token supplier for OIDC/SAML
// credentials. One of SubjectTokenProvider, AWSSecurityCredentialSupplier
// or CredentialSource must be provided. Optional.
SubjectTokenSupplier SubjectTokenProvider
// AwsSecurityCredentialsSupplier is an AWS Security Credential supplier
SubjectTokenProvider SubjectTokenProvider
// AwsSecurityCredentialsProvider is an AWS Security Credential supplier
// for AWS credentials. One of SubjectTokenSupplier,
// AWSSecurityCredentialSupplier or CredentialSource must be provided. Optional.
AwsSecurityCredentialsSupplier AwsSecurityCredentialsProvider
AwsSecurityCredentialsProvider AwsSecurityCredentialsProvider

// Client configures the underlying client used to make network requests
// when fetching tokens. Optional.
Client *http.Client
}

type CredentialSource struct {

Check failure on line 99 in auth/credentials/externalaccount/externalaccount.go

View workflow job for this annotation

GitHub Actions / vet

exported type CredentialSource should have comment or be unexported
// File is the location for file sourced credentials.
// One field amongst File, URL, Executable, or EnvironmentID should be
// provided, depending on the kind of credential in question.
Expand Down Expand Up @@ -165,12 +165,12 @@
// The external account token source does not cache the returned subject
codyoss marked this conversation as resolved.
Show resolved Hide resolved
// token, so caching logic should be implemented in the supplier to prevent
// multiple requests for the same subject token.
SubjectToken(ctx context.Context, options *SupplierOptions) (string, error)
SubjectToken(ctx context.Context, opts *RequestOptions) (string, error)
}

// SupplierOptions contains information about the requested subject token or AWS
// RequestOptions contains information about the requested subject token or AWS
// security credentials from the Google external account credential.
type SupplierOptions struct {
type RequestOptions struct {
// Audience is the requested audience for the external account credential.
Audience string
// Subject token type is the requested subject token type for the external
Expand All @@ -186,13 +186,13 @@
// and an AWS Region to exchange for a GCP access token.
type AwsSecurityCredentialsProvider interface {
// AwsRegion should return the AWS region or an error.
AwsRegion(ctx context.Context, options SupplierOptions) (string, error)
AwsRegion(ctx context.Context, opts *RequestOptions) (string, error)
// GetAwsSecurityCredentials should return a valid set of
// AwsSecurityCredentials or an error. The external account token source
codyoss marked this conversation as resolved.
Show resolved Hide resolved
// does not cache the returned security credentials, so caching logic should
// be implemented in the supplier to prevent multiple requests for the
// same security credentials.
AwsSecurityCredentials(ctx context.Context, options SupplierOptions) (*AwsSecurityCredentials, error)
AwsSecurityCredentials(ctx context.Context, opts *RequestOptions) (*AwsSecurityCredentials, error)
}

// AwsSecurityCredentials models AWS security credentials.
Expand Down Expand Up @@ -270,7 +270,7 @@
return iOpts
}

func NewCredentials(opts *Options) (*auth.Credentials, error) {

Check failure on line 273 in auth/credentials/externalaccount/externalaccount.go

View workflow job for this annotation

GitHub Actions / vet

exported function NewCredentials should have comment or be unexported
if err := opts.validate(); err != nil {
return nil, err
}
Expand Down
39 changes: 22 additions & 17 deletions auth/credentials/internal/externalaccount/aws_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,16 @@ type awsSubjectProvider struct {
TargetResource string
requestSigner *awsRequestSigner
region string
securityCredentialsProvider AwsSecurityCredentialsProvider
reqOpts *RequestOptions

Client *http.Client
}

func (sp *awsSubjectProvider) subjectToken(ctx context.Context) (string, error) {
if sp.requestSigner == nil {
headers := make(map[string]string)
if shouldUseMetadataServer() {
if sp.shouldUseMetadataServer() {
awsSessionToken, err := sp.getAWSSessionToken(ctx)
if err != nil {
return "", err
Expand Down Expand Up @@ -170,6 +172,9 @@ func (sp *awsSubjectProvider) subjectToken(ctx context.Context) (string, error)
}

func (sp *awsSubjectProvider) providerType() string {
if sp.securityCredentialsProvider != nil {
return programmaticProviderType
}
return awsProviderType
}

Expand Down Expand Up @@ -200,6 +205,9 @@ func (sp *awsSubjectProvider) getAWSSessionToken(ctx context.Context) (string, e
}

func (sp *awsSubjectProvider) getRegion(ctx context.Context, headers map[string]string) (string, error) {
if sp.securityCredentialsProvider != nil {
return sp.securityCredentialsProvider.AwsRegion(ctx, sp.reqOpts)
}
if canRetrieveRegionFromEnvironment() {
if envAwsRegion := getenv(awsRegionEnvVar); envAwsRegion != "" {
return envAwsRegion, nil
Expand Down Expand Up @@ -244,12 +252,15 @@ func (sp *awsSubjectProvider) getRegion(ctx context.Context, headers map[string]
return string(respBody[:bodyLen-1]), nil
}

func (sp *awsSubjectProvider) getSecurityCredentials(ctx context.Context, headers map[string]string) (result awsSecurityCredentials, err error) {
func (sp *awsSubjectProvider) getSecurityCredentials(ctx context.Context, headers map[string]string) (result *AwsSecurityCredentials, err error) {
if sp.securityCredentialsProvider != nil {
return sp.securityCredentialsProvider.AwsSecurityCredentials(ctx, sp.reqOpts)
}
if canRetrieveSecurityCredentialFromEnvironment() {
return awsSecurityCredentials{
return &AwsSecurityCredentials{
AccessKeyID: getenv(awsAccessKeyIDEnvVar),
SecretAccessKey: getenv(awsSecretAccessKeyEnvVar),
SecurityToken: getenv(awsSessionTokenEnvVar),
SessionToken: getenv(awsSessionTokenEnvVar),
}, nil
}

Expand All @@ -272,8 +283,8 @@ func (sp *awsSubjectProvider) getSecurityCredentials(ctx context.Context, header
return credentials, nil
}

func (sp *awsSubjectProvider) getMetadataSecurityCredentials(ctx context.Context, roleName string, headers map[string]string) (awsSecurityCredentials, error) {
var result awsSecurityCredentials
func (sp *awsSubjectProvider) getMetadataSecurityCredentials(ctx context.Context, roleName string, headers map[string]string) (*AwsSecurityCredentials, error) {
var result *AwsSecurityCredentials

req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s", sp.CredVerificationURL, roleName), nil)
if err != nil {
Expand Down Expand Up @@ -328,16 +339,10 @@ func (sp *awsSubjectProvider) getMetadataRoleName(ctx context.Context, headers m
return string(respBody), nil
}

type awsSecurityCredentials struct {
AccessKeyID string `json:"AccessKeyID"`
SecretAccessKey string `json:"SecretAccessKey"`
SecurityToken string `json:"Token"`
}

// awsRequestSigner is a utility class to sign http requests using a AWS V4 signature.
type awsRequestSigner struct {
RegionName string
AwsSecurityCredentials awsSecurityCredentials
AwsSecurityCredentials *AwsSecurityCredentials
}

// signRequest adds the appropriate headers to an http.Request
Expand All @@ -347,8 +352,8 @@ func (rs *awsRequestSigner) signRequest(req *http.Request) error {
signedRequest := cloneRequest(req)
timestamp := now()
signedRequest.Header.Set("host", requestHost(req))
if rs.AwsSecurityCredentials.SecurityToken != "" {
signedRequest.Header.Set(awsSecurityTokenHeader, rs.AwsSecurityCredentials.SecurityToken)
if rs.AwsSecurityCredentials.SessionToken != "" {
signedRequest.Header.Set(awsSecurityTokenHeader, rs.AwsSecurityCredentials.SessionToken)
}
if signedRequest.Header.Get("date") == "" {
signedRequest.Header.Set(awsDateHeader, timestamp.Format(awsTimeFormatLong))
Expand Down Expand Up @@ -531,6 +536,6 @@ func canRetrieveSecurityCredentialFromEnvironment() bool {
return getenv(awsAccessKeyIDEnvVar) != "" && getenv(awsSecretAccessKeyEnvVar) != ""
}

func shouldUseMetadataServer() bool {
return !canRetrieveRegionFromEnvironment() || !canRetrieveSecurityCredentialFromEnvironment()
func (sp *awsSubjectProvider) shouldUseMetadataServer() bool {
return sp.securityCredentialsProvider == nil && (!canRetrieveRegionFromEnvironment() || !canRetrieveSecurityCredentialFromEnvironment())
}
38 changes: 19 additions & 19 deletions auth/credentials/internal/externalaccount/aws_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ type validateHeaders func(r *http.Request)
const (
accessKeyID = "ASIARD4OQDT6A77FR3CL"
secretAccessKey = "Y8AfSaucF37G4PpvfguKZ3/l7Id4uocLXxX0+VTx"
securityToken = "IQoJb3JpZ2luX2VjEIz//////////wEaCXVzLWVhc3QtMiJGMEQCIH7MHX/Oy/OB8OlLQa9GrqU1B914+iMikqWQW7vPCKlgAiA/Lsv8Jcafn14owfxXn95FURZNKaaphj0ykpmS+Ki+CSq0AwhlEAAaDDA3NzA3MTM5MTk5NiIMx9sAeP1ovlMTMKLjKpEDwuJQg41/QUKx0laTZYjPlQvjwSqS3OB9P1KAXPWSLkliVMMqaHqelvMF/WO/glv3KwuTfQsavRNs3v5pcSEm4SPO3l7mCs7KrQUHwGP0neZhIKxEXy+Ls//1C/Bqt53NL+LSbaGv6RPHaX82laz2qElphg95aVLdYgIFY6JWV5fzyjgnhz0DQmy62/Vi8pNcM2/VnxeCQ8CC8dRDSt52ry2v+nc77vstuI9xV5k8mPtnaPoJDRANh0bjwY5Sdwkbp+mGRUJBAQRlNgHUJusefXQgVKBCiyJY4w3Csd8Bgj9IyDV+Azuy1jQqfFZWgP68LSz5bURyIjlWDQunO82stZ0BgplKKAa/KJHBPCp8Qi6i99uy7qh76FQAqgVTsnDuU6fGpHDcsDSGoCls2HgZjZFPeOj8mmRhFk1Xqvkbjuz8V1cJk54d3gIJvQt8gD2D6yJQZecnuGWd5K2e2HohvCc8Fc9kBl1300nUJPV+k4tr/A5R/0QfEKOZL1/k5lf1g9CREnrM8LVkGxCgdYMxLQow1uTL+QU67AHRRSp5PhhGX4Rek+01vdYSnJCMaPhSEgcLqDlQkhk6MPsyT91QMXcWmyO+cAZwUPwnRamFepuP4K8k2KVXs/LIJHLELwAZ0ekyaS7CptgOqS7uaSTFG3U+vzFZLEnGvWQ7y9IPNQZ+Dffgh4p3vF4J68y9049sI6Sr5d5wbKkcbm8hdCDHZcv4lnqohquPirLiFQ3q7B17V9krMPu3mz1cg4Ekgcrn/E09NTsxAqD8NcZ7C7ECom9r+X3zkDOxaajW6hu3Az8hGlyylDaMiFfRbBJpTIlxp7jfa7CxikNgNtEKLH9iCzvuSg2vhA=="
sessionToken = "IQoJb3JpZ2luX2VjEIz//////////wEaCXVzLWVhc3QtMiJGMEQCIH7MHX/Oy/OB8OlLQa9GrqU1B914+iMikqWQW7vPCKlgAiA/Lsv8Jcafn14owfxXn95FURZNKaaphj0ykpmS+Ki+CSq0AwhlEAAaDDA3NzA3MTM5MTk5NiIMx9sAeP1ovlMTMKLjKpEDwuJQg41/QUKx0laTZYjPlQvjwSqS3OB9P1KAXPWSLkliVMMqaHqelvMF/WO/glv3KwuTfQsavRNs3v5pcSEm4SPO3l7mCs7KrQUHwGP0neZhIKxEXy+Ls//1C/Bqt53NL+LSbaGv6RPHaX82laz2qElphg95aVLdYgIFY6JWV5fzyjgnhz0DQmy62/Vi8pNcM2/VnxeCQ8CC8dRDSt52ry2v+nc77vstuI9xV5k8mPtnaPoJDRANh0bjwY5Sdwkbp+mGRUJBAQRlNgHUJusefXQgVKBCiyJY4w3Csd8Bgj9IyDV+Azuy1jQqfFZWgP68LSz5bURyIjlWDQunO82stZ0BgplKKAa/KJHBPCp8Qi6i99uy7qh76FQAqgVTsnDuU6fGpHDcsDSGoCls2HgZjZFPeOj8mmRhFk1Xqvkbjuz8V1cJk54d3gIJvQt8gD2D6yJQZecnuGWd5K2e2HohvCc8Fc9kBl1300nUJPV+k4tr/A5R/0QfEKOZL1/k5lf1g9CREnrM8LVkGxCgdYMxLQow1uTL+QU67AHRRSp5PhhGX4Rek+01vdYSnJCMaPhSEgcLqDlQkhk6MPsyT91QMXcWmyO+cAZwUPwnRamFepuP4K8k2KVXs/LIJHLELwAZ0ekyaS7CptgOqS7uaSTFG3U+vzFZLEnGvWQ7y9IPNQZ+Dffgh4p3vF4J68y9049sI6Sr5d5wbKkcbm8hdCDHZcv4lnqohquPirLiFQ3q7B17V9krMPu3mz1cg4Ekgcrn/E09NTsxAqD8NcZ7C7ECom9r+X3zkDOxaajW6hu3Az8hGlyylDaMiFfRbBJpTIlxp7jfa7CxikNgNtEKLH9iCzvuSg2vhA=="
)

var (
defaultTime = time.Date(2011, 9, 9, 23, 36, 0, 0, time.UTC)
secondDefaultTime = time.Date(2020, 8, 11, 6, 55, 22, 0, time.UTC)
requestSignerWithToken = &awsRequestSigner{
RegionName: "us-east-2",
AwsSecurityCredentials: awsSecurityCredentials{
AwsSecurityCredentials: &AwsSecurityCredentials{
AccessKeyID: accessKeyID,
SecretAccessKey: secretAccessKey,
SecurityToken: securityToken,
SessionToken: sessionToken,
},
}
)
Expand Down Expand Up @@ -300,7 +300,7 @@ func TestAWSv4Signature_GetRequestWithSecurityToken(t *testing.T) {
"Host": []string{"ec2.us-east-2.amazonaws.com"},
"Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + accessKeyID + "/20200811/us-east-2/ec2/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=631ea80cddfaa545fdadb120dc92c9f18166e38a5c47b50fab9fce476e022855"},
"X-Amz-Date": []string{"20200811T065522Z"},
"X-Amz-Security-Token": []string{securityToken},
"X-Amz-Security-Token": []string{sessionToken},
}

oldNow := now
Expand All @@ -318,7 +318,7 @@ func TestAWSv4Signature_PostRequestWithSecurityToken(t *testing.T) {
"Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + accessKeyID + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=73452984e4a880ffdc5c392355733ec3f5ba310d5e0609a89244440cadfe7a7a"},
"Host": []string{"sts.us-east-2.amazonaws.com"},
"X-Amz-Date": []string{"20200811T065522Z"},
"X-Amz-Security-Token": []string{securityToken},
"X-Amz-Security-Token": []string{sessionToken},
}

oldNow := now
Expand All @@ -341,7 +341,7 @@ func TestAWSv4Signature_PostRequestWithSecurityTokenAndAdditionalHeaders(t *test
"X-Amz-Date": []string{"20200811T065522Z"},
"Content-Type": []string{"application/x-amz-json-1.0"},
"X-Amz-Target": []string{"DynamoDB_20120810.CreateTable"},
"X-Amz-Security-Token": []string{securityToken},
"X-Amz-Security-Token": []string{sessionToken},
}

oldNow := now
Expand All @@ -354,7 +354,7 @@ func TestAWSv4Signature_PostRequestWithSecurityTokenAndAdditionalHeaders(t *test
func TestAWSv4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) {
var requestSigner = &awsRequestSigner{
RegionName: "us-east-2",
AwsSecurityCredentials: awsSecurityCredentials{
AwsSecurityCredentials: &AwsSecurityCredentials{
AccessKeyID: accessKeyID,
SecretAccessKey: secretAccessKey,
},
Expand Down Expand Up @@ -433,7 +433,7 @@ func createDefaultAwsTestServer() *testAwsServer {
map[string]string{
"SecretAccessKey": secretAccessKey,
"AccessKeyId": accessKeyID,
"Token": securityToken,
"Token": sessionToken,
},
"",
noHeaderValidation,
Expand Down Expand Up @@ -465,7 +465,7 @@ func createDefaultAwsTestServerWithImdsv2(t *testing.T) *testAwsServer {
map[string]string{
"SecretAccessKey": secretAccessKey,
"AccessKeyId": accessKeyID,
"Token": securityToken,
"Token": sessionToken,
},
"sessiontoken",
validateSessionTokenHeaders,
Expand Down Expand Up @@ -502,15 +502,15 @@ func (server *testAwsServer) getCredentialSource(url string) *credsfile.Credenti
}
}

func getExpectedSubjectToken(url, region, accessKeyID, secretAccessKey, securityToken string) string {
func getExpectedSubjectToken(url, region, accessKeyID, secretAccessKey, sessionToken string) string {
req, _ := http.NewRequest("POST", url, nil)
req.Header.Set("x-goog-cloud-target-resource", cloneTestOpts().Audience)
signer := &awsRequestSigner{
RegionName: region,
AwsSecurityCredentials: awsSecurityCredentials{
AwsSecurityCredentials: &AwsSecurityCredentials{
AccessKeyID: accessKeyID,
SecretAccessKey: secretAccessKey,
SecurityToken: securityToken,
SessionToken: sessionToken,
},
}
signer.signRequest(req)
Expand All @@ -532,10 +532,10 @@ func getExpectedSubjectToken(url, region, accessKeyID, secretAccessKey, security
},
}

if securityToken != "" {
if sessionToken != "" {
result.Headers = append(result.Headers, awsRequestHeader{
Key: "X-Amz-Security-Token",
Value: securityToken,
Value: sessionToken,
})
}

Expand Down Expand Up @@ -583,7 +583,7 @@ func TestAWSCredential_BasicRequest(t *testing.T) {
"us-east-2",
accessKeyID,
secretAccessKey,
securityToken,
sessionToken,
)

if got != want {
Expand Down Expand Up @@ -622,7 +622,7 @@ func TestAWSCredential_IMDSv2(t *testing.T) {
"us-east-2",
accessKeyID,
secretAccessKey,
securityToken,
sessionToken,
)

if got != want {
Expand Down Expand Up @@ -1148,7 +1148,7 @@ func TestAWSCredential_ShouldCallMetadataEndpointWhenNoAccessKey(t *testing.T) {
"us-west-1",
accessKeyID,
secretAccessKey,
securityToken,
sessionToken,
)

if got != want {
Expand Down Expand Up @@ -1190,7 +1190,7 @@ func TestAWSCredential_ShouldCallMetadataEndpointWhenNoSecretAccessKey(t *testin
"us-west-1",
accessKeyID,
secretAccessKey,
securityToken,
sessionToken,
)

if got != want {
Expand Down Expand Up @@ -1270,7 +1270,7 @@ func setEnvironment(env map[string]string) func(string) string {

var defaultRequestSigner = &awsRequestSigner{
RegionName: "us-east-1",
AwsSecurityCredentials: awsSecurityCredentials{
AwsSecurityCredentials: &AwsSecurityCredentials{
AccessKeyID: "AKIDEXAMPLE",
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
},
Expand Down