From e25f4b85299664be1b33e60d970a64c81296e361 Mon Sep 17 00:00:00 2001 From: Joseph Petersen Date: Sun, 11 Aug 2019 01:12:25 +0200 Subject: [PATCH] make x-vault-token optional, allows using Vault Agent --- .../java/com/bettercloud/vault/Vault.java | 2 +- .../java/com/bettercloud/vault/api/Auth.java | 12 +-- .../java/com/bettercloud/vault/api/Debug.java | 4 +- .../com/bettercloud/vault/api/Leases.java | 8 +- .../com/bettercloud/vault/api/Logical.java | 16 ++-- .../java/com/bettercloud/vault/api/Seal.java | 2 +- .../bettercloud/vault/api/mounts/Mounts.java | 10 +-- .../com/bettercloud/vault/api/pki/Pki.java | 10 +-- .../vault/api/VaultAgentTests.java | 71 ++++++++++++++++++ .../bettercloud/vault/util/TestConstants.java | 6 ++ .../vault/util/VaultAgentContainer.java | 75 +++++++++++++++++++ .../vault/util/VaultContainer.java | 8 +- src/test-integration/resources/agent.hcl | 27 +++++++ .../resources/approlePolicy.hcl | 25 +++++++ src/test-integration/resources/libressl.conf | 6 +- 15 files changed, 246 insertions(+), 36 deletions(-) create mode 100644 src/test-integration/java/com/bettercloud/vault/api/VaultAgentTests.java create mode 100644 src/test-integration/java/com/bettercloud/vault/util/VaultAgentContainer.java create mode 100644 src/test-integration/resources/agent.hcl create mode 100644 src/test-integration/resources/approlePolicy.hcl diff --git a/src/main/java/com/bettercloud/vault/Vault.java b/src/main/java/com/bettercloud/vault/Vault.java index a12f396f..339b7e38 100644 --- a/src/main/java/com/bettercloud/vault/Vault.java +++ b/src/main/java/com/bettercloud/vault/Vault.java @@ -249,7 +249,7 @@ private Map collectSecretEngineVersions() { try { final RestResponse restResponse = new Rest()//NOPMD .url(vaultConfig.getAddress() + "/v1/sys/mounts") - .header("X-Vault-Token", vaultConfig.getToken()) + .optionalHeader("X-Vault-Token", vaultConfig.getToken()) .optionalHeader("X-Vault-Namespace", this.vaultConfig.getNameSpace()) .connectTimeoutSeconds(vaultConfig.getOpenTimeout()) .readTimeoutSeconds(vaultConfig.getReadTimeout()) diff --git a/src/main/java/com/bettercloud/vault/api/Auth.java b/src/main/java/com/bettercloud/vault/api/Auth.java index 17c8d874..c5e955f5 100644 --- a/src/main/java/com/bettercloud/vault/api/Auth.java +++ b/src/main/java/com/bettercloud/vault/api/Auth.java @@ -261,7 +261,7 @@ public AuthResponse createToken(final TokenRequest tokenRequest, final String to // HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD .url(url) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .body(requestJson.getBytes(StandardCharsets.UTF_8)) .connectTimeoutSeconds(config.getOpenTimeout()) @@ -1152,7 +1152,7 @@ public AuthResponse renewSelf(final long increment, final String tokenAuthMount) final String requestJson = Json.object().add("increment", increment).toString(); final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/auth/" + mount + "/renew-self") - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .body(increment < 0 ? null : requestJson.getBytes(StandardCharsets.UTF_8)) .connectTimeoutSeconds(config.getOpenTimeout()) @@ -1215,7 +1215,7 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept // HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/auth/" + mount + "/lookup-self") - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -1277,7 +1277,7 @@ public LogicalResponse lookupWrap() throws VaultException { // HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/sys/wrapping/lookup") - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -1339,7 +1339,7 @@ public void revokeSelf(final String tokenAuthMount) throws VaultException { // HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/auth/" + mount + "/revoke-self") - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -1440,7 +1440,7 @@ public AuthResponse unwrap(final String wrappedToken) throws VaultException { // HTTP request to Vault final RestResponse restResponse = new Rest() .url(url) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .body(requestJson.getBytes(StandardCharsets.UTF_8)) .connectTimeoutSeconds(config.getOpenTimeout()) diff --git a/src/main/java/com/bettercloud/vault/api/Debug.java b/src/main/java/com/bettercloud/vault/api/Debug.java index 50ed7e27..370d73c7 100644 --- a/src/main/java/com/bettercloud/vault/api/Debug.java +++ b/src/main/java/com/bettercloud/vault/api/Debug.java @@ -96,9 +96,7 @@ public HealthResponse health( .sslVerification(config.getSslConfig().isVerify()) .sslContext(config.getSslConfig().getSslContext()); // Add token if present - if (config.getToken() != null) { - rest.header("X-Vault-Token", config.getToken()); - } + rest.optionalHeader("X-Vault-Token", config.getToken()); rest.optionalHeader("X-Vault-Namespace", this.nameSpace); // Add params if present if (standbyOk != null) rest.parameter("standbyok", standbyOk.toString()); diff --git a/src/main/java/com/bettercloud/vault/api/Leases.java b/src/main/java/com/bettercloud/vault/api/Leases.java index 1a54c6e8..1c36a984 100644 --- a/src/main/java/com/bettercloud/vault/api/Leases.java +++ b/src/main/java/com/bettercloud/vault/api/Leases.java @@ -62,7 +62,7 @@ public VaultResponse revoke(final String leaseId) throws VaultException { */ final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/sys/leases/revoke/" + leaseId) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -117,7 +117,7 @@ public VaultResponse revokePrefix(final String prefix) throws VaultException { try { final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/sys/revoke-prefix/" + prefix) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -175,7 +175,7 @@ public VaultResponse revokeForce(final String prefix) throws VaultException { try { final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/sys/revoke-force/" + prefix) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -238,7 +238,7 @@ public VaultResponse renew(final String leaseId, final long increment) throws Va final String requestJson = Json.object().add("increment", increment).toString(); final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/sys/renew/" + leaseId) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .body(increment < 0 ? null : requestJson.getBytes(StandardCharsets.UTF_8)) .connectTimeoutSeconds(config.getOpenTimeout()) diff --git a/src/main/java/com/bettercloud/vault/api/Logical.java b/src/main/java/com/bettercloud/vault/api/Logical.java index b6a379ac..22f79ebe 100644 --- a/src/main/java/com/bettercloud/vault/api/Logical.java +++ b/src/main/java/com/bettercloud/vault/api/Logical.java @@ -83,7 +83,7 @@ private LogicalResponse read(final String path, Boolean shouldRetry, final logic // Make an HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, operation)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -151,7 +151,7 @@ public LogicalResponse read(final String path, Boolean shouldRetry, final Intege // Make an HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, logicalOperations.readV2)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .parameter("version", version.toString()) .connectTimeoutSeconds(config.getOpenTimeout()) @@ -251,7 +251,7 @@ private LogicalResponse write(final String path, final Map nameV final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, operation)) .body(jsonObjectToWriteFromEngineVersion(operation, requestJson).toString().getBytes(StandardCharsets.UTF_8)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -355,7 +355,7 @@ private LogicalResponse delete(final String path, final Logical.logicalOperation // Make an HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/" + adjustPathForDelete(path, operation)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -415,7 +415,7 @@ public LogicalResponse delete(final String path, final int[] versions) throws Va JsonObject versionsToDelete = new JsonObject().add("versions", versions); final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/" + adjustPathForVersionDelete(path)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -486,7 +486,7 @@ public LogicalResponse unDelete(final String path, final int[] versions) throws JsonObject versionsToUnDelete = new JsonObject().add("versions", versions); final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/" + adjustPathForVersionUnDelete(path)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -545,7 +545,7 @@ public LogicalResponse destroy(final String path, final int[] versions) throws V JsonObject versionsToDestroy = new JsonObject().add("versions", versions); final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/" + adjustPathForVersionDestroy(path)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -596,7 +596,7 @@ public LogicalResponse upgrade(final String kvPath) throws VaultException { JsonObject kvToUpgrade = new JsonObject().add("options", new JsonObject().add("version", 2)); final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/sys/mounts/" + (kvPath.replaceAll("/", "") + "/tune")) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) diff --git a/src/main/java/com/bettercloud/vault/api/Seal.java b/src/main/java/com/bettercloud/vault/api/Seal.java index 5aa53ba3..857772f6 100644 --- a/src/main/java/com/bettercloud/vault/api/Seal.java +++ b/src/main/java/com/bettercloud/vault/api/Seal.java @@ -47,7 +47,7 @@ public void seal() throws VaultException { // HTTP request to Vault final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/sys/seal") - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) diff --git a/src/main/java/com/bettercloud/vault/api/mounts/Mounts.java b/src/main/java/com/bettercloud/vault/api/mounts/Mounts.java index 92c1ea65..441de871 100644 --- a/src/main/java/com/bettercloud/vault/api/mounts/Mounts.java +++ b/src/main/java/com/bettercloud/vault/api/mounts/Mounts.java @@ -47,7 +47,7 @@ public MountResponse list() throws VaultException { try { final RestResponse restResponse = new Rest()//NOPMD .url(String.format("%s/v1/sys/mounts", config.getAddress())) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) .sslVerification(config.getSslConfig().isVerify()) @@ -132,7 +132,7 @@ public MountResponse enable(final String path, final MountType type, final Mount final RestResponse restResponse = new Rest()//NOPMD .url(String.format("%s/v1/sys/mounts/%s", config.getAddress(), path)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .body(requestJson.getBytes("UTF-8")) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -199,7 +199,7 @@ public MountResponse disable(final String path) throws VaultException { try { final RestResponse restResponse = new Rest()//NOPMD .url(String.format("%s/v1/sys/mounts/%s", config.getAddress(), path)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) .sslVerification(config.getSslConfig().isVerify()) @@ -265,7 +265,7 @@ public MountResponse read(final String path) throws VaultException { try { final RestResponse restResponse = new Rest()//NOPMD .url(String.format("%s/v1/sys/mounts/%s/tune", config.getAddress(), path)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) .sslVerification(config.getSslConfig().isVerify()) @@ -346,7 +346,7 @@ public MountResponse tune(final String path, final MountPayload payload) throws final RestResponse restResponse = new Rest()//NOPMD .url(String.format("%s/v1/sys/mounts/%s/tune", config.getAddress(), path)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .body(requestJson.getBytes("UTF-8")) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) diff --git a/src/main/java/com/bettercloud/vault/api/pki/Pki.java b/src/main/java/com/bettercloud/vault/api/pki/Pki.java index 588cdaeb..9a7c3d91 100644 --- a/src/main/java/com/bettercloud/vault/api/pki/Pki.java +++ b/src/main/java/com/bettercloud/vault/api/pki/Pki.java @@ -115,7 +115,7 @@ public PkiResponse createOrUpdateRole(final String roleName, final RoleOptions o final RestResponse restResponse = new Rest()//NOPMD .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .body(requestJson.getBytes(StandardCharsets.UTF_8)) .connectTimeoutSeconds(config.getOpenTimeout()) @@ -177,7 +177,7 @@ public PkiResponse getRole(final String roleName) throws VaultException { try { final RestResponse restResponse = new Rest()//NOPMD .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -244,7 +244,7 @@ public PkiResponse revoke(final String serialNumber) throws VaultException { try { final RestResponse restResponse = new Rest()//NOPMD .url(String.format("%s/v1/%s/revoke", config.getAddress(), this.mountPath)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -306,7 +306,7 @@ public PkiResponse deleteRole(final String roleName) throws VaultException { try { final RestResponse restResponse = new Rest()//NOPMD .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) @@ -462,7 +462,7 @@ public PkiResponse issue( String endpoint = (csr == null || csr.isEmpty()) ? "%s/v1/%s/issue/%s" : "%s/v1/%s/sign/%s"; final RestResponse restResponse = new Rest()//NOPMD .url(String.format(endpoint, config.getAddress(), this.mountPath, roleName)) - .header("X-Vault-Token", config.getToken()) + .optionalHeader("X-Vault-Token", config.getToken()) .optionalHeader("X-Vault-Namespace", this.nameSpace) .body(requestJson.getBytes(StandardCharsets.UTF_8)) .connectTimeoutSeconds(config.getOpenTimeout()) diff --git a/src/test-integration/java/com/bettercloud/vault/api/VaultAgentTests.java b/src/test-integration/java/com/bettercloud/vault/api/VaultAgentTests.java new file mode 100644 index 00000000..d63849be --- /dev/null +++ b/src/test-integration/java/com/bettercloud/vault/api/VaultAgentTests.java @@ -0,0 +1,71 @@ +package com.bettercloud.vault.api; + +import com.bettercloud.vault.Vault; +import com.bettercloud.vault.VaultConfig; +import com.bettercloud.vault.VaultException; +import com.bettercloud.vault.response.LogicalResponse; +import com.bettercloud.vault.util.VaultAgentContainer; +import com.bettercloud.vault.util.VaultContainer; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static junit.framework.TestCase.assertEquals; +import static org.apache.commons.io.FileUtils.writeStringToFile; +import static org.junit.Assert.assertNotNull; + +public class VaultAgentTests { + @ClassRule + public static final VaultContainer container = new VaultContainer(); + @ClassRule + public static final TemporaryFolder temp = new TemporaryFolder(); + public static VaultAgentContainer vaultAgentContainer; + + @BeforeClass + public static void setupClass() throws IOException, InterruptedException, VaultException { + container.initAndUnsealVault(); + container.setupBackendAppRole(); + container.setEngineVersions(); + + final Vault vault = container.getRootVaultWithCustomVaultConfig(new VaultConfig().engineVersion(1)); + + final LogicalResponse roleIdResponse = vault.logical().read("auth/approle/role/testrole/role-id"); + String appRoleId = roleIdResponse.getData().get("role_id"); + final LogicalResponse secretIdResponse = vault.logical().write("auth/approle/role/testrole/secret-id", null); + String secretId = secretIdResponse.getData().get("secret_id"); + + assertNotNull(appRoleId); + assertNotNull(secretId); + + temp.create(); + File role_id = temp.newFile("role_id"); + File secret_id = temp.newFile("secret_id"); + writeStringToFile(role_id, appRoleId); + writeStringToFile(secret_id, secretId); + vaultAgentContainer = new VaultAgentContainer(role_id.toPath(), secret_id.toPath()); + } + + @Test + public void testWriteAndReadFromAgent() throws VaultException { + final String pathToWrite = "secret/hello"; + final String pathToRead = "secret/hello"; + + final String value = "world"; + final Vault vault = vaultAgentContainer.getVault(); + + final Map testMap = new HashMap<>(); + testMap.put("value", value); + + vault.logical().write(pathToWrite, testMap); + + final String valueRead = vault.logical().read(pathToRead).getData().get("value"); + assertEquals(value, valueRead); + } + +} diff --git a/src/test-integration/java/com/bettercloud/vault/util/TestConstants.java b/src/test-integration/java/com/bettercloud/vault/util/TestConstants.java index 440e6192..7b0d6eef 100644 --- a/src/test-integration/java/com/bettercloud/vault/util/TestConstants.java +++ b/src/test-integration/java/com/bettercloud/vault/util/TestConstants.java @@ -1,5 +1,7 @@ package com.bettercloud.vault.util; +import org.testcontainers.containers.Network; + import java.io.File; /** @@ -31,4 +33,8 @@ interface TestConstants { String CONTAINER_CERT_PEMFILE = CONTAINER_SSL_DIRECTORY + "/vault-cert.pem"; String CONTAINER_CLIENT_CERT_PEMFILE = CONTAINER_SSL_DIRECTORY + "/client-cert.pem"; + String AGENT_CONFIG_FILE = "/home/vault/agent.hcl"; + String APPROLE_POLICY_FILE = "/home/vault/approlePolicy.hcl"; + + Network CONTAINER_NETWORK = Network.newNetwork(); } diff --git a/src/test-integration/java/com/bettercloud/vault/util/VaultAgentContainer.java b/src/test-integration/java/com/bettercloud/vault/util/VaultAgentContainer.java new file mode 100644 index 00000000..ad4b5aab --- /dev/null +++ b/src/test-integration/java/com/bettercloud/vault/util/VaultAgentContainer.java @@ -0,0 +1,75 @@ +package com.bettercloud.vault.util; + +import com.bettercloud.vault.Vault; +import com.bettercloud.vault.VaultConfig; +import com.bettercloud.vault.VaultException; +import com.github.dockerjava.api.command.CreateContainerCmd; +import com.github.dockerjava.api.model.Capability; + +import java.nio.file.Path; +import java.util.function.Consumer; + +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.Wait; + +import static org.testcontainers.utility.MountableFile.forHostPath; + +public class VaultAgentContainer implements TestConstants { + + private final GenericContainer container; + + /** + * Establishes a running Docker container, hosting a Vault agent instance. + */ + public VaultAgentContainer( + Path roleId, + Path secretId) { + container = new GenericContainer("vault:1.2.1") + .withNetwork(CONTAINER_NETWORK) + .withClasspathResourceMapping("/agent.hcl", AGENT_CONFIG_FILE, BindMode.READ_ONLY) + .withFileSystemBind(SSL_DIRECTORY, CONTAINER_SSL_DIRECTORY, BindMode.READ_ONLY) + .withCreateContainerCmdModifier(new Consumer() { + @Override + public void accept(final CreateContainerCmd createContainerCmd) { + createContainerCmd.withCapAdd(Capability.IPC_LOCK); + } + }) + .withCopyFileToContainer(forHostPath(roleId), "/home/vault/role_id") + .withCopyFileToContainer(forHostPath(secretId), "/home/vault/secret_id") + .withExposedPorts(8100) + .withEnv("VAULT_CACERT", CONTAINER_CERT_PEMFILE) + .withCommand(String.format("vault agent -config=%s", AGENT_CONFIG_FILE)) + .waitingFor(Wait.forLogMessage(".*renewed auth token.*", 1)); + container.start(); + } + + /** + * Constructs an instance of the Vault driver, using sensible defaults. + * + * @return + * @throws VaultException + */ + public Vault getVault() throws VaultException { + final VaultConfig config = + new VaultConfig() + .address(getAddress()) + .openTimeout(5) + .readTimeout(30) + .build(); + return new Vault(config); + } + + /** + * The Docker container uses bridged networking. Meaning that Vault listens on port 8200 inside the container, + * but the tests running on the host machine cannot reach that port directly. Instead, the Vault connection + * config has to use a port that is mapped to the container's port 8200. There is no telling what the mapped + * port will be until runtime, so this method is necessary to build a Vault connection URL with the appropriate + * values. + * + * @return The URL of the Vault instance + */ + public String getAddress() { + return String.format("http://%s:%d", container.getContainerIpAddress(), container.getMappedPort(8100)); + } +} diff --git a/src/test-integration/java/com/bettercloud/vault/util/VaultContainer.java b/src/test-integration/java/com/bettercloud/vault/util/VaultContainer.java index e45e24df..47bc49ef 100644 --- a/src/test-integration/java/com/bettercloud/vault/util/VaultContainer.java +++ b/src/test-integration/java/com/bettercloud/vault/util/VaultContainer.java @@ -39,9 +39,12 @@ public class VaultContainer implements TestRule, TestConstants { */ public VaultContainer() { container = new GenericContainer("vault:1.1.3") + .withNetwork(CONTAINER_NETWORK) + .withNetworkAliases("vault") .withClasspathResourceMapping("/startup.sh", CONTAINER_STARTUP_SCRIPT, BindMode.READ_ONLY) .withClasspathResourceMapping("/config.json", CONTAINER_CONFIG_FILE, BindMode.READ_ONLY) .withClasspathResourceMapping("/libressl.conf", CONTAINER_OPENSSL_CONFIG_FILE, BindMode.READ_ONLY) + .withClasspathResourceMapping("/approlePolicy.hcl", APPROLE_POLICY_FILE, BindMode.READ_ONLY) .withFileSystemBind(SSL_DIRECTORY, CONTAINER_SSL_DIRECTORY, BindMode.READ_WRITE) .withCreateContainerCmdModifier(new Consumer() { // TODO: Why does the compiler freak out when this anonymous class is converted to a lambda? @@ -158,9 +161,10 @@ public void setupBackendUserPass() throws IOException, InterruptedException { public void setupBackendAppRole() throws IOException, InterruptedException { runCommand("vault", "login", "-ca-cert=" + CONTAINER_CERT_PEMFILE, rootToken); + runCommand("vault", "policy", "write", "-ca-cert=" + CONTAINER_CERT_PEMFILE, "testerrole", APPROLE_POLICY_FILE); runCommand("vault", "auth", "enable", "-ca-cert=" + CONTAINER_CERT_PEMFILE, "approle"); runCommand("vault", "write", "-ca-cert=" + CONTAINER_CERT_PEMFILE, "auth/approle/role/testrole", - "secret_id_ttl=10m", "token_ttl=20m", "token_max_ttl=30m", "secret_id_num_uses=40"); + "secret_id_ttl=10m", "token_ttl=20m", "token_max_ttl=30m", "secret_id_num_uses=40", "policies=testerrole"); } /** @@ -205,7 +209,7 @@ public void setEngineVersions() throws IOException, InterruptedException { runCommand("vault", "secrets", "enable", "-ca-cert=" + CONTAINER_CERT_PEMFILE, "-path=kv-v1", "-version=1", "kv"); runCommand("vault", "secrets", "enable", "-ca-cert=" + CONTAINER_CERT_PEMFILE, "-path=kv-v1-Upgrade-Test", "-version=1", "kv"); } - + /** *

Constructs an instance of the Vault driver, providing maximum flexibility to control all options * explicitly.

diff --git a/src/test-integration/resources/agent.hcl b/src/test-integration/resources/agent.hcl new file mode 100644 index 00000000..1c618ece --- /dev/null +++ b/src/test-integration/resources/agent.hcl @@ -0,0 +1,27 @@ +pid_file = "/tmp/agent_pidfile" + +vault { + address = "https://vault:8200" +} +auto_auth { + method { + type = "approle" + config = { + role_id_file_path = "/home/vault/role_id" + secret_id_file_path = "/home/vault/secret_id" + } + } + sink { + type = "file" + config = { + path = "/tmp/file-foo" + } + } +} +cache { + use_auto_auth_token = true +} +listener "tcp" { + address = "0.0.0.0:8100" + tls_disable = true +} diff --git a/src/test-integration/resources/approlePolicy.hcl b/src/test-integration/resources/approlePolicy.hcl new file mode 100644 index 00000000..d4e800ef --- /dev/null +++ b/src/test-integration/resources/approlePolicy.hcl @@ -0,0 +1,25 @@ +path "secret/*" { + capabilities = [ + "create", + "read", + "list"] +} + +path "kv-v1/*" { + capabilities = [ + "create", + "read", + "list"] +} + +path "kv-v2/*" { + capabilities = [ + "create", + "read", + "list"] +} + +path "auth/token/lookup-self" { + capabilities = [ + "read"] +} diff --git a/src/test-integration/resources/libressl.conf b/src/test-integration/resources/libressl.conf index 36006b1d..6b90efb9 100644 --- a/src/test-integration/resources/libressl.conf +++ b/src/test-integration/resources/libressl.conf @@ -25,7 +25,11 @@ organizationalUnitName = optional [ myca_extensions ] basicConstraints = CA:false subjectKeyIdentifier = hash -subjectAltName = IP:127.0.0.1 +subjectAltName = @alt_names keyUsage = digitalSignature,keyEncipherment extendedKeyUsage = serverAuth +[alt_names] +IP.1 = 127.0.0.1 +DNS.1 = vault +DNS.2 = localhost