diff --git a/README.md b/README.md index d7569f9..de8891b 100644 --- a/README.md +++ b/README.md @@ -109,11 +109,11 @@ Token token = Token.builder() .withDisplayName("new test token") .withPolicies("pol1", "pol2") .build(); -vault.createToken(token); +vault.token().create(token); // Create AppRole credentials -vault.createAppRole("testrole", policyList); -AppRoleSecretResponse secret = vault.createAppRoleSecret("testrole"); +vault.appRole().create("testrole", policyList); +AppRoleSecretResponse secret = vault.appRole().createSecret("testrole"); ``` ## Links diff --git a/pom.xml b/pom.xml index ab5fd50..7d59b43 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ de.stklcode.jvault jvault-connector - 1.5.3-SNAPSHOT + 2.0.0-SNAPSHOT jar diff --git a/src/main/java/de/stklcode/jvault/connector/AppRoleClient.java b/src/main/java/de/stklcode/jvault/connector/AppRoleClient.java new file mode 100644 index 0000000..58dbfa9 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/AppRoleClient.java @@ -0,0 +1,217 @@ +/* + * Copyright 2016-2025 Stefan Kalscheuer + * + * 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 de.stklcode.jvault.connector; + +import de.stklcode.jvault.connector.exception.VaultConnectorException; +import de.stklcode.jvault.connector.model.AppRole; +import de.stklcode.jvault.connector.model.AppRoleSecret; +import de.stklcode.jvault.connector.model.Token; +import de.stklcode.jvault.connector.model.TokenRole; +import de.stklcode.jvault.connector.model.response.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * AppRole client interface. + * Provides methods to interact with Vault's AppRole API. + * + * @since 2.0.0 extracted from {@link VaultConnector} + */ +public interface AppRoleClient { + + /** + * Register a new AppRole role from given metamodel. + * + * @param role The role + * @return {@code true} on success + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + boolean create(final AppRole role) throws VaultConnectorException; + + /** + * Register new AppRole role with default policy. + * + * @param roleName The role name + * @return {@code true} on success + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + default boolean create(final String roleName) throws VaultConnectorException { + return create(roleName, new ArrayList<>()); + } + + /** + * Register new AppRole role with policies. + * + * @param roleName The role name + * @param policies The policies to associate with + * @return {@code true} on success + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + default boolean create(final String roleName, final List policies) throws VaultConnectorException { + return create(roleName, policies, null); + } + + /** + * Register new AppRole role with default policy and custom ID. + * + * @param roleName The role name + * @param roleID A custom role ID + * @return {@code true} on success + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + default boolean create(final String roleName, final String roleID) throws VaultConnectorException { + return create(roleName, new ArrayList<>(), roleID); + } + + /** + * Register new AppRole role with policies and custom ID. + * + * @param roleName The role name + * @param policies The policies to associate with + * @param roleID A custom role ID + * @return {@code true} on success + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + default boolean create(final String roleName, final List policies, final String roleID) + throws VaultConnectorException { + return create(AppRole.builder(roleName).withTokenPolicies(policies).withId(roleID).build()); + } + + /** + * Delete AppRole role from Vault. + * + * @param roleName The role name + * @return {@code true} on success + * @throws VaultConnectorException on error + */ + boolean delete(final String roleName) throws VaultConnectorException; + + /** + * Lookup an AppRole role. + * + * @param roleName The role name + * @return Result of the lookup + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + AppRoleResponse lookup(final String roleName) throws VaultConnectorException; + + /** + * Retrieve ID for an AppRole role. + * + * @param roleName The role name + * @return The role ID + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + String getRoleID(final String roleName) throws VaultConnectorException; + + /** + * Set custom ID for an AppRole role. + * + * @param roleName The role name + * @param roleID The role ID + * @return {@code true} on success + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + boolean setRoleID(final String roleName, final String roleID) throws VaultConnectorException; + + /** + * Register new random generated AppRole secret. + * + * @param roleName The role name + * @return The secret ID + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + default AppRoleSecretResponse createSecret(final String roleName) throws VaultConnectorException { + return createSecret(roleName, new AppRoleSecret()); + } + + /** + * Register new AppRole secret with custom ID. + * + * @param roleName The role name + * @param secretID A custom secret ID + * @return The secret ID + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + default AppRoleSecretResponse createSecret(final String roleName, final String secretID) + throws VaultConnectorException { + return createSecret(roleName, new AppRoleSecret(secretID)); + } + + /** + * Register new AppRole secret with custom ID. + * + * @param roleName The role name + * @param secret The secret meta object + * @return The secret ID + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + AppRoleSecretResponse createSecret(final String roleName, final AppRoleSecret secret) + throws VaultConnectorException; + + /** + * Lookup an AppRole secret. + * + * @param roleName The role name + * @param secretID The secret ID + * @return Result of the lookup + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + AppRoleSecretResponse lookupSecret(final String roleName, final String secretID) + throws VaultConnectorException; + + /** + * Destroy an AppRole secret. + * + * @param roleName The role name + * @param secretID The secret meta object + * @return The secret ID + * @throws VaultConnectorException on error + * @since 0.4.0 + */ + boolean destroySecret(final String roleName, final String secretID) throws VaultConnectorException; + + /** + * List existing (accessible) AppRole roles. + * + * @return List of roles + * @throws VaultConnectorException on error + */ + List listRoles() throws VaultConnectorException; + + /** + * List existing (accessible) secret IDs for AppRole role. + * + * @param roleName The role name + * @return List of roles + * @throws VaultConnectorException on error + */ + List listSecrets(final String roleName) throws VaultConnectorException; +} diff --git a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java index 33c23e8..e65264b 100644 --- a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java +++ b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java @@ -106,55 +106,11 @@ public class HTTPVaultConnector implements VaultConnector { authorized = false; } - @Override - public final SealResponse sealStatus() throws VaultConnectorException { - return request.get(SYS_SEAL_STATUS, emptyMap(), token, SealResponse.class); - } - - @Override - public final void seal() throws VaultConnectorException { - request.put(SYS_SEAL, emptyMap(), token); - } - - @Override - public final SealResponse unseal(final String key, final Boolean reset) throws VaultConnectorException { - Map param = mapOfStrings( - "key", key, - "reset", reset - ); - - return request.put(SYS_UNSEAL, param, token, SealResponse.class); - } - - @Override - public HealthResponse getHealth() throws VaultConnectorException { - - return request.get( - SYS_HEALTH, - // Force status code to be 200, so we don't need to modify the request sequence. - Map.of( - "standbycode", "200", // Default: 429. - "sealedcode", "200", // Default: 503. - "uninitcode", "200" // Default: 501. - ), - token, - HealthResponse.class - ); - } - @Override public final boolean isAuthorized() { return authorized && (tokenTTL == 0 || tokenTTL >= System.currentTimeMillis()); } - @Override - public final List getAuthBackends() throws VaultConnectorException { - /* Issue request and parse response */ - AuthMethodsResponse amr = request.get(SYS_AUTH, emptyMap(), token, AuthMethodsResponse.class); - - return amr.getSupportedMethods().values().stream().map(AuthMethod::getType).collect(Collectors.toList()); - } - @Override public final TokenResponse authToken(final String token) throws VaultConnectorException { /* set token */ @@ -202,142 +158,6 @@ public class HTTPVaultConnector implements VaultConnector { return auth; } - @Override - public final boolean createAppRole(final AppRole role) throws VaultConnectorException { - requireAuth(); - - /* Issue request and expect code 204 with empty response */ - request.postWithoutResponse(String.format(AUTH_APPROLE_ROLE, role.getName(), ""), role, token); - - /* Set custom ID if provided */ - return !(role.getId() != null && !role.getId().isEmpty()) || setAppRoleID(role.getName(), role.getId()); - } - - @Override - public final AppRoleResponse lookupAppRole(final String roleName) throws VaultConnectorException { - requireAuth(); - /* Request HTTP response and parse Secret */ - return request.get( - String.format(AUTH_APPROLE_ROLE, roleName, ""), - emptyMap(), - token, - AppRoleResponse.class - ); - } - - @Override - public final boolean deleteAppRole(final String roleName) throws VaultConnectorException { - requireAuth(); - - /* Issue request and expect code 204 with empty response */ - request.deleteWithoutResponse(String.format(AUTH_APPROLE_ROLE, roleName, ""), token); - - return true; - } - - @Override - public final String getAppRoleID(final String roleName) throws VaultConnectorException { - requireAuth(); - /* Issue request, parse response and extract Role ID */ - return request.get( - String.format(AUTH_APPROLE_ROLE, roleName, "/role-id"), - emptyMap(), - token, - RawDataResponse.class - ).getData().get("role_id").toString(); - } - - @Override - public final boolean setAppRoleID(final String roleName, final String roleID) throws VaultConnectorException { - requireAuth(); - - /* Issue request and expect code 204 with empty response */ - request.postWithoutResponse( - String.format(AUTH_APPROLE_ROLE, roleName, "/role-id"), - singletonMap("role_id", roleID), - token - ); - - return true; - } - - @Override - public final AppRoleSecretResponse createAppRoleSecret(final String roleName, final AppRoleSecret secret) - throws VaultConnectorException { - requireAuth(); - - if (secret.getId() != null && !secret.getId().isEmpty()) { - return request.post( - String.format(AUTH_APPROLE_ROLE, roleName, "/custom-secret-id"), - secret, - token, - AppRoleSecretResponse.class - ); - } else { - return request.post( - String.format(AUTH_APPROLE_ROLE, roleName, "/secret-id"), - secret, token, - AppRoleSecretResponse.class - ); - } - } - - @Override - public final AppRoleSecretResponse lookupAppRoleSecret(final String roleName, final String secretID) - throws VaultConnectorException { - requireAuth(); - - /* Issue request and parse secret response */ - return request.post( - String.format(AUTH_APPROLE_ROLE, roleName, "/secret-id/lookup"), - new AppRoleSecret(secretID), - token, - AppRoleSecretResponse.class - ); - } - - @Override - public final boolean destroyAppRoleSecret(final String roleName, final String secretID) - throws VaultConnectorException { - requireAuth(); - - /* Issue request and expect code 204 with empty response */ - request.postWithoutResponse( - String.format(AUTH_APPROLE_ROLE, roleName, "/secret-id/destroy"), - new AppRoleSecret(secretID), - token); - - return true; - } - - @Override - public final List listAppRoles() throws VaultConnectorException { - requireAuth(); - - SecretListResponse secrets = request.get( - AUTH_APPROLE + "/role?list=true", - emptyMap(), - token, - SecretListResponse.class - ); - - return secrets.getKeys(); - } - - @Override - public final List listAppRoleSecrets(final String roleName) throws VaultConnectorException { - requireAuth(); - - SecretListResponse secrets = request.get( - String.format(AUTH_APPROLE_ROLE, roleName, "/secret-id?list=true"), - emptyMap(), - token, - SecretListResponse.class - ); - - return secrets.getKeys(); - } - @Override public final SecretResponse read(final String key) throws VaultConnectorException { requireAuth(); @@ -345,66 +165,6 @@ public class HTTPVaultConnector implements VaultConnector { return request.get(key, emptyMap(), token, PlainSecretResponse.class); } - @Override - public final SecretResponse readSecretVersion(final String mount, final String key, final Integer version) - throws VaultConnectorException { - requireAuth(); - /* Request HTTP response and parse secret metadata */ - Map args = mapOfStrings("version", version); - - return request.get(mount + SECRET_DATA + key, args, token, MetaSecretResponse.class); - } - - @Override - public final MetadataResponse readSecretMetadata(final String mount, final String key) - throws VaultConnectorException { - requireAuth(); - - /* Request HTTP response and parse secret metadata */ - return request.get(mount + SECRET_METADATA + key, emptyMap(), token, MetadataResponse.class); - } - - @Override - public void updateSecretMetadata(final String mount, - final String key, - final Integer maxVersions, - final boolean casRequired) throws VaultConnectorException { - requireAuth(); - - Map payload = mapOf( - "max_versions", maxVersions, - "cas_required", casRequired - ); - - write(mount + SECRET_METADATA + key, payload); - } - - @Override - public final SecretVersionResponse writeSecretData(final String mount, - final String key, - final Map data, - final Integer cas) throws VaultConnectorException { - requireAuth(); - - if (key == null || key.isEmpty()) { - throw new InvalidRequestException("Secret path must not be empty."); - } - - // Add CAS value to options map if present. - Map options = mapOf("cas", cas); - - /* Issue request and parse metadata response */ - return request.post( - mount + SECRET_DATA + key, - Map.of( - "data", data, - "options", options - ), - token, - SecretVersionResponse.class - ); - } - @Override public final List list(final String path) throws VaultConnectorException { requireAuth(); @@ -446,57 +206,6 @@ public class HTTPVaultConnector implements VaultConnector { request.deleteWithoutResponse(key, token); } - @Override - public final void deleteLatestSecretVersion(final String mount, final String key) throws VaultConnectorException { - delete(mount + SECRET_DATA + key); - } - - @Override - public final void deleteAllSecretVersions(final String mount, final String key) throws VaultConnectorException { - delete(mount + SECRET_METADATA + key); - } - - @Override - public final void deleteSecretVersions(final String mount, final String key, final int... versions) - throws VaultConnectorException { - handleSecretVersions(mount, SECRET_DELETE, key, versions); - } - - @Override - public final void undeleteSecretVersions(final String mount, final String key, final int... versions) - throws VaultConnectorException { - handleSecretVersions(mount, SECRET_UNDELETE, key, versions); - } - - @Override - public final void destroySecretVersions(final String mount, final String key, final int... versions) - throws VaultConnectorException { - handleSecretVersions(mount, SECRET_DESTROY, key, versions); - } - - /** - * Common method to bundle secret version operations. - * - * @param mount Secret store mount point (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 mount, - final String pathPart, - final String key, - final int... versions) throws VaultConnectorException { - requireAuth(); - - /* Request HTTP response and expect empty result */ - Map payload = singletonMap("versions", versions); - - /* Issue request and expect code 204 with empty response */ - request.postWithoutResponse(mount + pathPart + key, payload, token); - } - @Override public final void revoke(final String leaseID) throws VaultConnectorException { requireAuth(); @@ -519,21 +228,28 @@ public class HTTPVaultConnector implements VaultConnector { } @Override - public final AuthResponse createToken(final Token token) throws VaultConnectorException { - return createTokenInternal(token, AUTH_TOKEN + TOKEN_CREATE); + public KV2ClientImpl kv2() { + return new KV2ClientImpl(); } @Override - public final AuthResponse createToken(final Token token, final boolean orphan) throws VaultConnectorException { - return createTokenInternal(token, AUTH_TOKEN + TOKEN_CREATE_ORPHAN); + public TokenClientImpl token() { + return new TokenClientImpl(); } @Override - public final AuthResponse createToken(final Token token, final String role) throws VaultConnectorException { - if (role == null || role.isEmpty()) { - throw new InvalidRequestException("No role name specified."); - } - return createTokenInternal(token, AUTH_TOKEN + TOKEN_CREATE + "/" + role); + public AppRoleClient appRole() { + return new AppRoleClientImpl(); + } + + @Override + public TransitClientImpl transit() { + return new TransitClientImpl(); + } + + @Override + public SysClientImpl sys() { + return new SysClientImpl(); } @Override @@ -543,124 +259,6 @@ public class HTTPVaultConnector implements VaultConnector { tokenTTL = 0; } - /** - * Create token. - * Centralized method to handle different token creation requests. - * - * @param token the token - * @param path request path - * @return the response - * @throws VaultConnectorException on error - */ - private AuthResponse createTokenInternal(final Token token, final String path) throws VaultConnectorException { - requireAuth(); - - if (token == null) { - throw new InvalidRequestException("Token must be provided."); - } - - return request.post(path, token, this.token, AuthResponse.class); - } - - @Override - public final TokenResponse lookupToken(final String token) throws VaultConnectorException { - requireAuth(); - - /* Request HTTP response and parse Secret */ - return request.get( - AUTH_TOKEN + TOKEN_LOOKUP, - singletonMap("token", token), - token, - TokenResponse.class - ); - } - - @Override - public boolean createOrUpdateTokenRole(final String name, final TokenRole role) throws VaultConnectorException { - requireAuth(); - - if (name == null) { - throw new InvalidRequestException("Role name must be provided."); - } else if (role == null) { - throw new InvalidRequestException("Role must be provided."); - } - - // Issue request and expect code 204 with empty response. - request.postWithoutResponse(AUTH_TOKEN + TOKEN_ROLES + "/" + name, role, token); - - return true; - } - - @Override - public TokenRoleResponse readTokenRole(final String name) throws VaultConnectorException { - requireAuth(); - - // Request HTTP response and parse response. - return request.get(AUTH_TOKEN + TOKEN_ROLES + "/" + name, emptyMap(), token, TokenRoleResponse.class); - } - - @Override - public List listTokenRoles() throws VaultConnectorException { - requireAuth(); - - return list(AUTH_TOKEN + TOKEN_ROLES); - } - - @Override - public boolean deleteTokenRole(final String name) throws VaultConnectorException { - requireAuth(); - - if (name == null) { - throw new InvalidRequestException("Role name must be provided."); - } - - // Issue request and expect code 204 with empty response. - request.deleteWithoutResponse(AUTH_TOKEN + TOKEN_ROLES + "/" + name, token); - - return true; - } - - @Override - public final TransitResponse transitEncrypt(final String keyName, final String plaintext) - throws VaultConnectorException { - requireAuth(); - - Map payload = mapOf( - "plaintext", plaintext - ); - - return request.post(TRANSIT_ENCRYPT + keyName, payload, token, TransitResponse.class); - } - - @Override - public final TransitResponse transitDecrypt(final String keyName, final String ciphertext) - throws VaultConnectorException { - requireAuth(); - - Map payload = mapOf( - "ciphertext", ciphertext - ); - - return request.post(TRANSIT_DECRYPT + keyName, payload, token, TransitResponse.class); - } - - @Override - public final TransitResponse transitHash(final String algorithm, final String input, final String format) - throws VaultConnectorException { - if (format != null && !"hex".equals(format) && !"base64".equals(format)) { - throw new IllegalArgumentException("Unsupported format " + format); - } - - requireAuth(); - - Map payload = mapOf( - "input", input, - "format", format - ); - - return request.post(TRANSIT_HASH + algorithm, payload, token, TransitResponse.class); - } - /** * Check for required authorization. * @@ -710,4 +308,459 @@ public class HTTPVaultConnector implements VaultConnector { return map; } + + /** + * Sub-client for KV v2 methods. + */ + public class KV2ClientImpl implements KV2Client { + @Override + public final SecretResponse readVersion(final String mount, final String key, final Integer version) + throws VaultConnectorException { + requireAuth(); + /* Request HTTP response and parse secret metadata */ + Map args = mapOfStrings("version", version); + + return request.get(mount + SECRET_DATA + key, args, token, MetaSecretResponse.class); + } + + @Override + public final MetadataResponse readMetadata(final String mount, final String key) + throws VaultConnectorException { + requireAuth(); + + /* Request HTTP response and parse secret metadata */ + return request.get(mount + SECRET_METADATA + key, emptyMap(), token, MetadataResponse.class); + } + + @Override + public void updateMetadata(final String mount, + final String key, + final Integer maxVersions, + final boolean casRequired) throws VaultConnectorException { + requireAuth(); + + Map payload = mapOf( + "max_versions", maxVersions, + "cas_required", casRequired + ); + + write(mount + SECRET_METADATA + key, payload); + } + + @Override + public final SecretVersionResponse writeData(final String mount, + final String key, + final Map data, + final Integer cas) throws VaultConnectorException { + requireAuth(); + + if (key == null || key.isEmpty()) { + throw new InvalidRequestException("Secret path must not be empty."); + } + + // Add CAS value to options map if present. + Map options = mapOf("cas", cas); + + /* Issue request and parse metadata response */ + return request.post( + mount + SECRET_DATA + key, + Map.of( + "data", data, + "options", options + ), + token, + SecretVersionResponse.class + ); + } + + @Override + public final void deleteLatestVersion(final String mount, final String key) throws VaultConnectorException { + delete(mount + SECRET_DATA + key); + } + + @Override + public final void deleteAllVersions(final String mount, final String key) throws VaultConnectorException { + delete(mount + SECRET_METADATA + key); + } + + @Override + public final void deleteVersions(final String mount, final String key, final int... versions) + throws VaultConnectorException { + handleSecretVersions(mount, SECRET_DELETE, key, versions); + } + + @Override + public final void undeleteVersions(final String mount, final String key, final int... versions) + throws VaultConnectorException { + handleSecretVersions(mount, SECRET_UNDELETE, key, versions); + } + + @Override + public final void destroyVersions(final String mount, final String key, final int... versions) + throws VaultConnectorException { + handleSecretVersions(mount, SECRET_DESTROY, key, versions); + } + + /** + * Common method to bundle secret version operations. + * + * @param mount Secret store mount point (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 mount, + final String pathPart, + final String key, + final int... versions) throws VaultConnectorException { + requireAuth(); + + /* Request HTTP response and expect empty result */ + Map payload = singletonMap("versions", versions); + + /* Issue request and expect code 204 with empty response */ + request.postWithoutResponse(mount + pathPart + key, payload, token); + } + } + + /** + * Sub-client for token methods. + */ + public class TokenClientImpl implements TokenClient { + + @Override + public final AuthResponse create(final Token token) throws VaultConnectorException { + return createInternal(token, AUTH_TOKEN + TOKEN_CREATE); + } + + @Override + public final AuthResponse create(final Token token, final boolean orphan) throws VaultConnectorException { + return createInternal(token, AUTH_TOKEN + TOKEN_CREATE_ORPHAN); + } + + @Override + public final AuthResponse create(final Token token, final String role) throws VaultConnectorException { + if (role == null || role.isEmpty()) { + throw new InvalidRequestException("No role name specified."); + } + return createInternal(token, AUTH_TOKEN + TOKEN_CREATE + "/" + role); + } + + /** + * Create token. + * Centralized method to handle different token creation requests. + * + * @param token the token + * @param path request path + * @return the response + * @throws VaultConnectorException on error + */ + private AuthResponse createInternal(final Token token, final String path) throws VaultConnectorException { + requireAuth(); + + if (token == null) { + throw new InvalidRequestException("Token must be provided."); + } + + return request.post(path, token, HTTPVaultConnector.this.token, AuthResponse.class); + } + + @Override + public final TokenResponse lookup(final String token) throws VaultConnectorException { + requireAuth(); + + /* Request HTTP response and parse Secret */ + return request.get( + AUTH_TOKEN + TOKEN_LOOKUP, + singletonMap("token", token), + token, + TokenResponse.class + ); + } + + @Override + public boolean createOrUpdateRole(final String name, final TokenRole role) throws VaultConnectorException { + requireAuth(); + + if (name == null) { + throw new InvalidRequestException("Role name must be provided."); + } else if (role == null) { + throw new InvalidRequestException("Role must be provided."); + } + + // Issue request and expect code 204 with empty response. + request.postWithoutResponse(AUTH_TOKEN + TOKEN_ROLES + "/" + name, role, token); + + return true; + } + + @Override + public TokenRoleResponse readRole(final String name) throws VaultConnectorException { + requireAuth(); + + // Request HTTP response and parse response. + return request.get(AUTH_TOKEN + TOKEN_ROLES + "/" + name, emptyMap(), token, TokenRoleResponse.class); + } + + @Override + public List listRoles() throws VaultConnectorException { + requireAuth(); + + return list(AUTH_TOKEN + TOKEN_ROLES); + } + + @Override + public boolean deleteRole(final String name) throws VaultConnectorException { + requireAuth(); + + if (name == null) { + throw new InvalidRequestException("Role name must be provided."); + } + + // Issue request and expect code 204 with empty response. + request.deleteWithoutResponse(AUTH_TOKEN + TOKEN_ROLES + "/" + name, token); + + return true; + } + } + + /** + * Sub-client for AppRole methods. + */ + public class AppRoleClientImpl implements AppRoleClient { + + @Override + public final boolean create(final AppRole role) throws VaultConnectorException { + requireAuth(); + + /* Issue request and expect code 204 with empty response */ + request.postWithoutResponse(String.format(AUTH_APPROLE_ROLE, role.getName(), ""), role, token); + + /* Set custom ID if provided */ + return !(role.getId() != null && !role.getId().isEmpty()) || setRoleID(role.getName(), role.getId()); + } + + @Override + public final AppRoleResponse lookup(final String roleName) throws VaultConnectorException { + requireAuth(); + /* Request HTTP response and parse Secret */ + return request.get( + String.format(AUTH_APPROLE_ROLE, roleName, ""), + emptyMap(), + token, + AppRoleResponse.class + ); + } + + @Override + public final boolean delete(final String roleName) throws VaultConnectorException { + requireAuth(); + + /* Issue request and expect code 204 with empty response */ + request.deleteWithoutResponse(String.format(AUTH_APPROLE_ROLE, roleName, ""), token); + + return true; + } + + @Override + public final String getRoleID(final String roleName) throws VaultConnectorException { + requireAuth(); + /* Issue request, parse response and extract Role ID */ + return request.get( + String.format(AUTH_APPROLE_ROLE, roleName, "/role-id"), + emptyMap(), + token, + RawDataResponse.class + ).getData().get("role_id").toString(); + } + + @Override + public final boolean setRoleID(final String roleName, final String roleID) throws VaultConnectorException { + requireAuth(); + + /* Issue request and expect code 204 with empty response */ + request.postWithoutResponse( + String.format(AUTH_APPROLE_ROLE, roleName, "/role-id"), + singletonMap("role_id", roleID), + token + ); + + return true; + } + + @Override + public final AppRoleSecretResponse createSecret(final String roleName, final AppRoleSecret secret) + throws VaultConnectorException { + requireAuth(); + + if (secret.getId() != null && !secret.getId().isEmpty()) { + return request.post( + String.format(AUTH_APPROLE_ROLE, roleName, "/custom-secret-id"), + secret, + token, + AppRoleSecretResponse.class + ); + } else { + return request.post( + String.format(AUTH_APPROLE_ROLE, roleName, "/secret-id"), + secret, token, + AppRoleSecretResponse.class + ); + } + } + + @Override + public final AppRoleSecretResponse lookupSecret(final String roleName, final String secretID) + throws VaultConnectorException { + requireAuth(); + + /* Issue request and parse secret response */ + return request.post( + String.format(AUTH_APPROLE_ROLE, roleName, "/secret-id/lookup"), + new AppRoleSecret(secretID), + token, + AppRoleSecretResponse.class + ); + } + + @Override + public final boolean destroySecret(final String roleName, final String secretID) + throws VaultConnectorException { + requireAuth(); + + /* Issue request and expect code 204 with empty response */ + request.postWithoutResponse( + String.format(AUTH_APPROLE_ROLE, roleName, "/secret-id/destroy"), + new AppRoleSecret(secretID), + token); + + return true; + } + + @Override + public final List listRoles() throws VaultConnectorException { + requireAuth(); + + SecretListResponse secrets = request.get( + AUTH_APPROLE + "/role?list=true", + emptyMap(), + token, + SecretListResponse.class + ); + + return secrets.getKeys(); + } + + @Override + public final List listSecrets(final String roleName) throws VaultConnectorException { + requireAuth(); + + SecretListResponse secrets = request.get( + String.format(AUTH_APPROLE_ROLE, roleName, "/secret-id?list=true"), + emptyMap(), + token, + SecretListResponse.class + ); + + return secrets.getKeys(); + } + } + + /** + * Sub-client for transit methods. + */ + public class TransitClientImpl implements TransitClient { + @Override + public final TransitResponse encrypt(final String keyName, final String plaintext) + throws VaultConnectorException { + requireAuth(); + + Map payload = mapOf( + "plaintext", plaintext + ); + + return request.post(TRANSIT_ENCRYPT + keyName, payload, token, TransitResponse.class); + } + + @Override + public final TransitResponse decrypt(final String keyName, final String ciphertext) + throws VaultConnectorException { + requireAuth(); + + Map payload = mapOf( + "ciphertext", ciphertext + ); + + return request.post(TRANSIT_DECRYPT + keyName, payload, token, TransitResponse.class); + } + + @Override + public final TransitResponse hash(final String algorithm, final String input, final String format) + throws VaultConnectorException { + if (format != null && !"hex".equals(format) && !"base64".equals(format)) { + throw new IllegalArgumentException("Unsupported format " + format); + } + + requireAuth(); + + Map payload = mapOf( + "input", input, + "format", format + ); + + return request.post(TRANSIT_HASH + algorithm, payload, token, TransitResponse.class); + } + } + + /** + * Sub-client for system methods. + */ + public class SysClientImpl implements SysClient { + + @Override + public final SealResponse sealStatus() throws VaultConnectorException { + return request.get(SYS_SEAL_STATUS, emptyMap(), token, SealResponse.class); + } + + @Override + public final void seal() throws VaultConnectorException { + request.put(SYS_SEAL, emptyMap(), token); + } + + @Override + public final SealResponse unseal(final String key, final Boolean reset) throws VaultConnectorException { + Map param = mapOfStrings( + "key", key, + "reset", reset + ); + + return request.put(SYS_UNSEAL, param, token, SealResponse.class); + } + + @Override + public HealthResponse getHealth() throws VaultConnectorException { + + return request.get( + SYS_HEALTH, + // Force status code to be 200, so we don't need to modify the request sequence. + Map.of( + "standbycode", "200", // Default: 429. + "sealedcode", "200", // Default: 503. + "uninitcode", "200" // Default: 501. + ), + token, + HealthResponse.class + ); + } + + @Override + public final List getAuthBackends() throws VaultConnectorException { + /* Issue request and parse response */ + AuthMethodsResponse amr = request.get(SYS_AUTH, emptyMap(), token, AuthMethodsResponse.class); + + return amr.getSupportedMethods().values().stream().map(AuthMethod::getType).collect(Collectors.toList()); + } + } } diff --git a/src/main/java/de/stklcode/jvault/connector/KV2Client.java b/src/main/java/de/stklcode/jvault/connector/KV2Client.java new file mode 100644 index 0000000..e832957 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/KV2Client.java @@ -0,0 +1,200 @@ +/* + * Copyright 2016-2025 Stefan Kalscheuer + * + * 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 de.stklcode.jvault.connector; + +import de.stklcode.jvault.connector.exception.VaultConnectorException; +import de.stklcode.jvault.connector.model.response.MetadataResponse; +import de.stklcode.jvault.connector.model.response.SecretResponse; +import de.stklcode.jvault.connector.model.response.SecretVersionResponse; + +import java.util.Map; + +/** + * KV v2 client interface. + * Provides methods to interact with Vault's KV v2 API. + * + * @since 2.0.0 extracted from {@link VaultConnector} + */ +public interface KV2Client { + + /** + * Retrieve the latest secret data for specific version from Vault. + *
+ * Path {@code /data/} is read here. + * Only available for KV v2 secrets. + * + * @param mount Secret store mount point (without leading or trailing slash). + * @param key Secret identifier + * @return Secret response + * @throws VaultConnectorException on error + * @since 0.8 + */ + default SecretResponse readData(final String mount, final String key) throws VaultConnectorException { + return readVersion(mount, key, null); + } + + /** + * Write secret to Vault. + *
+ * Path {@code /data/} is written here. + * Only available for KV v2 secrets. + * + * @param mount Secret store mount point (without leading or trailing slash). + * @param key Secret identifier + * @param data Secret content. Value must be be JSON serializable. + * @return Metadata for the created/updated secret. + * @throws VaultConnectorException on error + * @since 0.8 + */ + default SecretVersionResponse writeData(final String mount, + final String key, + final Map data) throws VaultConnectorException { + return writeData(mount, key, data, null); + } + + /** + * Write secret to Vault. + *
+ * Path {@code /data/} is written here. + * Only available for KV v2 secrets. + * + * @param mount Secret store mount point (without leading or trailing slash). + * @param key Secret identifier + * @param data Secret content. Value must be be JSON serializable. + * @param cas Use Check-And-Set operation, i.e. only allow writing if current version matches this value. + * @return Metadata for the created/updated secret. + * @throws VaultConnectorException on error + * @since 0.8 + */ + SecretVersionResponse writeData(final String mount, + final String key, + final Map data, + final Integer cas) throws VaultConnectorException; + + /** + * Retrieve secret data from Vault. + *
+ * Path {@code /data/} is read here. + * Only available for KV v2 secrets. + * + * @param mount Secret store mount point (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 readVersion(final String mount, final String key, final Integer version) + throws VaultConnectorException; + + /** + * Retrieve secret metadata from Vault. + *
+ * Path {@code /metadata/} is read here. + * Only available for KV v2 secrets. + * + * @param mount Secret store mount point (without leading or trailing slash). + * @param key Secret identifier + * @return Metadata response + * @throws VaultConnectorException on error + * @since 0.8 + */ + MetadataResponse readMetadata(final String mount, final String key) throws VaultConnectorException; + + /** + * Update secret metadata. + *
+ * Path {@code /metadata/} is written here. + * Only available for KV v2 secrets. + * + * @param mount Secret store mount point (without leading or trailing slash). + * @param key Secret identifier + * @param maxVersions Maximum number of versions (fallback to backend default if {@code null}) + * @param casRequired Specify if Check-And-Set is required for this secret. + * @throws VaultConnectorException on error + * @since 0.8 + */ + void updateMetadata(final String mount, + final String key, + final Integer maxVersions, + final boolean casRequired) throws VaultConnectorException; + + /** + * Delete latest version of a secret from Vault. + *
+ * Only available for KV v2 stores. + * + * @param mount Secret store mount point (without leading or trailing slash). + * @param key Secret path. + * @throws VaultConnectorException on error + * @since 0.8 + */ + void deleteLatestVersion(final String mount, final String key) throws VaultConnectorException; + + /** + * Delete latest version of a secret from Vault. + *
+ * Prefix {@code secret/} is automatically added to path. + * Only available for KV v2 stores. + * + * @param mount Secret store mount point (without leading or trailing slash). + * @param key Secret path. + * @throws VaultConnectorException on error + * @since 0.8 + */ + void deleteAllVersions(final String mount, final String key) throws VaultConnectorException; + + /** + * Delete secret versions from Vault. + *
+ * Only available for KV v2 stores. + * + * @param mount Secret store mount point (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 deleteVersions(final String mount, final String key, final int... versions) + throws VaultConnectorException; + + /** + * Undelete (restore) secret versions from Vault. + * Only available for KV v2 stores. + * + * @param mount Secret store mount point (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 undeleteVersions(final String mount, final String key, final int... versions) + throws VaultConnectorException; + + /** + * Destroy secret versions from Vault. + * Only available for KV v2 stores. + * + * @param mount Secret store mount point (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 destroyVersions(final String mount, final String key, final int... versions) + throws VaultConnectorException; +} diff --git a/src/main/java/de/stklcode/jvault/connector/SysClient.java b/src/main/java/de/stklcode/jvault/connector/SysClient.java new file mode 100644 index 0000000..18538ca --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/SysClient.java @@ -0,0 +1,88 @@ +/* + * Copyright 2016-2025 Stefan Kalscheuer + * + * 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 de.stklcode.jvault.connector; + +import de.stklcode.jvault.connector.exception.VaultConnectorException; +import de.stklcode.jvault.connector.model.AuthBackend; +import de.stklcode.jvault.connector.model.Token; +import de.stklcode.jvault.connector.model.TokenRole; +import de.stklcode.jvault.connector.model.response.*; + +import java.util.List; + +/** + * Sys client interface. + * Provides methods to interact with Vault's system API. + * + * @since 2.0.0 extracted from {@link VaultConnector} + */ +public interface SysClient { + + /** + * Retrieve status of vault seal. + * + * @return Seal status + * @throws VaultConnectorException on error + */ + SealResponse sealStatus() throws VaultConnectorException; + + /** + * Seal vault. + * + * @throws VaultConnectorException on error + */ + void seal() throws VaultConnectorException; + + /** + * Unseal vault. + * + * @param key A single master share key + * @param reset Discard previously provided keys (optional) + * @return Response with seal status + * @throws VaultConnectorException on error + */ + SealResponse unseal(final String key, final Boolean reset) throws VaultConnectorException; + + /** + * Unseal vault. + * + * @param key A single master share key + * @return Response with seal status + * @throws VaultConnectorException on error + */ + default SealResponse unseal(final String key) throws VaultConnectorException { + return unseal(key, null); + } + + /** + * Query server health information. + * + * @return Health information. + * @throws VaultConnectorException on error + * @since 0.7.0 + */ + HealthResponse getHealth() throws VaultConnectorException; + + /** + * Get all available authentication backends. + * + * @return List of backends + * @throws VaultConnectorException on error + */ + List getAuthBackends() throws VaultConnectorException; + +} diff --git a/src/main/java/de/stklcode/jvault/connector/TokenClient.java b/src/main/java/de/stklcode/jvault/connector/TokenClient.java new file mode 100644 index 0000000..74beaa7 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/TokenClient.java @@ -0,0 +1,125 @@ +/* + * Copyright 2016-2025 Stefan Kalscheuer + * + * 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 de.stklcode.jvault.connector; + +import de.stklcode.jvault.connector.exception.VaultConnectorException; +import de.stklcode.jvault.connector.model.Token; +import de.stklcode.jvault.connector.model.TokenRole; +import de.stklcode.jvault.connector.model.response.AuthResponse; +import de.stklcode.jvault.connector.model.response.TokenResponse; +import de.stklcode.jvault.connector.model.response.TokenRoleResponse; + +import java.util.List; + +/** + * Token client interface. + * Provides methods to interact with Vault's token API. + * + * @since 2.0.0 extracted from {@link VaultConnector} + */ +public interface TokenClient { + + /** + * Create a new token. + * + * @param token the token + * @return the result response + * @throws VaultConnectorException on error + */ + AuthResponse create(final Token token) throws VaultConnectorException; + + /** + * Create a new token. + * + * @param token the token + * @param orphan create orphan token + * @return the result response + * @throws VaultConnectorException on error + */ + AuthResponse create(final Token token, boolean orphan) throws VaultConnectorException; + + /** + * Create a new token for specific role. + * + * @param token the token + * @param role the role name + * @return the result response + * @throws VaultConnectorException on error + */ + AuthResponse create(final Token token, final String role) throws VaultConnectorException; + + /** + * Lookup token information. + * + * @param token the token + * @return the result response + * @throws VaultConnectorException on error + */ + TokenResponse lookup(final String token) throws VaultConnectorException; + + /** + * Create a new or update an existing token role. + * + * @param role the role entity (name must be set) + * @return {@code true} on success + * @throws VaultConnectorException on error + * @since 0.9 + */ + default boolean createOrUpdateRole(final TokenRole role) throws VaultConnectorException { + return createOrUpdateRole(role.getName(), role); + } + + /** + * Create a new or update an existing token role. + * + * @param name the role name (overrides name possibly set in role entity) + * @param role the role entity + * @return {@code true} on success + * @throws VaultConnectorException on error + * @since 0.9 + */ + boolean createOrUpdateRole(final String name, final TokenRole role) throws VaultConnectorException; + + /** + * Lookup token information. + * + * @param name the role name + * @return the result response + * @throws VaultConnectorException on error + * @since 0.9 + */ + TokenRoleResponse readRole(final String name) throws VaultConnectorException; + + /** + * List available token roles from Vault. + * + * @return List of token roles + * @throws VaultConnectorException on error + * @since 0.9 + */ + List listRoles() throws VaultConnectorException; + + /** + * Delete a token role. + * + * @param name the role name to delete + * @return {@code true} on success + * @throws VaultConnectorException on error + * @since 0.9 + */ + boolean deleteRole(final String name) throws VaultConnectorException; +} diff --git a/src/main/java/de/stklcode/jvault/connector/TransitClient.java b/src/main/java/de/stklcode/jvault/connector/TransitClient.java new file mode 100644 index 0000000..5b8f329 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/TransitClient.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016-2025 Stefan Kalscheuer + * + * 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 de.stklcode.jvault.connector; + +import de.stklcode.jvault.connector.exception.VaultConnectorException; +import de.stklcode.jvault.connector.model.response.TransitResponse; + +import java.util.Base64; + +/** + * Transit client interface. + * Provides methods to interact with Vault's transit API. + * + * @since 2.0.0 extracted from {@link VaultConnector} + */ +public interface TransitClient { + + /** + * Encrypt plaintext via transit engine from Vault. + * + * @param keyName Transit key name + * @param plaintext Text to encrypt (Base64 encoded) + * @return Transit response + * @throws VaultConnectorException on error + * @since 1.5.0 + */ + TransitResponse encrypt(final String keyName, final String plaintext) throws VaultConnectorException; + + /** + * Encrypt plaintext via transit engine from Vault. + * + * @param keyName Transit key name + * @param plaintext Binary data to encrypt + * @return Transit response + * @throws VaultConnectorException on error + * @since 1.5.0 + */ + default TransitResponse encrypt(final String keyName, final byte[] plaintext) + throws VaultConnectorException { + return encrypt(keyName, Base64.getEncoder().encodeToString(plaintext)); + } + + /** + * Decrypt ciphertext via transit engine from Vault. + * + * @param keyName Transit key name + * @param ciphertext Text to decrypt + * @return Transit response + * @throws VaultConnectorException on error + * @since 1.5.0 + */ + TransitResponse decrypt(final String keyName, final String ciphertext) throws VaultConnectorException; + + /** + * Hash data in hex format via transit engine from Vault. + * + * @param algorithm Specifies the hash algorithm to use + * @param input Data to hash + * @return Transit response + * @throws VaultConnectorException on error + * @since 1.5.0 + */ + default TransitResponse hash(final String algorithm, final String input) throws VaultConnectorException { + return hash(algorithm, input, "hex"); + } + + /** + * Hash data via transit engine from Vault. + * + * @param algorithm Specifies the hash algorithm to use + * @param input Data to hash (Base64 encoded) + * @param format Specifies the output encoding (hex/base64) + * @return Transit response + * @throws VaultConnectorException on error + * @since 1.5.0 + */ + TransitResponse hash(final String algorithm, final String input, final String format) + throws VaultConnectorException; + + /** + * Hash data via transit engine from Vault. + * + * @param algorithm Specifies the hash algorithm to use + * @param input Data to hash + * @return Transit response + * @throws VaultConnectorException on error + * @since 1.5.0 + */ + default TransitResponse hash(final String algorithm, final byte[] input, final String format) + throws VaultConnectorException { + return hash(algorithm, Base64.getEncoder().encodeToString(input), format); + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java index 6d883e3..86cc950 100644 --- a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java +++ b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java @@ -37,59 +37,6 @@ public interface VaultConnector extends AutoCloseable, Serializable { */ void resetAuth(); - /** - * Retrieve status of vault seal. - * - * @return Seal status - * @throws VaultConnectorException on error - */ - SealResponse sealStatus() throws VaultConnectorException; - - /** - * Seal vault. - * - * @throws VaultConnectorException on error - */ - void seal() throws VaultConnectorException; - - /** - * Unseal vault. - * - * @param key A single master share key - * @param reset Discard previously provided keys (optional) - * @return Response with seal status - * @throws VaultConnectorException on error - */ - SealResponse unseal(final String key, final Boolean reset) throws VaultConnectorException; - - /** - * Unseal vault. - * - * @param key A single master share key - * @return Response with seal status - * @throws VaultConnectorException on error - */ - default SealResponse unseal(final String key) throws VaultConnectorException { - return unseal(key, null); - } - - /** - * Query server health information. - * - * @return Health information. - * @throws VaultConnectorException on error - * @since 0.7.0 - */ - HealthResponse getHealth() throws VaultConnectorException; - - /** - * Get all available authentication backends. - * - * @return List of backends - * @throws VaultConnectorException on error - */ - List getAuthBackends() throws VaultConnectorException; - /** * Authorize to Vault using token. * @@ -132,187 +79,6 @@ public interface VaultConnector extends AutoCloseable, Serializable { */ AuthResponse authAppRole(final String roleID, final String secretID) throws VaultConnectorException; - /** - * Register a new AppRole role from given metamodel. - * - * @param role The role - * @return {@code true} on success - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - boolean createAppRole(final AppRole role) throws VaultConnectorException; - - /** - * Register new AppRole role with default policy. - * - * @param roleName The role name - * @return {@code true} on success - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - default boolean createAppRole(final String roleName) throws VaultConnectorException { - return createAppRole(roleName, new ArrayList<>()); - } - - /** - * Register new AppRole role with policies. - * - * @param roleName The role name - * @param policies The policies to associate with - * @return {@code true} on success - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - default boolean createAppRole(final String roleName, final List policies) throws VaultConnectorException { - return createAppRole(roleName, policies, null); - } - - /** - * Register new AppRole role with default policy and custom ID. - * - * @param roleName The role name - * @param roleID A custom role ID - * @return {@code true} on success - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - default boolean createAppRole(final String roleName, final String roleID) throws VaultConnectorException { - return createAppRole(roleName, new ArrayList<>(), roleID); - } - - /** - * Register new AppRole role with policies and custom ID. - * - * @param roleName The role name - * @param policies The policies to associate with - * @param roleID A custom role ID - * @return {@code true} on success - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - default boolean createAppRole(final String roleName, final List policies, final String roleID) - throws VaultConnectorException { - return createAppRole(AppRole.builder(roleName).withTokenPolicies(policies).withId(roleID).build()); - } - - /** - * Delete AppRole role from Vault. - * - * @param roleName The role name - * @return {@code true} on success - * @throws VaultConnectorException on error - */ - boolean deleteAppRole(final String roleName) throws VaultConnectorException; - - /** - * Lookup an AppRole role. - * - * @param roleName The role name - * @return Result of the lookup - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - AppRoleResponse lookupAppRole(final String roleName) throws VaultConnectorException; - - /** - * Retrieve ID for an AppRole role. - * - * @param roleName The role name - * @return The role ID - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - String getAppRoleID(final String roleName) throws VaultConnectorException; - - /** - * Set custom ID for an AppRole role. - * - * @param roleName The role name - * @param roleID The role ID - * @return {@code true} on success - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - boolean setAppRoleID(final String roleName, final String roleID) throws VaultConnectorException; - - /** - * Register new random generated AppRole secret. - * - * @param roleName The role name - * @return The secret ID - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - default AppRoleSecretResponse createAppRoleSecret(final String roleName) throws VaultConnectorException { - return createAppRoleSecret(roleName, new AppRoleSecret()); - } - - /** - * Register new AppRole secret with custom ID. - * - * @param roleName The role name - * @param secretID A custom secret ID - * @return The secret ID - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - default AppRoleSecretResponse createAppRoleSecret(final String roleName, final String secretID) - throws VaultConnectorException { - return createAppRoleSecret(roleName, new AppRoleSecret(secretID)); - } - - /** - * Register new AppRole secret with custom ID. - * - * @param roleName The role name - * @param secret The secret meta object - * @return The secret ID - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - AppRoleSecretResponse createAppRoleSecret(final String roleName, final AppRoleSecret secret) - throws VaultConnectorException; - - /** - * Lookup an AppRole secret. - * - * @param roleName The role name - * @param secretID The secret ID - * @return Result of the lookup - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - AppRoleSecretResponse lookupAppRoleSecret(final String roleName, final String secretID) - throws VaultConnectorException; - - /** - * Destroy an AppRole secret. - * - * @param roleName The role name - * @param secretID The secret meta object - * @return The secret ID - * @throws VaultConnectorException on error - * @since 0.4.0 - */ - boolean destroyAppRoleSecret(final String roleName, final String secretID) throws VaultConnectorException; - - /** - * List existing (accessible) AppRole roles. - * - * @return List of roles - * @throws VaultConnectorException on error - */ - List listAppRoles() throws VaultConnectorException; - - /** - * List existing (accessible) secret IDs for AppRole role. - * - * @param roleName The role name - * @return List of roles - * @throws VaultConnectorException on error - */ - List listAppRoleSecrets(final String roleName) throws VaultConnectorException; - /** * Get authorization status. * @@ -330,108 +96,6 @@ public interface VaultConnector extends AutoCloseable, Serializable { */ SecretResponse read(final String key) throws VaultConnectorException; - /** - * Retrieve the latest secret data for specific version from Vault. - *
- * Path {@code /data/} is read here. - * Only available for KV v2 secrets. - * - * @param mount Secret store mount point (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); - } - - /** - * Write secret to Vault. - *
- * Path {@code /data/} is written here. - * Only available for KV v2 secrets. - * - * @param mount Secret store mount point (without leading or trailing slash). - * @param key Secret identifier - * @param data Secret content. Value must be be JSON serializable. - * @return Metadata for the created/updated secret. - * @throws VaultConnectorException on error - * @since 0.8 - */ - default SecretVersionResponse writeSecretData(final String mount, - final String key, - final Map data) throws VaultConnectorException { - return writeSecretData(mount, key, data, null); - } - - /** - * Write secret to Vault. - *
- * Path {@code /data/} is written here. - * Only available for KV v2 secrets. - * - * @param mount Secret store mount point (without leading or trailing slash). - * @param key Secret identifier - * @param data Secret content. Value must be be JSON serializable. - * @param cas Use Check-And-Set operation, i.e. only allow writing if current version matches this value. - * @return Metadata for the created/updated secret. - * @throws VaultConnectorException on error - * @since 0.8 - */ - SecretVersionResponse writeSecretData(final String mount, - final String key, - final Map data, - final Integer cas) throws VaultConnectorException; - - /** - * Retrieve secret data from Vault. - *
- * Path {@code /data/} is read here. - * Only available for KV v2 secrets. - * - * @param mount Secret store mount point (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. - *
- * Path {@code /metadata/} is read here. - * Only available for KV v2 secrets. - * - * @param mount Secret store mount point (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; - - /** - * Update secret metadata. - *
- * Path {@code /metadata/} is written here. - * Only available for KV v2 secrets. - * - * @param mount Secret store mount point (without leading or trailing slash). - * @param key Secret identifier - * @param maxVersions Maximum number of versions (fallback to backend default if {@code null}) - * @param casRequired Specify if Check-And-Set is required for this secret. - * @throws VaultConnectorException on error - * @since 0.8 - */ - void updateSecretMetadata(final String mount, - final String key, - final Integer maxVersions, - final boolean casRequired) throws VaultConnectorException; - /** * List available nodes from Vault. * @@ -487,71 +151,6 @@ public interface VaultConnector extends AutoCloseable, Serializable { */ void delete(final String key) throws VaultConnectorException; - /** - * Delete latest version of a secret from Vault. - *
- * Only available for KV v2 stores. - * - * @param mount Secret store mount point (without leading or trailing slash). - * @param key Secret path. - * @throws VaultConnectorException on error - * @since 0.8 - */ - void deleteLatestSecretVersion(final String mount, final String key) throws VaultConnectorException; - - /** - * Delete latest version of a secret from Vault. - *
- * Prefix {@code secret/} is automatically added to path. - * Only available for KV v2 stores. - * - * @param mount Secret store mount point (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. - *
- * Only available for KV v2 stores. - * - * @param mount Secret store mount point (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. - * Only available for KV v2 stores. - * - * @param mount Secret store mount point (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. - * Only available for KV v2 stores. - * - * @param mount Secret store mount point (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. * @@ -582,170 +181,44 @@ public interface VaultConnector extends AutoCloseable, Serializable { SecretResponse renew(final String leaseID, final Integer increment) throws VaultConnectorException; /** - * Create a new token. + * Get client for KV v2 API. * - * @param token the token - * @return the result response - * @throws VaultConnectorException on error + * @return KV v2 client + * @since 2.0.0 */ - AuthResponse createToken(final Token token) throws VaultConnectorException; + KV2Client kv2(); /** - * Create a new token. + * Get client for token API. * - * @param token the token - * @param orphan create orphan token - * @return the result response - * @throws VaultConnectorException on error + * @return Token client + * @since 2.0.0 */ - AuthResponse createToken(final Token token, boolean orphan) throws VaultConnectorException; + TokenClient token(); /** - * Create a new token for specific role. + * Get client for AppRole API. * - * @param token the token - * @param role the role name - * @return the result response - * @throws VaultConnectorException on error + * @return AppRole client + * @since 2.0.0 */ - AuthResponse createToken(final Token token, final String role) throws VaultConnectorException; + AppRoleClient appRole(); /** - * Lookup token information. + * Get client for transit API. * - * @param token the token - * @return the result response - * @throws VaultConnectorException on error + * @return Transit client + * @since 2.0.0 */ - TokenResponse lookupToken(final String token) throws VaultConnectorException; + TransitClient transit(); /** - * Create a new or update an existing token role. + * Get client for system API. * - * @param role the role entity (name must be set) - * @return {@code true} on success - * @throws VaultConnectorException on error - * @since 0.9 + * @return System client + * @since 2.0.0 */ - default boolean createOrUpdateTokenRole(final TokenRole role) throws VaultConnectorException { - return createOrUpdateTokenRole(role.getName(), role); - } - - /** - * Create a new or update an existing token role. - * - * @param name the role name (overrides name possibly set in role entity) - * @param role the role entity - * @return {@code true} on success - * @throws VaultConnectorException on error - * @since 0.9 - */ - boolean createOrUpdateTokenRole(final String name, final TokenRole role) throws VaultConnectorException; - - /** - * Lookup token information. - * - * @param name the role name - * @return the result response - * @throws VaultConnectorException on error - * @since 0.9 - */ - TokenRoleResponse readTokenRole(final String name) throws VaultConnectorException; - - /** - * List available token roles from Vault. - * - * @return List of token roles - * @throws VaultConnectorException on error - * @since 0.9 - */ - List listTokenRoles() throws VaultConnectorException; - - /** - * Delete a token role. - * - * @param name the role name to delete - * @return {@code true} on success - * @throws VaultConnectorException on error - * @since 0.9 - */ - boolean deleteTokenRole(final String name) throws VaultConnectorException; - - /** - * Encrypt plaintext via transit engine from Vault. - * - * @param keyName Transit key name - * @param plaintext Text to encrypt (Base64 encoded) - * @return Transit response - * @throws VaultConnectorException on error - * @since 1.5.0 - */ - TransitResponse transitEncrypt(final String keyName, final String plaintext) throws VaultConnectorException; - - /** - * Encrypt plaintext via transit engine from Vault. - * - * @param keyName Transit key name - * @param plaintext Binary data to encrypt - * @return Transit response - * @throws VaultConnectorException on error - * @since 1.5.0 - */ - default TransitResponse transitEncrypt(final String keyName, final byte[] plaintext) - throws VaultConnectorException { - return transitEncrypt(keyName, Base64.getEncoder().encodeToString(plaintext)); - } - - /** - * Decrypt ciphertext via transit engine from Vault. - * - * @param keyName Transit key name - * @param ciphertext Text to decrypt - * @return Transit response - * @throws VaultConnectorException on error - * @since 1.5.0 - */ - TransitResponse transitDecrypt(final String keyName, final String ciphertext) throws VaultConnectorException; - - /** - * Hash data in hex format via transit engine from Vault. - * - * @param algorithm Specifies the hash algorithm to use - * @param input Data to hash - * @return Transit response - * @throws VaultConnectorException on error - * @since 1.5.0 - */ - default TransitResponse transitHash(final String algorithm, final String input) throws VaultConnectorException { - return transitHash(algorithm, input, "hex"); - } - - /** - * Hash data via transit engine from Vault. - * - * @param algorithm Specifies the hash algorithm to use - * @param input Data to hash (Base64 encoded) - * @param format Specifies the output encoding (hex/base64) - * @return Transit response - * @throws VaultConnectorException on error - * @since 1.5.0 - */ - TransitResponse transitHash(final String algorithm, final String input, final String format) - throws VaultConnectorException; - - /** - * Hash data via transit engine from Vault. - * - * @param algorithm Specifies the hash algorithm to use - * @param input Data to hash - * @return Transit response - * @throws VaultConnectorException on error - * @since 1.5.0 - */ - default TransitResponse transitHash(final String algorithm, final byte[] input, final String format) - throws VaultConnectorException { - return transitHash(algorithm, Base64.getEncoder().encodeToString(input), format); - } + SysClient sys(); /** * Read credentials for MySQL backend at default mount point. @@ -816,4 +289,5 @@ public interface VaultConnector extends AutoCloseable, Serializable { throws VaultConnectorException { return (CredentialsResponse) read(mount + "/creds/" + role); } + } diff --git a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorIT.java b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorIT.java index 7423556..8540dc2 100644 --- a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorIT.java +++ b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorIT.java @@ -95,10 +95,10 @@ class HTTPVaultConnectorIT { connector = builder.build(); // Unseal Vault and check result. - SealResponse sealStatus = connector.unseal(KEY1); + SealResponse sealStatus = connector.sys().unseal(KEY1); assumeTrue(sealStatus != null, "Seal status could not be determined after startup"); assumeTrue(sealStatus.isSealed(), "Vault is not sealed after startup"); - sealStatus = connector.unseal(KEY2); + sealStatus = connector.sys().unseal(KEY2); assumeTrue(sealStatus != null, "Seal status could not be determined"); assumeFalse(sealStatus.isSealed(), "Vault is not unsealed"); assumeTrue(sealStatus.isInitialized(), "Vault is not initialized"); // Initialized flag of Vault 0.11.2 (#20). @@ -337,7 +337,7 @@ class HTTPVaultConnectorIT { // Try to read accessible path with known value. SecretResponse res = assertDoesNotThrow( - () -> connector.readSecretData(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readData(MOUNT_KV2, SECRET2_KEY), "Valid secret path could not be read" ); assertNotNull(res.getMetadata(), "Metadata not populated for KV v2 secret"); @@ -346,7 +346,7 @@ class HTTPVaultConnectorIT { // Try to read different version of same secret. res = assertDoesNotThrow( - () -> connector.readSecretVersion(MOUNT_KV2, SECRET2_KEY, 1), + () -> connector.kv2().readVersion(MOUNT_KV2, SECRET2_KEY, 1), "Valid secret version could not be read" ); assertEquals(1, res.getMetadata().getVersion(), "Unexpected secret version"); @@ -365,7 +365,7 @@ class HTTPVaultConnectorIT { // First get the current version of the secret. MetadataResponse res = assertDoesNotThrow( - () -> connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readMetadata(MOUNT_KV2, SECRET2_KEY), "Reading secret metadata failed" ); int currentVersion = res.getMetadata().getCurrentVersion(); @@ -374,7 +374,7 @@ class HTTPVaultConnectorIT { Map data = new HashMap<>(); data.put("value", SECRET2_VALUE3); SecretVersionResponse res2 = assertDoesNotThrow( - () -> connector.writeSecretData(MOUNT_KV2, SECRET2_KEY, data), + () -> connector.kv2().writeData(MOUNT_KV2, SECRET2_KEY, data), "Writing secret to KV v2 store failed" ); assertEquals(currentVersion + 1, res2.getMetadata().getVersion(), "Version not updated after writing secret"); @@ -382,7 +382,7 @@ class HTTPVaultConnectorIT { // Verify the content. SecretResponse res3 = assertDoesNotThrow( - () -> connector.readSecretData(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readData(MOUNT_KV2, SECRET2_KEY), "Reading secret from KV v2 store failed" ); assertEquals(SECRET2_VALUE3, res3.get("value"), "Data not updated correctly"); @@ -391,13 +391,13 @@ class HTTPVaultConnectorIT { Map data4 = singletonMap("value", SECRET2_VALUE4); assertThrows( InvalidResponseException.class, - () -> connector.writeSecretData(MOUNT_KV2, SECRET2_KEY, data4, currentVersion2 - 1), + () -> connector.kv2().writeData(MOUNT_KV2, SECRET2_KEY, data4, currentVersion2 - 1), "Writing secret to KV v2 with invalid CAS value succeeded" ); // And finally with a correct CAS value. Map data5 = singletonMap("value", SECRET2_VALUE4); - assertDoesNotThrow(() -> connector.writeSecretData(MOUNT_KV2, SECRET2_KEY, data5, currentVersion2)); + assertDoesNotThrow(() -> connector.kv2().writeData(MOUNT_KV2, SECRET2_KEY, data5, currentVersion2)); } /** @@ -412,7 +412,7 @@ class HTTPVaultConnectorIT { // Read current metadata first. MetadataResponse res = assertDoesNotThrow( - () -> connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readMetadata(MOUNT_KV2, SECRET2_KEY), "Reading secret metadata failed" ); Integer maxVersions = res.getMetadata().getMaxVersions(); @@ -420,13 +420,13 @@ class HTTPVaultConnectorIT { // Now update the metadata. assertDoesNotThrow( - () -> connector.updateSecretMetadata(MOUNT_KV2, SECRET2_KEY, maxVersions + 1, true), + () -> connector.kv2().updateMetadata(MOUNT_KV2, SECRET2_KEY, maxVersions + 1, true), "Updating secret metadata failed" ); // And verify the result. res = assertDoesNotThrow( - () -> connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readMetadata(MOUNT_KV2, SECRET2_KEY), "Reading secret metadata failed" ); assertEquals(maxVersions + 1, res.getMetadata().getMaxVersions(), "Unexpected maximum number of versions"); @@ -444,7 +444,7 @@ class HTTPVaultConnectorIT { // Try to read accessible path with known value. MetadataResponse res = assertDoesNotThrow( - () -> connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readMetadata(MOUNT_KV2, SECRET2_KEY), "Valid secret path could not be read" ); assertNotNull(res.getMetadata(), "Metadata not populated for KV v2 secret"); @@ -467,21 +467,21 @@ class HTTPVaultConnectorIT { // Try to delete non-existing versions. assertDoesNotThrow( - () -> connector.deleteSecretVersions(MOUNT_KV2, SECRET2_KEY, 5, 42), + () -> connector.kv2().deleteVersions(MOUNT_KV2, SECRET2_KEY, 5, 42), "Revealed non-existence of secret versions" ); assertDoesNotThrow( - () -> connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readMetadata(MOUNT_KV2, SECRET2_KEY), "Revealed non-existence of secret versions" ); // Now delete existing version and verify. assertDoesNotThrow( - () -> connector.deleteSecretVersions(MOUNT_KV2, SECRET2_KEY, 1), + () -> connector.kv2().deleteVersions(MOUNT_KV2, SECRET2_KEY, 1), "Deleting existing version failed" ); MetadataResponse meta = assertDoesNotThrow( - () -> connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readMetadata(MOUNT_KV2, SECRET2_KEY), "Reading deleted secret metadata failed" ); assertNotNull( @@ -491,11 +491,11 @@ class HTTPVaultConnectorIT { // Undelete the just deleted version. assertDoesNotThrow( - () -> connector.undeleteSecretVersions(MOUNT_KV2, SECRET2_KEY, 1), + () -> connector.kv2().undeleteVersions(MOUNT_KV2, SECRET2_KEY, 1), "Undeleting existing version failed" ); meta = assertDoesNotThrow( - () -> connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readMetadata(MOUNT_KV2, SECRET2_KEY), "Reading deleted secret metadata failed" ); assertNull( @@ -505,11 +505,11 @@ class HTTPVaultConnectorIT { // Now destroy it. assertDoesNotThrow( - () -> connector.destroySecretVersions(MOUNT_KV2, SECRET2_KEY, 1), + () -> connector.kv2().destroyVersions(MOUNT_KV2, SECRET2_KEY, 1), "Destroying existing version failed" ); meta = assertDoesNotThrow( - () -> connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readMetadata(MOUNT_KV2, SECRET2_KEY), "Reading destroyed secret metadata failed" ); assertTrue( @@ -519,11 +519,11 @@ class HTTPVaultConnectorIT { // Delete latest version. assertDoesNotThrow( - () -> connector.deleteLatestSecretVersion(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().deleteLatestVersion(MOUNT_KV2, SECRET2_KEY), "Deleting latest version failed" ); meta = assertDoesNotThrow( - () -> connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readMetadata(MOUNT_KV2, SECRET2_KEY), "Reading deleted secret metadata failed" ); assertNotNull( @@ -533,12 +533,12 @@ class HTTPVaultConnectorIT { // Delete all versions. assertDoesNotThrow( - () -> connector.deleteAllSecretVersions(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().deleteAllVersions(MOUNT_KV2, SECRET2_KEY), "Deleting latest version failed" ); assertThrows( InvalidResponseException.class, - () -> connector.readSecretMetadata(MOUNT_KV2, SECRET2_KEY), + () -> connector.kv2().readMetadata(MOUNT_KV2, SECRET2_KEY), "Reading metadata of deleted secret should not succeed" ); } @@ -620,21 +620,21 @@ class HTTPVaultConnectorIT { // Try unauthorized access first. assumeFalse(connector.isAuthorized()); - assertThrows(AuthorizationRequiredException.class, () -> connector.listAppRoles()); + assertThrows(AuthorizationRequiredException.class, () -> connector.appRole().listRoles()); - assertThrows(AuthorizationRequiredException.class, () -> connector.listAppRoleSecrets("")); + assertThrows(AuthorizationRequiredException.class, () -> connector.appRole().listSecrets("")); // Authorize. authRoot(); assumeTrue(connector.isAuthorized()); // Verify pre-existing rules. - List res = assertDoesNotThrow(() -> connector.listAppRoles(), "Role listing failed"); + List res = assertDoesNotThrow(() -> connector.appRole().listRoles(), "Role listing failed"); assertEquals(2, res.size(), "Unexpected number of AppRoles"); assertTrue(res.containsAll(List.of(APPROLE_ROLE_NAME, APPROLE_ROLE2_NAME)), "Pre-configured roles not listed"); // Check secret IDs. - res = assertDoesNotThrow(() -> connector.listAppRoleSecrets(APPROLE_ROLE_NAME), "AppRole secret listing failed"); + res = assertDoesNotThrow(() -> connector.appRole().listSecrets(APPROLE_ROLE_NAME), "AppRole secret listing failed"); assertEquals(List.of(APPROLE_SECRET_ACCESSOR), res, "Pre-configured AppRole secret not listed"); } @@ -647,14 +647,14 @@ class HTTPVaultConnectorIT { void createAppRoleTest() { // Try unauthorized access first. assumeFalse(connector.isAuthorized()); - assertThrows(AuthorizationRequiredException.class, () -> connector.createAppRole(new AppRole())); - assertThrows(AuthorizationRequiredException.class, () -> connector.lookupAppRole("")); - assertThrows(AuthorizationRequiredException.class, () -> connector.deleteAppRole("")); - assertThrows(AuthorizationRequiredException.class, () -> connector.getAppRoleID("")); - assertThrows(AuthorizationRequiredException.class, () -> connector.setAppRoleID("", "")); - assertThrows(AuthorizationRequiredException.class, () -> connector.createAppRoleSecret("", "")); - assertThrows(AuthorizationRequiredException.class, () -> connector.lookupAppRoleSecret("", "")); - assertThrows(AuthorizationRequiredException.class, () -> connector.destroyAppRoleSecret("", "")); + assertThrows(AuthorizationRequiredException.class, () -> connector.appRole().create(new AppRole())); + assertThrows(AuthorizationRequiredException.class, () -> connector.appRole().lookup("")); + assertThrows(AuthorizationRequiredException.class, () -> connector.appRole().delete("")); + assertThrows(AuthorizationRequiredException.class, () -> connector.appRole().getRoleID("")); + assertThrows(AuthorizationRequiredException.class, () -> connector.appRole().setRoleID("", "")); + assertThrows(AuthorizationRequiredException.class, () -> connector.appRole().createSecret("", "")); + assertThrows(AuthorizationRequiredException.class, () -> connector.appRole().lookupSecret("", "")); + assertThrows(AuthorizationRequiredException.class, () -> connector.appRole().destroySecret("", "")); // Authorize. authRoot(); @@ -666,23 +666,23 @@ class HTTPVaultConnectorIT { AppRole role = AppRole.builder(roleName).build(); // Create role. - boolean createRes = assertDoesNotThrow(() -> connector.createAppRole(role), "Role creation failed"); + boolean createRes = assertDoesNotThrow(() -> connector.appRole().create(role), "Role creation failed"); assertTrue(createRes, "Role creation failed"); // Lookup role. - AppRoleResponse res = assertDoesNotThrow(() -> connector.lookupAppRole(roleName), "Role lookup failed"); + AppRoleResponse res = assertDoesNotThrow(() -> connector.appRole().lookup(roleName), "Role lookup failed"); assertNotNull(res.getRole(), "Role lookup returned no role"); // Lookup role ID. - String roleID = assertDoesNotThrow(() -> connector.getAppRoleID(roleName), "Role ID lookup failed"); + String roleID = assertDoesNotThrow(() -> connector.appRole().getRoleID(roleName), "Role ID lookup failed"); assertNotEquals("", roleID, "Role ID lookup returned empty ID"); // Set custom role ID. String roleID2 = "custom-role-id"; - assertDoesNotThrow(() -> connector.setAppRoleID(roleName, roleID2), "Setting custom role ID failed"); + assertDoesNotThrow(() -> connector.appRole().setRoleID(roleName, roleID2), "Setting custom role ID failed"); // Verify role ID. - String res2 = assertDoesNotThrow(() -> connector.getAppRoleID(roleName), "Role ID lookup failed"); + String res2 = assertDoesNotThrow(() -> connector.appRole().getRoleID(roleName), "Role ID lookup failed"); assertEquals(roleID2, res2, "Role ID lookup returned wrong ID"); // Update role model with custom flags. @@ -691,44 +691,44 @@ class HTTPVaultConnectorIT { .build(); // Create role. - boolean res3 = assertDoesNotThrow(() -> connector.createAppRole(role2), "Role creation failed"); + boolean res3 = assertDoesNotThrow(() -> connector.appRole().create(role2), "Role creation failed"); assertTrue(res3, "No result given"); // Lookup updated role. - res = assertDoesNotThrow(() -> connector.lookupAppRole(roleName), "Role lookup failed"); + res = assertDoesNotThrow(() -> connector.appRole().lookup(roleName), "Role lookup failed"); assertNotNull(res.getRole(), "Role lookup returned no role"); assertEquals(321, res.getRole().getTokenPeriod(), "Token period not set for role"); // Create role by name. String roleName2 = "RoleByName"; - assertDoesNotThrow(() -> connector.createAppRole(roleName2), "Creation of role by name failed"); - res = assertDoesNotThrow(() -> connector.lookupAppRole(roleName2), "Creation of role by name failed"); + assertDoesNotThrow(() -> connector.appRole().create(roleName2), "Creation of role by name failed"); + res = assertDoesNotThrow(() -> connector.appRole().lookup(roleName2), "Creation of role by name failed"); assertNotNull(res.getRole(), "Role lookuo returned not value"); // Create role by name with custom ID. String roleName3 = "RoleByName"; String roleID3 = "RolyByNameID"; - assertDoesNotThrow(() -> connector.createAppRole(roleName3, roleID3), "Creation of role by name failed"); - res = assertDoesNotThrow(() -> connector.lookupAppRole(roleName3), "Creation of role by name failed"); + assertDoesNotThrow(() -> connector.appRole().create(roleName3, roleID3), "Creation of role by name failed"); + res = assertDoesNotThrow(() -> connector.appRole().lookup(roleName3), "Creation of role by name failed"); assertNotNull(res.getRole(), "Role lookuo returned not value"); - res2 = assertDoesNotThrow(() -> connector.getAppRoleID(roleName3), "Creation of role by name failed"); + res2 = assertDoesNotThrow(() -> connector.appRole().getRoleID(roleName3), "Creation of role by name failed"); assertEquals(roleID3, res2, "Role lookuo returned wrong ID"); // Create role by name with policies. assertDoesNotThrow( - () -> connector.createAppRole(roleName3, Collections.singletonList("testpolicy")), + () -> connector.appRole().create(roleName3, Collections.singletonList("testpolicy")), "Creation of role by name failed" ); - res = assertDoesNotThrow(() -> connector.lookupAppRole(roleName3), "Creation of role by name failed"); + res = assertDoesNotThrow(() -> connector.appRole().lookup(roleName3), "Creation of role by name failed"); // Note: As of Vault 0.8.3 default policy is not added automatically, so this test should return 1, not 2. assertEquals(List.of("testpolicy"), res.getRole().getTokenPolicies(), "Role lookup returned unexpected policies"); // Delete role. - assertDoesNotThrow(() -> connector.deleteAppRole(roleName3), "Deletion of role failed"); + assertDoesNotThrow(() -> connector.appRole().delete(roleName3), "Deletion of role failed"); assertThrows( InvalidResponseException.class, - () -> connector.lookupAppRole(roleName3), + () -> connector.appRole().lookup(roleName3), "Deleted role could be looked up" ); } @@ -745,7 +745,7 @@ class HTTPVaultConnectorIT { // Create default (random) secret for existing role. AppRoleSecretResponse res = assertDoesNotThrow( - () -> connector.createAppRoleSecret(APPROLE_ROLE_NAME), + () -> connector.appRole().createSecret(APPROLE_ROLE_NAME), "AppRole secret creation failed" ); assertNotNull(res.getSecret(), "No secret returned"); @@ -753,26 +753,26 @@ class HTTPVaultConnectorIT { // Create secret with custom ID. String secretID = "customSecretId"; res = assertDoesNotThrow( - () -> connector.createAppRoleSecret(APPROLE_ROLE_NAME, secretID), + () -> connector.appRole().createSecret(APPROLE_ROLE_NAME, secretID), "AppRole secret creation failed" ); assertEquals(secretID, res.getSecret().getId(), "Unexpected secret ID returned"); // Lookup secret. res = assertDoesNotThrow( - () -> connector.lookupAppRoleSecret(APPROLE_ROLE_NAME, secretID), + () -> connector.appRole().lookupSecret(APPROLE_ROLE_NAME, secretID), "AppRole secret lookup failed" ); assertNotNull(res.getSecret(), "No secret information returned"); // Destroy secret. assertDoesNotThrow( - () -> connector.destroyAppRoleSecret(APPROLE_ROLE_NAME, secretID), + () -> connector.appRole().destroySecret(APPROLE_ROLE_NAME, secretID), "AppRole secret destruction failed" ); assertThrows( InvalidResponseException.class, - () -> connector.lookupAppRoleSecret(APPROLE_ROLE_NAME, secretID), + () -> connector.appRole().lookupSecret(APPROLE_ROLE_NAME, secretID), "Destroyed AppRole secret successfully read" ); } @@ -825,7 +825,7 @@ class HTTPVaultConnectorIT { .build(); // Create token. - AuthResponse res = assertDoesNotThrow(() -> connector.createToken(token), "Token creation failed"); + AuthResponse res = assertDoesNotThrow(() -> connector.token().create(token), "Token creation failed"); assertNotNull(res, "No result given"); assertEquals("test-id", res.getAuth().getClientToken(), "Invalid token ID returned"); assertEquals(List.of("root"), res.getAuth().getPolicies(), "Expected inherited root policy"); @@ -847,7 +847,7 @@ class HTTPVaultConnectorIT { .withoutDefaultPolicy() .withMeta("foo", "bar") .build(); - res = assertDoesNotThrow(() -> connector.createToken(token2), "Token creation failed"); + res = assertDoesNotThrow(() -> connector.token().create(token2), "Token creation failed"); assertEquals("test-id2", res.getAuth().getClientToken(), "Invalid token ID returned"); assertEquals(List.of("testpolicy"), res.getAuth().getPolicies(), "Invalid policies returned"); assertNotNull(res.getAuth().getMetadata(), "Metadata not given"); @@ -866,7 +866,7 @@ class HTTPVaultConnectorIT { .build(); InvalidResponseException e = assertThrows( InvalidResponseException.class, - () -> connector.createToken(token3), + () -> connector.token().create(token3), "Overwriting token should fail as of Vault 0.8.0" ); assertEquals(400, e.getStatusCode()); @@ -880,7 +880,7 @@ class HTTPVaultConnectorIT { .withoutDefaultPolicy() .withType(Token.Type.BATCH) .build(); - res = assertDoesNotThrow(() -> connector.createToken(token4), "Token creation failed"); + res = assertDoesNotThrow(() -> connector.token().create(token4), "Token creation failed"); assertTrue( // Expecting batch token. "hvb." Prefix as of Vault 1.10, "b." before. res.getAuth().getClientToken().startsWith("b.") || res.getAuth().getClientToken().startsWith("hvb."), @@ -908,12 +908,12 @@ class HTTPVaultConnectorIT { .withId("my-token") .withType(Token.Type.SERVICE) .build(); - assertDoesNotThrow(() -> connector.createToken(token), "Token creation failed"); + assertDoesNotThrow(() -> connector.token().create(token), "Token creation failed"); authRoot(); assumeTrue(connector.isAuthorized()); - TokenResponse res = assertDoesNotThrow(() -> connector.lookupToken("my-token"), "Token creation failed"); + TokenResponse res = assertDoesNotThrow(() -> connector.token().lookup("my-token"), "Token creation failed"); assertEquals(token.getId(), res.getData().getId(), "Unexpected token ID"); assertEquals(1, res.getData().getPolicies().size(), "Unexpected number of policies"); assertTrue(res.getData().getPolicies().contains("root"), "Unexpected policy"); @@ -936,14 +936,14 @@ class HTTPVaultConnectorIT { final TokenRole role = TokenRole.builder().build(); boolean creationRes = assertDoesNotThrow( - () -> connector.createOrUpdateTokenRole(roleName, role), + () -> connector.token().createOrUpdateRole(roleName, role), "Token role creation failed" ); assertTrue(creationRes, "Token role creation failed"); // Read the role. TokenRoleResponse res = assertDoesNotThrow( - () -> connector.readTokenRole(roleName), + () -> connector.token().readRole(roleName), "Reading token role failed" ); assertNotNull(res, "Token role response must not be null"); @@ -963,12 +963,12 @@ class HTTPVaultConnectorIT { .build(); creationRes = assertDoesNotThrow( - () -> connector.createOrUpdateTokenRole(role2), + () -> connector.token().createOrUpdateRole(role2), "Token role update failed" ); assertTrue(creationRes, "Token role update failed"); - res = assertDoesNotThrow(() -> connector.readTokenRole(roleName), "Reading token role failed"); + res = assertDoesNotThrow(() -> connector.token().readRole(roleName), "Reading token role failed"); assertNotNull(res, "Token role response must not be null"); assertNotNull(res.getData(), "Token role must not be null"); assertEquals(roleName, res.getData().getName(), "Token role name not as expected"); @@ -977,15 +977,15 @@ class HTTPVaultConnectorIT { assertEquals(42, res.getData().getTokenNumUses(), "Unexpected number of token uses after update"); // List roles. - List listRes = assertDoesNotThrow(() -> connector.listTokenRoles(), "Listing token roles failed"); + List listRes = assertDoesNotThrow(() -> connector.token().listRoles(), "Listing token roles failed"); assertNotNull(listRes, "Token role list must not be null"); assertEquals(List.of(roleName), listRes, "Unexpected token role list"); // Delete the role. - creationRes = assertDoesNotThrow(() -> connector.deleteTokenRole(roleName), "Token role deletion failed"); + creationRes = assertDoesNotThrow(() -> connector.token().deleteRole(roleName), "Token role deletion failed"); assertTrue(creationRes, "Token role deletion failed"); - assertThrows(InvalidResponseException.class, () -> connector.readTokenRole(roleName), "Reading nonexistent token role should fail"); - assertThrows(InvalidResponseException.class, () -> connector.listTokenRoles(), "Listing nonexistent token roles should fail"); + assertThrows(InvalidResponseException.class, () -> connector.token().readRole(roleName), "Reading nonexistent token role should fail"); + assertThrows(InvalidResponseException.class, () -> connector.token().listRoles(), "Listing nonexistent token roles should fail"); } } @@ -1000,14 +1000,14 @@ class HTTPVaultConnectorIT { assumeTrue(connector.isAuthorized()); TransitResponse transitResponse = assertDoesNotThrow( - () -> connector.transitEncrypt("my-key", "dGVzdCBtZQ=="), + () -> connector.transit().encrypt("my-key", "dGVzdCBtZQ=="), "Failed to encrypt via transit" ); assertNotNull(transitResponse.getCiphertext()); assertTrue(transitResponse.getCiphertext().startsWith("vault:v1:")); transitResponse = assertDoesNotThrow( - () -> connector.transitEncrypt("my-key", "test me".getBytes(UTF_8)), + () -> connector.transit().encrypt("my-key", "test me".getBytes(UTF_8)), "Failed to encrypt binary data via transit" ); assertNotNull(transitResponse.getCiphertext()); @@ -1022,7 +1022,7 @@ class HTTPVaultConnectorIT { assumeTrue(connector.isAuthorized()); TransitResponse transitResponse = assertDoesNotThrow( - () -> connector.transitDecrypt("my-key", "vault:v1:1mhLVkBAR2nrFtIkJF/qg57DWfRj0FWgR6tvkGO8XOnL6sw="), + () -> connector.transit().decrypt("my-key", "vault:v1:1mhLVkBAR2nrFtIkJF/qg57DWfRj0FWgR6tvkGO8XOnL6sw="), "Failed to decrypt via transit" ); @@ -1036,21 +1036,21 @@ class HTTPVaultConnectorIT { assumeTrue(connector.isAuthorized()); TransitResponse transitResponse = assertDoesNotThrow( - () -> connector.transitHash("sha2-512", "dGVzdCBtZQ=="), + () -> connector.transit().hash("sha2-512", "dGVzdCBtZQ=="), "Failed to hash via transit" ); assertEquals("7677af0ee4effaa9f35e9b1e82d182f79516ab8321786baa23002de7c06851059492dd37d5fc3791f17d81d4b58198d24a6fd8bbd62c42c1c30b371da500f193", transitResponse.getSum()); TransitResponse transitResponseBase64 = assertDoesNotThrow( - () -> connector.transitHash("sha2-256", "dGVzdCBtZQ==", "base64"), + () -> connector.transit().hash("sha2-256", "dGVzdCBtZQ==", "base64"), "Failed to hash via transit with base64 output" ); assertEquals("5DfYkW7cvGLkfy36cXhqmZcygEy9HpnFNB4WWXKOl1M=", transitResponseBase64.getSum()); transitResponseBase64 = assertDoesNotThrow( - () -> connector.transitHash("sha2-256", "test me".getBytes(UTF_8), "base64"), + () -> connector.transit().hash("sha2-256", "test me".getBytes(UTF_8), "base64"), "Failed to hash binary data via transit" ); @@ -1072,7 +1072,7 @@ class HTTPVaultConnectorIT { assumeTrue(connector.isAuthorized()); List supportedBackends = assertDoesNotThrow( - () -> connector.getAuthBackends(), + () -> connector.sys().getAuthBackends(), "Could not list supported auth backends" ); @@ -1132,22 +1132,22 @@ class HTTPVaultConnectorIT { @Test @DisplayName("Seal test") void sealTest() throws VaultConnectorException { - SealResponse sealStatus = connector.sealStatus(); + SealResponse sealStatus = connector.sys().sealStatus(); assumeFalse(sealStatus.isSealed()); // Unauthorized sealing should fail. - assertThrows(VaultConnectorException.class, connector::seal, "Unauthorized sealing succeeded"); + assertThrows(VaultConnectorException.class, () -> connector.sys().seal(), "Unauthorized sealing succeeded"); assertFalse(sealStatus.isSealed(), "Vault sealed, although sealing failed"); // Root user should be able to seal. authRoot(); assumeTrue(connector.isAuthorized()); - assertDoesNotThrow(connector::seal, "Sealing failed"); - sealStatus = connector.sealStatus(); + assertDoesNotThrow(() -> connector.sys().seal(), "Sealing failed"); + sealStatus = connector.sys().sealStatus(); assertTrue(sealStatus.isSealed(), "Vault not sealed"); - sealStatus = connector.unseal(KEY2); + sealStatus = connector.sys().unseal(KEY2); assertTrue(sealStatus.isSealed(), "Vault unsealed with only 1 key"); - sealStatus = connector.unseal(KEY3); + sealStatus = connector.sys().unseal(KEY3); assertFalse(sealStatus.isSealed(), "Vault not unsealed"); } @@ -1157,7 +1157,7 @@ class HTTPVaultConnectorIT { @Test @DisplayName("Health test") void healthTest() { - HealthResponse res = assertDoesNotThrow(connector::getHealth, "Retrieving health status failed"); + HealthResponse res = assertDoesNotThrow(() -> connector.sys().getHealth(), "Retrieving health status failed"); assertNotNull(res, "Health response should be set"); assertEquals(VAULT_VERSION, res.getVersion(), "Unexpected version"); assertTrue(res.isInitialized(), "Unexpected init status"); @@ -1166,11 +1166,11 @@ class HTTPVaultConnectorIT { // No seal vault and verify correct status. authRoot(); - assertDoesNotThrow(connector::seal, "Unexpected exception on sealing"); - SealResponse sealStatus = assertDoesNotThrow(connector::sealStatus); + assertDoesNotThrow(() -> connector.sys().seal(), "Unexpected exception on sealing"); + SealResponse sealStatus = assertDoesNotThrow(() -> connector.sys().sealStatus()); assumeTrue(sealStatus.isSealed()); connector.resetAuth(); // Should work unauthenticated - res = assertDoesNotThrow(connector::getHealth, "Retrieving health status failed when sealed"); + res = assertDoesNotThrow(() -> connector.sys().getHealth(), "Retrieving health status failed when sealed"); assertTrue(res.isSealed(), "Unexpected seal status"); } diff --git a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java index a072a4f..4bfb4dc 100644 --- a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java +++ b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java @@ -54,51 +54,51 @@ class HTTPVaultConnectorTest { */ @Test void requestExceptionTest(WireMockRuntimeInfo wireMock) throws IOException, URISyntaxException { - HTTPVaultConnector connector = HTTPVaultConnector.builder(wireMock.getHttpBaseUrl()).withTimeout(250).build(); + try (var connector = HTTPVaultConnector.builder(wireMock.getHttpBaseUrl()).withTimeout(250).build()) { + // Test invalid response code. + final int responseCode = 400; + mockHttpResponse(responseCode, "", "application/json"); + VaultConnectorException e = assertThrows( + InvalidResponseException.class, + () -> connector.sys().getHealth(), + "Querying health status succeeded on invalid instance" + ); + assertEquals("Invalid response code", e.getMessage(), "Unexpected exception message"); + assertEquals(responseCode, ((InvalidResponseException) e).getStatusCode(), "Unexpected status code in exception"); + assertNull(((InvalidResponseException) e).getResponse(), "Response message where none was expected"); - // Test invalid response code. - final int responseCode = 400; - mockHttpResponse(responseCode, "", "application/json"); - VaultConnectorException e = assertThrows( - InvalidResponseException.class, - connector::getHealth, - "Querying health status succeeded on invalid instance" - ); - assertEquals("Invalid response code", e.getMessage(), "Unexpected exception message"); - assertEquals(responseCode, ((InvalidResponseException) e).getStatusCode(), "Unexpected status code in exception"); - assertNull(((InvalidResponseException) e).getResponse(), "Response message where none was expected"); - - // Simulate permission denied response. - mockHttpResponse(responseCode, "{\"errors\":[\"permission denied\"]}", "application/json"); - assertThrows( - PermissionDeniedException.class, - connector::getHealth, - "Querying health status succeeded on invalid instance" - ); + // Simulate permission denied response. + mockHttpResponse(responseCode, "{\"errors\":[\"permission denied\"]}", "application/json"); + assertThrows( + PermissionDeniedException.class, + () -> connector.sys().getHealth(), + "Querying health status succeeded on invalid instance" + ); + } // Test exception thrown during request. - try (ServerSocket s = new ServerSocket(0)) { - connector = HTTPVaultConnector.builder("http://localst:" + s.getLocalPort() + "/").withTimeout(250).build(); + try (ServerSocket s = new ServerSocket(0); + var connector = HTTPVaultConnector.builder("http://localst:" + s.getLocalPort() + "/").withTimeout(250).build()) { + var e = assertThrows( + ConnectionException.class, + () -> connector.sys().getHealth(), + "Querying health status succeeded on invalid instance" + ); + assertEquals("Unable to connect to Vault server", e.getMessage(), "Unexpected exception message"); + assertInstanceOf(IOException.class, e.getCause(), "Unexpected cause"); } - e = assertThrows( - ConnectionException.class, - connector::getHealth, - "Querying health status succeeded on invalid instance" - ); - assertEquals("Unable to connect to Vault server", e.getMessage(), "Unexpected exception message"); - assertInstanceOf(IOException.class, e.getCause(), "Unexpected cause"); // Now simulate a failing request that succeeds on second try. - connector = HTTPVaultConnector.builder(wireMock.getHttpBaseUrl()).withNumberOfRetries(1).withTimeout(250).build(); - - stubFor( - WireMock.any(anyUrl()) - .willReturn(aResponse().withStatus(500)) - .willReturn(aResponse().withStatus(500)) - .willReturn(aResponse().withStatus(500)) - .willReturn(aResponse().withStatus(200).withBody("{}").withHeader("Content-Type", "application/json")) - ); - assertDoesNotThrow(connector::getHealth, "Request failed unexpectedly"); + try (var connector3 = HTTPVaultConnector.builder(wireMock.getHttpBaseUrl()).withNumberOfRetries(1).withTimeout(250).build()) { + stubFor( + WireMock.any(anyUrl()) + .willReturn(aResponse().withStatus(500)) + .willReturn(aResponse().withStatus(500)) + .willReturn(aResponse().withStatus(500)) + .willReturn(aResponse().withStatus(200).withBody("{}").withHeader("Content-Type", "application/json")) + ); + assertDoesNotThrow(() -> connector3.sys().getHealth(), "Request failed unexpectedly"); + } } /** @@ -160,7 +160,7 @@ class HTTPVaultConnectorTest { } ConnectionException e = assertThrows( ConnectionException.class, - connector::sealStatus, + () -> connector.sys().sealStatus(), "Querying seal status succeeded on invalid instance" ); assertEquals("Unable to connect to Vault server", e.getMessage(), "Unexpected exception message"); @@ -178,7 +178,7 @@ class HTTPVaultConnectorTest { } ConnectionException e = assertThrows( ConnectionException.class, - connector::getHealth, + () -> connector.sys().getHealth(), "Querying health status succeeded on invalid instance" ); assertEquals("Unable to connect to Vault server", e.getMessage(), "Unexpected exception message"); @@ -196,21 +196,21 @@ class HTTPVaultConnectorTest { mockHttpResponse(200, "invalid", "application/json"); // Now test the methods. - assertParseError(connector::sealStatus, "sealStatus() succeeded on invalid instance"); - assertParseError(() -> connector.unseal("key"), "unseal() succeeded on invalid instance"); - assertParseError(connector::getHealth, "getHealth() succeeded on invalid instance"); - assertParseError(connector::getAuthBackends, "getAuthBackends() succeeded on invalid instance"); + assertParseError(() -> connector.sys().sealStatus(), "sys().sealStatus() succeeded on invalid instance"); + assertParseError(() -> connector.sys().unseal("key"), "sys().unseal() succeeded on invalid instance"); + assertParseError(() -> connector.sys().getHealth(), "sys().getHealth() succeeded on invalid instance"); + assertParseError(() -> connector.sys().getAuthBackends(), "sys().getAuthBackends() succeeded on invalid instance"); assertParseError(() -> connector.authToken("token"), "authToken() succeeded on invalid instance"); - assertParseError(() -> connector.lookupAppRole("roleName"), "lookupAppRole() succeeded on invalid instance"); - assertParseError(() -> connector.getAppRoleID("roleName"), "getAppRoleID() succeeded on invalid instance"); - assertParseError(() -> connector.createAppRoleSecret("roleName"), "createAppRoleSecret() succeeded on invalid instance"); - assertParseError(() -> connector.lookupAppRoleSecret("roleName", "secretID"), "lookupAppRoleSecret() succeeded on invalid instance"); - assertParseError(connector::listAppRoles, "listAppRoles() succeeded on invalid instance"); - assertParseError(() -> connector.listAppRoleSecrets("roleName"), "listAppRoleSecrets() succeeded on invalid instance"); + assertParseError(() -> connector.appRole().lookup("roleName"), "appRole().lookup() succeeded on invalid instance"); + assertParseError(() -> connector.appRole().getRoleID("roleName"), "appRole().getRoleID() succeeded on invalid instance"); + assertParseError(() -> connector.appRole().createSecret("roleName"), "appRole().createSecret() succeeded on invalid instance"); + assertParseError(() -> connector.appRole().lookupSecret("roleName", "secretID"), "appRole().lookupSecret() succeeded on invalid instance"); + assertParseError(() -> connector.appRole().listRoles(), "appRole().listRoles() succeeded on invalid instance"); + assertParseError(() -> connector.appRole().listSecrets("roleName"), "appRole().listSecrets() succeeded on invalid instance"); assertParseError(() -> connector.read("key"), "read() succeeded on invalid instance"); assertParseError(() -> connector.list("path"), "list() succeeded on invalid instance"); assertParseError(() -> connector.renew("leaseID"), "renew() succeeded on invalid instance"); - assertParseError(() -> connector.lookupToken("token"), "lookupToken() succeeded on invalid instance"); + assertParseError(() -> connector.token().lookup("token"), "token().lookup() succeeded on invalid instance"); } private void assertParseError(Executable executable, String message) { @@ -232,32 +232,32 @@ class HTTPVaultConnectorTest { // Now test the methods expecting a 204. assertThrows( InvalidResponseException.class, - () -> connector.createAppRole("appID", Collections.singletonList("policy")), - "createAppRole() with 200 response succeeded" + () -> connector.appRole().create("appID", Collections.singletonList("policy")), + "appRole().create() with 200 response succeeded" ); assertThrows( InvalidResponseException.class, - () -> connector.deleteAppRole("roleName"), - "deleteAppRole() with 200 response succeeded" + () -> connector.delete("roleName"), + "appRole().delete() with 200 response succeeded" ); assertThrows( InvalidResponseException.class, - () -> connector.setAppRoleID("roleName", "roleID"), - "setAppRoleID() with 200 response succeeded" + () -> connector.appRole().setRoleID("roleName", "roleID"), + "appRole().setRoleID() with 200 response succeeded" ); assertThrows( InvalidResponseException.class, - () -> connector.destroyAppRoleSecret("roleName", "secretID"), - "destroyAppRoleSecret() with 200 response succeeded" + () -> connector.appRole().destroySecret("roleName", "secretID"), + "appRole().destroySecret() with 200 response succeeded" ); assertThrows( InvalidResponseException.class, - () -> connector.destroyAppRoleSecret("roleName", "secretUD"), - "destroyAppRoleSecret() with 200 response succeeded" + () -> connector.appRole().destroySecret("roleName", "secretUD"), + "appRole().destroySecret() with 200 response succeeded" ); assertThrows(