Skip to content

Commit

Permalink
Load config with secrets from vault (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
haroon-sheikh committed Nov 16, 2023
1 parent ce9d3f8 commit a95e72d
Show file tree
Hide file tree
Showing 14 changed files with 341 additions and 26 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## 1.5.1
## 1.6.0

### Added

- Adds reproducible builds configuration based on https://maven.apache.org/guides/mini/guide-reproducible-builds.html
- Adds support for reading secrets from Vault

## 1.5.0

Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ compile 'com.github.sitture:env-config:${version}'
| `env.config.keepass.enabled` | `ENV_CONFIG_KEEPASS_ENABLED` | Whether to load properties from a keepass file. **default:** `false` |
| `env.config.keepass.filename` | `ENV_CONFIG_KEEPASS_FILENAME` | The keepass filename to load from the resources folder (src/main/resources). **default:** the root project directory name. i.e. `project.build.directory` |
| `env.config.keepass.masterkey` | `ENV_CONFIG_KEEPASS_MASTERKEY` | The password to open the keepass file. This is required if `env.config.keepass.enabled=true`. |
| `env.config.vault.enabled` | `ENV_CONFIG_VAULT_ENABLED` | Whether to load properties from a vault secret. **default:** `false` |
| `env.config.vault.address` | `ENV_CONFIG_VAULT_ADDRESS` | The host address of the vault instance. This is required if `env.config.vault.enabled=true`. |
| `env.config.vault.namespace` | `ENV_CONFIG_VAULT_NAMESPACE` | The vault namespace to look for secrets. This is required if `env.config.vault.enabled=true`. |
| `env.config.vault.secret.path` | `ENV_CONFIG_VAULT_SECRET_PATH` | The base secret path for the project. This is required if `env.config.vault.enabled=true`. |
| `env.config.vault.token` | `ENV_CONFIG_VAULT_TOKEN` | The vault token used for authentication. This is required if `env.config.vault.enabled=true`. |

## Configuration precedence

Expand Down
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
<maven-pmd-plugin.version>3.21.2</maven-pmd-plugin.version>
<system-rules.version>1.19.0</system-rules.version>
<openkeepass.version>0.8.2</openkeepass.version>
<vault-java-driver.version>6.2.0</vault-java-driver.version>
<wiremock-standalone.version>3.3.1</wiremock-standalone.version>
<nexus-staging-maven-plugin.version>1.6.13</nexus-staging-maven-plugin.version>
<maven-source-plugin.version>3.3.0</maven-source-plugin.version>
<maven-javadoc-plugin.version>3.6.2</maven-javadoc-plugin.version>
Expand Down Expand Up @@ -162,6 +164,11 @@
<artifactId>openkeepass</artifactId>
<version>${openkeepass.version}</version>
</dependency>
<dependency>
<groupId>io.github.jopenlibs</groupId>
<artifactId>vault-java-driver</artifactId>
<version>${vault-java-driver.version}</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
Expand All @@ -177,6 +184,12 @@
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>${wiremock-standalone.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.github.sitture.envconfig;

class EnvConfigKeepassProperties {
private final String filename;
private final String masterKey;

EnvConfigKeepassProperties(final String filename, final String masterKey) {
this.filename = filename;
this.masterKey = masterKey;
}

String getFilename() {
return filename;
}

String getMasterKey() {
return masterKey;
}
}
22 changes: 17 additions & 5 deletions src/main/java/com/github/sitture/envconfig/EnvConfigLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class EnvConfigLoader {
final String configProfile = this.configProperties.getConfigProfile();
final Map<String, Configuration> envConfiguration = getEnvironmentConfiguration(environments);
loadEnvConfigurations(envConfiguration);
loadVaultConfigurations(environments);
loadKeepassConfigurations(environments);
if (!configProfile.isEmpty()) {
final Map<String, Configuration> profileConfiguration = getEnvironmentProfileConfiguration(environments, configProfile);
Expand All @@ -35,13 +36,24 @@ class EnvConfigLoader {
environments.forEach(env -> this.configuration.addConfiguration(envConfiguration.get(env)));
}

private void loadVaultConfigurations(final List<String> environments) {
if (this.configProperties.isConfigVaultEnabled()) {
final EnvConfigVaultProperties vaultProperties = this.configProperties.getVaultProperties();
final String address = vaultProperties.getAddress();
final String namespace = vaultProperties.getNamespace();
LOG.debug("Loading config from vault {} namespace {}", address, namespace);
final VaultConfiguration entries = new VaultConfiguration(vaultProperties);
environments.forEach(env -> this.configuration.addConfiguration(entries.getConfiguration(env)));
}
}

private void loadKeepassConfigurations(final List<String> environments) {
if (this.configProperties.isConfigKeePassEnabled()) {
final String groupName = this.configProperties.getConfigKeePassFilename();
final String masterKey = this.configProperties.getConfigKeePassMasterKey();
if (this.configProperties.isConfigKeepassEnabled()) {
final EnvConfigKeepassProperties keepassProperties = this.configProperties.getKeepassProperties();
final String groupName = keepassProperties.getFilename();
LOG.debug("Loading config from keepass {}", groupName);
final KeePassEntries keepassEntries = new KeePassEntries(masterKey, groupName);
environments.forEach(env -> this.configuration.addConfiguration(keepassEntries.getEntriesConfiguration(env)));
final KeepassConfiguration entries = new KeepassConfiguration(keepassProperties);
environments.forEach(env -> this.configuration.addConfiguration(entries.getConfiguration(env)));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,28 @@ private String getEnvByPropertyKey(final String key) {
.orElse(System.getenv(key));
}

String getConfigKeePassFilename() {
return new File(getConfigurationProperty(EnvConfigUtils.CONFIG_KEEPASS_FILENAME_KEY, getBuildDir())).getName();
private String getRequiredConfigKey(final String key) {
return Optional.ofNullable(getConfigurationProperty(key, null))
.orElseThrow(() -> new EnvConfigException(String.format("Missing required variable '%s'", key)));
}

boolean isConfigKeePassEnabled() {
boolean isConfigKeepassEnabled() {
return Boolean.parseBoolean(getConfigurationProperty(EnvConfigUtils.CONFIG_KEEPASS_ENABLED_KEY, "false"));
}

String getConfigKeePassMasterKey() {
final String key = EnvConfigUtils.CONFIG_KEEPASS_MASTERKEY_KEY;
return Optional.ofNullable(getConfigurationProperty(key, null))
.orElseThrow(() -> new EnvConfigException(String.format("Missing required variable '%s'", key)));
EnvConfigKeepassProperties getKeepassProperties() {
return new EnvConfigKeepassProperties(new File(getConfigurationProperty(EnvConfigUtils.CONFIG_KEEPASS_FILENAME_KEY, getBuildDir())).getName(),
getRequiredConfigKey(EnvConfigUtils.CONFIG_KEEPASS_MASTERKEY_KEY));
}

boolean isConfigVaultEnabled() {
return Boolean.parseBoolean(getConfigurationProperty(EnvConfigUtils.CONFIG_VAULT_ENABLED_KEY, "false"));
}

EnvConfigVaultProperties getVaultProperties() {
return new EnvConfigVaultProperties(getRequiredConfigKey(EnvConfigUtils.CONFIG_VAULT_ADDRESS_KEY),
getRequiredConfigKey(EnvConfigUtils.CONFIG_VAULT_NAMESPACE_KEY),
getRequiredConfigKey(EnvConfigUtils.CONFIG_VAULT_TOKEN_KEY),
getRequiredConfigKey(EnvConfigUtils.CONFIG_VAULT_SECRET_PATH_KEY));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ final class EnvConfigUtils {
public static final String CONFIG_KEEPASS_ENABLED_KEY = CONFIG_PREFIX + "keepass.enabled";
public static final String CONFIG_KEEPASS_FILENAME_KEY = CONFIG_PREFIX + "keepass.filename";
public static final String CONFIG_KEEPASS_MASTERKEY_KEY = CONFIG_PREFIX + "keepass.masterkey";
public static final String CONFIG_VAULT_ENABLED_KEY = CONFIG_PREFIX + "vault.enabled";
public static final String CONFIG_VAULT_ADDRESS_KEY = CONFIG_PREFIX + "vault.address";
public static final String CONFIG_VAULT_NAMESPACE_KEY = CONFIG_PREFIX + "vault.namespace";
public static final String CONFIG_VAULT_SECRET_PATH_KEY = CONFIG_PREFIX + "vault.secret.path";
public static final String CONFIG_VAULT_TOKEN_KEY = CONFIG_PREFIX + "vault.token";

private EnvConfigUtils() {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.github.sitture.envconfig;

class EnvConfigVaultProperties {

private final String address;
private final String namespace;
private final String token;
private final String secretPath;

EnvConfigVaultProperties(final String address, final String namespace, final String token, final String secretPath) {
this.address = address;
this.namespace = namespace;
this.token = token;
this.secretPath = secretPath;
}

String getAddress() {
return address;
}

String getNamespace() {
return namespace;
}

String getToken() {
return token;
}

String getSecretPath() {
return secretPath;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,25 @@
import java.util.Map;
import java.util.Optional;

class KeePassEntries {
class KeepassConfiguration {

private static final String KEEPASS_DB_FILE_EXTENSION = ".kdbx";
private final KeePassFile keePassFile;

KeePassEntries(final String masterKey, final String groupName) {
KeepassConfiguration(final EnvConfigKeepassProperties keepassProperties) {
final String groupName = keepassProperties.getFilename();
final String keePassGroupName = null != groupName && groupName.endsWith(KEEPASS_DB_FILE_EXTENSION)
? groupName.split(KEEPASS_DB_FILE_EXTENSION)[0]
: groupName;
keePassFile = KeePassDatabase.getInstance(getKeepassDatabaseFile(keePassGroupName.concat(KEEPASS_DB_FILE_EXTENSION)))
.openDatabase(masterKey);
.openDatabase(keepassProperties.getMasterKey());
}

private static String getProcessedPropertyKey(final String envVar) {
return envVar.replaceAll("_", ".").toLowerCase();
}

public Configuration getEntriesConfiguration(final String env) {
public Configuration getConfiguration(final String env) {
final String keePassGroupName = !keePassFile.getTopGroups().isEmpty()
? keePassFile.getTopGroups().get(0).getName()
: "Root";
Expand Down
45 changes: 45 additions & 0 deletions src/main/java/com/github/sitture/envconfig/VaultConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.github.sitture.envconfig;

import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.response.LogicalResponse;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.MapConfiguration;
import org.apache.commons.lang3.StringUtils;

class VaultConfiguration {

private final Vault vault;
private final EnvConfigVaultProperties vaultProperties;

VaultConfiguration(final EnvConfigVaultProperties vaultProperties) {
this.vaultProperties = vaultProperties;
try {
final VaultConfig config = new VaultConfig()
.address(vaultProperties.getAddress())
.nameSpace(vaultProperties.getNamespace())
.token(vaultProperties.getToken())
.build();
this.vault = Vault.create(config);
// attempt to lookupSelf to validate token
this.vault.auth().lookupSelf();
} catch (VaultException e) {
throw new EnvConfigException("Could not connect to vault", e);
}
}

public Configuration getConfiguration(final String env) {
try {
final String secret = String.format("%s/%s", StringUtils.removeEnd(this.vaultProperties.getSecretPath(), "/"), env);
final LogicalResponse response = this.vault.logical().read(secret);
if (response.getRestResponse().getStatus() != 200 && EnvConfigUtils.CONFIG_ENV_DEFAULT.equals(env)) {
throw new EnvConfigException(String.format("Could not find the vault secret: %s", secret));
}
return new MapConfiguration(response.getData());
} catch (VaultException e) {
throw new EnvConfigException("Could not read data from vault.", e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
@ExtendWith(SystemStubsExtension.class)
class EnvConfigPrecedenceTest {

public static final String PROPERTY_KEEPASS = "property.keepass";
public static final String SYS_ENV_VALUE = "sys.env.value";
public static final String SYS_PROPERTY_VALUE = "sys.property.value";
private static final String PROPERTY_KEEPASS = "property.keepass";
private static final String SYS_ENV_VALUE = "sys.env.value";
private static final String SYS_PROPERTY_VALUE = "sys.property.value";
private static final String CONFIG_KEEPASS_MASTERKEY = "envconfig";

@SystemStub
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ void tearDown() {
System.clearProperty(EnvConfigUtils.CONFIG_KEEPASS_FILENAME_KEY);
System.clearProperty(EnvConfigUtils.CONFIG_PROFILE_KEY);
System.clearProperty(EnvConfigUtils.CONFIG_PROFILES_PATH_KEY);
System.clearProperty(EnvConfigUtils.CONFIG_KEEPASS_MASTERKEY_KEY);
}

@Test
Expand Down Expand Up @@ -130,39 +131,42 @@ void testCanGetConfigPathWhenAbsolute() throws IOException {
@Test
void testCanGetConfigKeepassEnabled() {
configProperties = new EnvConfigProperties();
Assertions.assertFalse(configProperties.isConfigKeePassEnabled(), "Incorrect keepass.enabled");
Assertions.assertFalse(configProperties.isConfigKeepassEnabled(), "Incorrect keepass.enabled");
System.setProperty(EnvConfigUtils.CONFIG_KEEPASS_ENABLED_KEY, "true");
Assertions.assertTrue(configProperties.isConfigKeePassEnabled(), "Incorrect keepass.enabled");
Assertions.assertTrue(configProperties.isConfigKeepassEnabled(), "Incorrect keepass.enabled");
}

@Test
void testCanGetConfigKeepassFileName() {
configProperties = new EnvConfigProperties();
System.setProperty(EnvConfigUtils.CONFIG_KEEPASS_MASTERKEY_KEY, "foo");
Assertions.assertEquals(new File(configProperties.getBuildDir()).getName(),
configProperties.getConfigKeePassFilename(), "Incorrect keepass.filename path");
configProperties.getKeepassProperties().getFilename(), "Incorrect keepass.filename path");
}

@Test
void testCanGetConfigKeepassFileNameWhenRelative() {
configProperties = new EnvConfigProperties();
System.setProperty(EnvConfigUtils.CONFIG_KEEPASS_FILENAME_KEY, "foobar.kdbx");
System.setProperty(EnvConfigUtils.CONFIG_KEEPASS_MASTERKEY_KEY, "foo");
Assertions.assertEquals("foobar.kdbx",
configProperties.getConfigKeePassFilename(), "Incorrect keepass.filename path");
configProperties.getKeepassProperties().getFilename(), "Incorrect keepass.filename path");
}

@Test
void testCanGetConfigKeepassFileNameWhenAbsolute() {
configProperties = new EnvConfigProperties();
System.setProperty(EnvConfigUtils.CONFIG_KEEPASS_FILENAME_KEY, "/dir/foobar.kdbx");
System.setProperty(EnvConfigUtils.CONFIG_KEEPASS_MASTERKEY_KEY, "foo");
Assertions.assertEquals("foobar.kdbx",
configProperties.getConfigKeePassFilename(), "Incorrect keepass.filename path");
configProperties.getKeepassProperties().getFilename(), "Incorrect keepass.filename path");
}

@Test
void testThrowsExceptionWhenKeepassMasterKeyNotPresent() {
configProperties = new EnvConfigProperties();
final EnvConfigException exception = Assertions.assertThrows(EnvConfigException.class,
configProperties::getConfigKeePassMasterKey);
configProperties::getKeepassProperties);
Assertions.assertEquals(String.format("Missing required variable '%s'", EnvConfigUtils.CONFIG_KEEPASS_MASTERKEY_KEY),
exception.getMessage());
}
Expand Down
Loading

0 comments on commit a95e72d

Please sign in to comment.