Adding Transform Secrets Engine support
This feature is only available if your vault binary is an enterprise version 1.4 or higher. Original pull request: gh-570.
This commit is contained in:
committed by
Mark Paluch
parent
06eb31857f
commit
1d18e52a51
@@ -84,6 +84,21 @@ public interface VaultOperations {
|
||||
*/
|
||||
VaultTokenOperations opsForToken();
|
||||
|
||||
/**
|
||||
* @return the operations interface to interact with the Vault transform backend.
|
||||
* @since 2.3
|
||||
*/
|
||||
VaultTransformOperations opsForTransform();
|
||||
|
||||
/**
|
||||
* Return {@link VaultTransformOperations} if the transit backend is mounted on a
|
||||
* different path than {@code transform}.
|
||||
* @param path the mount path
|
||||
* @return the operations interface to interact with the Vault transform backend.
|
||||
* @since 2.3
|
||||
*/
|
||||
VaultTransformOperations opsForTransform(String path);
|
||||
|
||||
/**
|
||||
* @return the operations interface to interact with the Vault transit backend.
|
||||
*/
|
||||
|
||||
@@ -320,6 +320,16 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
|
||||
return new VaultTokenTemplate(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultTransformOperations opsForTransform() {
|
||||
return opsForTransform("transform");
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultTransformOperations opsForTransform(String path) {
|
||||
return new VaultTransformTemplate(this, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultTransitOperations opsForTransit() {
|
||||
return opsForTransit("transit");
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.core;
|
||||
|
||||
import org.springframework.vault.support.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface that specifies operations using the {@code transform} backend.
|
||||
*
|
||||
* @author Lauren Voswinkel
|
||||
* @see <a href="https://www.vaultproject.io/docs/secrets/transform/index.html">Transform
|
||||
* Secrets Engine</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public interface VaultTransformOperations {
|
||||
/**
|
||||
* Encodes the provided plaintext using the named role.
|
||||
* @param roleName must not be empty or {@literal null}.
|
||||
* @param plaintext must not be empty or {@literal null}.
|
||||
* @return cipher text.
|
||||
*/
|
||||
String encode(String roleName, String plaintext);
|
||||
|
||||
/**
|
||||
* Encodes the provided plaintext using the named role.
|
||||
* @param roleName must not be empty or {@literal null}.
|
||||
* @param plaintext must not be {@literal null}.
|
||||
* @return cipher text.
|
||||
*/
|
||||
TransformCiphertext encode(String roleName, TransformPlaintext plaintext);
|
||||
|
||||
/**
|
||||
* Encodes the provided plaintext using the named role.
|
||||
* @param roleName must not be empty or {@literal null}.
|
||||
* @param plaintext must not be empty or {@literal null}.
|
||||
* @param transformRequest must not be {@literal null}. Use
|
||||
* {@link VaultTransformContext#empty()} if no request options provided.
|
||||
* @return cipher text.
|
||||
*/
|
||||
String encode(String roleName, byte[] plaintext, VaultTransformContext transformRequest);
|
||||
|
||||
/**
|
||||
* Encode the provided batch of plaintext using the role given and transformation in
|
||||
* each list item. The encryption is done using transformation secret backend's batch
|
||||
* operation.
|
||||
* @param roleName must not be empty or {@literal null}.
|
||||
* @param batchRequest a list of {@link Plaintext} which includes plaintext and an
|
||||
* optional context.
|
||||
* @return the encrypted result in the order of {@code batchRequest} plaintexts.
|
||||
*/
|
||||
List<VaultTransformEncodeResult> encode(String roleName, List<TransformPlaintext> batchRequest);
|
||||
|
||||
/**
|
||||
* Decode the provided ciphertext using the named role.
|
||||
* @param roleName must not be empty or {@literal null}.
|
||||
* @param ciphertext must not be empty or {@literal null}.
|
||||
* @return plain text.
|
||||
*/
|
||||
String decode(String roleName, String ciphertext);
|
||||
|
||||
/**
|
||||
* Decode the provided ciphertext using the named role.
|
||||
* @param roleName must not be empty or {@literal null}.
|
||||
* @param ciphertext must not be {@literal null}.
|
||||
* @return plain text.
|
||||
*/
|
||||
TransformPlaintext decode(String roleName, TransformCiphertext ciphertext);
|
||||
|
||||
/**
|
||||
* Decode the provided ciphertext using the named role.
|
||||
* @param roleName must not be empty or {@literal null}.
|
||||
* @param ciphertext must not be empty or {@literal null}.
|
||||
* @param transformContext must not be {@literal null}. Use
|
||||
* {@link VaultTransformContext#empty()} if no request options provided.
|
||||
* @return plain text.
|
||||
*/
|
||||
String decode(String roleName, String ciphertext, VaultTransformContext transformContext);
|
||||
|
||||
/**
|
||||
* Decode the provided batch of ciphertext using the role given and transformation in
|
||||
* each list item. The decryption is done using transformation secret backend's batch
|
||||
* operation.
|
||||
* @param roleName must not be empty or {@literal null}.
|
||||
* @param batchRequest a list of {@link Ciphertext} which includes plaintext and an
|
||||
* optional context.
|
||||
* @return the decrypted result in the order of {@code batchRequest} ciphertexts.
|
||||
*/
|
||||
List<VaultTransformDecodeResult> decode(String roleName, List<TransformCiphertext> batchRequest);
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.core;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.support.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link VaultTransformOperations}.
|
||||
*
|
||||
* @author Lauren Voswinkel
|
||||
* @since 2.3
|
||||
*/
|
||||
public class VaultTransformTemplate implements VaultTransformOperations {
|
||||
|
||||
private final VaultOperations vaultOperations;
|
||||
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultTransformTemplate} given {@link VaultOperations} and the
|
||||
* mount {@code path}.
|
||||
* @param vaultOperations must not be {@literal null}.
|
||||
* @param path must not be empty or {@literal null}.
|
||||
*/
|
||||
public VaultTransformTemplate(VaultOperations vaultOperations, String path) {
|
||||
|
||||
Assert.notNull(vaultOperations, "VaultOperations must not be null");
|
||||
Assert.hasText(path, "Path must not be empty");
|
||||
|
||||
this.vaultOperations = vaultOperations;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(String roleName, String plaintext) {
|
||||
|
||||
Assert.hasText(roleName, "Role name must not be empty");
|
||||
Assert.notNull(plaintext, "Plaintext must not be null");
|
||||
|
||||
Map<String, String> request = new LinkedHashMap<>();
|
||||
|
||||
request.put("value", plaintext);
|
||||
|
||||
return (String) this.vaultOperations.write(String.format("%s/encode/%s", this.path, roleName), request)
|
||||
.getRequiredData().get("encoded_value");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformCiphertext encode(String roleName, TransformPlaintext plaintext) {
|
||||
|
||||
Assert.hasText(roleName, "Role name must not be empty");
|
||||
Assert.notNull(plaintext, "Plaintext must not be null");
|
||||
|
||||
String ciphertext = encode(roleName, plaintext.getPlaintext(), plaintext.getContext());
|
||||
|
||||
return toCiphertext(ciphertext, plaintext.getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(String roleName, byte[] plaintext, VaultTransformContext transformContext) {
|
||||
|
||||
Assert.hasText(roleName, "Role name must not be empty");
|
||||
Assert.notNull(plaintext, "Plaintext must not be null");
|
||||
Assert.notNull(transformContext, "VaultTransformContext must not be null");
|
||||
|
||||
Map<String, String> request = new LinkedHashMap<>();
|
||||
|
||||
String value = new String(plaintext);
|
||||
request.put("value", value);
|
||||
|
||||
applyTransformOptions(transformContext, request);
|
||||
|
||||
return (String) this.vaultOperations.write(String.format("%s/encode/%s", this.path, roleName), request)
|
||||
.getRequiredData().get("encoded_value");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VaultTransformEncodeResult> encode(String roleName, List<TransformPlaintext> batchRequest) {
|
||||
|
||||
Assert.hasText(roleName, "Role name must not be empty");
|
||||
Assert.notEmpty(batchRequest, "BatchRequest must not be null and must have at least one entry");
|
||||
|
||||
List<Map<String, String>> batch = new ArrayList<>(batchRequest.size());
|
||||
|
||||
for (TransformPlaintext request : batchRequest) {
|
||||
|
||||
Map<String, String> vaultRequest = new LinkedHashMap<>(2);
|
||||
|
||||
vaultRequest.put("value", request.asString());
|
||||
|
||||
if (request.getContext() != null) {
|
||||
applyTransformOptions(request.getContext(), vaultRequest);
|
||||
}
|
||||
|
||||
batch.add(vaultRequest);
|
||||
}
|
||||
|
||||
VaultResponse vaultResponse = this.vaultOperations.write(String.format("%s/encode/%s", this.path, roleName),
|
||||
Collections.singletonMap("batch_input", batch));
|
||||
|
||||
return toEncodedResults(vaultResponse, batchRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decode(String roleName, String ciphertext) {
|
||||
|
||||
Assert.hasText(roleName, "Key name must not be empty");
|
||||
Assert.hasText(ciphertext, "Ciphertext must not be empty");
|
||||
|
||||
Map<String, String> request = new LinkedHashMap<>();
|
||||
|
||||
request.put("value", ciphertext);
|
||||
|
||||
String plaintext = (String) this.vaultOperations
|
||||
.write(String.format("%s/decode/%s", this.path, roleName), request).getRequiredData().get("decoded_value");
|
||||
|
||||
return new String(plaintext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformPlaintext decode(String roleName, TransformCiphertext ciphertext) {
|
||||
|
||||
Assert.hasText(roleName, "Role name must not be null");
|
||||
Assert.notNull(ciphertext, "Ciphertext must not be null");
|
||||
|
||||
String plaintext = decode(roleName, ciphertext.getCiphertext(), ciphertext.getContext());
|
||||
|
||||
return TransformPlaintext.of(plaintext).with(ciphertext.getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decode(String roleName, String ciphertext, VaultTransformContext transformContext) {
|
||||
|
||||
Assert.hasText(roleName, "Role name must not be empty");
|
||||
Assert.hasText(ciphertext, "Ciphertext must not be empty");
|
||||
Assert.notNull(transformContext, "VaultTransformContext must not be null");
|
||||
|
||||
Map<String, String> request = new LinkedHashMap<>();
|
||||
|
||||
request.put("value", ciphertext);
|
||||
|
||||
applyTransformOptions(transformContext, request);
|
||||
|
||||
String plaintext = (String) this.vaultOperations
|
||||
.write(String.format("%s/decode/%s", this.path, roleName), request).getRequiredData().get("decoded_value");
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VaultTransformDecodeResult> decode(String roleName, List<TransformCiphertext> batchRequest) {
|
||||
|
||||
Assert.hasText(roleName, "Role name must not be empty");
|
||||
Assert.notEmpty(batchRequest, "BatchRequest must not be null and must have at least one entry");
|
||||
|
||||
List<Map<String, String>> batch = new ArrayList<>(batchRequest.size());
|
||||
|
||||
for (TransformCiphertext request : batchRequest) {
|
||||
|
||||
Map<String, String> vaultRequest = new LinkedHashMap<>(2);
|
||||
|
||||
vaultRequest.put("value", request.getCiphertext());
|
||||
|
||||
if (request.getContext() != null) {
|
||||
applyTransformOptions(request.getContext(), vaultRequest);
|
||||
}
|
||||
|
||||
batch.add(vaultRequest);
|
||||
}
|
||||
|
||||
VaultResponse vaultResponse = this.vaultOperations.write(String.format("%s/decode/%s", this.path, roleName),
|
||||
Collections.singletonMap("batch_input", batch));
|
||||
|
||||
return toDecryptionResults(vaultResponse, batchRequest);
|
||||
}
|
||||
|
||||
private static void applyTransformOptions(VaultTransformContext context, Map<String, String> request) {
|
||||
|
||||
if (!ObjectUtils.isEmpty(context.getTransformation())) {
|
||||
request.put("transformation", context.getTransformation());
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(context.getTweak())) {
|
||||
request.put("tweak", Base64Utils.encodeToString(context.getTweak()));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<VaultTransformEncodeResult> toEncodedResults(VaultResponse vaultResponse,
|
||||
List<TransformPlaintext> batchRequest) {
|
||||
|
||||
List<VaultTransformEncodeResult> result = new ArrayList<>(batchRequest.size());
|
||||
List<Map<String, String>> batchData = getBatchData(vaultResponse);
|
||||
|
||||
for (int i = 0; i < batchRequest.size(); i++) {
|
||||
|
||||
VaultTransformEncodeResult encoded;
|
||||
TransformPlaintext plaintext = batchRequest.get(i);
|
||||
if (batchData.size() > i) {
|
||||
|
||||
Map<String, String> data = batchData.get(i);
|
||||
if (StringUtils.hasText(data.get("error"))) {
|
||||
encoded = new VaultTransformEncodeResult(new VaultException(data.get("error")));
|
||||
}
|
||||
else {
|
||||
encoded = new VaultTransformEncodeResult(toCiphertext(data.get("encoded_value"), plaintext.getContext()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
encoded = new VaultTransformEncodeResult(new VaultException("No result for plaintext #" + i));
|
||||
}
|
||||
|
||||
result.add(encoded);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<VaultTransformDecodeResult> toDecryptionResults(VaultResponse vaultResponse,
|
||||
List<TransformCiphertext> batchRequest) {
|
||||
|
||||
List<VaultTransformDecodeResult> result = new ArrayList<>(batchRequest.size());
|
||||
List<Map<String, String>> batchData = getBatchData(vaultResponse);
|
||||
|
||||
for (int i = 0; i < batchRequest.size(); i++) {
|
||||
|
||||
VaultTransformDecodeResult encrypted;
|
||||
TransformCiphertext ciphertext = batchRequest.get(i);
|
||||
|
||||
if (batchData.size() > i) {
|
||||
encrypted = getDecryptionResult(batchData.get(i), ciphertext);
|
||||
}
|
||||
else {
|
||||
encrypted = new VaultTransformDecodeResult(new VaultException("No result for ciphertext #" + i));
|
||||
}
|
||||
|
||||
result.add(encrypted);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static VaultTransformDecodeResult getDecryptionResult(Map<String, String> data, TransformCiphertext ciphertext) {
|
||||
|
||||
if (StringUtils.hasText(data.get("error"))) {
|
||||
return new VaultTransformDecodeResult(new VaultException(data.get("error")));
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(data.get("decoded_value"))) {
|
||||
|
||||
byte[] plaintext = data.get("decoded_value").getBytes();
|
||||
return new VaultTransformDecodeResult(TransformPlaintext.of(plaintext).with(ciphertext.getContext()));
|
||||
}
|
||||
|
||||
return new VaultTransformDecodeResult(TransformPlaintext.empty().with(ciphertext.getContext()));
|
||||
}
|
||||
|
||||
private static TransformCiphertext toCiphertext(String ciphertext, @Nullable VaultTransformContext context) {
|
||||
return context != null ? TransformCiphertext.of(ciphertext).with(context) : TransformCiphertext.of(ciphertext);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Map<String, String>> getBatchData(VaultResponse vaultResponse) {
|
||||
return (List<Map<String, String>>) vaultResponse.getRequiredData().get("batch_results");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [vaultOperations=").append(this.vaultOperations);
|
||||
sb.append(", path='").append(this.path).append('\'');
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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 org.springframework.util.Assert;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Value object representing ciphertext with an optional {@link VaultTransformContext}.
|
||||
*
|
||||
* @author Lauren Voswinkel
|
||||
* @since 2.3
|
||||
*/
|
||||
public class TransformCiphertext {
|
||||
|
||||
private final String ciphertext;
|
||||
|
||||
private final VaultTransformContext context;
|
||||
|
||||
private TransformCiphertext(String ciphertext, VaultTransformContext context) {
|
||||
|
||||
this.ciphertext = ciphertext;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create {@link TransformCiphertext} from the given {@code ciphertext}.
|
||||
* @param ciphertext the ciphertext to decrypt, must not be {@literal null} or empty.
|
||||
* @return the {@link TransformCiphertext} for {@code ciphertext}.
|
||||
*/
|
||||
public static TransformCiphertext of(String ciphertext) {
|
||||
|
||||
Assert.hasText(ciphertext, "Ciphertext must not be null or empty");
|
||||
|
||||
return new TransformCiphertext(ciphertext, VaultTransformContext.empty());
|
||||
}
|
||||
|
||||
public String getCiphertext() {
|
||||
return this.ciphertext;
|
||||
}
|
||||
|
||||
public VaultTransformContext getContext() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link TransformCiphertext} object from this ciphertext associated with the
|
||||
* given {@link VaultTransformContext}.
|
||||
* @param context transit context, must not be {@literal null}.
|
||||
* @return the new {@link TransformCiphertext} object.
|
||||
*/
|
||||
public TransformCiphertext with(VaultTransformContext context) {
|
||||
|
||||
Assert.notNull(context, "VaultTransitContext must not be null");
|
||||
|
||||
return new TransformCiphertext(getCiphertext(), context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof TransformCiphertext))
|
||||
return false;
|
||||
TransformCiphertext that = (TransformCiphertext) o;
|
||||
return this.ciphertext.equals(that.ciphertext) && this.context.equals(that.context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.ciphertext, this.context);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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 org.springframework.util.Assert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Value object representing plaintext with an optional {@link VaultTransformContext}.
|
||||
* Plaintext is represented binary safe as {@code byte[]}.
|
||||
*
|
||||
* @author Lauren Voswinkel
|
||||
* @since 2.3
|
||||
*/
|
||||
public class TransformPlaintext {
|
||||
|
||||
private static final TransformPlaintext EMPTY = new TransformPlaintext(new byte[0], VaultTransformContext.empty());
|
||||
|
||||
private final byte[] plaintext;
|
||||
|
||||
private final VaultTransformContext context;
|
||||
|
||||
private TransformPlaintext(byte[] plaintext, VaultTransformContext context) {
|
||||
|
||||
this.plaintext = plaintext;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create an empty {@link TransformPlaintext}.
|
||||
* @return the empty {@link TransformPlaintext} object.
|
||||
* @since 1.1.2
|
||||
*/
|
||||
public static TransformPlaintext empty() {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create {@link TransformPlaintext} from a byte sequence.
|
||||
* @param plaintext the plaintext to encrypt, must not be {@literal null}.
|
||||
* @return the {@link TransformPlaintext} for {@code plaintext}.
|
||||
*/
|
||||
public static TransformPlaintext of(byte[] plaintext) {
|
||||
|
||||
Assert.notNull(plaintext, "Plaintext must not be null");
|
||||
|
||||
if (plaintext.length == 0) {
|
||||
return empty();
|
||||
}
|
||||
|
||||
return new TransformPlaintext(plaintext, VaultTransformContext.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create {@link TransformPlaintext} using from {@link String}.
|
||||
* {@link String} is encoded to {@code byte} using the default
|
||||
* {@link java.nio.charset.Charset}.
|
||||
* @param plaintext the plaintext to encrypt, must not be {@literal null}.
|
||||
* @return the {@link TransformPlaintext} for {@code plaintext}.
|
||||
*/
|
||||
public static TransformPlaintext of(String plaintext) {
|
||||
|
||||
Assert.notNull(plaintext, "Plaintext must not be null");
|
||||
|
||||
if (plaintext.length() == 0) {
|
||||
return empty();
|
||||
}
|
||||
|
||||
return of(plaintext.getBytes());
|
||||
}
|
||||
|
||||
public byte[] getPlaintext() {
|
||||
return this.plaintext;
|
||||
}
|
||||
|
||||
public VaultTransformContext getContext() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link TransformPlaintext} object from this plaintext associated with the given
|
||||
* {@link VaultTransformContext}.
|
||||
* @param context transform context.
|
||||
* @return the new {@link TransformPlaintext} object.
|
||||
*/
|
||||
public TransformPlaintext with(VaultTransformContext context) {
|
||||
return new TransformPlaintext(getPlaintext(), context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the plaintext as {@link String} decoded using the default
|
||||
* {@link java.nio.charset.Charset}.
|
||||
*/
|
||||
public String asString() {
|
||||
return new String(getPlaintext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof TransformPlaintext))
|
||||
return false;
|
||||
TransformPlaintext plaintext1 = (TransformPlaintext) o;
|
||||
return Arrays.equals(this.plaintext, plaintext1.plaintext) && this.context.equals(plaintext1.context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(this.context);
|
||||
result = 31 * result + Arrays.hashCode(this.plaintext);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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 org.springframework.util.Assert;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Transform backend encode/decode context object.
|
||||
*
|
||||
* @author Lauren Voswinkel
|
||||
* @since 2.3
|
||||
*/
|
||||
public class VaultTransformContext {
|
||||
|
||||
/**
|
||||
* Empty (default) {@link VaultTransformContext} without a {@literal context} and
|
||||
* {@literal nonce}.
|
||||
*/
|
||||
private static final VaultTransformContext EMPTY = new VaultTransformContext("", new byte[0]);
|
||||
|
||||
private final String transformation;
|
||||
|
||||
private final byte[] tweak;
|
||||
|
||||
VaultTransformContext(String transformation, byte[] tweak) {
|
||||
this.transformation = transformation;
|
||||
this.tweak = tweak;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new {@link VaultTransformRequestBuilder}.
|
||||
*/
|
||||
public static VaultTransformRequestBuilder builder() {
|
||||
return new VaultTransformRequestBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an empty {@link VaultTransformContext}.
|
||||
*/
|
||||
public static VaultTransformContext empty() {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link VaultTransformContext} given {@code transformation} bytes.
|
||||
* @param transformation name as a byte array, must not be {@literal null}.
|
||||
* @return a {@link VaultTransformContext} for {@code transformation}.
|
||||
*/
|
||||
public static VaultTransformContext fromTransformation(String transformation) {
|
||||
return builder().transformation(transformation).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link VaultTransformContext} given {@code tweak} String.
|
||||
* @param tweak bytes, must be 7 characters long, must not be {@literal null}.
|
||||
* @return a {@link VaultTransformContext} for {@code tweak}.
|
||||
*/
|
||||
public static VaultTransformContext fromTweak(byte[] tweak) {
|
||||
return builder().tweak(tweak).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the transformation name.
|
||||
*/
|
||||
public String getTransformation() {
|
||||
return this.transformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the tweak
|
||||
*/
|
||||
public byte[] getTweak() {
|
||||
return this.tweak;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof VaultTransformContext))
|
||||
return false;
|
||||
VaultTransformContext that = (VaultTransformContext) o;
|
||||
return this.transformation.equals(that.transformation) && Arrays.equals(this.tweak, that.tweak);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.transformation.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(this.tweak);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link VaultTransformContext}.
|
||||
*/
|
||||
public static class VaultTransformRequestBuilder {
|
||||
|
||||
private String transformation = "";
|
||||
|
||||
private byte[] tweak = new byte[0];
|
||||
|
||||
VaultTransformRequestBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a transformation to be used with the {@code transform} operation.
|
||||
* @param transformation name, provided as a String.
|
||||
* @return {@code this} {@link VaultTransformRequestBuilder}.
|
||||
*/
|
||||
public VaultTransformRequestBuilder transformation(String transformation) {
|
||||
|
||||
Assert.notNull(transformation, "Transformation must not be null");
|
||||
|
||||
this.transformation = transformation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the tweak value for a {@code transform} operation. Must be provided
|
||||
* during decoding for all transformations with a tweak_source of "supplied" or
|
||||
* "generated". Must be provided during encoding for all transformations with a
|
||||
* tweak_source of "supplied".
|
||||
* @param tweak value must be exactly 56 bits (7 bytes), value must be supplied
|
||||
* during any subsequent decoding after an encoding. Failure to do so will result
|
||||
* in a decode that does not return the original value.
|
||||
* @return {@code this} {@link VaultTransformRequestBuilder}.
|
||||
*/
|
||||
public VaultTransformRequestBuilder tweak(byte[] tweak) {
|
||||
|
||||
Assert.notNull(tweak, "Tweak must not be null");
|
||||
|
||||
this.tweak = tweak;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new {@link VaultTransformContext} instance.
|
||||
* @return a new {@link VaultTransformContext}.
|
||||
*/
|
||||
public VaultTransformContext build() {
|
||||
return new VaultTransformContext(this.transformation, this.tweak);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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 org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.VaultException;
|
||||
|
||||
/**
|
||||
* Holds the response from decryption operation and provides methods to access the result.
|
||||
*
|
||||
* @author Lauren Voswinkel
|
||||
* @since 2.3
|
||||
*/
|
||||
public class VaultTransformDecodeResult extends AbstractResult<TransformPlaintext> {
|
||||
|
||||
private final @Nullable TransformPlaintext plaintext;
|
||||
|
||||
/**
|
||||
* Create {@link VaultTransformDecodeResult} for a successfully decrypted {@link TransformPlaintext}
|
||||
* .
|
||||
* @param plaintext must not be {@literal null}.
|
||||
*/
|
||||
public VaultTransformDecodeResult(TransformPlaintext plaintext) {
|
||||
|
||||
Assert.notNull(plaintext, "Plaintext must not be null");
|
||||
|
||||
this.plaintext = plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link VaultTransformDecodeResult} for an error during decryption.
|
||||
* @param exception must not be {@literal null}.
|
||||
*/
|
||||
public VaultTransformDecodeResult(VaultException exception) {
|
||||
|
||||
super(exception);
|
||||
this.plaintext = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected TransformPlaintext get0() {
|
||||
return this.plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result as {@link String} or throw a {@link VaultException} if the
|
||||
* operation completed with an error. Use {@link #isSuccessful()} to verify the
|
||||
* success status of this result without throwing an exception.
|
||||
* @return the result value.
|
||||
* @throws VaultException if the operation completed with an error.
|
||||
*/
|
||||
@Nullable
|
||||
public String getAsString() {
|
||||
|
||||
TransformPlaintext plaintext = get();
|
||||
return plaintext == null ? null : plaintext.asString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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 org.springframework.lang.Nullable;
|
||||
import org.springframework.vault.VaultException;
|
||||
|
||||
/**
|
||||
* Holds the response from encryption operation and provides methods to access the result.
|
||||
*
|
||||
* @author Lauren Voswinkel
|
||||
* @since 2.3
|
||||
*/
|
||||
public class VaultTransformEncodeResult extends AbstractResult<TransformCiphertext> {
|
||||
|
||||
private final @Nullable TransformCiphertext cipherText;
|
||||
|
||||
/**
|
||||
* Create {@link VaultTransformEncodeResult} for a successfully encrypted
|
||||
* {@link TransformCiphertext} .
|
||||
* @param cipherText must not be {@literal null}.
|
||||
*/
|
||||
public VaultTransformEncodeResult(TransformCiphertext cipherText) {
|
||||
this.cipherText = cipherText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link VaultTransformEncodeResult} for an error during encryption.
|
||||
* @param exception must not be {@literal null}.
|
||||
*/
|
||||
public VaultTransformEncodeResult(VaultException exception) {
|
||||
|
||||
super(exception);
|
||||
this.cipherText = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected TransformCiphertext get0() {
|
||||
return this.cipherText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result as {@link String} or throw a {@link VaultException} if the
|
||||
* operation completed with an error. Use {@link #isSuccessful()} to verify the
|
||||
* success status of this result without throwing an exception.
|
||||
* @return the result value.
|
||||
* @throws VaultException if the operation completed with an error.
|
||||
*/
|
||||
@Nullable
|
||||
public String getAsString() {
|
||||
|
||||
TransformCiphertext ciphertext = get();
|
||||
return ciphertext == null ? null : ciphertext.getCiphertext();
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ class VaultNamespaceSecretIntegrationTests extends IntegrationTestSupport {
|
||||
|
||||
RestTemplateBuilder devRestTemplate;
|
||||
|
||||
RestTemplateBuilder maketingRestTemplate;
|
||||
RestTemplateBuilder marketingRestTemplate;
|
||||
|
||||
WebClientBuilder marketingWebClientBuilder = WebClientBuilder.builder()
|
||||
.httpConnector(ClientHttpConnectorFactory.create(new ClientOptions(), Settings.createSslConfiguration()))
|
||||
@@ -97,7 +97,7 @@ class VaultNamespaceSecretIntegrationTests extends IntegrationTestSupport {
|
||||
.endpoint(TestRestTemplateFactory.TEST_VAULT_ENDPOINT).customizers(restTemplate -> restTemplate
|
||||
.getInterceptors().add(VaultClients.createNamespaceInterceptor("dev")));
|
||||
|
||||
this.maketingRestTemplate = RestTemplateBuilder.builder()
|
||||
this.marketingRestTemplate = RestTemplateBuilder.builder()
|
||||
.requestFactory(
|
||||
ClientHttpRequestFactoryFactory.create(new ClientOptions(), Settings.createSslConfiguration()))
|
||||
.endpoint(TestRestTemplateFactory.TEST_VAULT_ENDPOINT)
|
||||
@@ -111,7 +111,7 @@ class VaultNamespaceSecretIntegrationTests extends IntegrationTestSupport {
|
||||
this.devToken = dev.opsForToken().create(VaultTokenRequest.builder().withPolicy("relaxed").build()).getToken()
|
||||
.getToken();
|
||||
|
||||
VaultTemplate marketing = new VaultTemplate(this.maketingRestTemplate,
|
||||
VaultTemplate marketing = new VaultTemplate(this.marketingRestTemplate,
|
||||
new SimpleSessionManager(new TokenAuthentication(Settings.token())));
|
||||
|
||||
mountKv(marketing, "marketing-secrets");
|
||||
@@ -137,7 +137,7 @@ class VaultNamespaceSecretIntegrationTests extends IntegrationTestSupport {
|
||||
|
||||
VaultTemplate dev = new VaultTemplate(this.devRestTemplate,
|
||||
new SimpleSessionManager(new TokenAuthentication(this.devToken)));
|
||||
VaultTemplate marketing = new VaultTemplate(this.maketingRestTemplate,
|
||||
VaultTemplate marketing = new VaultTemplate(this.marketingRestTemplate,
|
||||
new SimpleSessionManager(new TokenAuthentication(this.marketingToken)));
|
||||
|
||||
dev.write("dev-secrets/my-secret", Collections.singletonMap("key", "dev"));
|
||||
@@ -163,7 +163,7 @@ class VaultNamespaceSecretIntegrationTests extends IntegrationTestSupport {
|
||||
@Test
|
||||
void reactiveNamespaceSecretsAreIsolated() {
|
||||
|
||||
VaultTemplate marketing = new VaultTemplate(this.maketingRestTemplate,
|
||||
VaultTemplate marketing = new VaultTemplate(this.marketingRestTemplate,
|
||||
new SimpleSessionManager(new TokenAuthentication(this.marketingToken)));
|
||||
|
||||
ReactiveVaultTemplate reactiveMarketing = new ReactiveVaultTemplate(this.marketingWebClientBuilder,
|
||||
@@ -181,7 +181,7 @@ class VaultNamespaceSecretIntegrationTests extends IntegrationTestSupport {
|
||||
@Test
|
||||
void shouldReportInitialized() {
|
||||
|
||||
VaultTemplate marketing = new VaultTemplate(this.maketingRestTemplate,
|
||||
VaultTemplate marketing = new VaultTemplate(this.marketingRestTemplate,
|
||||
new SimpleSessionManager(new TokenAuthentication(this.marketingToken)));
|
||||
|
||||
assertThat(marketing.opsForSys().isInitialized()).isTrue();
|
||||
@@ -190,7 +190,7 @@ class VaultNamespaceSecretIntegrationTests extends IntegrationTestSupport {
|
||||
@Test
|
||||
void shouldReportHealth() {
|
||||
|
||||
VaultTemplate marketing = new VaultTemplate(this.maketingRestTemplate,
|
||||
VaultTemplate marketing = new VaultTemplate(this.marketingRestTemplate,
|
||||
new SimpleSessionManager(new TokenAuthentication(this.marketingToken)));
|
||||
|
||||
assertThat(marketing.opsForSys().health().isInitialized()).isTrue();
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.core;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.vault.support.VaultMount;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
import org.springframework.vault.util.RequiresVaultVersion;
|
||||
import org.springframework.vault.util.Version;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultTemplate} using the {@code transform} backend.
|
||||
*
|
||||
* @author Lauren Voswinkel
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = VaultIntegrationTestConfiguration.class)
|
||||
@RequiresVaultVersion("1.4.0")
|
||||
class VaultTemplateTransformIntegrationTests extends IntegrationTestSupport {
|
||||
|
||||
@Autowired
|
||||
VaultOperations vaultOperations;
|
||||
|
||||
Version vaultVersion;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
Assumptions.assumeTrue(prepare().getVersion().isEnterprise(), "Transform Secrets Engine requires enterprise version");
|
||||
|
||||
VaultSysOperations adminOperations = this.vaultOperations.opsForSys();
|
||||
|
||||
this.vaultVersion = prepare().getVersion();
|
||||
|
||||
if (!adminOperations.getMounts().containsKey("transform/")) {
|
||||
adminOperations.mount("transform", VaultMount.create("transform"));
|
||||
}
|
||||
|
||||
// Write a transformation/role
|
||||
this.vaultOperations.write("transform/transformation/myssn", "{\"type\": \"fpe\", \"template\": \"builtin/socialsecuritynumber\", \"allowed_roles\": [\"myrole\"]}");
|
||||
this.vaultOperations.write("transform/role/myrole", "{\"transformations\": [\"myssn\"]}");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
this.vaultOperations.delete("transform/role/myrole");
|
||||
this.vaultOperations.delete("transform/transformation/myssn");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncode() {
|
||||
|
||||
VaultResponse response = this.vaultOperations.write("transform/encode/myrole",
|
||||
String.format("{\"value\": \"123-45-6789\", \"tweak\": \"%s\"}",
|
||||
Base64Utils.encodeToString("somenum".getBytes())));
|
||||
|
||||
assertThat((String) response.getRequiredData().get("encoded_value")).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncodeAndDecode() {
|
||||
|
||||
String value = "123-45-6789";
|
||||
VaultResponse response = this.vaultOperations.write("transform/encode/myrole",
|
||||
String.format("{\"value\": \"%s\", \"tweak\": \"%s\"}",
|
||||
value,
|
||||
Base64Utils.encodeToString("somenum".getBytes())));
|
||||
|
||||
String encoded = (String) response.getRequiredData().get("encoded_value");
|
||||
VaultResponse decoded = this.vaultOperations.write("transform/decode/myrole",
|
||||
String.format("{\"value\": \"%s\", \"tweak\": \"%s\"}",
|
||||
encoded,
|
||||
Base64Utils.encodeToString("somenum".getBytes())));
|
||||
|
||||
assertThat((String) decoded.getRequiredData().get("decoded_value"))
|
||||
.isEqualTo(value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.core;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.vault.VaultException;
|
||||
import org.springframework.vault.support.TransformPlaintext;
|
||||
import org.springframework.vault.support.TransformCiphertext;
|
||||
import org.springframework.vault.support.VaultMount;
|
||||
import org.springframework.vault.support.VaultTransformContext;
|
||||
import org.springframework.vault.support.VaultTransformDecodeResult;
|
||||
import org.springframework.vault.support.VaultTransformEncodeResult;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
import org.springframework.vault.util.RequiresVaultVersion;
|
||||
import org.springframework.vault.util.Version;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link VaultTemplate} using the {@code transform} backend.
|
||||
*
|
||||
* @author Lauren Voswinkel
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = VaultIntegrationTestConfiguration.class)
|
||||
@RequiresVaultVersion("1.4.0")
|
||||
class VaultTransformTemplateIntegrationTests extends IntegrationTestSupport {
|
||||
|
||||
@Autowired
|
||||
VaultOperations vaultOperations;
|
||||
|
||||
VaultTransformOperations transformOperations;
|
||||
|
||||
Version vaultVersion;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
Assumptions.assumeTrue(prepare().getVersion().isEnterprise(), "Transform Secrets Engine requires enterprise version");
|
||||
|
||||
VaultSysOperations adminOperations = this.vaultOperations.opsForSys();
|
||||
this.transformOperations = this.vaultOperations.opsForTransform();
|
||||
|
||||
this.vaultVersion = prepare().getVersion();
|
||||
|
||||
if (!adminOperations.getMounts().containsKey("transform/")) {
|
||||
adminOperations.mount("transform", VaultMount.create("transform"));
|
||||
}
|
||||
|
||||
// Write a transformation/role
|
||||
this.vaultOperations.write("transform/transformation/myssn", "{" +
|
||||
"\"type\": \"fpe\", " +
|
||||
"\"template\": \"builtin/socialsecuritynumber\", " +
|
||||
"\"allowed_roles\": [\"myrole\"]}"
|
||||
);
|
||||
this.vaultOperations.write("transform/role/myrole", "{\"transformations\": [\"myssn\", \"internalssn\"]}");
|
||||
|
||||
this.vaultOperations.write("transform/transformation/internalssn", "{" +
|
||||
"\"type\": \"fpe\", " +
|
||||
"\"tweak_source\": \"internal\", " +
|
||||
"\"template\": \"builtin/socialsecuritynumber\", " +
|
||||
"\"allowed_roles\": [\"myrole\", \"internalrole\"]}"
|
||||
);
|
||||
|
||||
this.vaultOperations.write("transform/role/internalrole", "{\"transformations\": [\"internalssn\"]}");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
this.vaultOperations.delete("transform/role/myrole");
|
||||
this.vaultOperations.delete("transform/transformation/myssn");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeCreatesCiphertextWithTransformationAndTweak() {
|
||||
|
||||
VaultTransformContext transformRequest = VaultTransformContext.builder().transformation("myssn")
|
||||
.tweak("somenum".getBytes())
|
||||
.build();
|
||||
|
||||
String response = this.transformOperations.encode("myrole", "123-45-6789".getBytes(), transformRequest);
|
||||
assertThat(response).matches("[0-9]{3}-[0-9]{2}-[0-9]{4}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeThrowsVaultExceptionWithSuppliedTransformationAndNoTweak() {
|
||||
|
||||
VaultTransformContext transformRequest = VaultTransformContext.builder().transformation("myssn").build();
|
||||
|
||||
Exception exception = assertThrows(VaultException.class, () -> {
|
||||
this.transformOperations.encode("myrole", "123-45-6789".getBytes(), transformRequest);
|
||||
});
|
||||
|
||||
String expectedMessage = "incorrect tweak size provided";
|
||||
String actualMessage = exception.getMessage();
|
||||
|
||||
assertThat(actualMessage).contains(expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeCreatesCiphertextWithInternalTransformationAndNoTweak() {
|
||||
|
||||
VaultTransformContext transformRequest = VaultTransformContext.builder().transformation("internalssn").build();
|
||||
|
||||
String response = this.transformOperations.encode("myrole", "123-45-6789".getBytes(), transformRequest);
|
||||
assertThat(response).matches("[0-9]{3}-[0-9]{2}-[0-9]{4}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeAndDecodeYieldsStartingResultWithSameTweakValueProvided() {
|
||||
VaultTransformContext transformRequest = VaultTransformContext.builder().transformation("myssn").tweak("somenum".getBytes()).build();
|
||||
String targetValue = "123-45-6789";
|
||||
|
||||
String response = this.transformOperations.encode("myrole", targetValue.getBytes(), transformRequest);
|
||||
assertThat(response).matches("[0-9]{3}-[0-9]{2}-[0-9]{4}");
|
||||
assertThat(response).isNotEqualTo(targetValue);
|
||||
|
||||
String decodeResponse = this.transformOperations.decode("myrole", response, transformRequest);
|
||||
assertThat(decodeResponse).isEqualTo(targetValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeAndDecodeDoesNotYieldStartingResultWithDifferentTweakValueProvided() {
|
||||
VaultTransformContext transformRequest = VaultTransformContext.builder().transformation("myssn").tweak("somenum".getBytes()).build();
|
||||
String targetValue = "123-45-6789";
|
||||
|
||||
String response = this.transformOperations.encode("myrole", targetValue.getBytes(), transformRequest);
|
||||
assertThat(response).matches("[0-9]{3}-[0-9]{2}-[0-9]{4}");
|
||||
assertThat(response).isNotEqualTo(targetValue);
|
||||
|
||||
VaultTransformContext decodeRequest = VaultTransformContext.builder().transformation("myssn").tweak("numsome".getBytes()).build();
|
||||
|
||||
String decodeResponse = this.transformOperations.decode("myrole", response, decodeRequest);
|
||||
assertThat(decodeResponse).isNotEqualTo(targetValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeAndDecodeWithoutContextWorksForInternalTweakSource() {
|
||||
String targetValue = "123-45-6789";
|
||||
|
||||
String response = this.transformOperations.encode("internalrole", targetValue);
|
||||
assertThat(response).matches("[0-9]{3}-[0-9]{2}-[0-9]{4}");
|
||||
assertThat(response).isNotEqualTo(targetValue);
|
||||
|
||||
String decodeResponse = this.transformOperations.decode("internalrole", response);
|
||||
assertThat(decodeResponse).isEqualTo(targetValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchEncodeAndDecodeYieldsStartingResults() {
|
||||
VaultTransformContext transformRequest = VaultTransformContext.builder().transformation("myssn").tweak("somenum".getBytes()).build();
|
||||
|
||||
List<String> ssns = Arrays.asList(
|
||||
"123-01-4567",
|
||||
"123-02-4567",
|
||||
"123-03-4567",
|
||||
"123-04-4567",
|
||||
"123-05-4567"
|
||||
);
|
||||
|
||||
List<VaultTransformEncodeResult> encoded = this.transformOperations.encode("myrole",
|
||||
Arrays.asList(
|
||||
TransformPlaintext.of(ssns.get(0)).with(transformRequest),
|
||||
TransformPlaintext.of(ssns.get(1)).with(transformRequest),
|
||||
TransformPlaintext.of(ssns.get(2)).with(transformRequest),
|
||||
TransformPlaintext.of(ssns.get(3)).with(transformRequest),
|
||||
TransformPlaintext.of(ssns.get(4)).with(transformRequest)
|
||||
)
|
||||
);
|
||||
|
||||
List<VaultTransformDecodeResult> decoded = this.transformOperations.decode("myrole",
|
||||
Arrays.asList(
|
||||
TransformCiphertext.of(encoded.get(0).getAsString()).with(transformRequest),
|
||||
TransformCiphertext.of(encoded.get(1).getAsString()).with(transformRequest),
|
||||
TransformCiphertext.of(encoded.get(2).getAsString()).with(transformRequest),
|
||||
TransformCiphertext.of(encoded.get(3).getAsString()).with(transformRequest),
|
||||
TransformCiphertext.of(encoded.get(4).getAsString()).with(transformRequest)
|
||||
)
|
||||
);
|
||||
|
||||
for (int i = 0; i < decoded.size(); i++) {
|
||||
assertThat(decoded.get(i).getAsString()).isEqualTo(ssns.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchEncodeAndDecodeYieldsStartingResultsForInternalWithNoContext() {
|
||||
|
||||
List<String> ssns = Arrays.asList(
|
||||
"123-01-4567",
|
||||
"123-02-4567",
|
||||
"123-03-4567",
|
||||
"123-04-4567",
|
||||
"123-05-4567"
|
||||
);
|
||||
|
||||
List<VaultTransformEncodeResult> encoded = this.transformOperations.encode("internalrole",
|
||||
Arrays.asList(
|
||||
TransformPlaintext.of(ssns.get(0)),
|
||||
TransformPlaintext.of(ssns.get(1)),
|
||||
TransformPlaintext.of(ssns.get(2)),
|
||||
TransformPlaintext.of(ssns.get(3)),
|
||||
TransformPlaintext.of(ssns.get(4))
|
||||
)
|
||||
);
|
||||
|
||||
List<VaultTransformDecodeResult> decoded = this.transformOperations.decode("internalrole",
|
||||
Arrays.asList(
|
||||
TransformCiphertext.of(encoded.get(0).getAsString()),
|
||||
TransformCiphertext.of(encoded.get(1).getAsString()),
|
||||
TransformCiphertext.of(encoded.get(2).getAsString()),
|
||||
TransformCiphertext.of(encoded.get(3).getAsString()),
|
||||
TransformCiphertext.of(encoded.get(4).getAsString())
|
||||
)
|
||||
);
|
||||
|
||||
for (int i = 0; i < decoded.size(); i++) {
|
||||
assertThat(decoded.get(i).getAsString()).isEqualTo(ssns.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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 org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link VaultTransitContext}.
|
||||
*
|
||||
* @author Lauren Voswinkel
|
||||
*/
|
||||
class VaultTransformContextUnitTests {
|
||||
|
||||
@Test
|
||||
void rejectsNullTransformation() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> VaultTransformContext.fromTransformation(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsFromTransformation() {
|
||||
|
||||
String transformName = "some_transformation";
|
||||
|
||||
VaultTransformContext context = VaultTransformContext.fromTransformation(transformName);
|
||||
|
||||
assertThat(context.getTransformation()).isEqualTo(transformName);
|
||||
assertThat(context.getTweak()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsNullTweak() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> VaultTransformContext.fromTweak(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsFromTweak() {
|
||||
|
||||
byte[] bytes = new byte[] { 1 };
|
||||
|
||||
VaultTransformContext context = VaultTransformContext.fromTweak(bytes);
|
||||
|
||||
assertThat(context.getTweak()).isEqualTo(bytes);
|
||||
assertThat(context.getTransformation()).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user