Workload identity federation and workforce identity federation for accessing Google Cloud from external identity providers including AWS, Azure, and OIDC-compatible providers.
import "golang.org/x/oauth2/google/externalaccount"The externalaccount package enables Google Cloud authentication using credentials from external identity providers:
This eliminates the need for service account keys and enables secure workload identity federation.
func NewTokenSource(ctx context.Context, conf Config) (oauth2.TokenSource, error)Returns an external account TokenSource using the provided configuration.
type Config struct {
// Audience is the STS audience containing the resource name for the workload
// identity pool or workforce pool and provider identifier (required)
Audience string
// SubjectTokenType is the STS token type based on OAuth2.0 token exchange spec (required)
SubjectTokenType string
// TokenURL is the STS token exchange endpoint (optional)
TokenURL string
// TokenInfoURL is the token_info endpoint (optional)
TokenInfoURL string
// ServiceAccountImpersonationURL is the URL for service account impersonation (optional)
ServiceAccountImpersonationURL string
// ServiceAccountImpersonationLifetimeSeconds is the impersonation token lifetime
// Default: 3600 (optional)
ServiceAccountImpersonationLifetimeSeconds int
// ClientSecret is required if token_info endpoint is used (optional)
ClientSecret string
// ClientID is required with ClientSecret (optional)
ClientID string
// CredentialSource contains information to retrieve the token (optional)
CredentialSource *CredentialSource
// QuotaProjectID is injected by gCloud (optional)
QuotaProjectID string
// Scopes contains desired scopes for the returned access token (optional)
Scopes []string
// WorkforcePoolUserProject is the workforce pool user project number (optional)
WorkforcePoolUserProject string
// SubjectTokenSupplier for OIDC/SAML credentials (optional)
SubjectTokenSupplier SubjectTokenSupplier
// AwsSecurityCredentialsSupplier for AWS credentials (optional)
AwsSecurityCredentialsSupplier AwsSecurityCredentialsSupplier
// UniverseDomain is the default service domain (optional)
UniverseDomain string
}type CredentialSource struct {
// File is the location for file sourced credentials (optional)
File string `json:"file"`
// URL is the URL to call for URL sourced credentials (optional)
URL string `json:"url"`
// Headers are headers to attach to URL requests
Headers map[string]string `json:"headers"`
// Executable configuration for executable sourced credentials (optional)
Executable *ExecutableConfig `json:"executable"`
// EnvironmentID for AWS sourced credentials (should start with "AWS") (optional)
EnvironmentID string `json:"environment_id"`
// RegionURL is the metadata URL to retrieve region from for EC2 AWS credentials
RegionURL string `json:"region_url"`
// RegionalCredVerificationURL is the AWS regional credential verification URL
RegionalCredVerificationURL string `json:"regional_cred_verification_url"`
// IMDSv2SessionTokenURL is the URL to retrieve session token for IMDSv2 in AWS
IMDSv2SessionTokenURL string `json:"imdsv2_session_token_url"`
// Format is the format type for the subject token (for File and URL sources)
Format Format `json:"format"`
}type ExecutableConfig struct {
// Command is the full command to run (must be absolute path) (required)
Command string `json:"command"`
// TimeoutMillis is timeout duration in milliseconds (default: 30000) (optional)
TimeoutMillis *int `json:"timeout_millis"`
// OutputFile is absolute path to output file for caching (optional)
OutputFile string `json:"output_file"`
}type Format struct {
// Type should be either "text" or "json"
Type string `json:"type"`
// SubjectTokenFieldName is required for JSON format
SubjectTokenFieldName string `json:"subject_token_field_name"`
}type SubjectTokenSupplier interface {
// SubjectToken returns a valid subject token or error
// The token source does not cache, so implement caching in the supplier
SubjectToken(ctx context.Context, options SupplierOptions) (string, error)
}type AwsSecurityCredentialsSupplier interface {
// AwsRegion returns the AWS region or error
AwsRegion(ctx context.Context, options SupplierOptions) (string, error)
// AwsSecurityCredentials returns valid AWS security credentials or error
// Implement caching in the supplier to prevent multiple requests
AwsSecurityCredentials(ctx context.Context, options SupplierOptions) (*AwsSecurityCredentials, error)
}type AwsSecurityCredentials struct {
// AccessKeyID is the AWS Access Key ID (required)
AccessKeyID string `json:"AccessKeyID"`
// SecretAccessKey is the AWS Secret Access Key (required)
SecretAccessKey string `json:"SecretAccessKey"`
// SessionToken is the AWS Session token for temporary credentials (optional)
SessionToken string `json:"Token"`
}type SupplierOptions struct {
// Audience is the requested audience for the external account credential
Audience string
// SubjectTokenType is the requested subject token type
SubjectTokenType string
}package main
import (
"context"
"encoding/json"
"io/ioutil"
"log"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google/externalaccount"
)
func main() {
ctx := context.Background()
// Load external account configuration from JSON file
data, err := ioutil.ReadFile("aws-credentials-config.json")
if err != nil {
log.Fatal(err)
}
var config externalaccount.Config
if err := json.Unmarshal(data, &config); err != nil {
log.Fatal(err)
}
// Create token source
ts, err := externalaccount.NewTokenSource(ctx, config)
if err != nil {
log.Fatal(err)
}
// Create authenticated client
client := oauth2.NewClient(ctx, ts)
// Use client for Google Cloud API requests
resp, err := client.Get("https://cloudresourcemanager.googleapis.com/v1/projects")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// ...
}type CustomTokenSupplier struct {
// Custom fields
}
func (s *CustomTokenSupplier) SubjectToken(ctx context.Context, options externalaccount.SupplierOptions) (string, error) {
// Fetch token from your identity provider
token, err := fetchTokenFromProvider(ctx)
if err != nil {
return "", err
}
return token, nil
}
// Use custom supplier
config := externalaccount.Config{
Audience: "//iam.googleapis.com/projects/PROJECT_NUM/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID",
SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
TokenURL: "https://sts.googleapis.com/v1/token",
SubjectTokenSupplier: &CustomTokenSupplier{},
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
}
ts, err := externalaccount.NewTokenSource(ctx, config)type AWSCredsSupplier struct {
region string
credentials *externalaccount.AwsSecurityCredentials
mu sync.Mutex
}
func (s *AWSCredsSupplier) AwsRegion(ctx context.Context, options externalaccount.SupplierOptions) (string, error) {
return s.region, nil
}
func (s *AWSCredsSupplier) AwsSecurityCredentials(ctx context.Context, options externalaccount.SupplierOptions) (*externalaccount.AwsSecurityCredentials, error) {
s.mu.Lock()
defer s.mu.Unlock()
// Implement caching logic
if s.credentials != nil && !s.isExpired() {
return s.credentials, nil
}
// Fetch new credentials from AWS
creds, err := s.fetchAWSCredentials(ctx)
if err != nil {
return nil, err
}
s.credentials = creds
return creds, nil
}
// Use supplier
config := externalaccount.Config{
Audience: "//iam.googleapis.com/projects/PROJECT_NUM/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID",
SubjectTokenType: "urn:ietf:params:aws:token-type:aws4_request",
TokenURL: "https://sts.googleapis.com/v1/token",
AwsSecurityCredentialsSupplier: &AWSCredsSupplier{region: "us-east-1"},
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
}External account configurations are typically stored in JSON files:
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/PROJECT_NUM/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"file": "/path/to/token/file"
},
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/SA_EMAIL:generateAccessToken"
}// JWT tokens
"urn:ietf:params:oauth:token-type:jwt"
// ID tokens
"urn:ietf:params:oauth:token-type:id_token"
// SAML 2.0 assertions
"urn:ietf:params:oauth:token-type:saml2"
// AWS signatures
"urn:ietf:params:aws:token-type:aws4_request"ts, err := externalaccount.NewTokenSource(ctx, config)
if err != nil {
log.Fatalf("Failed to create token source: %v", err)
}
token, err := ts.Token()
if err != nil {
if authErr, ok := err.(*google.AuthenticationError); ok {
if authErr.Temporary() {
// Retry
}
}
log.Fatalf("Failed to get token: %v", err)
}