Add support for GCP authentication.
We now support GCP Compute and GCP IAM authentication. We also provide different RestTemplate's for Vault and for external service communication. Closes gh-249.
This commit is contained in:
@@ -485,6 +485,125 @@ See also:
|
||||
* https://www.vaultproject.io/docs/secrets/cubbyhole/index.html[Vault Documentation: Cubbyhole Secret Backend]
|
||||
* https://www.vaultproject.io/docs/concepts/response-wrapping.html[Vault Documentation: Response Wrapping]
|
||||
|
||||
[[vault.config.authentication.gcpgce]]
|
||||
== GCP-GCE authentication
|
||||
|
||||
The https://www.vaultproject.io/docs/auth/gcp.html[gcp]
|
||||
auth backend allows Vault login by using existing GCP (Google Cloud Platform) IAM and GCE credentials.
|
||||
|
||||
GCP GCE (Google Compute Engine) authentication creates a signature in the form of a
|
||||
JSON Web Token (JWT) for a service account. A JWT for a Compute Engine instance
|
||||
is obtained from the GCE metadata service using https://cloud.google.com/compute/docs/instances/verifying-instance-identity[Instance identification].
|
||||
This API creates a JSON Web Token that can be used to confirm the instance identity.
|
||||
|
||||
Unlike most Vault authentication backends, this backend
|
||||
does not require first-deploying, or provisioning security-sensitive
|
||||
credentials (tokens, username/password, client certificates, etc.).
|
||||
Instead, it treats GCP as a Trusted Third Party and uses the
|
||||
cryptographically signed dynamic metadata information that uniquely
|
||||
represents each GCP service account.
|
||||
|
||||
.bootstrap.yml with required GCP-GCE Authentication properties
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring.cloud.vault:
|
||||
authentication: GCP_GCE
|
||||
gcp-gce:
|
||||
role: my-dev-role
|
||||
----
|
||||
====
|
||||
|
||||
.bootstrap.yml with all GCP-GCE Authentication properties
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring.cloud.vault:
|
||||
authentication: GCP_GCE
|
||||
gcp-gce:
|
||||
gcp-path: gcp
|
||||
role: my-dev-role
|
||||
service-account: my-service@projectid.iam.gserviceaccount.com
|
||||
----
|
||||
====
|
||||
|
||||
* `role` sets the name of the role against which the login is being attempted.
|
||||
* `gcp-path` sets the path of the GCP mount to use
|
||||
* `service-account` allows overriding the service account Id to a specific value. Defaults to the `default` service account.
|
||||
|
||||
See also:
|
||||
|
||||
* https://www.vaultproject.io/docs/auth/gcp.html[Vault Documentation: Using the GCP auth backend]
|
||||
* https://cloud.google.com/compute/docs/instances/verifying-instance-identity[GCP Documentation: Verifying the Identity of Instances]
|
||||
|
||||
[[vault.config.authentication.gcpiam]]
|
||||
== GCP-IAM authentication
|
||||
|
||||
The https://www.vaultproject.io/docs/auth/gcp.html[gcp]
|
||||
auth backend allows Vault login by using existing GCP (Google Cloud Platform) IAM and GCE credentials.
|
||||
|
||||
GCP IAM authentication creates a signature in the form of a JSON Web Token (JWT)
|
||||
for a service account. A JWT for a service account is obtained by
|
||||
calling GCP IAM's https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signJwt[`projects.serviceAccounts.signJwt`] API. The caller authenticates against GCP IAM
|
||||
and proves thereby its identity. This Vault backend treats GCP as a Trusted Third Party.
|
||||
|
||||
IAM credentials can be obtained from either the runtime environment
|
||||
, specifically the https://cloud.google.com/docs/authentication/production[`GOOGLE_APPLICATION_CREDENTIALS`]
|
||||
environment variable, the Google Compute metadata service,
|
||||
or supplied externally as e.g. JSON or base64 encoded.
|
||||
JSON is the preferred form as it carries the project id and
|
||||
service account identifier required for calling ``projects.serviceAccounts.signJwt``.
|
||||
|
||||
.bootstrap.yml with required GCP-IAM Authentication properties
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring.cloud.vault:
|
||||
authentication: GCP_IAM
|
||||
gcp-iam:
|
||||
role: my-dev-role
|
||||
----
|
||||
====
|
||||
|
||||
.bootstrap.yml with all GCP-IAM Authentication properties
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring.cloud.vault:
|
||||
authentication: GCP_IAM
|
||||
gcp-iam:
|
||||
credentials:
|
||||
location: classpath:credentials.json
|
||||
encoded-key: e+KApn0=
|
||||
gcp-path: gcp
|
||||
jwt-validity: 15m
|
||||
project-id: my-project-id
|
||||
role: my-dev-role
|
||||
service-account: my-service@projectid.iam.gserviceaccount.com
|
||||
----
|
||||
====
|
||||
|
||||
* `role` sets the name of the role against which the login is being attempted.
|
||||
* `credentials.location` path to the credentials resource that contains Google credentials in JSON format.
|
||||
* `credentials.encoded-key` the base64 encoded contents of an OAuth2 account private key in the JSON format.
|
||||
* `gcp-path` sets the path of the GCP mount to use
|
||||
* `jwt-validity` configures the JWT token validity. Defaults to 15 minutes.
|
||||
* `project-id` allows overriding the project Id to a specific value. Defaults to the project Id from the obtained credential.
|
||||
* `service-account` allows overriding the service account Id to a specific value. Defaults to the service account from the obtained credential.
|
||||
|
||||
GCP IAM authentication requires the Google Cloud Java SDK dependency
|
||||
(`com.google.apis:google-api-services-iam` and `com.google.auth:google-auth-library-oauth2-http`)
|
||||
as the authentication implementation uses Google APIs for credentials and JWT signing.
|
||||
|
||||
NOTE: Google credentials require an OAuth 2 token maintaining the token lifecycle. All API
|
||||
is synchronous therefore, `GcpIamAuthentication` does not support `AuthenticationSteps` which is
|
||||
required for reactive usage.
|
||||
|
||||
See also:
|
||||
|
||||
* https://www.vaultproject.io/docs/auth/gcp.html[Vault Documentation: Using the GCP auth backend]
|
||||
* https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signJwt[GCP Documentation: projects.serviceAccounts.signJwt][[vault.authentication.gcpiam]]
|
||||
|
||||
[[vault.config.authentication.kubernetes]]
|
||||
=== Kubernetes authentication
|
||||
|
||||
|
||||
18
pom.xml
18
pom.xml
@@ -46,6 +46,9 @@
|
||||
<httpcore.version>4.4.9</httpcore.version>
|
||||
<netty.version>4.1.30.Final</netty.version>
|
||||
<okhttp3.version>3.10.0</okhttp3.version>
|
||||
|
||||
<google-api-services-iam.version>v1-rev259-1.25.0</google-api-services-iam.version>
|
||||
<google-auth-library-oauth2-http.version>0.10.0</google-auth-library-oauth2-http.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -105,6 +108,21 @@
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- GCP SDK -->
|
||||
<dependency>
|
||||
<groupId>com.google.apis</groupId>
|
||||
<artifactId>google-api-services-iam</artifactId>
|
||||
<version>${google-api-services-iam.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.auth</groupId>
|
||||
<artifactId>google-auth-library-oauth2-http</artifactId>
|
||||
<version>${google-auth-library-oauth2-http.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -53,6 +53,18 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.apis</groupId>
|
||||
<artifactId>google-api-services-iam</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.auth</groupId>
|
||||
<artifactId>google-auth-library-oauth2-http</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
|
||||
@@ -15,18 +15,26 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.amazonaws.auth.AWSCredentials;
|
||||
import com.amazonaws.auth.AWSCredentialsProvider;
|
||||
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.cloud.vault.config.VaultProperties.AppRoleProperties;
|
||||
import org.springframework.cloud.vault.config.VaultProperties.AwsIamProperties;
|
||||
import org.springframework.cloud.vault.config.VaultProperties.AzureMsiProperties;
|
||||
import org.springframework.cloud.vault.config.VaultProperties.GcpCredentials;
|
||||
import org.springframework.cloud.vault.config.VaultProperties.GcpIamProperties;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -36,6 +44,8 @@ import org.springframework.vault.authentication.AppRoleAuthenticationOptions.Rol
|
||||
import org.springframework.vault.authentication.AppRoleAuthenticationOptions.SecretId;
|
||||
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions.Nonce;
|
||||
import org.springframework.vault.authentication.AwsIamAuthenticationOptions.AwsIamAuthenticationOptionsBuilder;
|
||||
import org.springframework.vault.authentication.GcpComputeAuthenticationOptions.GcpComputeAuthenticationOptionsBuilder;
|
||||
import org.springframework.vault.authentication.GcpIamAuthenticationOptions.GcpIamAuthenticationOptionsBuilder;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
@@ -48,12 +58,15 @@ import org.springframework.web.client.RestOperations;
|
||||
* @since 1.1
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@CommonsLog
|
||||
class ClientAuthenticationFactory {
|
||||
|
||||
private final VaultProperties vaultProperties;
|
||||
|
||||
private final RestOperations restOperations;
|
||||
|
||||
private final RestOperations externalRestOperations;
|
||||
|
||||
/**
|
||||
* @return a new {@link ClientAuthentication}.
|
||||
*/
|
||||
@@ -82,6 +95,12 @@ class ClientAuthenticationFactory {
|
||||
case CUBBYHOLE:
|
||||
return cubbyholeAuthentication();
|
||||
|
||||
case GCP_GCE:
|
||||
return gcpGceAuthentication(vaultProperties);
|
||||
|
||||
case GCP_IAM:
|
||||
return gcpIamAuthentication(vaultProperties);
|
||||
|
||||
case KUBERNETES:
|
||||
return kubernetesAuthentication(vaultProperties);
|
||||
|
||||
@@ -224,7 +243,7 @@ class ClientAuthenticationFactory {
|
||||
.build();
|
||||
|
||||
return new AwsEc2Authentication(authenticationOptions, restOperations,
|
||||
restOperations);
|
||||
externalRestOperations);
|
||||
}
|
||||
|
||||
private ClientAuthentication awsIamAuthentication(VaultProperties vaultProperties) {
|
||||
@@ -264,7 +283,7 @@ class ClientAuthenticationFactory {
|
||||
AzureMsiAuthenticationOptions options = AzureMsiAuthenticationOptions.builder()
|
||||
.role(azureMsi.getRole()).build();
|
||||
|
||||
return new AzureMsiAuthentication(options, restOperations);
|
||||
return new AzureMsiAuthentication(options, restOperations, externalRestOperations);
|
||||
}
|
||||
|
||||
private ClientAuthentication cubbyholeAuthentication() {
|
||||
@@ -280,6 +299,72 @@ class ClientAuthenticationFactory {
|
||||
return new CubbyholeAuthentication(options, restOperations);
|
||||
}
|
||||
|
||||
private ClientAuthentication gcpGceAuthentication(VaultProperties vaultProperties) {
|
||||
|
||||
VaultProperties.GcpGceProperties gcp = vaultProperties.getGcpGce();
|
||||
|
||||
Assert.hasText(gcp.getRole(),
|
||||
"Role (spring.cloud.vault.gcp-gce.role) must not be empty");
|
||||
|
||||
GcpComputeAuthenticationOptionsBuilder builder = GcpComputeAuthenticationOptions
|
||||
.builder().path(gcp.getGcpPath()).role(gcp.getRole());
|
||||
|
||||
if (StringUtils.hasText(gcp.getServiceAccount())) {
|
||||
builder.serviceAccount(gcp.getServiceAccount());
|
||||
}
|
||||
|
||||
return new GcpComputeAuthentication(builder.build(), restOperations,
|
||||
externalRestOperations);
|
||||
}
|
||||
|
||||
private ClientAuthentication gcpIamAuthentication(VaultProperties vaultProperties) {
|
||||
|
||||
VaultProperties.GcpIamProperties gcp = vaultProperties.getGcpIam();
|
||||
|
||||
Assert.hasText(gcp.getRole(),
|
||||
"Role (spring.cloud.vault.gcp-iam.role) must not be empty");
|
||||
|
||||
GcpIamAuthenticationOptionsBuilder builder = GcpIamAuthenticationOptions
|
||||
.builder().path(gcp.getGcpPath()).role(gcp.getRole())
|
||||
.jwtValidity(gcp.getJwtValidity());
|
||||
|
||||
if (StringUtils.hasText(gcp.getProjectId())) {
|
||||
builder.projectId(gcp.getProjectId());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(gcp.getServiceAccountId())) {
|
||||
builder.serviceAccountId(gcp.getServiceAccountId());
|
||||
}
|
||||
|
||||
GcpCredentialSupplier supplier = () -> getGoogleCredential(gcp);
|
||||
builder.credential(supplier.get());
|
||||
|
||||
GcpIamAuthenticationOptions options = builder.build();
|
||||
|
||||
try {
|
||||
return new GcpIamAuthentication(options, restOperations);
|
||||
}
|
||||
catch (IOException | GeneralSecurityException e) {
|
||||
throw new IllegalStateException("Cannot create GcpIamAuthentication", e);
|
||||
}
|
||||
}
|
||||
|
||||
private GoogleCredential getGoogleCredential(GcpIamProperties gcp) throws IOException {
|
||||
|
||||
GcpCredentials credentialProperties = gcp.getCredentials();
|
||||
if (credentialProperties.getLocation() != null) {
|
||||
return GoogleCredential.fromStream(credentialProperties.getLocation()
|
||||
.getInputStream());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(credentialProperties.getEncodedKey())) {
|
||||
return GoogleCredential.fromStream(new ByteArrayInputStream(Base64
|
||||
.getDecoder().decode(credentialProperties.getEncodedKey())));
|
||||
}
|
||||
|
||||
return GoogleCredential.getApplicationDefault();
|
||||
}
|
||||
|
||||
private ClientAuthentication kubernetesAuthentication(VaultProperties vaultProperties) {
|
||||
|
||||
VaultProperties.KubernetesProperties kubernetes = vaultProperties.getKubernetes();
|
||||
|
||||
@@ -49,6 +49,7 @@ import org.springframework.vault.core.VaultTemplate;
|
||||
import org.springframework.vault.support.ClientOptions;
|
||||
import org.springframework.vault.support.SslConfiguration;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for Spring Vault support.
|
||||
@@ -68,8 +69,16 @@ public class VaultBootstrapConfiguration implements InitializingBean {
|
||||
|
||||
private final VaultEndpointProvider endpointProvider;
|
||||
|
||||
/**
|
||||
* Used for Vault communication.
|
||||
*/
|
||||
private RestOperations restOperations;
|
||||
|
||||
/**
|
||||
* Used for external (AWS, GCP) communication.
|
||||
*/
|
||||
private RestOperations externalRestOperations;
|
||||
|
||||
public VaultBootstrapConfiguration(ConfigurableApplicationContext applicationContext,
|
||||
VaultProperties vaultProperties,
|
||||
ObjectProvider<VaultEndpointProvider> endpointProvider) {
|
||||
@@ -96,6 +105,8 @@ public class VaultBootstrapConfiguration implements InitializingBean {
|
||||
|
||||
this.restOperations = VaultClients.createRestTemplate(endpointProvider,
|
||||
clientHttpRequestFactory);
|
||||
|
||||
this.externalRestOperations = new RestTemplate(clientHttpRequestFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,7 +201,7 @@ public class VaultBootstrapConfiguration implements InitializingBean {
|
||||
public ClientAuthentication clientAuthentication() {
|
||||
|
||||
ClientAuthenticationFactory factory = new ClientAuthenticationFactory(
|
||||
vaultProperties, restOperations);
|
||||
vaultProperties, restOperations, externalRestOperations);
|
||||
|
||||
return factory.createClientAuthentication();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.cloud.vault.config;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import lombok.Data;
|
||||
@@ -100,6 +102,10 @@ public class VaultProperties implements EnvironmentAware {
|
||||
|
||||
private AzureMsiProperties azureMsi = new AzureMsiProperties();
|
||||
|
||||
private GcpGceProperties gcpGce = new GcpGceProperties();
|
||||
|
||||
private GcpIamProperties gcpIam = new GcpIamProperties();
|
||||
|
||||
private KubernetesProperties kubernetes = new KubernetesProperties();
|
||||
|
||||
private Ssl ssl = new Ssl();
|
||||
@@ -265,6 +271,79 @@ public class VaultProperties implements EnvironmentAware {
|
||||
private String role = "";
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class GcpGceProperties {
|
||||
|
||||
/**
|
||||
* Mount path of the Kubernetes authentication backend.
|
||||
*/
|
||||
@NotEmpty
|
||||
private String gcpPath = "gcp";
|
||||
|
||||
/**
|
||||
* Name of the role against which the login is being attempted.
|
||||
*/
|
||||
private String role = "";
|
||||
|
||||
/**
|
||||
* Optional service account id. Using the default id if left unconfigured.
|
||||
*/
|
||||
private String serviceAccount = "";
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class GcpIamProperties {
|
||||
|
||||
/**
|
||||
* Mount path of the Kubernetes authentication backend.
|
||||
*/
|
||||
@NotEmpty
|
||||
private String gcpPath = "gcp";
|
||||
|
||||
/**
|
||||
* Name of the role against which the login is being attempted.
|
||||
*/
|
||||
private String role = "";
|
||||
|
||||
/**
|
||||
* Overrides the GCP project Id.
|
||||
*/
|
||||
private String projectId = "";
|
||||
|
||||
/**
|
||||
* Overrides the GCP service account Id.
|
||||
*/
|
||||
private String serviceAccountId = "";
|
||||
|
||||
/**
|
||||
* Validity of the JWT token.
|
||||
*/
|
||||
private Duration jwtValidity = Duration.ofMinutes(15);
|
||||
|
||||
/**
|
||||
* Credentials configuration.
|
||||
*/
|
||||
private final GcpCredentials credentials = new GcpCredentials();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class GcpCredentials {
|
||||
|
||||
/**
|
||||
* Location of the OAuth2 credentials private key.
|
||||
*
|
||||
* <p>
|
||||
* Since this is a Resource, the private key can be in a multitude of locations,
|
||||
* such as a local file system, classpath, URL, etc.
|
||||
*/
|
||||
private Resource location;
|
||||
|
||||
/**
|
||||
* The base64 encoded contents of an OAuth2 account private key in JSON format.
|
||||
*/
|
||||
private String encodedKey;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class KubernetesProperties {
|
||||
|
||||
@@ -345,6 +424,6 @@ public class VaultProperties implements EnvironmentAware {
|
||||
}
|
||||
|
||||
public enum AuthenticationMethod {
|
||||
TOKEN, APPID, APPROLE, AWS_EC2, AWS_IAM, AZURE_MSI, CERT, CUBBYHOLE, KUBERNETES
|
||||
TOKEN, APPID, APPROLE, AWS_EC2, AWS_IAM, AZURE_MSI, CERT, CUBBYHOLE, GCP_GCE, GCP_IAM, KUBERNETES
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user