Skip to content

Commit

Permalink
Fix secret-config-kv-prefix-path property #13300
Browse files Browse the repository at this point in the history
  • Loading branch information
vsevel committed Nov 25, 2020
1 parent 8b8bba7 commit 30f62a1
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static java.lang.Integer.parseInt;
import static java.util.Collections.emptyMap;
import static java.util.Optional.empty;
import static java.util.regex.Pattern.compile;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

Expand Down Expand Up @@ -52,12 +53,13 @@ public class VaultConfigSource implements ConfigSource {

private static final Logger log = Logger.getLogger(VaultConfigSource.class);

private static final String PROPERTY_PREFIX = "quarkus.vault.";
public static final Pattern CREDENTIALS_PATTERN = Pattern.compile("^quarkus\\.vault\\.credentials-provider\\.([^.]+)\\.");
public static final Pattern TRANSIT_KEY_PATTERN = Pattern.compile("^quarkus\\.vault\\.transit.key\\.([^.]+)\\.");
public static final Pattern SECRET_CONFIG_KV_PATH_PATTERN = Pattern
.compile("^quarkus\\.vault\\.secret-config-kv-path\\.(?:([^.]+)|(?:\"([^\"]+)\"))$");
public static final Pattern EXPANSION_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");
static final String PROPERTY_PREFIX = "quarkus.vault.";
public static final Pattern CREDENTIALS_PATTERN = compile("^quarkus\\.vault\\.credentials-provider\\.([^.]+)\\.");
public static final Pattern TRANSIT_KEY_PATTERN = compile("^quarkus\\.vault\\.transit.key\\.([^.]+)\\.");
public static final String SECRET_CONFIG_KV_PREFIX_PATHS = "secret-config-kv-path";
public static final Pattern SECRET_CONFIG_KV_PREFIX_PATH_PATTERN = compile(
"^quarkus\\.vault\\." + SECRET_CONFIG_KV_PREFIX_PATHS + "\\.(?:([^.]+)|(?:\"([^\"]+)\"))$");
public static final Pattern EXPANSION_PATTERN = compile("\\$\\{([^}]+)\\}");

private AtomicReference<VaultCacheEntry<Map<String, String>>> cache = new AtomicReference<>(null);
private AtomicReference<VaultRuntimeConfig> serverConfig = new AtomicReference<>(null);
Expand Down Expand Up @@ -117,13 +119,10 @@ private Map<String, String> getSecretConfig() {

try {
// default kv paths
if (serverConfig.secretConfigKvPath.isPresent()) {
fetchSecrets(serverConfig.secretConfigKvPath.get(), null, properties);
}
serverConfig.secretConfigKvPath.ifPresent(strings -> fetchSecrets(strings, null, properties));

// prefixed kv paths
serverConfig.secretConfigKvPrefixPath.entrySet()
.forEach(entry -> fetchSecrets(entry.getValue(), entry.getKey(), properties));
serverConfig.secretConfigKvPathPrefix.forEach((key, value) -> fetchSecrets(value.paths, key, properties));

log.debug("loaded " + properties.size() + " properties from vault");
} catch (RuntimeException e) {
Expand Down Expand Up @@ -252,7 +251,7 @@ private VaultRuntimeConfig loadRuntimeConfig() {

serverConfig.credentialsProvider = createCredentialProviderConfigParser().getConfig();
serverConfig.transit.key = createTransitKeyConfigParser().getConfig();
serverConfig.secretConfigKvPrefixPath = getSecretConfigKvPrefixPaths();
serverConfig.secretConfigKvPathPrefix = getSecretConfigKvPrefixPaths();

return serverConfig;
}
Expand Down Expand Up @@ -362,15 +361,16 @@ protected String getBaseProperty(String propertyName, String defaultValue) {
.orElse(defaultValue);
}

private Map<String, List<String>> getSecretConfigKvPrefixPaths() {
private Map<String, VaultRuntimeConfig.KvPathConfig> getSecretConfigKvPrefixPaths() {

return getConfigSourceStream()
.flatMap(configSource -> configSource.getPropertyNames().stream())
.map(this::getSecretConfigKvPrefixPathName)
.filter(Objects::nonNull)
.distinct()
.map(this::createNameSecretConfigKvPrefixPathPair)
.collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue));
.collect(toMap(SimpleEntry::getKey,
kvStore -> new VaultRuntimeConfig.KvPathConfig(kvStore.getValue())));
}

private Stream<ConfigSource> getConfigSourceStream() {
Expand All @@ -394,12 +394,12 @@ private SimpleEntry<String, List<String>> createNameSecretConfigKvPrefixPathPair
}

private String getSecretConfigKvPrefixPathName(String propertyName) {
Matcher matcher = SECRET_CONFIG_KV_PATH_PATTERN.matcher(propertyName);
Matcher matcher = SECRET_CONFIG_KV_PREFIX_PATH_PATTERN.matcher(propertyName);
return matcher.matches() ? (matcher.group(1) != null ? matcher.group(1) : matcher.group(2)) : null;
}

private List<String> getSecretConfigKvPrefixPath(String prefixName) {
return getOptionalListProperty("secret-config-kv-path." + prefixName).get();
return getOptionalListProperty(SECRET_CONFIG_KV_PREFIX_PATHS + "." + prefixName).get();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@

import java.net.URL;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigDocMapKey;
import io.quarkus.runtime.annotations.ConfigDocSection;
import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
Expand All @@ -23,7 +26,6 @@ public class VaultRuntimeConfig {

public static final String DEFAULT_KUBERNETES_JWT_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token";
public static final String DEFAULT_KV_SECRET_ENGINE_MOUNT_PATH = "secret";
public static final String KV_SECRET_ENGINE_VERSION_V1 = "1";
public static final String KV_SECRET_ENGINE_VERSION_V2 = "2";
public static final String DEFAULT_RENEW_GRACE_PERIOD = "1H";
public static final String DEFAULT_SECRET_CONFIG_CACHE_PERIOD = "10M";
Expand All @@ -35,9 +37,9 @@ public class VaultRuntimeConfig {

/**
* Vault server url.
* <p>
*
* Example: https://localhost:8200
* <p>
*
* See also the documentation for the `kv-secret-engine-mount-path` property for some insights on how
* the full Vault url gets built.
*
Expand All @@ -55,7 +57,7 @@ public class VaultRuntimeConfig {

/**
* Renew grace period duration.
* <p>
*
* This value if used to extend a lease before it expires its ttl, or recreate a new lease before the current
* lease reaches its max_ttl.
* By default Vault leaseDuration is equal to 7 days (ie: 168h or 604800s).
Expand All @@ -75,7 +77,7 @@ public class VaultRuntimeConfig {

/**
* Vault config source cache period.
* <p>
*
* Properties fetched from vault as MP config will be kept in a cache, and will not be fetched from vault
* again until the expiration of that period.
* This property is ignored if `secret-config-kv-path` is not set.
Expand All @@ -89,20 +91,20 @@ public class VaultRuntimeConfig {
/**
* List of comma separated vault paths in kv store,
* where all properties will be available as MP config properties **as-is**, with no prefix.
* <p>
*
* For instance, if vault contains property `foo`, it will be made available to the
* quarkus application as `@ConfigProperty(name = "foo") String foo;`
* <p>
*
* If 2 paths contain the same property, the last path will win.
* <p>
*
* For instance if
* <p>
*
* * `secret/base-config` contains `foo=bar` and
* * `secret/myapp/config` contains `foo=myappbar`, then
* <p>
*
* `@ConfigProperty(name = "foo") String foo` will have value `myappbar`
* with application properties `quarkus.vault.secret-config-kv-path=base-config,myapp/config`
* <p>
*
* See also the documentation for the `kv-secret-engine-mount-path` property for some insights on how
* the full Vault url gets built.
*
Expand All @@ -112,32 +114,17 @@ public class VaultRuntimeConfig {
@ConfigItem
public Optional<List<String>> secretConfigKvPath;

// @formatter:off
/**
* List of comma separated vault paths in kv store,
* where all properties will be available as **prefixed** MP config properties.
* <p>
* For instance if the application properties contains
* `quarkus.vault.secret-config-kv-path.myprefix=config`, and
* vault path `secret/config` contains `foo=bar`, then `myprefix.foo`
* will be available in the MP config.
* <p>
* If the same property is available in 2 different paths for the same prefix, the last one
* will win.
* <p>
* See also the documentation for the `kv-secret-engine-mount-path` property for some insights on how
* the full Vault url gets built.
*
* @asciidoclet
* KV store paths configuration.
*/
// @formatter:on
@ConfigItem(name = "secret-config-kv-path.\"prefix\"")
public Map<String, List<String>> secretConfigKvPrefixPath;
@ConfigItem(name = "secret-config-kv-path")
@ConfigDocMapKey("prefix")
public Map<String, KvPathConfig> secretConfigKvPathPrefix;

/**
* Used to hide confidential infos, for logging in particular.
* Possible values are:
* <p>
*
* * low: display all secrets.
* * medium: display only usernames and lease ids (ie: passwords and tokens are masked).
* * high: hide lease ids and dynamic credentials username.
Expand All @@ -149,7 +136,7 @@ public class VaultRuntimeConfig {

/**
* Kv secret engine version.
* <p>
*
* see https://www.vaultproject.io/docs/secrets/kv/index.html
*
* @asciidoclet
Expand All @@ -159,24 +146,24 @@ public class VaultRuntimeConfig {

/**
* KV secret engine path.
* <p>
*
* This value is used when building the url path in the KV secret engine programmatic access
* (i.e. `VaultKVSecretEngine`) and the vault config source (i.e. fetching configuration properties from Vault).
* <p>
*
* For a v2 KV secret engine (default - see `kv-secret-engine-version property`)
* the full url is built from the expression `<url>/v1/</kv-secret-engine-mount-path>/data/...`.
* <p>
*
* With property `quarkus.vault.url=https://localhost:8200`, the following call
* `vaultKVSecretEngine.readSecret("foo/bar")` would lead eventually to a `GET` on Vault with the following
* url: `https://localhost:8200/v1/secret/data/foo/bar`.
* <p>
*
* With a KV secret engine v1, the url changes to: `<url>/v1/</kv-secret-engine-mount-path>/...`.
* <p>
*
* The same logic is applied to the Vault config source. With `quarkus.vault.secret-config-kv-path=config/myapp`
* The secret properties would be fetched from Vault using a `GET` on
* `https://localhost:8200/v1/secret/data/config/myapp` for a KV secret engine v2 (or
* `https://localhost:8200/v1/secret/config/myapp` for a KV secret engine v1).
* <p>
*
* see https://www.vaultproject.io/docs/secrets/kv/index.html
*
* @asciidoclet
Expand Down Expand Up @@ -205,10 +192,10 @@ public class VaultRuntimeConfig {

/**
* List of named credentials providers, such as: `quarkus.vault.credentials-provider.foo.kv-path=mypath`
* <p>
*
* This defines a credentials provider `foo` returning key `password` from vault path `mypath`.
* Once defined, this provider can be used in credentials consumers, such as the Agroal connection pool.
* <p>
*
* Example: `quarkus.datasource.credentials-provider=foo`
*
* @asciidoclet
Expand Down Expand Up @@ -271,4 +258,43 @@ public String toString() {
'}';
}

@ConfigGroup
public static class KvPathConfig {
// @formatter:off
/**
* List of comma separated vault paths in kv store,
* where all properties will be available as **prefixed** MP config properties.
*
* For instance if the application properties contains
* `quarkus.vault.secret-config-kv-path.myprefix=config`, and
* vault path `secret/config` contains `foo=bar`, then `myprefix.foo`
* will be available in the MP config.
*
* If the same property is available in 2 different paths for the same prefix, the last one
* will win.
*
* See also the documentation for the `quarkus.vault.kv-secret-engine-mount-path` property for some insights on how
* the full Vault url gets built.
*
* @asciidoclet
*/
// @formatter:on
@ConfigItem(name = ConfigItem.PARENT)
List<String> paths;

public KvPathConfig(List<String> paths) {
this.paths = paths;
}

public KvPathConfig() {
paths = Collections.emptyList();
}

@Override
public String toString() {
return "SecretConfigKvPathConfig{" +
"paths=" + paths +
'}';
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.quarkus.vault.runtime.config;

import static io.quarkus.vault.runtime.config.VaultConfigSource.SECRET_CONFIG_KV_PATH_PATTERN;
import static io.quarkus.vault.runtime.config.VaultConfigSource.PROPERTY_PREFIX;
import static io.quarkus.vault.runtime.config.VaultConfigSource.SECRET_CONFIG_KV_PREFIX_PATHS;
import static io.quarkus.vault.runtime.config.VaultConfigSource.SECRET_CONFIG_KV_PREFIX_PATH_PATTERN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

Expand All @@ -10,16 +12,18 @@

public class VaultConfigSourceTest {

private static final String PROPERTY = PROPERTY_PREFIX + SECRET_CONFIG_KV_PREFIX_PATHS;

@Test
void secretConfigKvPathPattern() {

Matcher matcher;

matcher = SECRET_CONFIG_KV_PATH_PATTERN.matcher("quarkus.vault.secret-config-kv-path.hello");
matcher = SECRET_CONFIG_KV_PREFIX_PATH_PATTERN.matcher(PROPERTY + ".hello");
assertTrue(matcher.matches());
assertEquals("hello", matcher.group(1));

matcher = SECRET_CONFIG_KV_PATH_PATTERN.matcher("quarkus.vault.secret-config-kv-path.\"mp.jwt.verify\"");
matcher = SECRET_CONFIG_KV_PREFIX_PATH_PATTERN.matcher(PROPERTY + ".\"mp.jwt.verify\"");
assertTrue(matcher.matches());
assertEquals("mp.jwt.verify", matcher.group(2));
}
Expand Down

0 comments on commit 30f62a1

Please sign in to comment.