From 28bc50e43121491cdf5d484ce7f26ed22d9df96e Mon Sep 17 00:00:00 2001 From: Connor Lafferty Date: Wed, 31 Jan 2024 09:25:23 -0800 Subject: [PATCH 1/2] Support GCP Web Identity session credentials Signed-off-by: Connor Lafferty --- pom.xml | 5 ++ .../cloudwatch/CloudWatchCollector.java | 57 ++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7722ece3..5ee92d27 100644 --- a/pom.xml +++ b/pom.xml @@ -125,6 +125,11 @@ 2.2 test + + com.google.cloud + google-cloud-iamcredentials + 2.0.2 + diff --git a/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java b/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java index cb34407f..5884b328 100644 --- a/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java +++ b/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java @@ -24,6 +24,7 @@ import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.cloudwatch.CloudWatchClient; @@ -39,11 +40,22 @@ import software.amazon.awssdk.services.resourcegroupstaggingapi.model.TagFilter; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider; +import software.amazon.awssdk.services.sts.auth.StsAssumeRoleWithWebIdentityCredentialsProvider; import software.amazon.awssdk.services.sts.model.AssumeRoleRequest; +import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest; + +import com.google.cloud.iam.credentials.v1.GenerateIdTokenRequest; +import com.google.cloud.iam.credentials.v1.GenerateIdTokenResponse; +import com.google.cloud.iam.credentials.v1.IamCredentialsClient; public class CloudWatchCollector extends Collector implements Describable { private static final Logger LOGGER = Logger.getLogger(CloudWatchCollector.class.getName()); + private static final String SERVICE_ACCOUNT_NAME_FORMAT = "projects/-/serviceAccounts/%s"; + + private static final int WEB_IDENTITY_CREDENTIAL_DURATION_SECONDS = 3600; + + static class ActiveConfig { ArrayList rules; CloudWatchClient cloudWatchClient; @@ -347,12 +359,55 @@ private void loadConfig( } } + private static String getIdToken(String serviceAccountEmail) throws IOException { + try (IamCredentialsClient credentialsClient = IamCredentialsClient.create()) { + GenerateIdTokenResponse idTokenResponse = + credentialsClient.generateIdToken( + GenerateIdTokenRequest.newBuilder() + .setName(String.format(SERVICE_ACCOUNT_NAME_FORMAT, serviceAccountEmail)) + .setAudience(serviceAccountEmail) + .setIncludeEmail(true) + .build()); + return idTokenResponse.getToken(); + } + } + private AwsCredentialsProvider getRoleCredentialProvider(Map config) { + String roleArn = (String) config.get("role_arn"); + if (config.containsKey("assume_role_web_identity")) { + // indicates we need to use gcp-based web identity to assume the role + return StsAssumeRoleWithWebIdentityCredentialsProvider.builder() + // intentionally using anonymous credentials since this is meant to be run only in gcp + // environments, and the AssumeRoleWithWebIdentityRequest can run without any credentials + .stsClient( + StsClient.builder() + .credentialsProvider(AnonymousCredentialsProvider.create()) + .region(Region.US_WEST_1) + .build()) + .refreshRequest( + () -> { + String idToken; + try { + idToken = getIdToken((String) config.get("assume_role_web_identity")); + } catch (IOException e) { + throw new RuntimeException( + "Failed to get id token for role arn: " + roleArn, e); + } + String[] roleSplit = roleArn.split("/"); + return AssumeRoleWithWebIdentityRequest.builder() + .roleArn(roleArn) + .webIdentityToken(idToken) + .roleSessionName(roleSplit[roleSplit.length - 1]) + .durationSeconds(WEB_IDENTITY_CREDENTIAL_DURATION_SECONDS) + .build(); + }) + .build(); + } StsClient stsClient = StsClient.builder().region(Region.of((String) config.get("region"))).build(); AssumeRoleRequest assumeRoleRequest = AssumeRoleRequest.builder() - .roleArn((String) config.get("role_arn")) + .roleArn(roleArn) .roleSessionName("cloudwatch_exporter") .build(); return StsAssumeRoleCredentialsProvider.builder() From cf9ed3d9c86d774c7f3c3f57cfd7e5114c93330e Mon Sep 17 00:00:00 2001 From: Connor Lafferty Date: Wed, 31 Jan 2024 12:13:58 -0800 Subject: [PATCH 2/2] clean --- .../cloudwatch/CloudWatchCollector.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java b/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java index 5884b328..3c21363b 100644 --- a/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java +++ b/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java @@ -2,6 +2,9 @@ import static io.prometheus.cloudwatch.CachingDimensionSource.DimensionCacheConfig; +import com.google.cloud.iam.credentials.v1.GenerateIdTokenRequest; +import com.google.cloud.iam.credentials.v1.GenerateIdTokenResponse; +import com.google.cloud.iam.credentials.v1.IamCredentialsClient; import io.prometheus.client.Collector; import io.prometheus.client.Collector.Describable; import io.prometheus.client.Counter; @@ -44,10 +47,6 @@ import software.amazon.awssdk.services.sts.model.AssumeRoleRequest; import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest; -import com.google.cloud.iam.credentials.v1.GenerateIdTokenRequest; -import com.google.cloud.iam.credentials.v1.GenerateIdTokenResponse; -import com.google.cloud.iam.credentials.v1.IamCredentialsClient; - public class CloudWatchCollector extends Collector implements Describable { private static final Logger LOGGER = Logger.getLogger(CloudWatchCollector.class.getName()); @@ -55,7 +54,6 @@ public class CloudWatchCollector extends Collector implements Describable { private static final int WEB_IDENTITY_CREDENTIAL_DURATION_SECONDS = 3600; - static class ActiveConfig { ArrayList rules; CloudWatchClient cloudWatchClient; @@ -390,8 +388,7 @@ private AwsCredentialsProvider getRoleCredentialProvider(Map con try { idToken = getIdToken((String) config.get("assume_role_web_identity")); } catch (IOException e) { - throw new RuntimeException( - "Failed to get id token for role arn: " + roleArn, e); + throw new RuntimeException("Failed to get id token for role arn: " + roleArn, e); } String[] roleSplit = roleArn.split("/"); return AssumeRoleWithWebIdentityRequest.builder() @@ -406,10 +403,7 @@ private AwsCredentialsProvider getRoleCredentialProvider(Map con StsClient stsClient = StsClient.builder().region(Region.of((String) config.get("region"))).build(); AssumeRoleRequest assumeRoleRequest = - AssumeRoleRequest.builder() - .roleArn(roleArn) - .roleSessionName("cloudwatch_exporter") - .build(); + AssumeRoleRequest.builder().roleArn(roleArn).roleSessionName("cloudwatch_exporter").build(); return StsAssumeRoleCredentialsProvider.builder() .stsClient(stsClient) .refreshRequest(assumeRoleRequest)