request, EnvironmentWatch watch,
- ConfigTokenProvider tokenProvider) {
+ SpringVaultClientConfiguration vaultClientConfiguration) {
return new SpringVaultEnvironmentRepositoryFactory(request, watch,
- tokenProvider);
+ vaultClientConfiguration);
}
}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java
index 97c77320..f561beb2 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/VaultEnvironmentProperties.java
@@ -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.
+ *
+ *
+ * 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;
+ }
+
+ }
+
}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultClientConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultClientConfiguration.java
new file mode 100644
index 00000000..7ea39bce
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultClientConfiguration.java
@@ -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
+ * org.springframework.cloud.vault.config.ClientAuthenticationFactory 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 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);
+ }
+
+ }
+
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactory.java
index 4d49b997..8aed15a6 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactory.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactory.java
@@ -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 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);
- }
-
- }
-
}
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultClientConfigurationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultClientConfigurationTests.java
new file mode 100644
index 00000000..5a1925da
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultClientConfigurationTests.java
@@ -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()));
+ }
+
+}
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactoryTests.java
index b82eadcf..be1fb0ad 100644
--- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactoryTests.java
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/vault/SpringVaultEnvironmentRepositoryFactoryTests.java
@@ -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")