Skip to content

Commit

Permalink
Sync
Browse files Browse the repository at this point in the history
  • Loading branch information
mbilski committed Aug 16, 2023
1 parent 50fec22 commit 1210e97
Show file tree
Hide file tree
Showing 10 changed files with 660 additions and 270 deletions.
1 change: 0 additions & 1 deletion google/appengine_gen1.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.

//go:build appengine
// +build appengine

// This file applies to App Engine first generation runtimes (<= Go 1.9).

Expand Down
1 change: 0 additions & 1 deletion google/appengine_gen2_flex.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.

//go:build !appengine
// +build !appengine

// This file applies to App Engine second generation runtimes (>= Go 1.11) and App Engine flexible.

Expand Down
37 changes: 24 additions & 13 deletions google/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"runtime"
"time"

"cloud.google.com/go/compute/metadata"
"golang.org/x/oauth2"
"golang.org/x/oauth2/authhandler"
)

const adcSetupURL = "https://cloud.google.com/docs/authentication/external/set-up-adc"

// Credentials holds Google credentials, including "Application Default Credentials".
// For more details, see:
// https://developers.google.com/accounts/docs/application-default-credentials
Expand Down Expand Up @@ -62,6 +64,18 @@ type CredentialsParams struct {

// PKCE is used to support PKCE flow. Optional for 3LO flow.
PKCE *authhandler.PKCEParams

// The OAuth2 TokenURL default override. This value overrides the default TokenURL,
// unless explicitly specified by the credentials config file. Optional.
TokenURL string

// EarlyTokenRefresh is the amount of time before a token expires that a new
// token will be preemptively fetched. If unset the default value is 10
// seconds.
//
// Note: This option is currently only respected when using credentials
// fetched from the GCE metadata server.
EarlyTokenRefresh time.Duration
}

func (params CredentialsParams) deepCopy() CredentialsParams {
Expand Down Expand Up @@ -127,17 +141,15 @@ func FindDefaultCredentialsWithParams(ctx context.Context, params CredentialsPar

// Second, try a well-known file.
filename := wellKnownFile()
if creds, err := readCredentialsFile(ctx, filename, params); err == nil {
return creds, nil
} else if !os.IsNotExist(err) {
return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err)
if b, err := os.ReadFile(filename); err == nil {
return CredentialsFromJSONWithParams(ctx, b, params)
}

// Third, if we're on a Google App Engine standard first generation runtime (<= Go 1.9)
// use those credentials. App Engine standard second generation runtimes (>= Go 1.11)
// and App Engine flexible use ComputeTokenSource and the metadata server.
if appengineTokenFunc != nil {
return &DefaultCredentials{
return &Credentials{
ProjectID: appengineAppIDFunc(ctx),
TokenSource: AppEngineTokenSource(ctx, params.Scopes...),
}, nil
Expand All @@ -147,15 +159,14 @@ func FindDefaultCredentialsWithParams(ctx context.Context, params CredentialsPar
// or App Engine flexible, use the metadata server.
if metadata.OnGCE() {
id, _ := metadata.ProjectID()
return &DefaultCredentials{
return &Credentials{
ProjectID: id,
TokenSource: ComputeTokenSource("", params.Scopes...),
TokenSource: computeTokenSource("", params.EarlyTokenRefresh, params.Scopes...),
}, nil
}

// None are found; return helpful error.
const url = "https://developers.google.com/accounts/docs/application-default-credentials"
return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url)
return nil, fmt.Errorf("google: could not find default credentials. See %v for more information", adcSetupURL)
}

// FindDefaultCredentials invokes FindDefaultCredentialsWithParams with the specified scopes.
Expand Down Expand Up @@ -194,7 +205,7 @@ func CredentialsFromJSONWithParams(ctx context.Context, jsonData []byte, params
return nil, err
}
ts = newErrWrappingTokenSource(ts)
return &DefaultCredentials{
return &Credentials{
ProjectID: f.ProjectID,
TokenSource: ts,
JSON: jsonData,
Expand All @@ -216,8 +227,8 @@ func wellKnownFile() string {
return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f)
}

func readCredentialsFile(ctx context.Context, filename string, params CredentialsParams) (*DefaultCredentials, error) {
b, err := ioutil.ReadFile(filename)
func readCredentialsFile(ctx context.Context, filename string, params CredentialsParams) (*Credentials, error) {
b, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
Expand Down
65 changes: 57 additions & 8 deletions google/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
//
// Using workload identity federation, your application can access Google Cloud
// resources from Amazon Web Services (AWS), Microsoft Azure or any identity
// provider that supports OpenID Connect (OIDC).
// provider that supports OpenID Connect (OIDC) or SAML 2.0.
// Traditionally, applications running outside Google Cloud have used service
// account keys to access Google Cloud resources. Using identity federation,
// you can allow your workload to impersonate a service account.
Expand All @@ -36,26 +36,75 @@
// Follow the detailed instructions on how to configure Workload Identity Federation
// in various platforms:
//
// Amazon Web Services (AWS): https://cloud.google.com/iam/docs/access-resources-aws
// Microsoft Azure: https://cloud.google.com/iam/docs/access-resources-azure
// OIDC identity provider: https://cloud.google.com/iam/docs/access-resources-oidc
// Amazon Web Services (AWS): https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds#aws
// Microsoft Azure: https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds#azure
// OIDC identity provider: https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers#oidc
// SAML 2.0 identity provider: https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers#saml
//
// For OIDC and SAML providers, the library can retrieve tokens in three ways:
// from a local file location (file-sourced credentials), from a server
// (URL-sourced credentials), or from a local executable (executable-sourced
// credentials).
// For file-sourced credentials, a background process needs to be continuously
// refreshing the file location with a new OIDC token prior to expiration.
// refreshing the file location with a new OIDC/SAML token prior to expiration.
// For tokens with one hour lifetimes, the token needs to be updated in the file
// every hour. The token can be stored directly as plain text or in JSON format.
// For URL-sourced credentials, a local server needs to host a GET endpoint to
// return the OIDC token. The response can be in plain text or JSON.
// return the OIDC/SAML token. The response can be in plain text or JSON.
// Additional required request headers can also be specified.
// For executable-sourced credentials, an application needs to be available to
// output the OIDC token and other information in a JSON format.
// output the OIDC/SAML token and other information in a JSON format.
// For more information on how these work (and how to implement
// executable-sourced credentials), please check out:
// https://cloud.google.com/iam/docs/using-workload-identity-federation#oidc
// https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers#create_a_credential_configuration
//
// Note that this library does not perform any validation on the token_url, token_info_url,
// or service_account_impersonation_url fields of the credential configuration.
// It is not recommended to use a credential configuration that you did not generate with
// the gcloud CLI unless you verify that the URL fields point to a googleapis.com domain.
//
// # Workforce Identity Federation
//
// Workforce identity federation lets you use an external identity provider (IdP) to
// authenticate and authorize a workforce—a group of users, such as employees, partners,
// and contractors—using IAM, so that the users can access Google Cloud services.
// Workforce identity federation extends Google Cloud's identity capabilities to support
// syncless, attribute-based single sign on.
//
// With workforce identity federation, your workforce can access Google Cloud resources
// using an external identity provider (IdP) that supports OpenID Connect (OIDC) or
// SAML 2.0 such as Azure Active Directory (Azure AD), Active Directory Federation
// Services (AD FS), Okta, and others.
//
// Follow the detailed instructions on how to configure Workload Identity Federation
// in various platforms:
//
// Azure AD: https://cloud.google.com/iam/docs/workforce-sign-in-azure-ad
// Okta: https://cloud.google.com/iam/docs/workforce-sign-in-okta
// OIDC identity provider: https://cloud.google.com/iam/docs/configuring-workforce-identity-federation#oidc
// SAML 2.0 identity provider: https://cloud.google.com/iam/docs/configuring-workforce-identity-federation#saml
//
// For workforce identity federation, the library can retrieve tokens in three ways:
// from a local file location (file-sourced credentials), from a server
// (URL-sourced credentials), or from a local executable (executable-sourced
// credentials).
// For file-sourced credentials, a background process needs to be continuously
// refreshing the file location with a new OIDC/SAML token prior to expiration.
// For tokens with one hour lifetimes, the token needs to be updated in the file
// every hour. The token can be stored directly as plain text or in JSON format.
// For URL-sourced credentials, a local server needs to host a GET endpoint to
// return the OIDC/SAML token. The response can be in plain text or JSON.
// Additional required request headers can also be specified.
// For executable-sourced credentials, an application needs to be available to
// output the OIDC/SAML token and other information in a JSON format.
// For more information on how these work (and how to implement
// executable-sourced credentials), please check out:
// https://cloud.google.com/iam/docs/workforce-obtaining-short-lived-credentials#generate_a_configuration_file_for_non-interactive_sign-in
//
// Note that this library does not perform any validation on the token_url, token_info_url,
// or service_account_impersonation_url fields of the credential configuration.
// It is not recommended to use a credential configuration that you did not generate with
// the gcloud CLI unless you verify that the URL fields point to a googleapis.com domain.
//
// # Credentials
//
Expand Down
15 changes: 13 additions & 2 deletions google/google.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ var Endpoint = oauth2.Endpoint{
AuthStyle: oauth2.AuthStyleInParams,
}

// MTLSTokenURL is Google's OAuth 2.0 default mTLS endpoint.
const MTLSTokenURL = "https://oauth2.mtls.googleapis.com/token"

// JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow.
const JWTTokenURL = "https://oauth2.googleapis.com/token"

Expand Down Expand Up @@ -172,7 +175,11 @@ func (f *credentialsFile) tokenSource(ctx context.Context, params CredentialsPar
cfg.Endpoint.AuthURL = Endpoint.AuthURL
}
if cfg.Endpoint.TokenURL == "" {
cfg.Endpoint.TokenURL = Endpoint.TokenURL
if params.TokenURL != "" {
cfg.Endpoint.TokenURL = params.TokenURL
} else {
cfg.Endpoint.TokenURL = Endpoint.TokenURL
}
}
tok := &oauth2.Token{RefreshToken: f.RefreshToken}
return cfg.TokenSource(ctx, tok), nil
Expand Down Expand Up @@ -224,7 +231,11 @@ func (f *credentialsFile) tokenSource(ctx context.Context, params CredentialsPar
// Further information about retrieving access tokens from the GCE metadata
// server can be found at https://cloud.google.com/compute/docs/authentication.
func ComputeTokenSource(account string, scope ...string) oauth2.TokenSource {
return oauth2.ReuseTokenSource(nil, computeSource{account: account, scopes: scope})
return computeTokenSource(account, 0, scope...)
}

func computeTokenSource(account string, earlyExpiry time.Duration, scope ...string) oauth2.TokenSource {
return oauth2.ReuseTokenSourceWithExpiry(nil, computeSource{account: account, scopes: scope}, earlyExpiry)

Check failure on line 238 in google/google.go

View workflow job for this annotation

GitHub Actions / build

undefined: oauth2.ReuseTokenSourceWithExpiry
}

type computeSource struct {
Expand Down
105 changes: 85 additions & 20 deletions google/internal/externalaccount/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ const (
// The AWS authorization header name for the auto-generated date.
awsDateHeader = "x-amz-date"

// Supported AWS configuration environment variables.
awsAccessKeyId = "AWS_ACCESS_KEY_ID"
awsDefaultRegion = "AWS_DEFAULT_REGION"
awsRegion = "AWS_REGION"
awsSecretAccessKey = "AWS_SECRET_ACCESS_KEY"
awsSessionToken = "AWS_SESSION_TOKEN"

awsTimeFormatLong = "20060102T150405Z"
awsTimeFormatShort = "20060102"
)
Expand Down Expand Up @@ -267,23 +274,83 @@ type awsRequest struct {
Headers []awsRequestHeader `json:"headers"`
}

func (cs awsCredentialSource) validateMetadataServers() error {
if err := cs.validateMetadataServer(cs.RegionURL, "region_url"); err != nil {
return err
}
if err := cs.validateMetadataServer(cs.CredVerificationURL, "url"); err != nil {
return err
}
return cs.validateMetadataServer(cs.IMDSv2SessionTokenURL, "imdsv2_session_token_url")
}

var validHostnames []string = []string{"169.254.169.254", "fd00:ec2::254"}

func (cs awsCredentialSource) isValidMetadataServer(metadataUrl string) bool {
if metadataUrl == "" {
// Zero value means use default, which is valid.
return true
}

u, err := url.Parse(metadataUrl)
if err != nil {
// Unparseable URL means invalid
return false
}

for _, validHostname := range validHostnames {
if u.Hostname() == validHostname {
// If it's one of the valid hostnames, everything is good
return true
}
}

// hostname not found in our allowlist, so not valid
return false
}

func (cs awsCredentialSource) validateMetadataServer(metadataUrl, urlName string) error {
if !cs.isValidMetadataServer(metadataUrl) {
return fmt.Errorf("oauth2/google: invalid hostname %s for %s", metadataUrl, urlName)
}

return nil
}

func (cs awsCredentialSource) doRequest(req *http.Request) (*http.Response, error) {
if cs.client == nil {
cs.client = oauth2.NewClient(cs.ctx, nil)
}
return cs.client.Do(req.WithContext(cs.ctx))
}

func canRetrieveRegionFromEnvironment() bool {
// The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION. Only one is
// required.
return getenv(awsRegion) != "" || getenv(awsDefaultRegion) != ""
}

func canRetrieveSecurityCredentialFromEnvironment() bool {
// Check if both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are available.
return getenv(awsAccessKeyId) != "" && getenv(awsSecretAccessKey) != ""
}

func shouldUseMetadataServer() bool {
return !canRetrieveRegionFromEnvironment() || !canRetrieveSecurityCredentialFromEnvironment()
}

func (cs awsCredentialSource) subjectToken() (string, error) {
if cs.requestSigner == nil {
awsSessionToken, err := cs.getAWSSessionToken()
if err != nil {
return "", err
}

headers := make(map[string]string)
if awsSessionToken != "" {
headers[awsIMDSv2SessionTokenHeader] = awsSessionToken
if shouldUseMetadataServer() {
awsSessionToken, err := cs.getAWSSessionToken()
if err != nil {
return "", err
}

if awsSessionToken != "" {
headers[awsIMDSv2SessionTokenHeader] = awsSessionToken
}
}

awsSecurityCredentials, err := cs.getSecurityCredentials(headers)
Expand Down Expand Up @@ -389,11 +456,11 @@ func (cs *awsCredentialSource) getAWSSessionToken() (string, error) {
}

func (cs *awsCredentialSource) getRegion(headers map[string]string) (string, error) {
if envAwsRegion := getenv("AWS_REGION"); envAwsRegion != "" {
return envAwsRegion, nil
}
if envAwsRegion := getenv("AWS_DEFAULT_REGION"); envAwsRegion != "" {
return envAwsRegion, nil
if canRetrieveRegionFromEnvironment() {
if envAwsRegion := getenv(awsRegion); envAwsRegion != "" {
return envAwsRegion, nil
}
return getenv("AWS_DEFAULT_REGION"), nil
}

if cs.RegionURL == "" {
Expand Down Expand Up @@ -434,14 +501,12 @@ func (cs *awsCredentialSource) getRegion(headers map[string]string) (string, err
}

func (cs *awsCredentialSource) getSecurityCredentials(headers map[string]string) (result awsSecurityCredentials, err error) {
if accessKeyID := getenv("AWS_ACCESS_KEY_ID"); accessKeyID != "" {
if secretAccessKey := getenv("AWS_SECRET_ACCESS_KEY"); secretAccessKey != "" {
return awsSecurityCredentials{
AccessKeyID: accessKeyID,
SecretAccessKey: secretAccessKey,
SecurityToken: getenv("AWS_SESSION_TOKEN"),
}, nil
}
if canRetrieveSecurityCredentialFromEnvironment() {
return awsSecurityCredentials{
AccessKeyID: getenv(awsAccessKeyId),
SecretAccessKey: getenv(awsSecretAccessKey),
SecurityToken: getenv(awsSessionToken),
}, nil
}

roleName, err := cs.getMetadataRoleName(headers)
Expand Down
Loading

0 comments on commit 1210e97

Please sign in to comment.