Add support for additional Vault authentication methods. (#1526)

* Add support for additional Vault authentication methods.

* Fix Vault client config test setup so it doesn't rely on local state.

Fixes gh-1475
This commit is contained in:
Scott Frederick
2019-12-19 10:20:46 -06:00
committed by Spencer Gibb
parent bfe4658f4b
commit 814d0b1e39
8 changed files with 1732 additions and 74 deletions

View File

@@ -29,6 +29,7 @@
<bintray.package>config</bintray.package>
<spring-cloud-commons.version>2.2.1.BUILD-SNAPSHOT</spring-cloud-commons.version>
<aws-java-sdk.version>1.11.52</aws-java-sdk.version>
<google-api-services-iam.version>v1-rev20191010-1.30.3</google-api-services-iam.version>
<maven-checkstyle-plugin.failsOnError>true</maven-checkstyle-plugin.failsOnError>
<maven-checkstyle-plugin.failsOnViolation>true
</maven-checkstyle-plugin.failsOnViolation>
@@ -76,6 +77,11 @@
<artifactId>aws-java-sdk-s3</artifactId>
<version>${aws-java-sdk.version}</version>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-iam</artifactId>
<version>${google-api-services-iam.version}</version>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>

View File

@@ -99,6 +99,11 @@
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</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>

View File

@@ -71,6 +71,7 @@ import org.springframework.cloud.config.server.environment.SvnKitEnvironmentRepo
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
import org.springframework.cloud.config.server.environment.VaultEnvironmentRepository;
import org.springframework.cloud.config.server.environment.VaultEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.vault.SpringVaultClientConfiguration;
import org.springframework.cloud.config.server.environment.vault.SpringVaultEnvironmentRepository;
import org.springframework.cloud.config.server.environment.vault.SpringVaultEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.support.GoogleCloudSourceSupport;
@@ -244,12 +245,19 @@ public class EnvironmentRepositoryConfiguration {
@ConditionalOnClass(VaultTemplate.class)
static class SpringVaultFactoryConfig {
@Bean
public SpringVaultClientConfiguration vaultClientConfiguration(
VaultEnvironmentProperties vaultProperties,
ConfigTokenProvider tokenProvider) {
return new SpringVaultClientConfiguration(vaultProperties, tokenProvider);
}
@Bean
public SpringVaultEnvironmentRepositoryFactory vaultEnvironmentRepositoryFactory(
ObjectProvider<HttpServletRequest> request, EnvironmentWatch watch,
ConfigTokenProvider tokenProvider) {
SpringVaultClientConfiguration vaultClientConfiguration) {
return new SpringVaultEnvironmentRepositoryFactory(request, watch,
tokenProvider);
vaultClientConfiguration);
}
}

View File

@@ -16,17 +16,24 @@
package org.springframework.cloud.config.server.environment;
import java.net.URI;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import javax.validation.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.proxy.ProxyHostProperties;
import org.springframework.cloud.config.server.support.HttpEnvironmentRepositoryProperties;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
import org.springframework.validation.annotation.Validated;
/**
* @author Dylan Roberts
* @author Haroun Pacquee
* @author Scott Frederick
*/
@ConfigurationProperties("spring.cloud.config.server.vault")
public class VaultEnvironmentProperties implements HttpEnvironmentRepositoryProperties {
@@ -79,6 +86,31 @@ public class VaultEnvironmentProperties implements HttpEnvironmentRepositoryProp
*/
private String namespace;
/**
* Static vault token. Required if {@link #authentication} is {@code TOKEN}.
*/
private String token;
private AppRoleProperties appRole = new AppRoleProperties();
private AwsEc2Properties awsEc2 = new AwsEc2Properties();
private AwsIamProperties awsIam = new AwsIamProperties();
private AzureMsiProperties azureMsi = new AzureMsiProperties();
private GcpGceProperties gcpGce = new GcpGceProperties();
private GcpIamProperties gcpIam = new GcpIamProperties();
private KubernetesProperties kubernetes = new KubernetesProperties();
private PcfProperties pcf = new PcfProperties();
private Ssl ssl = new Ssl();
private AuthenticationMethod authentication;
public String getHost() {
return this.host;
}
@@ -180,4 +212,685 @@ public class VaultEnvironmentProperties implements HttpEnvironmentRepositoryProp
this.namespace = namespace;
}
public String getToken() {
return this.token;
}
public void setToken(String token) {
this.token = token;
}
public AppRoleProperties getAppRole() {
return this.appRole;
}
public AwsEc2Properties getAwsEc2() {
return this.awsEc2;
}
public AwsIamProperties getAwsIam() {
return this.awsIam;
}
public AzureMsiProperties getAzureMsi() {
return this.azureMsi;
}
public GcpGceProperties getGcpGce() {
return this.gcpGce;
}
public GcpIamProperties getGcpIam() {
return this.gcpIam;
}
public KubernetesProperties getKubernetes() {
return this.kubernetes;
}
public PcfProperties getPcf() {
return this.pcf;
}
public Ssl getSsl() {
return this.ssl;
}
public void setAuthentication(AuthenticationMethod authentication) {
this.authentication = authentication;
}
public AuthenticationMethod getAuthentication() {
return authentication;
}
public enum AuthenticationMethod {
/**
* Vault AppRole machine authentication.
*/
APPROLE,
/**
* Amazon Web Services Compute authentication.
*/
AWS_EC2,
/**
* Amazon Web Services IAM authentication.
*/
AWS_IAM,
/**
* Azure Cloud MSI authentication.
*/
AZURE_MSI,
/**
* TLS certificate authentication.
*/
CERT,
/**
* Cubbyhole token authentication.
*/
CUBBYHOLE,
/**
* Google Cloud Compute authentication.
*/
GCP_GCE,
/**
* Google Cloud IAM authentication.
*/
GCP_IAM,
/**
* Kubernetes service account token authentication.
*/
KUBERNETES,
/**
* Cloud Foundry instance identity certificate authentication.
*/
PCF,
/**
* Static token authentication.
*/
TOKEN
}
/**
* AppRole properties.
*/
@Validated
public static class AppRoleProperties {
/**
* Mount path of the AppRole authentication backend.
*/
private String appRolePath = "approle";
/**
* Name of the role, optional, used for pull-mode.
*/
private String role = "";
/**
* The RoleId.
*/
private String roleId = null;
/**
* The SecretId.
*/
private String secretId = null;
public String getAppRolePath() {
return this.appRolePath;
}
public String getRole() {
return this.role;
}
public String getRoleId() {
return this.roleId;
}
public String getSecretId() {
return this.secretId;
}
public void setAppRolePath(String appRolePath) {
this.appRolePath = appRolePath;
}
public void setRole(String role) {
this.role = role;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
public void setSecretId(String secretId) {
this.secretId = secretId;
}
}
/**
* AWS-EC2 properties.
*/
@Validated
public static class AwsEc2Properties {
/**
* URL of the AWS-EC2 PKCS7 identity document.
*/
@NotEmpty
private String identityDocument = "http://169.254.169.254/latest/dynamic/instance-identity/pkcs7";
/**
* Mount path of the AWS-EC2 authentication backend.
*/
@NotEmpty
private String awsEc2Path = "aws-ec2";
/**
* Name of the role, optional.
*/
private String role = "";
/**
* Nonce used for AWS-EC2 authentication. An empty nonce defaults to nonce
* generation.
*/
private String nonce;
public String getIdentityDocument() {
return this.identityDocument;
}
public String getAwsEc2Path() {
return this.awsEc2Path;
}
public String getRole() {
return this.role;
}
public String getNonce() {
return this.nonce;
}
public void setIdentityDocument(String identityDocument) {
this.identityDocument = identityDocument;
}
public void setAwsEc2Path(String awsEc2Path) {
this.awsEc2Path = awsEc2Path;
}
public void setRole(String role) {
this.role = role;
}
public void setNonce(String nonce) {
this.nonce = nonce;
}
}
/**
* AWS-IAM properties.
*/
public static class AwsIamProperties {
/**
* Mount path of the AWS authentication backend.
*/
@NotEmpty
private String awsPath = "aws";
/**
* Name of the role, optional. Defaults to the friendly IAM name if not set.
*/
private String role = "";
/**
* Name of the server used to set {@code X-Vault-AWS-IAM-Server-ID} header in the
* headers of login requests.
*/
private String serverName;
/**
* STS server URI.
*
* @since 2.2
*/
private URI endpointUri;
public String getAwsPath() {
return this.awsPath;
}
public String getRole() {
return this.role;
}
public String getServerName() {
return this.serverName;
}
public void setAwsPath(String awsPath) {
this.awsPath = awsPath;
}
public void setRole(String role) {
this.role = role;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public URI getEndpointUri() {
return this.endpointUri;
}
public void setEndpointUri(URI endpointUri) {
this.endpointUri = endpointUri;
}
}
/**
* Azure MSI properties.
*/
public static class AzureMsiProperties {
/**
* Mount path of the Azure MSI authentication backend.
*/
@NotEmpty
private String azurePath = "azure";
/**
* Name of the role.
*/
private String role = "";
public String getAzurePath() {
return this.azurePath;
}
public String getRole() {
return this.role;
}
public void setAzurePath(String azurePath) {
this.azurePath = azurePath;
}
public void setRole(String role) {
this.role = role;
}
}
/**
* GCP-GCE properties.
*/
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 = "";
public String getGcpPath() {
return this.gcpPath;
}
public String getRole() {
return this.role;
}
public String getServiceAccount() {
return this.serviceAccount;
}
public void setGcpPath(String gcpPath) {
this.gcpPath = gcpPath;
}
public void setRole(String role) {
this.role = role;
}
public void setServiceAccount(String serviceAccount) {
this.serviceAccount = serviceAccount;
}
}
/**
* GCP-IAM properties.
*/
public static class GcpIamProperties {
/**
* Credentials configuration.
*/
private final GcpCredentials credentials = new GcpCredentials();
/**
* 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);
public GcpCredentials getCredentials() {
return this.credentials;
}
public String getGcpPath() {
return this.gcpPath;
}
public String getRole() {
return this.role;
}
public String getProjectId() {
return this.projectId;
}
public String getServiceAccountId() {
return this.serviceAccountId;
}
public Duration getJwtValidity() {
return this.jwtValidity;
}
public void setGcpPath(String gcpPath) {
this.gcpPath = gcpPath;
}
public void setRole(String role) {
this.role = role;
}
public void setProjectId(String projectId) {
this.projectId = projectId;
}
public void setServiceAccountId(String serviceAccountId) {
this.serviceAccountId = serviceAccountId;
}
public void setJwtValidity(Duration jwtValidity) {
this.jwtValidity = jwtValidity;
}
}
/**
* GCP credential properties.
*/
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;
public Resource getLocation() {
return this.location;
}
public String getEncodedKey() {
return this.encodedKey;
}
public void setLocation(Resource location) {
this.location = location;
}
public void setEncodedKey(String encodedKey) {
this.encodedKey = encodedKey;
}
}
/**
* Kubernetes properties.
*/
public static class KubernetesProperties {
/**
* Mount path of the Kubernetes authentication backend.
*/
@NotEmpty
private String kubernetesPath = "kubernetes";
/**
* Name of the role against which the login is being attempted.
*/
private String role = "";
/**
* Path to the service account token file.
*/
@NotEmpty
private String serviceAccountTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token";
public String getKubernetesPath() {
return this.kubernetesPath;
}
public String getRole() {
return this.role;
}
public String getServiceAccountTokenFile() {
return this.serviceAccountTokenFile;
}
public void setKubernetesPath(String kubernetesPath) {
this.kubernetesPath = kubernetesPath;
}
public void setRole(String role) {
this.role = role;
}
public void setServiceAccountTokenFile(String serviceAccountTokenFile) {
this.serviceAccountTokenFile = serviceAccountTokenFile;
}
}
/**
* PCF properties.
*/
public static class PcfProperties {
/**
* Mount path of the Kubernetes authentication backend.
*/
@NotEmpty
private String pcfPath = "pcf";
/**
* Name of the role against which the login is being attempted.
*/
private String role = "";
/**
* Path to the instance certificate (PEM). Defaults to {@code CF_INSTANCE_CERT}
* env variable.
*/
private Resource instanceCertificate;
/**
* Path to the instance key (PEM). Defaults to {@code CF_INSTANCE_KEY} env
* variable.
*/
private Resource instanceKey;
public String getPcfPath() {
return this.pcfPath;
}
public void setPcfPath(String pcfPath) {
this.pcfPath = pcfPath;
}
public String getRole() {
return this.role;
}
public void setRole(String role) {
this.role = role;
}
public Resource getInstanceCertificate() {
return this.instanceCertificate;
}
public void setInstanceCertificate(Resource instanceCertificate) {
this.instanceCertificate = instanceCertificate;
}
public Resource getInstanceKey() {
return this.instanceKey;
}
public void setInstanceKey(Resource instanceKey) {
this.instanceKey = instanceKey;
}
}
/**
* SSL properties.
*/
@Validated
public static class Ssl {
/**
* Trust store that holds certificates and private keys.
*/
private Resource keyStore;
/**
* Password used to access the key store.
*/
private String keyStorePassword;
/**
* Trust store that holds SSL certificates.
*/
private Resource trustStore;
/**
* Password used to access the trust store.
*/
private String trustStorePassword;
/**
* Mount path of the TLS cert authentication backend.
*/
@NotEmpty
private String certAuthPath = "cert";
public Resource getKeyStore() {
return this.keyStore;
}
public String getKeyStorePassword() {
return this.keyStorePassword;
}
public Resource getTrustStore() {
return this.trustStore;
}
public String getTrustStorePassword() {
return this.trustStorePassword;
}
public String getCertAuthPath() {
return this.certAuthPath;
}
public void setKeyStore(Resource keyStore) {
this.keyStore = keyStore;
}
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public void setTrustStore(Resource trustStore) {
this.trustStore = trustStore;
}
public void setTrustStorePassword(String trustStorePassword) {
this.trustStorePassword = trustStorePassword;
}
public void setCertAuthPath(String certAuthPath) {
this.certAuthPath = certAuthPath;
}
}
}

View File

@@ -0,0 +1,582 @@
/*
* Copyright 2018-2019 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.cloud.config.server.environment.vault;
import java.io.ByteArrayInputStream;
import java.net.URI;
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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.config.server.environment.ConfigTokenProvider;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AppRoleProperties;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AwsEc2Properties;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AwsIamProperties;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AzureMsiProperties;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.GcpCredentials;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.GcpIamProperties;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.KubernetesProperties;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.PcfProperties;
import org.springframework.core.io.Resource;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.vault.VaultException;
import org.springframework.vault.authentication.AppRoleAuthentication;
import org.springframework.vault.authentication.AppRoleAuthenticationOptions;
import org.springframework.vault.authentication.AppRoleAuthenticationOptions.AppRoleAuthenticationOptionsBuilder;
import org.springframework.vault.authentication.AppRoleAuthenticationOptions.RoleId;
import org.springframework.vault.authentication.AppRoleAuthenticationOptions.SecretId;
import org.springframework.vault.authentication.AwsEc2Authentication;
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions;
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions.Nonce;
import org.springframework.vault.authentication.AwsIamAuthentication;
import org.springframework.vault.authentication.AwsIamAuthenticationOptions;
import org.springframework.vault.authentication.AwsIamAuthenticationOptions.AwsIamAuthenticationOptionsBuilder;
import org.springframework.vault.authentication.AzureMsiAuthentication;
import org.springframework.vault.authentication.AzureMsiAuthenticationOptions;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.ClientCertificateAuthentication;
import org.springframework.vault.authentication.CubbyholeAuthentication;
import org.springframework.vault.authentication.CubbyholeAuthenticationOptions;
import org.springframework.vault.authentication.GcpComputeAuthentication;
import org.springframework.vault.authentication.GcpComputeAuthenticationOptions;
import org.springframework.vault.authentication.GcpComputeAuthenticationOptions.GcpComputeAuthenticationOptionsBuilder;
import org.springframework.vault.authentication.GcpCredentialSupplier;
import org.springframework.vault.authentication.GcpIamAuthentication;
import org.springframework.vault.authentication.GcpIamAuthenticationOptions;
import org.springframework.vault.authentication.GcpIamAuthenticationOptions.GcpIamAuthenticationOptionsBuilder;
import org.springframework.vault.authentication.KubernetesAuthentication;
import org.springframework.vault.authentication.KubernetesAuthenticationOptions;
import org.springframework.vault.authentication.KubernetesServiceAccountTokenFile;
import org.springframework.vault.authentication.PcfAuthentication;
import org.springframework.vault.authentication.PcfAuthenticationOptions;
import org.springframework.vault.authentication.PcfAuthenticationOptions.PcfAuthenticationOptionsBuilder;
import org.springframework.vault.authentication.ResourceCredentialSupplier;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.client.RestTemplateBuilder;
import org.springframework.vault.client.VaultClients;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.config.AbstractVaultConfiguration;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.APPROLE;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.AWS_IAM;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.AZURE_MSI;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.CUBBYHOLE;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.GCP_GCE;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.GCP_IAM;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.KUBERNETES;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.PCF;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.TOKEN;
/**
* This class is adapted from
* {@link org.springframework.vault.config.EnvironmentVaultConfiguration} and <a href=
* https://github.com/spring-cloud/spring-cloud-vault/blob/master/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java>
* org.springframework.cloud.vault.config.ClientAuthenticationFactory</a> in order to
* provide configuration consistent with Spring Cloud Vault's property-based
* configuration.
*
* @author Scott Frederick
*/
public class SpringVaultClientConfiguration extends AbstractVaultConfiguration {
private static final String VAULT_PROPERTIES_PREFIX = "spring.cloud.config.server.vault.";
private final VaultEnvironmentProperties vaultProperties;
private final ConfigTokenProvider configTokenProvider;
private final RestOperations externalRestOperations;
private final Log log = LogFactory.getLog(getClass());
public SpringVaultClientConfiguration(VaultEnvironmentProperties vaultProperties,
ConfigTokenProvider configTokenProvider) {
this.vaultProperties = vaultProperties;
this.configTokenProvider = configTokenProvider;
this.externalRestOperations = new RestTemplate(
clientHttpRequestFactoryWrapper().getClientHttpRequestFactory());
}
@Override
public VaultEndpoint vaultEndpoint() {
URI baseUrl = UriComponentsBuilder.newInstance()
.scheme(vaultProperties.getScheme()).host(vaultProperties.getHost())
.port(vaultProperties.getPort()).build().toUri();
return VaultEndpoint.from(baseUrl);
}
@Override
protected RestTemplateBuilder restTemplateBuilder(
VaultEndpointProvider endpointProvider,
ClientHttpRequestFactory requestFactory) {
RestTemplateBuilder restTemplateBuilder = super.restTemplateBuilder(
endpointProvider, requestFactory);
if (vaultProperties.getNamespace() != null) {
restTemplateBuilder.customizers(
restTemplate -> restTemplate.getInterceptors().add(VaultClients
.createNamespaceInterceptor(vaultProperties.getNamespace())));
}
return restTemplateBuilder;
}
@Override
public SslConfiguration sslConfiguration() {
if (vaultProperties.isSkipSslValidation()) {
log.warn("The '" + VAULT_PROPERTIES_PREFIX + "skipSslValidation' property "
+ "is not supported by this Vault environment repository implementation. "
+ "Use the '" + VAULT_PROPERTIES_PREFIX
+ "ssl` properties to provide "
+ "custom keyStore and trustStore material instead.");
}
VaultEnvironmentProperties.Ssl ssl = vaultProperties.getSsl();
SslConfiguration.KeyStoreConfiguration keyStoreConfiguration = getKeyStoreConfiguration(
ssl.getKeyStore(), ssl.getKeyStorePassword());
SslConfiguration.KeyStoreConfiguration trustStoreConfiguration = getKeyStoreConfiguration(
ssl.getTrustStore(), ssl.getTrustStorePassword());
return new SslConfiguration(keyStoreConfiguration, trustStoreConfiguration);
}
private SslConfiguration.KeyStoreConfiguration getKeyStoreConfiguration(
Resource resourceProperty, String passwordProperty) {
if (resourceProperty == null) {
return SslConfiguration.KeyStoreConfiguration.unconfigured();
}
if (StringUtils.hasText(passwordProperty)) {
return SslConfiguration.KeyStoreConfiguration.of(resourceProperty,
passwordProperty.toCharArray());
}
return SslConfiguration.KeyStoreConfiguration.of(resourceProperty);
}
/**
* @return a new {@link ClientAuthentication}.
*/
public ClientAuthentication clientAuthentication() {
AuthenticationMethod authentication = this.vaultProperties.getAuthentication();
if (authentication == null) {
return new ConfigTokenProviderAuthentication(configTokenProvider);
}
switch (authentication) {
case APPROLE:
return appRoleAuthentication(this.vaultProperties);
case AWS_EC2:
return awsEc2Authentication(this.vaultProperties);
case AWS_IAM:
return awsIamAuthentication(this.vaultProperties);
case AZURE_MSI:
return azureMsiAuthentication(this.vaultProperties);
case CERT:
return new ClientCertificateAuthentication(restOperations());
case CUBBYHOLE:
return cubbyholeAuthentication();
case GCP_GCE:
return gcpGceAuthentication(this.vaultProperties);
case GCP_IAM:
return gcpIamAuthentication(this.vaultProperties);
case KUBERNETES:
return kubernetesAuthentication(this.vaultProperties);
case PCF:
return pcfAuthentication(this.vaultProperties);
case TOKEN:
Assert.hasText(this.vaultProperties.getToken(),
missingPropertyForAuthMethod("token", TOKEN));
return new TokenAuthentication(this.vaultProperties.getToken());
}
throw new UnsupportedOperationException(
String.format("Client authentication %s not supported", authentication));
}
private ClientAuthentication appRoleAuthentication(
VaultEnvironmentProperties vaultProperties) {
AppRoleAuthenticationOptions options = getAppRoleAuthenticationOptions(
vaultProperties);
return new AppRoleAuthentication(options, restOperations());
}
static AppRoleAuthenticationOptions getAppRoleAuthenticationOptions(
VaultEnvironmentProperties vaultProperties) {
AppRoleProperties appRole = vaultProperties.getAppRole();
AppRoleAuthenticationOptionsBuilder builder = AppRoleAuthenticationOptions
.builder().path(appRole.getAppRolePath());
if (StringUtils.hasText(appRole.getRole())) {
builder.appRole(appRole.getRole());
}
RoleId roleId = getRoleId(vaultProperties, appRole);
SecretId secretId = getSecretId(vaultProperties, appRole);
builder.roleId(roleId).secretId(secretId);
return builder.build();
}
private static RoleId getRoleId(VaultEnvironmentProperties vaultProperties,
AppRoleProperties appRole) {
if (StringUtils.hasText(appRole.getRoleId())) {
return RoleId.provided(appRole.getRoleId());
}
if (StringUtils.hasText(vaultProperties.getToken())
&& StringUtils.hasText(appRole.getRole())) {
return RoleId.pull(VaultToken.of(vaultProperties.getToken()));
}
if (StringUtils.hasText(vaultProperties.getToken())) {
return RoleId.wrapped(VaultToken.of(vaultProperties.getToken()));
}
throw new IllegalArgumentException(
"Any of '" + VAULT_PROPERTIES_PREFIX + "app-role.role-id', '.token', "
+ "or '.app-role.role' and '.token' must be provided if the "
+ APPROLE + " authentication method is specified.");
}
private static SecretId getSecretId(VaultEnvironmentProperties vaultProperties,
AppRoleProperties appRole) {
if (StringUtils.hasText(appRole.getSecretId())) {
return SecretId.provided(appRole.getSecretId());
}
if (StringUtils.hasText(vaultProperties.getToken())
&& StringUtils.hasText(appRole.getRole())) {
return SecretId.pull(VaultToken.of(vaultProperties.getToken()));
}
if (StringUtils.hasText(vaultProperties.getToken())) {
return SecretId.wrapped(VaultToken.of(vaultProperties.getToken()));
}
return SecretId.absent();
}
private ClientAuthentication awsEc2Authentication(
VaultEnvironmentProperties vaultProperties) {
AwsEc2Properties awsEc2 = vaultProperties.getAwsEc2();
Nonce nonce = StringUtils.hasText(awsEc2.getNonce())
? Nonce.provided(awsEc2.getNonce().toCharArray()) : Nonce.generated();
AwsEc2AuthenticationOptions authenticationOptions = AwsEc2AuthenticationOptions
.builder().role(awsEc2.getRole()) //
.path(awsEc2.getAwsEc2Path()) //
.nonce(nonce) //
.identityDocumentUri(URI.create(awsEc2.getIdentityDocument())) //
.build();
return new AwsEc2Authentication(authenticationOptions, restOperations(),
this.externalRestOperations);
}
private ClientAuthentication awsIamAuthentication(
VaultEnvironmentProperties vaultProperties) {
assertClassPresent("com.amazonaws.auth.AWSCredentials", missingClassForAuthMethod(
"AWSCredentials", "aws-java-sdk-core", AWS_IAM));
AwsIamProperties awsIam = vaultProperties.getAwsIam();
AWSCredentialsProvider credentialsProvider = AwsCredentialProvider
.getAwsCredentialsProvider();
AwsIamAuthenticationOptionsBuilder builder = AwsIamAuthenticationOptions
.builder();
if (StringUtils.hasText(awsIam.getRole())) {
builder.role(awsIam.getRole());
}
if (StringUtils.hasText(awsIam.getServerName())) {
builder.serverName(awsIam.getServerName());
}
if (awsIam.getEndpointUri() != null) {
builder.endpointUri(awsIam.getEndpointUri());
}
builder.path(awsIam.getAwsPath()) //
.credentialsProvider(credentialsProvider);
AwsIamAuthenticationOptions options = builder
.credentialsProvider(credentialsProvider).build();
return new AwsIamAuthentication(options, restOperations());
}
private ClientAuthentication azureMsiAuthentication(
VaultEnvironmentProperties vaultProperties) {
AzureMsiProperties azureMsi = vaultProperties.getAzureMsi();
Assert.hasText(azureMsi.getRole(),
missingPropertyForAuthMethod("azure-msi.role", AZURE_MSI));
AzureMsiAuthenticationOptions options = AzureMsiAuthenticationOptions.builder()
.role(azureMsi.getRole()).build();
return new AzureMsiAuthentication(options, restOperations(),
this.externalRestOperations);
}
private ClientAuthentication cubbyholeAuthentication() {
String token = this.vaultProperties.getToken();
Assert.hasText(token, missingPropertyForAuthMethod("token", CUBBYHOLE));
CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions.builder() //
.wrapped() //
.initialToken(VaultToken.of(token)) //
.build();
return new CubbyholeAuthentication(options, restOperations());
}
private ClientAuthentication gcpGceAuthentication(
VaultEnvironmentProperties vaultProperties) {
VaultEnvironmentProperties.GcpGceProperties gcp = vaultProperties.getGcpGce();
Assert.hasText(gcp.getRole(),
missingPropertyForAuthMethod("gcp-iam.role", GCP_GCE));
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(),
this.externalRestOperations);
}
private ClientAuthentication gcpIamAuthentication(
VaultEnvironmentProperties vaultProperties) {
assertClassPresent(
"com.google.api.client.googleapis.auth.oauth2.GoogleCredential",
missingClassForAuthMethod("GoogleCredential", "google-api-client",
GCP_IAM));
VaultEnvironmentProperties.GcpIamProperties gcp = vaultProperties.getGcpIam();
Assert.hasText(gcp.getRole(),
missingPropertyForAuthMethod("gcp-iam.role", GCP_IAM));
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 = GcpCredentialProvider.getGoogleCredential(gcp);
builder.credential(supplier.get());
GcpIamAuthenticationOptions options = builder.build();
return new GcpIamAuthentication(options, restOperations());
}
private ClientAuthentication kubernetesAuthentication(
VaultEnvironmentProperties vaultProperties) {
KubernetesProperties kubernetes = vaultProperties.getKubernetes();
Assert.hasText(kubernetes.getRole(),
missingPropertyForAuthMethod("kubernetes.role", KUBERNETES));
Assert.hasText(kubernetes.getServiceAccountTokenFile(),
missingPropertyForAuthMethod("kubernetes.service-account-token-file",
KUBERNETES));
KubernetesAuthenticationOptions options = KubernetesAuthenticationOptions
.builder().path(kubernetes.getKubernetesPath()).role(kubernetes.getRole())
.jwtSupplier(new KubernetesServiceAccountTokenFile(
kubernetes.getServiceAccountTokenFile()))
.build();
return new KubernetesAuthentication(options, restOperations());
}
private ClientAuthentication pcfAuthentication(
VaultEnvironmentProperties vaultProperties) {
PcfProperties pcfProperties = vaultProperties.getPcf();
assertClassPresent("org.bouncycastle.crypto.signers.PSSSigner",
missingClassForAuthMethod("BouncyCastle", "bcpkix-jdk15on", PCF));
Assert.hasText(pcfProperties.getRole(),
missingPropertyForAuthMethod("pcf.role", PCF));
PcfAuthenticationOptionsBuilder builder = PcfAuthenticationOptions.builder()
.role(pcfProperties.getRole()).path(pcfProperties.getPcfPath());
if (pcfProperties.getInstanceCertificate() != null) {
builder.instanceCertificate(new ResourceCredentialSupplier(
pcfProperties.getInstanceCertificate()));
}
if (pcfProperties.getInstanceKey() != null) {
builder.instanceKey(
new ResourceCredentialSupplier(pcfProperties.getInstanceKey()));
}
return new PcfAuthentication(builder.build(), restOperations());
}
private String missingPropertyForAuthMethod(String propertyName,
AuthenticationMethod authenticationMethod) {
return "The '" + VAULT_PROPERTIES_PREFIX + propertyName
+ "' property must be provided " + "when the " + authenticationMethod
+ " authentication method is specified.";
}
private String missingClassForAuthMethod(String className, String classArtifact,
AuthenticationMethod authenticationMethod) {
return className + "(" + classArtifact + ")"
+ " must be on the classpath when the " + authenticationMethod
+ " authentication method is specified";
}
private void assertClassPresent(String className, String message) {
Assert.isTrue(ClassUtils.isPresent(className, getClass().getClassLoader()),
message);
}
private static class AwsCredentialProvider {
private static AWSCredentialsProvider getAwsCredentialsProvider() {
DefaultAWSCredentialsProviderChain backingCredentialsProvider = DefaultAWSCredentialsProviderChain
.getInstance();
// Eagerly fetch credentials preventing lag during the first, actual login.
AWSCredentials firstAccess = backingCredentialsProvider.getCredentials();
AtomicReference<AWSCredentials> once = new AtomicReference<>(firstAccess);
return new AWSCredentialsProvider() {
@Override
public AWSCredentials getCredentials() {
if (once.compareAndSet(firstAccess, null)) {
return firstAccess;
}
return backingCredentialsProvider.getCredentials();
}
@Override
public void refresh() {
backingCredentialsProvider.refresh();
}
};
}
}
@SuppressWarnings("deprecation")
private static class GcpCredentialProvider {
public static GcpCredentialSupplier getGoogleCredential(GcpIamProperties gcp) {
return () -> {
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();
};
}
}
static class ConfigTokenProviderAuthentication implements ClientAuthentication {
private final ConfigTokenProvider tokenProvider;
ConfigTokenProviderAuthentication(ConfigTokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public VaultToken login() throws VaultException {
String token = tokenProvider.getToken();
if (!StringUtils.hasLength(token)) {
throw new IllegalArgumentException(
"A Vault token must be supplied by a token provider");
}
return VaultToken.of(token);
}
}
}

View File

@@ -16,27 +16,15 @@
package org.springframework.cloud.config.server.environment.vault;
import java.net.URI;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.config.server.environment.ConfigTokenProvider;
import org.springframework.cloud.config.server.environment.EnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.EnvironmentWatch;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
import org.springframework.util.StringUtils;
import org.springframework.vault.VaultException;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.SimpleSessionManager;
import org.springframework.vault.client.RestTemplateBuilder;
import org.springframework.vault.client.VaultClients;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.core.VaultKeyValueOperations;
import org.springframework.vault.core.VaultKeyValueOperationsSupport;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.util.UriComponentsBuilder;
/**
* @author Dylan Roberts
@@ -49,23 +37,20 @@ public class SpringVaultEnvironmentRepositoryFactory implements
private final EnvironmentWatch watch;
private final ConfigTokenProvider tokenProvider;
private final SpringVaultClientConfiguration clientConfiguration;
public SpringVaultEnvironmentRepositoryFactory(
ObjectProvider<HttpServletRequest> request, EnvironmentWatch watch,
ConfigTokenProvider tokenProvider) {
SpringVaultClientConfiguration clientConfiguration) {
this.request = request;
this.watch = watch;
this.tokenProvider = tokenProvider;
this.clientConfiguration = clientConfiguration;
}
@Override
public SpringVaultEnvironmentRepository build(
VaultEnvironmentProperties vaultProperties) {
RestTemplateBuilder restTemplateBuilder = buildRestTemplateBuilder(
vaultProperties);
VaultTemplate vaultTemplate = buildVaultTemplate(restTemplateBuilder);
VaultTemplate vaultTemplate = clientConfiguration.vaultTemplate();
VaultKeyValueOperations accessStrategy = buildVaultAccessStrategy(vaultProperties,
vaultTemplate);
@@ -74,29 +59,6 @@ public class SpringVaultEnvironmentRepositoryFactory implements
vaultProperties, accessStrategy);
}
private RestTemplateBuilder buildRestTemplateBuilder(
VaultEnvironmentProperties vaultProperties) {
URI baseUrl = UriComponentsBuilder.newInstance()
.scheme(vaultProperties.getScheme()).host(vaultProperties.getHost())
.port(vaultProperties.getPort()).build().toUri();
RestTemplateBuilder restTemplateBuilder = RestTemplateBuilder.builder()
.endpoint(VaultEndpoint.from(baseUrl));
if (vaultProperties.getNamespace() != null) {
restTemplateBuilder.customizers(
restTemplate -> restTemplate.getInterceptors().add(VaultClients
.createNamespaceInterceptor(vaultProperties.getNamespace())));
}
return restTemplateBuilder;
}
private VaultTemplate buildVaultTemplate(RestTemplateBuilder restTemplateBuilder) {
return new VaultTemplate(restTemplateBuilder, new SimpleSessionManager(
new ConfigTokenProviderAuthentication(tokenProvider)));
}
private VaultKeyValueOperations buildVaultAccessStrategy(
VaultEnvironmentProperties vaultProperties, VaultTemplate vaultTemplate) {
String backend = vaultProperties.getBackend();
@@ -115,25 +77,4 @@ public class SpringVaultEnvironmentRepositoryFactory implements
}
}
public static class ConfigTokenProviderAuthentication
implements ClientAuthentication {
private final ConfigTokenProvider tokenProvider;
public ConfigTokenProviderAuthentication(ConfigTokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public VaultToken login() throws VaultException {
String token = tokenProvider.getToken();
if (!StringUtils.hasLength(token)) {
throw new IllegalArgumentException(
"A Vault token must be supplied by a token provider");
}
return VaultToken.of(token);
}
}
}

View File

@@ -0,0 +1,395 @@
/*
* Copyright 2018-2019 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.cloud.config.server.environment.vault;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod;
import org.springframework.cloud.config.server.environment.vault.SpringVaultClientConfiguration.ConfigTokenProviderAuthentication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.vault.authentication.AppRoleAuthenticationOptions;
import org.springframework.vault.authentication.AppRoleAuthenticationOptions.RoleId;
import org.springframework.vault.authentication.AppRoleAuthenticationOptions.SecretId;
import org.springframework.vault.authentication.AwsEc2Authentication;
import org.springframework.vault.authentication.AwsIamAuthentication;
import org.springframework.vault.authentication.AzureMsiAuthentication;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.ClientCertificateAuthentication;
import org.springframework.vault.authentication.CubbyholeAuthentication;
import org.springframework.vault.authentication.GcpComputeAuthentication;
import org.springframework.vault.authentication.GcpIamAuthentication;
import org.springframework.vault.authentication.KubernetesAuthentication;
import org.springframework.vault.authentication.PcfAuthentication;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
import org.springframework.vault.support.VaultToken;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.AWS_EC2;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.AWS_IAM;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.AZURE_MSI;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.CERT;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.CUBBYHOLE;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.GCP_GCE;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.GCP_IAM;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.KUBERNETES;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.PCF;
import static org.springframework.cloud.config.server.environment.VaultEnvironmentProperties.AuthenticationMethod.TOKEN;
class SpringVaultClientConfigurationTests {
@Test
public void appRoleRoleIdProvidedSecretIdProvided() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
properties.getAppRole().setRoleId("foo");
properties.getAppRole().setSecretId("bar");
AppRoleAuthenticationOptions options = SpringVaultClientConfiguration
.getAppRoleAuthenticationOptions(properties);
assertThat(options.getRoleId()).isInstanceOf(RoleId.provided("foo").getClass());
assertThat(options.getSecretId())
.isInstanceOf(SecretId.provided("bar").getClass());
}
@Test
public void appRoleRoleIdProvidedSecretIdAbsent() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
properties.getAppRole().setRoleId("foo");
AppRoleAuthenticationOptions options = SpringVaultClientConfiguration
.getAppRoleAuthenticationOptions(properties);
assertThat(options.getRoleId()).isInstanceOf(RoleId.provided("foo").getClass());
assertThat(options.getSecretId()).isInstanceOf(SecretId.absent().getClass());
}
@Test
public void appRoleRoleIdProvidedSecretIdPull() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
properties.setToken("token");
properties.getAppRole().setRoleId("foo");
properties.getAppRole().setRole("my-role");
AppRoleAuthenticationOptions options = SpringVaultClientConfiguration
.getAppRoleAuthenticationOptions(properties);
assertThat(options.getAppRole()).isEqualTo("my-role");
assertThat(options.getRoleId()).isInstanceOf(RoleId.provided("foo").getClass());
assertThat(options.getSecretId())
.isInstanceOf(SecretId.pull(VaultToken.of("token")).getClass());
}
@Test
public void appRoleWithFullPull() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
properties.setToken("token");
properties.getAppRole().setRole("my-role");
AppRoleAuthenticationOptions options = SpringVaultClientConfiguration
.getAppRoleAuthenticationOptions(properties);
assertThat(options.getAppRole()).isEqualTo("my-role");
assertThat(options.getRoleId())
.isInstanceOf(RoleId.pull(VaultToken.of("token")).getClass());
assertThat(options.getSecretId())
.isInstanceOf(SecretId.pull(VaultToken.of("token")).getClass());
}
@Test
public void appRoleFullWrapped() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
properties.setToken("token");
AppRoleAuthenticationOptions options = SpringVaultClientConfiguration
.getAppRoleAuthenticationOptions(properties);
assertThat(options.getRoleId())
.isInstanceOf(RoleId.wrapped(VaultToken.of("token")).getClass());
assertThat(options.getSecretId())
.isInstanceOf(SecretId.wrapped(VaultToken.of("token")).getClass());
}
@Test
public void appRoleRoleIdWrappedSecretIdProvided() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
properties.setToken("token");
properties.getAppRole().setSecretId("bar");
AppRoleAuthenticationOptions options = SpringVaultClientConfiguration
.getAppRoleAuthenticationOptions(properties);
assertThat(options.getRoleId())
.isInstanceOf(RoleId.wrapped(VaultToken.of("token")).getClass());
assertThat(options.getSecretId())
.isInstanceOf(SecretId.provided("bar").getClass());
}
@Test
public void appRoleRoleIdProvidedSecretIdWrapped() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
properties.setToken("token");
properties.getAppRole().setRoleId("foo");
AppRoleAuthenticationOptions options = SpringVaultClientConfiguration
.getAppRoleAuthenticationOptions(properties);
assertThat(options.getRoleId()).isInstanceOf(RoleId.provided("foo").getClass());
assertThat(options.getSecretId())
.isInstanceOf(SecretId.wrapped(VaultToken.of("token")).getClass());
}
@Test
public void appRoleWithUnconfiguredRoleId() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
assertThatThrownBy(() -> SpringVaultClientConfiguration
.getAppRoleAuthenticationOptions(properties))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
public void appRoleWithUnconfiguredRoleIdIfRoleNameSet() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
properties.getAppRole().setRole("my-role");
assertThatThrownBy(() -> SpringVaultClientConfiguration
.getAppRoleAuthenticationOptions(properties))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
public void defaultAuthentication() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
assertClientAuthenticationOfType(properties,
ConfigTokenProviderAuthentication.class);
}
@Test
public void awsEc2Authentication() {
VaultEnvironmentProperties properties = getPropertiesForAuthMethod(AWS_EC2);
properties.getAwsEc2().setRole("server");
properties.getAwsEc2().setAwsEc2Path("aws-ec2");
assertClientAuthenticationOfType(properties, AwsEc2Authentication.class);
}
@Test
public void awsIamAuthentication() {
System.setProperty("aws.accessKeyId", "access-key-id");
System.setProperty("aws.secretKey", "secret-key");
VaultEnvironmentProperties properties = getPropertiesForAuthMethod(AWS_IAM);
properties.getAwsIam().setRole("server");
properties.getAwsIam().setAwsPath("aws-iam");
assertClientAuthenticationOfType(properties, AwsIamAuthentication.class);
}
@Test
public void azureMsiAuthentication() {
VaultEnvironmentProperties properties = getPropertiesForAuthMethod(AZURE_MSI);
properties.getAzureMsi().setRole("server");
properties.getAzureMsi().setAzurePath("azure-msi");
assertClientAuthenticationOfType(properties, AzureMsiAuthentication.class);
}
@Test
public void clientCertificateAuthentication() {
VaultEnvironmentProperties properties = getPropertiesForAuthMethod(CERT);
assertClientAuthenticationOfType(properties,
ClientCertificateAuthentication.class);
}
@Test
public void cubbyholeAuthentication() {
VaultEnvironmentProperties properties = getPropertiesForAuthMethod(CUBBYHOLE);
properties.setToken("token");
assertClientAuthenticationOfType(properties, CubbyholeAuthentication.class);
}
@Test
public void gcpComputeAuthentication() {
VaultEnvironmentProperties properties = getPropertiesForAuthMethod(GCP_GCE);
properties.getGcpGce().setRole("server");
properties.getGcpGce().setServiceAccount("service-account");
assertClientAuthenticationOfType(properties, GcpComputeAuthentication.class);
}
@Test
public void gcpIamAuthentication() {
final String GCE_JSON = "{" + " \"type\": \"service_account\","
+ " \"project_id\": \"project\","
+ " \"private_key_id\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\","
+ " \"private_key\": \""
+ "-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5qHafKgP/FAKE\\n"
+ "xfRl0i47zXKbGQJvGAGpcmiXRgeWkZp+kwNwBguOYNwO1qDcmewKvMPazj7EL0hV\\n"
+ "XMkPxgshZ9ZSxPwg7/XHHcyGCYBJhDc2hyunQvc2WGUOlQKg/nOlq3Dg8d9c/0yF\\n"
+ "mFOh2K+IrbV6Vqs3nXsupV1q2FbUCVg6NGB0HCdTBZO4e36tmcaWgC1cKTv/Nh+j\\n"
+ "f2Bf7qBTk0GOL9AjKoa/HP24Yto5zFoOFLU+2ZkVbb8hhO8OMUKW8dLIynqqRqwv\\n"
+ "oI8e4oiHX3dBvwcS0zZkEUtQiDI80OCbU7ZhPgn5xQpndanD9dZ4TYSgKuXRVTzr\\n"
+ "1cyoyP7HAgMBAAECggEAV2fOYOSg+V60WvYhN4aaKaFxoT9G/BJrReENCJr5m5N1\\n"
+ "Dr4b0jOmYSOMtpepJ/J3RB7Wfj63Ihm4jieeqQRt3Q5Lwq/mm4MdTN7kmP4EHZhX\\n"
+ "fh5pGNfYFwfKm/DfSfhBbe+mtuBobhnrZsHuLbYb/db6J1yCQy6q/azwrAqp5iyq\\n"
+ "GjNN+WiDIcrydPKKiaszMnb9mNH+Y6Ianx1mvSLT35nBEF6Z4rJVERl26diOoo3I\\n"
+ "F6WadIwTqcoLo5duUO3SaHKKLcoSEEaGkutuTCHcFOzhvZrXbuIyD567Vp5oVFe9\\n"
+ "SHN10vceQQdWPh2UsKrVQfIdc70+9tlslka5X6BbIQKBgQDss/APs65NNev2lPdb\\n"
+ "Jzdd+0YwKEQXeENWkU1xJJkNH/wF0ZGuYxoKafZR0efs1LnrbaPfHveFCnUcDuXU\\n"
+ "yDnzG2zMw1Q72F8eGHpLItPSh0ZkfSlN58uM1oYMdTFUE6ezlOYEnKIYdhjmQWiE\\n"
+ "uEa1G4ZW0aX0NLICet597GnLKQKBgQDIyzRVbOOzOxGUgrWT0RPT12VVNCn7edn1\\n"
+ "UWLKDl4L2uF8vE4g8WW7gwNkVbuO3VPKqdGuCBDfVyyysOOOCDN0IxSxDk3458VY\\n"
+ "4I3jAuBcgDsixwC28l0QtFnz2yRuD2fsBhLnoSfsM/T2hNbf7atDtMQhbbgU37me\\n"
+ "X+Ewtr+obwKBgCdbb/IcbUH3UknI0Sw95A3jZvNA7rl8TK4LMPY8IJq3E7+f7foy\\n"
+ "DjVnEwbdwRN294b2zwWdb4iWiYxlyb9Mn54VlEyjudDNlFs7tLHjk5bw2TqCOSjz\\n"
+ "/rtnPBi8L7yMHBlXC7v+k1E/6bn3bDqNLWyVrAuphk+Jp4OUDIShl6GpAoGAFIAC\\n"
+ "mNIkMTFPqyzpIu1Oq+sq0lcgDiezpAMqJdzvpyAys0x6YYyjyVAn8X97Rau9GUzb\\n"
+ "NnxmVJcO3jOHGAIoVqwaObVvKoFnOZq7gbjSdT82Smes4ADAlasEIAx4nK//+S3p\\n"
+ "kjJ24/ut/9kyIuyd9qym9Y7BI4hv6AZ79EBEMwsCgYEAgXzq5+NCfJIi6Zduugym\\n"
+ "iUU3y/3CWc/pLhnw3XZ5r3M5fLXokLhLU6FsNflTpdcf2QoNL58mE0tanPqg09Xh\\n"
+ "7fHWR/8rISt2TsMlqFjc5rQxWg8yRpdd5Ti/Ln8v7EV3RGbhFlOqlC9hiyqfyd7V\\n"
+ "qZjZg4zUxPO1I8ae8hbGMWs=\\n" + "-----END PRIVATE KEY-----\\n\","
+ " \"client_email\": \"test@example.com\","
+ " \"client_id\": \"111111111111111111111\","
+ " \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\","
+ " \"token_uri\": \"https://accounts.google.com/o/oauth2/token\","
+ " \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\","
+ " \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/toolsmiths-pcf-sa%40cf-spinnaker.iam.gserviceaccount.com\""
+ "}";
VaultEnvironmentProperties properties = getPropertiesForAuthMethod(GCP_IAM);
properties.getGcpIam().setRole("server");
properties.getGcpIam().setProjectId("project");
properties.getGcpIam().setServiceAccountId("service-account");
properties.getGcpIam().getCredentials().setEncodedKey(base64(GCE_JSON));
assertClientAuthenticationOfType(properties, GcpIamAuthentication.class);
}
@Test
public void kuberneteAuthentication() throws IOException {
Files.write(Paths.get("target", "token"), "token".getBytes());
VaultEnvironmentProperties properties = getPropertiesForAuthMethod(KUBERNETES);
properties.getKubernetes().setRole("server");
properties.getKubernetes().setServiceAccountTokenFile("target/token");
assertClientAuthenticationOfType(properties, KubernetesAuthentication.class);
}
@Test
public void pcfAuthentication() {
VaultEnvironmentProperties properties = getPropertiesForAuthMethod(PCF);
properties.getPcf().setRole("my-role");
properties.getPcf()
.setInstanceKey(new ClassPathResource("configserver-test.yml"));
properties.getPcf()
.setInstanceCertificate(new ClassPathResource("configserver-test.yml"));
assertClientAuthenticationOfType(properties, PcfAuthentication.class);
}
@Test
public void tokenAuthentication() {
VaultEnvironmentProperties properties = getPropertiesForAuthMethod(TOKEN);
properties.setToken("token");
assertClientAuthenticationOfType(properties, TokenAuthentication.class);
}
@Test
public void defaultSslConfiguration() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
SpringVaultClientConfiguration configuration = getConfiguration(properties);
SslConfiguration sslConfiguration = configuration.sslConfiguration();
assertThat(sslConfiguration.getKeyStoreConfiguration())
.isEqualTo(KeyStoreConfiguration.unconfigured());
assertThat(sslConfiguration.getTrustStoreConfiguration())
.isEqualTo(KeyStoreConfiguration.unconfigured());
}
@Test
public void customSslConfiguration() {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
properties.getSsl().setKeyStore(new ClassPathResource("ssl-test.jks"));
properties.getSsl().setKeyStorePassword("password");
properties.getSsl().setTrustStore(new ClassPathResource("ssl-test.jks"));
properties.getSsl().setTrustStorePassword("password");
SpringVaultClientConfiguration configuration = getConfiguration(properties);
SslConfiguration sslConfiguration = configuration.sslConfiguration();
KeyStoreConfiguration keyStoreConfiguration = sslConfiguration
.getKeyStoreConfiguration();
KeyStoreConfiguration trustStoreConfiguration = sslConfiguration
.getTrustStoreConfiguration();
assertThat(keyStoreConfiguration.isPresent()).isTrue();
assertThat(new String(keyStoreConfiguration.getStorePassword()))
.isEqualTo("password");
assertThat(trustStoreConfiguration.isPresent()).isTrue();
assertThat(new String(trustStoreConfiguration.getStorePassword()))
.isEqualTo("password");
}
private VaultEnvironmentProperties getPropertiesForAuthMethod(
AuthenticationMethod authMethod) {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
properties.setAuthentication(authMethod);
return properties;
}
private void assertClientAuthenticationOfType(VaultEnvironmentProperties properties,
Class<? extends ClientAuthentication> type) {
ClientAuthentication clientAuthentication = getConfiguration(properties)
.clientAuthentication();
assertThat(clientAuthentication).isInstanceOf(type);
}
private SpringVaultClientConfiguration getConfiguration(
VaultEnvironmentProperties properties) {
return new SpringVaultClientConfiguration(properties, () -> null);
}
private String base64(String value) {
return new String(Base64.getEncoder().encode(value.getBytes()));
}
}

View File

@@ -21,11 +21,13 @@ import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.config.server.environment.ConfigTokenProvider;
import org.springframework.cloud.config.server.environment.EnvironmentWatch;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.core.VaultKeyValueOperations;
import org.springframework.vault.core.VaultKeyValueOperationsSupport;
import org.springframework.vault.core.VaultTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -41,8 +43,8 @@ public class SpringVaultEnvironmentRepositoryFactoryTests {
VaultEnvironmentProperties properties = new VaultEnvironmentProperties();
SpringVaultEnvironmentRepository environmentRepository = new SpringVaultEnvironmentRepositoryFactory(
mockHttpRequest(), new EnvironmentWatch.Default(), mockTokenProvider())
.build(properties);
mockHttpRequest(), new EnvironmentWatch.Default(),
mockClientConfiguration()).build(properties);
VaultKeyValueOperations keyValueTemplate = environmentRepository
.getKeyValueTemplate();
@@ -56,8 +58,8 @@ public class SpringVaultEnvironmentRepositoryFactoryTests {
properties.setKvVersion(2);
SpringVaultEnvironmentRepository environmentRepository = new SpringVaultEnvironmentRepositoryFactory(
mockHttpRequest(), new EnvironmentWatch.Default(), mockTokenProvider())
.build(properties);
mockHttpRequest(), new EnvironmentWatch.Default(),
mockClientConfiguration()).build(properties);
VaultKeyValueOperations keyValueTemplate = environmentRepository
.getKeyValueTemplate();
@@ -65,10 +67,16 @@ public class SpringVaultEnvironmentRepositoryFactoryTests {
.isEqualTo(VaultKeyValueOperationsSupport.KeyValueBackend.KV_2);
}
private ConfigTokenProvider mockTokenProvider() {
ConfigTokenProvider tokenProvider = mock(ConfigTokenProvider.class);
when(tokenProvider.getToken()).thenReturn("token");
return tokenProvider;
private SpringVaultClientConfiguration mockClientConfiguration() {
VaultTemplate vaultTemplate = new VaultTemplate(
VaultEndpoint.create("localhost", 8200),
new TokenAuthentication("token"));
SpringVaultClientConfiguration clientConfiguration = mock(
SpringVaultClientConfiguration.class);
when(clientConfiguration.vaultTemplate()).thenReturn(vaultTemplate);
return clientConfiguration;
}
@SuppressWarnings("unchecked")