Initial Import
This commit is contained in:
@ -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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user