Skip to content

Commit 7af2e3b

Browse files
committed
Changed the custom credential scripts to use the secrets.json file to read properties.
1 parent a9b2c59 commit 7af2e3b

14 files changed

+664
-232
lines changed

auth/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Ignore GCP and IdP secret files
2+
src/main/java/com/google/cloud/auth/samples/customcredentials/aws/custom-credentials-aws-secrets.json
3+
src/main/java/com/google/cloud/auth/samples/customcredentials/okta/custom-credentials-okta-secrets.json

auth/README.md

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -67,51 +67,6 @@ You can then run `DownscopingExample` via:
6767

6868
mvn exec:java -Dexec.mainClass=com.google.cloud.auth.samples.DownscopingExample
6969

70-
## Custom Credential Suppliers
71-
72-
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.
73-
74-
### Authenticate with Okta (Custom Supplier)
75-
76-
This sample demonstrates how to use a custom `IdentityPoolSubjectTokenSupplier` to fetch an OIDC token from Okta using the Client Credentials flow and exchange it for Google Cloud credentials.
77-
78-
1. **Set required environment variables:**
79-
```bash
80-
export OKTA_DOMAIN="https://your-domain.okta.com"
81-
export OKTA_CLIENT_ID="your-client-id"
82-
export OKTA_CLIENT_SECRET="your-client-secret"
83-
export GCP_WORKLOAD_AUDIENCE="//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/my-pool/providers/my-provider"
84-
export GCS_BUCKET_NAME="your-bucket-name"
85-
# Optional:
86-
# export GCP_SERVICE_ACCOUNT_IMPERSONATION_URL="..."
87-
```
88-
89-
2. **Run the sample:**
90-
```bash
91-
mvn exec:java -Dexec.mainClass=com.google.cloud.auth.samples.CustomCredentialSupplierOktaWorkload
92-
```
93-
94-
### Authenticate with AWS (Custom Supplier)
95-
96-
This sample demonstrates how to use the **AWS SDK for Java (v2)** as a custom `AwsSecurityCredentialsSupplier` to bridge AWS credentials (from environment, `~/.aws/credentials`, or EKS/ECS metadata) to Google Cloud Workload Identity.
97-
98-
1. **Set required environment variables:**
99-
```bash
100-
# Google Cloud Config
101-
export GCP_WORKLOAD_AUDIENCE="//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/my-pool/providers/my-aws-provider"
102-
export GCS_BUCKET_NAME="your-bucket-name"
103-
104-
# AWS Credentials (or use ~/.aws/credentials)
105-
export AWS_ACCESS_KEY_ID="your-aws-key"
106-
export AWS_SECRET_ACCESS_KEY="your-aws-secret"
107-
export AWS_REGION="us-east-1"
108-
```
109-
110-
2. **Run the sample:**
111-
```bash
112-
mvn exec:java -Dexec.mainClass=com.google.cloud.auth.samples.CustomCredentialSupplierAwsWorkload
113-
```
114-
11570
## Tests
11671
Run all tests:
11772
```

auth/src/main/java/com/google/cloud/auth/samples/CustomCredentialSupplierAwsWorkload.java renamed to auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/CustomCredentialSupplierAwsWorkload.java

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.google.cloud.auth.samples;
17+
package com.google.cloud.auth.samples.customcredentials.aws;
1818

1919
// [START auth_custom_credential_supplier_aws]
2020
import com.google.auth.oauth2.AwsCredentials;
@@ -25,7 +25,14 @@
2525
import com.google.cloud.storage.Bucket;
2626
import com.google.cloud.storage.Storage;
2727
import com.google.cloud.storage.StorageOptions;
28+
import com.google.gson.Gson;
29+
import com.google.gson.reflect.TypeToken;
2830
import java.io.IOException;
31+
import java.io.Reader;
32+
import java.lang.reflect.Type;
33+
import java.nio.file.Files;
34+
import java.nio.file.Paths;
35+
import java.util.Map;
2936
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
3037
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
3138
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
@@ -41,30 +48,106 @@
4148
public class CustomCredentialSupplierAwsWorkload {
4249

4350
public static void main(String[] args) throws IOException {
51+
52+
// Reads the custom-credentials-aws-secrets.json if running locally.
53+
loadConfigFromFile();
54+
4455
// The audience for the workload identity federation.
4556
// Format: //iam.googleapis.com/projects/<project-number>/locations/global/
4657
// workloadIdentityPools/<pool-id>/providers/<provider-id>
47-
String gcpWorkloadAudience = System.getenv("GCP_WORKLOAD_AUDIENCE");
58+
String gcpWorkloadAudience = getConfiguration("GCP_WORKLOAD_AUDIENCE");
4859

4960
// The bucket to fetch data from.
50-
String gcsBucketName = System.getenv("GCS_BUCKET_NAME");
61+
String gcsBucketName = getConfiguration("GCS_BUCKET_NAME");
5162

5263
// (Optional) The service account impersonation URL.
53-
String saImpersonationUrl = System.getenv("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL");
64+
String saImpersonationUrl = getConfiguration("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL");
5465

5566
if (gcpWorkloadAudience == null || gcsBucketName == null) {
5667
System.err.println(
57-
"Error: GCP_WORKLOAD_AUDIENCE and GCS_BUCKET_NAME environment variables are required.");
68+
"Required configuration missing. Please provide it in a "
69+
+ "custom-credentials-aws-secrets.json file or as environment variables: "
70+
+ "GCP_WORKLOAD_AUDIENCE, GCS_BUCKET_NAME");
71+
return;
72+
}
73+
74+
try {
75+
System.out.println("Retrieving metadata for bucket: " + gcsBucketName + "...");
76+
Bucket bucket =
77+
authenticateWithAwsCredentials(gcpWorkloadAudience, saImpersonationUrl, gcsBucketName);
78+
79+
System.out.println(" --- SUCCESS! ---");
80+
System.out.println("Bucket details:");
81+
System.out.printf(" Name: %s%n", bucket.getName());
82+
System.out.printf(" Location: %s%n", bucket.getLocation());
83+
System.out.printf(" Storage Class: %s%n", bucket.getStorageClass());
84+
System.out.printf(" Metageneration: %s%n", bucket.getMetageneration());
85+
} catch (Exception e) {
86+
System.err.println("Authentication or Request failed: " + e.getMessage());
87+
}
88+
}
89+
90+
/**
91+
* Helper method to retrieve configuration. It checks Environment variables first, then System
92+
* properties (populated by loadConfigFromFile).
93+
*/
94+
static String getConfiguration(String key) {
95+
String value = System.getenv(key);
96+
if (value == null) {
97+
value = System.getProperty(key);
98+
}
99+
return value;
100+
}
101+
102+
/**
103+
* If a local secrets file is present, load it into the System Properties. This is a
104+
* "just-in-time" configuration for local development. These variables are only set for the
105+
* current process.
106+
*/
107+
static void loadConfigFromFile() {
108+
String secretsFile = "custom-credentials-aws-secrets.json";
109+
if (!Files.exists(Paths.get(secretsFile))) {
58110
return;
59111
}
60112

61-
System.out.println("Getting metadata for bucket: " + gcsBucketName + "...");
62-
Bucket bucket =
63-
authenticateWithAwsCredentials(gcpWorkloadAudience, saImpersonationUrl, gcsBucketName);
113+
try (Reader reader = Files.newBufferedReader(Paths.get(secretsFile))) {
114+
// Use Gson to parse the JSON file into a Map
115+
Gson gson = new Gson();
116+
Type type = new TypeToken<Map<String, String>>() {}.getType();
117+
Map<String, String> secrets = gson.fromJson(reader, type);
118+
119+
if (secrets == null) {
120+
return;
121+
}
64122

65-
System.out.println(" --- SUCCESS! ---");
66-
System.out.printf("Bucket Name: %s%n", bucket.getName());
67-
System.out.printf("Bucket Location: %s%n", bucket.getLocation());
123+
// AWS SDK for Java looks for System Properties with specific names (camelCase)
124+
// if environment variables are missing.
125+
if (secrets.containsKey("aws_access_key_id")) {
126+
System.setProperty("aws.accessKeyId", secrets.get("aws_access_key_id"));
127+
}
128+
if (secrets.containsKey("aws_secret_access_key")) {
129+
System.setProperty("aws.secretAccessKey", secrets.get("aws_secret_access_key"));
130+
}
131+
if (secrets.containsKey("aws_region")) {
132+
System.setProperty("aws.region", secrets.get("aws_region"));
133+
}
134+
135+
// Set custom GCP variables as System Properties so getConfiguration() can find them.
136+
if (secrets.containsKey("gcp_workload_audience")) {
137+
System.setProperty("GCP_WORKLOAD_AUDIENCE", secrets.get("gcp_workload_audience"));
138+
}
139+
if (secrets.containsKey("gcs_bucket_name")) {
140+
System.setProperty("GCS_BUCKET_NAME", secrets.get("gcs_bucket_name"));
141+
}
142+
if (secrets.containsKey("gcp_service_account_impersonation_url")) {
143+
System.setProperty(
144+
"GCP_SERVICE_ACCOUNT_IMPERSONATION_URL",
145+
secrets.get("gcp_service_account_impersonation_url"));
146+
}
147+
148+
} catch (IOException e) {
149+
System.err.println("Error reading secrets file: " + e.getMessage());
150+
}
68151
}
69152

70153
/**
@@ -117,7 +200,7 @@ private static class CustomAwsSupplier implements AwsSecurityCredentialsSupplier
117200
private String region;
118201

119202
public CustomAwsSupplier() {
120-
// The AWS SDK handles memoization and refreshing internally.
203+
// The AWS SDK handles caching internally.
121204
this.awsCredentialsProvider = DefaultCredentialsProvider.create();
122205
}
123206

@@ -153,4 +236,4 @@ public AwsSecurityCredentials getCredentials(ExternalAccountSupplierContext cont
153236
}
154237
}
155238
// [END auth_custom_credential_supplier_aws]
156-
}
239+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Build the application
2+
FROM maven:3.9-eclipse-temurin-17 AS builder
3+
4+
WORKDIR /app
5+
6+
# Copy only the build config first (for better layer caching)
7+
COPY pom.xml .
8+
COPY src ./src
9+
10+
# 'clean package': Compiles the code and creates the thin jar in /app/target
11+
# 'dependency:copy-dependencies': Copies all JARs to /app/target/libs
12+
# We explicitly set -DoutputDirectory so we know EXACTLY where they are.
13+
RUN mvn clean package dependency:copy-dependencies \
14+
-DoutputDirectory=target/libs \
15+
-DskipTests
16+
17+
# Run the application
18+
FROM eclipse-temurin:17-jre-focal
19+
20+
# Security: Create a non-root user
21+
RUN useradd -m appuser
22+
USER appuser
23+
WORKDIR /app
24+
25+
# Copy the Thin Jar (Your application code)
26+
# Ensure the name 'auth-1.0.jar' matches your pom.xml artifactId/version
27+
COPY --from=builder --chown=appuser:appuser /app/target/auth-1.0.jar app.jar
28+
29+
# Copy the Dependencies (The libraries)
30+
COPY --from=builder --chown=appuser:appuser /app/target/libs lib/
31+
32+
# Run with Classpath
33+
# We add 'app.jar' and everything in 'lib/' to the classpath.
34+
# We explicitly specify the Main Class here, so your shared pom.xml doesn't need to change.
35+
CMD ["java", "-cp", "app.jar:lib/*", "com.google.cloud.auth.samples.customcredentials.aws.CustomCredentialSupplierAwsWorkload"]
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Running the Custom AWS Credential Supplier Sample (Java)
2+
3+
This sample demonstrates how to use a custom AWS security credential supplier to authenticate with Google Cloud using AWS as an external identity provider. It uses the **AWS SDK for Java (v2)** to fetch credentials from sources like Amazon Elastic Kubernetes Service (EKS) with IAM Roles for Service Accounts (IRSA), Elastic Container Service (ECS), or Fargate.
4+
5+
## Prerequisites
6+
7+
* An AWS account.
8+
* A Google Cloud project with the IAM API enabled.
9+
* A GCS bucket.
10+
* **Java 11** or later installed.
11+
* **Maven** installed.
12+
13+
If you want to use AWS security credentials that cannot be retrieved using methods supported natively by the Google Auth library, a custom `AwsSecurityCredentialsSupplier` implementation may be specified. The supplier must return valid, unexpired AWS security credentials when called by the Google Cloud Auth library.
14+
15+
## Running Locally
16+
17+
For local development, you can provide credentials and configuration in a JSON file.
18+
19+
### Build the Project
20+
21+
Ensure you have Java and Maven installed, then build the project to download dependencies and create an executable JAR:
22+
23+
```bash
24+
mvn clean package
25+
```
26+
27+
### Configure Credentials for Local Development
28+
29+
1. Copy the example secrets file to a new file named `custom-credentials-aws-secrets.json` in the project root:
30+
```bash
31+
cp custom-credentials-aws-secrets.json.example custom-credentials-aws-secrets.json
32+
```
33+
2. Open `custom-credentials-aws-secrets.json` and fill in the required values for your AWS and Google Cloud configuration. Do not check your `custom-credentials-aws-secrets.json` file into version control.
34+
35+
**Note:** This file is only used for local development and is not needed when running in a containerized environment like EKS with IRSA.
36+
37+
### Run the Application
38+
39+
Execute the JAR file generated in the `target` directory:
40+
41+
```bash
42+
java -jar target/custom-credential-aws-1.0-SNAPSHOT.jar
43+
```
44+
45+
*Note: Adjust the JAR filename version if you modified it in your `pom.xml`.*
46+
47+
When run locally, the application will detect the `custom-credentials-aws-secrets.json` file and use it to configure the necessary system properties for the AWS SDK.
48+
49+
## Running in a Containerized Environment (EKS)
50+
51+
This section provides a brief overview of how to run the sample in an Amazon EKS cluster.
52+
53+
### EKS Cluster Setup
54+
55+
First, you need an EKS cluster. You can create one using `eksctl` or the AWS Management Console. For detailed instructions, refer to the [Amazon EKS documentation](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html).
56+
57+
### Configure IAM Roles for Service Accounts (IRSA)
58+
59+
IRSA enables you to associate an IAM role with a Kubernetes service account. This provides a secure way for your pods to access AWS services without hardcoding long-lived credentials.
60+
61+
Run the following command to create the IAM role and bind it to a Kubernetes Service Account:
62+
63+
```bash
64+
eksctl create iamserviceaccount \
65+
--name your-k8s-service-account \
66+
--namespace default \
67+
--cluster your-cluster-name \
68+
--region your-aws-region \
69+
--role-name your-role-name \
70+
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
71+
--approve
72+
```
73+
74+
> **Note**: The `--attach-policy-arn` flag is used here to demonstrate attaching permissions. Update this with the specific AWS policy ARN your application requires.
75+
76+
For a deep dive into how this works without using `eksctl`, refer to the [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) documentation.
77+
78+
### Configure Google Cloud to Trust the AWS Role
79+
80+
To allow your AWS role to authenticate as a Google Cloud service account, you need to configure Workload Identity Federation. This process involves these key steps:
81+
82+
1. **Create a Workload Identity Pool and an AWS Provider:** The pool holds the configuration, and the provider is set up to trust your AWS account.
83+
84+
2. **Create or select a Google Cloud Service Account:** This service account will be impersonated by your AWS role.
85+
86+
3. **Bind the AWS Role to the Google Cloud Service Account:** Create an IAM policy binding that gives your AWS role the `Workload Identity User` (`roles/iam.workloadIdentityUser`) role on the Google Cloud service account.
87+
88+
For more detailed information, see the documentation on [Configuring Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds).
89+
90+
### Containerize and Package the Application
91+
92+
Create a `Dockerfile` for the Java application and push the image to a container registry (for example Amazon ECR) that your EKS cluster can access.
93+
94+
**Note:** The provided [`Dockerfile`](Dockerfile) uses a multi-stage build to compile the Java code. It is an example that may need modification for your specific needs.
95+
96+
Build and push the image:
97+
```bash
98+
docker build -t your-container-image:latest .
99+
docker push your-container-image:latest
100+
```
101+
102+
### Deploy to EKS
103+
104+
Create a Kubernetes deployment manifest to deploy your application to the EKS cluster. See the [`pod.yaml`](pod.yaml) file for an example.
105+
106+
**Note:** The provided [`pod.yaml`](pod.yaml) is an example and may need to be modified for your specific needs.
107+
108+
Deploy the pod:
109+
110+
```bash
111+
kubectl apply -f pod.yaml
112+
```
113+
114+
### Clean Up
115+
116+
To clean up the resources, delete the EKS cluster and any other AWS and Google Cloud resources you created.
117+
118+
```bash
119+
eksctl delete cluster --name your-cluster-name
120+
```
121+
122+
## Testing
123+
124+
This sample is not continuously tested. It is provided for instructional purposes and may require modifications to work in your environment.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"aws_access_key_id": "YOUR_AWS_ACCESS_KEY_ID",
3+
"aws_secret_access_key": "YOUR_AWS_SECRET_ACCESS_KEY",
4+
"aws_region": "YOUR_AWS_REGION",
5+
"gcp_workload_audience": "YOUR_GCP_WORKLOAD_AUDIENCE",
6+
"gcs_bucket_name": "YOUR_GCS_BUCKET_NAME",
7+
"gcp_service_account_impersonation_url": "YOUR_GCP_SERVICE_ACCOUNT_IMPERSONATION_URL"
8+
}

0 commit comments

Comments
 (0)