diff --git a/spring-vault-core/pom.xml b/spring-vault-core/pom.xml
index a3721355..f5d52e53 100644
--- a/spring-vault-core/pom.xml
+++ b/spring-vault-core/pom.xml
@@ -194,6 +194,25 @@
+
+ com.google.cloud
+ google-cloud-iamcredentials
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ commons-logging
+ commons-logging
+
+
+ true
+
com.google.auth
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/DefaultGcpCredentialsAccessors.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/DefaultGcpCredentialsAccessors.java
new file mode 100644
index 00000000..83af9794
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/DefaultGcpCredentialsAccessors.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.
+ */
+package org.springframework.vault.authentication;
+
+import org.springframework.util.Assert;
+
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+
+/**
+ * Default implementation of {@link GcpCredentialsAccountIdAccessor}. Used by
+ * {@link GcpIamCredentialsAuthentication}.
+ *
+ * @author Andreas Gebauer
+ * @since 2.4
+ * @see GcpIamCredentialsAuthentication
+ */
+enum DefaultGcpCredentialsAccessors implements GcpCredentialsAccountIdAccessor {
+
+ INSTANCE;
+
+ /**
+ * Get a the service account id (email) to be placed in the signed JWT.
+ * @param credentials credentials object to obtain the service account id from.
+ * @return the service account id to use.
+ */
+ @Override
+ public String getServiceAccountId(GoogleCredentials credentials) {
+
+ Assert.notNull(credentials, "GoogleCredentials must not be null");
+ Assert.isInstanceOf(ServiceAccountCredentials.class, credentials,
+ "The configured GoogleCredentials does not represent a service account. Configure the service account id with GcpIamCredentialsAuthenticationOptionsBuilder#serviceAccountId(String).");
+
+ return ((ServiceAccountCredentials) credentials).getAccount();
+ }
+
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpCredentialsAccountIdAccessor.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpCredentialsAccountIdAccessor.java
new file mode 100644
index 00000000..6fa974a9
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpCredentialsAccountIdAccessor.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.
+ */
+package org.springframework.vault.authentication;
+
+import com.google.auth.oauth2.GoogleCredentials;
+
+/**
+ * Interface to obtain a service account id for GCP IAM credentials authentication.
+ * Implementations are used by {@link GcpIamCredentialsAuthentication}.
+ *
+ * @author Andreas Gebauer
+ * @since 2.4
+ * @see GcpIamCredentialsAuthentication
+ */
+@FunctionalInterface
+public interface GcpCredentialsAccountIdAccessor {
+
+ /**
+ * Get a the service account id (email) to be placed in the signed JWT.
+ * @param credentials credential object to obtain the service account id from.
+ * @return the service account id to use.
+ */
+ String getServiceAccountId(GoogleCredentials credentials);
+
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpCredentialsSupplier.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpCredentialsSupplier.java
new file mode 100644
index 00000000..bca78113
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpCredentialsSupplier.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.
+ */
+package org.springframework.vault.authentication;
+
+import java.io.IOException;
+import java.util.function.Supplier;
+
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+
+/**
+ * Interface to obtain a {@link ServiceAccountCredentials} for GCP IAM credentials
+ * authentication. Implementations are used by {@link GcpIamCredentialsAuthentication}.
+ *
+ * @author Andreas Gebauer
+ * @since 2.4
+ * @see GcpIamCredentialsAuthentication
+ */
+@FunctionalInterface
+public interface GcpCredentialsSupplier extends Supplier {
+
+ /**
+ * Exception-safe helper to get {@link ServiceAccountCredentials} from
+ * {@link #getCredentials}.
+ * @return the ServiceAccountCredentials for JWT signing.
+ */
+ @Override
+ default GoogleCredentials get() {
+
+ try {
+ return getCredentials();
+ }
+ catch (IOException e) {
+ throw new IllegalStateException("Cannot obtain GoogleCredential", e);
+ }
+ }
+
+ /**
+ * Get a {@link GoogleCredentials} for GCP IAM credentials authentication via JWT
+ * signing.
+ * @return the {@link GoogleCredentials}.
+ * @throws IOException if the credentials lookup fails.
+ */
+ GoogleCredentials getCredentials() throws IOException;
+
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpIamAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpIamAuthentication.java
index fe9dece4..307718a1 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpIamAuthentication.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpIamAuthentication.java
@@ -64,7 +64,9 @@ import org.springframework.web.client.RestOperations;
* @see GCP:
* projects.serviceAccounts.signJwt
+ * @deprecated Use {@link GcpIamCredentialsAuthentication} instead.
*/
+@Deprecated
public class GcpIamAuthentication extends GcpJwtAuthenticationSupport implements ClientAuthentication {
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpIamCredentialsAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpIamCredentialsAuthentication.java
new file mode 100644
index 00000000..5dd19635
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpIamCredentialsAuthentication.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.
+ */
+package org.springframework.vault.authentication;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.vault.VaultException;
+import org.springframework.vault.support.VaultToken;
+import org.springframework.web.client.RestOperations;
+
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
+import com.google.api.gax.rpc.TransportChannelProvider;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.cloud.iam.credentials.v1.IamCredentialsClient;
+import com.google.cloud.iam.credentials.v1.IamCredentialsSettings;
+import com.google.cloud.iam.credentials.v1.ServiceAccountName;
+import com.google.cloud.iam.credentials.v1.SignJwtResponse;
+import com.google.cloud.iam.credentials.v1.stub.IamCredentialsStubSettings;
+
+/**
+ * GCP IAM credentials login implementation using GCP IAM service accounts to legitimate
+ * its authenticity via JSON Web Token.
+ *
+ * This authentication method uses Googles IAM Credentials API to obtain a signed token
+ * for a specific {@link com.google.api.client.auth.oauth2.Credential}. Service account
+ * details are obtained from a {@link GoogleCredentials} that can be retrieved either from
+ * a JSON file or the runtime environment (GAE, GCE).
+ *
+ * {@link GcpIamCredentialsAuthentication} uses Google Java API that uses synchronous API.
+ *
+ * @author Andreas Gebauer
+ * @since 2.4
+ * @see GcpIamCredentialsAuthenticationOptions
+ * @see HttpTransport
+ * @see GoogleCredentials
+ * @see GoogleCredentials#getApplicationDefault()
+ * @see RestOperations
+ * @see Auth Backend: gcp
+ * (IAM)
+ * @see GCP:
+ * projects.serviceAccounts.signJwt
+ */
+public class GcpIamCredentialsAuthentication extends GcpJwtAuthenticationSupport implements ClientAuthentication {
+
+ private static final JsonFactory JSON_FACTORY = new JacksonFactory();
+
+ private final GcpIamCredentialsAuthenticationOptions options;
+
+ private final TransportChannelProvider transportChannelProvider;
+
+ private final GoogleCredentials credentials;
+
+ /**
+ * Create a new instance of {@link GcpIamCredentialsAuthentication} given
+ * {@link GcpIamCredentialsAuthenticationOptions} and {@link RestOperations}. This
+ * constructor initializes {@link InstantiatingGrpcChannelProvider} for Google API
+ * usage.
+ * @param options must not be {@literal null}.
+ * @param restOperations HTTP client for for Vault login, must not be {@literal null}.
+ */
+ public GcpIamCredentialsAuthentication(GcpIamCredentialsAuthenticationOptions options,
+ RestOperations restOperations) {
+ this(options, restOperations, IamCredentialsStubSettings.defaultGrpcTransportProviderBuilder().build());
+ }
+
+ /**
+ * Create a new instance of {@link GcpIamCredentialsAuthentication} given
+ * {@link GcpIamCredentialsAuthenticationOptions}, {@link RestOperations} and
+ * {@link TransportChannelProvider}.
+ * @param options must not be {@literal null}.
+ * @param restOperations HTTP client for for Vault login, must not be {@literal null}.
+ * @param transportChannelProvider Provider for transport channel Google API use, must
+ * not be {@literal null}.
+ */
+ public GcpIamCredentialsAuthentication(GcpIamCredentialsAuthenticationOptions options,
+ RestOperations restOperations, TransportChannelProvider transportChannelProvider) {
+
+ super(restOperations);
+
+ Assert.notNull(options, "GcpAuthenticationOptions must not be null");
+ Assert.notNull(restOperations, "RestOperations must not be null");
+ Assert.notNull(transportChannelProvider, "TransportChannelProvider must not be null");
+
+ this.options = options;
+ this.transportChannelProvider = transportChannelProvider;
+ this.credentials = options.getCredentialSupplier().get();
+ }
+
+ @Override
+ public VaultToken login() throws VaultException {
+
+ String signedJwt = signJwt();
+
+ return doLogin("GCP-IAM", signedJwt, this.options.getPath(), this.options.getRole());
+ }
+
+ protected String signJwt() {
+
+ String serviceAccount = getServiceAccountId();
+ Map jwtPayload = getJwtPayload(this.options, serviceAccount);
+
+ try {
+ IamCredentialsSettings credentialsSettings = IamCredentialsSettings.newBuilder()
+ .setCredentialsProvider(() -> this.credentials)
+ .setTransportChannelProvider(this.transportChannelProvider).build();
+ try (IamCredentialsClient iamCredentialsClient = IamCredentialsClient.create(credentialsSettings)) {
+ String payload = JSON_FACTORY.toString(jwtPayload);
+ ServiceAccountName serviceAccountName = ServiceAccountName.of("-", serviceAccount);
+ SignJwtResponse response = iamCredentialsClient.signJwt(serviceAccountName, Collections.emptyList(),
+ payload);
+ return response.getSignedJwt();
+ }
+ }
+ catch (IOException e) {
+ throw new VaultLoginException("Cannot sign JWT", e);
+ }
+ }
+
+ private String getServiceAccountId() {
+ return this.options.getServiceAccountIdAccessor().getServiceAccountId(this.credentials);
+ }
+
+ private static Map getJwtPayload(GcpIamCredentialsAuthenticationOptions options,
+ String serviceAccount) {
+
+ Instant validUntil = options.getClock().instant().plus(options.getJwtValidity());
+
+ Map payload = new LinkedHashMap<>();
+
+ payload.put("sub", serviceAccount);
+ payload.put("aud", "vault/" + options.getRole());
+ payload.put("exp", validUntil.getEpochSecond());
+
+ return payload;
+ }
+
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpIamCredentialsAuthenticationOptions.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpIamCredentialsAuthenticationOptions.java
new file mode 100644
index 00000000..01bd3948
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/GcpIamCredentialsAuthenticationOptions.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.
+ */
+package org.springframework.vault.authentication;
+
+import java.time.Clock;
+import java.time.Duration;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+import com.google.api.client.auth.oauth2.Credential;
+import com.google.api.core.ApiClock;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+
+public class GcpIamCredentialsAuthenticationOptions {
+
+ public static final String DEFAULT_GCP_AUTHENTICATION_PATH = "gcp";
+
+ /**
+ * Path of the gcp authentication backend mount.
+ */
+ private final String path;
+
+ private final GcpCredentialsSupplier credentialSupplier;
+
+ /**
+ * Name of the role against which the login is being attempted. If role is not
+ * specified, the friendly name (i.e., role name or username) of the IAM principal
+ * authenticated. If a matching role is not found, login fails.
+ */
+ private final String role;
+
+ /**
+ * JWT validity/expiration.
+ */
+ private final Duration jwtValidity;
+
+ /**
+ * {@link ApiClock} to calculate JWT expiration.
+ */
+ private final Clock clock;
+
+ /**
+ * Provide the service account id to use as sub/iss claims.
+ */
+ private final GcpCredentialsAccountIdAccessor serviceAccountIdAccessor;
+
+ private GcpIamCredentialsAuthenticationOptions(String path, GcpCredentialsSupplier credentialSupplier, String role,
+ Duration jwtValidity, Clock clock, GcpCredentialsAccountIdAccessor serviceAccountIdSupplier) {
+
+ this.path = path;
+ this.credentialSupplier = credentialSupplier;
+ this.role = role;
+ this.jwtValidity = jwtValidity;
+ this.clock = clock;
+ this.serviceAccountIdAccessor = serviceAccountIdSupplier;
+ }
+
+ /**
+ * @return a new
+ * {@link GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder}.
+ */
+ public static GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder builder() {
+ return new GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder();
+ }
+
+ /**
+ * @return the path of the gcp authentication backend mount.
+ */
+ public String getPath() {
+ return this.path;
+ }
+
+ /**
+ * @return the gcp {@link Credential} supplier.
+ */
+ public GcpCredentialsSupplier getCredentialSupplier() {
+ return this.credentialSupplier;
+ }
+
+ /**
+ * @return name of the role against which the login is being attempted.
+ */
+ public String getRole() {
+ return this.role;
+ }
+
+ /**
+ * @return {@link Duration} of the JWT to generate.
+ */
+ public Duration getJwtValidity() {
+ return this.jwtValidity;
+ }
+
+ /**
+ * @return {@link Clock} used to calculate epoch seconds until the JWT expires.
+ */
+ public Clock getClock() {
+ return this.clock;
+ }
+
+ /**
+ * @return the service account id to use as sub/iss claims.
+ * @since 2.1
+ */
+ public GcpCredentialsAccountIdAccessor getServiceAccountIdAccessor() {
+ return this.serviceAccountIdAccessor;
+ }
+
+ /**
+ * Builder for {@link GcpIamCredentialsAuthenticationOptions}.
+ */
+ public static class GcpIamCredentialsAuthenticationOptionsBuilder {
+
+ private String path = DEFAULT_GCP_AUTHENTICATION_PATH;
+
+ @Nullable
+ private String role;
+
+ @Nullable
+ private GcpCredentialsSupplier credentialsSupplier;
+
+ private Duration jwtValidity = Duration.ofMinutes(15);
+
+ private Clock clock = Clock.systemDefaultZone();
+
+ private GcpCredentialsAccountIdAccessor serviceAccountIdAccessor = DefaultGcpCredentialsAccessors.INSTANCE;
+
+ GcpIamCredentialsAuthenticationOptionsBuilder() {
+ }
+
+ /**
+ * Configure the mount path, defaults to {@literal aws}.
+ * @param path must not be empty or {@literal null}.
+ * @return {@code this}
+ * {@link GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder}.
+ */
+ public GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder path(String path) {
+
+ Assert.hasText(path, "Path must not be empty");
+
+ this.path = path;
+ return this;
+ }
+
+ /**
+ * Configure static Google credentials, required to create a signed JWT. Either
+ * use static credentials or provide a
+ * {@link #credentialsSupplier(GcpCredentialsSupplier) credentials provider}.
+ * @param credentials must not be {@literal null}.
+ * @return {@code this}
+ * {@link GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder}.
+ * @see #credentialsSupplier(GcpCredentialsSupplier)
+ */
+ public GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder credentials(
+ GoogleCredentials credentials) {
+
+ Assert.notNull(credentials, "ServiceAccountCredentials must not be null");
+
+ return credentialsSupplier(() -> credentials);
+ }
+
+ /**
+ * Configure a {@link GcpCredentialsSupplier}, required to create a signed JWT.
+ * Alternatively, configure static {@link #credentials(GoogleCredentials)
+ * credentials}.
+ * @param credentialsSupplier must not be {@literal null}.
+ * @return {@code this}
+ * {@link GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder}.
+ * @see #credentials(GoogleCredentials)
+ */
+ public GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder credentialsSupplier(
+ GcpCredentialsSupplier credentialsSupplier) {
+
+ Assert.notNull(credentialsSupplier, "GcpServiceAccountCredentialsSupplier must not be null");
+
+ this.credentialsSupplier = credentialsSupplier;
+ return this;
+ }
+
+ /**
+ * Configure an explicit service account id to use in GCP IAM calls. If none is
+ * configured, falls back to using {@link ServiceAccountCredentials#getAccount()}.
+ * @param serviceAccountId the service account id (email) to use
+ * @return {@code this}
+ * {@link GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder}.
+ * @since 2.1
+ */
+ public GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder serviceAccountId(
+ String serviceAccountId) {
+
+ Assert.notNull(serviceAccountId, "Service account id may not be null");
+
+ return serviceAccountIdAccessor((GoogleCredentials credentials) -> serviceAccountId);
+ }
+
+ /**
+ * Configure an {@link GcpCredentialsAccountIdAccessor} to obtain the service
+ * account id used in GCP IAM calls. If none is configured, falls back to using
+ * {@link ServiceAccountCredentials#getAccount()}.
+ * @param serviceAccountIdAccessor the service account id provider to use
+ * @return {@code this}
+ * {@link GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder}.
+ * @see GcpCredentialsAccountIdAccessor
+ * @since 2.1
+ */
+ GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder serviceAccountIdAccessor(
+ GcpCredentialsAccountIdAccessor serviceAccountIdAccessor) {
+
+ Assert.notNull(serviceAccountIdAccessor, "GcpServiceAccountIdAccessor must not be null");
+
+ this.serviceAccountIdAccessor = serviceAccountIdAccessor;
+ return this;
+ }
+
+ /**
+ * Configure the name of the role against which the login is being attempted.
+ * @param role must not be empty or {@literal null}.
+ * @return {@code this}
+ * {@link GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder}.
+ */
+ public GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder role(String role) {
+
+ Assert.hasText(role, "Role must not be null or empty");
+
+ this.role = role;
+ return this;
+ }
+
+ /**
+ * Configure the {@link Duration} for the JWT expiration. This defaults to 15
+ * minutes and cannot be more than a hour.
+ * @param jwtValidity must not be {@literal null}.
+ * @return {@code this}
+ * {@link GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder}.
+ */
+ public GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder jwtValidity(
+ Duration jwtValidity) {
+
+ Assert.hasText(this.role, "JWT validity duration must not be null");
+
+ this.jwtValidity = jwtValidity;
+ return this;
+ }
+
+ /**
+ * Configure the {@link Clock} used to calculate epoch seconds until the JWT
+ * expiration.
+ * @param clock must not be {@literal null}.
+ * @return {@code this}
+ * {@link GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder}.
+ */
+ public GcpIamCredentialsAuthenticationOptions.GcpIamCredentialsAuthenticationOptionsBuilder clock(Clock clock) {
+
+ Assert.hasText(this.role, "Clock must not be null");
+
+ this.clock = clock;
+ return this;
+ }
+
+ /**
+ * Build a new {@link GcpIamCredentialsAuthenticationOptions} instance.
+ * @return a new {@link GcpIamCredentialsAuthenticationOptions}.
+ */
+ public GcpIamCredentialsAuthenticationOptions build() {
+
+ Assert.notNull(this.credentialsSupplier, "GcpServiceAccountCredentialsSupplier must not be null");
+ Assert.notNull(this.role, "Role must not be null");
+
+ return new GcpIamCredentialsAuthenticationOptions(this.path, this.credentialsSupplier, this.role,
+ this.jwtValidity, this.clock, this.serviceAccountIdAccessor);
+ }
+
+ }
+
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/GcpIamCredentialsAuthenticationOptionsBuilderUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/GcpIamCredentialsAuthenticationOptionsBuilderUnitTests.java
new file mode 100644
index 00000000..d618b1e4
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/GcpIamCredentialsAuthenticationOptionsBuilderUnitTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.
+ */
+package org.springframework.vault.authentication;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+import java.security.PrivateKey;
+import java.util.Date;
+
+import org.junit.jupiter.api.Test;
+
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+
+/**
+ * Unit tests for {@link GcpIamCredentialsAuthenticationOptions}.
+ *
+ * @author Andreas Gebauer
+ */
+class GcpIamCredentialsAuthenticationOptionsBuilderUnitTests {
+
+ @Test
+ void shouldDefaultToCredentialServiceAccountId() {
+
+ ServiceAccountCredentials credentials = createServiceAccountCredentials();
+
+ GcpIamCredentialsAuthenticationOptions options = GcpIamCredentialsAuthenticationOptions.builder()
+ .credentials(credentials).role("foo").build();
+
+ assertThat(options.getServiceAccountIdAccessor().getServiceAccountId(credentials)).isEqualTo("hello@world");
+ }
+
+ @Test
+ void shouldAllowServiceAccountIdOverride() {
+
+ ServiceAccountCredentials credential = createServiceAccountCredentials();
+
+ GcpIamCredentialsAuthenticationOptions options = GcpIamCredentialsAuthenticationOptions.builder()
+ .credentials(credential).serviceAccountId("override@foo.com").role("foo").build();
+
+ assertThat(options.getServiceAccountIdAccessor().getServiceAccountId(credential)).isEqualTo("override@foo.com");
+ }
+
+ @Test
+ void shouldAllowServiceAccountIdProviderOverride() {
+
+ ServiceAccountCredentials credential = createServiceAccountCredentials();
+
+ GcpIamCredentialsAuthenticationOptions options = GcpIamCredentialsAuthenticationOptions.builder()
+ .credentials(credential)
+ .serviceAccountIdAccessor((GoogleCredentials googleCredential) -> "override@foo.com").role("foo")
+ .build();
+
+ assertThat(options.getServiceAccountIdAccessor().getServiceAccountId(credential)).isEqualTo("override@foo.com");
+ }
+
+ private static ServiceAccountCredentials createServiceAccountCredentials() {
+ return (ServiceAccountCredentials) ServiceAccountCredentials.newBuilder().setClientEmail("hello@world")
+ .setProjectId("project-id").setPrivateKey(mock(PrivateKey.class)).setPrivateKeyId("key-id")
+ .setAccessToken(new AccessToken("foobar", new Date())).build();
+ }
+
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/GcpIamCredentialsAuthenticationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/GcpIamCredentialsAuthenticationUnitTests.java
new file mode 100644
index 00000000..e81b3e34
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/GcpIamCredentialsAuthenticationUnitTests.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.
+ */
+package org.springframework.vault.authentication;
+
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.client.MockRestServiceServer;
+import org.springframework.vault.client.VaultClients.PrefixAwareUriTemplateHandler;
+import org.springframework.vault.support.VaultToken;
+import org.springframework.web.client.RestTemplate;
+
+import com.google.api.gax.grpc.GrpcTransportChannel;
+import com.google.api.gax.rpc.FixedTransportChannelProvider;
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+import com.google.cloud.iam.credentials.v1.SignJwtRequest;
+import com.google.cloud.iam.credentials.v1.SignJwtResponse;
+
+import io.grpc.ManagedChannel;
+import io.grpc.MethodDescriptor;
+import io.grpc.Server;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.protobuf.lite.ProtoLiteUtils;
+import io.grpc.stub.ServerCalls;
+
+/**
+ * Unit tests for {@link GcpIamCredentialsAuthentication}.
+ *
+ * @author Andreas Gebauer
+ */
+class GcpIamCredentialsAuthenticationUnitTests {
+
+ RestTemplate restTemplate;
+
+ MockRestServiceServer mockRest;
+
+ private Server server;
+
+ private ManagedChannel managedChannel;
+
+ private ServerCalls.UnaryMethod serverCall;
+
+ @BeforeEach
+ void before() throws IOException {
+
+ RestTemplate restTemplate = new RestTemplate();
+ restTemplate.setUriTemplateHandler(new PrefixAwareUriTemplateHandler());
+
+ this.mockRest = MockRestServiceServer.createServer(restTemplate);
+ this.restTemplate = restTemplate;
+
+ String serverName = InProcessServerBuilder.generateName();
+ this.server = InProcessServerBuilder.forName(serverName).directExecutor()
+ .addService(ServerServiceDefinition.builder("google.iam.credentials.v1.IAMCredentials")
+ .addMethod(
+ MethodDescriptor
+ .newBuilder(ProtoLiteUtils.marshaller(SignJwtRequest.getDefaultInstance()),
+ ProtoLiteUtils.marshaller(SignJwtResponse.getDefaultInstance()))
+ .setType(MethodDescriptor.MethodType.UNARY)
+ .setFullMethodName("google.iam.credentials.v1.IAMCredentials/SignJwt").build(),
+ asyncUnaryCall((request, responseObserver) -> {
+ this.serverCall.invoke(request, responseObserver);
+ }))
+ .build())
+ .build().start();
+ this.managedChannel = InProcessChannelBuilder.forName(serverName).directExecutor().build();
+ }
+
+ @AfterEach
+ void after() {
+ this.server.shutdown();
+ }
+
+ @Test
+ void shouldLogin() {
+ this.serverCall = ((request, responseObserver) -> {
+ SignJwtResponse signJwtResponse = SignJwtResponse.newBuilder().setSignedJwt("my-jwt").setKeyId("key-id")
+ .build();
+ responseObserver.onNext(signJwtResponse);
+ responseObserver.onCompleted();
+ });
+
+ this.mockRest.expect(requestTo("/auth/gcp/login")).andExpect(method(HttpMethod.POST))
+ .andExpect(jsonPath("$.role").value("dev-role")).andExpect(jsonPath("$.jwt").value("my-jwt"))
+ .andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON).body(
+ "{" + "\"auth\":{\"client_token\":\"my-token\", \"renewable\": true, \"lease_duration\": 10}"
+ + "}"));
+
+ PrivateKey privateKeyMock = mock(PrivateKey.class);
+ ServiceAccountCredentials credential = (ServiceAccountCredentials) ServiceAccountCredentials.newBuilder()
+ .setClientEmail("hello@world").setProjectId("foobar").setPrivateKey(privateKeyMock)
+ .setPrivateKeyId("key-id")
+ .setAccessToken(new AccessToken("foobar", Date.from(Instant.now().plus(1, ChronoUnit.DAYS)))).build();
+
+ GcpIamCredentialsAuthenticationOptions options = GcpIamCredentialsAuthenticationOptions.builder()
+ .role("dev-role").credentials(credential).build();
+ GcpIamCredentialsAuthentication authentication = new GcpIamCredentialsAuthentication(options, this.restTemplate,
+ FixedTransportChannelProvider.create(GrpcTransportChannel.create(managedChannel)));
+
+ VaultToken login = authentication.login();
+
+ assertThat(login).isInstanceOf(LoginToken.class);
+ assertThat(login.getToken()).isEqualTo("my-token");
+
+ LoginToken loginToken = (LoginToken) login;
+ assertThat(loginToken.isRenewable()).isTrue();
+ assertThat(loginToken.getLeaseDuration()).isEqualTo(Duration.ofSeconds(10));
+ }
+
+ @Test
+ void shouldCreateNewGcpIamObjectInstance() {
+
+ PrivateKey privateKeyMock = mock(PrivateKey.class);
+ ServiceAccountCredentials credential = (ServiceAccountCredentials) ServiceAccountCredentials.newBuilder()
+ .setClientEmail("hello@world").setProjectId("foobar").setPrivateKey(privateKeyMock)
+ .setPrivateKeyId("key-id")
+ .setAccessToken(new AccessToken("foobar", Date.from(Instant.now().plus(1, ChronoUnit.DAYS)))).build();
+
+ GcpIamCredentialsAuthenticationOptions options = GcpIamCredentialsAuthenticationOptions.builder()
+ .role("dev-role").credentials(credential).build();
+
+ new GcpIamCredentialsAuthentication(options, this.restTemplate);
+ }
+
+}
diff --git a/spring-vault-dependencies/pom.xml b/spring-vault-dependencies/pom.xml
index 583e7e4c..d0e3200f 100644
--- a/spring-vault-dependencies/pom.xml
+++ b/spring-vault-dependencies/pom.xml
@@ -65,6 +65,7 @@
2.12.0
1.11.924
v1-rev20201112-1.31.0
+ 1.1.9
0.22.2
1.67
@@ -144,6 +145,13 @@
true
+
+ com.google.cloud
+ google-cloud-iamcredentials
+ ${google-cloud-iamcredentials.version}
+ true
+
+
com.google.auth
google-auth-library-oauth2-http