Merge branch 'feature/16-kv_v2_support' into develop
This commit is contained in:
commit
ad2986195d
@ -74,6 +74,11 @@ public class HTTPVaultConnector implements VaultConnector {
|
|||||||
private static final String PATH_AUTH_APPROLE_ROLE = "auth/approle/role/%s%s";
|
private static final String PATH_AUTH_APPROLE_ROLE = "auth/approle/role/%s%s";
|
||||||
private static final String PATH_REVOKE = "sys/leases/revoke/";
|
private static final String PATH_REVOKE = "sys/leases/revoke/";
|
||||||
private static final String PATH_HEALTH = "sys/health";
|
private static final String PATH_HEALTH = "sys/health";
|
||||||
|
private static final String PATH_DATA = "/data/";
|
||||||
|
private static final String PATH_METADATA = "/metadata/";
|
||||||
|
private static final String PATH_DELETE = "/delete/";
|
||||||
|
private static final String PATH_UNDELETE = "/undelete/";
|
||||||
|
private static final String PATH_DESTROY = "/destroy/";
|
||||||
|
|
||||||
private static final String HEADER_VAULT_TOKEN = "X-Vault-Token";
|
private static final String HEADER_VAULT_TOKEN = "X-Vault-Token";
|
||||||
|
|
||||||
@ -620,6 +625,44 @@ public class HTTPVaultConnector implements VaultConnector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final SecretResponse readSecretVersion(final String key, final Integer version) throws VaultConnectorException {
|
||||||
|
if (!isAuthorized()) {
|
||||||
|
throw new AuthorizationRequiredException();
|
||||||
|
}
|
||||||
|
/* Request HTTP response and parse secret metadata */
|
||||||
|
try {
|
||||||
|
Map<String, String> args = new HashMap<>();
|
||||||
|
if (version != null) {
|
||||||
|
args.put("version", version.toString());
|
||||||
|
}
|
||||||
|
String response = requestGet(PATH_SECRET + PATH_DATA + key, args);
|
||||||
|
return jsonMapper.readValue(response, SecretResponse.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||||
|
} catch (URISyntaxException ignored) {
|
||||||
|
/* this should never occur and may leak sensible information */
|
||||||
|
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final MetadataResponse readSecretMetadata(final String key) throws VaultConnectorException {
|
||||||
|
if (!isAuthorized()) {
|
||||||
|
throw new AuthorizationRequiredException();
|
||||||
|
}
|
||||||
|
/* Request HTTP response and parse secret metadata */
|
||||||
|
try {
|
||||||
|
String response = requestGet(PATH_SECRET + PATH_METADATA + key, new HashMap<>());
|
||||||
|
return jsonMapper.readValue(response, MetadataResponse.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InvalidResponseException(Error.PARSE_RESPONSE, e);
|
||||||
|
} catch (URISyntaxException ignored) {
|
||||||
|
/* this should never occur and may leak sensible information */
|
||||||
|
throw new InvalidRequestException(Error.URI_FORMAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final List<String> list(final String path) throws VaultConnectorException {
|
public final List<String> list(final String path) throws VaultConnectorException {
|
||||||
if (!isAuthorized()) {
|
if (!isAuthorized()) {
|
||||||
@ -639,7 +682,7 @@ public class HTTPVaultConnector implements VaultConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void write(final String key, final Map<String, Object> data) throws VaultConnectorException {
|
public final void write(final String key, final Map<String, Object> data, final Map<String, Object> options) throws VaultConnectorException {
|
||||||
if (!isAuthorized()) {
|
if (!isAuthorized()) {
|
||||||
throw new AuthorizationRequiredException();
|
throw new AuthorizationRequiredException();
|
||||||
}
|
}
|
||||||
@ -648,7 +691,18 @@ public class HTTPVaultConnector implements VaultConnector {
|
|||||||
throw new InvalidRequestException("Secret path must not be empty.");
|
throw new InvalidRequestException("Secret path must not be empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!requestPost(key, data).isEmpty()) {
|
// By default data is directly passed as payload.
|
||||||
|
Object payload = data;
|
||||||
|
|
||||||
|
// If options are given, split payload in two parts.
|
||||||
|
if (options != null) {
|
||||||
|
Map<String, Object> payloadMap = new HashMap<>();
|
||||||
|
payloadMap.put("data", data);
|
||||||
|
payloadMap.put("options", options);
|
||||||
|
payload = payloadMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requestPost(key, payload).isEmpty()) {
|
||||||
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -668,6 +722,56 @@ public class HTTPVaultConnector implements VaultConnector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void deleteLatestSecretVersion(final String key) throws VaultConnectorException {
|
||||||
|
delete(PATH_SECRET + PATH_DATA + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void deleteAllSecretVersions(final String key) throws VaultConnectorException {
|
||||||
|
delete(PATH_SECRET + PATH_METADATA + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void deleteSecretVersions(final String key, final int... versions) throws VaultConnectorException {
|
||||||
|
handleSecretVersions(PATH_DELETE, key, versions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void undeleteSecretVersions(final String key, final int... versions) throws VaultConnectorException {
|
||||||
|
handleSecretVersions(PATH_UNDELETE, key, versions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void destroySecretVersions(final String key, final int... versions) throws VaultConnectorException {
|
||||||
|
handleSecretVersions(PATH_DESTROY, key, versions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common method to bundle secret version operations.
|
||||||
|
*
|
||||||
|
* @param pathPart Path part to query.
|
||||||
|
* @param key Secret key.
|
||||||
|
* @param versions Versions to handle.
|
||||||
|
* @throws VaultConnectorException on error
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
private void handleSecretVersions(final String pathPart, final String key, final int... versions) throws VaultConnectorException {
|
||||||
|
if (!isAuthorized()) {
|
||||||
|
throw new AuthorizationRequiredException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Request HTTP response and expect empty result */
|
||||||
|
Map<String, Object> payload = new HashMap<>();
|
||||||
|
payload.put("versions", versions);
|
||||||
|
String response = requestPost(PATH_SECRET + pathPart + key, payload);
|
||||||
|
|
||||||
|
/* Response should be code 204 without content */
|
||||||
|
if (!response.isEmpty()) {
|
||||||
|
throw new InvalidResponseException(Error.UNEXPECTED_RESPONSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void revoke(final String leaseID) throws VaultConnectorException {
|
public final void revoke(final String leaseID) throws VaultConnectorException {
|
||||||
if (!isAuthorized()) {
|
if (!isAuthorized()) {
|
||||||
|
@ -408,6 +408,42 @@ public interface VaultConnector extends AutoCloseable, Serializable {
|
|||||||
return read(PATH_SECRET + "/" + key);
|
return read(PATH_SECRET + "/" + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the latest secret data for specific version from Vault.
|
||||||
|
* Prefix "secret/data" is automatically added to key. Only available for KV v2 secrets.
|
||||||
|
*
|
||||||
|
* @param key Secret identifier
|
||||||
|
* @return Secret response
|
||||||
|
* @throws VaultConnectorException on error
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
default SecretResponse readSecretData(final String key) throws VaultConnectorException {
|
||||||
|
return readSecretVersion(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve secret data from Vault.
|
||||||
|
* Prefix "secret/data" is automatically added to key. Only available for KV v2 secrets.
|
||||||
|
*
|
||||||
|
* @param key Secret identifier
|
||||||
|
* @param version Version to read. If {@code null} or zero, the latest version will be returned.
|
||||||
|
* @return Secret response
|
||||||
|
* @throws VaultConnectorException on error
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
SecretResponse readSecretVersion(final String key, final Integer version) throws VaultConnectorException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve secret metadata from Vault.
|
||||||
|
* Prefix "secret/metadata" is automatically added to key. Only available for KV v2 secrets.
|
||||||
|
*
|
||||||
|
* @param key Secret identifier
|
||||||
|
* @return Metadata response
|
||||||
|
* @throws VaultConnectorException on error
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
MetadataResponse readSecretMetadata(final String key) throws VaultConnectorException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List available nodes from Vault.
|
* List available nodes from Vault.
|
||||||
*
|
*
|
||||||
@ -452,7 +488,20 @@ public interface VaultConnector extends AutoCloseable, Serializable {
|
|||||||
* @throws VaultConnectorException on error
|
* @throws VaultConnectorException on error
|
||||||
* @since 0.5.0
|
* @since 0.5.0
|
||||||
*/
|
*/
|
||||||
void write(final String key, final Map<String, Object> data) throws VaultConnectorException;
|
default void write(final String key, final Map<String, Object> data) throws VaultConnectorException {
|
||||||
|
write(key, data, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write value to Vault.
|
||||||
|
*
|
||||||
|
* @param key Secret path
|
||||||
|
* @param data Secret content. Value must be be JSON serializable.
|
||||||
|
* @param options Secret options (optional).
|
||||||
|
* @throws VaultConnectorException on error
|
||||||
|
* @since 0.8 {@code options} parameter added
|
||||||
|
*/
|
||||||
|
void write(final String key, final Map<String, Object> data, final Map<String, Object> options) throws VaultConnectorException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write secret to Vault.
|
* Write secret to Vault.
|
||||||
@ -504,6 +553,59 @@ public interface VaultConnector extends AutoCloseable, Serializable {
|
|||||||
delete(PATH_SECRET + "/" + key);
|
delete(PATH_SECRET + "/" + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete latest version of a secret from Vault.
|
||||||
|
* Only available for KV v2 stores.
|
||||||
|
*
|
||||||
|
* @param key Secret path.
|
||||||
|
* @throws VaultConnectorException on error
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
void deleteLatestSecretVersion(final String key) throws VaultConnectorException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete latest version of a secret from Vault.
|
||||||
|
* Only available for KV v2 stores.
|
||||||
|
*
|
||||||
|
* @param key Secret path.
|
||||||
|
* @throws VaultConnectorException on error
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
void deleteAllSecretVersions(final String key) throws VaultConnectorException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete secret versions from Vault.
|
||||||
|
* Only available for KV v2 stores.
|
||||||
|
*
|
||||||
|
* @param key Secret path.
|
||||||
|
* @param versions Versions of the secret to delete.
|
||||||
|
* @throws VaultConnectorException on error
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
void deleteSecretVersions(final String key, final int... versions) throws VaultConnectorException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undelete (restore) secret versions from Vault.
|
||||||
|
* Only available for KV v2 stores.
|
||||||
|
*
|
||||||
|
* @param key Secret path.
|
||||||
|
* @param versions Versions of the secret to undelete.
|
||||||
|
* @throws VaultConnectorException on error
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
void undeleteSecretVersions(final String key, final int... versions) throws VaultConnectorException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy secret versions from Vault.
|
||||||
|
* Only available for KV v2 stores.
|
||||||
|
*
|
||||||
|
* @param key Secret path.
|
||||||
|
* @param versions Versions of the secret to destroy.
|
||||||
|
* @throws VaultConnectorException on error
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
void destroySecretVersions(final String key, final int... versions) throws VaultConnectorException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revoke given lease immediately.
|
* Revoke given lease immediately.
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016-2018 Stefan Kalscheuer
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.SecretMetadata;
|
||||||
|
import de.stklcode.jvault.connector.model.response.embedded.VersionMetadata;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vault response for secret metadata (KV v2).
|
||||||
|
*
|
||||||
|
* @author Stefan Kalscheuer
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class MetadataResponse extends VaultDataResponse {
|
||||||
|
|
||||||
|
private SecretMetadata metadata;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setData(final Map<String, Object> data) throws InvalidResponseException {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
try {
|
||||||
|
this.metadata = mapper.readValue(mapper.writeValueAsString(data), SecretMetadata.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InvalidResponseException("Failed deserializing response", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actual metadata.
|
||||||
|
*
|
||||||
|
* @return Metadata.
|
||||||
|
*/
|
||||||
|
public SecretMetadata getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ package de.stklcode.jvault.connector.model.response;
|
|||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import de.stklcode.jvault.connector.exception.InvalidResponseException;
|
import de.stklcode.jvault.connector.exception.InvalidResponseException;
|
||||||
|
import de.stklcode.jvault.connector.model.response.embedded.VersionMetadata;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -33,10 +34,25 @@ import java.util.Map;
|
|||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class SecretResponse extends VaultDataResponse {
|
public class SecretResponse extends VaultDataResponse {
|
||||||
private Map<String, Object> data;
|
private Map<String, Object> data;
|
||||||
|
private VersionMetadata metadata;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void setData(final Map<String, Object> data) throws InvalidResponseException {
|
public final void setData(final Map<String, Object> data) throws InvalidResponseException {
|
||||||
this.data = data;
|
if (data.size() == 2
|
||||||
|
&& data.containsKey("data") && data.get("data") instanceof Map
|
||||||
|
&& data.containsKey("metadata") && data.get("metadata") instanceof Map) {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
try {
|
||||||
|
// This is apparently a KV v2 value.
|
||||||
|
this.data = (Map<String, Object>) data.get("data");
|
||||||
|
this.metadata = mapper.readValue(mapper.writeValueAsString(data.get("metadata")), VersionMetadata.class);
|
||||||
|
} catch (ClassCastException | IOException e) {
|
||||||
|
throw new InvalidResponseException("Failed deserializing response", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For KV v1 without metadata just store the data map.
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,6 +68,16 @@ public class SecretResponse extends VaultDataResponse {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get secret metadata. This is only available for KV v2 secrets.
|
||||||
|
*
|
||||||
|
* @return Metadata of the secret.
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
public final VersionMetadata getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a single value for given key.
|
* Get a single value for given key.
|
||||||
*
|
*
|
||||||
@ -100,7 +126,7 @@ public class SecretResponse extends VaultDataResponse {
|
|||||||
/**
|
/**
|
||||||
* Get response parsed as JSON.
|
* Get response parsed as JSON.
|
||||||
*
|
*
|
||||||
* @param key the key
|
* @param key the key
|
||||||
* @param type Class to parse response
|
* @param type Class to parse response
|
||||||
* @param <T> Class to parse response
|
* @param <T> Class to parse response
|
||||||
* @return Parsed object or {@code null} if absent
|
* @return Parsed object or {@code null} if absent
|
||||||
|
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016-2018 Stefan Kalscheuer
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.stklcode.jvault.connector.model.response.embedded;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Embedded metadata for Key-Value v2 secrets.
|
||||||
|
*
|
||||||
|
* @author Stefan Kalscheuer
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public final class SecretMetadata {
|
||||||
|
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSX");
|
||||||
|
|
||||||
|
@JsonProperty("created_time")
|
||||||
|
private String createdTimeString;
|
||||||
|
|
||||||
|
@JsonProperty("current_version")
|
||||||
|
private Integer currentVersion;
|
||||||
|
|
||||||
|
@JsonProperty("max_versions")
|
||||||
|
private Integer maxVersions;
|
||||||
|
|
||||||
|
@JsonProperty("oldest_version")
|
||||||
|
private Integer oldestVersion;
|
||||||
|
|
||||||
|
@JsonProperty("updated_time")
|
||||||
|
private String updatedTime;
|
||||||
|
|
||||||
|
@JsonProperty("versions")
|
||||||
|
private Map<Integer, VersionMetadata> versions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Time of secret creation as raw string representation.
|
||||||
|
*/
|
||||||
|
public String getCreatedTimeString() {
|
||||||
|
return createdTimeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Time of secret creation.
|
||||||
|
*/
|
||||||
|
public ZonedDateTime getCreatedTime() {
|
||||||
|
if (createdTimeString != null && !createdTimeString.isEmpty()) {
|
||||||
|
try {
|
||||||
|
return ZonedDateTime.parse(createdTimeString, TIME_FORMAT);
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Current version number.
|
||||||
|
*/
|
||||||
|
public Integer getCurrentVersion() {
|
||||||
|
return currentVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Maximum number of versions.
|
||||||
|
*/
|
||||||
|
public Integer getMaxVersions() {
|
||||||
|
return maxVersions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Oldest available version number.
|
||||||
|
*/
|
||||||
|
public Integer getOldestVersion() {
|
||||||
|
return oldestVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Time of secret update as raw string representation.
|
||||||
|
*/
|
||||||
|
public String getUpdatedTimeString() {
|
||||||
|
return updatedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Time of secret update..
|
||||||
|
*/
|
||||||
|
public ZonedDateTime getUpdatedTime() {
|
||||||
|
if (updatedTime != null && !updatedTime.isEmpty()) {
|
||||||
|
try {
|
||||||
|
return ZonedDateTime.parse(updatedTime, TIME_FORMAT);
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Version of the entry.
|
||||||
|
*/
|
||||||
|
public Map<Integer, VersionMetadata> getVersions() {
|
||||||
|
return versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016-2018 Stefan Kalscheuer
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.stklcode.jvault.connector.model.response.embedded;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Embedded metadata for a single Key-Value v2 version.
|
||||||
|
*
|
||||||
|
* @author Stefan Kalscheuer
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public final class VersionMetadata {
|
||||||
|
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSX");
|
||||||
|
|
||||||
|
@JsonProperty("created_time")
|
||||||
|
private String createdTimeString;
|
||||||
|
|
||||||
|
@JsonProperty("deletion_time")
|
||||||
|
private String deletionTimeString;
|
||||||
|
|
||||||
|
@JsonProperty("destroyed")
|
||||||
|
private boolean destroyed;
|
||||||
|
|
||||||
|
@JsonProperty("version")
|
||||||
|
private Integer version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Time of secret creation as raw string representation.
|
||||||
|
*/
|
||||||
|
public String getCreatedTimeString() {
|
||||||
|
return createdTimeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Time of secret creation.
|
||||||
|
*/
|
||||||
|
public ZonedDateTime getCreatedTime() {
|
||||||
|
if (createdTimeString != null && !createdTimeString.isEmpty()) {
|
||||||
|
try {
|
||||||
|
return ZonedDateTime.parse(createdTimeString, TIME_FORMAT);
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Time for secret deletion as raw string representation.
|
||||||
|
*/
|
||||||
|
public String getDeletionTimeString() {
|
||||||
|
return deletionTimeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Time for secret deletion.
|
||||||
|
*/
|
||||||
|
public ZonedDateTime getDeletionTime() {
|
||||||
|
if (deletionTimeString != null && !deletionTimeString.isEmpty()) {
|
||||||
|
try {
|
||||||
|
return ZonedDateTime.parse(deletionTimeString, TIME_FORMAT);
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Whether the secret is destroyed.
|
||||||
|
*/
|
||||||
|
public boolean isDestroyed() {
|
||||||
|
return destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Version of the entry.
|
||||||
|
*/
|
||||||
|
public Integer getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -77,6 +77,12 @@ public class HTTPVaultConnectorTest {
|
|||||||
private static final String SECRET_KEY_JSON = "json";
|
private static final String SECRET_KEY_JSON = "json";
|
||||||
private static final String SECRET_KEY_COMPLEX = "complex";
|
private static final String SECRET_KEY_COMPLEX = "complex";
|
||||||
|
|
||||||
|
// KV v2 secret with 2 versions.
|
||||||
|
private static final String PATH_KV2 = "kv/";
|
||||||
|
private static final String SECRET2_KEY = "foo2";
|
||||||
|
private static final String SECRET2_VALUE1 = "bar2";
|
||||||
|
private static final String SECRET2_VALUE2 = "bar3";
|
||||||
|
|
||||||
private Process vaultProcess;
|
private Process vaultProcess;
|
||||||
private VaultConnector connector;
|
private VaultConnector connector;
|
||||||
|
|
||||||
@ -731,6 +737,123 @@ public class HTTPVaultConnectorTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test reading of secrets from KV v2 store.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void readSecretV2Test() {
|
||||||
|
authUser();
|
||||||
|
assumeTrue(connector.isAuthorized());
|
||||||
|
|
||||||
|
// Try to read accessible path with known value.
|
||||||
|
SecretResponse res;
|
||||||
|
try {
|
||||||
|
res = connector.readSecretData(SECRET2_KEY);
|
||||||
|
assertThat("Metadata not populated for KV v2 secret", res.getMetadata(), is(notNullValue()));
|
||||||
|
assertThat("Unexpected secret version", res.getMetadata().getVersion(), is(2));
|
||||||
|
assertThat("Known secret returned invalid value.", res.getValue(), is(SECRET2_VALUE2));
|
||||||
|
} catch (VaultConnectorException e) {
|
||||||
|
fail("Valid secret path could not be read: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read different version of same secret.
|
||||||
|
try {
|
||||||
|
res = connector.readSecretVersion(SECRET2_KEY, 1);
|
||||||
|
assertThat("Unexpected secret version", res.getMetadata().getVersion(), is(1));
|
||||||
|
assertThat("Known secret returned invalid value.", res.getValue(), is(SECRET2_VALUE1));
|
||||||
|
} catch (VaultConnectorException e) {
|
||||||
|
fail("Valid secret version could not be read: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test reading of secret metadata from KV v2 store.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void readSecretMetadataTest() {
|
||||||
|
authUser();
|
||||||
|
assumeTrue(connector.isAuthorized());
|
||||||
|
|
||||||
|
// Try to read accessible path with known value.
|
||||||
|
try {
|
||||||
|
MetadataResponse res = connector.readSecretMetadata(SECRET2_KEY);
|
||||||
|
assertThat("Metadata not populated for KV v2 secret", res.getMetadata(), is(notNullValue()));
|
||||||
|
assertThat("Unexpected secret version", res.getMetadata().getCurrentVersion(), is(2));
|
||||||
|
assertThat("Unexpected number of secret versions", res.getMetadata().getVersions().size(), is(2));
|
||||||
|
assertThat("Creation date should be present", res.getMetadata().getCreatedTime(), is(notNullValue()));
|
||||||
|
assertThat("Update date should be present", res.getMetadata().getUpdatedTime(), is(notNullValue()));
|
||||||
|
} catch (VaultConnectorException e) {
|
||||||
|
fail("Valid secret path could not be read: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test deleting specific secret versions from KV v2 store.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void handleSecretVersionsTest() {
|
||||||
|
authUser();
|
||||||
|
assumeTrue(connector.isAuthorized());
|
||||||
|
|
||||||
|
// Try to delete inexisting versions.
|
||||||
|
MetadataResponse meta;
|
||||||
|
try {
|
||||||
|
connector.deleteSecretVersions(SECRET2_KEY, 5, 42);
|
||||||
|
meta = connector.readSecretMetadata(SECRET2_KEY);
|
||||||
|
} catch (VaultConnectorException e) {
|
||||||
|
fail("Revealed non-existence of secret versions");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now delete existing version and verify.
|
||||||
|
try {
|
||||||
|
connector.deleteSecretVersions(SECRET2_KEY, 1);
|
||||||
|
meta = connector.readSecretMetadata(SECRET2_KEY);
|
||||||
|
assertThat("Expected deletion time for secret 1", meta.getMetadata().getVersions().get(1).getDeletionTime(), is(notNullValue()));
|
||||||
|
} catch (VaultConnectorException e) {
|
||||||
|
fail("Deleting existing version failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undelete the just deleted version.
|
||||||
|
try {
|
||||||
|
connector.undeleteSecretVersions(SECRET2_KEY, 1);
|
||||||
|
meta = connector.readSecretMetadata(SECRET2_KEY);
|
||||||
|
assertThat("Expected deletion time for secret 1 to be reset", meta.getMetadata().getVersions().get(1).getDeletionTime(), is(nullValue()));
|
||||||
|
} catch (VaultConnectorException e) {
|
||||||
|
fail("Undeleting existing version failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now destroy it.
|
||||||
|
try {
|
||||||
|
connector.destroySecretVersions(SECRET2_KEY, 1);
|
||||||
|
meta = connector.readSecretMetadata(SECRET2_KEY);
|
||||||
|
assertThat("Expected secret 1 to be marked destroyed", meta.getMetadata().getVersions().get(1).isDestroyed(), is(true));
|
||||||
|
} catch (VaultConnectorException e) {
|
||||||
|
fail("Destroying existing version failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete latest version.
|
||||||
|
try {
|
||||||
|
connector.deleteLatestSecretVersion(SECRET2_KEY);
|
||||||
|
meta = connector.readSecretMetadata(SECRET2_KEY);
|
||||||
|
assertThat("Expected secret 2 to be deleted", meta.getMetadata().getVersions().get(2).getDeletionTime(), is(notNullValue()));
|
||||||
|
} catch (VaultConnectorException e) {
|
||||||
|
fail("Deleting latest version failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all versions.
|
||||||
|
try {
|
||||||
|
connector.deleteAllSecretVersions(SECRET2_KEY);
|
||||||
|
} catch (VaultConnectorException e) {
|
||||||
|
fail("Deleting latest version failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
connector.readSecretMetadata(SECRET2_KEY);
|
||||||
|
fail("Reading metadata of deleted secret should not succeed");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e, is(instanceOf(InvalidResponseException.class)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test listing secrets.
|
* Test listing secrets.
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016-2018 Stefan Kalscheuer
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.stklcode.jvault.connector.model.response;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import de.stklcode.jvault.connector.exception.InvalidResponseException;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JUnit Test for {@link MetadataResponse} model.
|
||||||
|
*
|
||||||
|
* @author Stefan Kalscheuer
|
||||||
|
* @since 0.8
|
||||||
|
*/
|
||||||
|
public class MetadataResponseTest {
|
||||||
|
private static final String V1_TIME = "2018-03-22T02:24:06.945319214Z";
|
||||||
|
private static final String V3_TIME = "2018-03-22T02:36:43.986212308Z";
|
||||||
|
private static final String V2_TIME = "2018-03-22T02:36:33.954880664Z";
|
||||||
|
private static final Integer CURRENT_VERSION = 3;
|
||||||
|
private static final Integer MAX_VERSIONS = 0;
|
||||||
|
private static final Integer OLDEST_VERSION = 1;
|
||||||
|
|
||||||
|
private static final String META_JSON = "{\n" +
|
||||||
|
" \"data\": {\n" +
|
||||||
|
" \"created_time\": \"" + V1_TIME + "\",\n" +
|
||||||
|
" \"current_version\": " + CURRENT_VERSION + ",\n" +
|
||||||
|
" \"max_versions\": " + MAX_VERSIONS + ",\n" +
|
||||||
|
" \"oldest_version\": " + OLDEST_VERSION + ",\n" +
|
||||||
|
" \"updated_time\": \"" + V3_TIME + "\",\n" +
|
||||||
|
" \"versions\": {\n" +
|
||||||
|
" \"1\": {\n" +
|
||||||
|
" \"created_time\": \"" + V1_TIME + "\",\n" +
|
||||||
|
" \"deletion_time\": \"" + V2_TIME + "\",\n" +
|
||||||
|
" \"destroyed\": true\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"2\": {\n" +
|
||||||
|
" \"created_time\": \"" + V2_TIME + "\",\n" +
|
||||||
|
" \"deletion_time\": \"\",\n" +
|
||||||
|
" \"destroyed\": false\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"3\": {\n" +
|
||||||
|
" \"created_time\": \"" + V3_TIME + "\",\n" +
|
||||||
|
" \"deletion_time\": \"\",\n" +
|
||||||
|
" \"destroyed\": false\n" +
|
||||||
|
" }\n" +
|
||||||
|
" }\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test creation from JSON value as returned by Vault (JSON example copied from Vault documentation).
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void jsonRoundtrip() {
|
||||||
|
try {
|
||||||
|
MetadataResponse res = new ObjectMapper().readValue(META_JSON, MetadataResponse.class);
|
||||||
|
assertThat("Parsed response is NULL", res, is(notNullValue()));
|
||||||
|
assertThat("Parsed metadatra is NULL", res.getMetadata(), is(notNullValue()));
|
||||||
|
assertThat("Incorrect created time", res.getMetadata().getCreatedTimeString(), is(V1_TIME));
|
||||||
|
assertThat("Parting created time failed", res.getMetadata().getCreatedTime(), is(notNullValue()));
|
||||||
|
assertThat("Incorrect current version", res.getMetadata().getCurrentVersion(), is(CURRENT_VERSION));
|
||||||
|
assertThat("Incorrect max versions", res.getMetadata().getMaxVersions(), is(MAX_VERSIONS));
|
||||||
|
assertThat("Incorrect oldest version", res.getMetadata().getOldestVersion(), is(OLDEST_VERSION));
|
||||||
|
assertThat("Incorrect updated time", res.getMetadata().getUpdatedTimeString(), is(V3_TIME));
|
||||||
|
assertThat("Parting updated time failed", res.getMetadata().getUpdatedTime(), is(notNullValue()));
|
||||||
|
assertThat("Incorrect number of versions", res.getMetadata().getVersions().size(), is(3));
|
||||||
|
assertThat("Incorrect version 1 delete time", res.getMetadata().getVersions().get(1).getDeletionTimeString(), is(V2_TIME));
|
||||||
|
assertThat("Parsion version delete time failed", res.getMetadata().getVersions().get(1).getDeletionTime(), is(notNullValue()));
|
||||||
|
assertThat("Incorrect version 1 destroyed state", res.getMetadata().getVersions().get(1).isDestroyed(), is(true));
|
||||||
|
assertThat("Incorrect version 2 created time", res.getMetadata().getVersions().get(2).getCreatedTimeString(), is(V2_TIME));
|
||||||
|
assertThat("Parsion version created failed", res.getMetadata().getVersions().get(2).getCreatedTime(), is(notNullValue()));
|
||||||
|
assertThat("Incorrect version 3 destroyed state", res.getMetadata().getVersions().get(3).isDestroyed(), is(false));
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
fail("MetadataResoponse deserialization failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,6 +53,8 @@ public class SecretResponseTest {
|
|||||||
private static final String SECRET_DATA_V1 = "yes";
|
private static final String SECRET_DATA_V1 = "yes";
|
||||||
private static final String SECRET_DATA_K2 = "value";
|
private static final String SECRET_DATA_K2 = "value";
|
||||||
private static final String SECRET_DATA_V2 = "world";
|
private static final String SECRET_DATA_V2 = "world";
|
||||||
|
private static final String SECRET_META_CREATED = "2018-03-22T02:24:06.945319214Z";
|
||||||
|
private static final String SECRET_META_DELETED = "2018-03-23T03:25:07.056420325Z";
|
||||||
private static final List<String> SECRET_WARNINGS = null;
|
private static final List<String> SECRET_WARNINGS = null;
|
||||||
private static final String SECRET_JSON = "{\n" +
|
private static final String SECRET_JSON = "{\n" +
|
||||||
" \"request_id\": \"" + SECRET_REQUEST_ID + "\",\n" +
|
" \"request_id\": \"" + SECRET_REQUEST_ID + "\",\n" +
|
||||||
@ -65,6 +67,44 @@ public class SecretResponseTest {
|
|||||||
" },\n" +
|
" },\n" +
|
||||||
" \"warnings\": " + SECRET_WARNINGS + "\n" +
|
" \"warnings\": " + SECRET_WARNINGS + "\n" +
|
||||||
"}";
|
"}";
|
||||||
|
private static final String SECRET_JSON_V2 = "{\n" +
|
||||||
|
" \"request_id\": \"" + SECRET_REQUEST_ID + "\",\n" +
|
||||||
|
" \"lease_id\": \"" + SECRET_LEASE_ID + "\",\n" +
|
||||||
|
" \"lease_duration\": " + SECRET_LEASE_DURATION + ",\n" +
|
||||||
|
" \"renewable\": " + SECRET_RENEWABLE + ",\n" +
|
||||||
|
" \"data\": {\n" +
|
||||||
|
" \"data\": {\n" +
|
||||||
|
" \"" + SECRET_DATA_K1 + "\": \"" + SECRET_DATA_V1 + "\",\n" +
|
||||||
|
" \"" + SECRET_DATA_K2 + "\": \"" + SECRET_DATA_V2 + "\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"metadata\": {\n" +
|
||||||
|
" \"created_time\": \"" + SECRET_META_CREATED + "\",\n" +
|
||||||
|
" \"deletion_time\": \"\",\n" +
|
||||||
|
" \"destroyed\": false,\n" +
|
||||||
|
" \"version\": 1\n" +
|
||||||
|
" }\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"warnings\": " + SECRET_WARNINGS + "\n" +
|
||||||
|
"}";
|
||||||
|
private static final String SECRET_JSON_V2_2 = "{\n" +
|
||||||
|
" \"request_id\": \"" + SECRET_REQUEST_ID + "\",\n" +
|
||||||
|
" \"lease_id\": \"" + SECRET_LEASE_ID + "\",\n" +
|
||||||
|
" \"lease_duration\": " + SECRET_LEASE_DURATION + ",\n" +
|
||||||
|
" \"renewable\": " + SECRET_RENEWABLE + ",\n" +
|
||||||
|
" \"data\": {\n" +
|
||||||
|
" \"data\": {\n" +
|
||||||
|
" \"" + SECRET_DATA_K1 + "\": \"" + SECRET_DATA_V1 + "\",\n" +
|
||||||
|
" \"" + SECRET_DATA_K2 + "\": \"" + SECRET_DATA_V2 + "\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"metadata\": {\n" +
|
||||||
|
" \"created_time\": \"" + SECRET_META_CREATED + "\",\n" +
|
||||||
|
" \"deletion_time\": \"" + SECRET_META_DELETED + "\",\n" +
|
||||||
|
" \"destroyed\": true,\n" +
|
||||||
|
" \"version\": 2\n" +
|
||||||
|
" }\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"warnings\": " + SECRET_WARNINGS + "\n" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -118,16 +158,49 @@ public class SecretResponseTest {
|
|||||||
@Test
|
@Test
|
||||||
public void jsonRoundtrip() {
|
public void jsonRoundtrip() {
|
||||||
try {
|
try {
|
||||||
SecretResponse res = new ObjectMapper().readValue(SECRET_JSON, SecretResponse.class);
|
assertSecretData(new ObjectMapper().readValue(SECRET_JSON, SecretResponse.class));
|
||||||
assertThat("Parsed response is NULL", res, is(notNullValue()));
|
} catch (IOException e) {
|
||||||
assertThat("Incorrect lease ID", res.getLeaseId(), is(SECRET_LEASE_ID));
|
fail("SecretResponse deserialization failed: " + e.getMessage());
|
||||||
assertThat("Incorrect lease duration", res.getLeaseDuration(), is(SECRET_LEASE_DURATION));
|
}
|
||||||
assertThat("Incorrect renewable status", res.isRenewable(), is(SECRET_RENEWABLE));
|
|
||||||
assertThat("Incorrect warnings", res.getWarnings(), is(SECRET_WARNINGS));
|
// KV v2 secret.
|
||||||
assertThat("Response does not contain correct data", res.get(SECRET_DATA_K1), is(SECRET_DATA_V1));
|
try {
|
||||||
assertThat("Response does not contain correct data", res.get(SECRET_DATA_K2), is(SECRET_DATA_V2));
|
SecretResponse res = new ObjectMapper().readValue(SECRET_JSON_V2, SecretResponse.class);
|
||||||
|
assertSecretData(res);
|
||||||
|
assertThat("SecretResponse does not contain metadata", res.getMetadata(), is(notNullValue()));
|
||||||
|
assertThat("Incorrect creation date string", res.getMetadata().getCreatedTimeString(), is(SECRET_META_CREATED));
|
||||||
|
assertThat("Creation date parsing failed", res.getMetadata().getCreatedTime(), is(notNullValue()));
|
||||||
|
assertThat("Incorrect deletion date string", res.getMetadata().getDeletionTimeString(), is(emptyString()));
|
||||||
|
assertThat("Incorrect deletion date", res.getMetadata().getDeletionTime(), is(nullValue()));
|
||||||
|
assertThat("Secret destroyed when not expected", res.getMetadata().isDestroyed(), is(false));
|
||||||
|
assertThat("Incorrect secret version", res.getMetadata().getVersion(), is(1));
|
||||||
|
} catch (IOException e) {
|
||||||
|
fail("SecretResponse deserialization failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleted KV v2 secret.
|
||||||
|
try {
|
||||||
|
SecretResponse res = new ObjectMapper().readValue(SECRET_JSON_V2_2, SecretResponse.class);
|
||||||
|
assertSecretData(res);
|
||||||
|
assertThat("SecretResponse does not contain metadata", res.getMetadata(), is(notNullValue()));
|
||||||
|
assertThat("Incorrect creation date string", res.getMetadata().getCreatedTimeString(), is(SECRET_META_CREATED));
|
||||||
|
assertThat("Creation date parsing failed", res.getMetadata().getCreatedTime(), is(notNullValue()));
|
||||||
|
assertThat("Incorrect deletion date string", res.getMetadata().getDeletionTimeString(), is(SECRET_META_DELETED));
|
||||||
|
assertThat("Incorrect deletion date", res.getMetadata().getDeletionTime(), is(notNullValue()));
|
||||||
|
assertThat("Secret destroyed when not expected", res.getMetadata().isDestroyed(), is(true));
|
||||||
|
assertThat("Incorrect secret version", res.getMetadata().getVersion(), is(2));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
fail("SecretResponse deserialization failed: " + e.getMessage());
|
fail("SecretResponse deserialization failed: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertSecretData(SecretResponse res) {
|
||||||
|
assertThat("Parsed response is NULL", res, is(notNullValue()));
|
||||||
|
assertThat("Incorrect lease ID", res.getLeaseId(), is(SECRET_LEASE_ID));
|
||||||
|
assertThat("Incorrect lease duration", res.getLeaseDuration(), is(SECRET_LEASE_DURATION));
|
||||||
|
assertThat("Incorrect renewable status", res.isRenewable(), is(SECRET_RENEWABLE));
|
||||||
|
assertThat("Incorrect warnings", res.getWarnings(), is(SECRET_WARNINGS));
|
||||||
|
assertThat("Response does not contain correct data", res.get(SECRET_DATA_K1), is(SECRET_DATA_V1));
|
||||||
|
assertThat("Response does not contain correct data", res.get(SECRET_DATA_K2), is(SECRET_DATA_V2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
{"Value":"AAAAAQLCgjqndhozT2JTFStJ8yqLGSlBsqtol6u7Rfl1oX1fIfYevraxwpCFORxRx3v77RDNX0xzXkJ1taJ8LVx/9m4GEp5XPh2AsB0nPy0Sfr0s1jqR4Ev8d+z6X01099F6mNfUAnx3gmGuubXZC28Sp3dLBf9Xy080mD0yd+GqlHp2WXnW0aWQKchWwArkTHRxR1722tkbXmr8E72aRz+5eyHapnWXnKhppznQPkGaOY2y9nxhoOM04FVqHA=="}
|
{"Value":"AAAAAQKh97jibnzLjYI1Er2PQ1+U2voHGqTowY24utgPth4i3fCwUbJ15i8JY/4DiqjjyNTfDni4deNiUNIsn2PzWsPsEoiDS83rVPDibov4TKjHFomxew1oEqXOTmqYlKZcaeR5FiDWMLA8jPwkkP/6mknRp3AnjUUGNvw5EJVFREBJh+qw52CCRztcPJ7lYyUuOfBn0OHLowTV9QlQ6zmyBSu+HY2xrFtucboe1l3VNUdAbq3qk139CGzn9chzUi6xBtUf2JLiuuyFYOcPMMtFu+tzq09WH07T4OuUp5l7ytI+9dTnyCtciHDZNlflZyaIssNoBFThiJ0GeeZK3wYSspb+wG86xS7MoGJZhAnlvXjvCebKY4hekX9O"}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user