Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
17145e53be | |||
e988833eb9 | |||
ea3b6d50cb | |||
3f7f88e14a | |||
3396693120 | |||
ca51fed145 | |||
9c216dd805 | |||
b98b7ab95c | |||
ecf398c9d0 | |||
a80805a044 |
@ -1,3 +1,8 @@
|
||||
## 0.4.1 [2016-12-24]
|
||||
* [fix] Factory Null-tolerant for trusted certificate (#6)
|
||||
* [test] StackTraces testet for secret leaks
|
||||
* [test] Tested against Vault 0.6.4
|
||||
|
||||
## 0.4.0 [2016-11-06]
|
||||
* [feature] Option to provide a trusted CA certificate (#2)
|
||||
* [feature] Deletion, revocation and renewal of secrets (#3)
|
||||
|
13
README.md
13
README.md
@ -5,26 +5,31 @@ Java Vault Connector is a connector library for [Vault](https://www.vaultproject
|
||||
**Current available features:**
|
||||
|
||||
* HTTP(S) backend connector
|
||||
* Ability to provide or enforce custom CA certificate
|
||||
* Authorization methods:
|
||||
* Token
|
||||
* Username/Password
|
||||
* AppID (register and authenticate)
|
||||
* AppID (register and authenticate) [_deprecated_]
|
||||
* AppRole (register and authenticate)
|
||||
* Tokens
|
||||
* Creation and lookup of tokens
|
||||
* TokenBuilder for speaking creation of complex configuraitons
|
||||
* Secrets
|
||||
* Read secrets
|
||||
* Write secrets
|
||||
* List secrets
|
||||
* Delete secrets
|
||||
* Renew/revoke leases
|
||||
* Raw secret content or JSON decoding
|
||||
* Connector Factory with builder pattern
|
||||
* Tested against Vault 0.6.2
|
||||
* Tested against Vault 0.6.4
|
||||
|
||||
**Usage Example**
|
||||
|
||||
```java
|
||||
// Instanciate using builder pattern style factory
|
||||
VaultConnector vault = VaultConnectorFactory.httpFactory()
|
||||
.wiithHost("127.0.0.1")
|
||||
.withHost("127.0.0.1")
|
||||
.withPort(8200)
|
||||
.withTLS()
|
||||
.build();
|
||||
@ -41,7 +46,7 @@ String secret = vault.readSecret("some/secret/key").getValue();
|
||||
<dependency>
|
||||
<groupId>de.stklcode.jvault</groupId>
|
||||
<artifactId>connector</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<version>0.4.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
8
pom.xml
8
pom.xml
@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>de.stklcode.jvault</groupId>
|
||||
<artifactId>connector</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<version>0.4.1</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@ -17,7 +17,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
@ -41,12 +41,12 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.8.4</version>
|
||||
<version>2.8.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.8.4</version>
|
||||
<version>2.8.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
@ -49,7 +49,6 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
private static final String PATH_SEAL_STATUS = "sys/seal-status";
|
||||
private static final String PATH_SEAL = "sys/seal";
|
||||
private static final String PATH_UNSEAL = "sys/unseal";
|
||||
private static final String PATH_INIT = "sys/init";
|
||||
private static final String PATH_RENEW = "sys/renew";
|
||||
private static final String PATH_AUTH = "sys/auth";
|
||||
private static final String PATH_TOKEN = "auth/token";
|
||||
@ -136,7 +135,8 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
/**
|
||||
* Create connector using full URL and trusted certificate.
|
||||
*
|
||||
* @param baseURL The URL
|
||||
* @param baseURL The URL
|
||||
* @param sslContext Custom SSL Context
|
||||
*/
|
||||
public HTTPVaultConnector(String baseURL, SSLContext sslContext) {
|
||||
this.baseURL = baseURL;
|
||||
@ -196,12 +196,6 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
return authorized && (tokenTTL == 0 || tokenTTL >= System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean init() {
|
||||
/* TODO: implement init() */
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuthBackend> getAuthBackends() throws VaultConnectorException {
|
||||
try {
|
||||
@ -240,6 +234,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public AuthResponse authAppId(final String appID, final String userID) throws VaultConnectorException {
|
||||
final Map<String, String> payload = new HashMap<>();
|
||||
payload.put("app_id", appID);
|
||||
@ -281,6 +276,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean registerAppId(final String appID, final String policy, final String displayName) throws VaultConnectorException {
|
||||
if (!isAuthorized())
|
||||
throw new AuthorizationRequiredException();
|
||||
@ -296,6 +292,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean registerUserId(final String appID, final String userID) throws VaultConnectorException {
|
||||
if (!isAuthorized())
|
||||
throw new AuthorizationRequiredException();
|
||||
@ -499,7 +496,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeSecret(final String key, final String value) throws VaultConnectorException {
|
||||
public void writeSecret(final String key, final String value) throws VaultConnectorException {
|
||||
if (!isAuthorized())
|
||||
throw new AuthorizationRequiredException();
|
||||
|
||||
@ -508,11 +505,12 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
|
||||
Map<String, String> param = new HashMap<>();
|
||||
param.put("value", value);
|
||||
return requestPost(PATH_SECRET + "/" + key, param).equals("");
|
||||
if (!requestPost(PATH_SECRET + "/" + key, param).equals(""))
|
||||
throw new InvalidResponseException("Received response where none was expected.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteSecret(String key) throws VaultConnectorException {
|
||||
public void deleteSecret(String key) throws VaultConnectorException {
|
||||
if (!isAuthorized())
|
||||
throw new AuthorizationRequiredException();
|
||||
|
||||
@ -521,13 +519,11 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
|
||||
/* Response should be code 204 without content */
|
||||
if (!response.equals(""))
|
||||
throw new InvalidResponseException("Received response where non was expected.");
|
||||
|
||||
return true;
|
||||
throw new InvalidResponseException("Received response where none was expected.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean revoke(String leaseID) throws VaultConnectorException {
|
||||
public void revoke(String leaseID) throws VaultConnectorException {
|
||||
if (!isAuthorized())
|
||||
throw new AuthorizationRequiredException();
|
||||
|
||||
@ -536,9 +532,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
||||
|
||||
/* Response should be code 204 without content */
|
||||
if (!response.equals(""))
|
||||
throw new InvalidResponseException("Received response where non was expected.");
|
||||
|
||||
return true;
|
||||
throw new InvalidResponseException("Received response where none was expected.");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -32,13 +32,6 @@ import java.util.List;
|
||||
* @since 0.1
|
||||
*/
|
||||
public interface VaultConnector {
|
||||
/**
|
||||
* Verify that vault connection is initialized.
|
||||
*
|
||||
* @return TRUE if correctly initialized
|
||||
*/
|
||||
boolean init();
|
||||
|
||||
/**
|
||||
* Reset authorization information.
|
||||
*/
|
||||
@ -387,34 +380,32 @@ public interface VaultConnector {
|
||||
*
|
||||
* @param key Secret path
|
||||
* @param value Secret value
|
||||
* @return TRUE on success
|
||||
* @throws VaultConnectorException on error
|
||||
*/
|
||||
boolean writeSecret(final String key, final String value) throws VaultConnectorException;
|
||||
void writeSecret(final String key, final String value) throws VaultConnectorException;
|
||||
|
||||
/**
|
||||
* Delete secret from Vault.
|
||||
*
|
||||
* @param key Secret path
|
||||
* @return TRUE on succevss
|
||||
* @throws VaultConnectorException on error
|
||||
*/
|
||||
boolean deleteSecret(final String key) throws VaultConnectorException;
|
||||
void deleteSecret(final String key) throws VaultConnectorException;
|
||||
|
||||
/**
|
||||
* Revoke given lease immediately.
|
||||
*
|
||||
* @param leaseID the lease ID
|
||||
* @return TRUE on success
|
||||
* @throws VaultConnectorException on error
|
||||
*/
|
||||
boolean revoke(final String leaseID) throws VaultConnectorException;
|
||||
void revoke(final String leaseID) throws VaultConnectorException;
|
||||
|
||||
/**
|
||||
* Renew lease with given ID.
|
||||
*
|
||||
* @param leaseID the lase ID
|
||||
* @return Renewed lease
|
||||
* @throws VaultConnectorException on error
|
||||
*/
|
||||
default SecretResponse renew(final String leaseID) throws VaultConnectorException {
|
||||
return renew(leaseID, null);
|
||||
@ -426,6 +417,7 @@ public interface VaultConnector {
|
||||
* @param leaseID the lase ID
|
||||
* @param increment number of seconds to extend lease time
|
||||
* @return Renewed lease
|
||||
* @throws VaultConnectorException on error
|
||||
*/
|
||||
SecretResponse renew(final String leaseID, final Integer increment) throws VaultConnectorException;
|
||||
|
||||
|
@ -128,10 +128,13 @@ public class HTTPVaultConnectorFactory extends VaultConnectorFactory {
|
||||
*
|
||||
* @param cert path to certificate file
|
||||
* @return self
|
||||
* @throws VaultConnectorException on error
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public HTTPVaultConnectorFactory withTrustedCA(Path cert) throws VaultConnectorException {
|
||||
return withSslContext(createSslContext(cert));
|
||||
if (cert != null)
|
||||
return withSslContext(createSslContext(cert));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,10 +30,7 @@ import org.junit.*;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.rules.TestName;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
@ -46,7 +43,8 @@ import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
/**
|
||||
* JUnit Test for HTTP Vault connector.
|
||||
* JUnit test for HTTP Vault connector.
|
||||
* This test requires Vault binary in executable Path as it instantiates a real Vault server on given test data.
|
||||
*
|
||||
* @author Stefan Kalscheuer
|
||||
* @since 0.1
|
||||
@ -143,10 +141,13 @@ public class HTTPVaultConnectorTest {
|
||||
@Test
|
||||
public void authTokenTest() {
|
||||
TokenResponse res;
|
||||
final String invalidToken = "52135869df23a5e64c5d33a9785af5edb456b8a4a235d1fe135e6fba1c35edf6";
|
||||
try {
|
||||
connector.authToken("52135869df23a5e64c5d33a9785af5edb456b8a4a235d1fe135e6fba1c35edf6");
|
||||
connector.authToken(invalidToken);
|
||||
fail("Logged in with invalid token");
|
||||
} catch (VaultConnectorException ignored) {
|
||||
} catch (VaultConnectorException e) {
|
||||
/* Assert that the exception does not reveal the token */
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(invalidToken)));
|
||||
}
|
||||
|
||||
try {
|
||||
@ -164,10 +165,15 @@ public class HTTPVaultConnectorTest {
|
||||
@Test
|
||||
public void authUserPassTest() {
|
||||
AuthResponse res = null;
|
||||
final String invalidUser = "foo";
|
||||
final String invalidPass = "bar";
|
||||
try {
|
||||
connector.authUserPass("foo", "bar");
|
||||
connector.authUserPass(invalidUser, invalidPass);
|
||||
fail("Logged in with invalid credentials");
|
||||
} catch (VaultConnectorException ignored) {
|
||||
} catch (VaultConnectorException e) {
|
||||
/* Assert that the exception does not reveal credentials */
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(invalidUser)));
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(invalidPass)));
|
||||
}
|
||||
|
||||
try {
|
||||
@ -183,6 +189,7 @@ public class HTTPVaultConnectorTest {
|
||||
* App-ID authentication roundtrip.
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void authAppIdTest() {
|
||||
authRoot();
|
||||
assumeTrue(connector.isAuthorized());
|
||||
@ -231,19 +238,27 @@ public class HTTPVaultConnectorTest {
|
||||
}
|
||||
|
||||
/* Authenticate with valid secret ID against unknown role */
|
||||
final String invalidRole = "foo";
|
||||
try {
|
||||
AuthResponse res = connector.authAppRole("foo", APPROLE_SECRET);
|
||||
connector.authAppRole(invalidRole, APPROLE_SECRET);
|
||||
fail("Successfully logged in with unknown role");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat(e, is(instanceOf(InvalidResponseException.class)));
|
||||
/* Assert that the exception does not reveal role ID or secret */
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(invalidRole)));
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(APPROLE_SECRET)));
|
||||
}
|
||||
|
||||
/* Authenticate without wrong secret ID */
|
||||
final String invalidSecret = "foo";
|
||||
try {
|
||||
AuthResponse res = connector.authAppRole(APPROLE_ROLE, "foo");
|
||||
fail("Successfully logged in without secret ID");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat(e, is(instanceOf(InvalidResponseException.class)));
|
||||
/* Assert that the exception does not reveal role ID or secret */
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(APPROLE_ROLE)));
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(invalidSecret)));
|
||||
}
|
||||
|
||||
/* Authenticate without secret ID */
|
||||
@ -252,6 +267,8 @@ public class HTTPVaultConnectorTest {
|
||||
fail("Successfully logged in without secret ID");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat(e, is(instanceOf(InvalidResponseException.class)));
|
||||
/* Assert that the exception does not reveal role ID */
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(APPROLE_ROLE)));
|
||||
}
|
||||
|
||||
/* Authenticate with secret ID on role with CIDR whitelist */
|
||||
@ -437,11 +454,17 @@ public class HTTPVaultConnectorTest {
|
||||
|
||||
/* Try to read path user has no permission to read */
|
||||
SecretResponse res = null;
|
||||
final String invalidPath = "invalid/path";
|
||||
try {
|
||||
res = connector.readSecret("invalid/path");
|
||||
res = connector.readSecret(invalidPath);
|
||||
fail("Invalid secret path successfully read.");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat(e, instanceOf(PermissionDeniedException.class));
|
||||
/* Assert that the exception does not reveal secret or credentials */
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(invalidPath)));
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(USER_VALID)));
|
||||
assertThat(stackTrace(e), not(stringContainsInOrder(PASS_VALID)));
|
||||
assertThat(stackTrace(e), not(matchesPattern("[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}")));
|
||||
}
|
||||
|
||||
/* Try to read accessible path with known value */
|
||||
@ -523,29 +546,28 @@ public class HTTPVaultConnectorTest {
|
||||
|
||||
/* Try to write to null path */
|
||||
try {
|
||||
boolean res = connector.writeSecret(null, "someValue");
|
||||
connector.writeSecret(null, "someValue");
|
||||
fail("Secret written to null path.");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat(e, instanceOf(InvalidRequestException.class));
|
||||
}
|
||||
/* Try to write to invalid path */
|
||||
try {
|
||||
boolean res = connector.writeSecret("", "someValue");
|
||||
connector.writeSecret("", "someValue");
|
||||
fail("Secret written to invalid path.");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat(e, instanceOf(InvalidRequestException.class));
|
||||
}
|
||||
/* Try to write to a path the user has no access for */
|
||||
try {
|
||||
boolean res = connector.writeSecret("invalid/path", "someValue");
|
||||
connector.writeSecret("invalid/path", "someValue");
|
||||
fail("Secret written to inaccessible path.");
|
||||
} catch (VaultConnectorException e) {
|
||||
assertThat(e, instanceOf(PermissionDeniedException.class));
|
||||
}
|
||||
/* Perform a valid write/read roundtrip to valid path. Also check UTF8-encoding. */
|
||||
try {
|
||||
boolean res = connector.writeSecret(SECRET_PATH + "/temp", "Abc123äöü,!");
|
||||
assertThat("Secret could not be written to valid path.", res, is(true));
|
||||
connector.writeSecret(SECRET_PATH + "/temp", "Abc123äöü,!");
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Secret written to inaccessible path.");
|
||||
}
|
||||
@ -567,8 +589,7 @@ public class HTTPVaultConnectorTest {
|
||||
|
||||
/* Write a test secret to vault */
|
||||
try {
|
||||
boolean res = connector.writeSecret(SECRET_PATH + "/toDelete", "secret content");
|
||||
assumeThat("Secret could not be written path.", res, is(true));
|
||||
connector.writeSecret(SECRET_PATH + "/toDelete", "secret content");
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Secret written to inaccessible path.");
|
||||
}
|
||||
@ -582,8 +603,7 @@ public class HTTPVaultConnectorTest {
|
||||
|
||||
/* Delete secret */
|
||||
try {
|
||||
boolean deleted = connector.deleteSecret(SECRET_PATH + "/toDelete");
|
||||
assertThat("Revocation of secret faiked.", deleted, is(true));
|
||||
connector.deleteSecret(SECRET_PATH + "/toDelete");
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Revocation threw unexpected exception.");
|
||||
}
|
||||
@ -608,8 +628,7 @@ public class HTTPVaultConnectorTest {
|
||||
|
||||
/* Write a test secret to vault */
|
||||
try {
|
||||
boolean res = connector.writeSecret(SECRET_PATH + "/toRevoke", "secret content");
|
||||
assumeThat("Secret could not be written path.", res, is(true));
|
||||
connector.writeSecret(SECRET_PATH + "/toRevoke", "secret content");
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Secret written to inaccessible path.");
|
||||
}
|
||||
@ -623,8 +642,7 @@ public class HTTPVaultConnectorTest {
|
||||
|
||||
/* Revoke secret */
|
||||
try {
|
||||
boolean revoked = connector.revoke(SECRET_PATH + "/toRevoke");
|
||||
assertThat("Revocation of secret faiked.", revoked, is(true));
|
||||
connector.revoke(SECRET_PATH + "/toRevoke");
|
||||
} catch (VaultConnectorException e) {
|
||||
fail("Revocation threw unexpected exception.");
|
||||
}
|
||||
@ -752,7 +770,7 @@ public class HTTPVaultConnectorTest {
|
||||
|
||||
/* Write configuration file */
|
||||
BufferedWriter bw = null;
|
||||
File configFile = null;
|
||||
File configFile;
|
||||
try {
|
||||
configFile = tmpDir.newFile("vault.conf");
|
||||
bw = new BufferedWriter(new FileWriter(configFile));
|
||||
@ -831,4 +849,16 @@ public class HTTPVaultConnectorTest {
|
||||
}
|
||||
throw new IllegalStateException("Unable to find a free TCP port.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve StackTrace from throwable as string
|
||||
*
|
||||
* @param th the throwable
|
||||
* @return the stack trace
|
||||
*/
|
||||
private static String stackTrace(final Throwable th) {
|
||||
StringWriter sw = new StringWriter();
|
||||
th.printStackTrace(new PrintWriter(sw, true));
|
||||
return sw.getBuffer().toString();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user