Skip to content

Commit

Permalink
Separate config and data import volumes (#675)
Browse files Browse the repository at this point in the history
- Fixes an issue causing a Gradle deprecation warning
- Adds user and group to the Docker image
- Adds new folder for external configuration
- Defines ownership and access control for the folders where import and config can be attached
- Adds new method to the Testcontainers module to allow RW attach of import files
- Adds new method to the Testcontainers module to use external configuration properties
- Adds new tests
- Updates documentation

Resolves #667
{minor}

Signed-off-by: Esta Nagy <[email protected]>
  • Loading branch information
nagyesta authored Aug 16, 2023
1 parent 53269bf commit 016cc2d
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 5 deletions.
6 changes: 6 additions & 0 deletions lowkey-vault-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,9 @@ Example:
```shell
java -jar lowkey-vault-app-<version>.jar --LOWKEY_IMPORT_LOCATION=export.json --LOWKEY_IMPORT_TEMPLATE_HOST=127.0.0.1 --LOWKEY_IMPORT_TEMPLATE_PORT=443
```

### External configuration

Since Lowkey Vault is a Spring Boot application, the default mechanism for Spring Boot external configuration can work as well. For example,
if there is a ./config/application.properties file relative to the folder where you are running Lowkey Vault, the contents will be picked up
automatically. For more information please see [the Spring Boot documentation](https://docs.spring.io/spring-boot/docs/2.1.9.RELEASE/reference/html/boot-features-external-config.html#boot-features-external-config-application-property-files).
8 changes: 8 additions & 0 deletions lowkey-vault-docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ export LOWKEY_ARGS="--server.port=8444"
docker run --rm --name lowkey -e LOWKEY_ARGS -d -p 8444:8444 nagyesta/lowkey-vault:<version>
```

### External configuration

Since Lowkey Vault is a Spring Boot application, the default mechanism for Spring Boot external
configuration can work as well. For example, if there is a ./config/application.properties file relative
to the folder where the Jar is running, the contents will be picked up automatically. To utilize this, the
recommended option is to attach a *.properties file to /config/application.properties (path inside the
container) using a volume.

## ARM builds

Lowkey Vault offers a multi-arch variant using Buildx. You can find the relevant project [here](https://github.com/nagyesta/lowkey-vault-docker-buildx).
10 changes: 10 additions & 0 deletions lowkey-vault-docker/src/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ FROM eclipse-temurin:11.0.20_8-jre-alpine@sha256:6a7bdc6f82c73a2fa1c95c5fe7465f8
LABEL maintainer="[email protected]"
EXPOSE 8443:8443
ADD lowkey-vault.jar /lowkey-vault.jar
RUN addgroup -S lowkey && adduser -S lowkey -G lowkey
RUN chown -R lowkey:lowkey "/lowkey-vault.jar"
RUN chmod 555 "/lowkey-vault.jar"
RUN mkdir "/import"
RUN chown -R lowkey:lowkey "/import"
RUN chmod 755 "/import"
RUN mkdir "/config"
RUN chown -R lowkey:lowkey "/config"
RUN chmod 555 "/config"
USER lowkey
WORKDIR /
CMD [ "sh", "-c", "ls /" ]
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /lowkey-vault.jar ${LOWKEY_ARGS}"]
2 changes: 1 addition & 1 deletion lowkey-vault-testcontainers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class Test {
final DockerImageName imageName = DockerImageName.parse("nagyesta/lowkey-vault:1.13.0");
final LowkeyVaultContainer lowkeyVaultContainer = lowkeyVault(imageName)
.noAutoRegistration()
.importFile(importFile)
.importFile(importFile, BindMode.READ_ONLY)
.logicalPort(8443)
.logicalHost("127.0.0.1")
.hostPort(8443)
Expand Down
1 change: 1 addition & 0 deletions lowkey-vault-testcontainers/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
}
testImplementation libs.mockito.core
testImplementation libs.jupiter
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation libs.logback.classic
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public LowkeyVaultArgLineBuilder importFile(final File file) {

public LowkeyVaultArgLineBuilder customSSLCertificate(final File file, final String password, final StoreType type) {
if (file != null) {
args.add("--server.ssl.key-store=/import/cert.store");
args.add("--server.ssl.key-store=/config/cert.store");
args.add("--server.ssl.key-store-type=" + Optional.ofNullable(type).orElse(StoreType.PKCS12).name());
args.add("--server.ssl.key-store-password=" + Optional.ofNullable(password).orElse(""));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,19 @@ public LowkeyVaultContainer(final DockerImageName dockerImageName, final Set<Str
if (containerBuilder.getImportFile() != null) {
final String absolutePath = containerBuilder.getImportFile().getAbsolutePath();
logger().info("Using path for import file: '{}'", absolutePath);
withFileSystemBind(absolutePath, "/import/vaults.json", BindMode.READ_ONLY);
withFileSystemBind(absolutePath, "/import/vaults.json", containerBuilder.getImportFileBindMode());
}

if (containerBuilder.getCustomSslCertStore() != null) {
final String absolutePath = containerBuilder.getCustomSslCertStore().getAbsolutePath();
logger().info("Using path for custom certificate: '{}'", absolutePath);
withFileSystemBind(absolutePath, "/import/cert.store", BindMode.READ_ONLY);
withFileSystemBind(absolutePath, "/config/cert.store", BindMode.READ_ONLY);
}

if (containerBuilder.getExternalConfigFile() != null) {
final String absolutePath = containerBuilder.getExternalConfigFile().getAbsolutePath();
logger().info("Using path for external configuration: '{}'", absolutePath);
withFileSystemBind(absolutePath, "/config/application.properties", BindMode.READ_ONLY);
}

final List<String> args = new LowkeyVaultArgLineBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.nagyesta.lowkeyvault.testcontainers;

import org.testcontainers.containers.BindMode;
import org.testcontainers.utility.DockerImageName;

import java.io.File;
Expand All @@ -11,6 +12,7 @@ public final class LowkeyVaultContainerBuilder {
private Set<String> vaultNames = Set.of();
private Map<String, Set<String>> aliasMap = Map.of();
private File importFile;
private BindMode importFileBindMode;
private File customSslCertStore;
private String customSslCertPassword;
private StoreType customSslCertType;
Expand All @@ -19,6 +21,7 @@ public final class LowkeyVaultContainerBuilder {
private String logicalHost;
private List<String> additionalArgs = List.of();
private boolean debug;
private File externalConfigFile;

public static LowkeyVaultContainerBuilder lowkeyVault(final DockerImageName dockerImageName) {
return new LowkeyVaultContainerBuilder(dockerImageName);
Expand Down Expand Up @@ -65,10 +68,26 @@ public LowkeyVaultContainerBuilder vaultAliases(final Map<String, Set<String>> a
}

public LowkeyVaultContainerBuilder importFile(final File importFile) {
return importFile(importFile, BindMode.READ_ONLY);
}

public LowkeyVaultContainerBuilder importFile(final File importFile, final BindMode bindMode) {
if (importFile == null) {
throw new IllegalArgumentException("Import file cannot be null.");
}
this.importFile = importFile;
this.importFileBindMode = bindMode;
return this;
}

public LowkeyVaultContainerBuilder externalConfigFile(final File externalConfigFile) {
if (externalConfigFile == null) {
throw new IllegalArgumentException("External configuration file cannot be null.");
}
if (!externalConfigFile.getName().endsWith(".properties")) {
throw new IllegalArgumentException("External configuration file must be a *.properties file.");
}
this.externalConfigFile = externalConfigFile;
return this;
}

Expand Down Expand Up @@ -139,10 +158,18 @@ public File getImportFile() {
return importFile;
}

public BindMode getImportFileBindMode() {
return importFileBindMode;
}

public File getCustomSslCertStore() {
return customSslCertStore;
}

public File getExternalConfigFile() {
return externalConfigFile;
}

public String getCustomSslCertPassword() {
return customSslCertPassword;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ void testImportFileShouldNotSetArgumentWhenCalledWithNull() {
void testCustomSslCertificateShouldSetArgumentWhenCalledWithValidFile() {
//given
final LowkeyVaultArgLineBuilder underTest = new LowkeyVaultArgLineBuilder();
final List<String> expected = List.of("--server.ssl.key-store=/import/cert.store",
final List<String> expected = List.of("--server.ssl.key-store=/config/cert.store",
"--server.ssl.key-store-type=JKS",
"--server.ssl.key-store-password=pass");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,60 @@ void testBuilderShouldThrowExceptionWhenCalledWithInvalidImageName() {
//then + exceptions
}

@Test
void testBuilderShouldThrowExceptionWhenCalledWithNullExternalConfigurationFile() {
//given
final DockerImageName imageName = DockerImageName
.parse(getCurrentLowkeyVaultImageName())
.asCompatibleSubstituteFor(LowkeyVaultContainer.DEFAULT_IMAGE_NAME);

//when
Assertions.assertThrows(IllegalArgumentException.class, () -> lowkeyVault(imageName).externalConfigFile(null));

//then + exceptions
}

@Test
void testBuilderShouldThrowExceptionWhenCalledWithAnExternalConfigurationFileThatIsNotAPropertiesFile() {
//given
final DockerImageName imageName = DockerImageName
.parse(getCurrentLowkeyVaultImageName())
.asCompatibleSubstituteFor(LowkeyVaultContainer.DEFAULT_IMAGE_NAME);
final File certFile = new File(Objects.requireNonNull(getClass().getResource("/cert.jks")).getFile());

//when
Assertions.assertThrows(IllegalArgumentException.class, () -> lowkeyVault(imageName).externalConfigFile(certFile));

//then + exceptions
}

@Test
void testContainerShouldStartUpWhenCalledWithExternalConfiguration() {
//given
final DockerImageName imageName = DockerImageName
.parse(getCurrentLowkeyVaultImageName())
.asCompatibleSubstituteFor(LowkeyVaultContainer.DEFAULT_IMAGE_NAME);
//noinspection ConstantConditions
final File configFile = new File(getClass().getResource("/config.properties").getFile());
final LowkeyVaultContainer underTest = lowkeyVault(imageName)
.vaultAliases(Map.of(LOCALHOST, Set.of(EXAMPLE_COM)))
.externalConfigFile(configFile)
.build()
.withImagePullPolicy(PullPolicy.defaultPolicy());

//when
underTest.start();

//then
final String authority = EXAMPLE_COM;
final String endpoint = "https://" + authority;
final AuthorityOverrideFunction authorityOverrideFunction = new AuthorityOverrideFunction(
authority,
underTest.getEndpointAuthority().replace(LOCALHOST, "127.0.0.1"));
final TokenCredential credentials = new BasicAuthenticationCredential(underTest.getUsername(), underTest.getPassword());
final ApacheHttpClient httpClient = new ApacheHttpClient(authorityOverrideFunction,
new TrustSelfSignedStrategy(), new DefaultHostnameVerifier());
verifyConnectionIsWorking(endpoint, httpClient, credentials);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
logging.level.root=INFO

0 comments on commit 016cc2d

Please sign in to comment.