1+ package com .google .cloud .auth .samples ;
2+
3+ /*
4+ * Copyright 2025 Google LLC
5+ *
6+ * Licensed under the Apache License, Version 2.0 (the "License");
7+ * you may not use this file except in compliance with the License.
8+ * You may obtain a copy of the License at
9+ *
10+ * http://www.apache.org/licenses/LICENSE-2.0
11+ *
12+ * Unless required by applicable law or agreed to in writing, software
13+ * distributed under the License is distributed on an "AS IS" BASIS,
14+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+ * See the License for the specific language governing permissions and
16+ * limitations under the License.
17+ */
18+
19+ // [START auth_custom_credential_supplier_aws]
20+ import com .google .auth .oauth2 .AwsCredentials ;
21+ import com .google .auth .oauth2 .AwsSecurityCredentials ;
22+ import com .google .auth .oauth2 .AwsSecurityCredentialsSupplier ;
23+ import com .google .auth .oauth2 .ExternalAccountSupplierContext ;
24+ import com .google .auth .oauth2 .GoogleCredentials ;
25+ import com .google .cloud .storage .Bucket ;
26+ import com .google .cloud .storage .Storage ;
27+ import com .google .cloud .storage .StorageOptions ;
28+ import java .io .IOException ;
29+ import software .amazon .awssdk .auth .credentials .AwsCredentialsProvider ;
30+ import software .amazon .awssdk .auth .credentials .AwsSessionCredentials ;
31+ import software .amazon .awssdk .auth .credentials .DefaultCredentialsProvider ;
32+ import software .amazon .awssdk .regions .Region ;
33+ import software .amazon .awssdk .regions .providers .DefaultAwsRegionProviderChain ;
34+ // [END auth_custom_credential_supplier_aws]
35+
36+ /**
37+ * This sample demonstrates how to use a custom AWS security credentials supplier to authenticate to
38+ * Google Cloud Storage using AWS Workload Identity Federation.
39+ */
40+ public class CustomCredentialSupplierAwsWorkload {
41+
42+ public static void main (String [] args ) throws IOException {
43+ // The audience for the workload identity federation.
44+ // Format:
45+ // //iam.googleapis.com/projects/<project-number>/locations/global/workloadIdentityPools/<pool-id>/providers/<provider-id>
46+ String gcpWorkloadAudience = System .getenv ("GCP_WORKLOAD_AUDIENCE" );
47+
48+ // The bucket to fetch data from.
49+ String gcsBucketName = System .getenv ("GCS_BUCKET_NAME" );
50+
51+ // (Optional) The service account impersonation URL.
52+ String saImpersonationUrl = System .getenv ("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL" );
53+
54+ if (gcpWorkloadAudience == null || gcsBucketName == null ) {
55+ System .err .println (
56+ "Error: GCP_WORKLOAD_AUDIENCE and GCS_BUCKET_NAME environment variables are required." );
57+ return ;
58+ }
59+
60+ System .out .println ("Getting metadata for bucket: " + gcsBucketName + "..." );
61+ Bucket bucket =
62+ authenticateWithAwsCredentials (gcpWorkloadAudience , saImpersonationUrl , gcsBucketName );
63+
64+ System .out .println (" --- SUCCESS! ---" );
65+ System .out .printf ("Bucket Name: %s%n" , bucket .getName ());
66+ System .out .printf ("Bucket Location: %s%n" , bucket .getLocation ());
67+ }
68+
69+ /**
70+ * Authenticates using a custom AWS credential supplier and retrieves bucket metadata.
71+ *
72+ * @param gcpWorkloadAudience The WIF provider audience.
73+ * @param saImpersonationUrl Optional service account impersonation URL.
74+ * @param gcsBucketName The GCS bucket name.
75+ * @return The Bucket object containing metadata.
76+ * @throws IOException If authentication fails.
77+ */
78+ // [START auth_custom_credential_supplier_aws]
79+ public static Bucket authenticateWithAwsCredentials (
80+ String gcpWorkloadAudience , String saImpersonationUrl , String gcsBucketName )
81+ throws IOException {
82+
83+ // 1. Instantiate the custom supplier.
84+ CustomAwsSupplier customSupplier = new CustomAwsSupplier ();
85+
86+ // 2. Configure the AwsCredentials options.
87+ AwsCredentials .Builder credentialsBuilder =
88+ AwsCredentials .newBuilder ()
89+ .setAudience (gcpWorkloadAudience )
90+ // This token type indicates that the subject token is an AWS Signature Version 4 signed
91+ // request. This is required for AWS Workload Identity Federation.
92+ .setSubjectTokenType ("urn:ietf:params:aws:token-type:aws4_request" )
93+ .setAwsSecurityCredentialsSupplier (customSupplier );
94+
95+ if (saImpersonationUrl != null ) {
96+ credentialsBuilder .setServiceAccountImpersonationUrl (saImpersonationUrl );
97+ }
98+
99+ GoogleCredentials credentials = credentialsBuilder .build ();
100+
101+ // 3. Use the credentials to make an authenticated request.
102+ Storage storage = StorageOptions .newBuilder ().setCredentials (credentials ).build ().getService ();
103+
104+ return storage .get (gcsBucketName );
105+ }
106+
107+ /**
108+ * Custom AWS Security Credentials Supplier.
109+ *
110+ * <p>This implementation resolves AWS credentials and regions using the default provider chains
111+ * from the AWS SDK (v2). This supports environment variables, ~/.aws/credentials, and EC2/EKS
112+ * metadata.
113+ */
114+ private static class CustomAwsSupplier implements AwsSecurityCredentialsSupplier {
115+ private final AwsCredentialsProvider awsCredentialsProvider ;
116+ private String region ;
117+
118+ public CustomAwsSupplier () {
119+ // The AWS SDK handles memoization and refreshing internally.
120+ this .awsCredentialsProvider = DefaultCredentialsProvider .create ();
121+ }
122+
123+ @ Override
124+ public String getRegion (ExternalAccountSupplierContext context ) {
125+ if (this .region == null ) {
126+ Region awsRegion = new DefaultAwsRegionProviderChain ().getRegion ();
127+ if (awsRegion == null ) {
128+ throw new IllegalStateException (
129+ "Unable to resolve AWS region. Ensure AWS_REGION is set or configured." );
130+ }
131+ this .region = awsRegion .id ();
132+ }
133+ return this .region ;
134+ }
135+
136+ @ Override
137+ public AwsSecurityCredentials getCredentials (ExternalAccountSupplierContext context ) {
138+ software .amazon .awssdk .auth .credentials .AwsCredentials credentials =
139+ this .awsCredentialsProvider .resolveCredentials ();
140+
141+ if (credentials == null ) {
142+ throw new IllegalStateException ("Unable to resolve AWS credentials." );
143+ }
144+
145+ String sessionToken = null ;
146+ if (credentials instanceof AwsSessionCredentials ) {
147+ sessionToken = ((AwsSessionCredentials ) credentials ).sessionToken ();
148+ }
149+
150+ return new AwsSecurityCredentials (
151+ credentials .accessKeyId (), credentials .secretAccessKey (), sessionToken );
152+ }
153+ }
154+ // [END auth_custom_credential_supplier_aws]
155+ }
0 commit comments