Add app-id authentication mechanism.
Support app-id authentication using app id and user id. User-ids are generated either from the SHA-256 hex-encoded IP address or using the Mac address represented as hex without separators. Network addresses are obtained from the InetAddress.getLocalHost() method. Tests use a test @Rule VaultRule to ensure Vault is unsealed and provides a persistent token that can be used as root token during tests. The token is static so it can be reused across multiple test runs without restarting vault. Add tests for generic secret backend usage with a static token and app-id authentication. Add tests for app-id authentication using IP address and Mac-address user-ids.
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -49,6 +49,12 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface AppIdUserIdMechanism {
|
||||
|
||||
/**
|
||||
* Creates a UserId for AppId authentication.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String createUserId();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
|
||||
/**
|
||||
* Mechanism to generate a SHA-256 hashed and hex-encoded representation of the IP address. Can be calculated with
|
||||
* {@code echo -n 192.168.99.1 | sha256sum}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class IpAddressUserId implements AppIdUserIdMechanism {
|
||||
|
||||
@Override
|
||||
public String createUserId() {
|
||||
try {
|
||||
return Sha256.toSha256(InetAddress.getLocalHost().getHostAddress());
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Mechanism to generate a UserId based on the Mac address. {@link MacAddressUserId} creates a hex-encoded
|
||||
* representation of the Mac address without any separators (0123456789AB). A
|
||||
* {@link org.springframework.cloud.vault.VaultProperties.AppIdProperties#networkInterface} can be
|
||||
* specified optionally to select a network interface (index/name).
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
@RequiredArgsConstructor
|
||||
@CommonsLog
|
||||
public class MacAddressUserId implements AppIdUserIdMechanism {
|
||||
|
||||
private final VaultProperties vaultProperties;
|
||||
|
||||
@Override
|
||||
public String createUserId() {
|
||||
try {
|
||||
|
||||
NetworkInterface networkInterface = null;
|
||||
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
|
||||
VaultProperties.AppIdProperties appId = vaultProperties.getAppId();
|
||||
if (StringUtils.hasText(appId.getNetworkInterface())) {
|
||||
try {
|
||||
networkInterface = getNetworkInterface(Integer.parseInt(appId.getNetworkInterface()), interfaces);
|
||||
} catch (NumberFormatException e) {
|
||||
networkInterface = getNetworkInterface((appId.getNetworkInterface()), interfaces);
|
||||
}
|
||||
}
|
||||
|
||||
if (networkInterface == null) {
|
||||
if (StringUtils.hasText(appId.getNetworkInterface())) {
|
||||
log.warn(
|
||||
String.format("Did not find a NetworkInterface applying hint %s", appId.getNetworkInterface()));
|
||||
}
|
||||
|
||||
InetAddress localHost = InetAddress.getLocalHost();
|
||||
networkInterface = NetworkInterface.getByInetAddress(localHost);
|
||||
|
||||
if (networkInterface == null) {
|
||||
throw new IllegalStateException(String.format("Cannot determine NetworkInterface for %s", localHost));
|
||||
}
|
||||
}
|
||||
|
||||
byte[] mac = networkInterface.getHardwareAddress();
|
||||
if (mac == null) {
|
||||
throw new IllegalStateException(String.format("Network interface %s has no hardware address", networkInterface.getName()));
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < mac.length; i++) {
|
||||
sb.append(String.format("%02X", mac[i]));
|
||||
}
|
||||
return Sha256.toSha256(sb.toString());
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private NetworkInterface getNetworkInterface(Number hint, List<NetworkInterface> interfaces) {
|
||||
|
||||
if (interfaces.size() > hint.intValue() && hint.intValue() >= 0) {
|
||||
return interfaces.get(hint.intValue());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private NetworkInterface getNetworkInterface(String hint, List<NetworkInterface> interfaces) {
|
||||
|
||||
for (NetworkInterface anInterface : interfaces) {
|
||||
if (hint.equals(anInterface.getDisplayName()) || hint.equals(anInterface.getName())) {
|
||||
return anInterface;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
61
src/main/java/org/springframework/cloud/vault/Sha256.java
Normal file
61
src/main/java/org/springframework/cloud/vault/Sha256.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility to generate SHA 256 checksums.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class Sha256 {
|
||||
|
||||
/**
|
||||
* Generates a hex-encoded SHA256 checksum from the supplied {@code content}.
|
||||
*
|
||||
* @param content
|
||||
* @return
|
||||
*/
|
||||
public static String toSha256(String content) {
|
||||
|
||||
Assert.hasText(content, "Content must not be empty!");
|
||||
MessageDigest messageDigest = getMessageDigest("SHA-256");
|
||||
byte[] digest = messageDigest.digest(content.getBytes(StandardCharsets.US_ASCII));
|
||||
return new String(Hex.encode(digest));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a MessageDigest instance for the given algorithm. Throws an IllegalArgumentException if <i>algorithm</i> is
|
||||
* unknown
|
||||
*
|
||||
* @return MessageDigest instance
|
||||
* @throws IllegalArgumentException if NoSuchAlgorithmException is thrown
|
||||
*/
|
||||
private static MessageDigest getMessageDigest(String algorithm) throws IllegalArgumentException {
|
||||
try {
|
||||
return MessageDigest.getInstance(algorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException("No such algorithm [" + algorithm + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
/**
|
||||
* A static UserId.
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
@RequiredArgsConstructor
|
||||
@CommonsLog
|
||||
public class StaticUserId implements AppIdUserIdMechanism {
|
||||
|
||||
private final VaultProperties vaultProperties;
|
||||
|
||||
@Override
|
||||
public String createUserId() {
|
||||
return vaultProperties.getAppId().getUserId();
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,19 @@
|
||||
|
||||
package org.springframework.cloud.vault;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
@@ -30,8 +36,16 @@ import org.springframework.context.annotation.Configuration;
|
||||
public class VaultBootstrapConfiguration {
|
||||
|
||||
@Bean
|
||||
public VaultClient vaultClient() {
|
||||
return new VaultClient(vaultProperties());
|
||||
public VaultClient vaultClient(ApplicationContext applicationContext) {
|
||||
|
||||
VaultClient vaultClient = new VaultClient(vaultProperties());
|
||||
|
||||
Map<String, AppIdUserIdMechanism> appIdUserIdMechanisms = applicationContext.getBeansOfType(AppIdUserIdMechanism.class);
|
||||
if(!appIdUserIdMechanisms.isEmpty()){
|
||||
vaultClient.setAppIdUserIdMechanism(appIdUserIdMechanisms.values().iterator().next());
|
||||
}
|
||||
|
||||
return vaultClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -40,7 +54,25 @@ public class VaultBootstrapConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public VaultPropertySourceLocator vaultPropertySourceLocator() {
|
||||
return new VaultPropertySourceLocator(vaultClient(), vaultProperties());
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = "spring.cloud.vault", name = "authentication", havingValue = "APPID")
|
||||
public AppIdUserIdMechanism appIdUserIdMechanism(VaultProperties vaultProperties) {
|
||||
|
||||
String userId = vaultProperties.getAppId().getUserId();
|
||||
Assert.hasText(userId, "UserId (spring.cloud.vault.app-id.user-id) must not be empty.");
|
||||
|
||||
switch (userId.toUpperCase()) {
|
||||
case VaultProperties.AppIdProperties.IP_ADDRESS:
|
||||
return new IpAddressUserId();
|
||||
case VaultProperties.AppIdProperties.MAC_ADDRESS:
|
||||
return new MacAddressUserId(vaultProperties);
|
||||
default:
|
||||
return new StaticUserId(vaultProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public VaultPropertySourceLocator vaultPropertySourceLocator(ApplicationContext applicationContext) {
|
||||
return new VaultPropertySourceLocator(vaultClient(applicationContext), vaultProperties());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,42 +17,54 @@
|
||||
package org.springframework.cloud.vault;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
|
||||
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Vault client. This client reads data from Vault secret backends and can authenticate with
|
||||
* Vault to obtain an access token.
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class VaultClient {
|
||||
public static final String VAULT_TOKEN = "X-Vault-Token";
|
||||
|
||||
// protected static final ParameterizedTypeReference<Map<String, String>> typeRef = new ParameterizedTypeReference<Map<String, String>>() { };
|
||||
public static final String API_VERSION = "v1";
|
||||
public static final String VAULT_TOKEN = "X-Vault-Token";
|
||||
|
||||
@Setter
|
||||
private RestTemplate rest = new RestTemplate();
|
||||
|
||||
@Setter
|
||||
private AppIdUserIdMechanism appIdUserIdMechanism;
|
||||
|
||||
private final VaultProperties properties;
|
||||
|
||||
public Map<String, String> read(String key) {
|
||||
String url = String.format("%s://%s:%s/v1/{backend}/{key}",
|
||||
this.properties.getScheme(), this.properties.getHost(), this.properties.getPort());
|
||||
public Map<String, String> read(String key, VaultToken vaultToken) {
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(VAULT_TOKEN, this.properties.getToken());
|
||||
Assert.hasText(key, "Key must not be empty!");
|
||||
Assert.notNull(vaultToken, "VaultToken must not be null!");
|
||||
|
||||
String url = buildUrl();
|
||||
|
||||
HttpHeaders headers = createHeaders(vaultToken);
|
||||
try {
|
||||
ResponseEntity<VaultResponse> response = this.rest.exchange(url, HttpMethod.GET,
|
||||
new HttpEntity<>(headers), VaultResponse.class, this.properties.getBackend(), key);
|
||||
ResponseEntity<VaultResponse> response = this.rest.exchange(url,
|
||||
HttpMethod.GET, new HttpEntity<>(headers), VaultResponse.class,
|
||||
this.properties.getBackend(), key);
|
||||
|
||||
HttpStatus status = response.getStatusCode();
|
||||
if (status == HttpStatus.OK) {
|
||||
@@ -67,4 +79,79 @@ public class VaultClient {
|
||||
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
private HttpHeaders createHeaders(VaultToken vaultToken) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(VAULT_TOKEN, vaultToken.getToken());
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a token using a configured authentication mechanism.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public VaultToken createToken() {
|
||||
|
||||
if (properties.getAuthentication() == AuthenticationMethod.APPID && appIdUserIdMechanism != null) {
|
||||
AppIdProperties appId = properties.getAppId();
|
||||
return createTokenUsingAppId(new AppIdTuple(properties.getApplicationName(), appIdUserIdMechanism.createUserId()), appId);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException(String.format(
|
||||
"Cannot create a token for auth method %s", properties.getAuthentication()));
|
||||
}
|
||||
|
||||
private VaultToken createTokenUsingAppId(AppIdTuple appIdTuple, AppIdProperties appId) {
|
||||
|
||||
String url = buildUrl();
|
||||
Map<String, String> variables = new HashMap<>();
|
||||
variables.put("backend", "auth/" + appId.getAppIdPath());
|
||||
variables.put("key", "login");
|
||||
|
||||
Map<String, String> login = getAppIdLogin(appIdTuple);
|
||||
|
||||
try {
|
||||
ResponseEntity<VaultResponse> response = this.rest.exchange(url,
|
||||
HttpMethod.POST, new HttpEntity<>(login), VaultResponse.class,
|
||||
variables);
|
||||
|
||||
HttpStatus status = response.getStatusCode();
|
||||
if (!status.is2xxSuccessful()) {
|
||||
throw new IllegalStateException("Cannot login using app-id");
|
||||
}
|
||||
|
||||
VaultResponse body = response.getBody();
|
||||
String token = (String) body.getAuth().get("client_token");
|
||||
|
||||
return VaultToken.of(token, body.getLeaseDuration());
|
||||
} catch (HttpClientErrorException e) {
|
||||
|
||||
if (e.getStatusCode().equals(HttpStatus.BAD_REQUEST)) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Cannot login using app-id: %s", e.getResponseBodyAsString()));
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> getAppIdLogin(AppIdTuple appIdTuple) {
|
||||
|
||||
Map<String, String> login = new HashMap<>();
|
||||
login.put("app_id", appIdTuple.getAppId());
|
||||
login.put("user_id", appIdTuple.getUserId());
|
||||
return login;
|
||||
}
|
||||
|
||||
private String buildUrl() {
|
||||
return String.format("%s://%s:%s/%s/{backend}/{key}", this.properties.getScheme(),
|
||||
this.properties.getHost(), this.properties.getPort(), API_VERSION);
|
||||
}
|
||||
|
||||
@Value
|
||||
private static class AppIdTuple{
|
||||
private String appId;
|
||||
private String userId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,29 +24,100 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.vault")
|
||||
@Data
|
||||
public class VaultProperties {
|
||||
|
||||
/**
|
||||
* Enable Vault config server.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Vault server host.
|
||||
*/
|
||||
@NotEmpty
|
||||
private String host = "127.0.0.1";
|
||||
|
||||
/**
|
||||
* Vault server port.
|
||||
*/
|
||||
@Range(min = 1, max = 65535)
|
||||
private int port = 8200;
|
||||
|
||||
/**
|
||||
* Protocol scheme. Can be either "http" or "https".
|
||||
*/
|
||||
private String scheme = "http";
|
||||
|
||||
/**
|
||||
* Name of the default backend.
|
||||
*/
|
||||
@NotEmpty
|
||||
private String backend = "secret";
|
||||
|
||||
/**
|
||||
* Name of the default context.
|
||||
*/
|
||||
@NotEmpty
|
||||
private String defaultContext = "application";
|
||||
|
||||
/**
|
||||
* Profile-separator to combine application name and profile.
|
||||
*/
|
||||
@NotEmpty
|
||||
private String profileSeparator = ",";
|
||||
|
||||
@NotEmpty
|
||||
/**
|
||||
* Static vault token. Required if {@link #authentication} is {@code TOKEN}.
|
||||
*/
|
||||
private String token;
|
||||
}
|
||||
|
||||
private AppIdProperties appId = new AppIdProperties();
|
||||
|
||||
/**
|
||||
* Application name for AppId authentication.
|
||||
*/
|
||||
@org.springframework.beans.factory.annotation.Value("${spring.application.name:application}")
|
||||
private String applicationName;
|
||||
|
||||
private AuthenticationMethod authentication = AuthenticationMethod.TOKEN;
|
||||
|
||||
@Data
|
||||
public static class AppIdProperties {
|
||||
|
||||
/**
|
||||
* Property value for UserId generation using a Mac-Address.
|
||||
* @see MacAddressUserId
|
||||
*/
|
||||
public final static String MAC_ADDRESS = "MAC_ADDRESS";
|
||||
|
||||
/**
|
||||
* Property value for UserId generation using an IP-Address.
|
||||
* @see IpAddressUserId
|
||||
*/
|
||||
public final static String IP_ADDRESS = "IP_ADDRESS";
|
||||
|
||||
/**
|
||||
* Mount path of the AppId authentication backend.
|
||||
*/
|
||||
private String appIdPath = "app-id";
|
||||
|
||||
/**
|
||||
* Network interface hint for the "MAC_ADDRESS" UserId mechanism.
|
||||
*/
|
||||
private String networkInterface = null;
|
||||
|
||||
/**
|
||||
* UserId mechanism. Can be either "MAC_ADDRESS", "IP_ADDRESS". Any other values are passed as UserId.
|
||||
*/
|
||||
@NotEmpty
|
||||
private String userId = MAC_ADDRESS;
|
||||
}
|
||||
|
||||
public enum AuthenticationMethod {
|
||||
TOKEN, APPID,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,35 +22,73 @@ import java.util.Set;
|
||||
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
|
||||
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@CommonsLog
|
||||
public class VaultPropertySource extends EnumerablePropertySource<VaultClient> {
|
||||
|
||||
private final VaultProperties vaultProperties;
|
||||
|
||||
private String context;
|
||||
|
||||
private Map<String, String> properties = new LinkedHashMap<>();
|
||||
|
||||
private transient VaultState vaultState;
|
||||
|
||||
public VaultPropertySource(String context, VaultClient source) {
|
||||
public VaultPropertySource(String context, VaultClient source, VaultProperties properties, VaultState state) {
|
||||
super(context, source);
|
||||
this.context = context;
|
||||
this.vaultProperties = properties;
|
||||
this.vaultState = state;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
|
||||
try {
|
||||
Map<String, String> values = this.source.read(this.context);
|
||||
Map<String, String> values = this.source.read(this.context, obtainToken());
|
||||
|
||||
if (values != null) {
|
||||
this.properties.putAll(values);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Unable to read properties from vault for key "+this.context, e);
|
||||
log.error("Unable to read properties from vault for key " + this.context, e);
|
||||
}
|
||||
}
|
||||
|
||||
private VaultToken obtainToken() {
|
||||
|
||||
if (vaultState.getToken() != null) {
|
||||
return vaultState.getToken();
|
||||
}
|
||||
|
||||
if (vaultProperties.getAuthentication() == AuthenticationMethod.TOKEN) {
|
||||
|
||||
Assert.hasText(vaultProperties.getToken(), "Token must not be empty");
|
||||
vaultState.setToken(VaultToken.of(vaultProperties.getToken()));
|
||||
|
||||
return vaultState.getToken();
|
||||
}
|
||||
|
||||
if (vaultProperties.getAuthentication() == AuthenticationMethod.APPID) {
|
||||
|
||||
AppIdProperties appId = vaultProperties.getAppId();
|
||||
Assert.hasText(vaultProperties.getApplicationName(), "AppId must not be empty");
|
||||
Assert.hasText(appId.getAppIdPath(), "AppIdPath must not be empty");
|
||||
|
||||
vaultState.setToken(source.createToken());
|
||||
return vaultState.getToken();
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
String.format("Authentication method %s not supported", vaultProperties.getAuthentication()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
return this.properties.get(name);
|
||||
|
||||
@@ -35,6 +35,7 @@ public class VaultPropertySourceLocator implements PropertySourceLocator {
|
||||
private VaultClient vault;
|
||||
|
||||
private VaultProperties properties;
|
||||
private transient final VaultState vaultState = new VaultState();
|
||||
|
||||
public VaultPropertySourceLocator(VaultClient vault, VaultProperties properties) {
|
||||
this.vault = vault;
|
||||
@@ -74,7 +75,7 @@ public class VaultPropertySourceLocator implements PropertySourceLocator {
|
||||
}
|
||||
|
||||
private VaultPropertySource create(String context) {
|
||||
return new VaultPropertySource(context, this.vault);
|
||||
return new VaultPropertySource(context, this.vault, this.properties, this.vaultState);
|
||||
}
|
||||
|
||||
private void addProfiles(List<String> contexts, String baseContext,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2016 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.
|
||||
@@ -24,11 +24,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Data
|
||||
public class VaultResponse {
|
||||
private String auth;
|
||||
private Map<String, Object> auth;
|
||||
private Map<String, String> data;
|
||||
private Map<String, String> metadata;
|
||||
@JsonProperty("lease_duration")
|
||||
private long leaseDuration;
|
||||
@JsonProperty("lease_id")
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* State of the Vault client.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Data
|
||||
class VaultState {
|
||||
private VaultToken token;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class VaultToken {
|
||||
private String token;
|
||||
private long leaseDuration;
|
||||
|
||||
public static VaultToken of(String token) {
|
||||
return of(token, 0);
|
||||
}
|
||||
|
||||
public static VaultToken of(String token, long leaseDuration) {
|
||||
Assert.hasText(token, "Token must not be empty");
|
||||
return new VaultToken(token, leaseDuration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.cloud.vault.VaultAppIdCustomMechanismTests.BootstrapConfiguration;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
import org.springframework.cloud.vault.util.VaultRule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = {BootstrapConfiguration.class, VaultAppIdCustomMechanismTests.TestApplication.class})
|
||||
@IntegrationTest({ "spring.cloud.vault.authentication=appid", "use.custom.config=true", "spring.application.name=VaultAppIdCustomMechanismTests"})
|
||||
public class VaultAppIdCustomMechanismTests {
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
|
||||
VaultRule vaultRule = new VaultRule();
|
||||
vaultRule.before();
|
||||
|
||||
vaultRule.prepare().writeSecret(VaultAppIdCustomMechanismTests.class.getSimpleName(), Collections.singletonMap("vault.value", "foo"));
|
||||
|
||||
VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
vaultProperties.setAuthentication(VaultProperties.AuthenticationMethod.APPID);
|
||||
|
||||
if (!vaultRule.prepare().hasAuth(vaultProperties.getAppId().getAppIdPath())) {
|
||||
vaultRule.prepare().mountAuth(vaultProperties.getAppId().getAppIdPath());
|
||||
}
|
||||
|
||||
vaultRule.prepare().mapAppId(VaultAppIdCustomMechanismTests.class.getSimpleName());
|
||||
vaultRule.prepare().mapUserId(VaultAppIdCustomMechanismTests.class.getSimpleName(), new StaticUserIdMechanism().createUserId());
|
||||
|
||||
}
|
||||
|
||||
@Value("${vault.value}") String configValue;
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
|
||||
assertThat(configValue).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
public static class TestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TestApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class BootstrapConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty("use.custom.config")
|
||||
AppIdUserIdMechanism appIdUserIdMechanism() {
|
||||
return new StaticUserIdMechanism();
|
||||
}
|
||||
}
|
||||
|
||||
public static class StaticUserIdMechanism implements AppIdUserIdMechanism {
|
||||
|
||||
@Override
|
||||
public String createUserId() {
|
||||
return "static-string";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
import org.springframework.cloud.vault.util.VaultRule;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = VaultAppIdTests.TestApplication.class)
|
||||
@IntegrationTest({"spring.cloud.vault.authentication=appid", "spring.cloud.vault.app-id.user-id=IP_ADDRESS", "spring.application.name=VaultAppIdTests"})
|
||||
public class VaultAppIdTests {
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
|
||||
VaultRule vaultRule = new VaultRule();
|
||||
vaultRule.before();
|
||||
|
||||
vaultRule.prepare().writeSecret(VaultAppIdTests.class.getSimpleName(), Collections.singletonMap("vault.value", "foo"));
|
||||
|
||||
VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
vaultProperties.setAuthentication(VaultProperties.AuthenticationMethod.APPID);
|
||||
vaultProperties.getAppId().setUserId(VaultProperties.AppIdProperties.IP_ADDRESS);
|
||||
|
||||
if (!vaultRule.prepare().hasAuth(vaultProperties.getAppId().getAppIdPath())) {
|
||||
vaultRule.prepare().mountAuth(vaultProperties.getAppId().getAppIdPath());
|
||||
}
|
||||
|
||||
vaultRule.prepare().mapAppId(VaultAppIdTests.class.getSimpleName());
|
||||
vaultRule.prepare().mapUserId(VaultAppIdTests.class.getSimpleName(), new IpAddressUserId().createUserId());
|
||||
}
|
||||
|
||||
@Value("${vault.value}")
|
||||
String configValue;
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
|
||||
assertThat(configValue).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
public static class TestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TestApplication.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,44 @@
|
||||
package org.springframework.cloud.vault;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.boot.test.WebIntegrationTest;
|
||||
import org.springframework.cloud.vault.util.PrepareVault;
|
||||
import org.springframework.cloud.vault.util.VaultRule;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = VaultTests.TestApplication.class)
|
||||
@WebIntegrationTest(randomPort = true)
|
||||
public class VaultTests {
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
|
||||
VaultRule vaultRule = new VaultRule();
|
||||
vaultRule.before();
|
||||
|
||||
vaultRule.prepare().writeSecret("testVaultApp", Collections.singletonMap("vault.value", "foo"));
|
||||
}
|
||||
|
||||
@Value("${vault.value}")
|
||||
String configValue;
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
|
||||
assertThat(configValue).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault.integration;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.springframework.cloud.vault.util.PrepareVault;
|
||||
import org.springframework.cloud.vault.util.VaultRule;
|
||||
|
||||
/**
|
||||
* Base class for integration tests using Vault.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public abstract class AbstractIntegrationTests {
|
||||
|
||||
@Rule
|
||||
public final VaultRule vaultRule = new VaultRule();
|
||||
|
||||
public final PrepareVault prepare() {
|
||||
return vaultRule.prepare();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault.integration;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.springframework.cloud.vault.IpAddressUserId;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
|
||||
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
|
||||
import org.springframework.cloud.vault.VaultToken;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using {@link AuthenticationMethod#APPID}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class AppIdAuthenticationIntegrationTests extends GenericSecretIntegrationTests {
|
||||
|
||||
private VaultClient vaultClient;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
super.setUp();
|
||||
|
||||
VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
|
||||
AppIdProperties appId = configureAppIdProperties();
|
||||
vaultProperties.setApplicationName("myapp");
|
||||
vaultProperties.setAuthentication(AuthenticationMethod.APPID);
|
||||
vaultProperties.setAppId(appId);
|
||||
|
||||
if (!prepare().hasAuth(appId.getAppIdPath())) {
|
||||
prepare().mountAuth(appId.getAppIdPath());
|
||||
}
|
||||
|
||||
IpAddressUserId userIdMechanism = new IpAddressUserId();
|
||||
String userId = userIdMechanism.createUserId();
|
||||
prepare().mapAppId(vaultProperties.getApplicationName());
|
||||
prepare().mapUserId(vaultProperties.getApplicationName(), userId);
|
||||
|
||||
vaultClient = new VaultClient(vaultProperties);
|
||||
vaultClient.setAppIdUserIdMechanism(userIdMechanism);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VaultToken createToken() {
|
||||
return vaultClient.createToken();
|
||||
}
|
||||
|
||||
private AppIdProperties configureAppIdProperties() {
|
||||
|
||||
AppIdProperties appId = new AppIdProperties();
|
||||
appId.setUserId(AppIdProperties.IP_ADDRESS);
|
||||
return appId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault.integration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.cloud.vault.*;
|
||||
import org.springframework.cloud.vault.VaultProperties.AppIdProperties;
|
||||
import org.springframework.cloud.vault.VaultProperties.AuthenticationMethod;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using various UserIds.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class AppIdAuthenticationMethodsIntegrationTests extends AbstractIntegrationTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
prepare().mapAppId("myapp");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginUsingIpAddressShouldCreateAToken() throws Exception {
|
||||
|
||||
VaultClient vaultClient = new VaultClient(
|
||||
prepareAppIdAuthenticationMethod(AppIdProperties.IP_ADDRESS, "myapp"));
|
||||
vaultClient.setAppIdUserIdMechanism(new IpAddressUserId());
|
||||
assertThat(vaultClient.createToken()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginUsingStaticUserIdShouldCreateAToken() throws Exception {
|
||||
|
||||
VaultProperties vaultProperties = prepareAppIdAuthenticationMethod("my-user-id", "myapp");
|
||||
VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
vaultClient.setAppIdUserIdMechanism(new StaticUserId(vaultProperties));
|
||||
assertThat(vaultClient.createToken()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginUsingMacAddressShouldCreateAToken() throws Exception {
|
||||
|
||||
VaultProperties vaultProperties = prepareAppIdAuthenticationMethod(AppIdProperties.MAC_ADDRESS, "myapp");
|
||||
VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
|
||||
vaultClient.setAppIdUserIdMechanism(
|
||||
new MacAddressUserId(vaultProperties));
|
||||
assertThat(vaultClient.createToken()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidLogin() throws Exception {
|
||||
|
||||
expectedException.expect(IllegalStateException.class);
|
||||
expectedException.expectMessage("Cannot login using app-id");
|
||||
|
||||
VaultProperties vaultProperties = prepareAppIdAuthenticationMethod(
|
||||
AppIdProperties.IP_ADDRESS, "myapp");
|
||||
vaultProperties.setApplicationName("foobar");
|
||||
|
||||
VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
vaultClient.setAppIdUserIdMechanism(new MacAddressUserId(vaultProperties));
|
||||
|
||||
vaultClient.createToken();
|
||||
|
||||
fail("Missing IllegalStateException");
|
||||
|
||||
}
|
||||
|
||||
private VaultProperties prepareAppIdAuthenticationMethod(String userId, String appId)
|
||||
throws SocketException {
|
||||
|
||||
VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
|
||||
AppIdProperties appIdProperties = new AppIdProperties();
|
||||
vaultProperties.setApplicationName(appId);
|
||||
appIdProperties.setUserId(userId);
|
||||
|
||||
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface
|
||||
.getNetworkInterfaces();
|
||||
NetworkInterface networkInterface = null;
|
||||
while (networkInterfaces.hasMoreElements()) {
|
||||
networkInterface = networkInterfaces.nextElement();
|
||||
if (networkInterface.getHardwareAddress() != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we have always a network interface even if the localhost reverse
|
||||
// lookup maps to an IP address that is not handled by this host.
|
||||
appIdProperties.setNetworkInterface(networkInterface.getName());
|
||||
|
||||
vaultProperties.setAuthentication(AuthenticationMethod.APPID);
|
||||
vaultProperties.setAppId(appIdProperties);
|
||||
|
||||
String userIdValue;
|
||||
if (userId.equals(AppIdProperties.IP_ADDRESS)) {
|
||||
userIdValue = new IpAddressUserId().createUserId();
|
||||
}
|
||||
else if (userId.equals(AppIdProperties.MAC_ADDRESS)) {
|
||||
userIdValue = new MacAddressUserId(vaultProperties).createUserId();
|
||||
}
|
||||
else {
|
||||
userIdValue = userId;
|
||||
}
|
||||
|
||||
prepare().mapUserId(vaultProperties.getApplicationName(), userIdValue);
|
||||
|
||||
return vaultProperties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault.integration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.VaultToken;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultClient} using the generic secret backend.
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class GenericSecretIntegrationTests extends AbstractIntegrationTests {
|
||||
|
||||
private VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
private VaultClient vaultClient = new VaultClient(vaultProperties);
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
prepare().writeSecret("app-name", (Map) createData());
|
||||
vaultClient.setRest(new RestTemplate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnSecretsCorrectly() throws Exception {
|
||||
|
||||
Map<String, String> secretProperties = vaultClient.read("app-name",
|
||||
createToken());
|
||||
|
||||
assertThat(secretProperties).containsAllEntriesOf(createExpectedMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnNullIfNotFound() throws Exception {
|
||||
|
||||
Map<String, String> secretProperties = vaultClient.read("missing", createToken());
|
||||
|
||||
assertThat(secretProperties).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overridden by subclasses.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected VaultToken createToken() {
|
||||
return Settings.token();
|
||||
}
|
||||
|
||||
private Map<String, Object> createData() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("string", "value");
|
||||
data.put("number", "1234");
|
||||
data.put("boolean", true);
|
||||
return data;
|
||||
}
|
||||
|
||||
private Map<String, String> createExpectedMap() {
|
||||
Map<String, String> data = new HashMap<>();
|
||||
data.put("string", "value");
|
||||
data.put("number", "1234");
|
||||
data.put("boolean", "true");
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault.integration;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.VaultToken;
|
||||
import org.springframework.cloud.vault.util.PrepareVault;
|
||||
import org.springframework.cloud.vault.util.Settings;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link PrepareVault}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class PrepareVaultTests {
|
||||
|
||||
private VaultProperties vaultProperties = Settings.createVaultProperties();
|
||||
private PrepareVault prepareVault = new PrepareVault(new TestRestTemplate());
|
||||
|
||||
@Test
|
||||
public void initializeShouldCreateANewVault() throws Exception {
|
||||
|
||||
prepareVault.setRootToken(Settings.token());
|
||||
prepareVault.setVaultProperties(vaultProperties);
|
||||
|
||||
if (!prepareVault.isAvailable()) {
|
||||
VaultToken rootToken = prepareVault.initializeVault();
|
||||
prepareVault.setRootToken(rootToken);
|
||||
prepareVault.createToken(vaultProperties.getToken(), "root");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.cloud.vault.VaultClient;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.VaultToken;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Test helper to prepare various settings within Vault.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class PrepareVault {
|
||||
|
||||
public final static String INITIALIZE_URL_TEMPLATE = "{baseuri}/sys/init";
|
||||
public final static String MOUNT_AUTH_URL_TEMPLATE = "{baseuri}/sys/auth/{authBackend}";
|
||||
public final static String SYS_AUTH_URL_TEMPLATE = "{baseuri}/sys/auth";
|
||||
public final static String SEAL_STATUS_URL_TEMPLATE = "{baseuri}/sys/seal-status";
|
||||
public final static String UNSEAL_URL_TEMPLATE = "{baseuri}/sys/unseal";
|
||||
public final static String CREATE_TOKEN_URL_TEMPLATE = "{baseuri}/auth/token/create-orphan";
|
||||
public final static String WRITE_URL_TEMPLATE = "{baseuri}/{path}";
|
||||
public static final ParameterizedTypeReference<Map<String, Map<String, String>>> MAP_OF_MAPS_TYPE = new ParameterizedTypeReference<Map<String, Map<String, String>>>() {
|
||||
|
||||
};
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
@Setter
|
||||
@NonNull
|
||||
private VaultProperties vaultProperties;
|
||||
|
||||
@Setter
|
||||
@NonNull
|
||||
private VaultToken rootToken;
|
||||
|
||||
public PrepareVault(RestTemplate restTemplate) {
|
||||
|
||||
Assert.notNull(restTemplate, "RestTemplate must not be null");
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Vault and unseal the vault.
|
||||
*
|
||||
* @return the root token.
|
||||
*/
|
||||
public VaultToken initializeVault() {
|
||||
|
||||
Assert.notNull(vaultProperties, "VaultProperties must not be null");
|
||||
|
||||
Map<String, String> parameters = parameters(vaultProperties);
|
||||
|
||||
int createKeys = 2;
|
||||
int requiredKeys = 2;
|
||||
|
||||
InitializeVault initializeVault = InitializeVault.of(createKeys, requiredKeys);
|
||||
|
||||
ResponseEntity<VaultInitialized> initResponse = restTemplate.exchange(
|
||||
INITIALIZE_URL_TEMPLATE, HttpMethod.PUT,
|
||||
new HttpEntity<>(initializeVault), VaultInitialized.class, parameters);
|
||||
|
||||
if (!initResponse.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot initialize vault: " + initResponse.toString());
|
||||
}
|
||||
VaultInitialized initialized = initResponse.getBody();
|
||||
|
||||
for (int i = 0; i < requiredKeys; i++) {
|
||||
|
||||
UnsealKey unsealKey = UnsealKey.of(initialized.getKeys().get(i));
|
||||
ResponseEntity<UnsealProgress> unsealResponse = restTemplate.exchange(
|
||||
UNSEAL_URL_TEMPLATE, HttpMethod.PUT, new HttpEntity<>(unsealKey),
|
||||
UnsealProgress.class, parameters);
|
||||
|
||||
UnsealProgress unsealProgress = unsealResponse.getBody();
|
||||
if (!unsealProgress.isSealed()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return VaultToken.of(initialized.getRootToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a token for the given {@code tokenId} and {@code policy}.
|
||||
*
|
||||
* @param tokenId
|
||||
* @param policy
|
||||
* @return
|
||||
*/
|
||||
public VaultToken createToken(String tokenId, String policy) {
|
||||
|
||||
Map<String, String> parameters = parameters(vaultProperties);
|
||||
|
||||
CreateToken createToken = new CreateToken();
|
||||
createToken.setId(tokenId);
|
||||
if (policy != null) {
|
||||
createToken.setPolicies(Collections.singletonList(policy));
|
||||
}
|
||||
|
||||
HttpHeaders headers = authenticatedHeaders();
|
||||
|
||||
HttpEntity<CreateToken> entity = new HttpEntity<>(createToken, headers);
|
||||
|
||||
ResponseEntity<TokenCreated> createTokenResponse = restTemplate.exchange(
|
||||
CREATE_TOKEN_URL_TEMPLATE, HttpMethod.POST, entity, TokenCreated.class,
|
||||
parameters);
|
||||
|
||||
if (!createTokenResponse.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot create token: " + createTokenResponse.toString());
|
||||
}
|
||||
|
||||
AuthToken authToken = createTokenResponse.getBody().getAuth();
|
||||
|
||||
return VaultToken.of(authToken.getClientToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether Vault is available (vault created and unsealed).
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isAvailable() {
|
||||
|
||||
Map<String, String> parameters = parameters(vaultProperties);
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate
|
||||
.getForEntity(SEAL_STATUS_URL_TEMPLATE, String.class, parameters);
|
||||
|
||||
if (exchange.getStatusCode().is2xxSuccessful()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (exchange.getStatusCode().is4xxClientError()) {
|
||||
return false;
|
||||
}
|
||||
throw new IllegalStateException("Vault error: " + exchange.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount an auth backend.
|
||||
*
|
||||
* @param authBackend
|
||||
*/
|
||||
public void mountAuth(String authBackend) {
|
||||
|
||||
Assert.hasText(authBackend, "AuthBackend must not be empty");
|
||||
|
||||
Map<String, String> parameters = parameters(vaultProperties);
|
||||
parameters.put("authBackend", authBackend);
|
||||
|
||||
Map<String, String> requestEntity = Collections.singletonMap("type", authBackend);
|
||||
|
||||
HttpEntity<Map<String, String>> entity = new HttpEntity<>(requestEntity,
|
||||
authenticatedHeaders());
|
||||
|
||||
ResponseEntity<String> responseEntity = restTemplate.exchange(
|
||||
MOUNT_AUTH_URL_TEMPLATE, HttpMethod.POST, entity, String.class,
|
||||
parameters);
|
||||
|
||||
if (!responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot create mount auth backend: " + responseEntity.toString());
|
||||
}
|
||||
|
||||
responseEntity.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a auth-backend is enabled.
|
||||
*
|
||||
* @param authBackend
|
||||
* @return
|
||||
*/
|
||||
public boolean hasAuth(String authBackend) {
|
||||
|
||||
Assert.hasText(authBackend, "AuthBackend must not be empty");
|
||||
|
||||
Map<String, String> parameters = parameters(vaultProperties);
|
||||
parameters.put("authBackend", authBackend);
|
||||
|
||||
HttpEntity<Map<String, String>> entity = new HttpEntity<>(authenticatedHeaders());
|
||||
|
||||
ResponseEntity<Map<String, Map<String, String>>> responseEntity = restTemplate
|
||||
.exchange(SYS_AUTH_URL_TEMPLATE, HttpMethod.GET, entity, MAP_OF_MAPS_TYPE,
|
||||
parameters);
|
||||
|
||||
if (!responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot create mount auth backend: " + responseEntity.toString());
|
||||
}
|
||||
|
||||
Map<String, Map<String, String>> body = responseEntity.getBody();
|
||||
for (Entry<String, Map<String, String>> entry : body.entrySet()) {
|
||||
if (entry.getKey().contains(authBackend)
|
||||
&& authBackend.equals(entry.getValue().get("type"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write key-value data to the Vault secret backend.
|
||||
*
|
||||
* @param path
|
||||
* @param data
|
||||
*/
|
||||
public void writeSecret(String path, Map<String, ?> data) {
|
||||
|
||||
Assert.hasText(path, "Path must not be empty");
|
||||
write("secret/" + path, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write key-value data to a path in Vault.
|
||||
*
|
||||
* @param path
|
||||
* @param data
|
||||
*/
|
||||
public void write(String path, Map<String, ?> data) {
|
||||
|
||||
Assert.hasText(path, "Path must not be empty");
|
||||
Assert.notNull(data, "Data must not be null");
|
||||
|
||||
HttpHeaders headers = authenticatedHeaders();
|
||||
|
||||
Map<String, String> parameters = parameters(vaultProperties);
|
||||
parameters.put("path", path);
|
||||
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(WRITE_URL_TEMPLATE,
|
||||
HttpMethod.PUT, new HttpEntity<Object>(data, headers), String.class,
|
||||
parameters);
|
||||
|
||||
if (!exchange.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(
|
||||
String.format("Cannot write to %s: %s", path, exchange.getBody()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an userId to appId mapping.
|
||||
*
|
||||
* @param appId
|
||||
* @param userId
|
||||
*/
|
||||
public void mapUserId(String appId, String userId) {
|
||||
|
||||
Map<String, String> userIdData = new HashMap<>();
|
||||
userIdData.put("value", appId); // name of the app-id
|
||||
userIdData.put("cidr_block", "0.0.0.0/0");
|
||||
|
||||
String appIdPath = vaultProperties.getAppId().getAppIdPath();
|
||||
if (!hasAuth(appIdPath)) {
|
||||
mountAuth(appIdPath);
|
||||
}
|
||||
|
||||
write(String.format("auth/%s/map/user-id/%s", appIdPath, userId), userIdData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an appId mapping.
|
||||
*
|
||||
* @param appId
|
||||
*/
|
||||
public void mapAppId(String appId) {
|
||||
|
||||
Map<String, String> appIdData = new HashMap<>();
|
||||
appIdData.put("value", "root"); // policy
|
||||
appIdData.put("display_name", "this is my test application");
|
||||
|
||||
write(String.format("auth/%s/map/app-id/%s",
|
||||
vaultProperties.getAppId().getAppIdPath(), appId), appIdData);
|
||||
}
|
||||
|
||||
private HttpHeaders authenticatedHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(VaultClient.VAULT_TOKEN, rootToken.getToken());
|
||||
return headers;
|
||||
}
|
||||
|
||||
private Map<String, String> parameters(VaultProperties vaultProperties) {
|
||||
|
||||
Map<String, String> parameters = new HashMap<>();
|
||||
|
||||
String baseUri = String.format("%s://%s:%s/%s", vaultProperties.getScheme(),
|
||||
vaultProperties.getHost(), vaultProperties.getPort(),
|
||||
VaultClient.API_VERSION);
|
||||
parameters.put("baseuri", baseUri);
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Data
|
||||
static class TokenCreated {
|
||||
|
||||
@JsonProperty("lease_duration")
|
||||
private long leaseDuration;
|
||||
@JsonProperty("renewable")
|
||||
private boolean renewable;
|
||||
@JsonProperty("auth")
|
||||
private AuthToken auth;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value(staticConstructor = "of")
|
||||
static class InitializeVault {
|
||||
|
||||
@JsonProperty("secret_shares")
|
||||
private int secretShares;
|
||||
|
||||
@JsonProperty("secret_threshold")
|
||||
private int secretThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Data
|
||||
static class CreateToken {
|
||||
|
||||
@JsonProperty("id")
|
||||
private String id;
|
||||
|
||||
@JsonProperty("policies")
|
||||
private List<String> policies;
|
||||
|
||||
@JsonProperty("ttl")
|
||||
private String ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value(staticConstructor = "of")
|
||||
static class UnsealKey {
|
||||
@JsonProperty
|
||||
@NonNull
|
||||
private String key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Data
|
||||
static class UnsealProgress {
|
||||
|
||||
@JsonProperty("sealed")
|
||||
private boolean sealed;
|
||||
@JsonProperty("t")
|
||||
private int t;
|
||||
@JsonProperty("n")
|
||||
private int n;
|
||||
@JsonProperty("progress")
|
||||
private int progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Data
|
||||
static class VaultInitialized {
|
||||
|
||||
@JsonProperty("keys")
|
||||
private List<String> keys;
|
||||
@JsonProperty("root_token")
|
||||
private String rootToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Data
|
||||
public static class AuthToken {
|
||||
|
||||
@JsonProperty("client_token")
|
||||
private String clientToken;
|
||||
|
||||
@JsonProperty("policies")
|
||||
private List<String> policies;
|
||||
|
||||
@JsonProperty("metadata")
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
@JsonProperty("lease_duration")
|
||||
private long leaseDuration;
|
||||
|
||||
@JsonProperty("renewable")
|
||||
private boolean renewable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault.util;
|
||||
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.VaultToken;
|
||||
|
||||
/**
|
||||
* Utility to retrieve settings during test.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class Settings {
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the vault properties.
|
||||
*/
|
||||
public static VaultProperties createVaultProperties() {
|
||||
|
||||
VaultProperties vaultProperties = new VaultProperties();
|
||||
vaultProperties.setToken(token().getToken());
|
||||
vaultProperties.setHost(System.getProperty("vault.host", "localhost"));
|
||||
|
||||
return vaultProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the token to use during tests.
|
||||
*/
|
||||
public static VaultToken token() {
|
||||
return VaultToken.of(System.getProperty("vault.token",
|
||||
"00000000-0000-0000-0000-000000000000"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.vault.util;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
import org.springframework.cloud.vault.VaultProperties;
|
||||
import org.springframework.cloud.vault.VaultToken;
|
||||
|
||||
/**
|
||||
* Vault rule to ensure a running and prepared Vault.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class VaultRule extends ExternalResource {
|
||||
|
||||
private final VaultProperties vaultProperties;
|
||||
private final PrepareVault prepareVault = new PrepareVault(new TestRestTemplate());
|
||||
|
||||
public VaultRule() {
|
||||
this(Settings.createVaultProperties());
|
||||
}
|
||||
|
||||
public VaultRule(VaultProperties vaultProperties) {
|
||||
this.vaultProperties = vaultProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before() {
|
||||
|
||||
try (Socket socket = new Socket()) {
|
||||
|
||||
socket.connect(new InetSocketAddress(InetAddress.getByName("localhost"),
|
||||
vaultProperties.getPort()));
|
||||
socket.close();
|
||||
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Vault is not running on localhost:%d which is required to run a test using @Rule %s",
|
||||
vaultProperties.getPort(), getClass().getSimpleName()));
|
||||
}
|
||||
|
||||
prepareVault.setVaultProperties(vaultProperties);
|
||||
|
||||
if (!prepareVault.isAvailable()) {
|
||||
VaultToken rootToken = prepareVault.initializeVault();
|
||||
prepareVault.setRootToken(rootToken);
|
||||
prepareVault.createToken(vaultProperties.getToken(), "root");
|
||||
}
|
||||
else {
|
||||
prepareVault.setRootToken(Settings.token());
|
||||
}
|
||||
}
|
||||
|
||||
public PrepareVault prepare() {
|
||||
return prepareVault;
|
||||
}
|
||||
|
||||
}
|
||||
3
src/test/resources/META-INF/spring.factories
Normal file
3
src/test/resources/META-INF/spring.factories
Normal file
@@ -0,0 +1,3 @@
|
||||
# Bootstrap Configuration
|
||||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||
org.springframework.cloud.vault.VaultAppIdCustomMechanismTests.BootstrapConfiguration
|
||||
7
src/test/resources/vault.conf
Normal file
7
src/test/resources/vault.conf
Normal file
@@ -0,0 +1,7 @@
|
||||
backend "inmem" {
|
||||
}
|
||||
|
||||
listener "tcp" {
|
||||
address = "127.0.0.1:8200"
|
||||
tls_disable = 1
|
||||
}
|
||||
Reference in New Issue
Block a user