diff --git a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java index 9d37749..93ae869 100644 --- a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java +++ b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java @@ -626,7 +626,7 @@ public class HTTPVaultConnector implements VaultConnector { } @Override - public final SecretResponse readSecretVersion(final String key, final Integer version) throws VaultConnectorException { + public final SecretResponse readSecretVersion(final String mount, final String key, final Integer version) throws VaultConnectorException { if (!isAuthorized()) { throw new AuthorizationRequiredException(); } @@ -636,7 +636,7 @@ public class HTTPVaultConnector implements VaultConnector { if (version != null) { args.put("version", version.toString()); } - String response = requestGet(PATH_SECRET + PATH_DATA + key, args); + String response = requestGet(mount + PATH_DATA + key, args); return jsonMapper.readValue(response, SecretResponse.class); } catch (IOException e) { throw new InvalidResponseException(Error.PARSE_RESPONSE, e); @@ -647,13 +647,13 @@ public class HTTPVaultConnector implements VaultConnector { } @Override - public final MetadataResponse readSecretMetadata(final String key) throws VaultConnectorException { + public final MetadataResponse readSecretMetadata(final String mount, final String key) throws VaultConnectorException { if (!isAuthorized()) { throw new AuthorizationRequiredException(); } /* Request HTTP response and parse secret metadata */ try { - String response = requestGet(PATH_SECRET + PATH_METADATA + key, new HashMap<>()); + String response = requestGet(mount + PATH_METADATA + key, new HashMap<>()); return jsonMapper.readValue(response, MetadataResponse.class); } catch (IOException e) { throw new InvalidResponseException(Error.PARSE_RESPONSE, e); @@ -723,40 +723,41 @@ public class HTTPVaultConnector implements VaultConnector { } @Override - public final void deleteLatestSecretVersion(final String key) throws VaultConnectorException { - delete(PATH_SECRET + PATH_DATA + key); + public final void deleteLatestSecretVersion(final String mount, final String key) throws VaultConnectorException { + delete(mount + PATH_DATA + key); } @Override - public final void deleteAllSecretVersions(final String key) throws VaultConnectorException { - delete(PATH_SECRET + PATH_METADATA + key); + public final void deleteAllSecretVersions(final String mount, final String key) throws VaultConnectorException { + delete(mount + PATH_METADATA + key); } @Override - public final void deleteSecretVersions(final String key, final int... versions) throws VaultConnectorException { - handleSecretVersions(PATH_DELETE, key, versions); + public final void deleteSecretVersions(final String mount, final String key, final int... versions) throws VaultConnectorException { + handleSecretVersions(mount, PATH_DELETE, key, versions); } @Override - public final void undeleteSecretVersions(final String key, final int... versions) throws VaultConnectorException { - handleSecretVersions(PATH_UNDELETE, key, versions); + public final void undeleteSecretVersions(final String mount, final String key, final int... versions) throws VaultConnectorException { + handleSecretVersions(mount, PATH_UNDELETE, key, versions); } @Override - public final void destroySecretVersions(final String key, final int... versions) throws VaultConnectorException { - handleSecretVersions(PATH_DESTROY, key, versions); + public final void destroySecretVersions(final String mount, final String key, final int... versions) throws VaultConnectorException { + handleSecretVersions(mount, PATH_DESTROY, key, versions); } /** * Common method to bundle secret version operations. * + * @param mount Secret store mountpoint (without leading or trailing slash). * @param pathPart Path part to query. * @param key Secret key. * @param versions Versions to handle. * @throws VaultConnectorException on error * @since 0.8 */ - private void handleSecretVersions(final String pathPart, final String key, final int... versions) throws VaultConnectorException { + private void handleSecretVersions(final String mount, final String pathPart, final String key, final int... versions) throws VaultConnectorException { if (!isAuthorized()) { throw new AuthorizationRequiredException(); } @@ -764,7 +765,7 @@ public class HTTPVaultConnector implements VaultConnector { /* Request HTTP response and expect empty result */ Map payload = new HashMap<>(); payload.put("versions", versions); - String response = requestPost(PATH_SECRET + pathPart + key, payload); + String response = requestPost(mount + pathPart + key, payload); /* Response should be code 204 without content */ if (!response.isEmpty()) { diff --git a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java index ea99e6b..ce02b60 100644 --- a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java +++ b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java @@ -355,7 +355,7 @@ public interface VaultConnector extends AutoCloseable, Serializable { * @return {@code true} on success * @throws VaultConnectorException on error * @deprecated As of Vault 0.6.1 App-ID is superseded by AppRole. - * Consider using {@link #createAppRoleSecret} instead. + * Consider using {@link #createAppRoleSecret} instead. */ @Deprecated boolean registerUserId(final String appID, final String userID) throws VaultConnectorException; @@ -421,6 +421,20 @@ public interface VaultConnector extends AutoCloseable, Serializable { return readSecretVersion(key, null); } + /** + * Retrieve the latest secret data for specific version from Vault. + * Prefix "secret/data" is automatically added to key. Only available for KV v2 secrets. + * + * @param mount Secret store mountpoint (without leading or trailing slash). + * @param key Secret identifier + * @return Secret response + * @throws VaultConnectorException on error + * @since 0.8 + */ + default SecretResponse readSecretData(final String mount, final String key) throws VaultConnectorException { + return readSecretVersion(mount, key, null); + } + /** * Retrieve secret data from Vault. * Prefix "secret/data" is automatically added to key. Only available for KV v2 secrets. @@ -431,7 +445,22 @@ public interface VaultConnector extends AutoCloseable, Serializable { * @throws VaultConnectorException on error * @since 0.8 */ - SecretResponse readSecretVersion(final String key, final Integer version) throws VaultConnectorException; + default SecretResponse readSecretVersion(final String key, final Integer version) throws VaultConnectorException { + return readSecretVersion(PATH_SECRET, key, version); + } + + /** + * Retrieve secret data from Vault. + * Prefix "secret/data" is automatically added to key. Only available for KV v2 secrets. + * + * @param mount Secret store mountpoint (without leading or trailing slash). + * @param key Secret identifier + * @param version Version to read. If {@code null} or zero, the latest version will be returned. + * @return Secret response + * @throws VaultConnectorException on error + * @since 0.8 + */ + SecretResponse readSecretVersion(final String mount, final String key, final Integer version) throws VaultConnectorException; /** * Retrieve secret metadata from Vault. @@ -442,7 +471,21 @@ public interface VaultConnector extends AutoCloseable, Serializable { * @throws VaultConnectorException on error * @since 0.8 */ - MetadataResponse readSecretMetadata(final String key) throws VaultConnectorException; + default MetadataResponse readSecretMetadata(final String key) throws VaultConnectorException { + return readSecretMetadata(PATH_SECRET, key); + } + + /** + * Retrieve secret metadata from Vault. + * Prefix "secret/metadata" is automatically added to key. Only available for KV v2 secrets. + * + * @param mount Secret store mountpoint (without leading or trailing slash). + * @param key Secret identifier + * @return Metadata response + * @throws VaultConnectorException on error + * @since 0.8 + */ + MetadataResponse readSecretMetadata(final String mount, final String key) throws VaultConnectorException; /** * List available nodes from Vault. @@ -557,21 +600,47 @@ public interface VaultConnector extends AutoCloseable, Serializable { * Delete latest version of a secret from Vault. * Only available for KV v2 stores. * - * @param key Secret path. + * @param key Secret path. * @throws VaultConnectorException on error * @since 0.8 */ - void deleteLatestSecretVersion(final String key) throws VaultConnectorException; + default void deleteLatestSecretVersion(final String key) throws VaultConnectorException { + deleteLatestSecretVersion(PATH_SECRET, key); + } /** * Delete latest version of a secret from Vault. * Only available for KV v2 stores. * - * @param key Secret path. + * @param mount Secret store mountpoint (without leading or trailing slash). + * @param key Secret path. * @throws VaultConnectorException on error * @since 0.8 */ - void deleteAllSecretVersions(final String key) throws VaultConnectorException; + void deleteLatestSecretVersion(final String mount, final String key) throws VaultConnectorException; + + /** + * Delete latest version of a secret from Vault. + * Only available for KV v2 stores. + * + * @param key Secret path. + * @throws VaultConnectorException on error + * @since 0.8 + */ + default void deleteAllSecretVersions(final String key) throws VaultConnectorException { + deleteAllSecretVersions(PATH_SECRET, key); + } + + /** + * Delete latest version of a secret from Vault. + * Only available for KV v2 stores. + * + * @param mount Secret store mountpoint (without leading or trailing slash). + * @param key Secret path. + * @throws VaultConnectorException on error + * @since 0.8 + */ + void deleteAllSecretVersions(final String mount, final String key) throws VaultConnectorException; /** * Delete secret versions from Vault. @@ -582,7 +651,21 @@ public interface VaultConnector extends AutoCloseable, Serializable { * @throws VaultConnectorException on error * @since 0.8 */ - void deleteSecretVersions(final String key, final int... versions) throws VaultConnectorException; + default void deleteSecretVersions(final String key, final int... versions) throws VaultConnectorException { + deleteSecretVersions(PATH_SECRET, key, versions); + } + + /** + * Delete secret versions from Vault. + * Only available for KV v2 stores. + * + * @param mount Secret store mountpoint (without leading or trailing slash). + * @param key Secret path. + * @param versions Versions of the secret to delete. + * @throws VaultConnectorException on error + * @since 0.8 + */ + void deleteSecretVersions(final String mount, final String key, final int... versions) throws VaultConnectorException; /** * Undelete (restore) secret versions from Vault. @@ -593,7 +676,21 @@ public interface VaultConnector extends AutoCloseable, Serializable { * @throws VaultConnectorException on error * @since 0.8 */ - void undeleteSecretVersions(final String key, final int... versions) throws VaultConnectorException; + default void undeleteSecretVersions(final String key, final int... versions) throws VaultConnectorException { + undeleteSecretVersions(PATH_SECRET, key, versions); + } + + /** + * Undelete (restore) secret versions from Vault. + * Only available for KV v2 stores. + * + * @param mount Secret store mountpoint (without leading or trailing slash). + * @param key Secret path. + * @param versions Versions of the secret to undelete. + * @throws VaultConnectorException on error + * @since 0.8 + */ + void undeleteSecretVersions(final String mount, final String key, final int... versions) throws VaultConnectorException; /** * Destroy secret versions from Vault. @@ -604,7 +701,21 @@ public interface VaultConnector extends AutoCloseable, Serializable { * @throws VaultConnectorException on error * @since 0.8 */ - void destroySecretVersions(final String key, final int... versions) throws VaultConnectorException; + default void destroySecretVersions(final String key, final int... versions) throws VaultConnectorException { + destroySecretVersions(PATH_SECRET, key, versions); + } + + /** + * Destroy secret versions from Vault. + * Only available for KV v2 stores. + * + * @param mount Secret store mountpoint (without leading or trailing slash). + * @param key Secret path. + * @param versions Versions of the secret to destroy. + * @throws VaultConnectorException on error + * @since 0.8 + */ + void destroySecretVersions(final String mount, final String key, final int... versions) throws VaultConnectorException; /** * Revoke given lease immediately. @@ -676,7 +787,7 @@ public interface VaultConnector extends AutoCloseable, Serializable { /** * Read credentials for MySQL backend at default mount point. * - * @param role the role name + * @param role the role name * @return the credentials response * @throws VaultConnectorException on error * @since 0.5.0 @@ -688,7 +799,7 @@ public interface VaultConnector extends AutoCloseable, Serializable { /** * Read credentials for PostgreSQL backend at default mount point. * - * @param role the role name + * @param role the role name * @return the credentials response * @throws VaultConnectorException on error * @since 0.5.0 @@ -700,7 +811,7 @@ public interface VaultConnector extends AutoCloseable, Serializable { /** * Read credentials for MSSQL backend at default mount point. * - * @param role the role name + * @param role the role name * @return the credentials response * @throws VaultConnectorException on error * @since 0.5.0 @@ -712,7 +823,7 @@ public interface VaultConnector extends AutoCloseable, Serializable { /** * Read credentials for MSSQL backend at default mount point. * - * @param role the role name + * @param role the role name * @return the credentials response * @throws VaultConnectorException on error * @since 0.5.0 diff --git a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java index c22da52..a9f03b8 100644 --- a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java +++ b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java @@ -78,7 +78,7 @@ public class HTTPVaultConnectorTest { private static final String SECRET_KEY_COMPLEX = "complex"; // KV v2 secret with 2 versions. - private static final String PATH_KV2 = "kv/"; + private static final String MOUNT_KV2 = "kv"; private static final String SECRET2_KEY = "foo2"; private static final String SECRET2_VALUE1 = "bar2"; private static final String SECRET2_VALUE2 = "bar3"; @@ -748,7 +748,7 @@ public class HTTPVaultConnectorTest { // Try to read accessible path with known value. SecretResponse res; try { - res = connector.readSecretData(SECRET2_KEY); + res = connector.readSecretData(MOUNT_KV2, SECRET2_KEY); assertThat("Metadata not populated for KV v2 secret", res.getMetadata(), is(notNullValue())); assertThat("Unexpected secret version", res.getMetadata().getVersion(), is(2)); assertThat("Known secret returned invalid value.", res.getValue(), is(SECRET2_VALUE2)); @@ -758,7 +758,7 @@ public class HTTPVaultConnectorTest { // Try to read different version of same secret. try { - res = connector.readSecretVersion(SECRET2_KEY, 1); + res = connector.readSecretVersion(MOUNT_KV2, SECRET2_KEY, 1); assertThat("Unexpected secret version", res.getMetadata().getVersion(), is(1)); assertThat("Known secret returned invalid value.", res.getValue(), is(SECRET2_VALUE1)); } catch (VaultConnectorException e) { @@ -776,7 +776,7 @@ public class HTTPVaultConnectorTest { // Try to read accessible path with known value. try { - MetadataResponse res = connector.readSecretMetadata(SECRET2_KEY); + MetadataResponse res = connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY); assertThat("Metadata not populated for KV v2 secret", res.getMetadata(), is(notNullValue())); assertThat("Unexpected secret version", res.getMetadata().getCurrentVersion(), is(2)); assertThat("Unexpected number of secret versions", res.getMetadata().getVersions().size(), is(2)); @@ -798,16 +798,16 @@ public class HTTPVaultConnectorTest { // Try to delete inexisting versions. MetadataResponse meta; try { - connector.deleteSecretVersions(SECRET2_KEY, 5, 42); - meta = connector.readSecretMetadata(SECRET2_KEY); + connector.deleteSecretVersions(MOUNT_KV2, SECRET2_KEY, 5, 42); + meta = connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY); } catch (VaultConnectorException e) { fail("Revealed non-existence of secret versions"); } // Now delete existing version and verify. try { - connector.deleteSecretVersions(SECRET2_KEY, 1); - meta = connector.readSecretMetadata(SECRET2_KEY); + connector.deleteSecretVersions(MOUNT_KV2, SECRET2_KEY, 1); + meta = connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY); assertThat("Expected deletion time for secret 1", meta.getMetadata().getVersions().get(1).getDeletionTime(), is(notNullValue())); } catch (VaultConnectorException e) { fail("Deleting existing version failed"); @@ -815,8 +815,8 @@ public class HTTPVaultConnectorTest { // Undelete the just deleted version. try { - connector.undeleteSecretVersions(SECRET2_KEY, 1); - meta = connector.readSecretMetadata(SECRET2_KEY); + connector.undeleteSecretVersions(MOUNT_KV2, SECRET2_KEY, 1); + meta = connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY); assertThat("Expected deletion time for secret 1 to be reset", meta.getMetadata().getVersions().get(1).getDeletionTime(), is(nullValue())); } catch (VaultConnectorException e) { fail("Undeleting existing version failed"); @@ -824,8 +824,8 @@ public class HTTPVaultConnectorTest { // Now destroy it. try { - connector.destroySecretVersions(SECRET2_KEY, 1); - meta = connector.readSecretMetadata(SECRET2_KEY); + connector.destroySecretVersions(MOUNT_KV2, SECRET2_KEY, 1); + meta = connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY); assertThat("Expected secret 1 to be marked destroyed", meta.getMetadata().getVersions().get(1).isDestroyed(), is(true)); } catch (VaultConnectorException e) { fail("Destroying existing version failed"); @@ -833,8 +833,8 @@ public class HTTPVaultConnectorTest { // Delete latest version. try { - connector.deleteLatestSecretVersion(SECRET2_KEY); - meta = connector.readSecretMetadata(SECRET2_KEY); + connector.deleteLatestSecretVersion(MOUNT_KV2, SECRET2_KEY); + meta = connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY); assertThat("Expected secret 2 to be deleted", meta.getMetadata().getVersions().get(2).getDeletionTime(), is(notNullValue())); } catch (VaultConnectorException e) { fail("Deleting latest version failed"); @@ -842,12 +842,12 @@ public class HTTPVaultConnectorTest { // Delete all versions. try { - connector.deleteAllSecretVersions(SECRET2_KEY); + connector.deleteAllSecretVersions(MOUNT_KV2, SECRET2_KEY); } catch (VaultConnectorException e) { fail("Deleting latest version failed: " + e.getMessage()); } try { - connector.readSecretMetadata(SECRET2_KEY); + connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY); fail("Reading metadata of deleted secret should not succeed"); } catch (Exception e) { assertThat(e, is(instanceOf(InvalidResponseException.class)));