Skip to content

Commit

Permalink
Merge pull request #7873 from lordofthejars/issue-7865
Browse files Browse the repository at this point in the history
Add support for Vault TOTP secrets
  • Loading branch information
gsmet authored Mar 19, 2020
2 parents 80a279c + 6e80ce5 commit 49b873e
Show file tree
Hide file tree
Showing 26 changed files with 794 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.quarkus.vault;

import java.util.List;
import java.util.Optional;

import io.quarkus.vault.secrets.totp.CreateKeyParameters;
import io.quarkus.vault.secrets.totp.KeyConfiguration;
import io.quarkus.vault.secrets.totp.KeyDefinition;

/**
* This service provides access to the TOTP secret engine.
*
* @see <a href="https://www.vaultproject.io/api/secret/totp/index.html">TOTP Secrets Engine </a>
*/
public interface VaultTOTPSecretEngine {

/**
* Creates or updates a key definition.
*
* @param name of the key.
* @param createKeyParameters required to create or update a key.
* @return Barcode and/or URL of the created OTP key.
*/
Optional<KeyDefinition> createKey(String name, CreateKeyParameters createKeyParameters);

/**
* Queries the key definition.
*
* @param name of the key.
* @return The key configuration.
*/
KeyConfiguration readKey(String name);

/**
* Returns a list of available keys. Only the key names are returned, not any values.
*
* @return List of available keys.
*/
List<String> listKeys();

/**
* Deletes the key definition.
*
* @param name of the key.
*/
void deleteKey(String name);

/**
* Generates a new time-based one-time use password based on the named key.
*
* @param name of the key.
* @return The Code.
*/
String generateCode(String name);

/**
* Validates a time-based one-time use password generated from the named key.
*
* @param name of the key.
* @param code to validate.
* @return True if valid, false otherwise.
*/
boolean validateCode(String name, String code);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class VaultManager {
private VaultDbManager vaultDbManager;
private VaultTransitManager vaultTransitManager;
private VaultCredentialsProvider vaultCredentialsProvider;
private VaultTOTPManager vaultTOTPManager;

public static VaultManager getInstance() {
return instance;
Expand Down Expand Up @@ -42,6 +43,7 @@ public VaultManager(VaultRuntimeConfig serverConfig, VaultClient vaultClient) {
this.vaultDbManager = new VaultDbManager(this.vaultAuthManager, this.vaultClient, serverConfig);
this.vaultTransitManager = new VaultTransitManager(this.vaultAuthManager, this.vaultClient, serverConfig);
this.vaultCredentialsProvider = new VaultCredentialsProvider(serverConfig, this.vaultKvManager, this.vaultDbManager);
this.vaultTOTPManager = new VaultTOTPManager(this.vaultAuthManager, this.vaultClient);
}

public VaultClient getVaultClient() {
Expand All @@ -60,6 +62,10 @@ public VaultKvManager getVaultKvManager() {
return vaultKvManager;
}

public VaultTOTPManager getVaultTOTPManager() {
return vaultTOTPManager;
}

public VaultTransitManager getVaultTransitManager() {
return vaultTransitManager;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import io.quarkus.vault.CredentialsProvider;
import io.quarkus.vault.VaultKVSecretEngine;
import io.quarkus.vault.VaultTOTPSecretEngine;
import io.quarkus.vault.VaultTransitSecretEngine;
import io.quarkus.vault.runtime.config.VaultRuntimeConfig;

Expand All @@ -25,6 +26,12 @@ public VaultTransitSecretEngine createTransitSecretEngine() {
return VaultManager.getInstance().getVaultTransitManager();
}

@Produces
@ApplicationScoped
public VaultTOTPSecretEngine createVaultTOTPSecretEngine() {
return VaultManager.getInstance().getVaultTOTPManager();
}

@Produces
@ApplicationScoped
@Named("vault-credentials-provider")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.quarkus.vault.runtime;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

import io.quarkus.vault.VaultTOTPSecretEngine;
import io.quarkus.vault.runtime.client.VaultClient;
import io.quarkus.vault.runtime.client.VaultClientException;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPCreateKeyBody;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPCreateKeyResult;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPReadKeyResult;
import io.quarkus.vault.secrets.totp.CreateKeyParameters;
import io.quarkus.vault.secrets.totp.KeyConfiguration;
import io.quarkus.vault.secrets.totp.KeyDefinition;

public class VaultTOTPManager implements VaultTOTPSecretEngine {

private VaultAuthManager vaultAuthManager;
private VaultClient vaultClient;

public VaultTOTPManager(VaultAuthManager vaultAuthManager, VaultClient vaultClient) {
this.vaultAuthManager = vaultAuthManager;
this.vaultClient = vaultClient;
}

@Override
public Optional<KeyDefinition> createKey(String name, CreateKeyParameters createKeyParameters) {
VaultTOTPCreateKeyBody body = new VaultTOTPCreateKeyBody();

body.accountName = createKeyParameters.getAccountName();
body.algorithm = createKeyParameters.getAlgorithm();
body.digits = createKeyParameters.getDigits();
body.exported = createKeyParameters.getExported();
body.generate = createKeyParameters.getGenerate();
body.issuer = createKeyParameters.getIssuer();
body.key = createKeyParameters.getKey();
body.keySize = createKeyParameters.getKeySize();
body.period = createKeyParameters.getPeriod();
body.qrSize = createKeyParameters.getQrSize();
body.skew = createKeyParameters.getSkew();
body.url = createKeyParameters.getUrl();

final VaultTOTPCreateKeyResult result = this.vaultClient
.createTOTPKey(getToken(), name, body);

return result == null ? Optional.empty() : Optional.of(new KeyDefinition(result.data.barcode, result.data.url));
}

@Override
public KeyConfiguration readKey(String name) {
final VaultTOTPReadKeyResult result = this.vaultClient.readTOTPKey(getToken(), name);
return new KeyConfiguration(result.data.accountName,
result.data.algorithm, result.data.digits,
result.data.issuer, result.data.period);
}

@Override
public List<String> listKeys() {
try {
return this.vaultClient.listTOTPKeys(getToken()).data.keys;
} catch (VaultClientException e) {
if (e.getStatus() == 404) {
return Collections.emptyList();
}
throw e;
}
}

@Override
public void deleteKey(String name) {
this.vaultClient.deleteTOTPKey(getToken(), name);
}

@Override
public String generateCode(String name) {
return this.vaultClient.generateTOTPCode(getToken(), name).data.code;
}

@Override
public boolean validateCode(String name, String code) {
return this.vaultClient.validateTOTPCode(getToken(), name, code).data.valid;
}

private String getToken() {
return vaultAuthManager.getClientToken();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import org.jboss.logging.Logger;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

Expand All @@ -31,6 +32,13 @@
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;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPCreateKeyBody;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPCreateKeyResult;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPGenerateCodeResult;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPListKeysResult;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPReadKeyResult;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPValidateCodeBody;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPValidateCodeResult;
import io.quarkus.vault.runtime.client.dto.transit.VaultTransitDecrypt;
import io.quarkus.vault.runtime.client.dto.transit.VaultTransitDecryptBody;
import io.quarkus.vault.runtime.client.dto.transit.VaultTransitEncrypt;
Expand Down Expand Up @@ -61,6 +69,7 @@ public OkHttpVaultClient(VaultRuntimeConfig serverConfig) {
this.client = createHttpClient(serverConfig);
this.url = serverConfig.url.get();
this.mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
this.mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}

@Override
Expand Down Expand Up @@ -103,12 +112,12 @@ public void writeSecretV2(String token, String secretEnginePath, String path, Va

@Override
public void deleteSecretV1(String token, String secretEnginePath, String path) {
delete(secretEnginePath + "/" + path, token, null, null, 204);
delete(secretEnginePath + "/" + path, token, 204);
}

@Override
public void deleteSecretV2(String token, String secretEnginePath, String path) {
delete(secretEnginePath + "/data/" + path, token, null, null, 204);
delete(secretEnginePath + "/data/" + path, token, 204);
}

@Override
Expand Down Expand Up @@ -167,11 +176,61 @@ public VaultTransitEncrypt rewrap(String token, String keyName, VaultTransitRewr
return post("transit/rewrap/" + keyName, token, body, VaultTransitEncrypt.class);
}

@Override
public VaultTOTPCreateKeyResult createTOTPKey(String token, String keyName,
VaultTOTPCreateKeyBody body) {
String path = "totp/keys/" + keyName;

// Depending on parameters it might produce an output or not
if (body.isProducingOutput()) {
return post(path, token, body, VaultTOTPCreateKeyResult.class, 200);
} else {
post(path, token, body, 204);
return null;
}
}

@Override
public VaultTOTPReadKeyResult readTOTPKey(String token, String keyName) {
String path = "totp/keys/" + keyName;
return get(path, token, VaultTOTPReadKeyResult.class);
}

@Override
public VaultTOTPListKeysResult listTOTPKeys(String token) {
return list("totp/keys", token, VaultTOTPListKeysResult.class);
}

@Override
public void deleteTOTPKey(String token, String keyName) {
String path = "totp/keys/" + keyName;
delete(path, token, 204);
}

@Override
public VaultTOTPGenerateCodeResult generateTOTPCode(String token, String keyName) {
String path = "totp/code/" + keyName;
return get(path, token, VaultTOTPGenerateCodeResult.class);
}

@Override
public VaultTOTPValidateCodeResult validateTOTPCode(String token, String keyName,
String code) {
String path = "totp/code/" + keyName;
VaultTOTPValidateCodeBody body = new VaultTOTPValidateCodeBody(code);
return post(path, token, body, VaultTOTPValidateCodeResult.class);
}

// ---

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 list(String path, String token, Class<T> resultClass) {
Request request = builder(path, token).method("LIST", null).build();
return exec(request, resultClass);
}

protected <T> T delete(String path, String token, int expectedCode) {
Request request = builder(path, token).delete().build();
return exec(request, expectedCode);
}

protected <T> T post(String path, String token, Object body, Class<T> resultClass, int expectedCode) {
Expand All @@ -184,6 +243,11 @@ protected <T> T post(String path, String token, Object body, Class<T> resultClas
return exec(request, resultClass);
}

protected <T> T post(String path, String token, Object body, int expectedCode) {
Request request = builder(path, token).post(requestBody(body)).build();
return exec(request, expectedCode);
}

protected <T> T put(String path, String token, Object body, Class<T> resultClass) {
Request request = builder(path, token).put(requestBody(body)).build();
return exec(request, resultClass);
Expand All @@ -198,6 +262,10 @@ private <T> T exec(Request request, Class<T> resultClass) {
return exec(request, resultClass, 200);
}

private <T> T exec(Request request, int expectedCode) {
return exec(request, null, expectedCode);
}

private <T> T exec(Request request, Class<T> resultClass, int expectedCode) {
try (Response response = client.newCall(request).execute()) {
if (response.code() != expectedCode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
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.totp.VaultTOTPCreateKeyBody;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPCreateKeyResult;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPGenerateCodeResult;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPListKeysResult;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPReadKeyResult;
import io.quarkus.vault.runtime.client.dto.totp.VaultTOTPValidateCodeResult;
import io.quarkus.vault.runtime.client.dto.transit.VaultTransitDecrypt;
import io.quarkus.vault.runtime.client.dto.transit.VaultTransitDecryptBody;
import io.quarkus.vault.runtime.client.dto.transit.VaultTransitEncrypt;
Expand Down Expand Up @@ -68,4 +74,15 @@ VaultTransitVerify verify(String token, String keyName, String hashAlgorithm,

VaultTransitEncrypt rewrap(String token, String keyName, VaultTransitRewrapBody body);

VaultTOTPCreateKeyResult createTOTPKey(String token, String keyName, VaultTOTPCreateKeyBody vaultTOTPCreateKeyBody);

VaultTOTPReadKeyResult readTOTPKey(String token, String keyName);

VaultTOTPListKeysResult listTOTPKeys(String token);

void deleteTOTPKey(String token, String keyName);

VaultTOTPGenerateCodeResult generateTOTPCode(String token, String keyName);

VaultTOTPValidateCodeResult validateTOTPCode(String token, String keyName, String code);
}
Loading

0 comments on commit 49b873e

Please sign in to comment.