Initial Import
This commit is contained in:
commit
b845e4b7ce
202
LICENSE.txt
Normal file
202
LICENSE.txt
Normal 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
58
README.md
Normal 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
66
pom.xml
Normal 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>
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
149
src/main/java/de/stklcode/jvault/connector/VaultConnector.java
Normal file
149
src/main/java/de/stklcode/jvault/connector/VaultConnector.java
Normal 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;
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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.");
|
||||
}
|
||||
}
|
@ -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" : "");
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
{"Key":"auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/salt","Value":"AAAAAQJUsuXXEpmdNY5aIh5HdzZRTFpOUIgyKLGiw65DBwSXW6yGAYe/zhN/Ow+vyRZxG4temgnTjN7RVGjyzXGG5yLY"}
|
@ -0,0 +1 @@
|
||||
{"Key":"auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/app-id/19d90b9adcec2bf5088304034622a169a148ff43","Value":"AAAAAQJuuRcCRinyawQ05brruZQY7ypgs1mOsFHI16XLwYB4dzwJob71wW+74RjvK4FVL4qPfgyMPKEtV2uO9+4hr2mC6BrcN///Ksxv+ns8FMVlBOMJpQ=="}
|
@ -0,0 +1 @@
|
||||
{"Key":"auth/20e9c2e6-5b1f-b9c9-5a99-21667e0a899d/struct/map/user-id/55a852babe045b5980fc8ac4a13af27021dbbfd4","Value":"AAAAAQICaFIxG2xAq0AuJryVn1XghDulkVdQicXvhEL45K2S48aZcvMEsrDUXm9o427Bp6eMiq0Hw070nosnB9SWSQJEFUfPmM6I7Jhsou6CKmocs/AmocxY3Du4Lg=="}
|
@ -0,0 +1 @@
|
||||
{"Key":"auth/6802ec63-11b0-0ccc-280a-982ad0a90621/user/validuser","Value":"AAAAAQJwFKMpgopAFjJaftTVY/iiawMw4Yj0S3pPDkzMPAfLxxaM3sCjOJt0q/07ozjTharT52wBv+s2ZEurPpr7VKDDzgy4xTMxFJbJs+0VkG3cjxRYEfW3bOIVAHhjLjmxZwYEATh0UUG7bQRNt56+/622bwR99ifWZ6e9zyRDGEwIn74JFN/3dY44qLQZmqfvDUrRQP5RfqDxqVdzbwse61s692Vy/QvlPsRFVRTkZHlNPqxT+OXd"}
|
1
src/test/resources/data_dir/core/_audit
Normal file
1
src/test/resources/data_dir/core/_audit
Normal file
@ -0,0 +1 @@
|
||||
{"Key":"core/audit","Value":"AAAAAQJ+0lfxYOKIXzquksd3Il4zfW4ja6BdScu7mCAijGDph63S5yWH92olwI2SQA=="}
|
1
src/test/resources/data_dir/core/_auth
Normal file
1
src/test/resources/data_dir/core/_auth
Normal 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="}
|
1
src/test/resources/data_dir/core/_keyring
Normal file
1
src/test/resources/data_dir/core/_keyring
Normal file
@ -0,0 +1 @@
|
||||
{"Key":"core/keyring","Value":"AAAAAQKCYFhCtIpVCVfA/MaX9/fCtNJB5z1z+tsi6VQzlDxo9dUxSsZC3ppnZZ9TYwRj6TUeP8QqyNDDhwiXp/i/WhAeoMV9UDKHQq7Ay4BO+AOz3VpHTX1ZYFrF0ZrYELqyFrOkhETpSpNzD6rxcwoJD9NuC+oQhR4TsMEXNzX3AqMKJE+b8iTbEl3tRZRQ5qgjIIy835JDTI3JoSXUxJAxYAbZLxunx2TF6fXs1urpY6GfCrvb/bidzBsfOT37y9Fok8TnOo8a1laqJpsdL6yOQnHj+ZmJVG+Pj5QLETg2L8hBikIMKA=="}
|
1
src/test/resources/data_dir/core/_master
Normal file
1
src/test/resources/data_dir/core/_master
Normal file
@ -0,0 +1 @@
|
||||
{"Key":"core/master","Value":"AAAAAQK8dxOynwwVLtj8fqeAPBSmo/cbdJBQgQt84CDEuYd3JMLLz3bRiP8G2rQ8mdaP7VVQjJyaWgG5AIFiyjswnYiOWWFIpFn7xPUr5Og1Pd0jTB5mCGEBSdoVLggt21JC4Rp7ceFlO8fNoc1q6h+IZI8ZMn8MPbqpMALNSqKhpOc5xfh6YkgL3XphWnbM5Gzc"}
|
1
src/test/resources/data_dir/core/_mounts
Normal file
1
src/test/resources/data_dir/core/_mounts
Normal 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"}
|
1
src/test/resources/data_dir/core/_seal-config
Normal file
1
src/test/resources/data_dir/core/_seal-config
Normal file
@ -0,0 +1 @@
|
||||
{"Key":"core/seal-config","Value":"eyJzZWNyZXRfc2hhcmVzIjoxLCJwZ3Bfa2V5cyI6bnVsbCwic2VjcmV0X3RocmVzaG9sZCI6MSwibm9uY2UiOiIiLCJiYWNrdXAiOmZhbHNlfQ=="}
|
@ -0,0 +1 @@
|
||||
{"Key":"logical/b85d867d-74d1-7d84-7a97-4597d813a5fb/userstore/foo","Value":"AAAAAQKEvkR4E7cn/rKtmyhFT75qQ/hMRUwoKNmlfFar6/sxK3icAAYGWMVq8brp"}
|
@ -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"}
|
@ -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"}
|
@ -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"}
|
@ -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"}
|
@ -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"}
|
1
src/test/resources/data_dir/sys/policy/_default
Normal file
1
src/test/resources/data_dir/sys/policy/_default
Normal 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=="}
|
1
src/test/resources/data_dir/sys/policy/_user
Normal file
1
src/test/resources/data_dir/sys/policy/_user
Normal file
@ -0,0 +1 @@
|
||||
{"Key":"sys/policy/user","Value":"AAAAAQL2/4QvXwqcImWZ0+3rMWv9w6B4ql33rXMnfzi2v/qA7mQbfdow/lB6j0fy79JPNLOPd9K3n7MOlsdDJTL6RJ1hUFFM7CeMYT3EviwKgFl4enaB/K40a/f8jYiLBkvHqdrhLrit7kjs2NytNw=="}
|
1
src/test/resources/data_dir/sys/token/_salt
Normal file
1
src/test/resources/data_dir/sys/token/_salt
Normal file
@ -0,0 +1 @@
|
||||
{"Key":"sys/token/salt","Value":"AAAAAQIHNULiDBunAxcG4lQlnKj21ShIgxm7GNc7Zf8an238P2F4XryORfdzXqPyehkJ/npBnI5rCMmt8xLymOXLFRnB"}
|
@ -0,0 +1 @@
|
||||
{"Key":"sys/token/accessor/1d9485c11b88512ab6e00f6e038105ddeacc8b61","Value":"AAAAAQLgGEtaqyYVvAdd75Ai904aegVg+D/K6PSw2j1ZF1b9Dqq8iqGm57BlpY7WdFI1pXwX9xWNSr4wa8/LofDfuQdW"}
|
@ -0,0 +1 @@
|
||||
{"Key":"sys/token/accessor/575eee1a6b5cea9bfe29e29fecffffcbf8ad4006","Value":"AAAAAQKdywIFtMoadN+LSrck4PggbvSJO3WLExzNGdJMrazlhY20PRUfLI3Wzwlet78eJdzhrJ6yymDYEoPAmBdl2unc"}
|
@ -0,0 +1 @@
|
||||
{"Key":"sys/token/accessor/7ab2fb12cd4d090ef2eca9be98f8e3375d42a8f6","Value":"AAAAAQKi28/4Q2x9KVoknHGdIUtoQu8aqb7iecYaVHjPE/DriZ3zG3kexhzspjH8nUp7LjaY7FHwZrSjsOu5CYIBnOOz"}
|
@ -0,0 +1 @@
|
||||
{"Key":"sys/token/accessor/92437f5eab1616023d9d221099c46657e7075387","Value":"AAAAAQKOxe300u7ZzybBdAYi5KtrxicnO/0hK9cWDaoCd6lGD71/g/AIWlWKz+DP4aRI+lJ0YFO9WEv6TmNf9gj3dN9m"}
|
@ -0,0 +1 @@
|
||||
{"Key":"sys/token/accessor/d2e5585c0050261f9182adcaa8693b5fc31ff553","Value":"AAAAAQJBaj8iLfbGRFRH90AiJ179KHanpct9ko4VMzsYjbd5vLcQs74x/JtkUoJuxdgS4iQp5+qLKAehDOZDqb5d4sE7"}
|
@ -0,0 +1 @@
|
||||
{"Key":"sys/token/accessor/f53e73156e57c97f8734c0d2a9892f3e2796e9d7","Value":"AAAAAQKKkJTy0/HniNoMKHMb4hvwg+LutHutGH5F3mKApfpl0M6j8+euKc6+bVvOfTC22NSe4GtzmJ7r7dTh83MpJhjA"}
|
@ -0,0 +1 @@
|
||||
{"Key":"sys/token/id/05b3023411dd89a9a27282d57d027f5312be4adc","Value":"AAAAAQIUAO6ILG2gwWjc+J2kp4n03Rp6ycNYAWBYEM0ygocB7DmIT531H5cLbqFVJF5Zw+OQie0HOVLX/zcAPWtxkTOIRXH9FIUT14T9k1IzoxEOYSrbI6ig8bFffe4cd9b0qj9UKgwakQ1GG8vfeSXZnJjVBCSsvWL46s/IGh+SEmirNTiGSE3iy8p3+zunl2s/mUP08i/We03LcLTsCBfSsHVa/CongLNKgSq4oF23LFxv3De+9j8+IQ9HKA0pAatTaCjHdU1TsAiBGsKUhujGW5oQuygkUYVIBFqFqwDOytpdcxiP/A2LAut6qvQjvfT7s7C/Cvke9ypOQAr7iSmUlAhKcXPPEN21NdBmFq4K4w=="}
|
@ -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="}
|
@ -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="}
|
@ -0,0 +1 @@
|
||||
{"Key":"sys/token/id/87656f27093d2d77837196faca0e4698c52bbd72","Value":"AAAAAQL+pJwgJ+xwD+Ut6lCskksaA9cLFNTrEp2lo8PGXmJ32pvgxSZ2Wo0WiFHQS7Y8mbQdnZCDlwsRTQJvFTFGHkltQ9SBlUwmasKKQKnAczRlRiwkadjDOaTbUX4Tu0Adr9o92M0CBltQUWIC5+3PsSxsbgaOVHsx72dhh2xUXVlJxUgYrAsqyLCH+nbsphELoRNSveTd6nWsZfIZAYY/dYmvMfYTinKCcpjd95Lb/Q81zJfO5ueg43YDDkR6E3avgK4tSUYktWf6DC6/dv2ORCHR+5opKfn5S+4TXscv0gmMiuBBIFq8SaiXb7EH1enWdkIPl50BIfoaJLCPhtuFagYAIbcRPQ7/XgrPsg6sIGOEiebVCpGTOyePHOH/4vKKoSDZPq9j4Hqj59cP/SAftJu6JfKC4D4Jizx2iT6VgP8LbY5aVK4PP4XibgnOoQ6oZ8KF5Yl2+HY="}
|
@ -0,0 +1 @@
|
||||
{"Key":"sys/token/id/907c609e9d43718c2d983e1b6fbead2d73f9b77b","Value":"AAAAAQJe42Qg1BhYScbMjIMlgB7pmzO4DAudGAqRV42py/7154fOu8IEKq60HolmwyqdA8kBvaaNetxC6LJDCXE/TX962L2TeMNd0PsvFcwjblNevRh/Vr4frVmMK0BjkXLLAb9KZhLFbX/58c2NbTu5efZosWn6hkxeggsnf9f/H7OS495kX01zaqV96X0Xp7MKH+HwDsG6BE3OrSSQ7piz89a+17AjQVH54LeMBZshkbwQ1rXFSG8vXs7oUV8IWhq+fGbU9VIwnJfudKcdaNsmk4CRm3hrrRjqHETq6OefPMGWsL8W0NoYq1iG3Ro9aB+VkdHDb9WuSeP8QkiUAcA0Zpnxhote3LXTQTBlYJqI32UuNIoIfdRezRFXSNR3sLzt6PxclfqUcfb2U97RZlYCKpYJqwTVu+T7AXxzgDaPSoMTpuq7F7Fu3fqpNrYAJoAgj3Dyh/7rjtk="}
|
@ -0,0 +1 @@
|
||||
{"Key":"sys/token/id/a5ecc2e5630b7e232b9c8744a0be6441ffb43229","Value":"AAAAAQLfKeWYOoWmIQesxjo8x4C+mJm49dCQ9otVnMKfB5ZFRPbQ60soeickhPYTC14yByV/ZJAGiiAoWa4xSHUbZXnYBmkffCMKCN3N5SVt3nICX1pmXHk6kSaqGKUbgLuhQkTu32Ahf27ghFll9czas6S0WWWvHblmb5iy43jiQczWo5YQjXBkXE36CUfMpjM6wrZn27ecKoHoRSTL17sOKK6R2FtZWS7juBlCDmUY3SffxIsIu2ZESFgX5E+hTUR/HnqnAq8/JeDvbHXJqBVZM647DI9Pg9M4M14Em5tEkY3dPaRYfWU7OV7/9vIebyR4kcBWycdlgejoBv7pKjUDxfavLM3/e8R2dVeubYWoLm+O+P2nVhY8S3Fq3K/9QgDQLZXq6QcWMdwMO6A+YelLW2TNaabRzDS8gGoonh21/I5Xi2Y8GwycCotY0WKBPNVi8lOT3jH16oc="}
|
Loading…
x
Reference in New Issue
Block a user