mirror of
https://github.com/jlengrand/quarkus.git
synced 2026-03-10 08:41:22 +00:00
Merge pull request #7294 from vsevel/write_secret
Add Vault write and delete secret Fixes #7155
This commit is contained in:
@@ -20,4 +20,21 @@ public interface VaultKVSecretEngine {
|
||||
*/
|
||||
Map<String, String> readSecret(String path);
|
||||
|
||||
/**
|
||||
* Writes the secret at the given path. If the path does not exist, the secret will
|
||||
* be created. If not the new secret will be merged with the existing one.
|
||||
*
|
||||
* @param path in Vault, without the kv engine mount path
|
||||
* @param secret to write at path
|
||||
*/
|
||||
void writeSecret(String path, Map<String, String> secret);
|
||||
|
||||
/**
|
||||
* Deletes the secret at the given path. It has no effect if no secret is currently
|
||||
* stored at path.
|
||||
*
|
||||
* @param path to delete
|
||||
*/
|
||||
void deleteSecret(String path);
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import java.util.Map;
|
||||
|
||||
import io.quarkus.vault.VaultKVSecretEngine;
|
||||
import io.quarkus.vault.runtime.client.VaultClient;
|
||||
import io.quarkus.vault.runtime.client.dto.kv.VaultKvSecretV1;
|
||||
import io.quarkus.vault.runtime.client.dto.kv.VaultKvSecretV2;
|
||||
import io.quarkus.vault.runtime.client.dto.kv.VaultKvSecretV2WriteBody;
|
||||
import io.quarkus.vault.runtime.config.VaultRuntimeConfig;
|
||||
|
||||
public class VaultKvManager implements VaultKVSecretEngine {
|
||||
@@ -25,10 +28,38 @@ public class VaultKvManager implements VaultKVSecretEngine {
|
||||
String mount = serverConfig.kvSecretEngineMountPath;
|
||||
|
||||
if (serverConfig.kvSecretEngineVersion == 1) {
|
||||
return vaultClient.getSecretV1(clientToken, mount, path).data;
|
||||
VaultKvSecretV1 secretV1 = vaultClient.getSecretV1(clientToken, mount, path);
|
||||
return secretV1.data;
|
||||
} else {
|
||||
return vaultClient.getSecretV2(clientToken, mount, path).data.data;
|
||||
VaultKvSecretV2 secretV2 = vaultClient.getSecretV2(clientToken, mount, path);
|
||||
return secretV2.data.data;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSecret(String path, Map<String, String> secret) {
|
||||
|
||||
String clientToken = vaultAuthManager.getClientToken();
|
||||
String mount = serverConfig.kvSecretEngineMountPath;
|
||||
|
||||
if (serverConfig.kvSecretEngineVersion == 1) {
|
||||
vaultClient.writeSecretV1(clientToken, mount, path, secret);
|
||||
} else {
|
||||
VaultKvSecretV2WriteBody body = new VaultKvSecretV2WriteBody();
|
||||
body.data = secret;
|
||||
vaultClient.writeSecretV2(clientToken, mount, path, body);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSecret(String path) {
|
||||
String clientToken = vaultAuthManager.getClientToken();
|
||||
String mount = serverConfig.kvSecretEngineMountPath;
|
||||
|
||||
if (serverConfig.kvSecretEngineVersion == 1) {
|
||||
vaultClient.deleteSecretV1(clientToken, mount, path);
|
||||
} else {
|
||||
vaultClient.deleteSecretV2(clientToken, mount, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import static io.quarkus.vault.runtime.client.OkHttpClientFactory.createHttpClie
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
@@ -25,6 +26,8 @@ import io.quarkus.vault.runtime.client.dto.auth.VaultUserPassAuthBody;
|
||||
import io.quarkus.vault.runtime.client.dto.database.VaultDatabaseCredentials;
|
||||
import io.quarkus.vault.runtime.client.dto.kv.VaultKvSecretV1;
|
||||
import io.quarkus.vault.runtime.client.dto.kv.VaultKvSecretV2;
|
||||
import io.quarkus.vault.runtime.client.dto.kv.VaultKvSecretV2Write;
|
||||
import io.quarkus.vault.runtime.client.dto.kv.VaultKvSecretV2WriteBody;
|
||||
import io.quarkus.vault.runtime.client.dto.sys.VaultLeasesBody;
|
||||
import io.quarkus.vault.runtime.client.dto.sys.VaultLeasesLookup;
|
||||
import io.quarkus.vault.runtime.client.dto.sys.VaultRenewLease;
|
||||
@@ -88,6 +91,26 @@ public class OkHttpVaultClient implements VaultClient {
|
||||
return get(secretEnginePath + "/data/" + path, token, VaultKvSecretV2.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSecretV1(String token, String secretEnginePath, String path, Map<String, String> secret) {
|
||||
post(secretEnginePath + "/" + path, token, secret, null, 204);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSecretV2(String token, String secretEnginePath, String path, VaultKvSecretV2WriteBody body) {
|
||||
post(secretEnginePath + "/data/" + path, token, body, VaultKvSecretV2Write.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSecretV1(String token, String secretEnginePath, String path) {
|
||||
delete(secretEnginePath + "/" + path, token, null, null, 204);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSecretV2(String token, String secretEnginePath, String path) {
|
||||
delete(secretEnginePath + "/data/" + path, token, null, null, 204);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultRenewSelf renewSelf(String token, String increment) {
|
||||
VaultRenewSelfBody body = new VaultRenewSelfBody(increment);
|
||||
@@ -146,6 +169,11 @@ public class OkHttpVaultClient implements VaultClient {
|
||||
|
||||
// ---
|
||||
|
||||
protected <T> T delete(String path, String token, Object body, Class<T> resultClass, int expectedCode) {
|
||||
Request request = builder(path, token).delete(requestBody(body)).build();
|
||||
return exec(request, resultClass, expectedCode);
|
||||
}
|
||||
|
||||
protected <T> T post(String path, String token, Object body, Class<T> resultClass, int expectedCode) {
|
||||
Request request = builder(path, token).post(requestBody(body)).build();
|
||||
return exec(request, resultClass, expectedCode);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.quarkus.vault.runtime.client;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.quarkus.vault.runtime.client.dto.auth.VaultAppRoleAuth;
|
||||
import io.quarkus.vault.runtime.client.dto.auth.VaultKubernetesAuth;
|
||||
import io.quarkus.vault.runtime.client.dto.auth.VaultLookupSelf;
|
||||
@@ -8,6 +10,7 @@ import io.quarkus.vault.runtime.client.dto.auth.VaultUserPassAuth;
|
||||
import io.quarkus.vault.runtime.client.dto.database.VaultDatabaseCredentials;
|
||||
import io.quarkus.vault.runtime.client.dto.kv.VaultKvSecretV1;
|
||||
import io.quarkus.vault.runtime.client.dto.kv.VaultKvSecretV2;
|
||||
import io.quarkus.vault.runtime.client.dto.kv.VaultKvSecretV2WriteBody;
|
||||
import io.quarkus.vault.runtime.client.dto.sys.VaultLeasesLookup;
|
||||
import io.quarkus.vault.runtime.client.dto.sys.VaultRenewLease;
|
||||
import io.quarkus.vault.runtime.client.dto.transit.VaultTransitDecrypt;
|
||||
@@ -43,6 +46,14 @@ public interface VaultClient {
|
||||
|
||||
VaultKvSecretV2 getSecretV2(String token, String secretEnginePath, String path);
|
||||
|
||||
void writeSecretV1(String token, String secretEnginePath, String path, Map<String, String> values);
|
||||
|
||||
void writeSecretV2(String token, String secretEnginePath, String path, VaultKvSecretV2WriteBody body);
|
||||
|
||||
void deleteSecretV1(String token, String secretEnginePath, String path);
|
||||
|
||||
void deleteSecretV2(String token, String secretEnginePath, String path);
|
||||
|
||||
VaultDatabaseCredentials generateDatabaseCredentials(String token, String databaseCredentialsRole);
|
||||
|
||||
VaultTransitEncrypt encrypt(String token, String keyName, VaultTransitEncryptBody body);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.quarkus.vault.runtime.client.dto.kv;
|
||||
|
||||
import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO;
|
||||
|
||||
/**
|
||||
* {"request_id":"89ce65f0-e494-cfea-975e-6029d235614e","lease_id":"","renewable":false,"lease_duration":0,"data":{"created_time":"2020-02-19T21:18:06.0367901Z","deletion_time":"","destroyed":false,"version":1},"wrap_info":null,"warnings":null,"auth":null}
|
||||
*/
|
||||
public class VaultKvSecretV2Write extends AbstractVaultDTO<VaultKvSecretV2WriteData, Object> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.quarkus.vault.runtime.client.dto.kv;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.quarkus.vault.runtime.client.dto.VaultModel;
|
||||
|
||||
public class VaultKvSecretV2WriteBody implements VaultModel {
|
||||
|
||||
public Map<String, Integer> options;
|
||||
public Map<String, String> data;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.quarkus.vault.runtime.client.dto.kv;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import io.quarkus.vault.runtime.client.dto.VaultModel;
|
||||
|
||||
public class VaultKvSecretV2WriteData implements VaultModel {
|
||||
|
||||
@JsonProperty("created_time")
|
||||
public String createdTime;
|
||||
@JsonProperty("deletion_time")
|
||||
public String deletionTime;
|
||||
public boolean destroyed;
|
||||
public int version;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.quarkus.vault;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import io.quarkus.test.QuarkusUnitTest;
|
||||
import io.quarkus.test.common.QuarkusTestResource;
|
||||
import io.quarkus.vault.test.VaultTestExtension;
|
||||
import io.quarkus.vault.test.VaultTestLifecycleManager;
|
||||
|
||||
@DisabledOnOs(OS.WINDOWS) // https://github.com/quarkusio/quarkus/issues/3796
|
||||
@QuarkusTestResource(VaultTestLifecycleManager.class)
|
||||
public class VaultKv2ITCase {
|
||||
|
||||
private static final Logger log = Logger.getLogger(VaultKv2ITCase.class.getName());
|
||||
|
||||
public static final String CRUD_PATH = "crud";
|
||||
|
||||
@RegisterExtension
|
||||
static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer(
|
||||
() -> ShrinkWrap.create(JavaArchive.class)
|
||||
.addAsResource("application-vault-kv-version2-datasource.properties", "application.properties"));
|
||||
@Inject
|
||||
VaultKVSecretEngine kvSecretEngine;
|
||||
|
||||
@Test
|
||||
public void crudSecretV2() {
|
||||
VaultTestExtension.assertCrudSecret(kvSecretEngine);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import org.jboss.logging.Logger;
|
||||
|
||||
import io.quarkus.vault.VaultKVSecretEngine;
|
||||
import io.quarkus.vault.VaultTransitSecretEngine;
|
||||
import io.quarkus.vault.runtime.client.VaultClientException;
|
||||
import io.quarkus.vault.transit.ClearData;
|
||||
import io.quarkus.vault.transit.SigningInput;
|
||||
|
||||
@@ -48,12 +49,29 @@ public class VaultTestService {
|
||||
return "password=" + password + "; expected: " + expectedPassword;
|
||||
}
|
||||
|
||||
// basic
|
||||
Map<String, String> secrets = kv.readSecret("foo");
|
||||
String expectedSecrets = "{secret=s\u20accr\u20act}";
|
||||
if (!expectedSecrets.equals(secrets.toString())) {
|
||||
return "/foo=" + secrets + "; expected: " + expectedSecrets;
|
||||
}
|
||||
|
||||
// crud
|
||||
kv.writeSecret("crud", secrets);
|
||||
secrets = kv.readSecret("crud");
|
||||
if (!expectedSecrets.equals(secrets.toString())) {
|
||||
return "/crud=" + secrets + "; expected: " + expectedSecrets;
|
||||
}
|
||||
kv.deleteSecret("crud");
|
||||
try {
|
||||
secrets = kv.readSecret("crud");
|
||||
return "/crud=" + secrets + "; expected 404";
|
||||
} catch (VaultClientException e) {
|
||||
if (e.getStatus() != 404) {
|
||||
return "http response code=" + e.getStatus() + "; expected: 404";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
List gifts = entityManager.createQuery("select g from Gift g").getResultList();
|
||||
int count = gifts.size();
|
||||
|
||||
@@ -66,6 +66,7 @@ import io.quarkus.vault.runtime.client.dto.transit.VaultTransitVerify;
|
||||
import io.quarkus.vault.runtime.client.dto.transit.VaultTransitVerifyBatchInput;
|
||||
import io.quarkus.vault.runtime.client.dto.transit.VaultTransitVerifyBody;
|
||||
import io.quarkus.vault.runtime.config.VaultAuthenticationType;
|
||||
import io.quarkus.vault.test.VaultTestExtension;
|
||||
import io.quarkus.vault.test.VaultTestLifecycleManager;
|
||||
import io.quarkus.vault.test.client.TestVaultClient;
|
||||
import io.quarkus.vault.test.client.dto.VaultTransitHash;
|
||||
@@ -79,6 +80,8 @@ public class VaultITCase {
|
||||
|
||||
public static final String MY_PASSWORD = "my-password";
|
||||
|
||||
public static final String CRUD_PATH = "crud";
|
||||
|
||||
@RegisterExtension
|
||||
static final QuarkusUnitTest config = new QuarkusUnitTest()
|
||||
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
|
||||
@@ -130,6 +133,11 @@ public class VaultITCase {
|
||||
assertEquals("{" + SECRET_KEY + "=" + SECRET_VALUE + "}", secrets.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void crudSecretV1() {
|
||||
VaultTestExtension.assertCrudSecret(kvSecretEngine);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpclient() {
|
||||
|
||||
|
||||
@@ -20,7 +20,10 @@ import java.sql.Statement;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -32,6 +35,7 @@ import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
|
||||
import io.quarkus.vault.VaultException;
|
||||
import io.quarkus.vault.VaultKVSecretEngine;
|
||||
import io.quarkus.vault.runtime.VaultManager;
|
||||
import io.quarkus.vault.runtime.client.VaultClientException;
|
||||
import io.quarkus.vault.runtime.config.VaultRuntimeConfig;
|
||||
@@ -76,6 +80,8 @@ public class VaultTestExtension {
|
||||
public static final String TMP_POSTGRES_INIT_SQL_FILE = "/tmp/postgres-init.sql";
|
||||
public static final String TEST_QUERY_STRING = "SELECT 1";
|
||||
|
||||
private static String CRUD_PATH = "crud";
|
||||
|
||||
public GenericContainer vaultContainer;
|
||||
public PostgreSQLContainer postgresContainer;
|
||||
public String rootToken = null;
|
||||
@@ -98,6 +104,40 @@ public class VaultTestExtension {
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertCrudSecret(VaultKVSecretEngine kvSecretEngine) {
|
||||
|
||||
assertDeleteSecret(kvSecretEngine);
|
||||
|
||||
assertDeleteSecret(kvSecretEngine);
|
||||
|
||||
Map<String, String> newsecrets = new HashMap<>();
|
||||
newsecrets.put("first", "one");
|
||||
newsecrets.put("second", "two");
|
||||
kvSecretEngine.writeSecret(CRUD_PATH, newsecrets);
|
||||
assertEquals("{first=one, second=two}", readSecretAsString(kvSecretEngine, CRUD_PATH));
|
||||
|
||||
newsecrets.put("first", "un");
|
||||
newsecrets.put("third", "tres");
|
||||
kvSecretEngine.writeSecret(CRUD_PATH, newsecrets);
|
||||
assertEquals("{first=un, second=two, third=tres}", readSecretAsString(kvSecretEngine, CRUD_PATH));
|
||||
|
||||
assertDeleteSecret(kvSecretEngine);
|
||||
}
|
||||
|
||||
private static void assertDeleteSecret(VaultKVSecretEngine kvSecretEngine) {
|
||||
kvSecretEngine.deleteSecret(CRUD_PATH);
|
||||
try {
|
||||
readSecretAsString(kvSecretEngine, CRUD_PATH);
|
||||
} catch (VaultClientException e) {
|
||||
assertEquals(404, e.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private static String readSecretAsString(VaultKVSecretEngine kvSecretEngine, String path) {
|
||||
Map<String, String> secret = kvSecretEngine.readSecret(path);
|
||||
return new TreeMap<>(secret).toString();
|
||||
}
|
||||
|
||||
private static VaultManager createVaultManager() {
|
||||
VaultRuntimeConfig serverConfig = new VaultRuntimeConfig();
|
||||
serverConfig.tls = new VaultTlsConfig();
|
||||
|
||||
@@ -41,3 +41,11 @@ path "transit/*" {
|
||||
#path "transit/sign/my-sign-key" {
|
||||
# capabilities = [ "read", "update" ]
|
||||
#}
|
||||
|
||||
path "secret/crud" {
|
||||
capabilities = ["read", "create", "update", "delete"]
|
||||
}
|
||||
|
||||
path "secret-v2/data/crud" {
|
||||
capabilities = ["read", "create", "update", "delete"]
|
||||
}
|
||||
Reference in New Issue
Block a user