Add support for policies.
We now support policy management via Vault's policy endpoint to enumerate policy names, read, write and delete policies. Policy parsing support is limited to JSON as there is no Java HCL parser. Closes gh-10.
This commit is contained in:
@@ -165,6 +165,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.skyscreamer</groupId>
|
||||
<artifactId>jsonassert</artifactId>
|
||||
<version>1.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
*/
|
||||
package org.springframework.vault.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.support.Policy;
|
||||
import org.springframework.vault.support.VaultHealth;
|
||||
import org.springframework.vault.support.VaultInitializationRequest;
|
||||
import org.springframework.vault.support.VaultInitializationResponse;
|
||||
@@ -126,6 +129,50 @@ public interface VaultSysOperations {
|
||||
*/
|
||||
void authUnmount(String path) throws VaultException;
|
||||
|
||||
/**
|
||||
* Lists policy names stored in Vault.
|
||||
*
|
||||
* @return policy names.
|
||||
* @since 2.0
|
||||
* @see <a href="https://www.vaultproject.io/api/system/policy.html">GET
|
||||
* /sys/policy/</a>
|
||||
*/
|
||||
List<String> getPolicyNames() throws VaultException;
|
||||
|
||||
/**
|
||||
* Read a {@link Policy} by its {@literal name}. Policies are either represented as
|
||||
* HCL (HashiCorp configuration language) or JSON. Retrieving policies is only
|
||||
* possible if the policy is represented as JSON.
|
||||
*
|
||||
* @return the {@link Policy} or {@literal null}, if the policy was not found.
|
||||
* @since 2.0
|
||||
* @throws UnsupportedOperationException if the policy is represented as HCL.
|
||||
* @see <a href="https://www.vaultproject.io/api/system/policy.html">GET
|
||||
* /sys/policy/{name}</a>
|
||||
*/
|
||||
@Nullable
|
||||
Policy getPolicy(String name) throws VaultException;
|
||||
|
||||
/**
|
||||
* Create or update a {@link Policy}.
|
||||
*
|
||||
* @param name the policy name, must not be {@literal null} or empty.
|
||||
* @since 2.0
|
||||
* @see <a href="https://www.vaultproject.io/api/system/policy.html">PUT
|
||||
* /sys/policy/{name}</a>
|
||||
*/
|
||||
void createOrUpdatePolicy(String name, Policy policy) throws VaultException;
|
||||
|
||||
/**
|
||||
* Delete a {@link Policy} by its {@literal name}.
|
||||
*
|
||||
* @param name the policy name, must not be {@literal null} or empty.
|
||||
* @since 2.0
|
||||
* @see <a href="https://www.vaultproject.io/api/system/policy.html">DELETE
|
||||
* /sys/policy/{name}</a>
|
||||
*/
|
||||
void deletePolicy(String name) throws VaultException;
|
||||
|
||||
/**
|
||||
* Return the health status of Vault.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.vault.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -31,15 +32,19 @@ import lombok.Data;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.client.VaultResponses;
|
||||
import org.springframework.vault.support.Policy;
|
||||
import org.springframework.vault.support.VaultHealth;
|
||||
import org.springframework.vault.support.VaultInitializationRequest;
|
||||
import org.springframework.vault.support.VaultInitializationResponse;
|
||||
import org.springframework.vault.support.VaultMount;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.vault.support.VaultResponseSupport;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.vault.support.VaultUnsealStatus;
|
||||
@@ -64,6 +69,8 @@ public class VaultSysTemplate implements VaultSysOperations {
|
||||
|
||||
private static final Health HEALTH = new Health();
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
private final VaultOperations vaultOperations;
|
||||
|
||||
/**
|
||||
@@ -194,6 +201,75 @@ public class VaultSysTemplate implements VaultSysOperations {
|
||||
vaultOperations.delete(String.format("sys/auth/%s", path));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<String> getPolicyNames() throws VaultException {
|
||||
return requireResponse((List<String>) vaultOperations.read("sys/policy")
|
||||
.getRequiredData().get("policies"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Policy getPolicy(String name) throws VaultException {
|
||||
|
||||
Assert.hasText(name, "Name must not be null or empty");
|
||||
|
||||
return vaultOperations.doWithSession(restOperations -> {
|
||||
|
||||
ResponseEntity<VaultResponse> response = restOperations.getForEntity(
|
||||
"sys/policy/{name}", VaultResponse.class, name);
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String rules = (String) response.getBody().getRequiredData().get("rules");
|
||||
|
||||
if (StringUtils.isEmpty(rules)) {
|
||||
return Policy.empty();
|
||||
}
|
||||
|
||||
if (rules.trim().startsWith("{")) {
|
||||
return VaultResponses.unwrap(rules, Policy.class);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Cannot parse policy in HCL format");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createOrUpdatePolicy(String name, Policy policy) throws VaultException {
|
||||
|
||||
Assert.hasText(name, "Name must not be null or empty");
|
||||
Assert.notNull(policy, "Policy must not be null");
|
||||
|
||||
String rules;
|
||||
|
||||
try {
|
||||
rules = OBJECT_MAPPER.writeValueAsString(policy);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new VaultException("Cannot serialize policy to JSON", e);
|
||||
}
|
||||
|
||||
vaultOperations.doWithSession(restOperations -> {
|
||||
|
||||
restOperations.exchange("sys/policy/{name}", HttpMethod.PUT,
|
||||
new HttpEntity<>(Collections.singletonMap("rules", rules)),
|
||||
VaultResponse.class, name);
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePolicy(String name) throws VaultException {
|
||||
|
||||
Assert.hasText(name, "Name must not be null or empty");
|
||||
|
||||
vaultOperations.delete(String.format("sys/policy/%s", name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultHealth health() {
|
||||
return requireResponse(vaultOperations.doWithVault(HEALTH));
|
||||
|
||||
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.vault.support;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
import com.fasterxml.jackson.databind.util.Converter;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.vault.support.Policy.PolicyDeserializer;
|
||||
import org.springframework.vault.support.Policy.PolicySerializer;
|
||||
|
||||
/**
|
||||
* Value object representing a Vault policy associated with {@link Rule}s. Instances of
|
||||
* {@link Policy} support JSON serialization and deserialization using Jackson.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
* @see Rule
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
*/
|
||||
@JsonSerialize(using = PolicySerializer.class)
|
||||
@JsonDeserialize(using = PolicyDeserializer.class)
|
||||
@EqualsAndHashCode
|
||||
public class Policy {
|
||||
|
||||
private static final Policy EMPTY = new Policy(Collections.emptySet());
|
||||
|
||||
private final Set<Rule> rules;
|
||||
|
||||
private Policy(Set<Rule> rules) {
|
||||
this.rules = rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty {@link Policy} without rules.
|
||||
*
|
||||
* @return an empty {@link Policy}.
|
||||
*/
|
||||
public static Policy empty() {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Policy} from one or more {@code rules}.
|
||||
*
|
||||
* @param rules must not be {@literal null}.
|
||||
* @return the {@link Policy} object containing {@code rules}.
|
||||
*/
|
||||
public static Policy of(Rule... rules) {
|
||||
|
||||
Assert.notNull(rules, "Rules must not be null");
|
||||
Assert.noNullElements(rules, "Rules must not contain null elements");
|
||||
|
||||
return new Policy(new LinkedHashSet<>(Arrays.asList(rules)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Policy} from one or more {@code rules}.
|
||||
*
|
||||
* @param rules must not be {@literal null}.
|
||||
* @return the {@link Policy} object containing {@code rules}.
|
||||
*/
|
||||
public static Policy of(Set<Rule> rules) {
|
||||
|
||||
Assert.notNull(rules, "Rules must not be null");
|
||||
|
||||
return new Policy(new LinkedHashSet<>(rules));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Policy} object containing all configured rules and add the
|
||||
* given {@link Rule} to the new policy object. If the given {@link Rule} matches an
|
||||
* existing rule path, the exiting rule will be overridden by the new rule object.
|
||||
*
|
||||
* @param rule must not be {@literal null}.
|
||||
* @return the new {@link Policy} object containing all configured rules and the given
|
||||
* {@link Rule}.
|
||||
*/
|
||||
public Policy with(Rule rule) {
|
||||
|
||||
Assert.notNull(rule, "Rule must not be null");
|
||||
|
||||
Set<Rule> rules = new LinkedHashSet<>(this.rules.size() + 1);
|
||||
rules.addAll(this.rules);
|
||||
rules.add(rule);
|
||||
|
||||
return new Policy(rules);
|
||||
}
|
||||
|
||||
public Set<Rule> getRules() {
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a {@link Rule} by its path. Returns {@literal null} if the rule was not
|
||||
* found.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @return the {@link Rule} or {@literal null}, if not found.
|
||||
*/
|
||||
@Nullable
|
||||
public Rule getRule(String path) {
|
||||
|
||||
Assert.notNull(path, "Path must not be null");
|
||||
|
||||
for (Rule rule : rules) {
|
||||
if (rule.getPath().equals(path)) {
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object representing a rule for a certain path. Rule equality is considered by
|
||||
* comparing only the path segment to guarante uniqueness within a {@link Set}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@EqualsAndHashCode(of = "path")
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
public static class Rule {
|
||||
|
||||
/**
|
||||
* Path or path with asterisk to which this rule applies to.
|
||||
*/
|
||||
@JsonIgnore
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* One or more capabilities which provide fine-grained control over permitted (or
|
||||
* denied) operations.
|
||||
*/
|
||||
@JsonSerialize(contentConverter = CapabilityToStringConverter.class)
|
||||
@JsonDeserialize(contentConverter = StringToCapabilityConverter.class)
|
||||
private final List<Capability> capabilities;
|
||||
|
||||
/**
|
||||
* The minimum allowed TTL that clients can specify for a wrapped response. In
|
||||
* practice, setting a minimum TTL of one second effectively makes response
|
||||
* wrapping mandatory for a particular path.
|
||||
*/
|
||||
@JsonProperty("min_wrapping_ttl")
|
||||
@JsonSerialize(converter = DurationToStringConverter.class)
|
||||
@Nullable
|
||||
private final Duration minWrappingTtl;
|
||||
|
||||
/**
|
||||
* The maximum allowed TTL that clients can specify for a wrapped response.
|
||||
*/
|
||||
@JsonProperty("max_wrapping_ttl")
|
||||
@JsonSerialize(converter = DurationToStringConverter.class)
|
||||
@Nullable
|
||||
private final Duration maxWrappingTtl;
|
||||
|
||||
/**
|
||||
* Whitelists a list of keys and values that are permitted on the given path.
|
||||
* Setting a parameter with a value of a populated list allows the parameter to
|
||||
* contain only those values.
|
||||
*/
|
||||
@JsonProperty("allowed_parameters")
|
||||
private final Map<String, List<String>> allowedParameters;
|
||||
|
||||
/**
|
||||
* Blacklists a list of parameter and values. Any values specified here take
|
||||
* precedence over {@link #allowedParameters}. Setting a parameter with a value of
|
||||
* a populated list denies any parameter containing those values. Setting to
|
||||
* {@literal *} will deny any parameter.
|
||||
*/
|
||||
@JsonProperty("denied_parameters")
|
||||
private final Map<String, List<String>> deniedParameters;
|
||||
|
||||
@JsonCreator
|
||||
private Rule(
|
||||
@JsonProperty("capabilities") List<Capability> capabilities,
|
||||
@JsonProperty("min_wrapping_ttl") @JsonDeserialize(converter = StringToDurationConverter.class) Duration minWrappingTtl,
|
||||
@JsonProperty("max_wrapping_ttl") @JsonDeserialize(converter = StringToDurationConverter.class) Duration maxWrappingTtl,
|
||||
@JsonProperty("allowed_parameters") Map<String, List<String>> allowedParameters,
|
||||
@JsonProperty("denied_parameters") Map<String, List<String>> deniedParameters) {
|
||||
|
||||
this.path = "";
|
||||
this.capabilities = capabilities;
|
||||
this.minWrappingTtl = minWrappingTtl;
|
||||
this.maxWrappingTtl = maxWrappingTtl;
|
||||
this.allowedParameters = allowedParameters;
|
||||
this.deniedParameters = deniedParameters;
|
||||
}
|
||||
|
||||
private Rule(String path, List<Capability> capabilities,
|
||||
@Nullable Duration minWrappingTtl, @Nullable Duration maxWrappingTtl,
|
||||
Map<String, List<String>> allowedParameters,
|
||||
Map<String, List<String>> deniedParameters) {
|
||||
|
||||
this.path = path;
|
||||
this.capabilities = capabilities;
|
||||
this.minWrappingTtl = minWrappingTtl;
|
||||
this.maxWrappingTtl = maxWrappingTtl;
|
||||
this.allowedParameters = allowedParameters;
|
||||
this.deniedParameters = deniedParameters;
|
||||
}
|
||||
|
||||
private Rule withPath(String path) {
|
||||
return new Rule(path, capabilities, minWrappingTtl, maxWrappingTtl,
|
||||
allowedParameters, deniedParameters);
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public List<Capability> getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Duration getMinWrappingTtl() {
|
||||
return minWrappingTtl;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Duration getMaxWrappingTtl() {
|
||||
return maxWrappingTtl;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getAllowedParameters() {
|
||||
return allowedParameters;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getDeniedParameters() {
|
||||
return deniedParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new builder for {@link Rule}.
|
||||
*
|
||||
* @return a new {@link RuleBuilder}.
|
||||
*/
|
||||
public static RuleBuilder builder() {
|
||||
return new RuleBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for a {@link Rule}.
|
||||
*/
|
||||
public static class RuleBuilder {
|
||||
|
||||
private @Nullable String path;
|
||||
|
||||
private Set<Capability> capabilities = new LinkedHashSet<>();
|
||||
|
||||
@Nullable
|
||||
private Duration minWrappingTtl;
|
||||
|
||||
@Nullable
|
||||
private Duration maxWrappingTtl;
|
||||
|
||||
private Map<String, List<String>> allowedParameters = new LinkedHashMap<String, List<String>>();
|
||||
|
||||
private Map<String, List<String>> deniedParameters = new LinkedHashMap<String, List<String>>();;
|
||||
|
||||
/**
|
||||
* Associate a {@code path} with the rule.
|
||||
*
|
||||
* @param path must not be {@literal null} or empty.
|
||||
* @return {@code this} {@link RuleBuilder}.
|
||||
*/
|
||||
public RuleBuilder path(String path) {
|
||||
|
||||
Assert.hasText(path, "Path must not be empty");
|
||||
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@link Capability} for the rule. Capabilities are added when
|
||||
* calling this method and do not replace already configured capabilities.
|
||||
*
|
||||
* @param capability must not be {@literal null}.
|
||||
* @return {@code this} {@link RuleBuilder}.
|
||||
*/
|
||||
public RuleBuilder capability(Capability capability) {
|
||||
|
||||
Assert.notNull(capability, "Capability must not be null");
|
||||
|
||||
this.capabilities.add(capability);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure capabilities. apabilities are added when calling this method and
|
||||
* do not replace already configured capabilities.
|
||||
*
|
||||
* @param capabilities must not be {@literal null}.
|
||||
* @return {@code this} {@link RuleBuilder}.
|
||||
*/
|
||||
public RuleBuilder capabilities(Capability... capabilities) {
|
||||
|
||||
Assert.notNull(capabilities, "Capabilities must not be null");
|
||||
Assert.noNullElements(capabilities,
|
||||
"Capabilities must not contain null elements");
|
||||
|
||||
return capabilities(Arrays.asList(capabilities));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure capabilities represented as {@link String} literals. This method
|
||||
* resolves capabilities using {@link BuiltinCapabilities}. Capabilities are
|
||||
* added when calling this method and do not replace already configured
|
||||
* capabilities.
|
||||
*
|
||||
* @param capabilities must not be {@literal null}.
|
||||
* @return {@code this} {@link RuleBuilder}.
|
||||
* @throws IllegalArgumentException if the capability cannot be resolved to a
|
||||
* built-in {@link Capability}.
|
||||
*/
|
||||
public RuleBuilder capabilities(String... capabilities) {
|
||||
|
||||
Assert.notNull(capabilities, "Capabilities must not be null");
|
||||
Assert.noNullElements(capabilities,
|
||||
"Capabilities must not contain null elements");
|
||||
|
||||
List<Capability> mapped = Arrays
|
||||
.stream(capabilities)
|
||||
.map(value -> {
|
||||
|
||||
Capability capability = BuiltinCapabilities.find(value);
|
||||
|
||||
if (capability == null) {
|
||||
throw new IllegalArgumentException("Cannot resolve "
|
||||
+ value + " to a capability");
|
||||
}
|
||||
return capability;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return capabilities(mapped);
|
||||
}
|
||||
|
||||
private RuleBuilder capabilities(Iterable<Capability> capabilities) {
|
||||
|
||||
for (Capability capability : capabilities) {
|
||||
this.capabilities.add(capability);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a min TTL for response wrapping.
|
||||
*
|
||||
* @param ttl must not be {@literal null}.
|
||||
* @return {@code this} {@link RuleBuilder}.
|
||||
*/
|
||||
public RuleBuilder minWrappingTtl(Duration ttl) {
|
||||
|
||||
Assert.notNull(ttl, "TTL must not be null");
|
||||
|
||||
this.minWrappingTtl = ttl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a max TTL for response wrapping.
|
||||
*
|
||||
* @param ttl must not be {@literal null}.
|
||||
* @return {@code this} {@link RuleBuilder}.
|
||||
*/
|
||||
public RuleBuilder maxWrappingTtl(Duration ttl) {
|
||||
|
||||
Assert.notNull(ttl, "TTL must not be null");
|
||||
|
||||
this.maxWrappingTtl = ttl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure allowed parameter values given {@code name} and {@code values}.
|
||||
* Allowing parameter values replaces previously configured allowed parameter
|
||||
* values. Empty {@code values} allow all values for the given parameter
|
||||
* {@code name}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param values must not be {@literal null}.
|
||||
* @return {@code this} {@link RuleBuilder}.
|
||||
*/
|
||||
public RuleBuilder allowedParameter(String name, String... values) {
|
||||
|
||||
Assert.hasText(name, "Allowed parameter name must not be empty");
|
||||
Assert.notNull(values, "Values must not be null");
|
||||
|
||||
this.allowedParameters.put(name, Arrays.asList(values));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure denied parameter values given {@code name} and {@code values}.
|
||||
* Denying parameter values replaces previously configured denied parameter
|
||||
* values. Empty {@code values} deny parameter usage.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param values must not be {@literal null}.
|
||||
* @return {@code this} {@link RuleBuilder}.
|
||||
*/
|
||||
public RuleBuilder deniedParameter(String name, String... values) {
|
||||
|
||||
Assert.hasText(name, "Denied parameter name must not be empty");
|
||||
Assert.notNull(values, "Values must not be null");
|
||||
|
||||
this.deniedParameters.put(name, Arrays.asList(values));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the {@link Rule} object. Requires a configured {@link #path(String)}
|
||||
* and at least one {@link #capability(Capability)}.
|
||||
*
|
||||
* @return the new {@link Rule} object.
|
||||
*/
|
||||
public Rule build() {
|
||||
|
||||
Assert.state(StringUtils.hasText(path), "Path must not be empty");
|
||||
Assert.state(!capabilities.isEmpty(),
|
||||
"Rule must define one or more capabilities");
|
||||
|
||||
List<Capability> capabilities;
|
||||
switch (this.capabilities.size()) {
|
||||
case 0:
|
||||
capabilities = Collections.emptyList();
|
||||
break;
|
||||
case 1:
|
||||
capabilities = Collections.singletonList(this.capabilities.iterator()
|
||||
.next());
|
||||
break;
|
||||
default:
|
||||
capabilities = Collections.unmodifiableList(new ArrayList<>(
|
||||
this.capabilities));
|
||||
}
|
||||
|
||||
return new Rule(path, capabilities, minWrappingTtl, maxWrappingTtl,
|
||||
createMap(this.allowedParameters),
|
||||
createMap(this.deniedParameters));
|
||||
}
|
||||
|
||||
private Map<String, List<String>> createMap(Map<String, List<String>> source) {
|
||||
|
||||
if (source.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(new LinkedHashMap<>(source));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Capability interface representing capability literals.
|
||||
*/
|
||||
public interface Capability {
|
||||
|
||||
/**
|
||||
* @return the capability literal.
|
||||
*/
|
||||
String name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in Vault capabilities.
|
||||
*/
|
||||
public enum BuiltinCapabilities implements Capability {
|
||||
|
||||
/**
|
||||
* Allows creating data at the given path. Very few parts of Vault distinguish
|
||||
* between create and update, so most operations require both create and update
|
||||
* capabilities.
|
||||
*/
|
||||
CREATE,
|
||||
|
||||
/**
|
||||
* Allows reading the data at the given path.
|
||||
*/
|
||||
READ,
|
||||
|
||||
/**
|
||||
* Allows change the data at the given path. In most parts of Vault, this
|
||||
* implicitly includes the ability to create the initial value at the path.
|
||||
*/
|
||||
UPDATE,
|
||||
|
||||
/**
|
||||
* Deprecated: Previous capability literal before it was split into
|
||||
* {@link #CREATE} and {@link #UPDATE}.
|
||||
*/
|
||||
WRITE,
|
||||
|
||||
/**
|
||||
* Allows deleting the data at the given path.
|
||||
*/
|
||||
DELETE,
|
||||
|
||||
/**
|
||||
* Allows listing values at the given path. Note that the keys returned by a list
|
||||
* operation are not filtered by policies. Do not encode sensitive information in
|
||||
* key names. Not all backends support listing.
|
||||
*/
|
||||
LIST,
|
||||
|
||||
/**
|
||||
* Allows access to paths that are root-protected. Tokens are not permitted to
|
||||
* interact with these paths unless they are have the sudo capability (in addition
|
||||
* to the other necessary capabilities for performing an operation against that
|
||||
* path, such as read or delete).
|
||||
*/
|
||||
SUDO,
|
||||
|
||||
/**
|
||||
* Disallows access. This always takes precedence regardless of any other defined
|
||||
* capabilities, including {@link #SUDO}.
|
||||
*/
|
||||
DENY;
|
||||
|
||||
/**
|
||||
* Find a {@link Capability} by its name. The name is compared case-insensitive.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return the {@link Capability} or {@literal null}, if not found.
|
||||
*/
|
||||
@Nullable
|
||||
public static Capability find(String value) {
|
||||
|
||||
for (BuiltinCapabilities cap : values()) {
|
||||
if (cap.name().equalsIgnoreCase(value)) {
|
||||
return cap;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class PolicySerializer extends JsonSerializer<Policy> {
|
||||
|
||||
@Override
|
||||
public void serialize(Policy value, JsonGenerator gen,
|
||||
SerializerProvider serializers) throws IOException {
|
||||
|
||||
gen.writeStartObject();
|
||||
|
||||
gen.writeFieldName("path");
|
||||
gen.writeStartObject();
|
||||
|
||||
for (Rule rule : value.getRules()) {
|
||||
gen.writeObjectField(rule.path, rule);
|
||||
}
|
||||
|
||||
gen.writeEndObject();
|
||||
gen.writeEndObject();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static class PolicyDeserializer extends JsonDeserializer<Policy> {
|
||||
|
||||
@Override
|
||||
public Policy deserialize(JsonParser p, DeserializationContext ctxt)
|
||||
throws IOException {
|
||||
|
||||
Assert.isTrue(p.getCurrentToken() == JsonToken.START_OBJECT,
|
||||
"Expected START_OBJECT, got: " + p.getCurrentToken());
|
||||
|
||||
String fieldName = p.nextFieldName();
|
||||
|
||||
Set<Rule> rules = new LinkedHashSet<>();
|
||||
|
||||
if ("path".equals(fieldName)) {
|
||||
|
||||
p.nextToken();
|
||||
Assert.isTrue(p.getCurrentToken() == JsonToken.START_OBJECT,
|
||||
"Expected START_OBJECT, got: " + p.getCurrentToken());
|
||||
|
||||
p.nextToken();
|
||||
|
||||
while (p.currentToken() == JsonToken.FIELD_NAME) {
|
||||
|
||||
String path = p.getCurrentName();
|
||||
p.nextToken();
|
||||
|
||||
Assert.isTrue(p.getCurrentToken() == JsonToken.START_OBJECT,
|
||||
"Expected START_OBJECT, got: " + p.getCurrentToken());
|
||||
|
||||
Rule rule = p.getCodec().readValue(p, Rule.class);
|
||||
rules.add(rule.withPath(path));
|
||||
|
||||
JsonToken jsonToken = p.nextToken();
|
||||
if (jsonToken == JsonToken.END_OBJECT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.isTrue(p.getCurrentToken() == JsonToken.END_OBJECT,
|
||||
"Expected END_OBJECT, got: " + p.getCurrentToken());
|
||||
p.nextToken();
|
||||
}
|
||||
|
||||
Assert.isTrue(p.getCurrentToken() == JsonToken.END_OBJECT,
|
||||
"Expected END_OBJECT, got: " + p.getCurrentToken());
|
||||
return Policy.of(rules);
|
||||
}
|
||||
}
|
||||
|
||||
static class CapabilityToStringConverter implements Converter<Capability, String> {
|
||||
|
||||
@Override
|
||||
public String convert(Capability value) {
|
||||
return value.name().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType getInputType(TypeFactory typeFactory) {
|
||||
return typeFactory.constructType(Capability.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType getOutputType(TypeFactory typeFactory) {
|
||||
return typeFactory.constructType(String.class);
|
||||
}
|
||||
}
|
||||
|
||||
static class StringToCapabilityConverter implements Converter<String, Capability> {
|
||||
|
||||
@Override
|
||||
public Capability convert(String value) {
|
||||
|
||||
Capability capability = BuiltinCapabilities.find(value);
|
||||
|
||||
return capability != null ? capability : () -> value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType getInputType(TypeFactory typeFactory) {
|
||||
return typeFactory.constructType(String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType getOutputType(TypeFactory typeFactory) {
|
||||
return typeFactory.constructType(Capability.class);
|
||||
}
|
||||
}
|
||||
|
||||
static class DurationToStringConverter implements Converter<Duration, String> {
|
||||
|
||||
@Override
|
||||
public String convert(Duration value) {
|
||||
return "" + value.getSeconds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType getInputType(TypeFactory typeFactory) {
|
||||
return typeFactory.constructType(Duration.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType getOutputType(TypeFactory typeFactory) {
|
||||
return typeFactory.constructType(String.class);
|
||||
}
|
||||
}
|
||||
|
||||
static class StringToDurationConverter implements Converter<String, Duration> {
|
||||
|
||||
static Pattern SECONDS = Pattern.compile("(\\d+)s");
|
||||
static Pattern MINUTES = Pattern.compile("(\\d+)m");
|
||||
static Pattern HOURS = Pattern.compile("(\\d+)h");
|
||||
|
||||
@Override
|
||||
public Duration convert(String value) {
|
||||
|
||||
try {
|
||||
return Duration.ofSeconds(Long.parseLong(value));
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
|
||||
Matcher matcher = SECONDS.matcher(value);
|
||||
if (matcher.matches()) {
|
||||
return Duration.ofSeconds(Long.parseLong(matcher.group(1)));
|
||||
}
|
||||
|
||||
matcher = MINUTES.matcher(value);
|
||||
if (matcher.matches()) {
|
||||
return Duration.ofMinutes(Long.parseLong(matcher.group(1)));
|
||||
}
|
||||
|
||||
matcher = HOURS.matcher(value);
|
||||
if (matcher.matches()) {
|
||||
return Duration.ofHours(Long.parseLong(matcher.group(1)));
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unsupported duration value: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType getInputType(TypeFactory typeFactory) {
|
||||
return typeFactory.constructType(String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType getOutputType(TypeFactory typeFactory) {
|
||||
return typeFactory.constructType(Capability.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,9 @@ public class VaultCertificateRequest {
|
||||
|
||||
@Nullable
|
||||
private String commonName;
|
||||
|
||||
private List<String> altNames = new ArrayList<>();
|
||||
|
||||
private List<String> ipSubjectAltNames = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package org.springframework.vault.core;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -26,11 +28,17 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.vault.support.Policy;
|
||||
import org.springframework.vault.support.VaultMount;
|
||||
import org.springframework.vault.support.VaultUnsealStatus;
|
||||
import org.springframework.vault.support.Policy.Rule;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
import org.springframework.vault.util.Version;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
import static org.springframework.vault.support.Policy.BuiltinCapabilities.READ;
|
||||
import static org.springframework.vault.support.Policy.BuiltinCapabilities.UPDATE;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultSysTemplate} through {@link VaultSysOperations}.
|
||||
@@ -44,10 +52,15 @@ public class VaultSysTemplateIntegrationTests extends IntegrationTestSupport {
|
||||
@Autowired
|
||||
private VaultOperations vaultOperations;
|
||||
|
||||
private Version vaultVersion;
|
||||
|
||||
private VaultSysOperations adminOperations;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
|
||||
vaultVersion = prepare().getVersion();
|
||||
|
||||
adminOperations = vaultOperations.opsForSys();
|
||||
}
|
||||
|
||||
@@ -120,6 +133,65 @@ public class VaultSysTemplateIntegrationTests extends IntegrationTestSupport {
|
||||
assertThat(secret.getType()).isEqualTo("userpass");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEnumeratePolicyNames() {
|
||||
|
||||
assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.1")));
|
||||
|
||||
List<String> policyNames = adminOperations.getPolicyNames();
|
||||
|
||||
assertThat(policyNames).contains("root", "default");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadRootPolicy() {
|
||||
|
||||
assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.1")));
|
||||
|
||||
Policy root = adminOperations.getPolicy("root");
|
||||
|
||||
assertThat(root).isEqualTo(Policy.empty());
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void shouldReadDefaultPolicy() {
|
||||
|
||||
assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.1")));
|
||||
|
||||
adminOperations.getPolicy("default");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreatePolicy() {
|
||||
|
||||
assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.7.0")));
|
||||
|
||||
Rule rule = Rule.builder().path("foo").capabilities(READ, UPDATE)
|
||||
.minWrappingTtl(Duration.ofSeconds(100))
|
||||
.maxWrappingTtl(Duration.ofHours(2)).build();
|
||||
|
||||
adminOperations.createOrUpdatePolicy("foo", Policy.of(rule));
|
||||
|
||||
assertThat(adminOperations.getPolicyNames()).contains("foo");
|
||||
|
||||
Policy loaded = adminOperations.getPolicy("foo");
|
||||
assertThat(loaded.getRules()).contains(rule);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDeletePolicy() {
|
||||
|
||||
assumeTrue(vaultVersion.isGreaterThanOrEqualTo(Version.parse("0.6.0")));
|
||||
|
||||
Rule rule = Rule.builder().path("foo").capabilities(READ).build();
|
||||
|
||||
adminOperations.createOrUpdatePolicy("foo", Policy.of(rule));
|
||||
|
||||
adminOperations.deletePolicy("foo");
|
||||
|
||||
assertThat(adminOperations.getPolicyNames()).doesNotContain("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isInitializedShouldReturnTrue() {
|
||||
assertThat(adminOperations.isInitialized()).isTrue();
|
||||
@@ -129,6 +201,7 @@ public class VaultSysTemplateIntegrationTests extends IntegrationTestSupport {
|
||||
public void getUnsealStatusShouldReturnStatus() {
|
||||
|
||||
VaultUnsealStatus unsealStatus = adminOperations.getUnsealStatus();
|
||||
|
||||
assertThat(unsealStatus.isSealed()).isFalse();
|
||||
assertThat(unsealStatus.getProgress()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.vault.support;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.vault.support.Policy.Rule;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Policy} JSON serialization/deserialization.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class PolicySerializationUnitTests {
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
public void shouldSerialize() throws Exception {
|
||||
|
||||
Rule rule = Rule.builder().path("secret/*")
|
||||
.capabilities("create", "read", "update")
|
||||
.allowedParameter("ttl", "1h", "2h").deniedParameter("password").build();
|
||||
|
||||
Rule another = Rule.builder().path("secret/foo")
|
||||
.capabilities("create", "read", "update", "delete", "list")
|
||||
.minWrappingTtl(Duration.ofMinutes(1))
|
||||
.maxWrappingTtl(Duration.ofHours(1)).allowedParameter("ttl", "1h", "2h")
|
||||
.deniedParameter("password").build();
|
||||
|
||||
Policy policy = Policy.of(rule, another);
|
||||
|
||||
try (InputStream is = new ClassPathResource("policy.json").getInputStream()) {
|
||||
|
||||
String expected = StreamUtils.copyToString(is, StandardCharsets.UTF_8);
|
||||
JSONAssert.assertEquals(expected, objectMapper.writeValueAsString(policy),
|
||||
JSONCompareMode.STRICT);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDeserialize() throws Exception {
|
||||
|
||||
Rule rule = Rule.builder().path("secret/*")
|
||||
.capabilities("create", "read", "update", "update")
|
||||
.allowedParameter("ttl", "1h", "2h").deniedParameter("password").build();
|
||||
|
||||
Rule another = Rule.builder().path("secret/foo")
|
||||
.capabilities("create", "read", "update", "delete", "list")
|
||||
.minWrappingTtl(Duration.ofMinutes(1))
|
||||
.maxWrappingTtl(Duration.ofHours(1)).allowedParameter("ttl", "1h", "2h")
|
||||
.allowedParameter("ttl", "1h", "2h").deniedParameter("password").build();
|
||||
|
||||
Policy expected = Policy.of(rule, another);
|
||||
|
||||
try (InputStream is = new ClassPathResource("policy.json").getInputStream()) {
|
||||
|
||||
Policy actual = objectMapper.readValue(is, Policy.class);
|
||||
|
||||
assertThat(actual.getRules()).hasSameClassAs(expected.getRules());
|
||||
|
||||
Rule secretAll = actual.getRule("secret/*");
|
||||
|
||||
assertThat(secretAll.getPath()).isEqualTo(rule.getPath());
|
||||
assertThat(secretAll.getCapabilities()).isEqualTo(rule.getCapabilities());
|
||||
assertThat(secretAll.getAllowedParameters()).isEqualTo(
|
||||
rule.getAllowedParameters());
|
||||
assertThat(secretAll.getDeniedParameters()).isEqualTo(
|
||||
rule.getDeniedParameters());
|
||||
|
||||
Rule secretFoo = actual.getRule("secret/foo");
|
||||
|
||||
assertThat(secretFoo.getPath()).isEqualTo(another.getPath());
|
||||
assertThat(secretFoo.getCapabilities()).isEqualTo(another.getCapabilities());
|
||||
assertThat(secretFoo.getMinWrappingTtl()).isEqualTo(
|
||||
another.getMinWrappingTtl());
|
||||
assertThat(secretFoo.getMaxWrappingTtl()).isEqualTo(
|
||||
another.getMaxWrappingTtl());
|
||||
assertThat(secretFoo.getAllowedParameters()).isEqualTo(
|
||||
another.getAllowedParameters());
|
||||
assertThat(secretFoo.getDeniedParameters()).isEqualTo(
|
||||
another.getDeniedParameters());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDeserializeEmptyPolicy() throws Exception {
|
||||
|
||||
assertThat(objectMapper.readValue("{}", Policy.class)).isEqualTo(Policy.empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRejectUnknownFieldNames() throws Exception {
|
||||
|
||||
assertThatThrownBy(
|
||||
() -> objectMapper.readValue("{\"foo\":1, \"path\": {} }", Policy.class))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatThrownBy(
|
||||
() -> objectMapper.readValue("{\"foo\":\"bar\"}", Policy.class))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDeserializePolicyWithEmptyRules() throws Exception {
|
||||
|
||||
Policy actual = objectMapper.readValue("{ \"path\": {} }", Policy.class);
|
||||
|
||||
assertThat(actual).isEqualTo(Policy.empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDeserializeRuleWithHour() throws Exception {
|
||||
|
||||
Policy actual = objectMapper.readValue(
|
||||
"{ \"path\": { \"secret\" : {\"min_wrapping_ttl\":\"1h\"} } }",
|
||||
Policy.class);
|
||||
|
||||
Rule rule = actual.getRule("secret");
|
||||
assertThat(rule.getMinWrappingTtl()).isEqualTo(Duration.ofHours(1));
|
||||
}
|
||||
}
|
||||
40
spring-vault-core/src/test/resources/policy.json
Normal file
40
spring-vault-core/src/test/resources/policy.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"path": {
|
||||
"secret/*": {
|
||||
"capabilities": [
|
||||
"create",
|
||||
"read",
|
||||
"update"
|
||||
],
|
||||
"allowed_parameters": {
|
||||
"ttl": [
|
||||
"1h",
|
||||
"2h"
|
||||
]
|
||||
},
|
||||
"denied_parameters": {
|
||||
"password": []
|
||||
}
|
||||
},
|
||||
"secret/foo": {
|
||||
"capabilities": [
|
||||
"create",
|
||||
"read",
|
||||
"update",
|
||||
"delete",
|
||||
"list"
|
||||
],
|
||||
"min_wrapping_ttl": "60",
|
||||
"max_wrapping_ttl": "3600",
|
||||
"allowed_parameters": {
|
||||
"ttl": [
|
||||
"1h",
|
||||
"2h"
|
||||
]
|
||||
},
|
||||
"denied_parameters": {
|
||||
"password": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
* Reactive Vault client via `ReactiveVaultOperations`.
|
||||
* <<vault.repositories,Vault repository support>> based on Spring Data KeyValue.
|
||||
* Transit batch encrypt and decrypt support.
|
||||
* Policy management for policies stored as JSON.
|
||||
|
||||
[[new-features.1-0-0]]
|
||||
=== What's new in Spring Vault 1.0
|
||||
|
||||
Reference in New Issue
Block a user