From 98b5fb930c6e3036b21db2ab9dbdae6a94198983 Mon Sep 17 00:00:00 2001 From: tkledkov Date: Mon, 10 Oct 2022 17:45:27 +0300 Subject: [PATCH] vjd-10 Retry logic refactoring --- .../io/github/jopenlibs/vault/api/Auth.java | 1661 ++++++++--------- .../io/github/jopenlibs/vault/api/Debug.java | 147 +- .../io/github/jopenlibs/vault/api/Leases.java | 265 +-- .../github/jopenlibs/vault/api/Logical.java | 718 ++++--- .../jopenlibs/vault/api/OperationsBase.java | 62 + .../vault/api/database/Database.java | 362 ++-- .../jopenlibs/vault/api/mounts/Mounts.java | 400 ++-- .../vault/api/mounts/TimeToLive.java | 4 +- .../github/jopenlibs/vault/api/pki/Pki.java | 462 +++-- 9 files changed, 1830 insertions(+), 2251 deletions(-) create mode 100644 src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java diff --git a/src/main/java/io/github/jopenlibs/vault/api/Auth.java b/src/main/java/io/github/jopenlibs/vault/api/Auth.java index 90818d4d..87f681d0 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/Auth.java +++ b/src/main/java/io/github/jopenlibs/vault/api/Auth.java @@ -23,21 +23,23 @@ /** *

The implementing class for operations on Vault's /v1/auth/* REST endpoints.

* - *

This class is not intended to be constructed directly. Rather, it is meant to used by way of Vault - * in a DSL-style builder pattern. See the Javadoc comments of each public method for usage examples.

+ *

This class is not intended to be constructed directly. Rather, it is meant to used by way of + * Vault + * in a DSL-style builder pattern. See the Javadoc comments of each public method for + * usage examples.

* * @see Vault#auth() */ -public class Auth { +public class Auth extends OperationsBase { /** - *

A container for all of the options that can be passed to the createToken(TokenRequest) method, to - * avoid that method having an excessive number of parameters (with null typically passed to most - * of them).

+ *

A container for all of the options that can be passed to the createToken(TokenRequest) + * method, to avoid that method having an excessive number of parameters (with null + * typically passed to most of them).

* - *

All properties here are optional. Use of this class resembles a builder pattern (i.e. call the mutator method - * for each property you wish to set), but this class lacks a final build() method as no - * post-initialization logic is necessary.

+ *

All properties here are optional. Use of this class resembles a builder pattern (i.e. + * call the mutator method for each property you wish to set), but this class lacks a final + * build() method as no post-initialization logic is necessary.

*/ public static class TokenRequest implements Serializable { @@ -57,7 +59,8 @@ public static class TokenRequest implements Serializable { private String entityAlias; /** - * @param id (optional) The ID of the client token. Can only be specified by a root token. Otherwise, the token ID is a randomly generated UUID. + * @param id (optional) The ID of the client token. Can only be specified by a root token. + * Otherwise, the token ID is a randomly generated UUID. * @return This object, with its id field populated */ public TokenRequest id(final UUID id) { @@ -66,7 +69,8 @@ public TokenRequest id(final UUID id) { } /** - * @param polices (optional) A list of policies for the token. This must be a subset of the policies belonging to the token + * @param polices (optional) A list of policies for the token. This must be a subset of the + * policies belonging to the token * @return This object, with its polices field populated */ public TokenRequest polices(final List polices) { @@ -75,7 +79,8 @@ public TokenRequest polices(final List polices) { } /** - * @param meta (optional) A map of string to string valued metadata. This is passed through to the audit backends. + * @param meta (optional) A map of string to string valued metadata. This is passed through + * to the audit backends. * @return This object, with its meta field populated */ public TokenRequest meta(final Map meta) { @@ -84,7 +89,8 @@ public TokenRequest meta(final Map meta) { } /** - * @param noParent (optional) If true and set by a root caller, the token will not have the parent token of the caller. This creates a token with no parent. + * @param noParent (optional) If true and set by a root caller, the token will not have the + * parent token of the caller. This creates a token with no parent. * @return This object, with its noParent field populated */ public TokenRequest noParent(final Boolean noParent) { @@ -93,7 +99,8 @@ public TokenRequest noParent(final Boolean noParent) { } /** - * @param noDefaultPolicy (optional) If true the default policy will not be a part of this token's policy set. + * @param noDefaultPolicy (optional) If true the default policy will not be a + * part of this token's policy set. * @return This object, with its noDefaultPolicy field populated */ public TokenRequest noDefaultPolicy(final Boolean noDefaultPolicy) { @@ -102,7 +109,9 @@ public TokenRequest noDefaultPolicy(final Boolean noDefaultPolicy) { } /** - * @param ttl (optional) The TTL period of the token, provided as "1h", where hour is the largest suffix. If not provided, the token is valid for the default lease TTL, or indefinitely if the root policy is used. + * @param ttl (optional) The TTL period of the token, provided as "1h", where hour is the + * largest suffix. If not provided, the token is valid for the default lease TTL, or + * indefinitely if the root policy is used. * @return This object, with its ttl field populated */ public TokenRequest ttl(final String ttl) { @@ -120,7 +129,9 @@ public TokenRequest displayName(final String displayName) { } /** - * @param numUses (optional) The maximum uses for the given token. This can be used to create a one-time-token or limited use token. Defaults to 0, which has no limit to the number of uses. + * @param numUses (optional) The maximum uses for the given token. This can be used to + * create a one-time-token or limited use token. Defaults to 0, which has no limit to the + * number of uses. * @return This object, with its numUses field populated */ public TokenRequest numUses(final Long numUses) { @@ -139,8 +150,8 @@ public TokenRequest role(final String role) { /** * @param renewable Set to false to disable the ability of the token to be renewed past its - * initial TTL. Setting the value to true will allow the token to be renewable up to - * the system/mount maximum TTL. + * initial TTL. Setting the value to true will allow the token to be renewable up to the + * system/mount maximum TTL. * @return This object, with its renewable field populated */ public TokenRequest renewable(final Boolean renewable) { @@ -149,7 +160,6 @@ public TokenRequest renewable(final Boolean renewable) { } /** - * * @param type The token type. Can be "batch" or "service". * @return This object, with its type field populated */ @@ -159,7 +169,6 @@ public TokenRequest type(final String type) { } /** - * * @param explicitMaxTtl If set, the token will have an explicit max TTL set upon it. * @return This object, with its explicitMaxTtl field populated */ @@ -169,7 +178,6 @@ public TokenRequest explicitMaxTtl(final String explicitMaxTtl) { } /** - * * @param period If specified, the token will be periodic * @return This object, with its period field populated */ @@ -179,7 +187,6 @@ public TokenRequest period(final String period) { } /** - * * @param entityAlias Name of the entity alias to associate with during token creation. * @return This object, with its period field populated */ @@ -245,12 +252,11 @@ public String getEntityAlias() { } } - private final VaultConfig config; - private String nameSpace; public Auth(final VaultConfig config) { - this.config = config; + super(config); + if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) { this.nameSpace = this.config.getNameSpace(); } @@ -262,8 +268,8 @@ public Auth withNameSpace(final String nameSpace) { } /** - *

Operation to create an authentication token. Relies on another token already being present in - * the VaultConfig instance. Example usage:

+ *

Operation to create an authentication token. Relies on another token already being + * present in the VaultConfig instance. Example usage:

* *
*
{@code
@@ -284,8 +290,8 @@ public AuthResponse createToken(final TokenRequest tokenRequest) throws VaultExc
     }
 
     /**
-     * 

Operation to create an authentication token. Relies on another token already being present in - * the VaultConfig instance. Example usage:

+ *

Operation to create an authentication token. Relies on another token already being + * present in the VaultConfig instance. Example usage:

* *
*
{@code
@@ -297,97 +303,109 @@ public AuthResponse createToken(final TokenRequest tokenRequest) throws VaultExc
      * }
*
* - * @param tokenRequest A container of optional configuration parameters - * @param tokenAuthMount The mount name of the token authentication back end. If null, defaults to "token" + * @param tokenRequest A container of optional configuration parameters + * @param tokenAuthMount The mount name of the token authentication back end. If null, defaults + * to "token" * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ - public AuthResponse createToken(final TokenRequest tokenRequest, final String tokenAuthMount) throws VaultException { - int retryCount = 0; - + public AuthResponse createToken(final TokenRequest tokenRequest, final String tokenAuthMount) + throws VaultException { final String mount = tokenAuthMount != null ? tokenAuthMount : "token"; - while (true) { - try { - // Parse parameters to JSON - final JsonObject jsonObject = Json.object(); - - if (tokenRequest.getId() != null) jsonObject.add("id", tokenRequest.getId().toString()); - if (tokenRequest.getPolices() != null && !tokenRequest.getPolices().isEmpty()) { - jsonObject.add( - "policies", - Json.array(tokenRequest.getPolices().toArray(new String[0])) - ); //NOPMD - } - if (tokenRequest.getMeta() != null && !tokenRequest.getMeta().isEmpty()) { - final JsonObject metaMap = Json.object(); - for (final Map.Entry entry : tokenRequest.getMeta().entrySet()) { - metaMap.add(entry.getKey(), entry.getValue()); - } - jsonObject.add("meta", metaMap); - } - if (tokenRequest.getNoParent() != null) jsonObject.add("no_parent", tokenRequest.getNoParent()); - if (tokenRequest.getNoDefaultPolicy() != null) - jsonObject.add("no_default_policy", tokenRequest.getNoDefaultPolicy()); - if (tokenRequest.getTtl() != null) jsonObject.add("ttl", tokenRequest.getTtl()); - if (tokenRequest.getDisplayName() != null) jsonObject.add("display_name", tokenRequest.getDisplayName()); - if (tokenRequest.getNumUses() != null) jsonObject.add("num_uses", tokenRequest.getNumUses()); - if (tokenRequest.getRenewable() != null) jsonObject.add("renewable", tokenRequest.getRenewable()); - if (tokenRequest.getType() != null) jsonObject.add("type", tokenRequest.getType()); - if (tokenRequest.getExplicitMaxTtl() != null) jsonObject.add("explicit_max_ttl", tokenRequest.getExplicitMaxTtl()); - if (tokenRequest.getPeriod() != null) jsonObject.add("period", tokenRequest.getPeriod()); - if (tokenRequest.getEntityAlias() != null) jsonObject.add("entity_alias", tokenRequest.getEntityAlias()); - final String requestJson = jsonObject.toString(); - - final StringBuilder urlBuilder = new StringBuilder(config.getAddress())//NOPMD - .append("/v1/auth/") - .append(mount) - .append("/create"); - if (tokenRequest.getRole() != null) { - urlBuilder.append("/").append(tokenRequest.getRole()); - } - final String url = urlBuilder.toString(); - - // HTTP request to Vault - final RestResponse restResponse = new Rest()//NOPMD - .url(url) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new AuthResponse(restResponse, retryCount); - } catch (final Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); + + return retry((attempt) -> { + // Parse parameters to JSON + final JsonObject jsonObject = Json.object(); + + if (tokenRequest.getId() != null) { + jsonObject.add("id", tokenRequest.getId().toString()); + } + if (tokenRequest.getPolices() != null && !tokenRequest.getPolices().isEmpty()) { + jsonObject.add( + "policies", + Json.array(tokenRequest.getPolices().toArray(new String[0])) + ); //NOPMD + } + + if (tokenRequest.getMeta() != null && !tokenRequest.getMeta().isEmpty()) { + final JsonObject metaMap = Json.object(); + for (final Map.Entry entry : tokenRequest.getMeta().entrySet()) { + metaMap.add(entry.getKey(), entry.getValue()); } + jsonObject.add("meta", metaMap); } - } + if (tokenRequest.getNoParent() != null) { + jsonObject.add("no_parent", tokenRequest.getNoParent()); + } + if (tokenRequest.getNoDefaultPolicy() != null) { + jsonObject.add("no_default_policy", tokenRequest.getNoDefaultPolicy()); + } + if (tokenRequest.getTtl() != null) { + jsonObject.add("ttl", tokenRequest.getTtl()); + } + if (tokenRequest.getDisplayName() != null) { + jsonObject.add("display_name", tokenRequest.getDisplayName()); + } + if (tokenRequest.getNumUses() != null) { + jsonObject.add("num_uses", tokenRequest.getNumUses()); + } + if (tokenRequest.getRenewable() != null) { + jsonObject.add("renewable", tokenRequest.getRenewable()); + } + if (tokenRequest.getType() != null) { + jsonObject.add("type", tokenRequest.getType()); + } + if (tokenRequest.getExplicitMaxTtl() != null) { + jsonObject.add("explicit_max_ttl", tokenRequest.getExplicitMaxTtl()); + } + if (tokenRequest.getPeriod() != null) { + jsonObject.add("period", tokenRequest.getPeriod()); + } + if (tokenRequest.getEntityAlias() != null) { + jsonObject.add("entity_alias", tokenRequest.getEntityAlias()); + } + + final String requestJson = jsonObject.toString(); + + final StringBuilder urlBuilder = new StringBuilder(config.getAddress())//NOPMD + .append("/v1/auth/") + .append(mount) + .append("/create"); + if (tokenRequest.getRole() != null) { + urlBuilder.append("/").append(tokenRequest.getRole()); + } + final String url = urlBuilder.toString(); + + // HTTP request to Vault + final RestResponse restResponse = new Rest()//NOPMD + .url(url) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); + } + + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + + return new AuthResponse(restResponse, attempt); + }); } /** @@ -401,66 +419,58 @@ public AuthResponse createToken(final TokenRequest tokenRequest, final String to * }
*
* - * NOTE: As of Vault 0.6.1, Hashicorp has deprecated the App ID authentication backend in - * favor of AppRole. This method will be removed at some point after this backend has been eliminated from Vault. + * NOTE: As of Vault 0.6.1, Hashicorp has deprecated the App ID + * authentication backend in favor of AppRole. This method will be removed at some point after + * this backend has been eliminated from Vault. * - * @param path The path on which the authentication is performed (e.g. auth/app-id/login) - * @param appId The app-id used for authentication + * @param path The path on which the authentication is performed (e.g. + * auth/app-id/login) + * @param appId The app-id used for authentication * @param userId The user-id used for authentication * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ @Deprecated - public AuthResponse loginByAppID(final String path, final String appId, final String userId) throws VaultException { - int retryCount = 0; - while (true) { - try { - // HTTP request to Vault - final String requestJson = Json.object().add("app_id", appId).add("user_id", userId).toString(); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/auth/" + path) - .optionalHeader("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new AuthResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + public AuthResponse loginByAppID(final String path, final String appId, final String userId) + throws VaultException { + return retry((attempt) -> { + // HTTP request to Vault + final String requestJson = Json.object().add("app_id", appId).add("user_id", userId) + .toString(); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/auth/" + path) + .optionalHeader("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + + return new AuthResponse(restResponse, attempt); + }); } /** - *

Basic login operation to authenticate to an app-role backend. This version of the overloaded method assumes - * that the auth backend is mounted on the default path (i.e. "/v1/auth/approle"). Example usage:

+ *

Basic login operation to authenticate to an app-role backend. This version of the + * overloaded method assumes that the auth backend is mounted on the default path (i.e. + * "/v1/auth/approle"). Example usage:

* *
*
{@code
@@ -470,19 +480,20 @@ public AuthResponse loginByAppID(final String path, final String appId, final St
      * }
*
* - * @param roleId The role-id used for authentication + * @param roleId The role-id used for authentication * @param secretId The secret-id used for authentication * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ - public AuthResponse loginByAppRole(final String roleId, final String secretId) throws VaultException { + public AuthResponse loginByAppRole(final String roleId, final String secretId) + throws VaultException { return loginByAppRole("approle", roleId, secretId); } /** - *

Basic login operation to authenticate to an app-role backend. This version of the overloaded method - * requires you to explicitly specify the path on which the auth backend is mounted, following the "/v1/auth/" - * prefix. Example usage:

+ *

Basic login operation to authenticate to an app-role backend. This version of the + * overloaded method requires you to explicitly specify the path on which the auth backend is + * mounted, following the "/v1/auth/" prefix. Example usage:

* *
*
{@code
@@ -492,69 +503,59 @@ public AuthResponse loginByAppRole(final String roleId, final String secretId) t
      * }
*
*

- * NOTE: I hate that this method takes the custom mount path as its first parameter, while all of the other - * methods in this class take it as the last parameter (a better practice). I just didn't think about it - * during code review. Now it's difficult to deprecate this, since a version of the method with path as - * the final parameter would have the same method signature. + * NOTE: I hate that this method takes the custom mount path as its first parameter, while all + * of the other methods in this class take it as the last parameter (a better practice). I just + * didn't think about it during code review. Now it's difficult to deprecate this, since a + * version of the method with path as the final parameter would have the same method signature. *

- * I may or may not change this in some future breaking-change major release, especially if we keep adding - * similar overloaded methods elsewhere and need the global consistency. At any rate, going forward no new - * methods should take a custom path as the first parameter. + * I may or may not change this in some future breaking-change major release, especially if we + * keep adding similar overloaded methods elsewhere and need the global consistency. At any + * rate, going forward no new methods should take a custom path as the first parameter. * - * @param path The path on which the authentication is performed, following the "/v1/auth/" prefix (e.g. "approle") - * @param roleId The role-id used for authentication + * @param path The path on which the authentication is performed, following the "/v1/auth/" + * prefix (e.g. "approle") + * @param roleId The role-id used for authentication * @param secretId The secret-id used for authentication * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ - public AuthResponse loginByAppRole(final String path, final String roleId, final String secretId) throws VaultException { - int retryCount = 0; - while (true) { - try { - // HTTP request to Vault - final String requestJson = Json.object().add("role_id", roleId).add("secret_id", secretId).toString(); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/auth/" + path + "/login") - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new AuthResponse(restResponse, retryCount); - } catch (Exception e) { - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + public AuthResponse loginByAppRole(final String path, final String roleId, + final String secretId) throws VaultException { + return retry(attempt -> { + // HTTP request to Vault + final String requestJson = Json.object().add("role_id", roleId) + .add("secret_id", secretId).toString(); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/auth/" + path + "/login") + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + return new AuthResponse(restResponse, attempt); + }); } /** - *

Basic login operation to authenticate to a Username & Password backend. Example usage:

+ *

Basic login operation to authenticate to a Username & Password backend. Example + * usage:

* *
*
{@code
@@ -569,12 +570,14 @@ public AuthResponse loginByAppRole(final String path, final String roleId, final
      * @return The auth token, with additional response metadata
      * @throws VaultException If any error occurs, or unexpected response received from Vault
      */
-    public AuthResponse loginByUserPass(final String username, final String password) throws VaultException {
+    public AuthResponse loginByUserPass(final String username, final String password)
+            throws VaultException {
         return loginByUserPass(username, password, "userpass");
     }
 
     /**
-     * 

Basic login operation to authenticate to a Username & Password backend. Example usage:

+ *

Basic login operation to authenticate to a Username & Password backend. Example + * usage:

* *
*
{@code
@@ -584,58 +587,46 @@ public AuthResponse loginByUserPass(final String username, final String password
      * }
*
* - * @param username The username used for authentication - * @param password The password used for authentication - * @param userpassAuthMount The mount name of the userpass authentication back end. If null, defaults to "userpass" + * @param username The username used for authentication + * @param password The password used for authentication + * @param userpassAuthMount The mount name of the userpass authentication back end. If null, + * defaults to "userpass" * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ - public AuthResponse loginByUserPass(final String username, final String password, final String userpassAuthMount) throws VaultException { - int retryCount = 0; - + public AuthResponse loginByUserPass(final String username, final String password, + final String userpassAuthMount) throws VaultException { final String mount = userpassAuthMount != null ? userpassAuthMount : "userpass"; - while (true) { - try { - // HTTP request to Vault - final String requestJson = Json.object().add("password", password).toString(); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/auth/" + mount + "/login/" + username) - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new AuthResponse(restResponse, retryCount); - } catch (Exception e) { - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + + return retry(attempt -> { + // HTTP request to Vault + final String requestJson = Json.object().add("password", password).toString(); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/auth/" + mount + "/login/" + username) + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + return new AuthResponse(restResponse, attempt); + }); } /** @@ -654,7 +645,8 @@ public AuthResponse loginByUserPass(final String username, final String password * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ - public AuthResponse loginByLDAP(final String username, final String password) throws VaultException { + public AuthResponse loginByLDAP(final String username, final String password) + throws VaultException { // TODO: Determine a way to feasibly add integration test coverage return loginByLDAP(username, password, "ldap"); } @@ -670,20 +662,23 @@ public AuthResponse loginByLDAP(final String username, final String password) th * }
*
* - * @param username The username used for authentication - * @param password The password used for authentication - * @param ldapAuthMount The mount name of the ldap authentication back end. If null, defaults to "ldap" + * @param username The username used for authentication + * @param password The password used for authentication + * @param ldapAuthMount The mount name of the ldap authentication back end. If null, defaults + * to "ldap" * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ - public AuthResponse loginByLDAP(final String username, final String password, final String ldapAuthMount) throws VaultException { + public AuthResponse loginByLDAP(final String username, final String password, + final String ldapAuthMount) throws VaultException { final String mount = ldapAuthMount != null ? ldapAuthMount : "ldap"; // LDAP has the same API like Username & Password backend return loginByUserPass(username, password, mount); } /** - *

Basic login operation to authenticate to a AWS backend using EC2 authentication. Example usage:

+ *

Basic login operation to authenticate to a AWS backend using EC2 authentication. Example + * usage:

* *
*
{@code
@@ -693,78 +688,68 @@ public AuthResponse loginByLDAP(final String username, final String password, fi
      * }
*
* - * @param role Name of the role against which the login is being attempted. If role is not specified, then the login endpoint - * looks for a role bearing the name of the AMI ID of the EC2 instance that is trying to login if using the ec2 - * auth method, or the "friendly name" (i.e., role name or username) of the IAM principal authenticated. - * If a matching role is not found, login fails. - * @param identity Base64 encoded EC2 instance identity document. - * @param signature Base64 encoded SHA256 RSA signature of the instance identity document. - * @param nonce Client nonce used for authentication. If null, a new nonce will be generated by Vault + * @param role Name of the role against which the login is being attempted. If role is not + * specified, then the login endpoint looks for a role bearing the name of the AMI ID of the EC2 + * instance that is trying to login if using the ec2 auth method, or the "friendly name" (i.e., + * role name or username) of the IAM principal authenticated. If a matching role is not found, + * login fails. + * @param identity Base64 encoded EC2 instance identity document. + * @param signature Base64 encoded SHA256 RSA signature of the instance identity document. + * @param nonce Client nonce used for authentication. If null, a new nonce will be generated by + * Vault * @param awsAuthMount AWS auth mount * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ // TODO: Needs integration test coverage if possible - public AuthResponse loginByAwsEc2(final String role, final String identity, final String signature, final String nonce, final String awsAuthMount) throws VaultException { - int retryCount = 0; - + public AuthResponse loginByAwsEc2(final String role, final String identity, + final String signature, final String nonce, final String awsAuthMount) + throws VaultException { final String mount = awsAuthMount != null ? awsAuthMount : "aws"; - while (true) { - try { - // HTTP request to Vault - final JsonObject request = Json.object().add("identity", identity) - .add("signature", signature); - if (role != null) { - request.add("role", role); - } - if (nonce != null) { - request.add("nonce", nonce); - } - final String requestJson = request.toString(); - - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/auth/" + mount + "/login") - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new AuthResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + + return retry(attempt -> { + // HTTP request to Vault + final JsonObject request = Json.object().add("identity", identity) + .add("signature", signature); + if (role != null) { + request.add("role", role); } - } + if (nonce != null) { + request.add("nonce", nonce); + } + final String requestJson = request.toString(); + + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/auth/" + mount + "/login") + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); + } + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + return new AuthResponse(restResponse, attempt); + }); } /** - *

Basic login operation to authenticate to a AWS backend using EC2 authentication. Example usage:

+ *

Basic login operation to authenticate to a AWS backend using EC2 authentication. Example + * usage:

* *
*
{@code
@@ -774,75 +759,67 @@ public AuthResponse loginByAwsEc2(final String role, final String identity, fina
      * }
*
* - * @param role Name of the role against which the login is being attempted. If role is not specified, then the login endpoint - * looks for a role bearing the name of the AMI ID of the EC2 instance that is trying to login if using the ec2 - * auth method, or the "friendly name" (i.e., role name or username) of the IAM principal authenticated. - * If a matching role is not found, login fails. - * @param pkcs7 PKCS7 signature of the identity document with all \n characters removed. - * @param nonce Client nonce used for authentication. If null, a new nonce will be generated by Vault + * @param role Name of the role against which the login is being attempted. If role is not + * specified, then the login endpoint looks for a role bearing the name of the AMI ID of the EC2 + * instance that is trying to login if using the ec2 auth method, or the "friendly name" (i.e., + * role name or username) of the IAM principal authenticated. If a matching role is not found, + * login fails. + * @param pkcs7 PKCS7 signature of the identity document with all \n characters removed. + * @param nonce Client nonce used for authentication. If null, a new nonce will be generated by + * Vault * @param awsAuthMount AWS auth mount * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ // TODO: Needs integration test coverage if possible - public AuthResponse loginByAwsEc2(final String role, final String pkcs7, final String nonce, final String awsAuthMount) throws VaultException { - int retryCount = 0; - + public AuthResponse loginByAwsEc2(final String role, final String pkcs7, final String nonce, + final String awsAuthMount) throws VaultException { final String mount = awsAuthMount != null ? awsAuthMount : "aws"; - while (true) { - try { - // HTTP request to Vault - final JsonObject request = Json.object().add("pkcs7", pkcs7); - if (role != null) { - request.add("role", role); - } - if (nonce != null) { - request.add("nonce", nonce); - } - final String requestJson = request.toString(); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/auth/" + mount + "/login") - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new AuthResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + + return retry(attempt -> { + // HTTP request to Vault + final JsonObject request = Json.object().add("pkcs7", pkcs7); + if (role != null) { + request.add("role", role); } - } + if (nonce != null) { + request.add("nonce", nonce); + } + final String requestJson = request.toString(); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/auth/" + mount + "/login") + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); + } + + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + + return new AuthResponse(restResponse, attempt); + }); } /** - *

Basic login operation to authenticate to a AWS backend using IAM authentication. Example usage:

+ *

Basic login operation to authenticate to a AWS backend using IAM authentication. Example + * usage:

* *
*
{@code
@@ -852,74 +829,64 @@ public AuthResponse loginByAwsEc2(final String role, final String pkcs7, final S
      * }
*
* - * @param role Name of the role against which the login is being attempted. If role is not specified, then the login endpoint - * looks for a role bearing the name of the AMI ID of the EC2 instance that is trying to login if using the ec2 - * auth method, or the "friendly name" (i.e., role name or username) of the IAM principal authenticated. - * If a matching role is not found, login fails. - * @param iamRequestUrl PKCS7 signature of the identity document with all \n characters removed.Base64-encoded HTTP URL used in the signed request. - * Most likely just aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8= (base64-encoding of https://sts.amazonaws.com/) as most requests will - * probably use POST with an empty URI. - * @param iamRequestBody Base64-encoded body of the signed request. Most likely QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNQ== which is - * the base64 encoding of Action=GetCallerIdentity&Version=2011-06-15. + * @param role Name of the role against which the login is being attempted. If role is not + * specified, then the login endpoint looks for a role bearing the name of the AMI ID of the EC2 + * instance that is trying to login if using the ec2 auth method, or the "friendly name" (i.e., + * role name or username) of the IAM principal authenticated. If a matching role is not found, + * login fails. + * @param iamRequestUrl PKCS7 signature of the identity document with all \n characters + * removed.Base64-encoded HTTP URL used in the signed request. Most likely just + * aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8= (base64-encoding of https://sts.amazonaws.com/) as most + * requests will probably use POST with an empty URI. + * @param iamRequestBody Base64-encoded body of the signed request. Most likely + * QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNQ== which is the base64 encoding of + * Action=GetCallerIdentity&Version=2011-06-15. * @param iamRequestHeaders Request headers - * @param awsAuthMount AWS auth mount + * @param awsAuthMount AWS auth mount * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ - public AuthResponse loginByAwsIam(final String role, final String iamRequestUrl, final String iamRequestBody, final String iamRequestHeaders, final String awsAuthMount) throws VaultException { - int retryCount = 0; - + public AuthResponse loginByAwsIam(final String role, final String iamRequestUrl, + final String iamRequestBody, final String iamRequestHeaders, final String awsAuthMount) + throws VaultException { final String mount = awsAuthMount != null ? awsAuthMount : "aws"; - while (true) { - try { - // HTTP request to Vault - final JsonObject request = Json.object().add("iam_request_url", iamRequestUrl) - .add("iam_request_body", iamRequestBody) - .add("iam_request_headers", iamRequestHeaders) - .add("iam_http_request_method", "POST"); - if (role != null) { - request.add("role", role); - } - final String requestJson = request.toString(); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/auth/" + mount + "/login") - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new AuthResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + + return retry(attempt -> { + // HTTP request to Vault + final JsonObject request = Json.object().add("iam_request_url", iamRequestUrl) + .add("iam_request_body", iamRequestBody) + .add("iam_request_headers", iamRequestHeaders) + .add("iam_http_request_method", "POST"); + if (role != null) { + request.add("role", role); } - } + final String requestJson = request.toString(); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/auth/" + mount + "/login") + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); + } + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + return new AuthResponse(restResponse, attempt); + }); } /** @@ -952,61 +919,46 @@ public AuthResponse loginByGithub(final String githubToken) throws VaultExceptio * } * * - * @param githubToken The app-id used for authentication - * @param githubAuthMount The mount name of the github authentication back end. If null, defaults to "github" + * @param githubToken The app-id used for authentication + * @param githubAuthMount The mount name of the github authentication back end. If null, + * defaults to "github" * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ - public AuthResponse loginByGithub(final String githubToken, final String githubAuthMount) throws VaultException { - + public AuthResponse loginByGithub(final String githubToken, final String githubAuthMount) + throws VaultException { // TODO: Add (optional?) integration test coverage - - int retryCount = 0; - final String mount = githubAuthMount != null ? githubAuthMount : "github"; - while (true) { - try { - // HTTP request to Vault - final String requestJson = Json.object().add("token", githubToken).toString(); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/auth/" + mount + "/login") - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new AuthResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + + return retry(attempt -> { + // HTTP request to Vault + final String requestJson = Json.object().add("token", githubToken).toString(); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/auth/" + mount + "/login") + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + return new AuthResponse(restResponse, attempt); + }); } /** @@ -1022,60 +974,45 @@ public AuthResponse loginByGithub(final String githubToken, final String githubA * * @param provider Provider of JWT token. * @param role The gcp role used for authentication - * @param jwt The JWT token for the role + * @param jwt The JWT token for the role * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ // TODO: Needs integration test coverage if possible - public AuthResponse loginByJwt(final String provider, final String role, final String jwt) throws VaultException { - int retryCount = 0; - - while (true) { - try { - // HTTP request to Vault - final String requestJson = Json.object().add("role", role).add("jwt", jwt).toString(); - final RestResponse restResponse = new Rest() - .url(config.getAddress() + "/v1/auth/" + provider + "/login") - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new AuthResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + public AuthResponse loginByJwt(final String provider, final String role, final String jwt) + throws VaultException { + return retry(attempt -> { + // HTTP request to Vault + final String requestJson = Json.object().add("role", role).add("jwt", jwt) + .toString(); + final RestResponse restResponse = new Rest() + .url(config.getAddress() + "/v1/auth/" + provider + "/login") + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + return new AuthResponse(restResponse, attempt); + }); } - /** *

Basic login operation to authenticate to an GCP backend. Example usage:

* @@ -1088,7 +1025,7 @@ public AuthResponse loginByJwt(final String provider, final String role, final S * * * @param role The gcp role used for authentication - * @param jwt The JWT token for the role + * @param jwt The JWT token for the role * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ @@ -1111,16 +1048,19 @@ public AuthResponse loginByGCP(final String role, final String jwt) throws Vault * * * @param role The kubernetes role used for authentication - * @param jwt The JWT token for the role, typically read from /var/run/secrets/kubernetes.io/serviceaccount/token + * @param jwt The JWT token for the role, typically read from + * /var/run/secrets/kubernetes.io/serviceaccount/token * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ - public AuthResponse loginByKubernetes(final String role, final String jwt) throws VaultException { + public AuthResponse loginByKubernetes(final String role, final String jwt) + throws VaultException { return loginByJwt("kubernetes", role, jwt); } /** - *

Basic login operation to authenticate using Vault's TLS Certificate auth backend. Example usage:

+ *

Basic login operation to authenticate using Vault's TLS Certificate auth backend. + * Example usage:

* *
*
{@code
@@ -1147,7 +1087,8 @@ public AuthResponse loginByCert() throws VaultException {
     }
 
     /**
-     * 

Basic login operation to authenticate using Vault's TLS Certificate auth backend. Example usage:

+ *

Basic login operation to authenticate using Vault's TLS Certificate auth backend. + * Example usage:

* *
*
{@code
@@ -1166,59 +1107,47 @@ public AuthResponse loginByCert() throws VaultException {
      * }
*
* - * @param certAuthMount The mount name of the cert authentication back end. If null, defaults to "cert" + * @param certAuthMount The mount name of the cert authentication back end. If null, defaults + * to "cert" * @return The auth token, with additional response metadata * @throws VaultException If any error occurs, or unexpected response received from Vault */ public AuthResponse loginByCert(final String certAuthMount) throws VaultException { - int retryCount = 0; - final String mount = certAuthMount != null ? certAuthMount : "cert"; - while (true) { - try { - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/auth/" + mount + "/login") - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new AuthResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + + return retry(attempt -> { + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/auth/" + mount + "/login") + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + + return new AuthResponse(restResponse, attempt); + }); } /** - *

Renews the lease associated with the calling token. This version of the method tells Vault to use the - * default lifespan for the new lease.

+ *

Renews the lease associated with the calling token. This version of the method tells + * Vault to use the default lifespan for the new lease.

* * @return The response information returned from Vault * @throws VaultException If any error occurs, or unexpected response received from Vault @@ -1228,9 +1157,9 @@ public AuthResponse renewSelf() throws VaultException { } /** - *

Renews the lease associated with the calling token. This version of the method accepts a parameter to - * explicitly declare how long the new lease period should be (in seconds). The Vault documentation suggests - * that this value may be ignored, however.

+ *

Renews the lease associated with the calling token. This version of the method accepts a + * parameter to explicitly declare how long the new lease period should be (in seconds). The + * Vault documentation suggests that this value may be ignored, however.

* * @param increment The number of seconds requested for the new lease lifespan * @return The response information returned from Vault @@ -1241,63 +1170,53 @@ public AuthResponse renewSelf(final long increment) throws VaultException { } /** - *

Renews the lease associated with the calling token. This version of the method accepts a parameter to - * explicitly declare how long the new lease period should be (in seconds). The Vault documentation suggests - * that this value may be ignored, however.

+ *

Renews the lease associated with the calling token. This version of the method accepts a + * parameter to explicitly declare how long the new lease period should be (in seconds). The + * Vault documentation suggests that this value may be ignored, however.

* - * @param increment The number of seconds requested for the new lease lifespan - * @param tokenAuthMount The mount name of the token authentication back end. If null, defaults to "token" + * @param increment The number of seconds requested for the new lease lifespan + * @param tokenAuthMount The mount name of the token authentication back end. If null, defaults + * to "token" * @return The response information returned from Vault * @throws VaultException If any error occurs, or unexpected response received from Vault */ - public AuthResponse renewSelf(final long increment, final String tokenAuthMount) throws VaultException { - int retryCount = 0; - + public AuthResponse renewSelf(final long increment, final String tokenAuthMount) + throws VaultException { final String mount = tokenAuthMount != null ? tokenAuthMount : "token"; - while (true) { - try { - // HTTP request to Vault - final String requestJson = Json.object().add("increment", increment).toString(); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/auth/" + mount + "/renew-self") - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .body(increment < 0 ? null : requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new AuthResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + + return retry(attempt -> { + // HTTP request to Vault + final String requestJson = Json.object().add("increment", increment).toString(); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/auth/" + mount + "/renew-self") + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .body(increment < 0 ? null : requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + + return new AuthResponse(restResponse, attempt); + }); } /** @@ -1313,60 +1232,46 @@ public LookupResponse lookupSelf() throws VaultException { /** *

Returns information about the current client token.

* - * @param tokenAuthMount The mount name of the token authentication back end. If null, defaults to "token" + * @param tokenAuthMount The mount name of the token authentication back end. If null, defaults + * to "token" * @return The response information returned from Vault * @throws VaultException If any error occurs, or unexpected response received from Vault */ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultException { - int retryCount = 0; final String mount = tokenAuthMount != null ? tokenAuthMount : "token"; - while (true) { - try { - // HTTP request to Vault - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/auth/" + mount + "/lookup-self") - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .get(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType(); - if (!"application/json".equals(mimeType)) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new LookupResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + + return retry(attempt -> { + // HTTP request to Vault + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/auth/" + mount + "/lookup-self") + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .get(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + final String mimeType = restResponse.getMimeType(); + if (!"application/json".equals(mimeType)) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + return new LookupResponse(restResponse, attempt); + }); } /** - *

Returns information about the current client token for a wrapped token, for which the lookup endpoint is - * different at "sys/wrapping/lookup". Example usage:

+ *

Returns information about the current client token for a wrapped token, for which the + * lookup endpoint is different at "sys/wrapping/lookup". Example usage:

* *
*
{@code
@@ -1383,49 +1288,34 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept
      * @throws VaultException If any error occurs, or unexpected response received from Vault
      */
     public LogicalResponse lookupWrap() throws VaultException {
-        int retryCount = 0;
-        while (true) {
-            try {
-                // HTTP request to Vault
-                final RestResponse restResponse = new Rest()//NOPMD
-                        .url(config.getAddress() + "/v1/sys/wrapping/lookup")
-                        .header("X-Vault-Token", config.getToken())
-                        .header("X-Vault-Namespace", this.nameSpace)
-                        .connectTimeoutSeconds(config.getOpenTimeout())
-                        .readTimeoutSeconds(config.getReadTimeout())
-                        .sslVerification(config.getSslConfig().isVerify())
-                        .sslContext(config.getSslConfig().getSslContext())
-                        .get();
-
-                // Validate restResponse
-                if (restResponse.getStatus() != 200) {
-                    throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(),
-                            restResponse.getStatus());
-                }
-                final String mimeType = restResponse.getMimeType();
-                if (!"application/json".equals(mimeType)) {
-                    throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus());
-                }
-                return new LogicalResponse(restResponse, retryCount, Logical.logicalOperations.authentication);
-            } catch (Exception e) {
-                // If there are retries to perform, then pause for the configured interval and then execute the loop
-                // again...
-                if (retryCount < config.getMaxRetries()) {
-                    retryCount++;
-                    try {
-                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
-                        Thread.sleep(retryIntervalMilliseconds);
-                    } catch (InterruptedException e1) {
-                        e1.printStackTrace();
-                    }
-                } else if (e instanceof VaultException) {
-                    // ... otherwise, give up.
-                    throw (VaultException) e;
-                } else {
-                    throw new VaultException(e);
-                }
+        return retry(attempt -> {
+            // HTTP request to Vault
+            final RestResponse restResponse = new Rest()//NOPMD
+                    .url(config.getAddress() + "/v1/sys/wrapping/lookup")
+                    .header("X-Vault-Token", config.getToken())
+                    .header("X-Vault-Namespace", this.nameSpace)
+                    .connectTimeoutSeconds(config.getOpenTimeout())
+                    .readTimeoutSeconds(config.getReadTimeout())
+                    .sslVerification(config.getSslConfig().isVerify())
+                    .sslContext(config.getSslConfig().getSslContext())
+                    .get();
+
+            // Validate restResponse
+            if (restResponse.getStatus() != 200) {
+                throw new VaultException(
+                        "Vault responded with HTTP status code: " + restResponse.getStatus(),
+                        restResponse.getStatus());
             }
-        }
+
+            final String mimeType = restResponse.getMimeType();
+            if (!"application/json".equals(mimeType)) {
+                throw new VaultException("Vault responded with MIME type: " + mimeType,
+                        restResponse.getStatus());
+            }
+
+            return new LogicalResponse(restResponse, attempt,
+                    Logical.logicalOperations.authentication);
+        });
     }
 
     /**
@@ -1440,59 +1330,46 @@ public void revokeSelf() throws VaultException {
     /**
      * 

Revokes current client token.

* - * @param tokenAuthMount The mount name of the token authentication back end. If null, defaults to "token" + * @param tokenAuthMount The mount name of the token authentication back end. If null, defaults + * to "token" * @throws VaultException If any error occurs, or unexpected response received from Vault */ public void revokeSelf(final String tokenAuthMount) throws VaultException { - int retryCount = 0; final String mount = tokenAuthMount != null ? tokenAuthMount : "token"; - while (true) { - try { - // HTTP request to Vault - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/auth/" + mount + "/revoke-self") - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 204) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - return; - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + + retry(attempt -> { + // HTTP request to Vault + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/auth/" + mount + "/revoke-self") + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 204) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + + return null; + }); } /** - *

Returns the original response inside the wrapped auth token. This method is useful if you need to unwrap a - * token without being authenticated. See {@link #unwrap(String)} if you need to do that authenticated.

+ *

Returns the original response inside the wrapped auth token. This method is useful if you + * need to unwrap a token without being authenticated. See {@link #unwrap(String)} if you need + * to do that authenticated.

* *

In the example below, you cannot use twice the {@code VaultConfig}, since - * after the first usage of the {@code wrappingToken}, it is not usable anymore. You need to use the - * {@code unwrappedToken} in a new vault configuration to continue. Example usage:

+ * after the first usage of the {@code wrappingToken}, it is not usable anymore. You need to use + * the {@code unwrappedToken} in a new vault configuration to continue. Example usage:

* *
*
{@code
@@ -1517,18 +1394,19 @@ public UnwrapResponse unwrap() throws VaultException {
      *
      * 

Returns the original response inside the given wrapping token. Unlike simply reading * {@code cubbyhole/response} (which is deprecated), this endpoint provides additional - * validation checks on the token, returns the original value on the wire rather than - * a JSON string representation of it, and ensures that the response is properly audit-logged.

+ * validation checks on the token, returns the original value on the wire rather than a JSON + * string representation of it, and ensures that the response is properly audit-logged.

* *

This endpoint can be used by using a wrapping token as the client token in the API call, - * in which case the token parameter is not required; or, a different token with permissions - * to access this endpoint can make the call and pass in the wrapping token in - * the token parameter. Do not use the wrapping token in both locations; - * this will cause the wrapping token to be revoked but the value to be unable to be looked up, - * as it will basically be a double-use of the token!

+ * in which case the token parameter is not required; or, a different token with permissions to + * access this endpoint can make the call and pass in the wrapping token in the token parameter. + * Do not use the wrapping token in both locations; this will cause the wrapping token to be + * revoked but the value to be unable to be looked up, as it will basically be a double-use of + * the token!

* - *

In the example below, {@code authToken} is NOT your wrapped token, and should have unwrapping permissions. - * The unwrapped data in {@link UnwrapResponse#getData()}. Example usage:

+ *

In the example below, {@code authToken} is NOT your wrapped token, and should have + * unwrapping permissions. The unwrapped data in {@link UnwrapResponse#getData()}. Example + * usage:

* *
*
{@code
@@ -1552,78 +1430,65 @@ public UnwrapResponse unwrap() throws VaultException {
      * }
*
* - * @param wrappedToken Specifies the wrapping token ID, do NOT also put this in your {@link VaultConfig#getToken()}, - * if token is {@code null}, this method will unwrap the auth token in {@link VaultConfig#getToken()} + * @param wrappedToken Specifies the wrapping token ID, do NOT also put this in your + * {@link VaultConfig#getToken()}, if token is {@code null}, this method will unwrap the auth + * token in {@link VaultConfig#getToken()} * @return The response information returned from Vault * @throws VaultException If any error occurs, or unexpected response received from Vault * @see #wrap(JsonObject, int) * @see #unwrap() */ public UnwrapResponse unwrap(final String wrappedToken) throws VaultException { - int retryCount = 0; - while (true) { - try { - // Parse parameters to JSON - final JsonObject jsonObject = Json.object(); - if (wrappedToken != null) { - jsonObject.add("token", wrappedToken); - } + return retry(attempt -> { + // Parse parameters to JSON + final JsonObject jsonObject = Json.object(); + if (wrappedToken != null) { + jsonObject.add("token", wrappedToken); + } - final String requestJson = jsonObject.toString(); - final String url = config.getAddress() + "/v1/sys/wrapping/unwrap"; - - // HTTP request to Vault - final RestResponse restResponse = new Rest() - .url(url) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } - return new UnwrapResponse(restResponse, retryCount); - } catch (final Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the - // loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + final String requestJson = jsonObject.toString(); + final String url = config.getAddress() + "/v1/sys/wrapping/unwrap"; + + // HTTP request to Vault + final RestResponse restResponse = new Rest() + .url(url) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + + return new UnwrapResponse(restResponse, attempt); + }); } /** *

Provide access to the {@code /sys/wrapping/wrap} endpoint.

* *

This provides a powerful mechanism for information sharing in many environments. - * In the types of scenarios, often the best practical option is to provide cover - * for the secret information, be able to detect malfeasance (interception, tampering), - * and limit lifetime of the secret's exposure. - * Response wrapping performs all three of these duties:

+ * In the types of scenarios, often the best practical option is to provide cover for the secret + * information, be able to detect malfeasance (interception, tampering), and limit lifetime of + * the secret's exposure. Response wrapping performs all three of these duties:

* *
    *
  • It provides cover by ensuring that the value being transmitted across the wire is @@ -1673,57 +1538,41 @@ public UnwrapResponse unwrap(final String wrappedToken) throws VaultException { public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws VaultException { Objects.requireNonNull(jsonObject); - int retryCount = 0; - while (true) { - try { - // Parse parameters to JSON - final String requestJson = jsonObject.toString(); - final String url = config.getAddress() + "/v1/sys/wrapping/wrap"; - - // HTTP request to Vault - final RestResponse restResponse = new Rest() - .url(url) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Wrap-TTL", Integer.toString(ttlInSec)) - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - - final String mimeType = restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); - if (!mimeType.equals("application/json")) { - throw new VaultException("Vault responded with MIME type: " + mimeType, restResponse.getStatus()); - } + return retry(attempt -> { + // Parse parameters to JSON + final String requestJson = jsonObject.toString(); + final String url = config.getAddress() + "/v1/sys/wrapping/wrap"; + + // HTTP request to Vault + final RestResponse restResponse = new Rest() + .url(url) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Wrap-TTL", Integer.toString(ttlInSec)) + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); + } - return new WrapResponse(restResponse, retryCount); - } catch (final Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the - // loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + final String mimeType = + restResponse.getMimeType() == null ? "null" : restResponse.getMimeType(); + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); } - } + + return new WrapResponse(restResponse, attempt); + }); } } diff --git a/src/main/java/io/github/jopenlibs/vault/api/Debug.java b/src/main/java/io/github/jopenlibs/vault/api/Debug.java index 48b8b8b1..d901af26 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/Debug.java +++ b/src/main/java/io/github/jopenlibs/vault/api/Debug.java @@ -4,28 +4,28 @@ import io.github.jopenlibs.vault.VaultException; import io.github.jopenlibs.vault.response.HealthResponse; import io.github.jopenlibs.vault.rest.Rest; -import io.github.jopenlibs.vault.rest.RestException; import io.github.jopenlibs.vault.rest.RestResponse; import java.util.HashSet; import java.util.Set; /** - *

    The implementing class for operations on REST endpoints, under the "Debug" section of the Vault HTTP API + *

    The implementing class for operations on REST endpoints, under the "Debug" section of the + * Vault HTTP API * docs (https://www.vaultproject.io/docs/http/index.html).

    * *

    This class is not intended to be constructed directly. Rather, it is meant to used by way of - * Vault in a DSL-style builder pattern. See the Javadoc comments of each public + * Vault in a DSL-style builder pattern. See the Javadoc comments of each + * public * method for usage examples.

    */ -public class Debug { - - private final VaultConfig config; +public class Debug extends OperationsBase { private String nameSpace; public Debug(final VaultConfig config) { - this.config = config; + super(config); + if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) { this.nameSpace = this.config.getNameSpace(); } @@ -42,8 +42,10 @@ public Debug withNameSpace(final String nameSpace) { * health check and provides a simple way to monitor the health of a Vault instance.

    * * @return The response information returned from Vault - * @throws VaultException If any errors occurs with the REST request (e.g. non-200 status code, invalid JSON payload, etc), and the maximum number of retries is exceeded. - * @see https://www.vaultproject.io/docs/http/sys-health.html + * @throws VaultException If any errors occurs with the REST request (e.g. non-200 status code, + * invalid JSON payload, etc), and the maximum number of retries is exceeded. + * @see https://www.vaultproject.io/docs/http/sys-health.html * *
    *
    {@code
    @@ -61,19 +63,27 @@ public HealthResponse health() throws VaultException {
         }
     
         /**
    -     * 

    An overloaded version of {@link Debug#health()} that allows for passing one or more optional parameters.

    + *

    An overloaded version of {@link Debug#health()} that allows for passing one or more + * optional parameters.

    * - *

    WARNING: In testing, we've found that changing the default HTTP status codes can result in the operation - * succeeding, but returning an empty JSON payload in the response. For example, this seems to happen when you - * set activeCode to 204, but not for 212 (the regular default is 200). When this happens, the - * HealthResponse return object will have null values in most of its fields, and you - * will need to check HealthReponse.getRestResponse().getStatus() to determine the result of - * the operation.

    + *

    WARNING: In testing, we've found that changing the default HTTP status codes can result + * in the operation + * succeeding, but returning an empty JSON payload in the response. For example, this seems to + * happen when you set activeCode to 204, but not for 212 (the regular default is + * 200). When this happens, the + * HealthResponse return object will have null values in most of its + * fields, and you + * will need to check HealthReponse.getRestResponse().getStatus() to determine the + * result of the operation.

    * - * @param standbyOk (optional) Indicates that being a standby should still return the active status code instead of the standby code - * @param activeCode (optional) Indicates the status code that should be returned for an active node instead of the default of 200 - * @param standbyCode (optional) Indicates the status code that should be returned for a standby node instead of the default of 429 - * @param sealedCode (optional) Indicates the status code that should be returned for a sealed node instead of the default of 500 + * @param standbyOk (optional) Indicates that being a standby should still return the active + * status code instead of the standby code + * @param activeCode (optional) Indicates the status code that should be returned for an active + * node instead of the default of 200 + * @param standbyCode (optional) Indicates the status code that should be returned for a standby + * node instead of the default of 429 + * @param sealedCode (optional) Indicates the status code that should be returned for a sealed + * node instead of the default of 500 * @return The response information returned from Vault * @throws VaultException If an error occurs or unexpected response received from Vault */ @@ -84,56 +94,55 @@ public HealthResponse health( final Integer sealedCode ) throws VaultException { final String path = "sys/health"; - int retryCount = 0; - while (true) { - try { - // Build an HTTP request for Vault - final Rest rest = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + path) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()); - // Add params if present - if (standbyOk != null) rest.parameter("standbyok", standbyOk.toString()); - if (activeCode != null) rest.parameter("activecode", activeCode.toString()); - if (standbyCode != null) rest.parameter("standbycode", standbyCode.toString()); - if (sealedCode != null) rest.parameter("sealedcode", sealedCode.toString()); - // Execute request - final RestResponse restResponse = rest.get(); - // Validate response - final Set validCodes = new HashSet<>();//NOPMD - validCodes.add(200); - validCodes.add(429); - validCodes.add(500); - if (activeCode != null) validCodes.add(activeCode); - if (standbyCode != null) validCodes.add(standbyCode); - if (sealedCode != null) validCodes.add(sealedCode); - if (!validCodes.contains(restResponse.getStatus())) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus()); - } - return new HealthResponse(restResponse, retryCount); - } catch (RuntimeException | VaultException | RestException e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + return retry(attempt -> { + // Build an HTTP request for Vault + final Rest rest = new Rest()//NOPMD + .url(config.getAddress() + "/v1/" + path) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()); + // Add params if present + if (standbyOk != null) { + rest.parameter("standbyok", standbyOk.toString()); } - } - } + if (activeCode != null) { + rest.parameter("activecode", activeCode.toString()); + } + if (standbyCode != null) { + rest.parameter("standbycode", standbyCode.toString()); + } + if (sealedCode != null) { + rest.parameter("sealedcode", sealedCode.toString()); + } + // Execute request + final RestResponse restResponse = rest.get(); + // Validate response + final Set validCodes = new HashSet<>();//NOPMD + validCodes.add(200); + validCodes.add(429); + validCodes.add(500); + if (activeCode != null) { + validCodes.add(activeCode); + } + if (standbyCode != null) { + validCodes.add(standbyCode); + } + if (sealedCode != null) { + validCodes.add(sealedCode); + } + + if (!validCodes.contains(restResponse.getStatus())) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus(), + restResponse.getStatus()); + } + + return new HealthResponse(restResponse, attempt); + }); + } } diff --git a/src/main/java/io/github/jopenlibs/vault/api/Leases.java b/src/main/java/io/github/jopenlibs/vault/api/Leases.java index 61760487..18ff9921 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/Leases.java +++ b/src/main/java/io/github/jopenlibs/vault/api/Leases.java @@ -10,21 +10,21 @@ /** - *

    The implementing class for operations on REST endpoints, under the "Leases" section of the Vault HTTP API - * docs (https://www.vaultproject.io/docs/http/index.html).

    + *

    The implementing class for operations on REST endpoints, under the "Leases" section of the + * Vault HTTP API docs (https://www.vaultproject.io/docs/http/index.html).

    * *

    This class is not intended to be constructed directly. Rather, it is meant to used by way of - * Vault in a DSL-style builder pattern. See the Javadoc comments of each public + * Vault in a DSL-style builder pattern. See the Javadoc comments of each + * public * method for usage examples.

    */ -public class Leases { - - private final VaultConfig config; +public class Leases extends OperationsBase { private String nameSpace; public Leases(final VaultConfig config) { - this.config = config; + super(config); + if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) { this.nameSpace = this.config.getNameSpace(); } @@ -50,54 +50,38 @@ public Leases withNameSpace(final String nameSpace) { * @throws VaultException If an error occurs, or unexpected reponse received from Vault */ public VaultResponse revoke(final String leaseId) throws VaultException { - int retryCount = 0; - while (true) { - try { - /** - * 2019-03-21 - * Changed the Lease revoke url due to invalid path. Vault deprecated the original - * path (/v1/sys/revoke) in favor of a new leases mount point (/v1/sys/leases/revoke) - * https://github.com/hashicorp/vault/blob/master/CHANGELOG.md#080-august-9th-2017 - */ - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/sys/leases/revoke/" + leaseId) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .put(); - - // Validate response - if (restResponse.getStatus() != 204) { - throw new VaultException("Expecting HTTP status 204, but instead receiving " + restResponse.getStatus(), restResponse.getStatus()); - } - return new VaultResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + return retry(attempt -> { + /** + * 2019-03-21 + * Changed the Lease revoke url due to invalid path. Vault deprecated the original + * path (/v1/sys/revoke) in favor of a new leases mount point (/v1/sys/leases/revoke) + * https://github.com/hashicorp/vault/blob/master/CHANGELOG.md#080-august-9th-2017 + */ + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/sys/leases/revoke/" + leaseId) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .put(); + + // Validate response + if (restResponse.getStatus() != 204) { + throw new VaultException("Expecting HTTP status 204, but instead receiving " + + restResponse.getStatus(), restResponse.getStatus()); } - } + + return new VaultResponse(restResponse, attempt); + }); } /** - *

    Revokes all secrets (via a lease ID prefix) or tokens (via the tokens' path property) generated under a - * given prefix immediately. This requires sudo capability and access to it should be tightly controlled as it - * can be used to revoke very large numbers of secrets/tokens at once. E.g.:

    + *

    Revokes all secrets (via a lease ID prefix) or tokens (via the tokens' path property) + * generated under a given prefix immediately. This requires sudo capability and access to it + * should be tightly controlled as it can be used to revoke very large numbers of secrets/tokens + * at once. E.g.:

    * *
    *
    {@code
    @@ -111,51 +95,34 @@ public VaultResponse revoke(final String leaseId) throws VaultException {
          * @throws VaultException If an error occurs, or unexpected reponse received from Vault
          */
         public VaultResponse revokePrefix(final String prefix) throws VaultException {
    -        int retryCount = 0;
    -        while (true) {
    -            try {
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(config.getAddress() + "/v1/sys/revoke-prefix/" + prefix)
    -                        .header("X-Vault-Token", config.getToken())
    -                        .header("X-Vault-Namespace", this.nameSpace)
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .put();
    -
    -                // Validate response
    -                if (restResponse.getStatus() != 204) {
    -                    throw new VaultException("Expecting HTTP status 204, but instead receiving " + restResponse.getStatus(), restResponse.getStatus());
    -                }
    -                return new VaultResponse(restResponse, retryCount);
    -            } catch (Exception e) {
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException) e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +        return retry(attempt -> {
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(config.getAddress() + "/v1/sys/revoke-prefix/" + prefix)
    +                    .header("X-Vault-Token", config.getToken())
    +                    .header("X-Vault-Namespace", this.nameSpace)
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .put();
    +
    +            // Validate response
    +            if (restResponse.getStatus() != 204) {
    +                throw new VaultException("Expecting HTTP status 204, but instead receiving "
    +                        + restResponse.getStatus(), restResponse.getStatus());
                 }
    -        }
    +            return new VaultResponse(restResponse, attempt);
    +        });
         }
     
         /**
    -     * 

    Revokes all secrets or tokens generated under a given prefix immediately. Unlike revokePrefix(String), - * this method ignores backend errors encountered during revocation. This is potentially very dangerous and should - * only be used in specific emergency situations where errors in the backend or the connected backend service - * prevent normal revocation. By ignoring these errors, Vault abdicates responsibility for ensuring that the - * issued credentials or secrets are properly revoked and/or cleaned up. Access to this endpoint should be tightly - * controlled. E.g.:

    + *

    Revokes all secrets or tokens generated under a given prefix immediately. Unlike + * revokePrefix(String), this method ignores backend errors encountered during revocation. This + * is potentially very dangerous and should only be used in specific emergency situations where + * errors in the backend or the connected backend service prevent normal revocation. By + * ignoring these errors, Vault abdicates responsibility for ensuring that the issued + * credentials or secrets are properly revoked and/or cleaned up. Access to this endpoint should + * be tightly controlled. E.g.:

    * *
    *
    {@code
    @@ -169,42 +136,25 @@ public VaultResponse revokePrefix(final String prefix) throws VaultException {
          * @throws VaultException If an error occurs, or unexpected reponse received from Vault
          */
         public VaultResponse revokeForce(final String prefix) throws VaultException {
    -        int retryCount = 0;
    -        while (true) {
    -            try {
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(config.getAddress() + "/v1/sys/revoke-force/" + prefix)
    -                        .header("X-Vault-Token", config.getToken())
    -                        .header("X-Vault-Namespace", this.nameSpace)
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .put();
    -
    -                // Validate response
    -                if (restResponse.getStatus() != 204) {
    -                    throw new VaultException("Expecting HTTP status 204, but instead receiving " + restResponse.getStatus(), restResponse.getStatus());
    -                }
    -                return new VaultResponse(restResponse, retryCount);
    -            } catch (Exception e) {
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException) e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +        return retry(attempt -> {
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(config.getAddress() + "/v1/sys/revoke-force/" + prefix)
    +                    .header("X-Vault-Token", config.getToken())
    +                    .header("X-Vault-Namespace", this.nameSpace)
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .put();
    +
    +            // Validate response
    +            if (restResponse.getStatus() != 204) {
    +                throw new VaultException("Expecting HTTP status 204, but instead receiving "
    +                        + restResponse.getStatus(), restResponse.getStatus());
                 }
    -        }
    +
    +            return new VaultResponse(restResponse, attempt);
    +        });
         }
     
         /**
    @@ -217,8 +167,9 @@ public VaultResponse revokeForce(final String prefix) throws VaultException {
          * }
    *
    * - * @param leaseId A lease ID associated with a secret - * @param increment A requested amount of time in seconds to extend the lease. This is advisory. + * @param leaseId A lease ID associated with a secret + * @param increment A requested amount of time in seconds to extend the lease. This is + * advisory. * @return The response information returned from Vault * @throws VaultException The response information returned from Vault */ @@ -231,42 +182,26 @@ public VaultResponse renew(final String leaseId, final long increment) throws Va // secrets. Now that the integration tests use a "real" Vault instance hosted in a Docker // container, we can revisit this. - int retryCount = 0; - while (true) { - try { - final String requestJson = Json.object().add("increment", increment).toString(); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/sys/renew/" + leaseId) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .body(increment < 0 ? null : requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate response - if (restResponse.getStatus() != 200) { - throw new VaultException("Expecting HTTP status 200, but instead receiving " + restResponse.getStatus(), restResponse.getStatus()); - } - return new VaultResponse(restResponse, retryCount); - } catch (Exception e) { - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + return retry(attempt -> { + final String requestJson = Json.object().add("increment", increment).toString(); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/sys/renew/" + leaseId) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .body(increment < 0 ? null : requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate response + if (restResponse.getStatus() != 200) { + throw new VaultException("Expecting HTTP status 200, but instead receiving " + + restResponse.getStatus(), restResponse.getStatus()); } - } + + return new VaultResponse(restResponse, attempt); + }); } } diff --git a/src/main/java/io/github/jopenlibs/vault/api/Logical.java b/src/main/java/io/github/jopenlibs/vault/api/Logical.java index d45d3855..425680a9 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/Logical.java +++ b/src/main/java/io/github/jopenlibs/vault/api/Logical.java @@ -7,7 +7,6 @@ import io.github.jopenlibs.vault.json.JsonValue; import io.github.jopenlibs.vault.response.LogicalResponse; import io.github.jopenlibs.vault.rest.Rest; -import io.github.jopenlibs.vault.rest.RestException; import io.github.jopenlibs.vault.rest.RestResponse; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -24,38 +23,41 @@ /** *

    The implementing class for Vault's core/logical operations (e.g. read, write).

    * - *

    This class is not intended to be constructed directly. Rather, it is meant to used by way of Vault - * in a DSL-style builder pattern. See the Javadoc comments of each public method for usage examples.

    + *

    This class is not intended to be constructed directly. Rather, it is meant to used by way of + * Vault + * in a DSL-style builder pattern. See the Javadoc comments of each public method for + * usage examples.

    */ -public class Logical { - - private final VaultConfig config; +public class Logical extends OperationsBase { private String nameSpace; public enum logicalOperations {authentication, deleteV1, deleteV2, destroy, listV1, listV2, readV1, readV2, writeV1, writeV2, unDelete, mount} public Logical(final VaultConfig config) { - this.config = config; + super(config); + if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) { this.nameSpace = this.config.getNameSpace(); } } /** - *

    Adds the requested namespace to the logical operation, which is then passed into the REST headers for said operation.

    + *

    Adds the requested namespace to the logical operation, which is then passed into the REST + * headers for said operation.

    * * @param nameSpace The Vault namespace to access (e.g. secret/). * @return The Logical instance, with the namespace set. */ public Logical withNameSpace(final String nameSpace) { this.nameSpace = nameSpace; + return this; } /** - *

    Basic read operation to retrieve a secret. A single secret key can map to multiple name-value pairs, - * which can be retrieved from the response object. E.g.:

    + *

    Basic read operation to retrieve a secret. A single secret key can map to multiple + * name-value pairs, which can be retrieved from the response object. E.g.:

    * *
    *
    {@code
    @@ -68,64 +70,50 @@ public Logical withNameSpace(final String nameSpace) {
          *
          * @param path The Vault key value from which to read (e.g. secret/hello)
          * @return The response information returned from Vault
    -     * @throws VaultException If any errors occurs with the REST request (e.g. non-200 status code, invalid JSON payload,
    -     *                        etc), and the maximum number of retries is exceeded.
    +     * @throws VaultException If any errors occurs with the REST request (e.g. non-200 status code,
    +     * invalid JSON payload, etc), and the maximum number of retries is exceeded.
          */
         public LogicalResponse read(final String path) throws VaultException {
             if (this.engineVersionForSecretPath(path).equals(2)) {
    -            return read(path, true, logicalOperations.readV2);
    -        } else return read(path, true, logicalOperations.readV1);
    +            return read(path, logicalOperations.readV2);
    +        } else {
    +            return read(path, logicalOperations.readV1);
    +        }
         }
     
    -    private LogicalResponse read(final String path, Boolean shouldRetry, final logicalOperations operation)
    +    private LogicalResponse read(final String path, final logicalOperations operation)
                 throws VaultException {
    -        int retryCount = 0;
    -        while (true) {
    -            try {
    -                // Make an HTTP request to Vault
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, config.getPrefixPathDepth(), operation))
    -                        .header("X-Vault-Token", config.getToken())
    -                        .header("X-Vault-Namespace", this.nameSpace)
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .get();
    -
    -                // Validate response - don't treat 4xx class errors as exceptions, we want to return an error as the response
    -                if (restResponse.getStatus() != 200 && !(restResponse.getStatus() >= 400 && restResponse.getStatus() < 500)) {
    -                    throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus()
    -                            + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8),
    -                            restResponse.getStatus());
    -                }
    -
    -                return new LogicalResponse(restResponse, retryCount, operation);
    -            } catch (RuntimeException | VaultException | RestException e) {
    -                if (!shouldRetry)
    -                    throw new VaultException(e);
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException) e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +        return retry(attempt -> {
    +            // Make an HTTP request to Vault
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path,
    +                            config.getPrefixPathDepth(), operation))
    +                    .header("X-Vault-Token", config.getToken())
    +                    .header("X-Vault-Namespace", this.nameSpace)
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .get();
    +
    +            // Validate response - don't treat 4xx class errors as exceptions, we want to return an error as the response
    +            if (restResponse.getStatus() != 200 && !(restResponse.getStatus() >= 400
    +                    && restResponse.getStatus() < 500)) {
    +                throw new VaultException(
    +                        "Vault responded with HTTP status code: " + restResponse.getStatus()
    +                                + "\nResponse body: " + new String(restResponse.getBody(),
    +                                StandardCharsets.UTF_8),
    +                        restResponse.getStatus());
                 }
    -        }
    +
    +            return new LogicalResponse(restResponse, attempt, operation);
    +        });
         }
     
         /**
    -     * 

    Basic read operation to retrieve a specified secret version for KV engine version 2. A single secret key version - * can map to multiple name-value pairs, which can be retrieved from the response object. E.g.:

    + *

    Basic read operation to retrieve a specified secret version for KV engine version 2. A + * single secret key version can map to multiple name-value pairs, which can be retrieved from + * the response object. E.g.:

    * *
    *
    {@code
    @@ -136,65 +124,56 @@ private LogicalResponse read(final String path, Boolean shouldRetry, final logic
          * }
    *
    * - * @param path The Vault key value from which to read (e.g. secret/hello + * @param path The Vault key value from which to read (e.g. secret/hello * @param shouldRetry Whether to try more than once - * @param version The Integer version number of the secret to read, e.g. "1" + * @param version The Integer version number of the secret to read, e.g. "1" * @return The response information returned from Vault - * @throws VaultException If any errors occurs with the REST request (e.g. non-200 status code, invalid JSON payload, - * etc), and the maximum number of retries is exceeded. + * @throws VaultException If any errors occurs with the REST request (e.g. non-200 status code, + * invalid JSON payload, etc), and the maximum number of retries is exceeded. */ - public LogicalResponse read(final String path, Boolean shouldRetry, final Integer version) throws VaultException { + public LogicalResponse read(final String path, Boolean shouldRetry, final Integer version) + throws VaultException { if (this.engineVersionForSecretPath(path) != 2) { throw new VaultException("Version reads are only supported in KV Engine version 2."); } - int retryCount = 0; - while (true) { - try { - // Make an HTTP request to Vault - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, config.getPrefixPathDepth(), logicalOperations.readV2)) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .parameter("version", version.toString()) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .get(); - - // Validate response - don't treat 4xx class errors as exceptions, we want to return an error as the response - if (restResponse.getStatus() != 200 && !(restResponse.getStatus() >= 400 && restResponse.getStatus() < 500)) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - return new LogicalResponse(restResponse, retryCount, logicalOperations.readV2); - } catch (RuntimeException | VaultException | RestException e) { - if (!shouldRetry) - throw new VaultException(e); - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); + return OperationsBase.retry( + attempt -> { + // Make an HTTP request to Vault + final RestResponse restResponse = + new Rest() //NOPMD + .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite( + path, + config.getPrefixPathDepth(), logicalOperations.readV2)) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .parameter("version", version.toString()) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .get(); + + // Validate response - don't treat 4xx class errors as exceptions, we want to return an error as the response + if (restResponse.getStatus() != 200 && !(restResponse.getStatus() >= 400 + && restResponse.getStatus() < 500)) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } - } - } + + return new LogicalResponse(restResponse, attempt, logicalOperations.readV2); + }, + shouldRetry ? config.getMaxRetries() : 1, + config.getRetryIntervalMilliseconds() + ); } /** - *

    Basic operation to store secrets. Multiple name value pairs can be stored under the same secret key. - * E.g.:

    + *

    Basic operation to store secrets. Multiple name value pairs can be stored under the same + * secret key. E.g.:

    * *
    *
    {@code
    @@ -206,114 +185,117 @@ public LogicalResponse read(final String path, Boolean shouldRetry, final Intege
          * }
    *
    * - *

    The values in these name-value pairs may be booleans, numerics, strings, or nested JSON objects. However, - * be aware that this method does not recursively parse any nested structures. If you wish to write arbitrary - * JSON objects to Vault... then you should parse them to JSON outside of this method, and pass them here as JSON - * strings.

    + *

    The values in these name-value pairs may be booleans, numerics, strings, or nested JSON + * objects. However, be aware that this method does not recursively parse any nested + * structures. If you wish to write arbitrary JSON objects to Vault... then you should parse + * them to JSON outside of this method, and pass them here as JSON strings.

    * - * @param path The Vault key value to which to write (e.g. secret/hello) - * @param nameValuePairs Secret name and value pairs to store under this Vault key (can be null for - * writing to keys that do not need or expect any fields to be specified) + * @param path The Vault key value to which to write (e.g. secret/hello) + * @param nameValuePairs Secret name and value pairs to store under this Vault key (can be + * null for writing to keys that do not need or expect any fields to be specified) * @return The response information received from Vault - * @throws VaultException If any errors occurs with the REST request, and the maximum number of retries is exceeded. + * @throws VaultException If any errors occurs with the REST request, and the maximum number of + * retries is exceeded. */ - public LogicalResponse write(final String path, final Map nameValuePairs) throws VaultException { + public LogicalResponse write(final String path, final Map nameValuePairs) + throws VaultException { if (engineVersionForSecretPath(path).equals(2)) { return write(path, nameValuePairs, logicalOperations.writeV2); - } else return write(path, nameValuePairs, logicalOperations.writeV1); + } else { + return write(path, nameValuePairs, logicalOperations.writeV1); + } } private LogicalResponse write(final String path, final Map nameValuePairs, - final logicalOperations operation) throws VaultException { - int retryCount = 0; - while (true) { - try { - JsonObject requestJson = Json.object(); - if (nameValuePairs != null) { - for (final Map.Entry pair : nameValuePairs.entrySet()) { - final Object value = pair.getValue(); - if (value == null) { - requestJson = requestJson.add(pair.getKey(), (String) null); - } else if (value instanceof Boolean) { - requestJson = requestJson.add(pair.getKey(), (Boolean) pair.getValue()); - } else if (value instanceof Integer) { - requestJson = requestJson.add(pair.getKey(), (Integer) pair.getValue()); - } else if (value instanceof Long) { - requestJson = requestJson.add(pair.getKey(), (Long) pair.getValue()); - } else if (value instanceof Float) { - requestJson = requestJson.add(pair.getKey(), (Float) pair.getValue()); - } else if (value instanceof Double) { - requestJson = requestJson.add(pair.getKey(), (Double) pair.getValue()); - } else if (value instanceof JsonValue) { - requestJson = requestJson.add(pair.getKey(), (JsonValue) pair.getValue()); - } else { - requestJson = requestJson.add(pair.getKey(), pair.getValue().toString()); - } - } - } - // Make an HTTP request to Vault - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, config.getPrefixPathDepth(), operation)) - .body(jsonObjectToWriteFromEngineVersion(operation, requestJson).toString().getBytes(StandardCharsets.UTF_8)) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // HTTP Status should be either 200 (with content - e.g. PKI write) or 204 (no content) - final int restStatus = restResponse.getStatus(); - if (restStatus == 200 || restStatus == 204 || (restResponse.getStatus() >= 400 && restResponse.getStatus() < 500)) { - return new LogicalResponse(restResponse, retryCount, operation); - } else { - throw new VaultException("Expecting HTTP status 204 or 200, but instead receiving " + restStatus - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), restStatus); - } - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); + final logicalOperations operation) throws VaultException { + + return retry(attempt -> { + JsonObject requestJson = Json.object(); + if (nameValuePairs != null) { + for (final Map.Entry pair : nameValuePairs.entrySet()) { + final Object value = pair.getValue(); + if (value == null) { + requestJson = requestJson.add(pair.getKey(), (String) null); + } else if (value instanceof Boolean) { + requestJson = requestJson.add(pair.getKey(), (Boolean) pair.getValue()); + } else if (value instanceof Integer) { + requestJson = requestJson.add(pair.getKey(), (Integer) pair.getValue()); + } else if (value instanceof Long) { + requestJson = requestJson.add(pair.getKey(), (Long) pair.getValue()); + } else if (value instanceof Float) { + requestJson = requestJson.add(pair.getKey(), (Float) pair.getValue()); + } else if (value instanceof Double) { + requestJson = requestJson.add(pair.getKey(), (Double) pair.getValue()); + } else if (value instanceof JsonValue) { + requestJson = requestJson.add(pair.getKey(), + (JsonValue) pair.getValue()); + } else { + requestJson = requestJson.add(pair.getKey(), + pair.getValue().toString()); } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); } } - } + // Make an HTTP request to Vault + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path, + config.getPrefixPathDepth(), operation)) + .body(jsonObjectToWriteFromEngineVersion(operation, requestJson).toString() + .getBytes(StandardCharsets.UTF_8)) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // HTTP Status should be either 200 (with content - e.g. PKI write) or 204 (no content) + final int restStatus = restResponse.getStatus(); + if (restStatus == 200 || restStatus == 204 || (restResponse.getStatus() >= 400 + && restResponse.getStatus() < 500)) { + return new LogicalResponse(restResponse, attempt, operation); + } else { + throw new VaultException( + "Expecting HTTP status 204 or 200, but instead receiving " + restStatus + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), restStatus); + } + }); } /** *

    Retrieve a list of keys corresponding to key/value pairs at a given Vault path.

    * - *

    Key values ending with a trailing-slash characters are sub-paths. Running a subsequent list() - * call, using the original path appended with this key, will retrieve all secret keys stored at that sub-path.

    + *

    Key values ending with a trailing-slash characters are sub-paths. Running a subsequent + * list() + * call, using the original path appended with this key, will retrieve all secret keys stored at + * that sub-path.

    * - *

    This method returns only the secret keys, not values. To retrieve the actual stored value for a key, - * use read() with the key appended onto the original base path.

    + *

    This method returns only the secret keys, not values. To retrieve the actual stored + * value for a key, use read() with the key appended onto the original base + * path.

    * * @param path The Vault key value at which to look for secrets (e.g. secret) - * @return A list of keys corresponding to key/value pairs at a given Vault path, or an empty list if there are none + * @return A list of keys corresponding to key/value pairs at a given Vault path, or an empty + * list if there are none * @throws VaultException If any errors occur, or unexpected response received from Vault */ public LogicalResponse list(final String path) throws VaultException { if (engineVersionForSecretPath(path).equals(2)) { return list(path, logicalOperations.listV2); - } else return list(path, logicalOperations.listV1); + } else { + return list(path, logicalOperations.listV1); + } } - private LogicalResponse list(final String path, final logicalOperations operation) throws VaultException { + private LogicalResponse list(final String path, final logicalOperations operation) + throws VaultException { LogicalResponse response = null; try { - response = read(adjustPathForList(path, config.getPrefixPathDepth(), operation), true, operation); + response = read( + adjustPathForList(path, config.getPrefixPathDepth(), operation), + operation + ); } catch (final VaultException e) { if (e.getHttpStatusCode() != 404) { throw e; @@ -326,8 +308,8 @@ private LogicalResponse list(final String path, final logicalOperations operatio /** *

    Deletes the key/value pair located at the provided path.

    * - *

    If the path represents a sub-path, then all of its contents must be deleted prior to deleting the empty - * sub-path itself.

    + *

    If the path represents a sub-path, then all of its contents must be deleted prior to + * deleting the empty sub-path itself.

    * * @param path The Vault key value to delete (e.g. secret/hello). * @return The response information received from Vault @@ -336,61 +318,52 @@ private LogicalResponse list(final String path, final logicalOperations operatio public LogicalResponse delete(final String path) throws VaultException { if (engineVersionForSecretPath(path).equals(2)) { return delete(path, logicalOperations.deleteV2); - } else return delete(path, logicalOperations.deleteV1); + } else { + return delete(path, logicalOperations.deleteV1); + } } - private LogicalResponse delete(final String path, final Logical.logicalOperations operation) throws VaultException { - int retryCount = 0; - while (true) { - try { - // Make an HTTP request to Vault - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForDelete(path, config.getPrefixPathDepth(), operation)) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .delete(); - - // Validate response - if (restResponse.getStatus() != 204) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - return new LogicalResponse(restResponse, retryCount, operation); - } catch (RuntimeException | VaultException | RestException e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + private LogicalResponse delete(final String path, final Logical.logicalOperations operation) + throws VaultException { + return retry(attempt -> { + // Make an HTTP request to Vault + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/" + adjustPathForDelete(path, + config.getPrefixPathDepth(), operation)) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .delete(); + + // Validate response + if (restResponse.getStatus() != 204) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + + return new LogicalResponse(restResponse, attempt, operation); + }); } /** - *

    Soft deletes the specified version of the key/value pair located at the provided path.

    + *

    Soft deletes the specified version of the key/value pair located at the provided + * path.

    *

    - * Only supported for KV Engine version 2. If the data is desired, it can be recovered with a matching unDelete operation. + * Only supported for KV Engine version 2. If the data is desired, it can be recovered with a + * matching unDelete operation. * - *

    If the path represents a sub-path, then all of its contents must be deleted prior to deleting the empty - * sub-path itself.

    + *

    If the path represents a sub-path, then all of its contents must be deleted prior to + * deleting the empty sub-path itself.

    * - * @param path The Vault key value to delete (e.g. secret/hello). - * @param versions An array of Integers corresponding to the versions you wish to delete, e.g. [1, 2] etc. + * @param path The Vault key value to delete (e.g. secret/hello). + * @param versions An array of Integers corresponding to the versions you wish to delete, e.g. + * [1, 2] etc. * @return The response information received from Vault * @throws VaultException If any error occurs, or unexpected response received from Vault */ @@ -399,48 +372,34 @@ public LogicalResponse delete(final String path, final int[] versions) throws Va throw new VaultException("Version deletes are only supported for KV Engine 2."); } intArrayCheck(versions); - int retryCount = 0; - while (true) { - try { - // Make an HTTP request to Vault - JsonObject versionsToDelete = new JsonObject().add("versions", versions); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForVersionDelete(path,config.getPrefixPathDepth())) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .body(versionsToDelete.toString().getBytes(StandardCharsets.UTF_8)) - .post(); - - // Validate response - return getLogicalResponse(retryCount, restResponse); - } catch (RuntimeException | VaultException | RestException e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } - } - } + + return retry(attempt -> { + // Make an HTTP request to Vault + JsonObject versionsToDelete = new JsonObject().add("versions", versions); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/" + adjustPathForVersionDelete(path, + config.getPrefixPathDepth())) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .body(versionsToDelete.toString().getBytes(StandardCharsets.UTF_8)) + .post(); + + // Validate response + return getLogicalResponse(attempt, restResponse); + }); } - private LogicalResponse getLogicalResponse(int retryCount, RestResponse restResponse) throws VaultException { + private LogicalResponse getLogicalResponse(int retryCount, RestResponse restResponse) + throws VaultException { if (restResponse.getStatus() != 204) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), restResponse.getStatus()); } return new LogicalResponse(restResponse, retryCount, logicalOperations.deleteV2); @@ -456,12 +415,14 @@ private void intArrayCheck(int[] versions) { } /** - *

    Recovers a soft delete of the specified version of the key/value pair located at the provided path.

    + *

    Recovers a soft delete of the specified version of the key/value pair located at the + * provided path.

    *

    * Only supported for KV Engine version 2. * - * @param path The Vault key value to undelete (e.g. secret/hello). - * @param versions An array of Integers corresponding to the versions you wish to undelete, e.g. [1, 2] etc. + * @param path The Vault key value to undelete (e.g. secret/hello). + * @param versions An array of Integers corresponding to the versions you wish to undelete, e.g. + * [1, 2] etc. * @return The response information received from Vault * @throws VaultException If any error occurs, or unexpected response received from Vault */ @@ -470,57 +431,44 @@ public LogicalResponse unDelete(final String path, final int[] versions) throws throw new VaultException("Version undeletes are only supported for KV Engine 2."); } intArrayCheck(versions); - int retryCount = 0; - while (true) { - try { - // Make an HTTP request to Vault - JsonObject versionsToUnDelete = new JsonObject().add("versions", versions); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForVersionUnDelete(path,config.getPrefixPathDepth())) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .body(versionsToUnDelete.toString().getBytes(StandardCharsets.UTF_8)) - .post(); - - // Validate response - if (restResponse.getStatus() != 204) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - return new LogicalResponse(restResponse, retryCount, logicalOperations.unDelete); - } catch (RuntimeException | VaultException | RestException e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + + return retry(attempt -> { + // Make an HTTP request to Vault + JsonObject versionsToUnDelete = new JsonObject().add("versions", versions); + final RestResponse restResponse = new Rest() //NOPMD + .url(config.getAddress() + "/v1/" + adjustPathForVersionUnDelete(path, + config.getPrefixPathDepth())) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .body(versionsToUnDelete.toString().getBytes(StandardCharsets.UTF_8)) + .post(); + + // Validate response + if (restResponse.getStatus() != 204) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + return new LogicalResponse(restResponse, attempt, logicalOperations.unDelete); + }); } /** - *

    Performs a hard delete of the specified version of the key/value pair located at the provided path.

    + *

    Performs a hard delete of the specified version of the key/value pair located at the + * provided path.

    *

    - * Only supported for KV Engine version 2. There are no recovery options for the specified version of the data deleted - * in this method. + * Only supported for KV Engine version 2. There are no recovery options for the specified + * version of the data deleted in this method. * - * @param path The Vault key value to destroy (e.g. secret/hello). - * @param versions An array of Integers corresponding to the versions you wish to destroy, e.g. [1, 2] etc. + * @param path The Vault key value to destroy (e.g. secret/hello). + * @param versions An array of Integers corresponding to the versions you wish to destroy, e.g. + * [1, 2] etc. * @return The response information received from Vault * @throws VaultException If any error occurs, or unexpected response received from Vault */ @@ -529,46 +477,30 @@ public LogicalResponse destroy(final String path, final int[] versions) throws V throw new VaultException("Secret destroys are only supported for KV Engine 2."); } intArrayCheck(versions); - int retryCount = 0; - while (true) { - try { - // Make an HTTP request to Vault - JsonObject versionsToDestroy = new JsonObject().add("versions", versions); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/" + adjustPathForVersionDestroy(path,config.getPrefixPathDepth())) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .body(versionsToDestroy.toString().getBytes(StandardCharsets.UTF_8)) - .post(); - - // Validate response - return getLogicalResponse(retryCount, restResponse); - } catch (RuntimeException | VaultException | RestException e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } - } - } + + return retry(attempt -> { + // Make an HTTP request to Vault + JsonObject versionsToDestroy = new JsonObject().add("versions", versions); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/" + adjustPathForVersionDestroy(path, + config.getPrefixPathDepth())) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .body(versionsToDestroy.toString().getBytes(StandardCharsets.UTF_8)) + .post(); + + // Validate response + return getLogicalResponse(attempt, restResponse); + }); } /** - *

    Performs an upgrade of the secrets engine version of the specified KV store to version 2.

    + *

    Performs an upgrade of the secrets engine version of the specified KV store to version + * 2.

    *

    * There is no downgrading this operation back to version 1. * @@ -580,47 +512,33 @@ public LogicalResponse upgrade(final String kvPath) throws VaultException { if (this.engineVersionForSecretPath(kvPath) == 2) { throw new VaultException("This KV engine is already version 2."); } - int retryCount = 0; - while (true) { - try { - // Make an HTTP request to Vault - JsonObject kvToUpgrade = new JsonObject().add("options", new JsonObject().add("version", 2)); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/sys/mounts/" + (kvPath.replaceAll("/", "") + "/tune")) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .body(kvToUpgrade.toString().getBytes(StandardCharsets.UTF_8)) - .post(); - - // Validate response - if (restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() - + "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), - restResponse.getStatus()); - } - return new LogicalResponse(restResponse, retryCount, logicalOperations.authentication); - } catch (RuntimeException | VaultException | RestException e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + + return retry(attempt -> { + // Make an HTTP request to Vault + JsonObject kvToUpgrade = new JsonObject().add("options", + new JsonObject().add("version", 2)); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/sys/mounts/" + (kvPath.replaceAll("/", "") + + "/tune")) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .body(kvToUpgrade.toString().getBytes(StandardCharsets.UTF_8)) + .post(); + + // Validate response + if (restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + "\nResponse body: " + new String(restResponse.getBody(), + StandardCharsets.UTF_8), + restResponse.getStatus()); } - } + return new LogicalResponse(restResponse, attempt, logicalOperations.authentication); + }); } private Integer engineVersionForSecretPath(final String secretPath) { @@ -634,8 +552,8 @@ private Integer engineVersionForSecretPath(final String secretPath) { /** *

    Provides the version of the secrets engine of the specified path, e.g. 1 or 2.

    - * First checks if the Vault config secrets engine path map contains the path. - * If not, then defaults to the Global Engine version fallback. + * First checks if the Vault config secrets engine path map contains the path. If not, then + * defaults to the Global Engine version fallback. *

    * * @param path The Vault secret path to check (e.g. secret/). diff --git a/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java b/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java new file mode 100644 index 00000000..b537ca0b --- /dev/null +++ b/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java @@ -0,0 +1,62 @@ +package io.github.jopenlibs.vault.api; + +import io.github.jopenlibs.vault.VaultConfig; +import io.github.jopenlibs.vault.VaultException; + + +/** + * The base class for all operation. + */ +public abstract class OperationsBase { + protected final VaultConfig config; + + protected OperationsBase(VaultConfig config) { + this.config = config; + } + + protected T retry(final EndpointOperation op) throws VaultException { + return retry(op, config.getMaxRetries(), config.getRetryIntervalMilliseconds()); + } + + /** + */ + static T retry(final EndpointOperation op, int retryCount, long retryIntervalMs) throws VaultException { + int attempt = 0; + + while (true) { + try { + return op.run(attempt); + } catch (final Exception e) { + // If there are retries to perform, then pause for the configured interval and then execute the loop again... + if (attempt < retryCount) { + attempt++; + + sleep(retryIntervalMs); + } else if (e instanceof VaultException) { + // ... otherwise, give up. + throw (VaultException) e; + } else { + throw new VaultException(e); + } + } + } + } + + public interface EndpointOperation { + /** + * Run an operation. + * + * @param attempt Number of current attempt. + * @return Operation response. + */ + T run(int attempt) throws Exception; + } + + private static void sleep(long delay) { + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/io/github/jopenlibs/vault/api/database/Database.java b/src/main/java/io/github/jopenlibs/vault/api/database/Database.java index 7f3b2b48..24feeb70 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/database/Database.java +++ b/src/main/java/io/github/jopenlibs/vault/api/database/Database.java @@ -2,6 +2,7 @@ import io.github.jopenlibs.vault.VaultConfig; import io.github.jopenlibs.vault.VaultException; +import io.github.jopenlibs.vault.api.OperationsBase; import io.github.jopenlibs.vault.json.Json; import io.github.jopenlibs.vault.json.JsonObject; import io.github.jopenlibs.vault.response.DatabaseResponse; @@ -13,12 +14,13 @@ /** *

    The implementing class for operations on Vault's database backend.

    * - *

    This class is not intended to be constructed directly. Rather, it is meant to used by way of Vault - * in a DSL-style builder pattern. See the Javadoc comments of each public method for usage examples.

    + *

    This class is not intended to be constructed directly. Rather, it is meant to used by way of + * Vault + * in a DSL-style builder pattern. See the Javadoc comments of each public method for + * usage examples.

    */ -public class Database { +public class Database extends OperationsBase { - private final VaultConfig config; private final String mountPath; private String nameSpace; @@ -28,22 +30,28 @@ public Database withNameSpace(final String nameSpace) { } /** - * Constructor for use when the Database backend is mounted on the default path (i.e. /v1/database). + * Constructor for use when the Database backend is mounted on the default path (i.e. + * /v1/database). * - * @param config A container for the configuration settings needed to initialize a Vault driver instance + * @param config A container for the configuration settings needed to initialize a + * Vault driver instance */ public Database(final VaultConfig config) { this(config, "database"); } /** - * Constructor for use when the Database backend is mounted on some non-default custom path (e.g. /v1/db123). + * Constructor for use when the Database backend is mounted on some non-default custom path + * (e.g. /v1/db123). * - * @param config A container for the configuration settings needed to initialize a Vault driver instance - * @param mountPath The path on which your Vault Database backend is mounted, without the /v1/ prefix (e.g. "root-ca") + * @param config A container for the configuration settings needed to initialize a + * Vault driver instance + * @param mountPath The path on which your Vault Database backend is mounted, without the + * /v1/ prefix (e.g. "root-ca") */ public Database(final VaultConfig config, final String mountPath) { - this.config = config; + super(config); + this.mountPath = mountPath; if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) { this.nameSpace = this.config.getNameSpace(); @@ -52,10 +60,11 @@ public Database(final VaultConfig config, final String mountPath) { /** *

    Operation to create or update an role using the Database Secret engine. - * Relies on an authentication token being present in the VaultConfig instance.

    + * Relies on an authentication token being present in the VaultConfig + * instance.

    * - *

    This version of the method accepts a DatabaseRoleOptions parameter, containing optional settings - * for the role creation operation. Example usage:

    + *

    This version of the method accepts a DatabaseRoleOptions parameter, + * containing optional settings for the role creation operation. Example usage:

    * *
    *
    {@code
    @@ -72,58 +81,44 @@ public Database(final VaultConfig config, final String mountPath) {
          * 
    * * @param roleName A name for the role to be created or updated - * @param options Optional settings for the role to be created or updated (e.g. db_name, ttl, etc) + * @param options Optional settings for the role to be created or updated (e.g. db_name, ttl, + * etc) * @return A container for the information returned by Vault * @throws VaultException If any error occurs or unexpected response is received from Vault */ - public DatabaseResponse createOrUpdateRole(final String roleName, final DatabaseRoleOptions options) throws VaultException { - int retryCount = 0; - while (true) { - try { - final String requestJson = roleOptionsToJson(options); + public DatabaseResponse createOrUpdateRole(final String roleName, + final DatabaseRoleOptions options) throws VaultException { + return retry(attempt -> { + final String requestJson = roleOptionsToJson(options); - final RestResponse restResponse = new Rest()//NOPMD - .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName)) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); + final RestResponse restResponse = new Rest()//NOPMD + .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, + roleName)) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); - // Validate restResponse - if (restResponse.getStatus() != 204) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus()); - } - return new DatabaseResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + // Validate restResponse + if (restResponse.getStatus() != 204) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus(), + restResponse.getStatus()); } - } + return new DatabaseResponse(restResponse, attempt); + }); } /** - *

    Operation to retrieve an role using the Database backend. Relies on an authentication token being present in - * the VaultConfig instance.

    + *

    Operation to retrieve an role using the Database backend. Relies on an authentication + * token being present in the VaultConfig instance.

    * - *

    The role information will be populated in the roleOptions field of the DatabaseResponse - * return value. Example usage:

    + *

    The role information will be populated in the roleOptions field of the + * DatabaseResponse return value. Example usage:

    * *
    *
    {@code
    @@ -140,52 +135,35 @@ public DatabaseResponse createOrUpdateRole(final String roleName, final Database
          * @throws VaultException If any error occurs or unexpected response is received from Vault
          */
         public DatabaseResponse getRole(final String roleName) throws VaultException {
    -        int retryCount = 0;
    -        while (true) {
    -            // Make an HTTP request to Vault
    -            try {
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName))
    -                        .header("X-Vault-Token", config.getToken())
    -                        .header("X-Vault-Namespace", this.nameSpace)
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .get();
    +        return retry(attempt -> {
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath,
    +                            roleName))
    +                    .header("X-Vault-Token", config.getToken())
    +                    .header("X-Vault-Namespace", this.nameSpace)
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .get();
     
    -                // Validate response
    -                if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) {
    -                    throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
    -                }
    -                return new DatabaseResponse(restResponse, retryCount);
    -            } catch (Exception e) {
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException) e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +            // Validate response
    +            if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) {
    +                throw new VaultException(
    +                        "Vault responded with HTTP status code: " + restResponse.getStatus(),
    +                        restResponse.getStatus());
                 }
    -        }
    +            return new DatabaseResponse(restResponse, attempt);
    +        });
         }
     
         /**
          * 

    Operation to revike a certificate in the vault using the Database backend. - * Relies on an authentication token being present in - * the VaultConfig instance.

    + * Relies on an authentication token being present in the VaultConfig + * instance.

    * - *

    A successful operation will return a 204 HTTP status. A VaultException will be thrown if - * the role does not exist, or if any other problem occurs. Example usage:

    + *

    A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if the role does not exist, or if any other problem occurs. Example usage:

    * *
    *
    {@code
    @@ -202,57 +180,41 @@ public DatabaseResponse getRole(final String roleName) throws VaultException {
          * @throws VaultException If any error occurs or unexpected response is received from Vault
          */
         public DatabaseResponse revoke(final String serialNumber) throws VaultException {
    -        int retryCount = 0;
    -        while (true) {
    +        return retry(attempt -> {
                 // Make an HTTP request to Vault
                 JsonObject jsonObject = new JsonObject();
                 if (serialNumber != null) {
                     jsonObject.add("serial_number", serialNumber);
                 }
                 final String requestJson = jsonObject.toString();
    -            try {
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(String.format("%s/v1/%s/revoke", config.getAddress(), this.mountPath))
    -                        .header("X-Vault-Token", config.getToken())
    -                        .header("X-Vault-Namespace", this.nameSpace)
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .body(requestJson.getBytes(StandardCharsets.UTF_8))
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .post();
     
    -                // Validate response
    -                if (restResponse.getStatus() != 200) {
    -                    throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
    -                }
    -                return new DatabaseResponse(restResponse, retryCount);
    -            } catch (Exception e) {
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException) e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(String.format("%s/v1/%s/revoke", config.getAddress(), this.mountPath))
    +                    .header("X-Vault-Token", config.getToken())
    +                    .header("X-Vault-Namespace", this.nameSpace)
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .body(requestJson.getBytes(StandardCharsets.UTF_8))
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .post();
    +
    +            // Validate response
    +            if (restResponse.getStatus() != 200) {
    +                throw new VaultException(
    +                        "Vault responded with HTTP status code: " + restResponse.getStatus(),
    +                        restResponse.getStatus());
                 }
    -        }
    +            return new DatabaseResponse(restResponse, attempt);
    +        });
         }
     
         /**
    -     * 

    Operation to delete an role using the Database backend. Relies on an authentication token being present in - * the VaultConfig instance.

    + *

    Operation to delete an role using the Database backend. Relies on an authentication + * token being present in the VaultConfig instance.

    * - *

    A successful operation will return a 204 HTTP status. A VaultException will be thrown if - * the role does not exist, or if any other problem occurs. Example usage:

    + *

    A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if the role does not exist, or if any other problem occurs. Example usage:

    * *
    *
    {@code
    @@ -269,51 +231,36 @@ public DatabaseResponse revoke(final String serialNumber) throws VaultException
          * @throws VaultException If any error occurs or unexpected response is received from Vault
          */
         public DatabaseResponse deleteRole(final String roleName) throws VaultException {
    -        int retryCount = 0;
    -        while (true) {
    -            // Make an HTTP request to Vault
    -            try {
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName))
    -                        .header("X-Vault-Token", config.getToken())
    -                        .header("X-Vault-Namespace", this.nameSpace)
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .delete();
    +        return retry(attempt -> {
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath,
    +                            roleName))
    +                    .header("X-Vault-Token", config.getToken())
    +                    .header("X-Vault-Namespace", this.nameSpace)
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .delete();
     
    -                // Validate response
    -                if (restResponse.getStatus() != 204) {
    -                    throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
    -                }
    -                return new DatabaseResponse(restResponse, retryCount);
    -            } catch (Exception e) {
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException) e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +            // Validate response
    +            if (restResponse.getStatus() != 204) {
    +                throw new VaultException(
    +                        "Vault responded with HTTP status code: " + restResponse.getStatus(),
    +                        restResponse.getStatus());
                 }
    -        }
    +            return new DatabaseResponse(restResponse, attempt);
    +        });
         }
     
         /**
          * 

    Operation to generate a new set of credentials using the Database backend. * - *

    A successful operation will return a 204 HTTP status. A VaultException will be thrown if - * the role does not exist, or if any other problem occurs. Credential information will be populated in the - * credential field of the DatabaseResponse return value. Example usage:

    + *

    A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if the role does not exist, or if any other problem occurs. Credential information + * will be populated in the + * credential field of the DatabaseResponse return value. Example + * usage:

    * *
    *
    {@code
    @@ -330,44 +277,30 @@ public DatabaseResponse deleteRole(final String roleName) throws VaultException
          * @throws VaultException If any error occurs or unexpected response is received from Vault
          */
         public DatabaseResponse creds(final String roleName) throws VaultException {
    -        int retryCount = 0;
    -        while (true) {
    -            try {
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(String.format("%s/v1/%s/creds/%s", config.getAddress(), this.mountPath, roleName))
    -                        .header("X-Vault-Token", config.getToken())
    -                        .header("X-Vault-Namespace", this.nameSpace)
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .get();
    -
    -                // Validate response
    -                if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) {
    -                    String body = restResponse.getBody() != null ? new String(restResponse.getBody()) : "(no body)";
    -                    throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() + " " + body, restResponse.getStatus());
    -                }
    +        return retry(attempt -> {
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(String.format("%s/v1/%s/creds/%s", config.getAddress(), this.mountPath,
    +                            roleName))
    +                    .header("X-Vault-Token", config.getToken())
    +                    .header("X-Vault-Namespace", this.nameSpace)
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .get();
     
    -                return new DatabaseResponse(restResponse, retryCount);
    -            } catch (Exception e) {
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException) e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +            // Validate response
    +            if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) {
    +                String body =
    +                        restResponse.getBody() != null ? new String(restResponse.getBody())
    +                                : "(no body)";
    +                throw new VaultException(
    +                        "Vault responded with HTTP status code: " + restResponse.getStatus()
    +                                + " " + body, restResponse.getStatus());
                 }
    -        }
    +
    +            return new DatabaseResponse(restResponse, attempt);
    +        });
         }
     
         private String roleOptionsToJson(final DatabaseRoleOptions options) {
    @@ -377,10 +310,14 @@ private String roleOptionsToJson(final DatabaseRoleOptions options) {
                 addJsonFieldIfNotNull(jsonObject, "db_name", options.getDbName());
                 addJsonFieldIfNotNull(jsonObject, "default_ttl", options.getDefaultTtl());
                 addJsonFieldIfNotNull(jsonObject, "max_ttl", options.getMaxTtl());
    -            addJsonFieldIfNotNull(jsonObject, "creation_statements", joinList(options.getCreationStatements()));
    -            addJsonFieldIfNotNull(jsonObject, "revocation_statements", joinList(options.getRevocationStatements()));
    -            addJsonFieldIfNotNull(jsonObject, "rollback_statements", joinList(options.getRollbackStatements()));
    -            addJsonFieldIfNotNull(jsonObject, "renew_statements", joinList(options.getRenewStatements()));
    +            addJsonFieldIfNotNull(jsonObject, "creation_statements",
    +                    joinList(options.getCreationStatements()));
    +            addJsonFieldIfNotNull(jsonObject, "revocation_statements",
    +                    joinList(options.getRevocationStatements()));
    +            addJsonFieldIfNotNull(jsonObject, "rollback_statements",
    +                    joinList(options.getRollbackStatements()));
    +            addJsonFieldIfNotNull(jsonObject, "renew_statements",
    +                    joinList(options.getRenewStatements()));
             }
     
             return jsonObject.toString();
    @@ -396,7 +333,8 @@ private String joinList(List data) {
             return result;
         }
     
    -    private JsonObject addJsonFieldIfNotNull(final JsonObject jsonObject, final String name, final Object value) {
    +    private JsonObject addJsonFieldIfNotNull(final JsonObject jsonObject, final String name,
    +            final Object value) {
             if (value == null) {
                 return jsonObject;
             }
    diff --git a/src/main/java/io/github/jopenlibs/vault/api/mounts/Mounts.java b/src/main/java/io/github/jopenlibs/vault/api/mounts/Mounts.java
    index 9f94c323..295c25ac 100644
    --- a/src/main/java/io/github/jopenlibs/vault/api/mounts/Mounts.java
    +++ b/src/main/java/io/github/jopenlibs/vault/api/mounts/Mounts.java
    @@ -2,30 +2,34 @@
     
     import io.github.jopenlibs.vault.VaultConfig;
     import io.github.jopenlibs.vault.VaultException;
    +import io.github.jopenlibs.vault.api.OperationsBase;
     import io.github.jopenlibs.vault.response.MountResponse;
     import io.github.jopenlibs.vault.rest.Rest;
     import io.github.jopenlibs.vault.rest.RestResponse;
     import java.nio.charset.StandardCharsets;
     
     /**
    - * 

    The implementing class for operations on Vault's /v1/sys/mounts/* REST endpoints.

    + *

    The implementing class for operations on Vault's /v1/sys/mounts/* REST + * endpoints.

    * - *

    This class is not intended to be constructed directly. Rather, it is meant to used by way of Vault - * in a DSL-style builder pattern. See the Javadoc comments of each public method for usage examples.

    + *

    This class is not intended to be constructed directly. Rather, it is meant to used by way of + * Vault + * in a DSL-style builder pattern. See the Javadoc comments of each public method for + * usage examples.

    */ -public class Mounts { - private final VaultConfig config; +public class Mounts extends OperationsBase { public Mounts(final VaultConfig config) { - this.config = config; + super(config); } /** - *

    Operation to list all the mounted secrets engines. Relies on an authentication token being present in - * the VaultConfig instance.

    + *

    Operation to list all the mounted secrets engines. Relies on an authentication token + * being present in the VaultConfig instance.

    * - *

    The list of mount points information will be populated in the mounts field of the MountResponse - * return value in the Map<String, Mount> format. Example usage:

    + *

    The list of mount points information will be populated in the mounts field + * of the MountResponse return value in the Map<String, Mount> + * format. Example usage:

    * *
    *
    {@code
    @@ -38,59 +42,42 @@ public Mounts(final VaultConfig config) {
          * 
    * * @return A container for the information returned by Vault - * * @throws VaultException If any error occurs or unexpected response is received from Vault */ public MountResponse list() throws VaultException { - int retryCount = 0; - - while (true) { - try { - final RestResponse restResponse = new Rest()//NOPMD - .url(String.format("%s/v1/sys/mounts", config.getAddress())) - .header("X-Vault-Token", config.getToken()) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .get(); - - // Validate restResponse - if (restResponse.getStatus() != 200) { - String body = restResponse.getBody() != null ? new String(restResponse.getBody()) : "(no body)"; - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() + " " + body, restResponse.getStatus()); - } - - return new MountResponse(restResponse, retryCount, true); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up - throw (VaultException)e; - } else { - throw new VaultException(e); - } + return retry(attempt -> { + final RestResponse restResponse = new Rest()//NOPMD + .url(String.format("%s/v1/sys/mounts", config.getAddress())) + .header("X-Vault-Token", config.getToken()) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .get(); + + // Validate restResponse + if (restResponse.getStatus() != 200) { + String body = restResponse.getBody() != null ? new String(restResponse.getBody()) + : "(no body)"; + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + " " + + body, restResponse.getStatus()); } - } + + return new MountResponse(restResponse, attempt, true); + }); } /** - *

    Operation to enable secrets engine at given path. Relies on an authentication token being present in - * the VaultConfig instance.

    + *

    Operation to enable secrets engine at given path. Relies on an authentication token + * being present in the VaultConfig instance.

    * - *

    This method accepts a MountConfig parameter, containing optional settings for the mount - * creation operation. Example usage:

    + *

    This method accepts a MountConfig parameter, containing optional settings + * for the mount creation operation. Example usage:

    * - *

    A successful operation will return a 204 HTTP status. A VaultException will be thrown if - * mount point already exists, or if any other problem occurs. Example usage:

    + *

    A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if mount point already exists, or if any other problem occurs. Example + * usage:

    * *
    *
    {@code
    @@ -111,70 +98,52 @@ public MountResponse list() throws VaultException {
          * @param path The path to enable secret engine on.
          * @param type The type of secret engine to enable.
          * @param payload The MountPayload instance to use to create secret engine.
    -     *
          * @return A container for the information returned by Vault
    -     *
          * @throws VaultException If any error occurs or unexpected response is received from Vault
          */
    -    public MountResponse enable(final String path, final MountType type, final MountPayload payload) throws VaultException {
    -        int retryCount = 0;
    -
    -        while (true) {
    -            try {
    -                if (type == null) {
    -                    throw new VaultException("Mount type is missing");
    -                }
    -
    -                if (payload == null) {
    -                    throw new VaultException("MountPayload is missing");
    -                }
    -
    -                final String requestJson = payload.toEnableJson(type).toString();
    -
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(String.format("%s/v1/sys/mounts/%s", config.getAddress(), path))
    -                        .header("X-Vault-Token", config.getToken())
    -                        .body(requestJson.getBytes(StandardCharsets.UTF_8))
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .post();
    -
    -                // Validate restResponse
    -                if (restResponse.getStatus() != 204) {
    -                    String body = restResponse.getBody() != null ? new String(restResponse.getBody()) : "(no body)";
    -                    throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() + " " + body, restResponse.getStatus());
    -                }
    +    public MountResponse enable(final String path, final MountType type, final MountPayload payload)
    +            throws VaultException {
    +        if (type == null) {
    +            throw new VaultException("Mount type is missing");
    +        }
     
    -                return new MountResponse(restResponse, retryCount, false);
    -            } catch (Exception e) {
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    +        if (payload == null) {
    +            throw new VaultException("MountPayload is missing");
    +        }
     
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException)e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +        return retry(attempt -> {
    +            final String requestJson = payload.toEnableJson(type).toString();
    +
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(String.format("%s/v1/sys/mounts/%s", config.getAddress(), path))
    +                    .header("X-Vault-Token", config.getToken())
    +                    .body(requestJson.getBytes(StandardCharsets.UTF_8))
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .post();
    +
    +            // Validate restResponse
    +            if (restResponse.getStatus() != 204) {
    +                String body =
    +                        restResponse.getBody() != null ? new String(restResponse.getBody())
    +                                : "(no body)";
    +                throw new VaultException(
    +                        "Vault responded with HTTP status code: " + restResponse.getStatus()
    +                                + " " + body, restResponse.getStatus());
                 }
    -        }
    +
    +            return new MountResponse(restResponse, attempt, false);
    +        });
         }
     
         /**
    -     * 

    Operation to disable secrets engine mount point of given path. Relies on an authentication token being - * present in the VaultConfig instance.

    + *

    Operation to disable secrets engine mount point of given path. Relies on an + * authentication token being present in the VaultConfig instance.

    * - *

    A successful operation will return a 204 HTTP status. A VaultException will be thrown if - * the mount point not exist, or if any other problem occurs. Example usage:

    + *

    A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if the mount point not exist, or if any other problem occurs. Example usage:

    * *
    *
    {@code
    @@ -188,59 +157,40 @@ public MountResponse enable(final String path, final MountType type, final Mount
          * 
    * * @param path The path to disable secret engine on. - * * @return A container for the information returned by Vault - * * @throws VaultException If any error occurs or unexpected response is received from Vault */ public MountResponse disable(final String path) throws VaultException { - int retryCount = 0; - - while (true) { - try { - final RestResponse restResponse = new Rest()//NOPMD - .url(String.format("%s/v1/sys/mounts/%s", config.getAddress(), path)) - .header("X-Vault-Token", config.getToken()) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .delete(); - - // Validate restResponse - if (restResponse.getStatus() != 204) { - String body = restResponse.getBody() != null ? new String(restResponse.getBody()) : "(no body)"; - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() + " " + body, restResponse.getStatus()); - } - - return new MountResponse(restResponse, retryCount, false); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException)e; - } else { - throw new VaultException(e); - } + return retry(attempt -> { + final RestResponse restResponse = new Rest()//NOPMD + .url(String.format("%s/v1/sys/mounts/%s", config.getAddress(), path)) + .header("X-Vault-Token", config.getToken()) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .delete(); + + // Validate restResponse + if (restResponse.getStatus() != 204) { + String body = + restResponse.getBody() != null ? new String(restResponse.getBody()) + : "(no body)"; + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + " " + body, restResponse.getStatus()); } - } + + return new MountResponse(restResponse, attempt, false); + }); } /** - *

    Operation to read secrets engine mount point's configuration of given path. Relies on an authentication - * token being present in the VaultConfig instance.

    + *

    Operation to read secrets engine mount point's configuration of given path. Relies on an + * authentication token being present in the VaultConfig instance.

    * - *

    The mount point information will be populated in the mount field of the MountResponse - * return value. Example usage:

    + *

    The mount point information will be populated in the mount field of the + * MountResponse return value. Example usage:

    * *
    *
    {@code
    @@ -254,62 +204,43 @@ public MountResponse disable(final String path) throws VaultException {
          * 
    * * @param path The path to read secret engine's configuration from. - * * @return A container for the information returned by Vault - * * @throws VaultException If any error occurs or unexpected response is received from Vault */ public MountResponse read(final String path) throws VaultException { - int retryCount = 0; - - while (true) { - try { - final RestResponse restResponse = new Rest()//NOPMD - .url(String.format("%s/v1/sys/mounts/%s/tune", config.getAddress(), path)) - .header("X-Vault-Token", config.getToken()) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .get(); - - // Validate restResponse - if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) { - String body = restResponse.getBody() != null ? new String(restResponse.getBody()) : "(no body)"; - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() + " " + body, restResponse.getStatus()); - } - - return new MountResponse(restResponse, retryCount, false); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException)e; - } else { - throw new VaultException(e); - } + return retry(attempt -> { + final RestResponse restResponse = new Rest()//NOPMD + .url(String.format("%s/v1/sys/mounts/%s/tune", config.getAddress(), path)) + .header("X-Vault-Token", config.getToken()) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .get(); + + // Validate restResponse + if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) { + String body = + restResponse.getBody() != null ? new String(restResponse.getBody()) + : "(no body)"; + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + " " + body, restResponse.getStatus()); } - } + + return new MountResponse(restResponse, attempt, false); + }); } /** - *

    Operation to tune secrets engine mount point's configuration of given path. Relies on an authentication - * token being present in the VaultConfig instance.

    + *

    Operation to tune secrets engine mount point's configuration of given path. Relies on an + * authentication token being present in the VaultConfig instance.

    * - *

    This the method accepts a MountConfig parameter, containing optional settings for the mount - * tune operation. Example usage:

    + *

    This the method accepts a MountConfig parameter, containing optional + * settings for the mount tune operation. Example usage:

    * - *

    A successful operation will return a 204 HTTP status. A VaultException will be thrown if - * the mount point not exist, or if any other problem occurs. Example usage:

    + *

    A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if the mount point not exist, or if any other problem occurs. Example usage:

    * *
    *
    {@code
    @@ -329,57 +260,38 @@ public MountResponse read(final String path) throws VaultException {
          *
          * @param path The path to tune secret engine's configuration on.
          * @param payload The MountPayload instance to use to tune secret engine.
    -     *
          * @return A container for the information returned by Vault
    -     *
          * @throws VaultException If any error occurs or unexpected response is received from Vault
          */
         public MountResponse tune(final String path, final MountPayload payload) throws VaultException {
    -        int retryCount = 0;
    -
    -        while (true) {
    -            try {
    -                if (payload == null) {
    -                    throw new VaultException("MountPayload is missing");
    -                }
    -
    -                final String requestJson = payload.toTuneJson().toString();
    -
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(String.format("%s/v1/sys/mounts/%s/tune", config.getAddress(), path))
    -                        .header("X-Vault-Token", config.getToken())
    -                        .body(requestJson.getBytes(StandardCharsets.UTF_8))
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .post();
    -
    -                // Validate restResponse
    -                if (restResponse.getStatus() != 204) {
    -                    String body = restResponse.getBody() != null ? new String(restResponse.getBody()) : "(no body)";
    -                    throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() + " " + body, restResponse.getStatus());
    -                }
    -
    -                return new MountResponse(restResponse, retryCount, false);
    -            } catch (Exception e) {
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    +        return retry(attempt -> {
    +            if (payload == null) {
    +                throw new VaultException("MountPayload is missing");
    +            }
     
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException)e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +            final String requestJson = payload.toTuneJson().toString();
    +
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(String.format("%s/v1/sys/mounts/%s/tune", config.getAddress(), path))
    +                    .header("X-Vault-Token", config.getToken())
    +                    .body(requestJson.getBytes(StandardCharsets.UTF_8))
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .post();
    +
    +            // Validate restResponse
    +            if (restResponse.getStatus() != 204) {
    +                String body =
    +                        restResponse.getBody() != null ? new String(restResponse.getBody())
    +                                : "(no body)";
    +                throw new VaultException(
    +                        "Vault responded with HTTP status code: " + restResponse.getStatus()
    +                                + " " + body, restResponse.getStatus());
                 }
    -        }
    +
    +            return new MountResponse(restResponse, attempt, false);
    +        });
         }
     }
    diff --git a/src/main/java/io/github/jopenlibs/vault/api/mounts/TimeToLive.java b/src/main/java/io/github/jopenlibs/vault/api/mounts/TimeToLive.java
    index 9198babf..153a779a 100644
    --- a/src/main/java/io/github/jopenlibs/vault/api/mounts/TimeToLive.java
    +++ b/src/main/java/io/github/jopenlibs/vault/api/mounts/TimeToLive.java
    @@ -1,5 +1,6 @@
     package io.github.jopenlibs.vault.api.mounts;
     
    +import java.util.Objects;
     import java.util.concurrent.TimeUnit;
     
     /**
    @@ -30,7 +31,8 @@ public static TimeToLive of(final int ttl, final TimeUnit unit) {
         }
     
         private TimeToLive(final int ttl, final TimeUnit unit) {
    -        if (unit == null) throw new NullPointerException("unit is null");
    +        Objects.requireNonNull(unit, "unit is null");
    +
             this.ttl = ttl;
             this.unit = unit;
         }
    diff --git a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
    index a8d35baa..345817ff 100644
    --- a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
    +++ b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
    @@ -2,6 +2,7 @@
     
     import io.github.jopenlibs.vault.VaultConfig;
     import io.github.jopenlibs.vault.VaultException;
    +import io.github.jopenlibs.vault.api.OperationsBase;
     import io.github.jopenlibs.vault.json.Json;
     import io.github.jopenlibs.vault.json.JsonObject;
     import io.github.jopenlibs.vault.response.PkiResponse;
    @@ -14,12 +15,13 @@
     /**
      * 

    The implementing class for operations on Vault's PKI backend.

    * - *

    This class is not intended to be constructed directly. Rather, it is meant to used by way of Vault - * in a DSL-style builder pattern. See the Javadoc comments of each public method for usage examples.

    + *

    This class is not intended to be constructed directly. Rather, it is meant to used by way of + * Vault + * in a DSL-style builder pattern. See the Javadoc comments of each public method for + * usage examples.

    */ -public class Pki { +public class Pki extends OperationsBase { - private final VaultConfig config; private final String mountPath; private String nameSpace; @@ -29,26 +31,28 @@ public Pki withNameSpace(final String nameSpace) { } /** - * Constructor for use when the PKI backend is mounted on the default path (i.e. /v1/pki). + * Constructor for use when the PKI backend is mounted on the default path (i.e. + * /v1/pki). * - * @param config A container for the configuration settings needed to initialize a Vault driver instance + * @param config A container for the configuration settings needed to initialize a + * Vault driver instance */ public Pki(final VaultConfig config) { - this.config = config; - this.mountPath = "pki"; - if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) { - this.nameSpace = this.config.getNameSpace(); - } + this(config, "pki"); } /** - * Constructor for use when the PKI backend is mounted on some non-default custom path (e.g. /v1/root-ca). + * Constructor for use when the PKI backend is mounted on some non-default custom path (e.g. + * /v1/root-ca). * - * @param config A container for the configuration settings needed to initialize a Vault driver instance - * @param mountPath The path on which your Vault PKI backend is mounted, without the /v1/ prefix (e.g. "root-ca") + * @param config A container for the configuration settings needed to initialize a + * Vault driver instance + * @param mountPath The path on which your Vault PKI backend is mounted, without the + * /v1/ prefix (e.g. "root-ca") */ public Pki(final VaultConfig config, final String mountPath) { - this.config = config; + super(config); + this.mountPath = mountPath; if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) { this.nameSpace = this.config.getNameSpace(); @@ -56,10 +60,11 @@ public Pki(final VaultConfig config, final String mountPath) { } /** - *

    Operation to create an role using the PKI backend. Relies on an authentication token being present in - * the VaultConfig instance.

    + *

    Operation to create an role using the PKI backend. Relies on an authentication token + * being present in the VaultConfig instance.

    * - *

    This version of the method uses default values for all optional settings. Example usage:

    + *

    This version of the method uses default values for all optional settings. Example + * usage:

    * *
    *
    {@code
    @@ -80,11 +85,11 @@ public PkiResponse createOrUpdateRole(final String roleName) throws VaultExcepti
         }
     
         /**
    -     * 

    Operation to create an role using the PKI backend. Relies on an authentication token being present in - * the VaultConfig instance.

    + *

    Operation to create an role using the PKI backend. Relies on an authentication token + * being present in the VaultConfig instance.

    * - *

    This version of the method accepts a RoleOptions parameter, containing optional settings - * for the role creation operation. Example usage:

    + *

    This version of the method accepts a RoleOptions parameter, containing + * optional settings for the role creation operation. Example usage:

    * *
    *
    {@code
    @@ -102,60 +107,46 @@ public PkiResponse createOrUpdateRole(final String roleName) throws VaultExcepti
          * 
    * * @param roleName A name for the role to be created or updated - * @param options Optional settings for the role to be created or updated (e.g. allowed domains, ttl, etc) + * @param options Optional settings for the role to be created or updated (e.g. allowed domains, + * ttl, etc) * @return A container for the information returned by Vault * @throws VaultException If any error occurs or unexpected response is received from Vault */ - public PkiResponse createOrUpdateRole(final String roleName, final RoleOptions options) throws VaultException { - int retryCount = 0; - while (true) { - try { - final String requestJson = roleOptionsToJson(options); - - final RestResponse restResponse = new Rest()//NOPMD - .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName)) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - // TODO: handle warnings - if (restResponse.getStatus() != 204 && restResponse.getStatus() != 200) { - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus()); - } + public PkiResponse createOrUpdateRole(final String roleName, final RoleOptions options) + throws VaultException { + return retry(attempt -> { + final String requestJson = roleOptionsToJson(options); - return new PkiResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + final RestResponse restResponse = new Rest()//NOPMD + .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, + roleName)) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + // TODO: handle warnings + if (restResponse.getStatus() != 204 && restResponse.getStatus() != 200) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus(), + restResponse.getStatus()); } - } + + return new PkiResponse(restResponse, attempt); + }); } /** - *

    Operation to retrieve an role using the PKI backend. Relies on an authentication token being present in - * the VaultConfig instance.

    + *

    Operation to retrieve an role using the PKI backend. Relies on an authentication token + * being present in the VaultConfig instance.

    * - *

    The role information will be populated in the roleOptions field of the PkiResponse - * return value. Example usage:

    + *

    The role information will be populated in the roleOptions field of the + * PkiResponse return value. Example usage:

    * *
    *
    {@code
    @@ -172,52 +163,35 @@ public PkiResponse createOrUpdateRole(final String roleName, final RoleOptions o
          * @throws VaultException If any error occurs or unexpected response is received from Vault
          */
         public PkiResponse getRole(final String roleName) throws VaultException {
    -        int retryCount = 0;
    -        while (true) {
    -            // Make an HTTP request to Vault
    -            try {
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName))
    -                        .header("X-Vault-Token", config.getToken())
    -                        .header("X-Vault-Namespace", this.nameSpace)
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .get();
    -
    -                // Validate response
    -                if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) {
    -                    throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
    -                }
    -                return new PkiResponse(restResponse, retryCount);
    -            } catch (Exception e) {
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException) e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +        return retry(attempt -> {
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath,
    +                            roleName))
    +                    .header("X-Vault-Token", config.getToken())
    +                    .header("X-Vault-Namespace", this.nameSpace)
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .get();
    +
    +            // Validate response
    +            if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) {
    +                throw new VaultException(
    +                        "Vault responded with HTTP status code: " + restResponse.getStatus(),
    +                        restResponse.getStatus());
                 }
    -        }
    +            return new PkiResponse(restResponse, attempt);
    +        });
         }
     
         /**
          * 

    Operation to revike a certificate in the vault using the PKI backend. - * Relies on an authentication token being present in - * the VaultConfig instance.

    + * Relies on an authentication token being present in the VaultConfig + * instance.

    * - *

    A successful operation will return a 204 HTTP status. A VaultException will be thrown if - * the role does not exist, or if any other problem occurs. Example usage:

    + *

    A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if the role does not exist, or if any other problem occurs. Example usage:

    * *
    *
    {@code
    @@ -234,57 +208,40 @@ public PkiResponse getRole(final String roleName) throws VaultException {
          * @throws VaultException If any error occurs or unexpected response is received from Vault
          */
         public PkiResponse revoke(final String serialNumber) throws VaultException {
    -        int retryCount = 0;
    -        while (true) {
    -            // Make an HTTP request to Vault
    +        return retry(attempt -> {
                 JsonObject jsonObject = new JsonObject();
    +
                 if (serialNumber != null) {
                     jsonObject.add("serial_number", serialNumber);
                 }
                 final String requestJson = jsonObject.toString();
    -            try {
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(String.format("%s/v1/%s/revoke", config.getAddress(), this.mountPath))
    -                        .header("X-Vault-Token", config.getToken())
    -                        .header("X-Vault-Namespace", this.nameSpace)
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .body(requestJson.getBytes(StandardCharsets.UTF_8))
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .post();
    -
    -                // Validate response
    -                if (restResponse.getStatus() != 200) {
    -                    throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
    -                }
    -                return new PkiResponse(restResponse, retryCount);
    -            } catch (Exception e) {
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException) e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(String.format("%s/v1/%s/revoke", config.getAddress(), this.mountPath))
    +                    .header("X-Vault-Token", config.getToken())
    +                    .header("X-Vault-Namespace", this.nameSpace)
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .body(requestJson.getBytes(StandardCharsets.UTF_8))
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .post();
    +
    +            // Validate response
    +            if (restResponse.getStatus() != 200) {
    +                throw new VaultException(
    +                        "Vault responded with HTTP status code: " + restResponse.getStatus(),
    +                        restResponse.getStatus());
                 }
    -        }
    +            return new PkiResponse(restResponse, attempt);
    +        });
         }
     
         /**
    -     * 

    Operation to delete an role using the PKI backend. Relies on an authentication token being present in - * the VaultConfig instance.

    + *

    Operation to delete an role using the PKI backend. Relies on an authentication token + * being present in the VaultConfig instance.

    * - *

    A successful operation will return a 204 HTTP status. A VaultException will be thrown if - * the role does not exist, or if any other problem occurs. Example usage:

    + *

    A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if the role does not exist, or if any other problem occurs. Example usage:

    * *
    *
    {@code
    @@ -301,53 +258,37 @@ public PkiResponse revoke(final String serialNumber) throws VaultException {
          * @throws VaultException If any error occurs or unexpected response is received from Vault
          */
         public PkiResponse deleteRole(final String roleName) throws VaultException {
    -        int retryCount = 0;
    -        while (true) {
    -            // Make an HTTP request to Vault
    -            try {
    -                final RestResponse restResponse = new Rest()//NOPMD
    -                        .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName))
    -                        .header("X-Vault-Token", config.getToken())
    -                        .header("X-Vault-Namespace", this.nameSpace)
    -                        .connectTimeoutSeconds(config.getOpenTimeout())
    -                        .readTimeoutSeconds(config.getReadTimeout())
    -                        .sslVerification(config.getSslConfig().isVerify())
    -                        .sslContext(config.getSslConfig().getSslContext())
    -                        .delete();
    -
    -                // Validate response
    -                if (restResponse.getStatus() != 204) {
    -                    throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus(), restResponse.getStatus());
    -                }
    -                return new PkiResponse(restResponse, retryCount);
    -            } catch (Exception e) {
    -                // If there are retries to perform, then pause for the configured interval and then execute the loop again...
    -                if (retryCount < config.getMaxRetries()) {
    -                    retryCount++;
    -                    try {
    -                        final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
    -                        Thread.sleep(retryIntervalMilliseconds);
    -                    } catch (InterruptedException e1) {
    -                        e1.printStackTrace();
    -                    }
    -                } else if (e instanceof VaultException) {
    -                    // ... otherwise, give up.
    -                    throw (VaultException) e;
    -                } else {
    -                    throw new VaultException(e);
    -                }
    +        return retry(attempt -> {
    +            final RestResponse restResponse = new Rest()//NOPMD
    +                    .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath,
    +                            roleName))
    +                    .header("X-Vault-Token", config.getToken())
    +                    .header("X-Vault-Namespace", this.nameSpace)
    +                    .connectTimeoutSeconds(config.getOpenTimeout())
    +                    .readTimeoutSeconds(config.getReadTimeout())
    +                    .sslVerification(config.getSslConfig().isVerify())
    +                    .sslContext(config.getSslConfig().getSslContext())
    +                    .delete();
    +
    +            // Validate response
    +            if (restResponse.getStatus() != 204) {
    +                throw new VaultException(
    +                        "Vault responded with HTTP status code: " + restResponse.getStatus(),
    +                        restResponse.getStatus());
                 }
    -        }
    +            return new PkiResponse(restResponse, attempt);
    +        });
         }
     
         /**
    -     * 

    Operation to generate a new set of credentials (private key and certificate) based on a given role using - * the PKI backend. The issuing CA certificate is returned as well, so that only the root CA need be in a - * client's trust store.

    + *

    Operation to generate a new set of credentials (private key and certificate) based on a + * given role using the PKI backend. The issuing CA certificate is returned as well, so that + * only the root CA need be in a client's trust store.

    * - *

    A successful operation will return a 204 HTTP status. A VaultException will be thrown if - * the role does not exist, or if any other problem occurs. Credential information will be populated in the - * credential field of the PkiResponse return value. Example usage:

    + *

    A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if the role does not exist, or if any other problem occurs. Credential information + * will be populated in the credential field of the PkiResponse return + * value. Example usage:

    * *
    *
    {@code
    @@ -359,12 +300,20 @@ public PkiResponse deleteRole(final String roleName) throws VaultException {
          * }
    *
    * - * @param roleName The role on which the credentials will be based. - * @param commonName The requested CN for the certificate. If the CN is allowed by role policy, it will be issued. - * @param altNames (optional) Requested Subject Alternative Names, in a comma-delimited list. These can be host names or email addresses; they will be parsed into their respective fields. If any requested names do not match role policy, the entire request will be denied. - * @param ipSans (optional) Requested IP Subject Alternative Names, in a comma-delimited list. Only valid if the role allows IP SANs (which is the default). - * @param ttl (optional) Requested Time To Live. Cannot be greater than the role's max_ttl value. If not provided, the role's ttl value will be used. Note that the role values default to system values if not explicitly set. - * @param format (optional) Format for returned data. Can be pem, der, or pem_bundle; defaults to pem. If der, the output is base64 encoded. If pem_bundle, the certificate field will contain the private key, certificate, and issuing CA, concatenated. + * @param roleName The role on which the credentials will be based. + * @param commonName The requested CN for the certificate. If the CN is allowed by role policy, + * it will be issued. + * @param altNames (optional) Requested Subject Alternative Names, in a comma-delimited list. + * These can be host names or email addresses; they will be parsed into their respective fields. + * If any requested names do not match role policy, the entire request will be denied. + * @param ipSans (optional) Requested IP Subject Alternative Names, in a comma-delimited list. + * Only valid if the role allows IP SANs (which is the default). + * @param ttl (optional) Requested Time To Live. Cannot be greater than the role's max_ttl + * value. If not provided, the role's ttl value will be used. Note that the role values default + * to system values if not explicitly set. + * @param format (optional) Format for returned data. Can be pem, der, or pem_bundle; defaults + * to pem. If der, the output is base64 encoded. If pem_bundle, the certificate field will + * contain the private key, certificate, and issuing CA, concatenated. * @return A container for the information returned by Vault * @throws VaultException If any error occurs or unexpected response is received from Vault */ @@ -380,14 +329,16 @@ public PkiResponse issue( } /** - *

    Operation to generate a new set of credentials or sign the embedded CSR, in the PKI backend. If CSR is passed the - * sign function of the vault will be called if not, issue will be used. - * The issuing CA certificate is returned as well, so that only the root CA need be in a + *

    Operation to generate a new set of credentials or sign the embedded CSR, in the PKI + * backend. If CSR is passed the sign function of the vault will be called if not, issue will be + * used. The issuing CA certificate is returned as well, so that only the root CA need be in a * client's trust store.

    * - *

    A successful operation will return a 204 HTTP status. A VaultException will be thrown if - * the role does not exist, or if any other problem occurs. Credential information will be populated in the - * credential field of the PkiResponse return value. Example usage:

    + *

    A successful operation will return a 204 HTTP status. A VaultException will + * be thrown if the role does not exist, or if any other problem occurs. Credential information + * will be populated in the + * credential field of the PkiResponse return value. Example + * usage:

    * *
    *
    {@code
    @@ -399,18 +350,24 @@ public PkiResponse issue(
          * }
    *
    * - * @param roleName The role on which the credentials will be based. - * @param commonName The requested CN for the certificate. If the CN is allowed by role policy, it will be issued. - * @param altNames (optional) Requested Subject Alternative Names, in a comma-delimited list. These can be host names or email addresses; they will be parsed into their respective fields. If any requested names do not match role policy, the entire request will be denied. - * @param ipSans (optional) Requested IP Subject Alternative Names, in a comma-delimited list. Only valid if the role allows IP SANs (which is the default). - * @param ttl (optional) Requested Time To Live. Cannot be greater than the role's max_ttl value. If not provided, the role's ttl value will be used. Note that the role values default to system values if not explicitly set. - * @param format (optional) Format for returned data. Can be pem, der, or pem_bundle; defaults to pem. If der, the output is base64 encoded. If pem_bundle, the certificate field will contain the private key, certificate, and issuing CA, concatenated. - * @param csr (optional) PEM Encoded CSR + * @param roleName The role on which the credentials will be based. + * @param commonName The requested CN for the certificate. If the CN is allowed by role policy, + * it will be issued. + * @param altNames (optional) Requested Subject Alternative Names, in a comma-delimited list. + * These can be host names or email addresses; they will be parsed into their respective fields. + * If any requested names do not match role policy, the entire request will be denied. + * @param ipSans (optional) Requested IP Subject Alternative Names, in a comma-delimited list. + * Only valid if the role allows IP SANs (which is the default). + * @param ttl (optional) Requested Time To Live. Cannot be greater than the role's max_ttl + * value. If not provided, the role's ttl value will be used. Note that the role values default + * to system values if not explicitly set. + * @param format (optional) Format for returned data. Can be pem, der, or pem_bundle; defaults + * to pem. If der, the output is base64 encoded. If pem_bundle, the certificate field will + * contain the private key, certificate, and issuing CA, concatenated. + * @param csr (optional) PEM Encoded CSR * @return A container for the information returned by Vault * @throws VaultException If any error occurs or unexpected response is received from Vault */ - - public PkiResponse issue( final String roleName, final String commonName, @@ -420,13 +377,14 @@ public PkiResponse issue( final CredentialFormat format, final String csr ) throws VaultException { - int retryCount = 0; - while (true) { + return retry(attempt -> { // Construct a JSON body from inputs final JsonObject jsonObject = Json.object(); + if (commonName != null) { jsonObject.add("common_name", commonName); } + if (altNames != null && !altNames.isEmpty()) { final StringBuilder altNamesCsv = new StringBuilder();//NOPMD for (int index = 0; index < altNames.size(); index++) { @@ -437,6 +395,7 @@ public PkiResponse issue( } jsonObject.add("alt_names", altNamesCsv.toString()); } + if (ipSans != null && !ipSans.isEmpty()) { final StringBuilder ipSansCsv = new StringBuilder();//NOPMD for (int index = 0; index < ipSans.size(); index++) { @@ -447,55 +406,46 @@ public PkiResponse issue( } jsonObject.add("ip_sans", ipSansCsv.toString()); } + if (ttl != null) { jsonObject.add("ttl", ttl); } + if (format != null) { jsonObject.add("format", format.toString()); } + if (csr != null) { jsonObject.add("csr", csr); } + final String requestJson = jsonObject.toString(); - // Make an HTTP request to Vault - try { - String endpoint = (csr == null || csr.isEmpty()) ? "%s/v1/%s/issue/%s" : "%s/v1/%s/sign/%s"; - final RestResponse restResponse = new Rest()//NOPMD - .url(String.format(endpoint, config.getAddress(), this.mountPath, roleName)) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate response - if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) { - String body = restResponse.getBody() != null ? new String(restResponse.getBody()) : "(no body)"; - throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus() + " " + body, restResponse.getStatus()); - } - return new PkiResponse(restResponse, retryCount); - } catch (Exception e) { - // If there are retries to perform, then pause for the configured interval and then execute the loop again... - if (retryCount < config.getMaxRetries()) { - retryCount++; - try { - final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds(); - Thread.sleep(retryIntervalMilliseconds); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } else if (e instanceof VaultException) { - // ... otherwise, give up. - throw (VaultException) e; - } else { - throw new VaultException(e); - } + String endpoint = + (csr == null || csr.isEmpty()) ? "%s/v1/%s/issue/%s" : "%s/v1/%s/sign/%s"; + + final RestResponse restResponse = new Rest()//NOPMD + .url(String.format(endpoint, config.getAddress(), this.mountPath, roleName)) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate response + if (restResponse.getStatus() != 200 && restResponse.getStatus() != 404) { + String body = + restResponse.getBody() != null ? new String(restResponse.getBody()) + : "(no body)"; + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus() + + " " + body, restResponse.getStatus()); } - } + return new PkiResponse(restResponse, attempt); + }); } @@ -506,7 +456,8 @@ private String roleOptionsToJson(final RoleOptions options) { addJsonFieldIfNotNull(jsonObject, "max_ttl", options.getMaxTtl()); addJsonFieldIfNotNull(jsonObject, "allow_localhost", options.getAllowLocalhost()); if (options.getAllowedDomains() != null && options.getAllowedDomains().size() > 0) { - addJsonFieldIfNotNull(jsonObject, "allowed_domains", String.join(",", options.getAllowedDomains())); + addJsonFieldIfNotNull(jsonObject, "allowed_domains", + String.join(",", options.getAllowedDomains())); } addJsonFieldIfNotNull(jsonObject, "allow_spiffe_name", options.getAllowSpiffename()); addJsonFieldIfNotNull(jsonObject, "allow_bare_domains", options.getAllowBareDomains()); @@ -517,19 +468,22 @@ private String roleOptionsToJson(final RoleOptions options) { addJsonFieldIfNotNull(jsonObject, "server_flag", options.getServerFlag()); addJsonFieldIfNotNull(jsonObject, "client_flag", options.getClientFlag()); addJsonFieldIfNotNull(jsonObject, "code_signing_flag", options.getCodeSigningFlag()); - addJsonFieldIfNotNull(jsonObject, "email_protection_flag", options.getEmailProtectionFlag()); + addJsonFieldIfNotNull(jsonObject, "email_protection_flag", + options.getEmailProtectionFlag()); addJsonFieldIfNotNull(jsonObject, "key_type", options.getKeyType()); addJsonFieldIfNotNull(jsonObject, "key_bits", options.getKeyBits()); addJsonFieldIfNotNull(jsonObject, "use_csr_common_name", options.getUseCsrCommonName()); addJsonFieldIfNotNull(jsonObject, "use_csr_sans", options.getUseCsrSans()); if (options.getKeyUsage() != null && options.getKeyUsage().size() > 0) { - addJsonFieldIfNotNull(jsonObject, "key_usage", String.join(",", options.getKeyUsage())); + addJsonFieldIfNotNull(jsonObject, "key_usage", + String.join(",", options.getKeyUsage())); } } return jsonObject.toString(); } - private JsonObject addJsonFieldIfNotNull(final JsonObject jsonObject, final String name, final Object value) { + private JsonObject addJsonFieldIfNotNull(final JsonObject jsonObject, final String name, + final Object value) { if (value == null) { return jsonObject; }