Initial Import

This commit is contained in:
Stefan Kalscheuer 2016-03-29 15:12:35 +02:00
commit b845e4b7ce
60 changed files with 2168 additions and 0 deletions

202
LICENSE.txt Normal file
View File

@ -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.

58
README.md Normal file
View File

@ -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**
```
<dependency>
<groupId>de.stklcode.jvault</groupId>
<artifactId>connector</artifactId>
<version>0.1</version>
</dependency>
```
**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).

66
pom.xml Normal file
View File

@ -0,0 +1,66 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<name>jVaultConnector</name>
<groupId>de.stklcode.jvault</groupId>
<artifactId>connector</artifactId>
<version>0.1</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-junit</artifactId>
<version>2.0.0.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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<String, Object> 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<AuthBackend> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String> 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<String, String> 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<String, Object> 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<String, Object> 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) {
}
}
}
}

View File

@ -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<AuthBackend> 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<String> 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;
}

View File

@ -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 {
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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<AuthMethod> supportedMethods;
@JsonAnySetter
public void setMethod(String path, Map<String, String> data) throws InvalidResponseException {
if (supportedMethods == null)
supportedMethods = new ArrayList<>();
supportedMethods.add(new AuthMethod(path, data.get("description"), data.get("type")));
}
public List<AuthMethod> getSupportedMethods() {
return supportedMethods;
}
}

View File

@ -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<String, Object> data;
private AuthData auth;
@JsonProperty("auth")
public void setAuth(Map<String, Object> 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<String, Object> data) {
this.data = data;
}
public Map<String, Object> getData() {
return data;
}
public AuthData getAuth() {
return auth;
}
}

View File

@ -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<String> errors;
public List<String > getErrors() {
return errors;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<String> keys;
@JsonProperty("data")
public void setData(Map<String, Object> data) throws InvalidResponseException {
try {
this.keys = (List<String>)data.get("keys");
}
catch (ClassCastException e) {
throw new InvalidResponseException("Keys could not be parsed from data.", e);
}
}
public List<String> getKeys() {
return keys;
}
}

View File

@ -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<String, Object> 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;
}
}

View File

@ -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<String, Object> 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;
}
}

View File

@ -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<String> warnings;
@JsonProperty("data")
public abstract void setData(Map<String, Object> data) throws InvalidResponseException;
public String getLeaseId() {
return leaseId;
}
public boolean isRenewable() {
return renewable;
}
public Integer getLeaseDuration() {
return leaseDuration;
}
public List<String> getWarnings() {
return warnings;
}
}

View File

@ -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 {
}

View File

@ -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<String> policies;
@JsonProperty("metadata")
private Map<String, Object> metadata;
@JsonProperty("lease_duration")
private Integer leaseDuration;
@JsonProperty("renewable")
private boolean renewable;
public String getClientToken() {
return clientToken;
}
public String getAccessor() {
return accessor;
}
public List<String> getPolicies() {
return policies;
}
public Map<String, Object> getMetadata() {
return metadata;
}
public Integer getLeaseDuration() {
return leaseDuration;
}
public boolean isRenewable() {
return renewable;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<AuthBackend> 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<String> 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.");
}
}

View File

@ -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" : "");
}
}

View File

@ -0,0 +1 @@
{"Key":"auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/salt","Value":"AAAAAQJUsuXXEpmdNY5aIh5HdzZRTFpOUIgyKLGiw65DBwSXW6yGAYe/zhN/Ow+vyRZxG4temgnTjN7RVGjyzXGG5yLY"}

View File

@ -0,0 +1 @@
{"Key":"auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/app-id/19d90b9adcec2bf5088304034622a169a148ff43","Value":"AAAAAQJuuRcCRinyawQ05brruZQY7ypgs1mOsFHI16XLwYB4dzwJob71wW+74RjvK4FVL4qPfgyMPKEtV2uO9+4hr2mC6BrcN///Ksxv+ns8FMVlBOMJpQ=="}

View File

@ -0,0 +1 @@
{"Key":"auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/user-id/55a852babe045b5980fc8ac4a13af27021dbbfd4","Value":"AAAAAQICaFIxG2xAq0AuJryVn1XghDulkVdQicXvhEL45K2S48aZcvMEsrDUXm9o427Bp6eMiq0Hw070nosnB9SWSQJEFUfPmM6I7Jhsou6CKmocs/AmocxY3Du4Lg=="}

View File

@ -0,0 +1 @@
{"Key":"auth/6802ec63-11b0-0ccc-280a-982ad0a90621/user/validuser","Value":"AAAAAQJwFKMpgopAFjJaftTVY/iiawMw4Yj0S3pPDkzMPAfLxxaM3sCjOJt0q/07ozjTharT52wBv+s2ZEurPpr7VKDDzgy4xTMxFJbJs+0VkG3cjxRYEfW3bOIVAHhjLjmxZwYEATh0UUG7bQRNt56+/622bwR99ifWZ6e9zyRDGEwIn74JFN/3dY44qLQZmqfvDUrRQP5RfqDxqVdzbwse61s692Vy/QvlPsRFVRTkZHlNPqxT+OXd"}

View File

@ -0,0 +1 @@
{"Key":"core/audit","Value":"AAAAAQJ+0lfxYOKIXzquksd3Il4zfW4ja6BdScu7mCAijGDph63S5yWH92olwI2SQA=="}

View File

@ -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="}

View File

@ -0,0 +1 @@
{"Key":"core/keyring","Value":"AAAAAQKCYFhCtIpVCVfA/MaX9/fCtNJB5z1z+tsi6VQzlDxo9dUxSsZC3ppnZZ9TYwRj6TUeP8QqyNDDhwiXp/i/WhAeoMV9UDKHQq7Ay4BO+AOz3VpHTX1ZYFrF0ZrYELqyFrOkhETpSpNzD6rxcwoJD9NuC+oQhR4TsMEXNzX3AqMKJE+b8iTbEl3tRZRQ5qgjIIy835JDTI3JoSXUxJAxYAbZLxunx2TF6fXs1urpY6GfCrvb/bidzBsfOT37y9Fok8TnOo8a1laqJpsdL6yOQnHj+ZmJVG+Pj5QLETg2L8hBikIMKA=="}

View File

@ -0,0 +1 @@
{"Key":"core/master","Value":"AAAAAQK8dxOynwwVLtj8fqeAPBSmo/cbdJBQgQt84CDEuYd3JMLLz3bRiP8G2rQ8mdaP7VVQjJyaWgG5AIFiyjswnYiOWWFIpFn7xPUr5Og1Pd0jTB5mCGEBSdoVLggt21JC4Rp7ceFlO8fNoc1q6h+IZI8ZMn8MPbqpMALNSqKhpOc5xfh6YkgL3XphWnbM5Gzc"}

View File

@ -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"}

View File

@ -0,0 +1 @@
{"Key":"core/seal-config","Value":"eyJzZWNyZXRfc2hhcmVzIjoxLCJwZ3Bfa2V5cyI6bnVsbCwic2VjcmV0X3RocmVzaG9sZCI6MSwibm9uY2UiOiIiLCJiYWNrdXAiOmZhbHNlfQ=="}

View File

@ -0,0 +1 @@
{"Key":"logical/b85d867d-74d1-7d84-7a97-4597d813a5fb/userstore/foo","Value":"AAAAAQKEvkR4E7cn/rKtmyhFT75qQ/hMRUwoKNmlfFar6/sxK3icAAYGWMVq8brp"}

View File

@ -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"}

View File

@ -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"}

View File

@ -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"}

View File

@ -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"}

View File

@ -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"}

View File

@ -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=="}

View File

@ -0,0 +1 @@
{"Key":"sys/policy/user","Value":"AAAAAQL2/4QvXwqcImWZ0+3rMWv9w6B4ql33rXMnfzi2v/qA7mQbfdow/lB6j0fy79JPNLOPd9K3n7MOlsdDJTL6RJ1hUFFM7CeMYT3EviwKgFl4enaB/K40a/f8jYiLBkvHqdrhLrit7kjs2NytNw=="}

View File

@ -0,0 +1 @@
{"Key":"sys/token/salt","Value":"AAAAAQIHNULiDBunAxcG4lQlnKj21ShIgxm7GNc7Zf8an238P2F4XryORfdzXqPyehkJ/npBnI5rCMmt8xLymOXLFRnB"}

View File

@ -0,0 +1 @@
{"Key":"sys/token/accessor/1d9485c11b88512ab6e00f6e038105ddeacc8b61","Value":"AAAAAQLgGEtaqyYVvAdd75Ai904aegVg+D/K6PSw2j1ZF1b9Dqq8iqGm57BlpY7WdFI1pXwX9xWNSr4wa8/LofDfuQdW"}

View File

@ -0,0 +1 @@
{"Key":"sys/token/accessor/575eee1a6b5cea9bfe29e29fecffffcbf8ad4006","Value":"AAAAAQKdywIFtMoadN+LSrck4PggbvSJO3WLExzNGdJMrazlhY20PRUfLI3Wzwlet78eJdzhrJ6yymDYEoPAmBdl2unc"}

View File

@ -0,0 +1 @@
{"Key":"sys/token/accessor/7ab2fb12cd4d090ef2eca9be98f8e3375d42a8f6","Value":"AAAAAQKi28/4Q2x9KVoknHGdIUtoQu8aqb7iecYaVHjPE/DriZ3zG3kexhzspjH8nUp7LjaY7FHwZrSjsOu5CYIBnOOz"}

View File

@ -0,0 +1 @@
{"Key":"sys/token/accessor/92437f5eab1616023d9d221099c46657e7075387","Value":"AAAAAQKOxe300u7ZzybBdAYi5KtrxicnO/0hK9cWDaoCd6lGD71/g/AIWlWKz+DP4aRI+lJ0YFO9WEv6TmNf9gj3dN9m"}

View File

@ -0,0 +1 @@
{"Key":"sys/token/accessor/d2e5585c0050261f9182adcaa8693b5fc31ff553","Value":"AAAAAQJBaj8iLfbGRFRH90AiJ179KHanpct9ko4VMzsYjbd5vLcQs74x/JtkUoJuxdgS4iQp5+qLKAehDOZDqb5d4sE7"}

View File

@ -0,0 +1 @@
{"Key":"sys/token/accessor/f53e73156e57c97f8734c0d2a9892f3e2796e9d7","Value":"AAAAAQKKkJTy0/HniNoMKHMb4hvwg+LutHutGH5F3mKApfpl0M6j8+euKc6+bVvOfTC22NSe4GtzmJ7r7dTh83MpJhjA"}

View File

@ -0,0 +1 @@
{"Key":"sys/token/id/05b3023411dd89a9a27282d57d027f5312be4adc","Value":"AAAAAQIUAO6ILG2gwWjc+J2kp4n03Rp6ycNYAWBYEM0ygocB7DmIT531H5cLbqFVJF5Zw+OQie0HOVLX/zcAPWtxkTOIRXH9FIUT14T9k1IzoxEOYSrbI6ig8bFffe4cd9b0qj9UKgwakQ1GG8vfeSXZnJjVBCSsvWL46s/IGh+SEmirNTiGSE3iy8p3+zunl2s/mUP08i/We03LcLTsCBfSsHVa/CongLNKgSq4oF23LFxv3De+9j8+IQ9HKA0pAatTaCjHdU1TsAiBGsKUhujGW5oQuygkUYVIBFqFqwDOytpdcxiP/A2LAut6qvQjvfT7s7C/Cvke9ypOQAr7iSmUlAhKcXPPEN21NdBmFq4K4w=="}

View File

@ -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="}

View File

@ -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="}

View File

@ -0,0 +1 @@
{"Key":"sys/token/id/87656f27093d2d77837196faca0e4698c52bbd72","Value":"AAAAAQL+pJwgJ+xwD+Ut6lCskksaA9cLFNTrEp2lo8PGXmJ32pvgxSZ2Wo0WiFHQS7Y8mbQdnZCDlwsRTQJvFTFGHkltQ9SBlUwmasKKQKnAczRlRiwkadjDOaTbUX4Tu0Adr9o92M0CBltQUWIC5+3PsSxsbgaOVHsx72dhh2xUXVlJxUgYrAsqyLCH+nbsphELoRNSveTd6nWsZfIZAYY/dYmvMfYTinKCcpjd95Lb/Q81zJfO5ueg43YDDkR6E3avgK4tSUYktWf6DC6/dv2ORCHR+5opKfn5S+4TXscv0gmMiuBBIFq8SaiXb7EH1enWdkIPl50BIfoaJLCPhtuFagYAIbcRPQ7/XgrPsg6sIGOEiebVCpGTOyePHOH/4vKKoSDZPq9j4Hqj59cP/SAftJu6JfKC4D4Jizx2iT6VgP8LbY5aVK4PP4XibgnOoQ6oZ8KF5Yl2+HY="}

View File

@ -0,0 +1 @@
{"Key":"sys/token/id/907c609e9d43718c2d983e1b6fbead2d73f9b77b","Value":"AAAAAQJe42Qg1BhYScbMjIMlgB7pmzO4DAudGAqRV42py/7154fOu8IEKq60HolmwyqdA8kBvaaNetxC6LJDCXE/TX962L2TeMNd0PsvFcwjblNevRh/Vr4frVmMK0BjkXLLAb9KZhLFbX/58c2NbTu5efZosWn6hkxeggsnf9f/H7OS495kX01zaqV96X0Xp7MKH+HwDsG6BE3OrSSQ7piz89a+17AjQVH54LeMBZshkbwQ1rXFSG8vXs7oUV8IWhq+fGbU9VIwnJfudKcdaNsmk4CRm3hrrRjqHETq6OefPMGWsL8W0NoYq1iG3Ro9aB+VkdHDb9WuSeP8QkiUAcA0Zpnxhote3LXTQTBlYJqI32UuNIoIfdRezRFXSNR3sLzt6PxclfqUcfb2U97RZlYCKpYJqwTVu+T7AXxzgDaPSoMTpuq7F7Fu3fqpNrYAJoAgj3Dyh/7rjtk="}

View File

@ -0,0 +1 @@
{"Key":"sys/token/id/a5ecc2e5630b7e232b9c8744a0be6441ffb43229","Value":"AAAAAQLfKeWYOoWmIQesxjo8x4C+mJm49dCQ9otVnMKfB5ZFRPbQ60soeickhPYTC14yByV/ZJAGiiAoWa4xSHUbZXnYBmkffCMKCN3N5SVt3nICX1pmXHk6kSaqGKUbgLuhQkTu32Ahf27ghFll9czas6S0WWWvHblmb5iy43jiQczWo5YQjXBkXE36CUfMpjM6wrZn27ecKoHoRSTL17sOKK6R2FtZWS7juBlCDmUY3SffxIsIu2ZESFgX5E+hTUR/HnqnAq8/JeDvbHXJqBVZM647DI9Pg9M4M14Em5tEkY3dPaRYfWU7OV7/9vIebyR4kcBWycdlgejoBv7pKjUDxfavLM3/e8R2dVeubYWoLm+O+P2nVhY8S3Fq3K/9QgDQLZXq6QcWMdwMO6A+YelLW2TNaabRzDS8gGoonh21/I5Xi2Y8GwycCotY0WKBPNVi8lOT3jH16oc="}