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
51 changes: 4 additions & 47 deletions auth/credentials/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,53 +25,6 @@
// For more information on using workload identity federation, refer to
// https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation.
//
// # 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:
//
// - [Amazon Web Services (AWS)](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds#aws)
// - [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
//
// # Security considerations
//
// 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
//
// The [cloud.google.com/go/auth.Credentials] type represents Google
Expand All @@ -85,4 +38,8 @@
// OpenID Connect (OIDC). Workload identity federation is recommended for
// non-Google Cloud environments as it avoids the need to download, manage, and
// store service account private keys locally.
//
// # Workforce Identity Federation
//
// For more information on this feature see [cloud.google.com/go/auth/credentials/externalaccount].
package credentials
51 changes: 51 additions & 0 deletions auth/credentials/externalaccount/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Package externalaccount provides support for creating workload identity
// federation and workforce identity federation token sources that can be used
codyoss marked this conversation as resolved.
Show resolved Hide resolved
// to access Google Cloud resources from external identity providers.
//
// # 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:
//
// - [Amazon Web Services (AWS)](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds#aws)
// - [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
//
// # Security considerations
//
// 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.
package externalaccount
227 changes: 227 additions & 0 deletions auth/credentials/externalaccount/externalaccount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package externalaccount

import (
"net/http"

"cloud.google.com/go/auth"
iexacc "cloud.google.com/go/auth/credentials/internal/externalaccount"
"cloud.google.com/go/auth/internal"
"cloud.google.com/go/auth/internal/credsfile"
)

type Options struct {

Check failure on line 12 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.
Audience string
// SubjectTokenType is the STS token type based on the Oauth2.0 token
// exchange spec. Expected values include:
// - “urn:ietf:params:oauth:token-type:jwt”
// - “urn:ietf:params:oauth:token-type:id-token”
// - “urn:ietf:params:oauth:token-type:saml2”
// - “urn:ietf:params:aws:token-type:aws4_request”
// Required.
SubjectTokenType string
// TokenURL is the STS token exchange endpoint. If not provided, will
// default to https://sts.UNIVERSE_DOMAIN/v1/token, with UNIVERSE_DOMAIN set
// to the default service domain googleapis.com unless UniverseDomain is
// set. Optional.
TokenURL string
// TokenInfoURL is the token_info endpoint used to retrieve the account
// related information (user attributes like account identifier, eg. email,
// username, uid, etc). This is needed for gCloud session account
// identification. Optional.
TokenInfoURL string
// ServiceAccountImpersonationURL is the URL for the service account
// impersonation request. This is only required for workload identity pools
// when APIs to be accessed have not integrated with UberMint.
ServiceAccountImpersonationURL string
// ServiceAccountImpersonationLifetimeSeconds is the number of seconds the
// service account impersonation token will be valid for.
ServiceAccountImpersonationLifetimeSeconds int
// ClientSecret is currently only required if token_info endpoint also
// needs to be called with the generated GCP access token. When provided,
// STS will be called with additional basic authentication using client_id
// as username and client_secret as password. Optional.
ClientSecret string
// ClientID is only required in conjunction with ClientSecret, as described
// above. Optional.
ClientID string
// CredentialSource contains the necessary information to retrieve the token
// itself, as well as some environmental information. Optional.
CredentialSource *CredentialSource
// QuotaProjectID is injected by gCloud. If the value is non-empty, the Auth
// libraries will set the x-goog-user-project which overrides the project
// associated with the credentials. Optional.
QuotaProjectID string
// Scopes contains the desired scopes for the returned access token.
// Optional.
Scopes []string
// WorkforcePoolUserProject should be set when it is a workforce pool and
// not a workload identity pool. The underlying principal must still have
// serviceusage.services.use IAM permission to use the project for
// billing/quota. Optional.
WorkforcePoolUserProject string
// UniverseDomain is the default service domain for a given Cloud universe.
// 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
// TODO(codyoss)
// SubjectTokenSupplier is an optional token supplier for OIDC/SAML credentials.
// One of SubjectTokenSupplier, AWSSecurityCredentialSupplier or CredentialSource must be provided. Optional.
//SubjectTokenSupplier SubjectTokenSupplier
// AwsSecurityCredentialsSupplier is an AWS Security Credential supplier for AWS credentials.
// One of SubjectTokenSupplier, AWSSecurityCredentialSupplier or CredentialSource must be provided. Optional.
//AwsSecurityCredentialsSupplier AwsSecurityCredentialsSupplier

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

type CredentialSource struct {

Check failure on line 82 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.
File string
// Url is the URL to call for URL sourced credentials.
// One field amongst File, URL, Executable, or EnvironmentID should be
// provided, depending on the kind of credential in question.
URL string
// Executable is the configuration object for executable sourced credentials.
// One field amongst File, URL, Executable, or EnvironmentID should be
// provided, depending on the kind of credential in question.
Executable *ExecutableConfig
// EnvironmentID is the EnvironmentID used for AWS sourced credentials.
// This should start with "AWS".
// One field amongst File, URL, Executable, or EnvironmentID should be provided, depending on the kind of credential in question.
EnvironmentID string

// Headers are the headers to attach to the request for URL sourced
// credentials.
Headers map[string]string
// RegionURL is the metadata URL to retrieve the region from for EC2 AWS
// credentials.
RegionURL string
// RegionalCredVerificationURL is the AWS regional credential verification
// URL, will default to `https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15`
// if not provided.
RegionalCredVerificationURL string
// IMDSv2SessionTokenURL is the URL to retrieve the session token when using
// IMDSv2 in AWS.
IMDSv2SessionTokenURL string
// Format is the format type for the subject token. Used for File and URL
// sourced credentials.
Format *Format
}

// Format contains information needed to retrieve a subject token for URL or
// File sourced credentials.
type Format struct {
quartzmo marked this conversation as resolved.
Show resolved Hide resolved
// Type should be either "text" or "json". This determines whether the file
// or URL sourced credentials expect a simple text subject token or if the
// subject token will be contained in a JSON object. When not provided
// "text" type is assumed.
Type string
// SubjectTokenFieldName is only required for JSON format. This is the field
// name that the credentials will check for the subject token in the file or
// URL response. This would be "access_token" for azure.
SubjectTokenFieldName string
}

// ExecutableConfig contains information needed for executable sourced credentials.
type ExecutableConfig struct {
// Command is the the full command to run to retrieve the subject token.
// This can include arguments. Must be an absolute path for the program. Required.
Command string
// TimeoutMillis is the timeout duration, in milliseconds. Defaults to 30000 milliseconds when not provided. Optional.
TimeoutMillis *int
// OutputFile is the absolute path to the output file where the executable will cache the response.
// If specified the auth libraries will first check this location before running the executable. Optional.
OutputFile string
}

func (o *Options) validate() error {
return nil
}

func (o *Options) client() *http.Client {
if o.Client != nil {
return o.Client
}
return internal.CloneDefaultClient()
}

func (o *Options) toInternalOpts() *iexacc.Options {
if o == nil {
return nil
}
iOpts := &iexacc.Options{
Audience: o.Audience,
SubjectTokenType: o.SubjectTokenType,
TokenURL: o.TokenURL,
TokenInfoURL: o.TokenInfoURL,
ServiceAccountImpersonationURL: o.ServiceAccountImpersonationURL,
ServiceAccountImpersonationLifetimeSeconds: o.ServiceAccountImpersonationLifetimeSeconds,
ClientSecret: o.ClientSecret,
ClientID: o.ClientID,
QuotaProjectID: o.QuotaProjectID,
Scopes: o.Scopes,
WorkforcePoolUserProject: o.WorkforcePoolUserProject,
UniverseDomain: o.UniverseDomain,
Client: o.client(),
}
if o.CredentialSource != nil {
cs := o.CredentialSource
iOpts.CredentialSource = credsfile.CredentialSource{
File: cs.File,
URL: cs.URL,
Headers: cs.Headers,
EnvironmentID: cs.EnvironmentID,
RegionURL: cs.RegionURL,
RegionalCredVerificationURL: cs.RegionalCredVerificationURL,
CredVerificationURL: cs.URL,
IMDSv2SessionTokenURL: cs.IMDSv2SessionTokenURL,
}
if cs.Executable != nil {
cse := cs.Executable
iOpts.CredentialSource.Executable = &credsfile.ExecutableConfig{
Command: cse.Command,
TimeoutMillis: cse.TimeoutMillis,
OutputFile: cse.OutputFile,
}
}
if cs.Format != nil {
csf := cs.Format
iOpts.CredentialSource.Format = &credsfile.Format{
Type: csf.Type,
SubjectTokenFieldName: csf.SubjectTokenFieldName,
}
}
}
return iOpts
}

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

Check failure on line 205 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
}

tp, err := iexacc.NewTokenProvider(opts.toInternalOpts())
if err != nil {
return nil, err
}

var udp, qpp auth.CredentialsPropertyProvider
if opts.UniverseDomain != "" {
udp = internal.StaticCredentialsProperty(opts.UniverseDomain)
}
if opts.QuotaProjectID != "" {
qpp = internal.StaticCredentialsProperty(opts.QuotaProjectID)
}
return auth.NewCredentials(&auth.CredentialsOptions{
TokenProvider: auth.NewCachedTokenProvider(tp, nil),
UniverseDomainProvider: udp,
QuotaProjectIDProvider: qpp,
}), nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ type Options struct {
// serviceusage.services.use IAM permission to use the project for
// billing/quota. Optional.
WorkforcePoolUserProject string
// UniverseDomain is the default service domain for a given Cloud universe.
// 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
// Client for token request.
Client *http.Client
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ var (
}
testBaseCredSource = credsfile.CredentialSource{
File: textBaseCredPath,
Format: credsfile.Format{Type: fileTypeText},
Format: &credsfile.Format{Type: fileTypeText},
}
testNow = func() time.Time { return time.Unix(expiry, 0) }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const (

type fileSubjectProvider struct {
File string
Format credsfile.Format
Format *credsfile.Format
}

func (sp *fileSubjectProvider) subjectToken(context.Context) (string, error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ func TestRetrieveFileSubjectToken(t *testing.T) {
name: "text file format",
cs: credsfile.CredentialSource{
File: textBaseCredPath,
Format: credsfile.Format{Type: fileTypeText},
Format: &credsfile.Format{Type: fileTypeText},
},
want: "street123",
},
{
name: "JSON file format",
cs: credsfile.CredentialSource{
File: jsonBaseCredPath,
Format: credsfile.Format{Type: fileTypeJSON, SubjectTokenFieldName: "SubjToken"},
Format: &credsfile.Format{Type: fileTypeJSON, SubjectTokenFieldName: "SubjToken"},
},
want: "321road",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const (
type urlSubjectProvider struct {
URL string
Headers map[string]string
Format credsfile.Format
Format *credsfile.Format
Client *http.Client
}

Expand Down