Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Vault TOTP secrets #7873

Merged
merged 1 commit into from
Mar 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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