Secure Cloud Access: Mastering GCP Workload Identity Federation in Java

Workload Identity Federation enables applications running outside of Google Cloud to securely access Google Cloud resources without needing to manage service account keys. This eliminates the security risks associated with long-lived credentials while providing seamless identity federation across different environments. This article explores how to implement Workload Identity Federation in Java applications.


Why Workload Identity Federation?

Traditional Challenges:

  • Service account key management and rotation
  • Security risks of long-lived credentials
  • Complex credential distribution
  • Limited audit trails for external access

Workload Identity Federation Benefits:

  • Keyless Authentication: No service account keys to manage
  • Short-lived Credentials: Automatic token rotation
  • Identity Federation: Use existing identity providers (AWS, Azure, OIDC)
  • Enhanced Security: Reduced attack surface
  • Audit Trail: Clear identity mapping in Cloud Audit Logs

Architecture Overview

External Workload (Java App) → Identity Provider → GCP Workload Identity Federation → GCP Services
↓                           ↓                           ↓                     ↓
AWS EC2/IAM Role           AWS STS Service             GCP Security Token      Cloud Storage
Azure VM                   Azure AD                    Service (STS)           BigQuery
Kubernetes Service Account OIDC Provider               GCP IAM                 Pub/Sub
On-premises system

Setting Up Dependencies

Maven Dependencies:

<properties>
<google.cloud.version>2.20.0</google.cloud.version>
<google.auth.version>1.19.0</google.auth.version>
<aws.java.sdk.version>2.20.0</aws.java.sdk.version>
</properties>
<dependencies>
<!-- Google Cloud Libraries -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
<version>${google.cloud.version}</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-bigquery</artifactId>
<version>${google.cloud.version}</version>
</dependency>
<!-- Google Auth Library for Workload Identity Federation -->
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>${google.auth.version}</version>
</dependency>
<!-- AWS SDK for AWS-based federation -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
<version>${aws.java.sdk.version}</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.11.0</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>

Gradle:

dependencies {
implementation("com.google.cloud:google-cloud-storage:2.20.0")
implementation("com.google.cloud:google-cloud-bigquery:2.20.0")
implementation("com.google.auth:google-auth-library-oauth2-http:1.19.0")
implementation("software.amazon.awssdk:sts:2.20.0")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2")
}

Example 1: AWS Workload Identity Federation

AWS to GCP Federation Setup:

1. AWS Credentials Provider:

package com.example.gcp.federation.aws;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.services.sts.model.AssumeRoleResponse;
import software.amazon.awssdk.services.sts.model.Credentials;
public class AWSCredentialsService {
private final StsClient stsClient;
public AWSCredentialsService() {
this.stsClient = StsClient.builder()
.credentialsProvider(DefaultCredentialsProvider.create())
.region(Region.US_EAST_1)
.build();
}
public Credentials getAWSCredentials(String roleArn, String sessionName) {
AssumeRoleRequest assumeRoleRequest = AssumeRoleRequest.builder()
.roleArn(roleArn)
.roleSessionName(sessionName)
.durationSeconds(3600) // 1 hour
.build();
AssumeRoleResponse assumeRoleResponse = stsClient.assumeRole(assumeRoleRequest);
return assumeRoleResponse.credentials();
}
public AwsCredentialsProvider getAwsCredentialsProvider(String roleArn, String sessionName) {
return () -> {
Credentials credentials = getAWSCredentials(roleArn, sessionName);
return software.amazon.awssdk.auth.credentials.AwsSessionCredentials.create(
credentials.accessKeyId(),
credentials.secretAccessKey(),
credentials.sessionToken()
);
};
}
}

2. AWS Workload Identity Federation Client:

package com.example.gcp.federation.aws;
import com.google.auth.oauth2.AwsCredentials;
import com.google.auth.oauth2.ExternalAccountCredentials;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import java.io.IOException;
import java.util.Collections;
public class AWSWorkloadIdentityFederation {
private final String workloadIdentityPoolId;
private final String workloadIdentityProviderId;
private final String awsRoleArn;
private final String gcpServiceAccountEmail;
public AWSWorkloadIdentityFederation(String workloadIdentityPoolId, 
String workloadIdentityProviderId,
String awsRoleArn,
String gcpServiceAccountEmail) {
this.workloadIdentityPoolId = workloadIdentityPoolId;
this.workloadIdentityProviderId = workloadIdentityProviderId;
this.awsRoleArn = awsRoleArn;
this.gcpServiceAccountEmail = gcpServiceAccountEmail;
}
public GoogleCredentials getFederatedCredentials() throws IOException {
// Configuration for AWS-based workload identity federation
String configJson = String.format(
"""
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/%s/locations/global/workloadIdentityPools/%s/providers/%s",
"subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"environment_id": "aws1",
"region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials",
"regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
},
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken"
}
""",
getProjectNumber(), // Your GCP project number
workloadIdentityPoolId,
workloadIdentityProviderId,
gcpServiceAccountEmail
);
return GoogleCredentials.fromJson(configJson);
}
public Storage createStorageClient() throws IOException {
GoogleCredentials credentials = getFederatedCredentials();
return StorageOptions.newBuilder()
.setCredentials(credentials)
.build()
.getService();
}
// Example usage with AWS EC2 instance profile
public void demonstrateAWSFederation() throws IOException {
Storage storage = createStorageClient();
// List buckets to verify authentication
storage.list().iterateAll().forEach(bucket -> {
System.out.println("Bucket: " + bucket.getName());
});
System.out.println("Successfully authenticated using AWS Workload Identity Federation!");
}
private String getProjectNumber() {
// In practice, you might get this from environment variables or configuration
return "123456789012"; // Your GCP project number
}
}

3. Advanced AWS Federation with Custom Configuration:

package com.example.gcp.federation.aws;
import com.google.auth.oauth2.AwsCredentials;
import com.google.auth.oauth2.ExternalAccountCredentials;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.gson.JsonObject;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
public class AdvancedAWSFederation {
public GoogleCredentials createCredentialsFromConfigFile(String configPath) throws IOException {
// Load configuration from JSON file
try (FileInputStream configStream = new FileInputStream(configPath)) {
return GoogleCredentials.fromStream(configStream)
.createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"));
}
}
public GoogleCredentials createCredentialsProgrammatically(
String projectNumber,
String poolId,
String providerId,
String serviceAccountEmail) throws IOException {
JsonObject credentialSource = new JsonObject();
credentialSource.addProperty("environment_id", "aws1");
credentialSource.addProperty("region_url", "http://169.254.169.254/latest/meta-data/placement/availability-zone");
credentialSource.addProperty("url", "http://169.254.169.254/latest/meta-data/iam/security-credentials");
credentialSource.addProperty("regional_cred_verification_url", 
"https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15");
JsonObject config = new JsonObject();
config.addProperty("type", "external_account");
config.addProperty("audience", 
String.format("//iam.googleapis.com/projects/%s/locations/global/workloadIdentityPools/%s/providers/%s",
projectNumber, poolId, providerId));
config.addProperty("subject_token_type", "urn:ietf:params:aws:token-type:aws4_request");
config.addProperty("token_url", "https://sts.googleapis.com/v1/token");
config.add("credential_source", credentialSource);
config.addProperty("service_account_impersonation_url",
String.format("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken",
serviceAccountEmail));
return GoogleCredentials.fromJson(config.toString())
.createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"));
}
}

Example 2: OIDC Workload Identity Federation

OIDC Provider Federation (e.g., GitHub Actions, Kubernetes):

1. OIDC Token Provider:

package com.example.gcp.federation.oidc;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import java.io.IOException;
import java.util.Base64;
public class OIDCTokenProvider {
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
private final String oidcProviderUrl;
public OIDCTokenProvider(String oidcProviderUrl) {
this.httpClient = new OkHttpClient();
this.objectMapper = new ObjectMapper();
this.oidcProviderUrl = oidcProviderUrl;
}
public String getOIDCToken(String clientId, String clientSecret) throws IOException {
RequestBody formBody = new FormBody.Builder()
.add("grant_type", "client_credentials")
.add("client_id", clientId)
.add("client_secret", clientSecret)
.add("scope", "openid")
.build();
Request request = new Request.Builder()
.url(oidcProviderUrl + "/token")
.post(formBody)
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("OIDC token request failed: " + response.code());
}
JsonNode responseJson = objectMapper.readTree(response.body().string());
return responseJson.get("access_token").asText();
}
}
public JsonNode decodeOIDCToken(String token) throws IOException {
String[] parts = token.split("\\.");
if (parts.length != 3) {
throw new IllegalArgumentException("Invalid JWT token");
}
String payload = new String(Base64.getUrlDecoder().decode(parts[1]));
return objectMapper.readTree(payload);
}
}

2. OIDC Workload Identity Federation:

package com.example.gcp.federation.oidc;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ExternalAccountCredentials;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.util.Collections;
public class OIDCWorkloadIdentityFederation {
public GoogleCredentials createOIDCCredentials(String projectNumber,
String poolId,
String providerId,
String serviceAccountEmail,
String oidcToken) throws IOException {
JsonObject credentialSource = new JsonObject();
credentialSource.addProperty("url", "https://oidc-provider.example.com/token");
credentialSource.addProperty("headers", String.format("{\"Authorization\": \"Bearer %s\"}", oidcToken));
JsonObject config = new JsonObject();
config.addProperty("type", "external_account");
config.addProperty("audience", 
String.format("//iam.googleapis.com/projects/%s/locations/global/workloadIdentityPools/%s/providers/%s",
projectNumber, poolId, providerId));
config.addProperty("subject_token_type", "urn:ietf:params:oauth:token-type:jwt");
config.addProperty("token_url", "https://sts.googleapis.com/v1/token");
config.add("credential_source", credentialSource);
config.addProperty("service_account_impersonation_url",
String.format("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken",
serviceAccountEmail));
return GoogleCredentials.fromJson(config.toString())
.createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"));
}
// For GitHub Actions OIDC
public GoogleCredentials createGitHubActionsCredentials(String projectNumber,
String poolId,
String providerId,
String serviceAccountEmail) throws IOException {
// In GitHub Actions, the OIDC token is available via ACTIONS_ID_TOKEN_REQUEST_URL
String githubOIDCToken = System.getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN");
String githubOIDCUrl = System.getenv("ACTIONS_ID_TOKEN_REQUEST_URL");
JsonObject credentialSource = new JsonObject();
credentialSource.addProperty("url", githubOIDCUrl);
credentialSource.addProperty("headers", String.format("{\"Authorization\": \"Bearer %s\"}", githubOIDCToken));
JsonObject config = new JsonObject();
config.addProperty("type", "external_account");
config.addProperty("audience", 
String.format("//iam.googleapis.com/projects/%s/locations/global/workloadIdentityPools/%s/providers/%s",
projectNumber, poolId, providerId));
config.addProperty("subject_token_type", "urn:ietf:params:oauth:token-type:jwt");
config.addProperty("token_url", "https://sts.googleapis.com/v1/token");
config.add("credential_source", credentialSource);
config.addProperty("service_account_impersonation_url",
String.format("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken",
serviceAccountEmail));
return GoogleCredentials.fromJson(config.toString())
.createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"));
}
}

Example 3: Azure Workload Identity Federation

Azure to GCP Federation:

package com.example.gcp.federation.azure;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.util.Collections;
public class AzureWorkloadIdentityFederation {
public GoogleCredentials createAzureCredentials(String projectNumber,
String poolId,
String providerId,
String serviceAccountEmail) throws IOException {
JsonObject credentialSource = new JsonObject();
credentialSource.addProperty("url", "http://169.254.169.254/metadata/identity/oauth2/token");
credentialSource.addProperty("headers", "{\"Metadata\": \"true\"}");
credentialSource.addProperty("format", 
"""
{
"type": "json",
"subject_token_field_name": "access_token"
}
""");
JsonObject config = new JsonObject();
config.addProperty("type", "external_account");
config.addProperty("audience", 
String.format("//iam.googleapis.com/projects/%s/locations/global/workloadIdentityPools/%s/providers/%s",
projectNumber, poolId, providerId));
config.addProperty("subject_token_type", "urn:ietf:params:oauth:token-type:jwt");
config.addProperty("token_url", "https://sts.googleapis.com/v1/token");
config.add("credential_source", credentialSource);
config.addProperty("service_account_impersonation_url",
String.format("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken",
serviceAccountEmail));
return GoogleCredentials.fromJson(config.toString())
.createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"));
}
}

Example 4: Generic Workload Identity Federation Service

Unified Federation Service:

package com.example.gcp.federation;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
public class WorkloadIdentityFederationService {
private final String configFilePath;
public WorkloadIdentityFederationService(String configFilePath) {
this.configFilePath = configFilePath;
}
public GoogleCredentials getFederatedCredentials() throws IOException {
try (FileInputStream configStream = new FileInputStream(configFilePath)) {
return GoogleCredentials.fromStream(configStream)
.createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"));
}
}
public Storage createStorageClient() throws IOException {
GoogleCredentials credentials = getFederatedCredentials();
return StorageOptions.newBuilder()
.setCredentials(credentials)
.build()
.getService();
}
public BigQuery createBigQueryClient() throws IOException {
GoogleCredentials credentials = getFederatedCredentials();
return BigQueryOptions.newBuilder()
.setCredentials(credentials)
.build()
.getService();
}
// Token refresh monitoring
public void monitorTokenRefresh() throws IOException {
GoogleCredentials credentials = getFederatedCredentials();
// The credentials automatically handle token refresh
// You can monitor the refresh process if needed
credentials.refreshIfExpired();
System.out.println("Access token type: " + credentials.getAccessToken().getTokenType());
System.out.println("Access token expires in: " + 
(credentials.getAccessToken().getExpirationTime().getTime() - System.currentTimeMillis()) + " ms");
}
}

Example 5: Configuration Management

Federation Configuration Manager:

package com.example.gcp.federation.config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class FederationConfigManager {
private final ObjectMapper objectMapper;
private final Map<String, JsonNode> configurations;
public FederationConfigManager() {
this.objectMapper = new ObjectMapper();
this.configurations = new HashMap<>();
}
public void loadConfiguration(String name, String configPath) throws IOException {
JsonNode config = objectMapper.readTree(new File(configPath));
configurations.put(name, config);
}
public JsonNode getConfiguration(String name) {
return configurations.get(name);
}
public String generateAWSConfiguration(String projectNumber,
String poolId,
String providerId,
String serviceAccountEmail) {
return String.format(
"""
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/%s/locations/global/workloadIdentityPools/%s/providers/%s",
"subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"environment_id": "aws1",
"region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials",
"regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
},
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken"
}
""",
projectNumber, poolId, providerId, serviceAccountEmail
);
}
public void saveConfiguration(String name, String configJson, String outputPath) throws IOException {
JsonNode config = objectMapper.readTree(configJson);
objectMapper.writerWithDefaultPrettyPrinter().writeValue(new File(outputPath), config);
configurations.put(name, config);
}
}

Example 6: Security and Best Practices

Secure Token Handling:

package com.example.gcp.federation.security;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.AccessToken;
import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class TokenSecurityManager {
private final GoogleCredentials credentials;
private final ScheduledExecutorService scheduler;
private AccessToken currentToken;
public TokenSecurityManager(GoogleCredentials credentials) {
this.credentials = credentials;
this.scheduler = Executors.newSingleThreadScheduledExecutor();
startTokenMonitoring();
}
private void startTokenMonitoring() {
// Check token expiration every 5 minutes
scheduler.scheduleAtFixedRate(this::checkTokenExpiration, 0, 5, TimeUnit.MINUTES);
}
private void checkTokenExpiration() {
try {
AccessToken token = credentials.getAccessToken();
if (token == null || isTokenExpiringSoon(token)) {
credentials.refresh();
currentToken = credentials.getAccessToken();
System.out.println("Token refreshed. New expiration: " + 
currentToken.getExpirationTime());
}
} catch (IOException e) {
System.err.println("Token refresh failed: " + e.getMessage());
}
}
private boolean isTokenExpiringSoon(AccessToken token) {
Instant expiration = token.getExpirationTime().toInstant();
Instant now = Instant.now();
return now.until(expiration, ChronoUnit.MINUTES) < 10; // 10 minutes threshold
}
public AccessToken getCurrentToken() {
return currentToken;
}
public void shutdown() {
scheduler.shutdown();
}
// Secure token logging (never log actual tokens)
public void logTokenInfo() {
if (currentToken != null) {
System.out.println("Token type: " + currentToken.getTokenType());
System.out.println("Token expires: " + currentToken.getExpirationTime());
System.out.println("Token preview: " + 
currentToken.getTokenValue().substring(0, 10) + "...");
}
}
}

Example 7: Error Handling and Retry Logic

Resilient Federation Client:

package com.example.gcp.federation.resilience;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageException;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
public class ResilientFederationClient {
private final GoogleCredentials credentials;
private final AtomicInteger retryCount = new AtomicInteger(0);
private final int maxRetries = 3;
private final long initialBackoffMs = 1000;
public ResilientFederationClient(GoogleCredentials credentials) {
this.credentials = credentials;
}
public Storage createStorageClientWithRetry() throws IOException {
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
return createStorageClient();
} catch (IOException e) {
if (attempt == maxRetries) {
throw new IOException("Failed to create storage client after " + maxRetries + " attempts", e);
}
System.out.println("Attempt " + (attempt + 1) + " failed, retrying...");
exponentialBackoff(attempt);
// Refresh credentials before retry
credentials.refresh();
}
}
throw new IOException("Unexpected error in createStorageClientWithRetry");
}
private Storage createStorageClient() throws IOException {
return StorageOptions.newBuilder()
.setCredentials(credentials)
.build()
.getService();
}
private void exponentialBackoff(int attempt) {
try {
long delay = initialBackoffMs * (1L << attempt); // Exponential backoff
Thread.sleep(Math.min(delay, 30000)); // Max 30 seconds
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Operation interrupted", e);
}
}
public void performOperationWithRetry(Runnable operation) {
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
operation.run();
retryCount.set(0); // Reset on success
return;
} catch (StorageException e) {
if (e.getCode() == 401) {
// Unauthorized - refresh token and retry
try {
credentials.refresh();
} catch (IOException refreshError) {
throw new RuntimeException("Token refresh failed", refreshError);
}
}
if (attempt == maxRetries) {
throw new RuntimeException("Operation failed after " + maxRetries + " attempts", e);
}
exponentialBackoff(attempt);
}
}
}
}

Example 8: Complete Implementation Example

Complete Workload Identity Federation Application:

package com.example.gcp.federation;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.StorageOptions;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.TableResult;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
public class WorkloadIdentityFederationDemo {
private final GoogleCredentials credentials;
private final Storage storage;
private final BigQuery bigQuery;
public WorkloadIdentityFederationDemo(String configPath) throws IOException {
// Load federation configuration
this.credentials = loadCredentials(configPath);
this.storage = createStorageClient();
this.bigQuery = createBigQueryClient();
}
private GoogleCredentials loadCredentials(String configPath) throws IOException {
try (FileInputStream configStream = new FileInputStream(configPath)) {
return GoogleCredentials.fromStream(configStream)
.createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"));
}
}
private Storage createStorageClient() {
return StorageOptions.newBuilder()
.setCredentials(credentials)
.build()
.getService();
}
private BigQuery createBigQueryClient() {
return BigQueryOptions.newBuilder()
.setCredentials(credentials)
.build()
.getService();
}
public void demonstrateStorageAccess() {
System.out.println("=== Cloud Storage Demo ===");
// List buckets
storage.list().iterateAll().forEach(bucket -> {
System.out.println("Bucket: " + bucket.getName());
});
// List objects in a specific bucket (if you have one)
String bucketName = "your-bucket-name";
try {
Iterable<Blob> blobs = storage.list(bucketName).iterateAll();
blobs.forEach(blob -> {
System.out.println("Blob: " + blob.getName());
});
} catch (Exception e) {
System.out.println("Note: Could not access bucket '" + bucketName + "' - " + e.getMessage());
}
}
public void demonstrateBigQueryAccess() {
System.out.println("=== BigQuery Demo ===");
// List datasets
bigQuery.listDatasets().iterateAll().forEach(dataset -> {
System.out.println("Dataset: " + dataset.getDatasetId().getDataset());
});
// Run a simple query
try {
String query = "SELECT 1 as value";
TableResult result = bigQuery.query(
com.google.cloud.bigquery.QueryJobConfiguration.of(query));
result.iterateAll().forEach(row -> {
System.out.println("Query result: " + row.get(0).getLongValue());
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Query interrupted: " + e.getMessage());
} catch (Exception e) {
System.out.println("Note: BigQuery query failed - " + e.getMessage());
}
}
public void demonstrateTokenInfo() throws IOException {
System.out.println("=== Token Information ===");
credentials.refreshIfExpired();
var token = credentials.getAccessToken();
System.out.println("Token type: " + token.getTokenType());
System.out.println("Expiration: " + token.getExpirationTime());
System.out.println("Scopes: " + credentials.getScopes());
}
public static void main(String[] args) {
try {
// Configuration file path - this should be your workload identity federation config
String configPath = args.length > 0 ? args[0] : "federation-config.json";
WorkloadIdentityFederationDemo demo = new WorkloadIdentityFederationDemo(configPath);
demo.demonstrateTokenInfo();
demo.demonstrateStorageAccess();
demo.demonstrateBigQueryAccess();
System.out.println("Workload Identity Federation demo completed successfully!");
} catch (Exception e) {
System.err.println("Demo failed: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
}
}

Configuration Files

AWS Federation Configuration (federation-config.json):

{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/my-pool/providers/my-aws-provider",
"subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"environment_id": "aws1",
"region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials",
"regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
},
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateAccessToken"
}

OIDC Federation Configuration:

{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/my-pool/providers/my-oidc-provider",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"url": "https://oidc-provider.example.com/token",
"headers": {
"Authorization": "Bearer ${OIDC_TOKEN}"
}
},
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateAccessToken"
}

Best Practices

1. Security:

public class SecurityBestPractices {
// Never hardcode credentials
// Use environment variables or secure configuration management
public String getConfigFromEnv() {
return System.getenv("FEDERATION_CONFIG_PATH");
}
// Implement proper token rotation
public void ensureTokenFreshness(GoogleCredentials credentials) throws IOException {
if (credentials.getAccessToken().getExpirationTime().getTime() - System.currentTimeMillis() < 300000) {
credentials.refresh();
}
}
// Use minimal required scopes
public GoogleCredentials createWithMinimalScopes(GoogleCredentials credentials) {
return credentials.createScoped(Arrays.asList(
"https://www.googleapis.com/auth/devstorage.read_only",
"https://www.googleapis.com/auth/bigquery.readonly"
));
}
}

2. Monitoring and Logging:

public class MonitoringUtils {
public void logAuthenticationAttempt(String provider, boolean success) {
System.out.printf("Authentication attempt - Provider: %s, Success: %s%n", 
provider, success);
}
public void monitorTokenUsage(GoogleCredentials credentials) throws IOException {
var token = credentials.getAccessToken();
long timeToExpiry = token.getExpirationTime().getTime() - System.currentTimeMillis();
System.out.printf("Token will expire in %d minutes%n", timeToExpiry / 60000);
if (timeToExpiry < 300000) { // 5 minutes
System.out.println("WARNING: Token expiration imminent");
}
}
}

Conclusion

GCP Workload Identity Federation provides a secure, keyless approach to accessing Google Cloud resources from external environments:

Key Benefits:

  • Eliminates Service Account Keys: No long-lived credentials to manage
  • Identity Federation: Leverage existing identity providers
  • Short-lived Tokens: Automatic rotation and expiration
  • Audit Trail: Clear identity mapping in Cloud Audit Logs

Supported Identity Providers:

  • AWS: EC2 instances, EKS pods, IAM roles
  • Azure: Virtual machines, AKS pods
  • OIDC: GitHub Actions, Kubernetes, custom providers
  • Generic: Any OIDC-compliant identity provider

Implementation Patterns:

  1. AWS Federation: Use AWS instance metadata service
  2. OIDC Federation: Integrate with OIDC token providers
  3. Azure Federation: Leverage Azure managed identities
  4. Generic Federation: Custom credential sources

Best Practices:

  • Use configuration files for easy management
  • Implement proper error handling and retry logic
  • Monitor token expiration and refresh proactively
  • Apply principle of least privilege with scopes
  • Use secure credential storage and transmission

By implementing Workload Identity Federation in your Java applications, you can significantly improve security while simplifying credential management across hybrid and multi-cloud environments.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper