diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 1dd83da3..bc21391f 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -17,29 +17,7 @@ jobs: java: - 11 vault: - - 1.1.3 - - 1.9.0 - - 1.9.1 - - 1.9.2 - - 1.9.3 - - 1.9.4 - - 1.9.5 - - 1.9.6 - - 1.9.7 - - 1.9.8 - - 1.9.9 - - 1.9.10 - - 1.10.0 - - 1.10.1 - - 1.10.2 - - 1.10.3 - - 1.10.4 - - 1.10.5 - - 1.10.6 - - 1.10.7 - - 1.10.8 - - 1.10.9 - - 1.10.10 + - 1.10.11 - 1.11.0 - 1.11.1 - 1.11.2 @@ -48,10 +26,13 @@ jobs: - 1.11.5 - 1.11.6 - 1.11.7 + - 1.11.8 - 1.12.0 - 1.12.1 - 1.12.2 - 1.12.3 + - 1.12.4 + - 1.13.0 - latest os: - ubuntu-latest @@ -64,9 +45,10 @@ jobs: steps: - uses: actions/checkout@v1 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: ${{ matrix.java }} + distribution: temurin - name: Build with Gradle uses: gradle/gradle-build-action@v2 with: diff --git a/src/main/java/io/github/jopenlibs/vault/Vault.java b/src/main/java/io/github/jopenlibs/vault/Vault.java index 51efdfdd..649ccfc8 100644 --- a/src/main/java/io/github/jopenlibs/vault/Vault.java +++ b/src/main/java/io/github/jopenlibs/vault/Vault.java @@ -2,12 +2,13 @@ import io.github.jopenlibs.vault.api.Auth; import io.github.jopenlibs.vault.api.Debug; -import io.github.jopenlibs.vault.api.Leases; import io.github.jopenlibs.vault.api.Logical; -import io.github.jopenlibs.vault.api.Seal; import io.github.jopenlibs.vault.api.database.Database; -import io.github.jopenlibs.vault.api.mounts.Mounts; import io.github.jopenlibs.vault.api.pki.Pki; +import io.github.jopenlibs.vault.api.sys.Leases; +import io.github.jopenlibs.vault.api.sys.Seal; +import io.github.jopenlibs.vault.api.sys.Sys; +import io.github.jopenlibs.vault.api.sys.mounts.Mounts; import io.github.jopenlibs.vault.json.Json; import io.github.jopenlibs.vault.json.JsonObject; import io.github.jopenlibs.vault.json.JsonValue; @@ -182,6 +183,16 @@ public Auth auth() { return new Auth(vaultConfig); } + /** + * Returns the implementing class for operations on Vault's /v1/sys/* REST + * endpoints + * + * @return The implementing class for Vault's auth operations. + */ + public Sys sys() { + return new Sys(vaultConfig); + } + /** * Returns the implementing class for Vault's PKI secret backend (i.e. /v1/pki/* * REST endpoints). @@ -227,9 +238,8 @@ public Database database(final String mountPath) { } /** - * Returns the implementing class for Vault's lease operations (e.g. revoke, revoke-prefix). - * - * @return The implementing class for Vault's lease operations (e.g. revoke, revoke-prefix). + * @see Sys#leases() + * @deprecated This method is deprecated and in future it will be removed */ public Leases leases() { return new Leases(vaultConfig); @@ -245,19 +255,16 @@ public Debug debug() { } /** - * Returns the implementing class for Vault's sys mounts operations (i.e. - * /v1/sys/mounts/* REST endpoints). - * - * @return the implementing class for Vault's sys mounts operations + * @see Sys#mounts() + * @deprecated This method is deprecated and in future it will be removed */ public Mounts mounts() { return new Mounts(vaultConfig); } /** - * Returns the implementing class for Vault's seal operations (e.g. seal, unseal, sealStatus). - * - * @return The implementing class for Vault's seal operations (e.g. seal, unseal, sealStatus). + * @see Sys#seal() + * @deprecated This method is deprecated and in future it will be removed */ public Seal seal() { return new Seal(vaultConfig); 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 d9800f38..4949be70 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/Auth.java +++ b/src/main/java/io/github/jopenlibs/vault/api/Auth.java @@ -3,6 +3,7 @@ import io.github.jopenlibs.vault.Vault; import io.github.jopenlibs.vault.VaultConfig; import io.github.jopenlibs.vault.VaultException; +import io.github.jopenlibs.vault.api.sys.Sys; import io.github.jopenlibs.vault.json.Json; import io.github.jopenlibs.vault.json.JsonObject; import io.github.jopenlibs.vault.response.AuthResponse; @@ -16,7 +17,6 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.UUID; @@ -840,8 +840,9 @@ public AuthResponse loginByAwsEc2(final String role, final String pkcs7, final S * 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. + * 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. @@ -1280,118 +1281,31 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept } /** - *

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

- * - *
- *
{@code
-     * final String wrappingToken = "...";
-     * final VaultConfig config = new VaultConfig().address(...).token(wrappingToken).build();
-     * final Vault vault = new Vault(config);
-     * final LogicalResponse response = vault.auth().lookupWarp();
-     * // Then you can validate "path" for example ...
-     * final String path = response.getData().get("path");
-     * }
- *
- * - * @return The response information returned from Vault - * @throws VaultException If any error occurs, or unexpected response received from Vault + * @see io.github.jopenlibs.vault.api.sys.Wrapping#lookupWrap() + * @deprecated This method is deprecated and in future it will be removed */ public LogicalResponse lookupWrap() throws VaultException { - return lookupWrap(config.getToken(), false); + Sys sys = new Sys(this.config); + return sys.wrapping().lookupWrap(config.getToken(), false); } /** - *

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

- * - *
- *
{@code
-     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
-     * final Vault vault = new Vault(config);
-     * ...
-     * final String wrappingToken = "...";
-     * final LogicalResponse response = vault.auth().lookupWarp(wrappingToken);
-     * // Then you can validate "path" for example ...
-     * final String path = response.getData().get("path");
-     * }
- *
- * - * @param wrappedToken Wrapped token. - * @return The response information returned from Vault - * @throws VaultException If any error occurs, or unexpected response received from Vault + * @see io.github.jopenlibs.vault.api.sys.Wrapping#lookupWrap(String) + * @deprecated This method is deprecated and in future it will be removed */ public LogicalResponse lookupWrap(final String wrappedToken) throws VaultException { - return lookupWrap(wrappedToken, true); + Sys sys = new Sys(this.config); + return sys.wrapping().lookupWrap(wrappedToken, true); } /** - *

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

- * - *
- *
{@code
-     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
-     * final Vault vault = new Vault(config);
-     * ...
-     * final String wrappingToken = "...";
-     * final LogicalResponse response = vault.auth().lookupWarp(wrappingToken);
-     * // Then you can validate "path" for example ...
-     * final String path = response.getData().get("path");
-     * }
- *
- * - * @param wrappedToken Wrapped token. - * @param inBody When {@code true} the token value placed in the body request: - * {@code {"token": "$wrappedToken"}}, otherwise, set the token into header: - * {@code "X-Vault-Token: $wrappedToken"}. - * @return The response information returned from Vault - * @throws VaultException If any error occurs, or unexpected response received from Vault + * @see io.github.jopenlibs.vault.api.sys.Wrapping#lookupWrap(String, boolean) + * @deprecated This method is deprecated and in future it will be removed */ public LogicalResponse lookupWrap(final String wrappedToken, boolean inBody) throws VaultException { - final String requestJson = - inBody ? Json.object().add("token", wrappedToken).toString() : null; - - return retry(attempt -> { - // HTTP request to Vault - Rest rest = new Rest()//NOPMD - .url(config.getAddress() + "/v1/sys/wrapping/lookup") - .header("X-Vault-Namespace", this.nameSpace) - .header("X-Vault-Request", "true") - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()); - - if (inBody) { - rest = rest - .header("X-Vault-Token", config.getToken()) - .body(requestJson.getBytes(StandardCharsets.UTF_8)); - } else { - rest = rest.header("X-Vault-Token", wrappedToken); - } - - final RestResponse restResponse = rest.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(); - if (!"application/json".equals(mimeType)) { - throw new VaultException("Vault responded with MIME type: " + mimeType, - restResponse.getStatus()); - } - - return new LogicalResponse(restResponse, attempt, - Logical.logicalOperations.authentication); - }); + Sys sys = new Sys(this.config); + return sys.wrapping().lookupWrap(wrappedToken, inBody); } /** @@ -1440,356 +1354,47 @@ public void revokeSelf(final String tokenAuthMount) throws VaultException { } /** - *

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:

- * - *
- *
{@code
-     * final String wrappingToken = "...";
-     * final VaultConfig config = new VaultConfig().address(...).token(wrappingToken).build();
-     * final Vault vault = new Vault(config);
-     * final AuthResponse response = vault.auth().unwrap();
-     * final String unwrappedToken = response.getAuthClientToken();
-     * }
- *
- * - * @return The response information returned from Vault - * @throws VaultException If any error occurs, or unexpected response received from Vault - * @see #unwrap(String) + * @see io.github.jopenlibs.vault.api.sys.Wrapping#unwrap() + * @deprecated This method is deprecated and in future it will be removed */ public UnwrapResponse unwrap() throws VaultException { - return unwrap(config.getToken(), false); + Sys sys = new Sys(this.config); + return sys.wrapping().unwrap(config.getToken(), false); } /** - *

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

- * - *

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.

- * - *

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 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
-     * final String authToken = "...";
-     * final String wrappingToken = "...";
-     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
-     * final Vault vault = new Vault(config);
-     *
-     * final WrapResponse wrapResponse = vault.auth().wrap(
-     *                 // Data to wrap
-     *                 new JsonObject()
-     *                         .add("foo", "bar")
-     *                         .add("zoo", "zar"),
-     *
-     *                 // TTL of the response-wrapping token
-     *                 60
-     *         );
-     *
-     * final UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse.getToken());
-     * final JsonObject unwrappedData = response.getData(); // original data
-     * }
- *
- * - * @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() + * @see io.github.jopenlibs.vault.api.sys.Wrapping#unwrap(String) + * @deprecated This method is deprecated and in future it will be removed */ public UnwrapResponse unwrap(final String wrappedToken) throws VaultException { - return unwrap(wrappedToken, true); + Sys sys = new Sys(this.config); + return sys.wrapping().unwrap(wrappedToken, true); } /** - *

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

- * - *

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.

- * - *

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 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
-     * final String authToken = "...";
-     * final String wrappingToken = "...";
-     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
-     * final Vault vault = new Vault(config);
-     *
-     * final WrapResponse wrapResponse = vault.auth().wrap(
-     *                 // Data to wrap
-     *                 new JsonObject()
-     *                         .add("foo", "bar")
-     *                         .add("zoo", "zar"),
-     *
-     *                 // TTL of the response-wrapping token
-     *                 60
-     *         );
-     *
-     * final UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse.getToken(), true);
-     * final JsonObject unwrappedData = response.getData(); // original data
-     * }
- *
- * - * @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 inBody When {@code true} the token value placed in the body request: - * {@code {"token": "$wrappedToken"}}, otherwise, set the token into header: - * {@code "X-Vault-Token: $wrappedToken"}. - * @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() + * @see io.github.jopenlibs.vault.api.sys.Wrapping#unwrap(String, boolean) + * @deprecated This method is deprecated and in future it will be removed */ public UnwrapResponse unwrap(final String wrappedToken, boolean inBody) throws VaultException { - Objects.requireNonNull(wrappedToken, "Wrapped token is null"); - - return retry(attempt -> { - final String url = config.getAddress() + "/v1/sys/wrapping/unwrap"; - - // HTTP request to Vault - Rest rest = new Rest() - .url(url) - .header("X-Vault-Namespace", this.nameSpace) - .header("X-Vault-Request", "true") - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()); - - if (inBody) { - final String requestJson = Json.object().add("token", wrappedToken).toString(); - rest = rest - .header("X-Vault-Token", config.getToken()) - .body(requestJson.getBytes(StandardCharsets.UTF_8)); - } else { - rest = rest - .header("X-Vault-Token", wrappedToken); - } - - RestResponse restResponse = rest.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); - }); + Sys sys = new Sys(this.config); + return sys.wrapping().unwrap(wrappedToken, inBody); } /** - *

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:

- * - * - * - *
- *
{@code
-     * final String authToken = "...";
-     * final String wrappingToken = "...";
-     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
-     * final Vault vault = new Vault(config);
-     *
-     * final WrapResponse wrapResponse = vault.auth().wrap(
-     *                 // Data to wrap
-     *                 new JsonObject()
-     *                         .add("foo", "bar")
-     *                         .add("zoo", "zar"),
-     *
-     *                 // TTL of the response-wrapping token
-     *                 60
-     *         );
-     *
-     * final UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse.getToken());
-     * final JsonObject unwrappedData = response.getData(); // original data
-     * }
- *
- * - * @param jsonObject User data to wrap. - * @param ttlInSec Wrap TTL in seconds - * @return The response information returned from Vault - * @throws VaultException If any error occurs, or unexpected response received from Vault - * @see #unwrap(String) + * @see io.github.jopenlibs.vault.api.sys.Wrapping#wrap(JsonObject, int) + * @deprecated This method is deprecated and in future it will be removed */ public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws VaultException { - Objects.requireNonNull(jsonObject); - - 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) - .header("X-Vault-Request", "true") - .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 WrapResponse(restResponse, attempt); - }); + Sys sys = new Sys(this.config); + return sys.wrapping().wrap(jsonObject, ttlInSec); } /** - *

Provide access to the {@code /sys/wrapping/rewrap} endpoint. This endpoint rewraps a - * response-wrapped token. The new token will use the same creation TTL as the original token - * and contain the same response. The old token will be invalidated. This can be used for - * long-term storage of a secret in a response-wrapped token when rotation is a - * requirement.

- * - *
- *
{@code
-     * final String authToken = "...";
-     * final String wrappingToken = "...";
-     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
-     * final Vault vault = new Vault(config);
-     *
-     * final WrapResponse wrapResponse = vault.auth().wrap(
-     *                 // Data to wrap
-     *                 new JsonObject()
-     *                         .add("foo", "bar")
-     *                         .add("zoo", "zar"),
-     *
-     *                 // TTL of the response-wrapping token
-     *                 60
-     *         );
-     * ...
-     * final WrapResponse wrapResponse2 = vault.auth().rewrap(wrapResponse.getToken());
-     *
-     * final UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse2.getToken());
-     * final JsonObject unwrappedData = response.getData(); // original data
-     * }
- *
- * - * @param wrappedToken Wrapped token ID to re-wrap. - * @return The response information returned from Vault - * @throws VaultException If any error occurs, or unexpected response received from Vault - * @see #wrap(JsonObject, int) + * @see io.github.jopenlibs.vault.api.sys.Wrapping#rewrap(String) + * @deprecated This method is deprecated and in future it will be removed */ public WrapResponse rewrap(final String wrappedToken) throws VaultException { - Objects.requireNonNull(wrappedToken); - - return retry(attempt -> { - // Parse parameters to JSON - final String requestJson = Json.object().add("token", wrappedToken).toString(); - final String url = config.getAddress() + "/v1/sys/wrapping/rewrap"; - - // HTTP request to Vault - final RestResponse restResponse = new Rest() - .url(url) -// .header("X-Vault-Token", wrappedToken) - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .header("X-Vault-Request", "true") - .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 WrapResponse(restResponse, attempt); - }); + Sys sys = new Sys(this.config); + return sys.wrapping().rewrap(wrappedToken); } } diff --git a/src/main/java/io/github/jopenlibs/vault/api/Seal.java b/src/main/java/io/github/jopenlibs/vault/api/Seal.java deleted file mode 100644 index fa016e64..00000000 --- a/src/main/java/io/github/jopenlibs/vault/api/Seal.java +++ /dev/null @@ -1,204 +0,0 @@ -package io.github.jopenlibs.vault.api; - -import io.github.jopenlibs.vault.VaultConfig; -import io.github.jopenlibs.vault.VaultException; -import io.github.jopenlibs.vault.json.Json; -import io.github.jopenlibs.vault.response.SealResponse; -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 REST endpoints, under the "seal/unseal/seal-status" - * section of the Vault HTTP API docs (https://www.vaultproject.io/api/system/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 method for usage examples.

- */ -public class Seal { - - private final VaultConfig config; - - private String nameSpace; - - public Seal(final VaultConfig config) { - this.config = config; - if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) { - this.nameSpace = this.config.getNameSpace(); - } - } - - public Seal withNameSpace(final String nameSpace) { - this.nameSpace = nameSpace; - return this; - } - - /** - *

Seal the Vault.

- * - * @throws VaultException If any error occurs, or unexpected response received from Vault - */ - public void seal() throws VaultException { - int retryCount = 0; - while (true) { - try { - // HTTP request to Vault - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/sys/seal") - .header("X-Vault-Token", config.getToken()) - .header("X-Vault-Namespace", this.nameSpace) - .header("X-Vault-Request", "true") - .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; - } 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); - } - } - } - } - - /** - *

Enter a single master key share to progress the unsealing of the Vault.

- * - * @param key Single master key share - * @return The response information returned from Vault - * @throws VaultException If any error occurs, or unexpected response received from Vault - */ - public SealResponse unseal(final String key) throws VaultException { - return unseal(key, false); - } - - - /** - *

Enter a single master key share to progress the unsealing of the Vault.

- * - * @param key Single master key share - * @param reset Specifies if previously-provided unseal keys are discarded and the unseal - * process is reset - * @return The response information returned from Vault - * @throws VaultException If any error occurs, or unexpected response received from Vault - */ - public SealResponse unseal(final String key, final Boolean reset) throws VaultException { - int retryCount = 0; - while (true) { - try { - // HTTP request to Vault - final String requestJson = Json.object().add("key", key).add("reset", reset) - .toString(); - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/sys/unseal") - .header("X-Vault-Namespace", this.nameSpace) - .header("X-Vault-Request", "true") - .body(requestJson.getBytes(StandardCharsets.UTF_8)) - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .post(); - - // Validate restResponse - return getSealResponse(retryCount, restResponse); - } 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); - } - } - } - } - - /** - *

Check progress of unsealing the Vault.

- * - * @return The response information returned from Vault - * @throws VaultException If any error occurs, or unexpected response received from Vault - */ - public SealResponse sealStatus() throws VaultException { - int retryCount = 0; - while (true) { - try { - // HTTP request to Vault - final RestResponse restResponse = new Rest()//NOPMD - .url(config.getAddress() + "/v1/sys/seal-status") - .header("X-Vault-Namespace", this.nameSpace) - .header("X-Vault-Request", "true") - .connectTimeoutSeconds(config.getOpenTimeout()) - .readTimeoutSeconds(config.getReadTimeout()) - .sslVerification(config.getSslConfig().isVerify()) - .sslContext(config.getSslConfig().getSslContext()) - .get(); - - // Validate restResponse - return getSealResponse(retryCount, restResponse); - } 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); - } - } - } - } - - private SealResponse getSealResponse(final int retryCount, final RestResponse restResponse) - throws VaultException { - if (restResponse.getStatus() != 200) { - throw new VaultException( - "Vault responded with HTTP status code: " + restResponse.getStatus(), - 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 SealResponse(restResponse, retryCount); - } -} diff --git a/src/main/java/io/github/jopenlibs/vault/api/Leases.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java similarity index 99% rename from src/main/java/io/github/jopenlibs/vault/api/Leases.java rename to src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java index 12b85013..30aa71d8 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/Leases.java +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java @@ -1,7 +1,8 @@ -package io.github.jopenlibs.vault.api; +package io.github.jopenlibs.vault.api.sys; 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.response.VaultResponse; import io.github.jopenlibs.vault.rest.Rest; diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java new file mode 100644 index 00000000..0f4d92e5 --- /dev/null +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java @@ -0,0 +1,144 @@ +package io.github.jopenlibs.vault.api.sys; + +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.response.SealResponse; +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 REST endpoints, under the "seal/unseal/seal-status" + * section of the Vault HTTP API docs (https://www.vaultproject.io/api/system/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 method for usage examples.

+ */ +public class Seal extends OperationsBase { + + private String nameSpace; + + public Seal(final VaultConfig config) { + super(config); + + if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) { + this.nameSpace = this.config.getNameSpace(); + } + } + + public Seal withNameSpace(final String nameSpace) { + this.nameSpace = nameSpace; + return this; + } + + /** + *

Seal the Vault.

+ * + * @return The response information returned from Vault + * @throws VaultException If any error occurs, or unexpected response received from Vault + */ + public SealResponse seal() throws VaultException { + return retry((attempt) -> { + // HTTP request to Vault + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/sys/seal") + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .header("X-Vault-Request", "true") + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + return getSealResponse(attempt, restResponse, 204); + }); + } + + /** + *

Enter a single master key share to progress the unsealing of the Vault.

+ * + * @param key Single master key share + * @return The response information returned from Vault + * @throws VaultException If any error occurs, or unexpected response received from Vault + */ + public SealResponse unseal(final String key) throws VaultException { + return unseal(key, false); + } + + + /** + *

Enter a single master key share to progress the unsealing of the Vault.

+ * + * @param key Single master key share + * @param reset Specifies if previously-provided unseal keys are discarded and the unseal + * process is reset + * @return The response information returned from Vault + * @throws VaultException If any error occurs, or unexpected response received from Vault + */ + public SealResponse unseal(final String key, final Boolean reset) throws VaultException { + return retry((attempt) -> { + // HTTP request to Vault + final String requestJson = Json.object().add("key", key).add("reset", reset) + .toString(); + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/sys/unseal") + .header("X-Vault-Namespace", this.nameSpace) + .header("X-Vault-Request", "true") + .body(requestJson.getBytes(StandardCharsets.UTF_8)) + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .post(); + + // Validate restResponse + return getSealResponse(attempt, restResponse, 200); + }); + } + + /** + *

Check progress of unsealing the Vault.

+ * + * @return The response information returned from Vault + * @throws VaultException If any error occurs, or unexpected response received from Vault + */ + public SealResponse sealStatus() throws VaultException { + return retry((attempt) -> { + // HTTP request to Vault + final RestResponse restResponse = new Rest()//NOPMD + .url(config.getAddress() + "/v1/sys/seal-status") + .header("X-Vault-Namespace", this.nameSpace) + .header("X-Vault-Request", "true") + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()) + .get(); + + // Validate restResponse + return getSealResponse(attempt, restResponse, 200); + }); + } + + private SealResponse getSealResponse(final int retryCount, final RestResponse restResponse, + final int expectedResponse) throws VaultException { + if (restResponse.getStatus() != expectedResponse) { + throw new VaultException( + "Vault responded with HTTP status code: " + restResponse.getStatus(), + restResponse.getStatus()); + } + + final String mimeType = String.valueOf(restResponse.getMimeType()); + if (!mimeType.equals("application/json")) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + return new SealResponse(restResponse, retryCount); + } +} diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java new file mode 100644 index 00000000..f18f5317 --- /dev/null +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java @@ -0,0 +1,71 @@ +package io.github.jopenlibs.vault.api.sys; + +import io.github.jopenlibs.vault.Vault; +import io.github.jopenlibs.vault.VaultConfig; +import io.github.jopenlibs.vault.api.OperationsBase; +import io.github.jopenlibs.vault.api.sys.mounts.Mounts; + +/** + *

The implementing class for operations on Vault's /v1/sys/* 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.

+ * + * @see Vault#sys() + */ +public class Sys extends OperationsBase { + + private String nameSpace; + + public Sys(final VaultConfig config) { + super(config); + + if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) { + this.nameSpace = this.config.getNameSpace(); + } + } + + public Sys withNameSpace(final String nameSpace) { + this.nameSpace = nameSpace; + return this; + } + + /** + * Returns the implementing class for /v1/sys/wrapping/* REST endpoints + * + * @return The implementing class for wrapping operations + */ + public Wrapping wrapping() { + return new Wrapping(this.config); + } + + /** + * Returns the implementing class for Vault's seal operations (e.g. seal, unseal, sealStatus). + * + * @return The implementing class for Vault's seal operations (e.g. seal, unseal, sealStatus). + */ + public Seal seal() { + return new Seal(this.config); + } + + /** + * Returns the implementing class for Vault's sys mounts operations (i.e. + * /v1/sys/mounts/* REST endpoints). + * + * @return the implementing class for Vault's sys mounts operations + */ + public Mounts mounts() { + return new Mounts(this.config); + } + + /** + * Returns the implementing class for Vault's lease operations /v1/sys/leases/* + * REST endpoints). + * + * @return The implementing class for Vault's lease operations + */ + public Leases leases() { + return new Leases(this.config); + } +} diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java new file mode 100644 index 00000000..08ff1fd9 --- /dev/null +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java @@ -0,0 +1,505 @@ +package io.github.jopenlibs.vault.api.sys; + +import io.github.jopenlibs.vault.VaultConfig; +import io.github.jopenlibs.vault.VaultException; +import io.github.jopenlibs.vault.api.Logical; +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.LogicalResponse; +import io.github.jopenlibs.vault.response.UnwrapResponse; +import io.github.jopenlibs.vault.response.WrapResponse; +import io.github.jopenlibs.vault.rest.Rest; +import io.github.jopenlibs.vault.rest.RestResponse; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + *

The implementing class for /v1/sys/wrapping/* 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.

+ * + * @see Sys#wrapping() + */ +public class Wrapping extends OperationsBase { + + private String nameSpace; + + public Wrapping(final VaultConfig config) { + super(config); + + if (this.config.getNameSpace() != null && !this.config.getNameSpace().isEmpty()) { + this.nameSpace = this.config.getNameSpace(); + } + } + + /** + *

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

+ * + *
+ *
{@code
+     * final String wrappingToken = "...";
+     * final VaultConfig config = new VaultConfig().address(...).token(wrappingToken).build();
+     * final Vault vault = new Vault(config);
+     * final LogicalResponse response = vault.sys().wrapping().lookupWarp();
+     * // Then you can validate "path" for example ...
+     * final String path = response.getData().get("path");
+     * }
+ *
+ * + * @return The response information returned from Vault + * @throws VaultException If any error occurs, or unexpected response received from Vault + */ + public LogicalResponse lookupWrap() throws VaultException { + return lookupWrap(config.getToken(), false); + } + + /** + *

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

+ * + *
+ *
{@code
+     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
+     * final Vault vault = new Vault(config);
+     * ...
+     * final String wrappingToken = "...";
+     * final LogicalResponse response = vault.sys().wrapping().lookupWarp(wrappingToken);
+     * // Then you can validate "path" for example ...
+     * final String path = response.getData().get("path");
+     * }
+ *
+ * + * @param wrappedToken Wrapped token. + * @return The response information returned from Vault + * @throws VaultException If any error occurs, or unexpected response received from Vault + */ + public LogicalResponse lookupWrap(final String wrappedToken) throws VaultException { + return lookupWrap(wrappedToken, true); + } + + /** + *

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

+ * + *
+ *
{@code
+     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
+     * final Vault vault = new Vault(config);
+     * ...
+     * final String wrappingToken = "...";
+     * final LogicalResponse response = vault.sys().wrapping().lookupWarp(wrappingToken);
+     * // Then you can validate "path" for example ...
+     * final String path = response.getData().get("path");
+     * }
+ *
+ * + * @param wrappedToken Wrapped token. + * @param inBody When {@code true} the token value placed in the body request: + * {@code {"token": "$wrappedToken"}}, otherwise, set the token into header: + * {@code "X-Vault-Token: $wrappedToken"}. + * @return The response information returned from Vault + * @throws VaultException If any error occurs, or unexpected response received from Vault + */ + public LogicalResponse lookupWrap(final String wrappedToken, boolean inBody) + throws VaultException { + final String requestJson = + inBody ? Json.object().add("token", wrappedToken).toString() : null; + + return retry(attempt -> { + // HTTP request to Vault + Rest rest = new Rest()//NOPMD + .url(config.getAddress() + "/v1/sys/wrapping/lookup") + .header("X-Vault-Namespace", this.nameSpace) + .header("X-Vault-Request", "true") + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()); + + if (inBody) { + rest = rest + .header("X-Vault-Token", config.getToken()) + .body(requestJson.getBytes(StandardCharsets.UTF_8)); + } else { + rest = rest.header("X-Vault-Token", wrappedToken); + } + + final RestResponse restResponse = rest.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(); + if (!"application/json".equals(mimeType)) { + throw new VaultException("Vault responded with MIME type: " + mimeType, + restResponse.getStatus()); + } + + return new LogicalResponse(restResponse, attempt, + Logical.logicalOperations.authentication); + }); + } + + /** + *

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:

+ * + * + * + *
+ *
{@code
+     * final String authToken = "...";
+     * final String wrappingToken = "...";
+     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
+     * final Vault vault = new Vault(config);
+     *
+     * final WrapResponse wrapResponse = vault.sys().wrapping().wrap(
+     *                 // Data to wrap
+     *                 new JsonObject()
+     *                         .add("foo", "bar")
+     *                         .add("zoo", "zar"),
+     *
+     *                 // TTL of the response-wrapping token
+     *                 60
+     *         );
+     *
+     * final UnwrapResponse unwrapResponse = vault.sys().wrapping().unwrap(wrapResponse.getToken());
+     * final JsonObject unwrappedData = response.getData(); // original data
+     * }
+ *
+ * + * @param jsonObject User data to wrap. + * @param ttlInSec Wrap TTL in seconds + * @return The response information returned from Vault + * @throws VaultException If any error occurs, or unexpected response received from Vault + * @see #unwrap(String) + */ + public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws VaultException { + Objects.requireNonNull(jsonObject); + + 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) + .header("X-Vault-Request", "true") + .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 WrapResponse(restResponse, attempt); + }); + } + + /** + *

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:

+ * + *
+ *
{@code
+     * final String wrappingToken = "...";
+     * final VaultConfig config = new VaultConfig().address(...).token(wrappingToken).build();
+     * final Vault vault = new Vault(config);
+     * final AuthResponse response = vault.sys().wrapping().unwrap();
+     * final String unwrappedToken = response.getAuthClientToken();
+     * }
+ *
+ * + * @return The response information returned from Vault + * @throws VaultException If any error occurs, or unexpected response received from Vault + * @see #unwrap(String) + */ + public UnwrapResponse unwrap() throws VaultException { + return unwrap(config.getToken(), false); + } + + /** + *

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

+ * + *

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.

+ * + *

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 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
+     * final String authToken = "...";
+     * final String wrappingToken = "...";
+     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
+     * final Vault vault = new Vault(config);
+     *
+     * final WrapResponse wrapResponse = vault.sys().wrapping().wrap(
+     *                 // Data to wrap
+     *                 new JsonObject()
+     *                         .add("foo", "bar")
+     *                         .add("zoo", "zar"),
+     *
+     *                 // TTL of the response-wrapping token
+     *                 60
+     *         );
+     *
+     * final UnwrapResponse unwrapResponse = vault.sys().wrapping().unwrap(wrapResponse.getToken());
+     * final JsonObject unwrappedData = response.getData(); // original data
+     * }
+ *
+ * + * @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 { + return unwrap(wrappedToken, true); + } + + /** + *

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

+ * + *

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.

+ * + *

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 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
+     * final String authToken = "...";
+     * final String wrappingToken = "...";
+     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
+     * final Vault vault = new Vault(config);
+     *
+     * final WrapResponse wrapResponse = vault.sys().wrapping().wrap(
+     *                 // Data to wrap
+     *                 new JsonObject()
+     *                         .add("foo", "bar")
+     *                         .add("zoo", "zar"),
+     *
+     *                 // TTL of the response-wrapping token
+     *                 60
+     *         );
+     *
+     * final UnwrapResponse unwrapResponse = vault.sys().wrapping().unwrap(wrapResponse.getToken(), true);
+     * final JsonObject unwrappedData = response.getData(); // original data
+     * }
+ *
+ * + * @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 inBody When {@code true} the token value placed in the body request: + * {@code {"token": "$wrappedToken"}}, otherwise, set the token into header: + * {@code "X-Vault-Token: $wrappedToken"}. + * @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, boolean inBody) throws VaultException { + Objects.requireNonNull(wrappedToken, "Wrapped token is null"); + + return retry(attempt -> { + final String url = config.getAddress() + "/v1/sys/wrapping/unwrap"; + + // HTTP request to Vault + Rest rest = new Rest() + .url(url) + .header("X-Vault-Namespace", this.nameSpace) + .header("X-Vault-Request", "true") + .connectTimeoutSeconds(config.getOpenTimeout()) + .readTimeoutSeconds(config.getReadTimeout()) + .sslVerification(config.getSslConfig().isVerify()) + .sslContext(config.getSslConfig().getSslContext()); + + if (inBody) { + final String requestJson = Json.object().add("token", wrappedToken).toString(); + rest = rest + .header("X-Vault-Token", config.getToken()) + .body(requestJson.getBytes(StandardCharsets.UTF_8)); + } else { + rest = rest + .header("X-Vault-Token", wrappedToken); + } + + RestResponse restResponse = rest.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/rewrap} endpoint. This endpoint rewraps a + * response-wrapped token. The new token will use the same creation TTL as the original token + * and contain the same response. The old token will be invalidated. This can be used for + * long-term storage of a secret in a response-wrapped token when rotation is a + * requirement.

+ * + *
+ *
{@code
+     * final String authToken = "...";
+     * final String wrappingToken = "...";
+     * final VaultConfig config = new VaultConfig().address(...).token(authToken).build();
+     * final Vault vault = new Vault(config);
+     *
+     * final WrapResponse wrapResponse = vault.auth().wrap(
+     *                 // Data to wrap
+     *                 new JsonObject()
+     *                         .add("foo", "bar")
+     *                         .add("zoo", "zar"),
+     *
+     *                 // TTL of the response-wrapping token
+     *                 60
+     *         );
+     * ...
+     * final WrapResponse wrapResponse2 = vault.auth().rewrap(wrapResponse.getToken());
+     *
+     * final UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse2.getToken());
+     * final JsonObject unwrappedData = response.getData(); // original data
+     * }
+ *
+ * + * @param wrappedToken Wrapped token ID to re-wrap. + * @return The response information returned from Vault + * @throws VaultException If any error occurs, or unexpected response received from Vault + * @see #wrap(JsonObject, int) + */ + public WrapResponse rewrap(final String wrappedToken) throws VaultException { + Objects.requireNonNull(wrappedToken); + + return retry(attempt -> { + // Parse parameters to JSON + final String requestJson = Json.object().add("token", wrappedToken).toString(); + final String url = config.getAddress() + "/v1/sys/wrapping/rewrap"; + + // HTTP request to Vault + final RestResponse restResponse = new Rest() + .url(url) + .header("X-Vault-Token", config.getToken()) + .header("X-Vault-Namespace", this.nameSpace) + .header("X-Vault-Request", "true") + .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 WrapResponse(restResponse, attempt); + }); + } +} diff --git a/src/main/java/io/github/jopenlibs/vault/api/mounts/Mount.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mount.java similarity index 96% rename from src/main/java/io/github/jopenlibs/vault/api/mounts/Mount.java rename to src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mount.java index c4fff712..4d1a2abc 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/mounts/Mount.java +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mount.java @@ -1,4 +1,4 @@ -package io.github.jopenlibs.vault.api.mounts; +package io.github.jopenlibs.vault.api.sys.mounts; import java.io.Serializable; diff --git a/src/main/java/io/github/jopenlibs/vault/api/mounts/MountConfig.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountConfig.java similarity index 98% rename from src/main/java/io/github/jopenlibs/vault/api/mounts/MountConfig.java rename to src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountConfig.java index 15e16b3c..1d2a3d4f 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/mounts/MountConfig.java +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountConfig.java @@ -1,4 +1,4 @@ -package io.github.jopenlibs.vault.api.mounts; +package io.github.jopenlibs.vault.api.sys.mounts; import java.io.Serializable; import java.util.ArrayList; diff --git a/src/main/java/io/github/jopenlibs/vault/api/mounts/MountPayload.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountPayload.java similarity index 99% rename from src/main/java/io/github/jopenlibs/vault/api/mounts/MountPayload.java rename to src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountPayload.java index dc0522ae..00c4073c 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/mounts/MountPayload.java +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountPayload.java @@ -1,4 +1,4 @@ -package io.github.jopenlibs.vault.api.mounts; +package io.github.jopenlibs.vault.api.sys.mounts; import io.github.jopenlibs.vault.json.Json; import io.github.jopenlibs.vault.json.JsonObject; diff --git a/src/main/java/io/github/jopenlibs/vault/api/mounts/MountType.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountType.java similarity index 95% rename from src/main/java/io/github/jopenlibs/vault/api/mounts/MountType.java rename to src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountType.java index c44518f6..9f2b9afc 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/mounts/MountType.java +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountType.java @@ -1,4 +1,4 @@ -package io.github.jopenlibs.vault.api.mounts; +package io.github.jopenlibs.vault.api.sys.mounts; import java.util.Arrays; diff --git a/src/main/java/io/github/jopenlibs/vault/api/mounts/Mounts.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java similarity index 98% rename from src/main/java/io/github/jopenlibs/vault/api/mounts/Mounts.java rename to src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java index b9ea0c03..30d0ce36 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/mounts/Mounts.java +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java @@ -1,4 +1,4 @@ -package io.github.jopenlibs.vault.api.mounts; +package io.github.jopenlibs.vault.api.sys.mounts; import io.github.jopenlibs.vault.VaultConfig; import io.github.jopenlibs.vault.VaultException; @@ -13,9 +13,8 @@ * 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.

+ * Vault in a DSL-style builder pattern. See the Javadoc comments of each + * public method for usage examples.

*/ public class Mounts extends OperationsBase { @@ -36,7 +35,7 @@ public Mounts(final VaultConfig config) { * final VaultConfig config = new VaultConfig.address(...).token(...).build(); * final Vault vault = new Vault(config); * - * final MountResponse response = vault.mounts().list(); + * final MountResponse response = vault.sys().mounts().list(); * final Map mounts = response.getMounts(); * } * diff --git a/src/main/java/io/github/jopenlibs/vault/api/mounts/TimeToLive.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/TimeToLive.java similarity index 96% rename from src/main/java/io/github/jopenlibs/vault/api/mounts/TimeToLive.java rename to src/main/java/io/github/jopenlibs/vault/api/sys/mounts/TimeToLive.java index e4c5378b..55ebfcf1 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/mounts/TimeToLive.java +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/TimeToLive.java @@ -1,4 +1,4 @@ -package io.github.jopenlibs.vault.api.mounts; +package io.github.jopenlibs.vault.api.sys.mounts; import java.util.Objects; import java.util.concurrent.TimeUnit; diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/package-info.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/package-info.java new file mode 100644 index 00000000..ee76693c --- /dev/null +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/package-info.java @@ -0,0 +1,8 @@ +/** + *

Classes implementing Vault's mounts system.

+ * + *

The classes in this package are not meant to be instantiated directly. Rather, they should + * be used by way of the io.github.jopenlibs.vault.Vault driver class, in a DSL-style + * builder pattern approach.

+ */ +package io.github.jopenlibs.vault.api.sys.mounts; diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/package-info.java b/src/main/java/io/github/jopenlibs/vault/api/sys/package-info.java new file mode 100644 index 00000000..b6803d39 --- /dev/null +++ b/src/main/java/io/github/jopenlibs/vault/api/sys/package-info.java @@ -0,0 +1,8 @@ +/** + *

Classes implementing the various endpoints of the Vault HTTP API.

+ * + *

The classes in this package are not meant to be instantiated directly. Rather, they should + * be used by way of the io.github.jopenlibs.vault.Vault driver class, in a DSL-style + * builder pattern approach.

+ */ +package io.github.jopenlibs.vault.api.sys; diff --git a/src/main/java/io/github/jopenlibs/vault/response/MountResponse.java b/src/main/java/io/github/jopenlibs/vault/response/MountResponse.java index c8040d6a..cff8cec0 100644 --- a/src/main/java/io/github/jopenlibs/vault/response/MountResponse.java +++ b/src/main/java/io/github/jopenlibs/vault/response/MountResponse.java @@ -1,9 +1,9 @@ package io.github.jopenlibs.vault.response; import io.github.jopenlibs.vault.api.Logical; -import io.github.jopenlibs.vault.api.mounts.Mount; -import io.github.jopenlibs.vault.api.mounts.MountConfig; -import io.github.jopenlibs.vault.api.mounts.MountType; +import io.github.jopenlibs.vault.api.sys.mounts.Mount; +import io.github.jopenlibs.vault.api.sys.mounts.MountConfig; +import io.github.jopenlibs.vault.api.sys.mounts.MountType; import io.github.jopenlibs.vault.json.JsonObject; import io.github.jopenlibs.vault.json.JsonObject.Member; import io.github.jopenlibs.vault.json.JsonValue; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index e2d5ebaa..c1201095 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,6 +2,7 @@ requires java.logging; exports io.github.jopenlibs.vault; exports io.github.jopenlibs.vault.api; + exports io.github.jopenlibs.vault.api.sys; exports io.github.jopenlibs.vault.json; exports io.github.jopenlibs.vault.response; exports io.github.jopenlibs.vault.rest; diff --git a/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendAppIdTests.java b/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendAppIdTests.java index 81875466..088284e1 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendAppIdTests.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendAppIdTests.java @@ -5,7 +5,6 @@ import io.github.jopenlibs.vault.util.VaultContainer; import io.github.jopenlibs.vault.util.VaultVersion; import java.io.IOException; -import java.util.Optional; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; @@ -24,24 +23,9 @@ public class AuthBackendAppIdTests { @ClassRule public static final VaultContainer container = new VaultContainer(); - private static boolean checkVersion() { - VaultVersion accepted = new VaultVersion("1.11.6"); - try { - VaultVersion current = new VaultVersion( - Optional.ofNullable(System.getenv("VAULT_VERSION")).orElse("latest")); - if (current.compareTo(accepted) > 0) { - return false; - } - } catch (NumberFormatException ignored) { - return false; - } - - return true; - } - @BeforeClass public static void setupClass() throws IOException, InterruptedException { - assumeTrue(checkVersion()); + assumeTrue(VaultVersion.lessThan("1.11.6")); container.initAndUnsealVault(); container.setupBackendAppId(); diff --git a/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendPkiTests.java b/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendPkiTests.java index 788bfe59..581ae528 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendPkiTests.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/api/AuthBackendPkiTests.java @@ -8,6 +8,7 @@ import io.github.jopenlibs.vault.rest.RestResponse; import io.github.jopenlibs.vault.util.SSLUtils; import io.github.jopenlibs.vault.util.VaultContainer; +import io.github.jopenlibs.vault.util.VaultVersion; import java.io.IOException; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -94,7 +95,9 @@ public void testIssueCredential() throws VaultException, InterruptedException { .allowSubdomains(true) .maxTtl("9h") ); - TestCase.assertEquals(204, createRoleResponse.getRestResponse().getStatus()); + int statusCode = VaultVersion.lessThan("1.13.0") ? 204 : 200; + int responseCode = createRoleResponse.getRestResponse().getStatus(); + TestCase.assertEquals(statusCode, responseCode); Thread.sleep(3000); // Issue cert @@ -133,7 +136,9 @@ public void testIssueCredentialWithCsr() .allowSubdomains(true) .maxTtl("9h") ); - TestCase.assertEquals(204, createRoleResponse.getRestResponse().getStatus()); + int statusCode = VaultVersion.lessThan("1.13.0") ? 204 : 200; + int responseCode = createRoleResponse.getRestResponse().getStatus(); + TestCase.assertEquals(statusCode, responseCode); Thread.sleep(3000); // Issue cert @@ -158,7 +163,9 @@ public void testRevocation() throws VaultException, InterruptedException { .allowSubdomains(true) .maxTtl("9h") ); - TestCase.assertEquals(204, createRoleResponse.getRestResponse().getStatus()); + int statusCode = VaultVersion.lessThan("1.13.0") ? 204 : 200; + int responseCode = createRoleResponse.getRestResponse().getStatus(); + TestCase.assertEquals(statusCode, responseCode); Thread.sleep(3000); // Issue cert final PkiResponse issueResponse = vault.pki() diff --git a/src/test-integration/java/io/github/jopenlibs/vault/api/LeasesTests.java b/src/test-integration/java/io/github/jopenlibs/vault/api/LeasesTests.java index 82b6b568..efd5f5ea 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/api/LeasesTests.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/api/LeasesTests.java @@ -69,7 +69,7 @@ public DatabaseResponse generateCredentials() throws VaultException { public void testRevoke() throws VaultException { DatabaseResponse credsResponse = this.generateCredentials(); - final VaultResponse response = vault.leases().revoke(credsResponse.getLeaseId()); + final VaultResponse response = vault.sys().leases().revoke(credsResponse.getLeaseId()); assertEquals(204, response.getRestResponse().getStatus()); } @@ -80,7 +80,7 @@ public void testRevokePrefix() throws VaultException { String prefix = Arrays.stream(credsResponse.getLeaseId().split("([^/]+)$")) .map(str -> str.substring(0, str.length() - 1)).findFirst().get(); - final VaultResponse response = vault.leases().revokePrefix(prefix); + final VaultResponse response = vault.sys().leases().revokePrefix(prefix); assertEquals(204, response.getRestResponse().getStatus()); } @@ -91,7 +91,7 @@ public void testRevokeForce() throws VaultException { String prefix = Arrays.stream(credsResponse.getLeaseId().split("([^/]+)$")) .map(str -> str.substring(0, str.length() - 1)).findFirst().get(); - final VaultResponse response = vault.leases().revokeForce(prefix); + final VaultResponse response = vault.sys().leases().revokeForce(prefix); assertEquals(204, response.getRestResponse().getStatus()); } @@ -99,7 +99,7 @@ public void testRevokeForce() throws VaultException { public void testRenew() throws VaultException { DatabaseResponse credsResponse = this.generateCredentials(); - final VaultResponse response = vault.leases().renew(credsResponse.getLeaseId(), + final VaultResponse response = vault.sys().leases().renew(credsResponse.getLeaseId(), credsResponse.getLeaseDuration()); assertEquals(200, response.getRestResponse().getStatus()); } diff --git a/src/test-integration/java/io/github/jopenlibs/vault/api/MountsTests.java b/src/test-integration/java/io/github/jopenlibs/vault/api/MountsTests.java index ff77f974..62518ac0 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/api/MountsTests.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/api/MountsTests.java @@ -2,11 +2,11 @@ import io.github.jopenlibs.vault.Vault; import io.github.jopenlibs.vault.VaultException; -import io.github.jopenlibs.vault.api.mounts.Mount; -import io.github.jopenlibs.vault.api.mounts.MountConfig; -import io.github.jopenlibs.vault.api.mounts.MountPayload; -import io.github.jopenlibs.vault.api.mounts.MountType; -import io.github.jopenlibs.vault.api.mounts.TimeToLive; +import io.github.jopenlibs.vault.api.sys.mounts.Mount; +import io.github.jopenlibs.vault.api.sys.mounts.MountConfig; +import io.github.jopenlibs.vault.api.sys.mounts.MountPayload; +import io.github.jopenlibs.vault.api.sys.mounts.MountType; +import io.github.jopenlibs.vault.api.sys.mounts.TimeToLive; import io.github.jopenlibs.vault.response.MountResponse; import io.github.jopenlibs.vault.util.VaultContainer; import java.io.IOException; @@ -43,7 +43,7 @@ public static void setupClass() throws IOException, InterruptedException { public void testList() throws VaultException { final Vault vault = container.getRootVault(); - final MountResponse response = vault.mounts().list(); + final MountResponse response = vault.sys().mounts().list(); final Map mounts = response.getMounts(); assertTrue(mounts.containsKey("pki-custom-path-1/")); @@ -60,7 +60,7 @@ public void testEnable() throws VaultException { .maxLeaseTtl(TimeToLive.of(12, TimeUnit.HOURS)) .description("description for pki engine"); - final MountResponse response = vault.mounts() + final MountResponse response = vault.sys().mounts() .enable("pki-itest-path-1", MountType.PKI, payload); TestCase.assertEquals(204, response.getRestResponse().getStatus()); @@ -78,7 +78,7 @@ public void testEnableExceptionAlreadyExist() throws VaultException { .maxLeaseTtl(TimeToLive.of(168, TimeUnit.HOURS)) .description("description for pki engine"); - vault.mounts().enable("pki-custom-path-1", MountType.PKI, payload); + vault.sys().mounts().enable("pki-custom-path-1", MountType.PKI, payload); } @Test @@ -93,7 +93,7 @@ public void testEnableExceptionNullType() throws VaultException { .maxLeaseTtl(TimeToLive.of(30, TimeUnit.MINUTES)) .description("description for pki engine"); - vault.mounts().enable("pki-itest-path-2", null, payload); + vault.sys().mounts().enable("pki-itest-path-2", null, payload); } @Test @@ -105,7 +105,7 @@ public void testEnableExceptionNullTimeUnit() throws VaultException { final MountPayload payload = new MountPayload() .defaultLeaseTtl(TimeToLive.of(7, null)); - vault.mounts().enable("pki-itest-path-3", MountType.PKI, payload); + vault.sys().mounts().enable("pki-itest-path-3", MountType.PKI, payload); } @Test @@ -118,14 +118,14 @@ public void testEnableExceptionInvalidTimeUnit() throws VaultException { final MountPayload payload = new MountPayload() .defaultLeaseTtl(TimeToLive.of(7, TimeUnit.DAYS)); - vault.mounts().enable("pki-itest-path-4", MountType.PKI, payload); + vault.sys().mounts().enable("pki-itest-path-4", MountType.PKI, payload); } @Test public void testDisable() throws VaultException { final Vault vault = container.getRootVault(); - final MountResponse response = vault.mounts().disable("pki-custom-path-3"); + final MountResponse response = vault.sys().mounts().disable("pki-custom-path-3"); TestCase.assertEquals(204, response.getRestResponse().getStatus()); } @@ -138,9 +138,9 @@ public void testRead() throws VaultException { .defaultLeaseTtl(TimeToLive.of(360, TimeUnit.MINUTES)) .maxLeaseTtl(TimeToLive.of(360, TimeUnit.MINUTES)); - vault.mounts().enable("pki-predefined-path-1", MountType.PKI, payload); + vault.sys().mounts().enable("pki-predefined-path-1", MountType.PKI, payload); - final MountResponse response = vault.mounts().read("pki-predefined-path-1"); + final MountResponse response = vault.sys().mounts().read("pki-predefined-path-1"); final Mount mount = response.getMount(); final MountConfig config = mount.getConfig(); @@ -157,7 +157,7 @@ public void testReadExceptionNotFound() throws VaultException { final Vault vault = container.getRootVault(); - vault.mounts().read("pki-non-existing-path"); + vault.sys().mounts().read("pki-non-existing-path"); } @Test @@ -168,18 +168,18 @@ public void testTune() throws VaultException { .defaultLeaseTtl(TimeToLive.of(6, TimeUnit.HOURS)) .maxLeaseTtl(TimeToLive.of(6, TimeUnit.HOURS)); - vault.mounts().enable("pki-predefined-path-2", MountType.PKI, enablePayload); + vault.sys().mounts().enable("pki-predefined-path-2", MountType.PKI, enablePayload); final MountPayload tunePayload = new MountPayload() .defaultLeaseTtl(TimeToLive.of(12, TimeUnit.HOURS)) .maxLeaseTtl(TimeToLive.of(12, TimeUnit.HOURS)); - final MountResponse tuneResponse = vault.mounts() + final MountResponse tuneResponse = vault.sys().mounts() .tune("pki-predefined-path-2", tunePayload); TestCase.assertEquals(204, tuneResponse.getRestResponse().getStatus()); - final MountResponse response = vault.mounts().read("pki-predefined-path-2"); + final MountResponse response = vault.sys().mounts().read("pki-predefined-path-2"); final Mount mount = response.getMount(); final MountConfig config = mount.getConfig(); @@ -198,6 +198,6 @@ public void testTuneExceptionNotFound() throws VaultException { .defaultLeaseTtl(TimeToLive.of(24, TimeUnit.HOURS)) .maxLeaseTtl(TimeToLive.of(24, TimeUnit.HOURS)); - vault.mounts().tune("pki-non-existing-path", tunePayload); + vault.sys().mounts().tune("pki-non-existing-path", tunePayload); } } diff --git a/src/test-integration/java/io/github/jopenlibs/vault/api/SealTests.java b/src/test-integration/java/io/github/jopenlibs/vault/api/SealTests.java index dd30172a..d1e4e5c0 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/api/SealTests.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/api/SealTests.java @@ -32,7 +32,7 @@ public static void setupClass() throws IOException, InterruptedException { public void testSealStatus_returnsFalse_fromInitialUnsealedState() throws VaultException { // Due to the "setupClass()" static method, the Vault instance should be in an unsealed state // at the start of this test suite. - final SealResponse response = container.getVault().seal().sealStatus(); + final SealResponse response = container.getVault().sys().seal().sealStatus(); assertFalse(response.getSealed()); assertEquals(1, response.getNumberOfShares().longValue()); @@ -43,13 +43,13 @@ public void testSealStatus_returnsFalse_fromInitialUnsealedState() throws VaultE @Test public void testSealAndUnseal_togglesAndRestoresUnsealedState() throws VaultException { // Seal Vault and verify its status - container.getRootVault().seal().seal(); - final SealResponse sealResponse = container.getRootVault().seal().sealStatus(); + container.getRootVault().sys().seal().seal(); + final SealResponse sealResponse = container.getRootVault().sys().seal().sealStatus(); assertTrue(sealResponse.getSealed()); // Unseal Vault again, and verify its status - container.getRootVault().seal().unseal(unsealKey); - final SealResponse unsealResponse = container.getRootVault().seal().sealStatus(); + container.getRootVault().sys().seal().unseal(unsealKey); + final SealResponse unsealResponse = container.getRootVault().sys().seal().sealStatus(); assertFalse(unsealResponse.getSealed()); } diff --git a/src/test-integration/java/io/github/jopenlibs/vault/api/WrappingTests.java b/src/test-integration/java/io/github/jopenlibs/vault/api/sys/WrappingTests.java similarity index 81% rename from src/test-integration/java/io/github/jopenlibs/vault/api/WrappingTests.java rename to src/test-integration/java/io/github/jopenlibs/vault/api/sys/WrappingTests.java index e68147ad..2e20605b 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/api/WrappingTests.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/api/sys/WrappingTests.java @@ -1,4 +1,4 @@ -package io.github.jopenlibs.vault.api; +package io.github.jopenlibs.vault.api.sys; import io.github.jopenlibs.vault.Vault; import io.github.jopenlibs.vault.VaultException; @@ -47,14 +47,14 @@ public static void setupClass() throws IOException, InterruptedException, VaultE public void testWrapUnwrap() throws Exception { final Vault vault = container.getVault(NONROOT_TOKEN); - WrapResponse wrapResponse = vault.auth().wrap( + WrapResponse wrapResponse = vault.sys().wrapping().wrap( new JsonObject() .add("foo", "bar") .add("zoo", "zar"), 60 ); - UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse.getToken()); + UnwrapResponse unwrapResponse = vault.sys().wrapping().unwrap(wrapResponse.getToken()); assertEquals("bar", unwrapResponse.getData().get("foo").asString()); assertEquals("zar", unwrapResponse.getData().get("zoo").asString()); @@ -68,28 +68,28 @@ public void testWrapUnwrap() throws Exception { public void testWrappingAll() throws Exception { final Vault vault = container.getVault(NONROOT_TOKEN); - WrapResponse wrapResponse0 = vault.auth().wrap( + WrapResponse wrapResponse0 = vault.sys().wrapping().wrap( new JsonObject() .add("foo", "bar") .add("zoo", "zar"), 60 ); - LogicalResponse look = vault.auth().lookupWrap(wrapResponse0.getToken()); + LogicalResponse look = vault.sys().wrapping().lookupWrap(wrapResponse0.getToken()); assertNotNull(look.getData().get("creation_time")); assertNotNull(look.getData().get("creation_ttl")); assertEquals("sys/wrapping/wrap", look.getData().get("creation_path")); - WrapResponse wrapResponse1 = vault.auth().rewrap(wrapResponse0.getToken()); + WrapResponse wrapResponse1 = vault.sys().wrapping().rewrap(wrapResponse0.getToken()); VaultException ex = assertThrows( VaultException.class, - () -> vault.auth().unwrap(wrapResponse0.getToken()) + () -> vault.sys().wrapping().unwrap(wrapResponse0.getToken()) ); assertTrue(ex.getMessage().contains("wrapping token is not valid or does not exist")); - UnwrapResponse unwrapResponse = vault.auth().unwrap(wrapResponse1.getToken()); + UnwrapResponse unwrapResponse = vault.sys().wrapping().unwrap(wrapResponse1.getToken()); assertEquals("bar", unwrapResponse.getData().get("foo").asString()); assertEquals("zar", unwrapResponse.getData().get("zoo").asString()); diff --git a/src/test-integration/java/io/github/jopenlibs/vault/util/VaultVersion.java b/src/test-integration/java/io/github/jopenlibs/vault/util/VaultVersion.java index 8a96dbe3..426e6b8e 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/util/VaultVersion.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/util/VaultVersion.java @@ -1,5 +1,7 @@ package io.github.jopenlibs.vault.util; +import java.util.Optional; + public class VaultVersion implements Comparable { private final String literal; @@ -14,7 +16,7 @@ public VaultVersion(String version) { for (int i = 0; i < split.length; i++) { try { - numbers[i] = Integer.valueOf(split[i]); + numbers[i] = Integer.parseInt(split[i]); } catch (NumberFormatException e) { throw new NumberFormatException("Not a semver version"); } @@ -42,6 +44,26 @@ public int compareTo(VaultVersion another) { return 0; } + public static boolean lessThan(String version) { + VaultVersion accepted = new VaultVersion(version); + try { + VaultVersion current = new VaultVersion( + Optional.ofNullable(System.getenv("VAULT_VERSION")).orElse("latest")); + + if (current.getLiteral().equals("latest")) { + return false; + } + + if (current.compareTo(accepted) >= 0) { + return false; + } + + return true; + } catch (NumberFormatException ignored) { + return false; + } + } + @Override public String toString() { return this.getLiteral(); diff --git a/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java b/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java index 55dd7c94..fc77f561 100644 --- a/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java +++ b/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java @@ -2,7 +2,12 @@ import io.github.jopenlibs.vault.json.Json; import io.github.jopenlibs.vault.json.JsonObject; +import io.github.jopenlibs.vault.vault.VaultTestUtils; +import io.github.jopenlibs.vault.vault.mock.EchoInputMockVault; import java.nio.charset.StandardCharsets; +import org.eclipse.jetty.server.Server; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -14,6 +19,22 @@ */ public class GetTests { + private Server server; + private final String URL = "http://127.0.0.1:8999/"; + + @Before + public void startServer() throws Exception { + EchoInputMockVault echoInputMockVault = new EchoInputMockVault(200); + + this.server = VaultTestUtils.initHttpMockVault(echoInputMockVault); + this.server.start(); + } + + @After + public void stopServer() throws Exception { + this.server.stop(); + } + /** * The REST client should refuse to handle any HTTP verb if the base URL has not already been * set. @@ -28,13 +49,15 @@ public void testFailsOnNoUrl() throws RestException { */ @Test public void testGet_Plain() throws RestException { - final RestResponse restResponse = new Rest().url("https://httpbin.org/get").get(); + final RestResponse restResponse = new Rest() + .url(this.URL) + .get(); assertEquals(200, restResponse.getStatus()); assertEquals("application/json", restResponse.getMimeType()); final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/get", jsonObject.getString("url", null)); + assertEquals("http://127.0.0.1:8999/", jsonObject.getString("URL", null)); } /** @@ -45,7 +68,7 @@ public void testGet_Plain() throws RestException { @Test public void testGet_InsertParams() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/get") + .url(this.URL) .parameter("foo", "bar") .parameter("apples", "oranges") .parameter("multi part", "this parameter has whitespace in its name and value") @@ -56,8 +79,8 @@ public void testGet_InsertParams() throws RestException { final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); assertEquals( - "https://httpbin.org/get?apples=oranges&foo=bar&multi+part=this+parameter+has+whitespace+in+its+name+and+value", - jsonObject.getString("url", null)); + "http://127.0.0.1:8999/?apples=oranges&foo=bar&multi+part=this+parameter+has+whitespace+in+its+name+and+value", + jsonObject.getString("URL", null)); final JsonObject args = jsonObject.get("args").asObject(); assertEquals("bar", args.getString("foo", null)); assertEquals("oranges", args.getString("apples", null)); @@ -76,7 +99,7 @@ public void testGet_InsertParams() throws RestException { @Test public void testGet_UpdateParams() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/get?hot=cold") + .url(this.URL + "?hot=cold") .parameter("foo", "bar") .parameter("apples", "oranges") .parameter("multi part", "this parameter has whitespace in its name and value") @@ -87,8 +110,8 @@ public void testGet_UpdateParams() throws RestException { final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); assertEquals( - "https://httpbin.org/get?hot=cold&apples=oranges&foo=bar&multi+part=this+parameter+has+whitespace+in+its+name+and+value", - jsonObject.getString("url", null)); + "http://127.0.0.1:8999/?hot=cold&apples=oranges&foo=bar&multi+part=this+parameter+has+whitespace+in+its+name+and+value", + jsonObject.getString("URL", null)); final JsonObject args = jsonObject.get("args").asObject(); assertEquals("cold", args.getString("hot", null)); assertEquals("bar", args.getString("foo", null)); @@ -106,19 +129,19 @@ public void testGet_UpdateParams() throws RestException { @Test public void testGet_WithHeaders() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/get") - .header("black", "white") - .header("day", "night") - .header("two-part", "Header value") + .url(this.URL) + .header("Black", "white") + .header("Day", "night") + .header("Two-Part", "Header value") .get(); assertEquals(200, restResponse.getStatus()); assertEquals("application/json", restResponse.getMimeType()); final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/get", jsonObject.getString("url", null)); + assertEquals("http://127.0.0.1:8999/", jsonObject.getString("URL", null)); + final JsonObject headers = jsonObject.get("headers").asObject(); - // Note that even though our header names where all-lowercase, the round trip process converts them to camel case. assertEquals("white", headers.getString("Black", null)); assertEquals("night", headers.getString("Day", null)); assertEquals("Header value", headers.getString("Two-Part", null)); @@ -133,10 +156,10 @@ public void testGet_WithHeaders() throws RestException { @Test public void testGet_WithOptionalHeaders() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/get") - .header("black", "white") - .header("day", "night") - .header("two-part", "Header value") + .url(this.URL) + .header("Black", "white") + .header("Day", "night") + .header("Two-Part", "Header value") .header("I am null", null) .get(); assertEquals(200, restResponse.getStatus()); @@ -144,9 +167,9 @@ public void testGet_WithOptionalHeaders() throws RestException { final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/get", jsonObject.getString("url", null)); + assertEquals("http://127.0.0.1:8999/", jsonObject.getString("URL", null)); + final JsonObject headers = jsonObject.get("headers").asObject(); - // Note that even though our header names where all-lowercase, the round trip process converts them to camel case. assertEquals("white", headers.getString("Black", null)); assertEquals("night", headers.getString("Day", null)); assertEquals("Header value", headers.getString("Two-Part", null)); @@ -159,13 +182,14 @@ public void testGet_WithOptionalHeaders() throws RestException { @Test public void testGet_RetrievesResponseBodyWhenStatusIs418() throws RestException { final RestResponse restResponse = new Rest() - .url("http://httpbin.org/status/418") + .url(this.URL + "status/200") .get(); - assertEquals(418, restResponse.getStatus()); + assertEquals(200, restResponse.getStatus()); final String responseBody = new String(restResponse.getBody(), StandardCharsets.UTF_8); assertTrue("Response body is empty", responseBody.length() > 0); - assertTrue("Response body doesn't contain word teapot", responseBody.contains("teapot")); + assertTrue("Response body doesn't contain word User-Agent", + responseBody.contains("User-Agent")); } } diff --git a/src/test/java/io/github/jopenlibs/vault/rest/PostTests.java b/src/test/java/io/github/jopenlibs/vault/rest/PostTests.java index 4bdc3368..57135804 100644 --- a/src/test/java/io/github/jopenlibs/vault/rest/PostTests.java +++ b/src/test/java/io/github/jopenlibs/vault/rest/PostTests.java @@ -2,7 +2,12 @@ import io.github.jopenlibs.vault.json.Json; import io.github.jopenlibs.vault.json.JsonObject; +import io.github.jopenlibs.vault.vault.VaultTestUtils; +import io.github.jopenlibs.vault.vault.mock.EchoInputMockVault; import java.nio.charset.StandardCharsets; +import org.eclipse.jetty.server.Server; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -13,20 +18,36 @@ */ public class PostTests { + private Server server; + private final String URL = "http://127.0.0.1:8999/"; + + @Before + public void startServer() throws Exception { + EchoInputMockVault echoInputMockVault = new EchoInputMockVault(200); + + this.server = VaultTestUtils.initHttpMockVault(echoInputMockVault); + this.server.start(); + } + + @After + public void stopServer() throws Exception { + this.server.stop(); + } + /** * Verify a basic POST request, with no parameters or headers. */ @Test public void testPost_Plain() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/post") + .url(this.URL) .post(); assertEquals(200, restResponse.getStatus()); assertEquals("application/json", restResponse.getMimeType()); final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/post", jsonObject.getString("url", null)); + assertEquals(this.URL, jsonObject.getString("URL", null)); } /** @@ -37,7 +58,7 @@ public void testPost_Plain() throws RestException { @Test public void testPost_InsertParams() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/post") + .url(this.URL) .parameter("foo", "bar") .parameter("apples", "oranges") .parameter("multi part", "this parameter has whitespace in its name and value") @@ -47,11 +68,9 @@ public void testPost_InsertParams() throws RestException { final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/post", jsonObject.getString("url", null)); + assertEquals(this.URL, jsonObject.getString("URL", null)); - // Note that with a POST (as with a PUT) to this "httpbin.org" test service, parameters are returned - // within a JSON object called "form", unlike it's "args" counterpart when doing a GET. - final JsonObject form = jsonObject.get("form").asObject(); + final JsonObject form = jsonObject.get("args").asObject(); assertEquals("bar", form.getString("foo", null)); assertEquals("oranges", form.getString("apples", null)); assertEquals("this parameter has whitespace in its name and value", @@ -66,7 +85,7 @@ public void testPost_InsertParams() throws RestException { @Test public void testPost_UpdateParams() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/post?hot=cold") + .url(this.URL + "?hot=cold") .parameter("foo", "bar") .parameter("apples", "oranges") .parameter("multi part", "this parameter has whitespace in its name and value") @@ -76,14 +95,13 @@ public void testPost_UpdateParams() throws RestException { final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/post?hot=cold", jsonObject.getString("url", null)); + assertEquals(this.URL + "?hot=cold", jsonObject.getString("URL", null)); final JsonObject args = jsonObject.get("args").asObject(); assertEquals("cold", args.getString("hot", null)); - final JsonObject form = jsonObject.get("form").asObject(); - assertEquals("bar", form.getString("foo", null)); - assertEquals("oranges", form.getString("apples", null)); + assertEquals("bar", args.getString("foo", null)); + assertEquals("oranges", args.getString("apples", null)); assertEquals("this parameter has whitespace in its name and value", - form.getString("multi part", null)); + args.getString("multi part", null)); } /** @@ -95,17 +113,17 @@ public void testPost_UpdateParams() throws RestException { @Test public void testPost_WithHeaders() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/post") - .header("black", "white") - .header("day", "night") - .header("two-part", "Header value") + .url(this.URL) + .header("Black", "white") + .header("Day", "night") + .header("Two-Part", "Header value") .post(); assertEquals(200, restResponse.getStatus()); assertEquals("application/json", restResponse.getMimeType()); final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/post", jsonObject.getString("url", null)); + assertEquals(this.URL, jsonObject.getString("URL", null)); final JsonObject headers = jsonObject.get("headers").asObject(); assertEquals("white", headers.getString("Black", null)); assertEquals("night", headers.getString("Day", null)); @@ -121,10 +139,10 @@ public void testPost_WithHeaders() throws RestException { @Test public void testPost_WithOptionalHeaders() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/post") - .header("black", "white") - .header("day", "night") - .header("two-part", "Header value") + .url(this.URL) + .header("Black", "white") + .header("Day", "night") + .header("Two-Part", "Header value") .header("I am null", null) .header("I am empty", "") .post(); @@ -133,7 +151,7 @@ public void testPost_WithOptionalHeaders() throws RestException { final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/post", jsonObject.getString("url", null)); + assertEquals(this.URL, jsonObject.getString("URL", null)); final JsonObject headers = jsonObject.get("headers").asObject(); assertEquals("white", headers.getString("Black", null)); assertEquals("night", headers.getString("Day", null)); diff --git a/src/test/java/io/github/jopenlibs/vault/rest/PutTests.java b/src/test/java/io/github/jopenlibs/vault/rest/PutTests.java index 01379ce5..babd029c 100644 --- a/src/test/java/io/github/jopenlibs/vault/rest/PutTests.java +++ b/src/test/java/io/github/jopenlibs/vault/rest/PutTests.java @@ -2,7 +2,12 @@ import io.github.jopenlibs.vault.json.Json; import io.github.jopenlibs.vault.json.JsonObject; +import io.github.jopenlibs.vault.vault.VaultTestUtils; +import io.github.jopenlibs.vault.vault.mock.EchoInputMockVault; import java.nio.charset.StandardCharsets; +import org.eclipse.jetty.server.Server; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -13,20 +18,36 @@ */ public class PutTests { + private Server server; + private final String URL = "http://127.0.0.1:8999/"; + + @Before + public void startServer() throws Exception { + EchoInputMockVault echoInputMockVault = new EchoInputMockVault(200); + + this.server = VaultTestUtils.initHttpMockVault(echoInputMockVault); + this.server.start(); + } + + @After + public void stopServer() throws Exception { + this.server.stop(); + } + /** * Verify a basic PUT request, with no parameters or headers. */ @Test public void testPut_Plain() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/put") + .url(this.URL) .put(); assertEquals(200, restResponse.getStatus()); assertEquals("application/json", restResponse.getMimeType()); final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/put", jsonObject.getString("url", null)); + assertEquals(this.URL, jsonObject.getString("URL", null)); } /** @@ -37,7 +58,7 @@ public void testPut_Plain() throws RestException { @Test public void testPut_InsertParams() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/put") + .url(this.URL) .parameter("foo", "bar") .parameter("apples", "oranges") .parameter("multi part", "this parameter has whitespace in its name and value") @@ -47,11 +68,9 @@ public void testPut_InsertParams() throws RestException { final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/put", jsonObject.getString("url", null)); + assertEquals(this.URL, jsonObject.getString("URL", null)); - // Note that with a PUT (as with a POST) to this "httpbin.org" test service, parameters are - // returned within a JSON object called "form", unlike it's "args" counterpart when doing a GET. - final JsonObject form = jsonObject.get("form").asObject(); + final JsonObject form = jsonObject.get("args").asObject(); assertEquals("bar", form.getString("foo", null)); assertEquals("oranges", form.getString("apples", null)); assertEquals("this parameter has whitespace in its name and value", @@ -66,7 +85,7 @@ public void testPut_InsertParams() throws RestException { @Test public void testPut_UpdateParams() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/put?hot=cold") + .url(this.URL+"?hot=cold") .parameter("foo", "bar") .parameter("apples", "oranges") .parameter("multi part", "this parameter has whitespace in its name and value") @@ -76,10 +95,10 @@ public void testPut_UpdateParams() throws RestException { final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/put?hot=cold", jsonObject.getString("url", null)); + assertEquals(this.URL+"?hot=cold", jsonObject.getString("URL", null)); final JsonObject args = jsonObject.get("args").asObject(); assertEquals("cold", args.getString("hot", null)); - final JsonObject form = jsonObject.get("form").asObject(); + final JsonObject form = jsonObject.get("args").asObject(); assertEquals("bar", form.getString("foo", null)); assertEquals("oranges", form.getString("apples", null)); assertEquals("this parameter has whitespace in its name and value", @@ -95,17 +114,17 @@ public void testPut_UpdateParams() throws RestException { @Test public void testPut_WithHeaders() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/put") - .header("black", "white") - .header("day", "night") - .header("two-part", "Header value") + .url(this.URL) + .header("Black", "white") + .header("Day", "night") + .header("Two-Part", "Header value") .put(); assertEquals(200, restResponse.getStatus()); assertEquals("application/json", restResponse.getMimeType()); final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/put", jsonObject.getString("url", null)); + assertEquals(this.URL, jsonObject.getString("URL", null)); final JsonObject headers = jsonObject.get("headers").asObject(); assertEquals("white", headers.getString("Black", null)); assertEquals("night", headers.getString("Day", null)); @@ -121,10 +140,10 @@ public void testPut_WithHeaders() throws RestException { @Test public void testPut_WithOptionalHeaders() throws RestException { final RestResponse restResponse = new Rest() - .url("https://httpbin.org/put") - .header("black", "white") - .header("day", "night") - .header("two-part", "Header value") + .url(this.URL) + .header("Black", "white") + .header("Day", "night") + .header("Two-Part", "Header value") .header("I am null", null) .header("I am empty", "") .put(); @@ -133,7 +152,7 @@ public void testPut_WithOptionalHeaders() throws RestException { final String jsonString = new String(restResponse.getBody(), StandardCharsets.UTF_8); final JsonObject jsonObject = Json.parse(jsonString).asObject(); - assertEquals("https://httpbin.org/put", jsonObject.getString("url", null)); + assertEquals(this.URL, jsonObject.getString("URL", null)); final JsonObject headers = jsonObject.get("headers").asObject(); assertEquals("white", headers.getString("Black", null)); assertEquals("night", headers.getString("Day", null)); diff --git a/src/test/java/io/github/jopenlibs/vault/vault/api/AuthLookupWrapTest.java b/src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingLookupWrapTest.java similarity index 92% rename from src/test/java/io/github/jopenlibs/vault/vault/api/AuthLookupWrapTest.java rename to src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingLookupWrapTest.java index a8aca43d..62eaca22 100644 --- a/src/test/java/io/github/jopenlibs/vault/vault/api/AuthLookupWrapTest.java +++ b/src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingLookupWrapTest.java @@ -1,4 +1,4 @@ -package io.github.jopenlibs.vault.vault.api; +package io.github.jopenlibs.vault.vault.api.sys; import io.github.jopenlibs.vault.Vault; import io.github.jopenlibs.vault.VaultConfig; @@ -14,7 +14,7 @@ import static org.junit.Assert.assertEquals; -public class AuthLookupWrapTest { +public class WrappingLookupWrapTest { private static final JsonObject RESPONSE_AUTH_LOOKUPSELF = new JsonObject() .add("data", new JsonObject() @@ -40,7 +40,7 @@ public void should_lookup_wrap_use_url_sys_wrapping_lookup() throws Exception { VaultConfig vaultConfig = new VaultConfig().address("http://127.0.0.1:8999") .token("wrapped").build(); Vault vault = new Vault(vaultConfig); - LogicalResponse response = vault.auth().lookupWrap(); + LogicalResponse response = vault.sys().wrapping().lookupWrap(); assertEquals(200, response.getRestResponse().getStatus()); diff --git a/src/test/java/io/github/jopenlibs/vault/vault/api/AuthUnwrapTest.java b/src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingUnwrapTest.java similarity index 92% rename from src/test/java/io/github/jopenlibs/vault/vault/api/AuthUnwrapTest.java rename to src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingUnwrapTest.java index 326192f9..a6a6b386 100644 --- a/src/test/java/io/github/jopenlibs/vault/vault/api/AuthUnwrapTest.java +++ b/src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingUnwrapTest.java @@ -1,4 +1,4 @@ -package io.github.jopenlibs.vault.vault.api; +package io.github.jopenlibs.vault.vault.api.sys; import io.github.jopenlibs.vault.Vault; import io.github.jopenlibs.vault.VaultConfig; @@ -15,7 +15,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -public class AuthUnwrapTest { +public class WrappingUnwrapTest { private static final JsonObject RESPONSE_AUTH_UNWRAP = new JsonObject() .add("renewable", false) @@ -44,7 +44,7 @@ public void should_unwrap_without_param_sends_no_token_and_return_unwrapped_toke VaultConfig vaultConfig = new VaultConfig().address("http://127.0.0.1:8999") .token("wrappedToken").build(); Vault vault = new Vault(vaultConfig); - AuthResponse response = vault.auth().unwrap(); + AuthResponse response = vault.sys().wrapping().unwrap(); assertEquals(200, response.getRestResponse().getStatus()); @@ -61,7 +61,7 @@ public void should_unwrap_param_sends_token_and_return_unwrapped_token() throws VaultConfig vaultConfig = new VaultConfig().address("http://127.0.0.1:8999") .token("authToken").build(); Vault vault = new Vault(vaultConfig); - AuthResponse response = vault.auth().unwrap("wrappedToken"); + AuthResponse response = vault.sys().wrapping().unwrap("wrappedToken"); assertEquals(200, response.getRestResponse().getStatus()); diff --git a/src/test/java/io/github/jopenlibs/vault/vault/api/AuthUnwrapWithoutAuthResponseTest.java b/src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingUnwrapWithoutAuthResponseTest.java similarity index 91% rename from src/test/java/io/github/jopenlibs/vault/vault/api/AuthUnwrapWithoutAuthResponseTest.java rename to src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingUnwrapWithoutAuthResponseTest.java index f6eeec19..f56bb983 100644 --- a/src/test/java/io/github/jopenlibs/vault/vault/api/AuthUnwrapWithoutAuthResponseTest.java +++ b/src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingUnwrapWithoutAuthResponseTest.java @@ -1,4 +1,4 @@ -package io.github.jopenlibs.vault.vault.api; +package io.github.jopenlibs.vault.vault.api.sys; import io.github.jopenlibs.vault.Vault; import io.github.jopenlibs.vault.VaultConfig; @@ -13,7 +13,7 @@ import static org.junit.Assert.assertEquals; -public class AuthUnwrapWithoutAuthResponseTest { +public class WrappingUnwrapWithoutAuthResponseTest { private Server server; private MockVault vaultServer; @@ -38,7 +38,7 @@ public void unwrap_response_without_auth() throws Exception { VaultConfig vaultConfig = new VaultConfig().address("http://127.0.0.1:8999") .token("wrappedToken").build(); Vault vault = new Vault(vaultConfig); - UnwrapResponse response = vault.auth().unwrap("wrappedToken"); + UnwrapResponse response = vault.sys().wrapping().unwrap("wrappedToken"); assertEquals(200, response.getRestResponse().getStatus()); @@ -64,7 +64,7 @@ public void unwrap_response_without_implicit_null_auth() throws Exception { VaultConfig vaultConfig = new VaultConfig().address("http://127.0.0.1:8999") .token("wrappedToken").build(); Vault vault = new Vault(vaultConfig); - UnwrapResponse response = vault.auth().unwrap("wrappedToken"); + UnwrapResponse response = vault.sys().wrapping().unwrap("wrappedToken"); assertEquals(200, response.getRestResponse().getStatus()); diff --git a/src/test/java/io/github/jopenlibs/vault/vault/api/AuthWrapTest.java b/src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingWrapTest.java similarity index 94% rename from src/test/java/io/github/jopenlibs/vault/vault/api/AuthWrapTest.java rename to src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingWrapTest.java index 0163c23a..f561ea8f 100644 --- a/src/test/java/io/github/jopenlibs/vault/vault/api/AuthWrapTest.java +++ b/src/test/java/io/github/jopenlibs/vault/vault/api/sys/WrappingWrapTest.java @@ -1,4 +1,4 @@ -package io.github.jopenlibs.vault.vault.api; +package io.github.jopenlibs.vault.vault.api.sys; import io.github.jopenlibs.vault.Vault; import io.github.jopenlibs.vault.VaultConfig; @@ -13,7 +13,7 @@ import static org.junit.Assert.assertEquals; -public class AuthWrapTest { +public class WrappingWrapTest { private static final JsonObject RESPONSE_AUTH_WRAP = new JsonObject() .add("renewable", false) @@ -45,7 +45,7 @@ public void check_wrap_request_response() throws Exception { .token("wrappedToken").build(); Vault vault = new Vault(vaultConfig); - WrapResponse response = vault.auth().wrap( + WrapResponse response = vault.sys().wrapping().wrap( new JsonObject() .add("foo", "bar") .add("zoo", "zar"), diff --git a/src/test/java/io/github/jopenlibs/vault/vault/mock/EchoInputMockVault.java b/src/test/java/io/github/jopenlibs/vault/vault/mock/EchoInputMockVault.java index c40fed36..90312464 100644 --- a/src/test/java/io/github/jopenlibs/vault/vault/mock/EchoInputMockVault.java +++ b/src/test/java/io/github/jopenlibs/vault/vault/mock/EchoInputMockVault.java @@ -21,7 +21,7 @@ */ public class EchoInputMockVault extends MockVault { - private int mockStatus; + private final int mockStatus; private String lastRequestDetails; public EchoInputMockVault(final int mockStatus) { @@ -36,6 +36,8 @@ public void handle( final HttpServletResponse response ) throws IOException { final JsonObject headers = Json.object(); + final JsonObject values = Json.object(); + final Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { final String name = headerNames.nextElement(); @@ -43,18 +45,25 @@ public void handle( headers.add(name, value); } - final StringBuilder url = new StringBuilder(request.getScheme()) - .append("://") - .append(request.getServerName()) - .append(request.getServerPort() == 0 ? "" : ":" + request.getServerPort()) - .append(request.getRequestURI()) - .append(request.getQueryString() == null || request.getQueryString().isEmpty() ? "" - : "?" + - request.getQueryString()); + final Enumeration parameterNames = request.getParameterNames(); + while (parameterNames.hasMoreElements()) { + final String name = parameterNames.nextElement(); + final String value = request.getParameter(name); + values.add(name, value); + } + + String url = request.getScheme() + + "://" + + request.getServerName() + + (request.getServerPort() == 0 ? "" : ":" + request.getServerPort()) + + request.getRequestURI() + + (request.getQueryString() == null || request.getQueryString().isEmpty() ? "" + : "?" + request.getQueryString()); final String mockResponse = Json.object() .add("method", request.getMethod()) - .add("URL", url.toString()) + .add("URL", url) + .add("args", values) .add("headers", headers) .toString();