Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"node/no-unsupported-features/es-syntax": ["off"]
},
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
}
}
6 changes: 6 additions & 0 deletions auth/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "../.eslintrc.json",
"rules": {
"no-unused-vars": "off"
}
}
73 changes: 72 additions & 1 deletion auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,79 @@ information](https://developers.google.com/identity/protocols/application-defaul

$ npm run test:downscoping

## Custom Credential Suppliers

If you want to use external credentials (like AWS or Okta) that require custom retrieval logic not supported natively by the library, you can provide a custom supplier implementation.

### Custom AWS Credential Supplier

This sample demonstrates how to use the AWS SDK for Node.js as a custom `AwsSecurityCredentialsSupplier` to bridge AWS credentials—from sources like EKS IRSA, ECS, or local profiles—to Google Cloud Workload Identity.

#### 1. Set Environment Variables

```bash
# AWS Credentials (or use ~/.aws/credentials)
export AWS_ACCESS_KEY_ID="YOUR_AWS_ACCESS_KEY_ID"
export AWS_SECRET_ACCESS_KEY="YOUR_AWS_SECRET_ACCESS_KEY"
export AWS_REGION="us-east-1"

# Google Cloud Config
# Format: //iam.googleapis.com/projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/<POOL_ID>/providers/<PROVIDER_ID>
export GCP_WORKLOAD_AUDIENCE="//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/my-pool/providers/my-aws-provider"
export GCS_BUCKET_NAME="your-bucket-name"

# Optional: Service Account Impersonation
# export GCP_SERVICE_ACCOUNT_IMPERSONATION_URL="https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateAccessToken"
```

#### 2. Run the Sample

```bash
node custom-credential-supplier-aws.js
```

#### Running in Kubernetes (EKS)

To run this in an EKS cluster using IAM Roles for Service Accounts (IRSA):

1. **Configure IRSA:** Associate an AWS IAM Role with your Kubernetes Service Account.
2. **Configure GCP:** Allow the AWS IAM Role ARN to impersonate your Workload Identity Pool.
3. **Deploy:** When deploying your Node.js application, ensure the Pod uses the annotated Service Account. The AWS SDK in the sample will automatically detect the credentials injected by the EKS OIDC webhook.

---

### Custom Okta Credential Supplier

This sample demonstrates how to use a custom `SubjectTokenSupplier` to fetch an OIDC token from **Okta** using the Client Credentials flow and exchange it for Google Cloud credentials via Workload Identity Federation.

#### 1. Okta Configuration

Ensure you have an Okta Machine-to-Machine (M2M) application set up with "Client Credentials" grant type enabled. You will need the Domain, Client ID, and Client Secret.

#### 2. Set Environment Variables

```bash
# Okta Configuration
export OKTA_DOMAIN="https://your-okta-domain.okta.com"
export OKTA_CLIENT_ID="your-okta-client-id"
export OKTA_CLIENT_SECRET="your-okta-client-secret"

# Google Cloud Config
export GCP_WORKLOAD_AUDIENCE="//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/my-pool/providers/my-oidc-provider"
export GCS_BUCKET_NAME="your-bucket-name"

# Optional: Service Account Impersonation
# export GCP_SERVICE_ACCOUNT_IMPERSONATION_URL="https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateAccessToken"
```

#### 3. Run the Sample

```bash
node custom-credential-supplier-okta.js
```

### Additional resources

For more information on downscoped credentials you can visit:

> https://github.com/googleapis/google-auth-library-nodejs
> https://github.com/googleapis/google-auth-library-nodejs
150 changes: 150 additions & 0 deletions auth/customCredentialSupplierAws.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// [START auth_custom_credential_supplier_aws]
const {AwsClient} = require('google-auth-library');
const {fromNodeProviderChain} = require('@aws-sdk/credential-providers');
const {STSClient} = require('@aws-sdk/client-sts');

/**
* Custom AWS Security Credentials Supplier.
*
* This implementation resolves AWS credentials using the default Node provider
* chain from the AWS SDK. This allows fetching credentials from environment
* variables, shared credential files (~/.aws/credentials), or IAM roles
* for service accounts (IRSA) in EKS, etc.
*/
class CustomAwsSupplier {
constructor() {
// Will be cached upon first resolution.
this.region = null;

// Initialize the AWS credential provider.
// The AWS SDK handles memoization (caching) and proactive refreshing internally.
this.awsCredentialsProvider = fromNodeProviderChain();
}

/**
* Returns the AWS region. This is required for signing the AWS request.
* It resolves the region automatically by using the default AWS region
* provider chain, which searches for the region in the standard locations
* (environment variables, AWS config file, etc.).
*/
async getAwsRegion(_context) {
if (this.region) {
return this.region;
}

const client = new STSClient({});
this.region = await client.config.region();

if (!this.region) {
throw new Error(
'CustomAwsSupplier: Unable to resolve AWS region. Please set the AWS_REGION environment variable or configure it in your ~/.aws/config file.'
);
}

return this.region;
}

/**
* Retrieves AWS security credentials using the AWS SDK's default provider chain.
*/
async getAwsSecurityCredentials(_context) {
// Call the initialized provider. It will return cached creds or refresh if needed.
const awsCredentials = await this.awsCredentialsProvider();

if (!awsCredentials.accessKeyId || !awsCredentials.secretAccessKey) {
throw new Error(
'Unable to resolve AWS credentials from the node provider chain. ' +
'Ensure your AWS CLI is configured, or AWS environment variables (like AWS_ACCESS_KEY_ID) are set.'
);
}

// Map the AWS SDK format to the google-auth-library format.
return {
accessKeyId: awsCredentials.accessKeyId,
secretAccessKey: awsCredentials.secretAccessKey,
token: awsCredentials.sessionToken,
};
}
}

/**
* Authenticates with Google Cloud using AWS credentials and retrieves bucket metadata.
*
* @param {string} bucketName The name of the bucket to retrieve.
* @param {string} audience The Workload Identity Pool audience.
* @param {string} [impersonationUrl] Optional Service Account impersonation URL.
*/
async function authenticateWithAwsCredentials(
bucketName,
audience,
impersonationUrl
) {
// 1. Instantiate the custom supplier.
const customSupplier = new CustomAwsSupplier();

// 2. Configure the AwsClient options.
const clientOptions = {
audience: audience,
subject_token_type: 'urn:ietf:params:aws:token-type:aws4_request',
service_account_impersonation_url: impersonationUrl,
aws_security_credentials_supplier: customSupplier,
};

// 3. Create the auth client
const client = new AwsClient(clientOptions);

// 4. Make an authenticated request to GCS.
const bucketUrl = `https://storage.googleapis.com/storage/v1/b/${bucketName}`;
const res = await client.request({url: bucketUrl});
return res.data;
}
// [END auth_custom_credential_supplier_aws]

async function main() {
require('dotenv').config();
const gcpAudience = process.env.GCP_WORKLOAD_AUDIENCE;
const saImpersonationUrl = process.env.GCP_SERVICE_ACCOUNT_IMPERSONATION_URL;
const gcsBucketName = process.env.GCS_BUCKET_NAME;

if (!gcpAudience || !gcsBucketName) {
throw new Error(
'Missing required environment variables: GCP_WORKLOAD_AUDIENCE, GCS_BUCKET_NAME'
);
}

try {
console.log(`Retrieving metadata for bucket: ${gcsBucketName}...`);
const bucketMetadata = await authenticateWithAwsCredentials(
gcsBucketName,
gcpAudience,
saImpersonationUrl
);
console.log('\n--- SUCCESS! ---');
console.log('Bucket Name:', bucketMetadata.name);
console.log('Bucket Location:', bucketMetadata.location);
} catch (error) {
console.error('\n--- FAILED ---');
console.error(error.response?.data || error);
process.exitCode = 1;
}
}

if (require.main === module) {
main();
}

exports.authenticateWithAwsCredentials = authenticateWithAwsCredentials;
Loading
Loading