commit b845e4b7ce032e522bca7ea2a4552aeb4ebaf884 Author: Stefan Kalscheuer Date: Tue Mar 29 15:12:35 2016 +0200 Initial Import diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..25909a4 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +Java Vault Connector +========= +Java Vault Connector is a connector library for [Vault](https://www.vaultproject.io) by [Hashicorp](https://www.hashicorp.com) written in Java. The connector allows simple usage of Vault's secret store in own applications. + +**Current available features:** + +* HTTP(S) backend connector +* Authorization methods: + * Token + * Username/Password + * AppID (register and authenticate) +* Secrets + * Read secrets + * Write secrets + * List secrets +* Connector Factory with builder pattern +* Tested against Vault 0.5.2 + +**Usage Example** + +```java +// Instanciate using builder pattern style factory +VaultConnector vault = VaultConnectorFactory.httpFactory() + .wiithHost("127.0.0.1") + .withPort(8200) + .withTLS() + .build(); + +//authenticate with token +vault.authToken("01234567-89ab-cdef-0123-456789abcdef"); + +// retrieve secret +String secret = vault.readSecret("some/secret/key").getValue(); +``` + +**Maven Artifact** +``` + + de.stklcode.jvault + connector + 0.1 + +``` + +**Links** + +[Project Page](http://jvault.stklcode.de) + +[JavaDoc API](http://jvault.stklcode.de/apidocs/) + +**Planned features:** + +* Creation and modification of policies +* Implement more authentication methods + +**License** + +The project is licensed under [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0). diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a5a94d6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,66 @@ + + 4.0.0 + + jVaultConnector + + de.stklcode.jvault + connector + 0.1 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + jar + + + + commons-io + commons-io + 2.4 + + + org.apache.httpcomponents + httpcore + 4.0.1 + + + org.apache.httpcomponents + httpclient + 4.0.2 + + + com.fasterxml.jackson.core + jackson-core + 2.7.2 + + + com.fasterxml.jackson.core + jackson-databind + 2.7.2 + + + + junit + junit + 4.11 + test + + + org.hamcrest + hamcrest-junit + 2.0.0.0 + test + + + diff --git a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java new file mode 100644 index 0000000..e9c3286 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java @@ -0,0 +1,401 @@ +package de.stklcode.jvault.connector; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.stklcode.jvault.connector.exception.*; +import de.stklcode.jvault.connector.model.AuthBackend; +import de.stklcode.jvault.connector.model.response.*; +import de.stklcode.jvault.connector.model.response.embedded.AuthMethod; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HTTP; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.*; +import java.util.stream.Collectors; + + +/** + * Vault Connector implementatin using Vault's HTTP API. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public class HTTPVaultConnector implements VaultConnector { + private static final String PATH_PREFIX = "/v1/"; + private static final String PATH_SEAL_STATUS = "sys/seal-status"; + private static final String PATH_SEAL = "sys/seal"; + private static final String PATH_UNSEAL = "sys/unseal"; + private static final String PATH_INIT = "sys/init"; + private static final String PATH_AUTH = "sys/auth"; + private static final String PATH_TOKEN_LOOKUP = "auth/token/lookup"; + private static final String PATH_AUTH_USERPASS = "auth/userpass/login/"; + private static final String PATH_AUTH_APPID = "auth/app-id/"; + private static final String PATH_SECRET = "secret"; + + private final ObjectMapper jsonMapper; + + private final HttpClient httpClient; /* HTTP client for connection */ + private final String baseURL; /* Base URL of Vault */ + + private boolean authorized = false; /* authorization status */ + private String token; /* current token */ + + /** + * Create connector using hostname and schema. + * @param hostname The hostname + * @param useTLS If TRUE, use HTTPS, otherwise HTTP + */ + public HTTPVaultConnector(String hostname, boolean useTLS) { + this(hostname, useTLS, null); + } + + /** + * Create connector using hostname, schema and port. + * @param hostname The hostname + * @param useTLS If TRUE, use HTTPS, otherwise HTTP + * @param port The port + */ + public HTTPVaultConnector(String hostname, boolean useTLS, Integer port) { + this(hostname, useTLS, port, PATH_PREFIX); + } + + /** + * Create connector using hostname, schame, port and path + * @param hostname The hostname + * @param useTLS If TRUE, use HTTPS, otherwise HTTP + * @param port The port + * @param prefix HTTP API prefix (default: /v1/" + */ + public HTTPVaultConnector(String hostname, boolean useTLS, Integer port, String prefix) { + this(((useTLS) ? "https" : "http") + + "://" + hostname + + ((port != null) ? ":" + port : "") + + prefix); + } + + /** + * Create connector using full URL + * @param baseURL The URL + */ + public HTTPVaultConnector(String baseURL) { + this.baseURL = baseURL; + this.httpClient = new DefaultHttpClient(); + this.jsonMapper = new ObjectMapper(); + } + + @Override + public void resetAuth() { + token = null; + authorized = false; + } + + @Override + public SealResponse sealStatus() { + try { + String response = requestGet(PATH_SEAL_STATUS, new HashMap<>()); + return jsonMapper.readValue(response, SealResponse.class); + } catch (VaultConnectorException | IOException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public boolean seal() { + try { + requestPut(PATH_SEAL, new HashMap<>()); + return true; + } catch (VaultConnectorException e) { + e.printStackTrace(); + return false; + } + } + + @Override + public SealResponse unseal(final String key, final Boolean reset) { + Map param = new HashMap<>(); + param.put("key", key); + if (reset != null) + param.put("reset", reset); + try { + String response = requestPut(PATH_UNSEAL, param); + return jsonMapper.readValue(response, SealResponse.class); + } catch (VaultConnectorException | IOException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public boolean isAuthorized() { + return authorized; + } + + @Override + public boolean init() { + /* TODO: implement init() */ + return true; + } + + @Override + public List getAuthBackends() throws VaultConnectorException { + try { + String response = requestGet(PATH_AUTH, new HashMap<>()); + /* Parse response */ + AuthMethodsResponse amr = jsonMapper.readValue(response, AuthMethodsResponse.class); + return amr.getSupportedMethods().stream().map(AuthMethod::getType).collect(Collectors.toList()); + } catch (IOException e) { + throw new InvalidResponseException("Unable to parse response", e); + } + } + + @Override + public TokenResponse authToken(final String token) throws VaultConnectorException { + /* set token */ + this.token = token; + try { + String response = requestPost(PATH_TOKEN_LOOKUP, new HashMap<>()); + TokenResponse res = jsonMapper.readValue(response, TokenResponse.class); + authorized = true; + return res; + } catch (IOException e) { + throw new InvalidResponseException("Unable to parse response", e); + } + } + + @Override + public AuthResponse authUserPass(final String username, final String password) throws VaultConnectorException { + Map payload = new HashMap<>(); + payload.put("password", password); + try { + /* Get response */ + String response = requestPost(PATH_AUTH_USERPASS + username, payload); + /* Parse response */ + AuthResponse upr = jsonMapper.readValue(response, AuthResponse.class); + /* verify response */ + this.token = upr.getAuth().getClientToken(); + this.authorized = true; + return upr; + } catch (IOException e) { + throw new InvalidResponseException("Unable to parse response", e); + } + } + + @Override + public AuthResponse authAppId(final String appID, final String userID) throws VaultConnectorException { + Map payload = new HashMap<>(); + payload.put("app_id", appID); + payload.put("user_id", userID); + try { + /* Get response */ + String response = requestPost(PATH_AUTH_APPID + "login", payload); + /* Parse response */ + AuthResponse auth = jsonMapper.readValue(response, AuthResponse.class); + /* verify response */ + this.token = auth.getAuth().getClientToken(); + this.authorized = true; + return auth; + } catch (IOException e) { + throw new InvalidResponseException("Unable to parse response", e); + } + } + + @Override + public boolean registerAppId(final String appID, final String policy, final String displayName) throws VaultConnectorException { + if (!isAuthorized()) + throw new AuthorizationRequiredException(); + Map payload = new HashMap<>(); + payload.put("value", policy); + payload.put("display_name", displayName); + /* Get response */ + String response = requestPost(PATH_AUTH_APPID + "map/app-id/" + appID, payload); + /* Response should be code 204 without content */ + if (!response.equals("")) + throw new InvalidResponseException("Received response where non was expected."); + return true; + } + + @Override + public boolean registerUserId(final String appID, final String userID) throws VaultConnectorException { + if (!isAuthorized()) + throw new AuthorizationRequiredException(); + Map payload = new HashMap<>(); + payload.put("value", appID); + /* Get response */ + String response = requestPost(PATH_AUTH_APPID + "map/user-id/" + userID, payload); + /* Response should be code 204 without content */ + if (!response.equals("")) + throw new InvalidResponseException("Received response where non was expected."); + return true; + } + + @Override + public SecretResponse readSecret(final String key) throws VaultConnectorException { + if (!isAuthorized()) + throw new AuthorizationRequiredException(); + /* Request HTTP response and parse Secret */ + try { + String response = requestGet(PATH_SECRET + "/" + key, new HashMap<>()); + return jsonMapper.readValue(response, SecretResponse.class); + } catch (IOException e) { + throw new InvalidResponseException("Unable to parse response", e); + } + } + + @Override + public List listSecrets(final String path) throws VaultConnectorException { + if (!isAuthorized()) + throw new AuthorizationRequiredException(); + + String response = requestGet(PATH_SECRET + "/" + path + "/?list=true", new HashMap<>()); + try { + SecretListResponse secrets = jsonMapper.readValue(response, SecretListResponse.class); + return secrets.getKeys(); + } catch (IOException e) { + throw new InvalidResponseException("Unable to parse response", e); + } + } + + @Override + public boolean writeSecret(final String key, final String value) throws VaultConnectorException { + if (key == null || key.isEmpty()) + throw new InvalidRequestException("Secret path must not be empty."); + + Map param = new HashMap<>(); + param.put("value", value); + return requestPost(PATH_SECRET + "/" + key, param).equals(""); + } + + + + /** + * Execute HTTP request using POST method. + * @param path URL path (relative to base) + * @param payload Map of payload values (will be converted to JSON) + * @return HTTP response + * @throws VaultConnectorException + */ + private String requestPost(final String path, final Map payload) throws VaultConnectorException { + /* Initialize post */ + HttpPost post = new HttpPost(baseURL + path); + /* generate JSON from payload */ + StringEntity input; + try { + input = new StringEntity(jsonMapper.writeValueAsString(payload), HTTP.UTF_8); + } catch (UnsupportedEncodingException | JsonProcessingException e) { + throw new InvalidRequestException("Unable to parse response", e); + } + input.setContentEncoding("UTF-8"); + input.setContentType("application/json"); + post.setEntity(input); + /* Set X-Vault-Token header */ + if (token != null) + post.addHeader("X-Vault-Token", token); + + return request(post); + } + + /** + * Execute HTTP request using PUT method. + * @param path URL path (relative to base) + * @param payload Map of payload values (will be converted to JSON) + * @return HTTP response + * @throws VaultConnectorException + */ + private String requestPut(final String path, final Map payload) throws VaultConnectorException { + /* Initialize post */ + HttpPut put = new HttpPut(baseURL + path); + /* generate JSON from payload */ + StringEntity entity = null; + try { + entity = new StringEntity(jsonMapper.writeValueAsString(payload)); + } catch (UnsupportedEncodingException | JsonProcessingException e) { + e.printStackTrace(); + } + /* Parse parameters */ + put.setEntity(entity); + /* Set X-Vault-Token header */ + if (token != null) + put.addHeader("X-Vault-Token", token); + + return request(put); + } + + /** + * Execute HTTP request using GET method. + * @param path URL path (relative to base) + * @param payload Map of payload values (will be converted to JSON) + * @return HTTP response + * @throws VaultConnectorException + */ + private String requestGet(final String path, final Map payload) throws VaultConnectorException { + /* Initialize post */ + HttpGet get = new HttpGet(baseURL + path); + /* Parse parameters */ + HttpParams params = new BasicHttpParams(); + payload.forEach(params::setParameter); + get.setParams(params); + + /* Set X-Vault-Token header */ + if (token != null) + get.addHeader("X-Vault-Token", token); + + return request(get); + } + + /** + * Execute prepared HTTP request and return result + * @param base Prepares Request + * @return HTTP response + * @throws VaultConnectorException + */ + private String request(HttpRequestBase base) throws VaultConnectorException { + /* Set JSON Header */ + base.addHeader("accept", "application/json"); + + HttpResponse response = null; + try { + response = httpClient.execute(base); + /* Check if response is valid */ + if (response == null) + throw new InvalidResponseException("Response unavailable"); + switch (response.getStatusLine().getStatusCode()) { + case 200: + return IOUtils.toString(response.getEntity().getContent()); + case 204: + return ""; + case 403: + throw new PermissionDeniedException(); + default: + InvalidResponseException ex = new InvalidResponseException("Invalid response code") + .withStatusCode(response.getStatusLine().getStatusCode()); + try { + throw ex.withResponse(IOUtils.toString(response.getEntity().getContent())); + } + catch (IOException e) { + throw ex; + } + } + } catch (IOException e) { + throw new InvalidResponseException("Unable to read response", e); + } + finally { + if (response != null && response.getEntity() != null) + try { + response.getEntity().consumeContent(); + } catch (IOException ignored) { + } + } + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java new file mode 100644 index 0000000..7124765 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java @@ -0,0 +1,149 @@ +package de.stklcode.jvault.connector; + +import de.stklcode.jvault.connector.exception.VaultConnectorException; +import de.stklcode.jvault.connector.model.AuthBackend; +import de.stklcode.jvault.connector.model.response.SealResponse; +import de.stklcode.jvault.connector.model.response.SecretResponse; +import de.stklcode.jvault.connector.model.response.TokenResponse; +import de.stklcode.jvault.connector.model.response.AuthResponse; + +import java.util.List; + +/** + * Vault Connector interface. + * Provides methods to connect with Vault backend and handle secrets. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public interface VaultConnector { + /** + * Verify that vault connection is initialized. + * @return TRUE if correctly initialized + */ + boolean init(); + + /** + * Reset authorization information. + */ + void resetAuth(); + + /** + * Retrieve status of vault seal. + * @return Seal status + */ + SealResponse sealStatus(); + + /** + * Seal vault. + * @return TRUE on success + */ + boolean seal(); + + /** + * Unseal vault. + * @param key A single master share key + * @param reset Discard previously provided keys (optional) + * @return TRUE on success + */ + SealResponse unseal(final String key, final Boolean reset); + + /** + * Unseal vault. + * @param key A single master share key + * @return TRUE on success + */ + default SealResponse unseal(final String key) { + return unseal(key, null); + } + + /** + * Get all availale authentication backends. + * @return List of backends + */ + List getAuthBackends() throws VaultConnectorException; + + /** + * Authorize to Vault using token. + * @param token The token + * @return Token response + */ + TokenResponse authToken(final String token) throws VaultConnectorException; + + /** + * Authorize to Vault using username and password. + * @param username The username + * @param password The password + * @return Authorization result + * @throws VaultConnectorException + */ + AuthResponse authUserPass(final String username, final String password) throws VaultConnectorException; + + /** + * Authorize to Vault using AppID method. + * @param appID The App ID + * @param userID The User ID + * @return TRUE on success + */ + AuthResponse authAppId(final String appID, final String userID) throws VaultConnectorException; + + /** + * Register new App-ID with policy. + * @param appID The unique App-ID + * @param policy The policy to associate with + * @param displayName Arbitrary name to display + * @return TRUE on success + * @throws VaultConnectorException + */ + boolean registerAppId(final String appID, final String policy, final String displayName) throws VaultConnectorException; + + /** + * Register User-ID with App-ID + * @param appID The App-ID + * @param userID The User-ID + * @return TRUE on success + * @throws VaultConnectorException + */ + boolean registerUserId(final String appID, final String userID) throws VaultConnectorException; + + /** + * Register new App-ID and User-ID at once. + * @param appID The App-ID + * @param policy The policy to associate with + * @param displayName Arbitrary name to display + * @param userID The User-ID + * @return TRUE on success + * @throws VaultConnectorException + */ + default boolean registerAppUserId(final String appID, final String policy, final String displayName, final String userID) throws VaultConnectorException { + return registerAppId(appID, policy, userID) && registerUserId(appID, userID); + } + + /** + * Get authorization status + * @return TRUE, if successfully authorized + */ + boolean isAuthorized(); + + /** + * Retrieve secret form Vault. + * @param key Secret identifier + * @return Secret response + */ + SecretResponse readSecret(final String key) throws VaultConnectorException; + + /** + * List available secrets from Vault. + * @param path Root path to search + * @return List of secret keys + */ + List listSecrets(final String path) throws VaultConnectorException; + + /** + * Write secret to Vault. + * @param key Secret path + * @param value Secret value + * @return TRUE on success + */ + boolean writeSecret(final String key, final String value) throws VaultConnectorException; +} diff --git a/src/main/java/de/stklcode/jvault/connector/exception/AuthorizationRequiredException.java b/src/main/java/de/stklcode/jvault/connector/exception/AuthorizationRequiredException.java new file mode 100644 index 0000000..5645fb1 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/exception/AuthorizationRequiredException.java @@ -0,0 +1,10 @@ +package de.stklcode.jvault.connector.exception; + +/** + * Exception thrown trying to do a request without any authorization handles. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public class AuthorizationRequiredException extends VaultConnectorException { +} diff --git a/src/main/java/de/stklcode/jvault/connector/exception/ConnectionException.java b/src/main/java/de/stklcode/jvault/connector/exception/ConnectionException.java new file mode 100644 index 0000000..d4cdf1f --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/exception/ConnectionException.java @@ -0,0 +1,24 @@ +package de.stklcode.jvault.connector.exception; + +/** + * Exception thrown on problems with connection to Vault backend. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public class ConnectionException extends VaultConnectorException { + public ConnectionException() { + } + + public ConnectionException(String message) { + super(message); + } + + public ConnectionException(Throwable cause) { + super(cause); + } + + public ConnectionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/exception/InvalidRequestException.java b/src/main/java/de/stklcode/jvault/connector/exception/InvalidRequestException.java new file mode 100644 index 0000000..8ea4f16 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/exception/InvalidRequestException.java @@ -0,0 +1,24 @@ +package de.stklcode.jvault.connector.exception; + +/** + * Exception thrown when trying to send malformed request. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public class InvalidRequestException extends VaultConnectorException { + public InvalidRequestException() { + } + + public InvalidRequestException(String message) { + super(message); + } + + public InvalidRequestException(Throwable cause) { + super(cause); + } + + public InvalidRequestException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/exception/InvalidResponseException.java b/src/main/java/de/stklcode/jvault/connector/exception/InvalidResponseException.java new file mode 100644 index 0000000..2135007 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/exception/InvalidResponseException.java @@ -0,0 +1,46 @@ +package de.stklcode.jvault.connector.exception; + +/** + * Exception thrown when response from vault returned with erroneous status code or payload could not be parsed + * to entity class. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public class InvalidResponseException extends VaultConnectorException { + private Integer statusCode; + private String response; + + public InvalidResponseException() { + } + + public InvalidResponseException(String message) { + super(message); + } + + public InvalidResponseException(Throwable cause) { + super(cause); + } + + public InvalidResponseException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidResponseException withStatusCode(Integer statusCode) { + this.statusCode = statusCode; + return this; + } + + public InvalidResponseException withResponse(String response) { + this.response = response; + return this; + } + + public Integer getStatusCode() { + return statusCode; + } + + public String getResponse() { + return response; + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/exception/PermissionDeniedException.java b/src/main/java/de/stklcode/jvault/connector/exception/PermissionDeniedException.java new file mode 100644 index 0000000..5e83bb0 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/exception/PermissionDeniedException.java @@ -0,0 +1,25 @@ +package de.stklcode.jvault.connector.exception; + +/** + * Exception thrown when trying to access a path the current user/token does not have permission to access. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public class PermissionDeniedException extends VaultConnectorException { + public PermissionDeniedException() { + super("Permission denied"); + } + + public PermissionDeniedException(String message) { + super(message); + } + + public PermissionDeniedException(Throwable cause) { + super(cause); + } + + public PermissionDeniedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/exception/VaultConnectorException.java b/src/main/java/de/stklcode/jvault/connector/exception/VaultConnectorException.java new file mode 100644 index 0000000..99fd74e --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/exception/VaultConnectorException.java @@ -0,0 +1,24 @@ +package de.stklcode.jvault.connector.exception; + +/** + * Abstract Exception class for Vault Connector internal exceptions. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public abstract class VaultConnectorException extends Exception { + public VaultConnectorException() { + } + + public VaultConnectorException(String message) { + super(message); + } + + public VaultConnectorException(Throwable cause) { + super(cause); + } + + public VaultConnectorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java b/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java new file mode 100644 index 0000000..4e6784e --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java @@ -0,0 +1,93 @@ +package de.stklcode.jvault.connector.factory; + +import de.stklcode.jvault.connector.HTTPVaultConnector; + +/** + * Vault Connector Factory implementation for HTTP Vault connectors. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public class HTTPVaultConnectorFactory extends VaultConnectorFactory { + public static final String DEFAULT_HOST = "127.0.0.1"; + public static final Integer DEFAULT_PORT = 8200; + public static final boolean DEFAULT_TLS = true; + public static final String DEFAULT_PREFIX = "/v1/"; + + private String host; + private Integer port; + private boolean tls; + private String prefix; + + /** + * Default empty constructor. + * Initializes factory with default values. + */ + public HTTPVaultConnectorFactory() { + host = DEFAULT_HOST; + port = DEFAULT_PORT; + tls = DEFAULT_TLS; + prefix = DEFAULT_PREFIX; + } + + /** + * Set hostname (default: 127.0.0.1) + * @param host Hostname or IP address + * @return self + */ + public HTTPVaultConnectorFactory withHost(String host) { + this.host = host; + return this; + } + + /** + * Set port (default: 8200) + * @param port Vault TCP port + * @return self + */ + public HTTPVaultConnectorFactory withPort(Integer port) { + this.port = port; + return this; + } + + /** + * Set TLS usage (default: TRUE) + * @param useTLS use TLS or not + * @return self + */ + public HTTPVaultConnectorFactory withTLS(boolean useTLS) { + this.tls = useTLS; + return this; + } + + /** + * Convenience Method for TLS usage (enabled by default) + * @return self + */ + public HTTPVaultConnectorFactory withTLS() { + return withTLS(true); + } + + /** + * Convenience Method for NOT using TLS + * @return self + */ + public HTTPVaultConnectorFactory withoutTLS() { + return withTLS(false); + } + + /** + * Set API prefix. Default is "/v1/" and changes should not be necessary for current state of development. + * @param prefix Vault API prefix (default: "/v1/" + * @return self + */ + public HTTPVaultConnectorFactory withPrefix(String prefix) { + this.prefix = prefix; + return this; + } + + @Override + public HTTPVaultConnector build() { + return new HTTPVaultConnector(host, tls, port, prefix); + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/factory/VaultConnectorFactory.java b/src/main/java/de/stklcode/jvault/connector/factory/VaultConnectorFactory.java new file mode 100644 index 0000000..a0aa8cb --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/factory/VaultConnectorFactory.java @@ -0,0 +1,26 @@ +package de.stklcode.jvault.connector.factory; + +import de.stklcode.jvault.connector.VaultConnector; + +/** + * Abstract Vault Connector Factory interface. + * Provides builder pattern style factory for Vault connectors. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public abstract class VaultConnectorFactory { + /** + * Get Factory implementation for HTTP Vault Connector + * @return HTTP Connector Factory + */ + public static HTTPVaultConnectorFactory httpFactory() { + return new HTTPVaultConnectorFactory(); + } + + /** + * Build command, produces connector after initialization. + * @return Vault Connector instance. + */ + public abstract VaultConnector build(); +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/AuthBackend.java b/src/main/java/de/stklcode/jvault/connector/model/AuthBackend.java new file mode 100644 index 0000000..839e4bc --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/AuthBackend.java @@ -0,0 +1,27 @@ +package de.stklcode.jvault.connector.model; + +/** + * Currently supported authentication backends. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public enum AuthBackend { + TOKEN("token"), + APPID("app-id"), + USERPASS("userpass"), + UNKNOWN(""); + + private final String type; + + AuthBackend(String type) { + this.type = type; + } + + public static AuthBackend forType(String type) { + for (AuthBackend v : values()) + if (v.type.equalsIgnoreCase(type)) + return v; + return UNKNOWN; + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/AuthMethodsResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/AuthMethodsResponse.java new file mode 100644 index 0000000..9f7b11a --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/AuthMethodsResponse.java @@ -0,0 +1,32 @@ +package de.stklcode.jvault.connector.model.response; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import de.stklcode.jvault.connector.exception.InvalidResponseException; +import de.stklcode.jvault.connector.model.response.embedded.AuthMethod; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Authentication method response. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public class AuthMethodsResponse implements VaultResponse { + + private List supportedMethods; + + @JsonAnySetter + public void setMethod(String path, Map data) throws InvalidResponseException { + if (supportedMethods == null) + supportedMethods = new ArrayList<>(); + + supportedMethods.add(new AuthMethod(path, data.get("description"), data.get("type"))); + } + + public List getSupportedMethods() { + return supportedMethods; + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/AuthResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/AuthResponse.java new file mode 100644 index 0000000..efb1af4 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/AuthResponse.java @@ -0,0 +1,47 @@ +package de.stklcode.jvault.connector.model.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.stklcode.jvault.connector.exception.InvalidResponseException; +import de.stklcode.jvault.connector.model.response.embedded.AuthData; + +import java.io.IOException; +import java.util.Map; + +/** + * Vault response for authentication providing auth info in {@link AuthData} field. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class AuthResponse extends VaultDataResponse { + private Map data; + + private AuthData auth; + + @JsonProperty("auth") + public void setAuth(Map auth) throws InvalidResponseException { + ObjectMapper mapper = new ObjectMapper(); + try { + this.auth = mapper.readValue(mapper.writeValueAsString(auth), AuthData.class); + } catch (IOException e) { + e.printStackTrace(); + throw new InvalidResponseException(); + } + } + + @Override + public void setData(Map data) { + this.data = data; + } + + public Map getData() { + return data; + } + + public AuthData getAuth() { + return auth; + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/ErrorResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/ErrorResponse.java new file mode 100644 index 0000000..8ece72c --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/ErrorResponse.java @@ -0,0 +1,22 @@ +package de.stklcode.jvault.connector.model.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * Vault response in case of errors. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ErrorResponse implements VaultResponse { + @JsonProperty("errors") + private List errors; + + public List getErrors() { + return errors; + } +} \ No newline at end of file diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/HelpResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/HelpResponse.java new file mode 100644 index 0000000..8ce4e63 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/HelpResponse.java @@ -0,0 +1,20 @@ +package de.stklcode.jvault.connector.model.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Vault response for help request. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class HelpResponse implements VaultResponse { + @JsonProperty("help") + private String help; + + public String getHelp() { + return help; + } +} \ No newline at end of file diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/SealResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/SealResponse.java new file mode 100644 index 0000000..28310e4 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/SealResponse.java @@ -0,0 +1,41 @@ +package de.stklcode.jvault.connector.model.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Vault response for seal status or unseal request. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class SealResponse implements VaultResponse { + @JsonProperty("sealed") + private boolean sealed; + + @JsonProperty("t") + private Integer threshold; + + @JsonProperty("n") + private Integer numberOfShares; + + @JsonProperty("progress") + private Integer progress; + + public boolean isSealed() { + return sealed; + } + + public Integer getThreshold() { + return threshold; + } + + public Integer getNumberOfShares() { + return numberOfShares; + } + + public Integer getProgress() { + return progress; + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/SecretListResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/SecretListResponse.java new file mode 100644 index 0000000..a93836d --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/SecretListResponse.java @@ -0,0 +1,33 @@ +package de.stklcode.jvault.connector.model.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import de.stklcode.jvault.connector.exception.InvalidResponseException; + +import java.util.List; +import java.util.Map; + +/** + * Vault response for secret list request. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecretListResponse extends VaultDataResponse { + private List keys; + + @JsonProperty("data") + public void setData(Map data) throws InvalidResponseException { + try { + this.keys = (List)data.get("keys"); + } + catch (ClassCastException e) { + throw new InvalidResponseException("Keys could not be parsed from data.", e); + } + } + + public List getKeys() { + return keys; + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/SecretResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/SecretResponse.java new file mode 100644 index 0000000..e05b023 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/SecretResponse.java @@ -0,0 +1,30 @@ +package de.stklcode.jvault.connector.model.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import de.stklcode.jvault.connector.exception.InvalidResponseException; + +import java.util.Map; + +/** + * Vault response for secret request. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecretResponse extends VaultDataResponse { + private String value; + + @Override + public void setData(Map data) throws InvalidResponseException { + try { + this.value = (String) data.get("value"); + } catch (ClassCastException e) { + throw new InvalidResponseException("Value could not be parsed", e); + } + } + + public String getValue() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/TokenResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/TokenResponse.java new file mode 100644 index 0000000..8ef9857 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/TokenResponse.java @@ -0,0 +1,40 @@ +package de.stklcode.jvault.connector.model.response; + + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.stklcode.jvault.connector.exception.InvalidResponseException; +import de.stklcode.jvault.connector.model.response.embedded.TokenData; + +import java.io.IOException; +import java.util.Map; + +/** + * Vault response from token lookup providing Token information in {@link TokenData} field. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class TokenResponse extends VaultDataResponse { + private TokenData data; + + @JsonProperty("auth") + private Boolean auth; + + @Override + public void setData(Map data) throws InvalidResponseException { + ObjectMapper mapper = new ObjectMapper(); + try { + this.data = mapper.readValue(mapper.writeValueAsString(data), TokenData.class); + } catch (IOException e) { + e.printStackTrace(); + throw new InvalidResponseException(); + } + } + + public TokenData getData() { + return data; + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/VaultDataResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/VaultDataResponse.java new file mode 100644 index 0000000..a0d7908 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/VaultDataResponse.java @@ -0,0 +1,46 @@ +package de.stklcode.jvault.connector.model.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import de.stklcode.jvault.connector.exception.InvalidResponseException; + +import java.util.List; +import java.util.Map; + +/** + * Abstract Vault response with default payload fields. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public abstract class VaultDataResponse implements VaultResponse { + @JsonProperty("lease_id") + private String leaseId; + + @JsonProperty("renewable") + private boolean renewable; + + @JsonProperty("lease_duration") + private Integer leaseDuration; + + @JsonProperty("warnings") + private List warnings; + + @JsonProperty("data") + public abstract void setData(Map data) throws InvalidResponseException; + + public String getLeaseId() { + return leaseId; + } + + public boolean isRenewable() { + return renewable; + } + + public Integer getLeaseDuration() { + return leaseDuration; + } + + public List getWarnings() { + return warnings; + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/VaultResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/VaultResponse.java new file mode 100644 index 0000000..9e83d25 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/VaultResponse.java @@ -0,0 +1,10 @@ +package de.stklcode.jvault.connector.model.response; + +/** + * Marker interface for responses from Vault backend. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public interface VaultResponse { +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthData.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthData.java new file mode 100644 index 0000000..8b03fb5 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthData.java @@ -0,0 +1,58 @@ +package de.stklcode.jvault.connector.model.response.embedded; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.Map; + +/** + * Embedded authorization information inside Vault response. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class AuthData { + @JsonProperty("client_token") + private String clientToken; + + @JsonProperty("accessor") + private String accessor; + + @JsonProperty("policies") + private List policies; + + @JsonProperty("metadata") + private Map metadata; + + @JsonProperty("lease_duration") + private Integer leaseDuration; + + @JsonProperty("renewable") + private boolean renewable; + + public String getClientToken() { + return clientToken; + } + + public String getAccessor() { + return accessor; + } + + public List getPolicies() { + return policies; + } + + public Map getMetadata() { + return metadata; + } + + public Integer getLeaseDuration() { + return leaseDuration; + } + + public boolean isRenewable() { + return renewable; + } +} \ No newline at end of file diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthMethod.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthMethod.java new file mode 100644 index 0000000..663dcb1 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthMethod.java @@ -0,0 +1,40 @@ +package de.stklcode.jvault.connector.model.response.embedded; + + +import de.stklcode.jvault.connector.model.AuthBackend; + +/** + * Embedded authentication method response. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public class AuthMethod { + private AuthBackend type; + private String rawType; + private String path; + private String description; + + public AuthMethod(String path, String description, String type) { + this.path = path; + this.description = description; + this.rawType = type; + this.type = AuthBackend.forType(type); + } + + public AuthBackend getType() { + return type; + } + + public String getRawType() { + return rawType; + } + + public String getPath() { + return path; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/TokenData.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/TokenData.java new file mode 100644 index 0000000..a90f5d0 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/TokenData.java @@ -0,0 +1,90 @@ +package de.stklcode.jvault.connector.model.response.embedded; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Embedded token information inside Vault response. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class TokenData { + @JsonProperty("accessor") + private String accessor; + + @JsonProperty("creation_time") + private Integer creationTime; + + @JsonProperty("creation_ttl") + private Integer creatinTtl; + + @JsonProperty("display_name") + private String name; + + @JsonProperty("id") + private String id; + + @JsonProperty("meta") + private String meta; + + @JsonProperty("num_uses") + private Integer numUses; + + @JsonProperty("orphan") + private boolean orphan; + + @JsonProperty("path") + private String path; + + @JsonProperty("role") + private String role; + + @JsonProperty("ttl") + private Integer ttl; + + public String getAccessor() { + return accessor; + } + + public Integer getCreationTime() { + return creationTime; + } + + public Integer getCreatinTtl() { + return creatinTtl; + } + + public String getName() { + return name; + } + + public String getId() { + return id; + } + + public Integer getNumUses() { + return numUses; + } + + public boolean isOrphan() { + return orphan; + } + + public String getPath() { + return path; + } + + public String getRole() { + return role; + } + + public Integer getTtl() { + return ttl; + } + + public String getMeta() { + return meta; + } +} \ No newline at end of file diff --git a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java new file mode 100644 index 0000000..9662b43 --- /dev/null +++ b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java @@ -0,0 +1,377 @@ +package de.stklcode.jvault.connector; + +import de.stklcode.jvault.connector.test.VaultConfiguration; +import de.stklcode.jvault.connector.exception.InvalidRequestException; +import de.stklcode.jvault.connector.exception.PermissionDeniedException; +import de.stklcode.jvault.connector.exception.VaultConnectorException; +import de.stklcode.jvault.connector.factory.VaultConnectorFactory; +import de.stklcode.jvault.connector.model.AuthBackend; +import de.stklcode.jvault.connector.model.response.AuthResponse; +import de.stklcode.jvault.connector.model.response.SealResponse; +import de.stklcode.jvault.connector.model.response.SecretResponse; +import de.stklcode.jvault.connector.model.response.TokenResponse; +import org.junit.*; +import org.junit.rules.TemporaryFolder; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.ServerSocket; +import java.util.List; + +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.*; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeNotNull; +import static org.junit.Assume.assumeTrue; + +/** + * JUnit Test for HTTP Vault connector. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public class HTTPVaultConnectorTest { + private static String KEY = "81011a8061e5c028bd0d9503eeba40bd9054b9af0408d080cb24f57405c27a61"; + private static String TOKEN_ROOT = "d1bd50e2-587b-6e68-d80b-a9a507625cb7"; + private static String USER_VALID = "validUser"; + private static String PASS_VALID = "validPass"; + private static String APP_ID = "152AEA38-85FB-47A8-9CBD-612D645BFACA"; + private static String USER_ID = "5ADF8218-D7FB-4089-9E38-287465DBF37E"; + private static String SECRET_PATH = "userstore"; + private static String SECRET_KEY = "foo"; + private static String SECRET_VALUE = "bar"; + + private Process vaultProcess; + private VaultConnector connector; + + @Rule + public TemporaryFolder tmpDir = new TemporaryFolder(); + + /** + * Initialize Vault instance with generated configuration and provided file backend. + * Requires "vault" binary to be in current user's executable path. Not using MLock, so no extended rights required. + */ + @Before + public void setUp() { + /* Initialize Vault */ + VaultConfiguration config = initializeVault(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + /* Initialize connector */ + connector = VaultConnectorFactory.httpFactory() + .withHost(config.getHost()) + .withPort(config.getPort()) + .withoutTLS() + .build(); + /* Unseal Vault and check result */ + SealResponse sealStatus = connector.unseal(KEY); + assumeNotNull(sealStatus); + assumeFalse(sealStatus.isSealed()); + } + + @After + public void tearDown() { + if (vaultProcess != null && vaultProcess.isAlive()) + vaultProcess.destroy(); + } + + /** + * Test listing of authentication backends + */ + @Test + public void authMethodsTest() { + /* Authenticate as valid user */ + try { + connector.authToken(TOKEN_ROOT); + } + catch(VaultConnectorException ignored) { + } + assumeTrue(connector.isAuthorized()); + + List supportedBackends = null; + try { + supportedBackends = connector.getAuthBackends(); + } catch (VaultConnectorException e) { + fail("Could not list supported auth backends: " + e.getMessage()); + } + assertThat(supportedBackends.size(), is(3)); + assertThat(supportedBackends, hasItems(AuthBackend.TOKEN, AuthBackend.USERPASS, AuthBackend.APPID)); + } + + /** + * Test authentication using token. + */ + @Test + public void authTokenTest() { + TokenResponse res = null; + try { + res = connector.authToken("52135869df23a5e64c5d33a9785af5edb456b8a4a235d1fe135e6fba1c35edf6"); + fail("Logged in with invalid token"); + } catch (VaultConnectorException ignored) { + } + + try { + res = connector.authToken(TOKEN_ROOT); + assertNotNull("Login failed with valid token", res); + assertThat("Login failed with valid token", connector.isAuthorized(), is(true)); + } catch (VaultConnectorException ignored) { + fail("Login failed with valid token"); + } + } + + /** + * Test authentication using username and password. + */ + @Test + public void authUserPassTest() { + AuthResponse res = null; + try { + connector.authUserPass("foo", "bar"); + fail("Logged in with invalid credentials"); + } + catch(VaultConnectorException ignored) { + } + + try { + res = connector.authUserPass(USER_VALID, PASS_VALID); + } catch (VaultConnectorException ignored) { + fail("Login failed with valid credentials: Exception thrown"); + } + assertNotNull("Login failed with valid credentials: Response not available", res.getAuth()); + assertThat("Login failed with valid credentials: Connector not authorized", connector.isAuthorized(), is(true)); + } + + /** + * App-ID authentication roundtrip. + */ + @Test + public void authAppIdTest() { + authRoot(); + assumeTrue(connector.isAuthorized()); + + /* Register App-ID */ + try { + boolean res = connector.registerAppId(APP_ID, "user", "App Name"); + assertThat("Failed to register App-ID", res, is(true)); + } + catch (VaultConnectorException e) { + fail("Failed to register App-ID: " + e.getMessage()); + } + + /* Register User-ID */ + try { + boolean res = connector.registerUserId(APP_ID, USER_ID); + assertThat("Failed to register App-ID", res, is(true)); + } + catch (VaultConnectorException e) { + fail("Failed to register App-ID: " + e.getMessage()); + } + + connector.resetAuth(); + assumeFalse(connector.isAuthorized()); + + /* Authenticate with created credentials */ + try { + AuthResponse res = connector.authAppId(APP_ID, USER_ID); + assertThat("Authorization flag not set after App-ID login.", connector.isAuthorized(), is(true)); + } catch (VaultConnectorException e) { + fail("Failed to authenticate using App-ID: " + e.getMessage()); + } + } + + /** + * Test reading of secrets. + */ + @Test + public void readSecretTest() { + authUser(); + assumeTrue(connector.isAuthorized()); + + /* Try to read path user has no permission to read */ + SecretResponse res = null; + try { + res = connector.readSecret("invalid/path"); + fail("Invalid secret path successfully read."); + } catch (VaultConnectorException e) { + assertThat(e, instanceOf(PermissionDeniedException.class)); + } + /* Try to read accessible path with known value */ + try { + res = connector.readSecret(SECRET_PATH + "/" + SECRET_KEY); + assertThat("Known secret returned invalid value.", res.getValue(), is(SECRET_VALUE)); + } catch (VaultConnectorException e) { + fail("Valid secret path could not be read: " + e.getMessage()); + } + } + + /** + * Test listing secrets. + */ + @Test + public void listSecretsTest() { + authRoot(); + assumeTrue(connector.isAuthorized()); + /* Try to list secrets from valid path */ + try { + List secrets = connector.listSecrets(SECRET_PATH); + assertThat("Invalid nmber of secrets.", secrets.size(), greaterThan(0)); + assertThat("Known secret key not found", secrets, hasItem(SECRET_KEY)); + } catch (VaultConnectorException e) { + fail("Secrets could not be listed: " + e.getMessage()); + } + } + + /** + * Test writing secrets. + */ + @Test + public void writeSecretTest() { + authUser(); + assumeTrue(connector.isAuthorized()); + + /* Try to write to null path */ + try { + boolean res = connector.writeSecret(null, "someValue"); + fail("Secret written to null path."); + } catch (VaultConnectorException e) { + assertThat(e, instanceOf(InvalidRequestException.class)); + } + /* Try to write to invalid path */ + try { + boolean res = connector.writeSecret("", "someValue"); + fail("Secret written to invalid path."); + } catch (VaultConnectorException e) { + assertThat(e, instanceOf(InvalidRequestException.class)); + } + /* Try to write to a path the user has no access for */ + try { + boolean res = connector.writeSecret("invalid/path", "someValue"); + fail("Secret written to inaccessible path."); + } catch (VaultConnectorException e) { + assertThat(e, instanceOf(PermissionDeniedException.class)); + } + /* Perform a valid write/read roundtrip to valid path. Also check UTF8-encoding. */ + try { + boolean res = connector.writeSecret(SECRET_PATH + "/temp", "Abc123äöü,!"); + assertThat("Secret could not be written to valid path.", res, is(true)); + } catch (VaultConnectorException e) { + fail("Secret written to inaccessible path."); + } + try { + SecretResponse res = connector.readSecret(SECRET_PATH + "/temp"); + assertThat(res.getValue(), is("Abc123äöü,!")); + } catch (VaultConnectorException e) { + fail("Written secret could not be read."); + } + } + + /** + * Initialize Vault with resource datastore and generated configuration. + * @return Vault Configuration + * @throws IllegalStateException + */ + private VaultConfiguration initializeVault() throws IllegalStateException { + String dataResource = getClass().getResource("/data_dir").getPath(); + + /* Generate vault local unencrypted configuration */ + VaultConfiguration config = new VaultConfiguration() + .withHost("127.0.0.1") + .withPort(getFreePort()) + .withDataLocation(dataResource) + .disableMlock(); + + /* Write configuration file */ + BufferedWriter bw = null; + File configFIle = null; + try { + configFIle = tmpDir.newFile("vault.conf"); + bw = new BufferedWriter(new FileWriter(configFIle)); + bw.write(config.toString()); + } + catch (IOException e) { + e.printStackTrace(); + throw new IllegalStateException("Unable to generate config file."); + } + finally { + try { + if (bw != null) + bw.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + /* Start vault process */ + try { + vaultProcess = Runtime.getRuntime().exec("vault server -config " + configFIle.toString()); + } catch (IOException e) { + e.printStackTrace(); + throw new IllegalStateException("Unable to start vault. Make sure vault binary is in your executable path."); + } + + return config; + } + + /** + * Authenticate with root token. + */ + private void authRoot() { + /* Authenticate as valid user */ + try { + connector.authToken(TOKEN_ROOT); + } + catch(VaultConnectorException ignored) { + } + } + + /** + * Authenticate with user credentials. + */ + private void authUser() { + try { + connector.authUserPass(USER_VALID, PASS_VALID); + } + catch(VaultConnectorException ignored) { + } + } + + /** + * Find and return a free TCP port. + * @return port number + */ + private static Integer getFreePort() { + ServerSocket socket = null; + try { + socket = new ServerSocket(0); + socket.setReuseAddress(true); + int port = socket.getLocalPort(); + try { + socket.close(); + } catch (IOException e) { + // Ignore IOException on close() + } + return port; + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + throw new IllegalStateException("Unable to find a free TCP port."); + } +} diff --git a/src/test/java/de/stklcode/jvault/connector/test/VaultConfiguration.java b/src/test/java/de/stklcode/jvault/connector/test/VaultConfiguration.java new file mode 100644 index 0000000..74c07e3 --- /dev/null +++ b/src/test/java/de/stklcode/jvault/connector/test/VaultConfiguration.java @@ -0,0 +1,76 @@ +package de.stklcode.jvault.connector.test; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Vault configuration String using builder pattern. + * + * @author Stefan Kalscheuer + * @since 0.1 + */ +public class VaultConfiguration { + private String host; + private Integer port; + private boolean disableTLS; + private boolean disableMlock; + private Path dataLocation; + + public VaultConfiguration() { + this.disableTLS = true; + this.disableMlock = false; + } + + public VaultConfiguration withHost(String host) { + this.host = host; + return this; + } + + public VaultConfiguration withPort(Integer port) { + this.port = port; + return this; + } + + public VaultConfiguration enableTLS() { + this.disableTLS = false; + return this; + } + + public VaultConfiguration disableMlock() { + this.disableMlock = true; + return this; + } + + public VaultConfiguration withDataLocation(String dataLocation) { + return withDataLocation(Paths.get(dataLocation)); + } + + public VaultConfiguration withDataLocation(Path dataLocation) { + this.dataLocation = dataLocation; + return this; + } + + public String getHost() { + return host; + } + + public Integer getPort() { + return port; + } + + public boolean isTLS() { + return !disableTLS; + } + + @Override + public String toString() { + return "backend \"file\" {\n" + + " path = \"" + dataLocation + "\"\n" + + "}\n" + + "listener \"tcp\" {\n" + + " address = \"" + host + ":" + port + "\"\n" + + ((disableTLS) ? " tls_disable = 1\n" : "") + + "}\n" + + ((disableMlock) ? "disable_mlock = true" : ""); + } +} \ No newline at end of file diff --git a/src/test/resources/data_dir/auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/_salt b/src/test/resources/data_dir/auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/_salt new file mode 100644 index 0000000..adc268f --- /dev/null +++ b/src/test/resources/data_dir/auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/_salt @@ -0,0 +1 @@ +{"Key":"auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/salt","Value":"AAAAAQJUsuXXEpmdNY5aIh5HdzZRTFpOUIgyKLGiw65DBwSXW6yGAYe/zhN/Ow+vyRZxG4temgnTjN7RVGjyzXGG5yLY"} diff --git a/src/test/resources/data_dir/auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/app-id/_19d90b9adcec2bf5088304034622a169a148ff43 b/src/test/resources/data_dir/auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/app-id/_19d90b9adcec2bf5088304034622a169a148ff43 new file mode 100644 index 0000000..881c3c1 --- /dev/null +++ b/src/test/resources/data_dir/auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/app-id/_19d90b9adcec2bf5088304034622a169a148ff43 @@ -0,0 +1 @@ +{"Key":"auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/app-id/19d90b9adcec2bf5088304034622a169a148ff43","Value":"AAAAAQJuuRcCRinyawQ05brruZQY7ypgs1mOsFHI16XLwYB4dzwJob71wW+74RjvK4FVL4qPfgyMPKEtV2uO9+4hr2mC6BrcN///Ksxv+ns8FMVlBOMJpQ=="} diff --git a/src/test/resources/data_dir/auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/user-id/_55a852babe045b5980fc8ac4a13af27021dbbfd4 b/src/test/resources/data_dir/auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/user-id/_55a852babe045b5980fc8ac4a13af27021dbbfd4 new file mode 100644 index 0000000..6124b22 --- /dev/null +++ b/src/test/resources/data_dir/auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/user-id/_55a852babe045b5980fc8ac4a13af27021dbbfd4 @@ -0,0 +1 @@ +{"Key":"auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/user-id/55a852babe045b5980fc8ac4a13af27021dbbfd4","Value":"AAAAAQICaFIxG2xAq0AuJryVn1XghDulkVdQicXvhEL45K2S48aZcvMEsrDUXm9o427Bp6eMiq0Hw070nosnB9SWSQJEFUfPmM6I7Jhsou6CKmocs/AmocxY3Du4Lg=="} diff --git a/src/test/resources/data_dir/auth/6802ec63-11b0-0ccc-280a-982ad0a90621/user/_validuser b/src/test/resources/data_dir/auth/6802ec63-11b0-0ccc-280a-982ad0a90621/user/_validuser new file mode 100644 index 0000000..bbe5161 --- /dev/null +++ b/src/test/resources/data_dir/auth/6802ec63-11b0-0ccc-280a-982ad0a90621/user/_validuser @@ -0,0 +1 @@ +{"Key":"auth/6802ec63-11b0-0ccc-280a-982ad0a90621/user/validuser","Value":"AAAAAQJwFKMpgopAFjJaftTVY/iiawMw4Yj0S3pPDkzMPAfLxxaM3sCjOJt0q/07ozjTharT52wBv+s2ZEurPpr7VKDDzgy4xTMxFJbJs+0VkG3cjxRYEfW3bOIVAHhjLjmxZwYEATh0UUG7bQRNt56+/622bwR99ifWZ6e9zyRDGEwIn74JFN/3dY44qLQZmqfvDUrRQP5RfqDxqVdzbwse61s692Vy/QvlPsRFVRTkZHlNPqxT+OXd"} diff --git a/src/test/resources/data_dir/core/_audit b/src/test/resources/data_dir/core/_audit new file mode 100644 index 0000000..9850d12 --- /dev/null +++ b/src/test/resources/data_dir/core/_audit @@ -0,0 +1 @@ +{"Key":"core/audit","Value":"AAAAAQJ+0lfxYOKIXzquksd3Il4zfW4ja6BdScu7mCAijGDph63S5yWH92olwI2SQA=="} diff --git a/src/test/resources/data_dir/core/_auth b/src/test/resources/data_dir/core/_auth new file mode 100644 index 0000000..c45fcdc --- /dev/null +++ b/src/test/resources/data_dir/core/_auth @@ -0,0 +1 @@ +{"Key":"core/auth","Value":"AAAAAQJeg+aK83bfYUPm5KV/+Y1Cf8fVX8Oq7cFiYE3PN4Tcl2j9YD7c+fijkopFzPLxh/EBRVzGTjBBkWBDTLC4EKn2lU8A+NJK6frGOu5Vjc4+WipBUcsGGcfosvxrZk8ZrTt09PRo7CnAFzmH4CgIVuEFv3NZyjXX08EMakxmH6R4S8Ti3cmit5Kr1TKSGnHeMfO6lBnAobAfNlfgNDM90yAxaQUT9gR27TabSKsVvkIY/0oxIcVFQ1RuF7xcdAOJ82wlWkcfT+rMhSS6roGt9Njx9t0PQNjv2eomh/leuhTUzfu4oAbtJJO3IPuC/KkJZpKinjJaGlA5Kx5m7W09DHieyI0GXmjJXH9oCj0z9w6eKDNZmjC1xeFylBvkvnkAKA6QmUmZjoq+m81/UC/C/ShJYxDFxaHwCPgF92qSrT1VTrz5lYAyyjJmrnbyuUXfgG6d4dbDxT/DVddHtIzHbBBT0xxjNM/+WxOSdRYZXKsfm8ovGaFWJr9AyFfEnyS0E9FJvkEXp68eNdacgdM3Ow5Zchm2/I9F4wN577ZwK9oGO2kjkDv4pwKQJ6uDYXeJDZxeMO2rXk/9sY8rAV20loOnqryfO3IE876H97KyE1LEzrky7mVw3YL/CDlZCO5Sf2IHTmwKylTRlsnUO1I5bviFD3a3EdeIGeDuNbWIqiju2fpMQX4l2gUuY4pysBM28AwbTe3zp348PePFpTcGE0/YwsW/aJit3VOc6xNX9WU="} diff --git a/src/test/resources/data_dir/core/_keyring b/src/test/resources/data_dir/core/_keyring new file mode 100644 index 0000000..93c367a --- /dev/null +++ b/src/test/resources/data_dir/core/_keyring @@ -0,0 +1 @@ +{"Key":"core/keyring","Value":"AAAAAQKCYFhCtIpVCVfA/MaX9/fCtNJB5z1z+tsi6VQzlDxo9dUxSsZC3ppnZZ9TYwRj6TUeP8QqyNDDhwiXp/i/WhAeoMV9UDKHQq7Ay4BO+AOz3VpHTX1ZYFrF0ZrYELqyFrOkhETpSpNzD6rxcwoJD9NuC+oQhR4TsMEXNzX3AqMKJE+b8iTbEl3tRZRQ5qgjIIy835JDTI3JoSXUxJAxYAbZLxunx2TF6fXs1urpY6GfCrvb/bidzBsfOT37y9Fok8TnOo8a1laqJpsdL6yOQnHj+ZmJVG+Pj5QLETg2L8hBikIMKA=="} diff --git a/src/test/resources/data_dir/core/_master b/src/test/resources/data_dir/core/_master new file mode 100644 index 0000000..60ed761 --- /dev/null +++ b/src/test/resources/data_dir/core/_master @@ -0,0 +1 @@ +{"Key":"core/master","Value":"AAAAAQK8dxOynwwVLtj8fqeAPBSmo/cbdJBQgQt84CDEuYd3JMLLz3bRiP8G2rQ8mdaP7VVQjJyaWgG5AIFiyjswnYiOWWFIpFn7xPUr5Og1Pd0jTB5mCGEBSdoVLggt21JC4Rp7ceFlO8fNoc1q6h+IZI8ZMn8MPbqpMALNSqKhpOc5xfh6YkgL3XphWnbM5Gzc"} diff --git a/src/test/resources/data_dir/core/_mounts b/src/test/resources/data_dir/core/_mounts new file mode 100644 index 0000000..9895ebe --- /dev/null +++ b/src/test/resources/data_dir/core/_mounts @@ -0,0 +1 @@ +{"Key":"core/mounts","Value":"AAAAAQLyZ1sJsIcAqhtCBFotH08os+J6UEjDnte1gzUp8md8RNH2LFTBe+8iwqSjgpJCyQPYXxDb7l5S4YG8ypUAk5yZA/CaC5y2idWXBrvzihCdDgOv+xLB17mGRuIJdPSdG2TlPY/HJBkadAPOzzD+qFHJKOeH+YOVwBQQP8GA9uLm3DZNHOSej59Y8wN7R9hJX1aXBnkqFhpLdARXqrlSI8SiWz3TNA2vmm4cCzxGcmKEUF3m6peBr47TTnZtcciGI0vd7yIbMeblF93HcrNMcQnKB4ItR8a+yO4WZHg/Y1iWn0JQB0y0FSYg+OFFolsMQpQDDd+YTUpj8zJ2pmlRZ9n19WBHD2VbkzkCyR214+NzCpOfCUsguYdGR34trwU6/4W5ApxtY/+T35vQ+esztU2HgQqJES6s8708Nn589Kvzg/+etbvhfq/DmWOcwZOtb2X/EQHaDmHWzsWqQ26Ux32WiAjedVwoba6vT23MIgUBjdHIKuTlrV6JqNVeuevfmfpBW8y/EhcAm6e2s8jTkdoaSPvq2NaBT9HF6FWJNXhVxAXQlOp7ibx5nk7l312umi2hbZ0+2Ad2wTGCVsECa26ZqXzBbU3uNni4sNdqbTq0i7unpqEQnyfTGcHkxe1uIbT2sK/AydtaOV8IDN4Z0HUBX9tj2UpQr8LX+JW9wX3MIXxQVfogyi/AmQRiDYuPeJyvvTTininGOHLSQyl/jRO/A3SdrTmppcwyNmTS1tzpwDawCP9yLWcw0/QVYgd8wMgmVjKsGuxJqWtEibnEnP9oU8Epi1Zy0OPXLUAT/06R9xtIMRReS2+VhBrFGmkD6eVL6EX0OOZ3yA6CxIft7cHQNishwIGL"} diff --git a/src/test/resources/data_dir/core/_seal-config b/src/test/resources/data_dir/core/_seal-config new file mode 100644 index 0000000..bbec772 --- /dev/null +++ b/src/test/resources/data_dir/core/_seal-config @@ -0,0 +1 @@ +{"Key":"core/seal-config","Value":"eyJzZWNyZXRfc2hhcmVzIjoxLCJwZ3Bfa2V5cyI6bnVsbCwic2VjcmV0X3RocmVzaG9sZCI6MSwibm9uY2UiOiIiLCJiYWNrdXAiOmZhbHNlfQ=="} diff --git a/src/test/resources/data_dir/logical/b85d867d-74d1-7d84-7a97-4597d813a5fb/userstore/_foo b/src/test/resources/data_dir/logical/b85d867d-74d1-7d84-7a97-4597d813a5fb/userstore/_foo new file mode 100644 index 0000000..6b07505 --- /dev/null +++ b/src/test/resources/data_dir/logical/b85d867d-74d1-7d84-7a97-4597d813a5fb/userstore/_foo @@ -0,0 +1 @@ +{"Key":"logical/b85d867d-74d1-7d84-7a97-4597d813a5fb/userstore/foo","Value":"AAAAAQKEvkR4E7cn/rKtmyhFT75qQ/hMRUwoKNmlfFar6/sxK3icAAYGWMVq8brp"} diff --git a/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_299de173bcf8d6ff55f53e9e947006d8c2c88878 b/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_299de173bcf8d6ff55f53e9e947006d8c2c88878 new file mode 100644 index 0000000..59a163e --- /dev/null +++ b/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_299de173bcf8d6ff55f53e9e947006d8c2c88878 @@ -0,0 +1 @@ +{"Key":"sys/expire/id/auth/userpass/login/validUser/299de173bcf8d6ff55f53e9e947006d8c2c88878","Value":"AAAAAQLaQfkPOOnH3B/DFOXThAGuJNNK5uLCkHM0DOI59wkVISOOKtZJ3tK88S9H2Ce1mQbTr4jJaouYB2L5CWhqju1DZxwk7ji0rTnHZHEIGgCr3fbJFD3rjgcearrJGkvnfpKnyLaV+QZLN59fPga/8KJktvZ+frAG0IOmquVN9G1gGnUcEzbWu7vr49hZWeVAK1lVYVtUDpd44OCBa1A5qHf0in+hvP2egFXqyb0nWBElKUS0CEMjiZrrmKxr0nOU/quMAROPyNEArioJGTE0OYIEcJ+J3oMqSfWokCKuRBQ1vMC8oSMfTva0nnxNVQTAKq997SjCH/XooKxehdQjisndlRH/YaRsWpk+iRfFNmiSpYarQNb7vlDOlm9YLqqE/paQAFe7NQHY1RbOMzT6WJZy2lZ2L2PiR67udhJDHQMMmulqaYuquaT6CDDJzdf9XdJcPPjyceCFCbRnJ7PgvpliKd7saai/EDGSEqK9Cb5YNoPh6ISMsRnZcYGGtpbmHCODVfhYqpT5Fh3X6h1gIs/1dgFD6+n0+eq9V8fjLXMIUEEJ+7Q3+igR78bNaJ6hxJnLoxJL+du2cVVTB76o6iwoiuG6XqipSI/bm8QbxYxhLSmynLBWJopPlgI+hXrLIgOLn66CGurCyIulYbn8hFF1k+XDbZ0siFXHl9iibLRjXHb02d+Fe1mRbODilJKYMBfTkpIdC35kfZiBkMSZNzAoxaemrbYGNV74JFniJ6ElGyUAbXN7ODxZ0zU5Vv9zOwp/LDLDva2vhoxNm7YGlVhSIOcKOgUhm1cfUw+bMCfbtYWxVSNt2R2cfbFGjsKTvba/TCrecTywgo9MC/Pwd2ZnrzDz"} diff --git a/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_2e9403fa105622cab037830348fb1dc2c309ac58 b/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_2e9403fa105622cab037830348fb1dc2c309ac58 new file mode 100644 index 0000000..54800c9 --- /dev/null +++ b/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_2e9403fa105622cab037830348fb1dc2c309ac58 @@ -0,0 +1 @@ +{"Key":"sys/expire/id/auth/userpass/login/validUser/2e9403fa105622cab037830348fb1dc2c309ac58","Value":"AAAAAQKShT84HucEAfQKWdl99gAflfpJ6L4OUDIpHL8eTaY8aVBBc9Uyp6Ee9kU5Ej9yJ1TEeJh3fhDQrrAs16tXsgM6R9VbEduvuDqSOleNt4254Zo5WVycQw4qRtbE/JStN0nuGP2zmjPMHwfrOrweEfIHiXwP2fLejGFLq9+AbTqgb/InhRBMSqd9rzSzHpl3RAqqTiwjkArh7Hw57PCCSgOZSOYAeBTqxKiTbwcX7sYjs0AsDR3FnYfb55Ok+aryU2i3j27yX+DGqz7ClAyiX53Q2DHoYdaypcQ08rskCE7Z3tS3X27fMNdYTUzWGyJRCKp5qfYf6RsfOUvtBRj28B4bBIYj1gnRPkThuZq5E5TYESSFQ0j/YauXZFzO/MZifnaHfP7VerBKOZQ2Db6Srz8Vz7LfefWCj9L2sDCbtmMSaH/+FNIxQthbK++/rzrX5K2BlkMn7jwYIe6251Ke8eFdOyPwbXhfui2xiiH8mcZvHs8lHAufgUZKk9okT4QqLkEusz7LmfergjIURIwqz8uBfEi/3SSA783hCnuHeRu7WTylbMUoyS5zge8bhwZ5YpP2Byp5Szbf24uvve3CCt7n4/7stW3PHNXuJk6+x1GU4jnz2RIeBatlsDoJtxUkIZGcDOzocgCc5PtEPFl0a4sh+pfG0WfkcWr9/39+A+rqeTj6KpXDtE3T9B48IvPH0UzhcNXFeQE4RBSIZcPrIex+bxuL3zBnkJiURlDsgXqb9wWRHhd5BtEVTF8qzcWLZYZ0TNs8VR8vux0Onegt1s0BcMkyxtjWHV+KJXgmuk6UDyrBehzXoJX4tsTKBPeU/KdZn87KGkLn5hR66Oojk5LRLvbM"} diff --git a/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_87656f27093d2d77837196faca0e4698c52bbd72 b/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_87656f27093d2d77837196faca0e4698c52bbd72 new file mode 100644 index 0000000..8ec3356 --- /dev/null +++ b/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_87656f27093d2d77837196faca0e4698c52bbd72 @@ -0,0 +1 @@ +{"Key":"sys/expire/id/auth/userpass/login/validUser/87656f27093d2d77837196faca0e4698c52bbd72","Value":"AAAAAQKiDmFCGjfaOmh1VNoL99Rqf2L5ZG2zI9CweA6G3Ytd/hHDGB9m5wYOIeBvh2oaZmqdGT3/LSpvAcqJ20NQGhF0rQBa+hxI95lQZ0HFI9b0pJgVZtiJfZAcci5PCb/V4mDytXfwH9/+y4rtmFRTqKww1OatW4cKNIEqQDQTiWzRlstDS+wRaiZyDU0yQmhVNzjjqagGeV+02JwIgnHm1bS6YgNrmLKvjEMYwYAFIf0dFEgf1CD0xr8V/lHaD6/EmxijncqqccQC97CU3RlQAho974zwUNITfRrM9ko1lsm4yUpI6d9/SrZGbpBkK76fW1IwqhU4elMYq07jXPseuE5LXH9hoZu5taaWhUGkwIJgpkBa+aZI3fHdBt7NQBC7YAOShixctpnzEdkXV510q+y41g//LupQkY98bDz5e8seIu59mwuH4TFl6UmcVUNtOiAxIE3/ACd60ycY2diksY0k7FZMO/e0LasO6uNi8ZsrmlYPOhoyVljW5ppsJVPemFW0IXcVrHRht81c2xHhmaTydQFVeuJL9snp1DM7sbDMcTHov7uYQP/ocdXDSTo5+DT7KeHmHH5jjDttX5iAGL8afh2vxmnipNR2uvFF6Rfl/q3Lam0MYRIBe8YdKfTuY4q3lJ4xzljYJlDySWat/mcAKLb+hbGnHXUFStYW4Cucz5BBGYn8dUY7kVV+z75RoJH/Oxv+GoOrXBimIA8nfR0hA9N5iGAubKJxw81RZRRbBKrnkuQ6dLWy+CLeoEEj7JmiJI+rdzET21bQpS+NVsIFJsXsqcEq6Lpqd0PL7auYf1OdvYNPXv4KTq8/yTiqzKhSVE/0YodNGiBmmtD6ChKKZmKv"} diff --git a/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_907c609e9d43718c2d983e1b6fbead2d73f9b77b b/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_907c609e9d43718c2d983e1b6fbead2d73f9b77b new file mode 100644 index 0000000..f103f35 --- /dev/null +++ b/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_907c609e9d43718c2d983e1b6fbead2d73f9b77b @@ -0,0 +1 @@ +{"Key":"sys/expire/id/auth/userpass/login/validUser/907c609e9d43718c2d983e1b6fbead2d73f9b77b","Value":"AAAAAQIVNhCvqI99YcDltmevVyBRkRw/W5EtkaJNll99ejxuGdyjhMLxytd+QoyTbEFwo09FTmdxfAI8z9qS1FJO7uuDxVyxDMlZYhQ1BVJh/HUP0jNdbvo3ko6NQ/DjYAJ+Z4JAJr/ap3YQg0p/wq1hhJy7m3Ea8slnOAf+RscQtgjW4ucLV20wCMcSEnASvVzbi1OjMMQvS9lyduIuI6R1HnMSBQDLizZVSG0karp7jhWAbLiHmAiE7FWofIKUR3NbTUOMODQsHS4xsN+wDlKsfiY4vM5eKFby1UFymWganzL1W6SUrEMPBc3O5ZI2BE1Z4zYJJl4Sr8deZYMQorb7qCzCI9M6U0B0PsqBGn0eU49hr+1GUCi4gBHZOF6QNxkwWMPMxOML9MX3jWTiD7r97bGQjQQJ1mEZ0UcwlkVJZ3piYaiUnsVkBaKwYCDylolX9k1EgMVeOi8YQvMunW+oNxcdxbNCRx5bXZmRfHWA+/1w0Klk+R/eWODtI86Azv6CscHWw7mjR7kobH8+hNq2X379DgmyFVWLSt+Gyc4G6sKinps19cOEvZP+XmXXPdnysfI/DYg4g98aMKVMlt9HUhoCAnWs+WT2LdG8fQK8G1mE3w2PD665ZQVikpNJCK9nfOOMjwVZGFe9/jWtjfxqJfimK9WjeAJJ5IBcreSrEdGXo+lKgGjcGiTZ6OD11+dvPShntufxCRt5t1XrZN8swAN5695hYa19NPesJRfKf7FFstOxMi1VNdZvdVmZiw8pOpHJWhp68JRXlloDySQ1echJVh3SFMAf7v4zy+Sw2EoHuEV982w6xA5hyNECa2NRT+HvNfaOWSU/HNaaC3rkr86mFQUa"} diff --git a/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_a5ecc2e5630b7e232b9c8744a0be6441ffb43229 b/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_a5ecc2e5630b7e232b9c8744a0be6441ffb43229 new file mode 100644 index 0000000..4a844bf --- /dev/null +++ b/src/test/resources/data_dir/sys/expire/id/auth/userpass/login/validUser/_a5ecc2e5630b7e232b9c8744a0be6441ffb43229 @@ -0,0 +1 @@ +{"Key":"sys/expire/id/auth/userpass/login/validUser/a5ecc2e5630b7e232b9c8744a0be6441ffb43229","Value":"AAAAAQIENn0L1HL6To2REZnua3OsYnV9HdBkhN478rYY/FRhVGcfmyrRA+iy2FwQXbHoEMgaza6cwr4QPM6EiMNZdDubPMO+NHhBi8NgWTR+bwk7nIs5rNab4fdth9ROsjz9W1asbGORJD4l9rcbZ7B0lqh8A8X01YildprPnuDCtOu8wsrSCfE7eJwNaB+qYWNkEYgzEVWuwMqBZ3vfjUXOK9vyQTd933e3OlrIFBZukJ9QXlTyuma5fq+RcCcvQ359IgFB1wMNSMO2yeEK2yInd1ZZyM/+d4px0PlEh+B95D/gH4akJQ1BvGErvKf87F8J2ROdWxA76zHUXkr6BuZdW6p8Y67UuhHUOVM7DZks2VRB3xHzZjszuHeOCvfhM6mXSU01SP95wUE7cSonYM6wGXAFA+CWhpMRs4py4404JHMUo2tUaLiZLrspW6ZuGHBv/szJ94gdQQF998BsnhtqBzWBRrISrdErcUVtKeTj01PxOKbZgzeuvNMfRwzlbwN9APmI+ub8gSWTjs78w7eMnB78Xpabu6+2WMBELWTg+TmoEqayvpoPbl/AYNB9pJc0sGqm9fnNxVp2mLXQYKomaxOpG9lIv5lb7QzcZgqSpzaZ4Wcg568BXrd30pHUtEujA/za9f0/ywEdtw9eON8tj92IGgcrvkcSQ4+3zbHu3hWDUid2kiuSPxA36hrf7F6xuLQQWtO9e5knQ9sfPwAh8blMBdihjqLw3Mi/jwRLtxD6fySMn+qOFUwUvTfFTdJLgYfqgGej2CS8+x3zrs3fewaL06CqA89QsNfPsAH24ZcSZ0GAOOlgpp1P538cu50OAgdJlw++C0k8i4B4HlgVigtz5xGC"} diff --git a/src/test/resources/data_dir/sys/policy/_default b/src/test/resources/data_dir/sys/policy/_default new file mode 100644 index 0000000..2ddf08a --- /dev/null +++ b/src/test/resources/data_dir/sys/policy/_default @@ -0,0 +1 @@ +{"Key":"sys/policy/default","Value":"AAAAAQLsSCMLrL6OyG1Sqtmatk8G6JI634tmLqR+kai/22iklR6iAGI0xel8Sgo6MuOZPYFqM36J7dlcTduFb1KvNDlYVh77jv1zeqHm1Bv5Jq8S4Ndj1dWm+xTL/Eet9Ay9jccnP2h71V7pP9Fdnjsz9+Dm7mR+9+FRq5YPI/IeFUq8xy8fKsaMprUEBJaTOj0h9PFOxtxUnzAwrdSHixETrRJ5gFvndw0FLoh3kzK7/6RJIzcTQBu5msDKFOhOcp7X5s38JzwICzpnHBDaWFFsAgV6wVysV/GjsTx+E6MtTQsrLMTwd9wKc3kAuP9UwmVaLuhr3D/KEL/ZMTtGR57+tevdpOTxRxvS4ZavR9CJjNingqZKGlkkB3EMtb+3GAbD2ivDKDCmkqpLoekZpYbIkvJ6dW2fugAhWaxzIDrcr2EmGHTXnAJNDr4Kz/G+ghq97U2LBa15JjeyV5mc2li280PeUUZp8g/Qyv/c1CT/0OVn/F3nvlWNVc37kKkrefMX507BCMSYAVULl+NmIWaj+pcOSGpT1CV4O4l4IchzKez5lzJ7fTba74pysGxg19oB4A=="} diff --git a/src/test/resources/data_dir/sys/policy/_user b/src/test/resources/data_dir/sys/policy/_user new file mode 100644 index 0000000..c54cd5c --- /dev/null +++ b/src/test/resources/data_dir/sys/policy/_user @@ -0,0 +1 @@ +{"Key":"sys/policy/user","Value":"AAAAAQL2/4QvXwqcImWZ0+3rMWv9w6B4ql33rXMnfzi2v/qA7mQbfdow/lB6j0fy79JPNLOPd9K3n7MOlsdDJTL6RJ1hUFFM7CeMYT3EviwKgFl4enaB/K40a/f8jYiLBkvHqdrhLrit7kjs2NytNw=="} diff --git a/src/test/resources/data_dir/sys/token/_salt b/src/test/resources/data_dir/sys/token/_salt new file mode 100644 index 0000000..3ff8a1e --- /dev/null +++ b/src/test/resources/data_dir/sys/token/_salt @@ -0,0 +1 @@ +{"Key":"sys/token/salt","Value":"AAAAAQIHNULiDBunAxcG4lQlnKj21ShIgxm7GNc7Zf8an238P2F4XryORfdzXqPyehkJ/npBnI5rCMmt8xLymOXLFRnB"} diff --git a/src/test/resources/data_dir/sys/token/accessor/_1d9485c11b88512ab6e00f6e038105ddeacc8b61 b/src/test/resources/data_dir/sys/token/accessor/_1d9485c11b88512ab6e00f6e038105ddeacc8b61 new file mode 100644 index 0000000..c3c6838 --- /dev/null +++ b/src/test/resources/data_dir/sys/token/accessor/_1d9485c11b88512ab6e00f6e038105ddeacc8b61 @@ -0,0 +1 @@ +{"Key":"sys/token/accessor/1d9485c11b88512ab6e00f6e038105ddeacc8b61","Value":"AAAAAQLgGEtaqyYVvAdd75Ai904aegVg+D/K6PSw2j1ZF1b9Dqq8iqGm57BlpY7WdFI1pXwX9xWNSr4wa8/LofDfuQdW"} diff --git a/src/test/resources/data_dir/sys/token/accessor/_575eee1a6b5cea9bfe29e29fecffffcbf8ad4006 b/src/test/resources/data_dir/sys/token/accessor/_575eee1a6b5cea9bfe29e29fecffffcbf8ad4006 new file mode 100644 index 0000000..5dd8767 --- /dev/null +++ b/src/test/resources/data_dir/sys/token/accessor/_575eee1a6b5cea9bfe29e29fecffffcbf8ad4006 @@ -0,0 +1 @@ +{"Key":"sys/token/accessor/575eee1a6b5cea9bfe29e29fecffffcbf8ad4006","Value":"AAAAAQKdywIFtMoadN+LSrck4PggbvSJO3WLExzNGdJMrazlhY20PRUfLI3Wzwlet78eJdzhrJ6yymDYEoPAmBdl2unc"} diff --git a/src/test/resources/data_dir/sys/token/accessor/_7ab2fb12cd4d090ef2eca9be98f8e3375d42a8f6 b/src/test/resources/data_dir/sys/token/accessor/_7ab2fb12cd4d090ef2eca9be98f8e3375d42a8f6 new file mode 100644 index 0000000..53c0cfa --- /dev/null +++ b/src/test/resources/data_dir/sys/token/accessor/_7ab2fb12cd4d090ef2eca9be98f8e3375d42a8f6 @@ -0,0 +1 @@ +{"Key":"sys/token/accessor/7ab2fb12cd4d090ef2eca9be98f8e3375d42a8f6","Value":"AAAAAQKi28/4Q2x9KVoknHGdIUtoQu8aqb7iecYaVHjPE/DriZ3zG3kexhzspjH8nUp7LjaY7FHwZrSjsOu5CYIBnOOz"} diff --git a/src/test/resources/data_dir/sys/token/accessor/_92437f5eab1616023d9d221099c46657e7075387 b/src/test/resources/data_dir/sys/token/accessor/_92437f5eab1616023d9d221099c46657e7075387 new file mode 100644 index 0000000..b9c6551 --- /dev/null +++ b/src/test/resources/data_dir/sys/token/accessor/_92437f5eab1616023d9d221099c46657e7075387 @@ -0,0 +1 @@ +{"Key":"sys/token/accessor/92437f5eab1616023d9d221099c46657e7075387","Value":"AAAAAQKOxe300u7ZzybBdAYi5KtrxicnO/0hK9cWDaoCd6lGD71/g/AIWlWKz+DP4aRI+lJ0YFO9WEv6TmNf9gj3dN9m"} diff --git a/src/test/resources/data_dir/sys/token/accessor/_d2e5585c0050261f9182adcaa8693b5fc31ff553 b/src/test/resources/data_dir/sys/token/accessor/_d2e5585c0050261f9182adcaa8693b5fc31ff553 new file mode 100644 index 0000000..c63db60 --- /dev/null +++ b/src/test/resources/data_dir/sys/token/accessor/_d2e5585c0050261f9182adcaa8693b5fc31ff553 @@ -0,0 +1 @@ +{"Key":"sys/token/accessor/d2e5585c0050261f9182adcaa8693b5fc31ff553","Value":"AAAAAQJBaj8iLfbGRFRH90AiJ179KHanpct9ko4VMzsYjbd5vLcQs74x/JtkUoJuxdgS4iQp5+qLKAehDOZDqb5d4sE7"} diff --git a/src/test/resources/data_dir/sys/token/accessor/_f53e73156e57c97f8734c0d2a9892f3e2796e9d7 b/src/test/resources/data_dir/sys/token/accessor/_f53e73156e57c97f8734c0d2a9892f3e2796e9d7 new file mode 100644 index 0000000..0412d1b --- /dev/null +++ b/src/test/resources/data_dir/sys/token/accessor/_f53e73156e57c97f8734c0d2a9892f3e2796e9d7 @@ -0,0 +1 @@ +{"Key":"sys/token/accessor/f53e73156e57c97f8734c0d2a9892f3e2796e9d7","Value":"AAAAAQKKkJTy0/HniNoMKHMb4hvwg+LutHutGH5F3mKApfpl0M6j8+euKc6+bVvOfTC22NSe4GtzmJ7r7dTh83MpJhjA"} diff --git a/src/test/resources/data_dir/sys/token/id/_05b3023411dd89a9a27282d57d027f5312be4adc b/src/test/resources/data_dir/sys/token/id/_05b3023411dd89a9a27282d57d027f5312be4adc new file mode 100644 index 0000000..04b4b69 --- /dev/null +++ b/src/test/resources/data_dir/sys/token/id/_05b3023411dd89a9a27282d57d027f5312be4adc @@ -0,0 +1 @@ +{"Key":"sys/token/id/05b3023411dd89a9a27282d57d027f5312be4adc","Value":"AAAAAQIUAO6ILG2gwWjc+J2kp4n03Rp6ycNYAWBYEM0ygocB7DmIT531H5cLbqFVJF5Zw+OQie0HOVLX/zcAPWtxkTOIRXH9FIUT14T9k1IzoxEOYSrbI6ig8bFffe4cd9b0qj9UKgwakQ1GG8vfeSXZnJjVBCSsvWL46s/IGh+SEmirNTiGSE3iy8p3+zunl2s/mUP08i/We03LcLTsCBfSsHVa/CongLNKgSq4oF23LFxv3De+9j8+IQ9HKA0pAatTaCjHdU1TsAiBGsKUhujGW5oQuygkUYVIBFqFqwDOytpdcxiP/A2LAut6qvQjvfT7s7C/Cvke9ypOQAr7iSmUlAhKcXPPEN21NdBmFq4K4w=="} diff --git a/src/test/resources/data_dir/sys/token/id/_299de173bcf8d6ff55f53e9e947006d8c2c88878 b/src/test/resources/data_dir/sys/token/id/_299de173bcf8d6ff55f53e9e947006d8c2c88878 new file mode 100644 index 0000000..2b5dcba --- /dev/null +++ b/src/test/resources/data_dir/sys/token/id/_299de173bcf8d6ff55f53e9e947006d8c2c88878 @@ -0,0 +1 @@ +{"Key":"sys/token/id/299de173bcf8d6ff55f53e9e947006d8c2c88878","Value":"AAAAAQJQt7nHCnFDD58j1l2vcKmg38jweu3B16dIAw1QJ7NhMkLZZxSZilmlLrVX7jD2mI5wQ33/VevIhBioBeTMeZ6CctRywdYhhN3PENTn4YFmy2owswF5iABdl6p8Q+8qYTzTKRV//d2RmzyNXUpuFMoeLKDUpeRcImlECqB5fwquIHKSohx2mw4LXwsixydW/Zz0o45R0t+44rMmX81PtAr8EHh1ibZyhSpgIXy6PPvhvkdIwK2/R7PxtG/gC1ETXLW9pFRMVCk3kED74BYg6d3YnS3HSvls0fBf8k9nfvVd/qVRfW2vipzy9X2Pdc/orb6eGb4zp5+umMwzlere9WLScqWvMXh6Z+u/t4b0ugx79QFDuBfRNNtV9Q9xkU7EzEQYJ9SlVS2GY1t0mvA6q6A3Y+FKr+MXbHo73Ep9yQCwsMfBYwSTw5bb+zk9KocWlVSlywx2oRI="} diff --git a/src/test/resources/data_dir/sys/token/id/_2e9403fa105622cab037830348fb1dc2c309ac58 b/src/test/resources/data_dir/sys/token/id/_2e9403fa105622cab037830348fb1dc2c309ac58 new file mode 100644 index 0000000..0b2aec9 --- /dev/null +++ b/src/test/resources/data_dir/sys/token/id/_2e9403fa105622cab037830348fb1dc2c309ac58 @@ -0,0 +1 @@ +{"Key":"sys/token/id/2e9403fa105622cab037830348fb1dc2c309ac58","Value":"AAAAAQKefcFaLPpMwphWhftjzK1inJ6+o4BS67QSBSjARw5SDqDPKIrrDhHSJWHT+lfYxNG3NGSSroOcF2NRLGKX0lN5uoBry/dJCUAESYeoQr2m4eWIdGnnRI/q0h8xtpt9snlE06GMBRMOiPFiPwzeRfRreMSWOPYXV+WcV5jXbp/oRwAYbimAta4XlPxWz/+Ie2KPKqlyFHOPC36RZkupR4NNn+IiWT/MOU6B83Z6S/v46mpRi3BP6H9d09LXKJUDUiq3Bw4SlWvnvZ4x6vz/tUIOggnj+5LydDJUe6XqMhSOMa79IEVDvlRQOl6/gQ/KeM0VmQ5EC0+gADcjsclq3gw8z0UG0ee9J4NwUzaOJU6TclWCd8InJUk0kb+rtoWlVxDRH6LFLFoloTXjFaAp99qD8dXlsFbh6zFrjp013ZECmre/XMrRlTHqzCFfW6lYl7sGdYzyWXY="} diff --git a/src/test/resources/data_dir/sys/token/id/_87656f27093d2d77837196faca0e4698c52bbd72 b/src/test/resources/data_dir/sys/token/id/_87656f27093d2d77837196faca0e4698c52bbd72 new file mode 100644 index 0000000..54e5b92 --- /dev/null +++ b/src/test/resources/data_dir/sys/token/id/_87656f27093d2d77837196faca0e4698c52bbd72 @@ -0,0 +1 @@ +{"Key":"sys/token/id/87656f27093d2d77837196faca0e4698c52bbd72","Value":"AAAAAQL+pJwgJ+xwD+Ut6lCskksaA9cLFNTrEp2lo8PGXmJ32pvgxSZ2Wo0WiFHQS7Y8mbQdnZCDlwsRTQJvFTFGHkltQ9SBlUwmasKKQKnAczRlRiwkadjDOaTbUX4Tu0Adr9o92M0CBltQUWIC5+3PsSxsbgaOVHsx72dhh2xUXVlJxUgYrAsqyLCH+nbsphELoRNSveTd6nWsZfIZAYY/dYmvMfYTinKCcpjd95Lb/Q81zJfO5ueg43YDDkR6E3avgK4tSUYktWf6DC6/dv2ORCHR+5opKfn5S+4TXscv0gmMiuBBIFq8SaiXb7EH1enWdkIPl50BIfoaJLCPhtuFagYAIbcRPQ7/XgrPsg6sIGOEiebVCpGTOyePHOH/4vKKoSDZPq9j4Hqj59cP/SAftJu6JfKC4D4Jizx2iT6VgP8LbY5aVK4PP4XibgnOoQ6oZ8KF5Yl2+HY="} diff --git a/src/test/resources/data_dir/sys/token/id/_907c609e9d43718c2d983e1b6fbead2d73f9b77b b/src/test/resources/data_dir/sys/token/id/_907c609e9d43718c2d983e1b6fbead2d73f9b77b new file mode 100644 index 0000000..48cc8ff --- /dev/null +++ b/src/test/resources/data_dir/sys/token/id/_907c609e9d43718c2d983e1b6fbead2d73f9b77b @@ -0,0 +1 @@ +{"Key":"sys/token/id/907c609e9d43718c2d983e1b6fbead2d73f9b77b","Value":"AAAAAQJe42Qg1BhYScbMjIMlgB7pmzO4DAudGAqRV42py/7154fOu8IEKq60HolmwyqdA8kBvaaNetxC6LJDCXE/TX962L2TeMNd0PsvFcwjblNevRh/Vr4frVmMK0BjkXLLAb9KZhLFbX/58c2NbTu5efZosWn6hkxeggsnf9f/H7OS495kX01zaqV96X0Xp7MKH+HwDsG6BE3OrSSQ7piz89a+17AjQVH54LeMBZshkbwQ1rXFSG8vXs7oUV8IWhq+fGbU9VIwnJfudKcdaNsmk4CRm3hrrRjqHETq6OefPMGWsL8W0NoYq1iG3Ro9aB+VkdHDb9WuSeP8QkiUAcA0Zpnxhote3LXTQTBlYJqI32UuNIoIfdRezRFXSNR3sLzt6PxclfqUcfb2U97RZlYCKpYJqwTVu+T7AXxzgDaPSoMTpuq7F7Fu3fqpNrYAJoAgj3Dyh/7rjtk="} diff --git a/src/test/resources/data_dir/sys/token/id/_a5ecc2e5630b7e232b9c8744a0be6441ffb43229 b/src/test/resources/data_dir/sys/token/id/_a5ecc2e5630b7e232b9c8744a0be6441ffb43229 new file mode 100644 index 0000000..d747417 --- /dev/null +++ b/src/test/resources/data_dir/sys/token/id/_a5ecc2e5630b7e232b9c8744a0be6441ffb43229 @@ -0,0 +1 @@ +{"Key":"sys/token/id/a5ecc2e5630b7e232b9c8744a0be6441ffb43229","Value":"AAAAAQLfKeWYOoWmIQesxjo8x4C+mJm49dCQ9otVnMKfB5ZFRPbQ60soeickhPYTC14yByV/ZJAGiiAoWa4xSHUbZXnYBmkffCMKCN3N5SVt3nICX1pmXHk6kSaqGKUbgLuhQkTu32Ahf27ghFll9czas6S0WWWvHblmb5iy43jiQczWo5YQjXBkXE36CUfMpjM6wrZn27ecKoHoRSTL17sOKK6R2FtZWS7juBlCDmUY3SffxIsIu2ZESFgX5E+hTUR/HnqnAq8/JeDvbHXJqBVZM647DI9Pg9M4M14Em5tEkY3dPaRYfWU7OV7/9vIebyR4kcBWycdlgejoBv7pKjUDxfavLM3/e8R2dVeubYWoLm+O+P2nVhY8S3Fq3K/9QgDQLZXq6QcWMdwMO6A+YelLW2TNaabRzDS8gGoonh21/I5Xi2Y8GwycCotY0WKBPNVi8lOT3jH16oc="}