Skip to content

Commit

Permalink
Lowkey Vault Docker does not support dynamic host ports
Browse files Browse the repository at this point in the history
- Adds new configuration option to allow relaxed matching of vault URI ports
- Updates tests
- Deprecates Testcontainers method setting a fixed host port
- Enhances Testcontainers module to always use the relaxed matching feature
- Adds more verification steps to Testcontainers tests
- Updates documentation

Updates #1319
{minor}

Signed-off-by: Esta Nagy <[email protected]>
  • Loading branch information
nagyesta committed Jan 18, 2025
1 parent 3155588 commit e45fab1
Show file tree
Hide file tree
Showing 29 changed files with 287 additions and 88 deletions.
12 changes: 12 additions & 0 deletions lowkey-vault-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ Set `--server.port=<port>` as an argument as usual with Spring Boot apps:
java -jar lowkey-vault-app-<version>.jar --server.port=8443
```

### Relaxed port matching

If you want to use Lowkey Vault in a scenario where you are accessing the vault through a dynamically mapped port,
for example using a random host port when exposing your container port with Testcontainers, you can tell Lowkey Vault
to ignore the port number when searching for a vault based on the request authority (essentially only matching based
on the request's hostname). To activate this feature, you need to use `v2.7.0` or higher, and provide the
`--LOWKEY_VAULT_RELAXED_PORTS=true` argument during startup:

```shell
java -jar lowkey-vault-app-<version>.jar --LOWKEY_VAULT_RELAXED_PORTS=true
```

### Overriding the challenge resource URI

The official Azure Key Vault clients verify the challenge resource URL returned by the server (see
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.github.nagyesta.lowkeyvault.service.vault.impl.VaultServiceImpl;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
Expand All @@ -17,6 +18,9 @@
import java.net.URI;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;

import static com.github.nagyesta.lowkeyvault.context.util.VaultUriUtil.replacePortWith;

@Setter
@Configuration
Expand All @@ -31,16 +35,26 @@ public class AppConfiguration {
private int port;
@Value("${LOWKEY_VAULT_ALIASES:}")
private String aliases;
@Value("${LOWKEY_VAULT_RELAXED_PORTS:false}")
private boolean useRelaxedPorts;

@Bean
public VaultService vaultService() throws IOException {
final VaultService service = new VaultServiceImpl();
final VaultService service = new VaultServiceImpl(portMapper());
if (!SKIP_AUTO_REGISTRATION.equals(autoRegisterVaults)) {
autoRegisterVaults(service);
}
return service;
}

@Bean
public Function<URI, URI> portMapper() {
return Optional.of(useRelaxedPorts)
.filter(BooleanUtils::isTrue)
.map(use -> (Function<URI, URI>) uri -> replacePortWith(uri, port))
.orElse(Function.identity());
}

private void autoRegisterVaults(final VaultService service) {
log.info("Starting up vault with port: {} , auto-registering vaults: '{}'", port, autoRegisterVaults);
doAutoRegisterVaults(service);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ public static URI aliasUri(@NonNull final String vaultAuthority, final int serve
final int port = Integer.parseInt(StringUtils.substringAfter(authority, COLON));
return VaultUriUtil.vaultUri(hostname, port);
}

public static URI replacePortWith(final URI uri, final int port) {
return VaultUriUtil.vaultUri(uri.getHost(), port);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
import java.net.URI;
import java.time.OffsetDateTime;
import java.util.Set;
import java.util.function.Function;

public interface VaultFake {

boolean matches(URI vaultUri);
boolean matches(URI vaultUri, Function<URI, URI> uriMapper);

URI baseUri();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

@Slf4j
@EqualsAndHashCode(onlyExplicitlyIncluded = true, doNotUseGetters = true)
Expand Down Expand Up @@ -52,8 +53,11 @@ public VaultFakeImpl(@NonNull final URI vaultUri, @NonNull final RecoveryLevel r
}

@Override
public boolean matches(@NonNull final URI vaultUri) {
return this.vaultUri.equals(vaultUri) || this.aliases.contains(vaultUri);
public boolean matches(@NonNull final URI vaultUri, final Function<URI, URI> uriMapper) {
final URI lookupUri = uriMapper.apply(vaultUri);
return uriMapper.apply(this.vaultUri).equals(lookupUri) || this.aliases.stream()
.map(uriMapper)
.anyMatch(lookupUri::equals);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,27 @@
import com.github.nagyesta.lowkeyvault.service.exception.NotFoundException;
import com.github.nagyesta.lowkeyvault.service.vault.VaultFake;
import com.github.nagyesta.lowkeyvault.service.vault.VaultService;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import java.net.URI;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

@Slf4j
public class VaultServiceImpl implements VaultService {

private final Function<URI, URI> uriMapper;
private final Set<VaultFake> vaultFakes = new CopyOnWriteArraySet<>();

public VaultServiceImpl(@NonNull final Function<URI, URI> uriMapper) {
this.uriMapper = uriMapper;
}

@Override
public VaultFake findByUri(final URI uri) {
return findByUriAndDeleteStatus(uri, VaultFake::isActive)
Expand Down Expand Up @@ -133,7 +140,7 @@ public VaultFake updateAlias(final URI baseUri, final URI add, final URI remove)

private Optional<VaultFake> findByUriAndDeleteStatus(final URI uri, final Predicate<VaultFake> deletedPredicate) {
return vaultFakes.stream()
.filter(v -> v.matches(uri))
.filter(v -> v.matches(uri, uriMapper))
.filter(deletedPredicate)
.findFirst();
}
Expand All @@ -152,7 +159,7 @@ private VaultFake create(final URI uri, final Supplier<VaultFake> vaultFakeSuppl

private boolean exists(final URI uri) {
return vaultFakes.stream()
.anyMatch(v -> v.matches(uri));
.anyMatch(v -> v.matches(uri, uriMapper));
}

private void purgeExpired() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.github.nagyesta.lowkeyvault;

import org.apache.tomcat.util.codec.binary.Base64;
import org.junit.jupiter.api.Assertions;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Optional;

public final class ResourceUtils {
Expand All @@ -28,7 +28,7 @@ public static String loadResourceAsString(final String resource) {
public static String loadResourceAsBase64String(final String resource) {
final byte[] binaryData = loadResourceAsByteArray(resource);
return Optional.ofNullable(binaryData)
.map(binary -> new Base64().encodeAsString(binary))
.map(binary -> Base64.getEncoder().encodeToString(binary))
.orElse(null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ private TestConstants() {
public static final int TOMCAT_SECURE_PORT = 8443;
public static final int HTTP_PORT = 80;
public static final String PORT_80 = ":80";
public static final String PORT_443 = ":443";
public static final String PORT_8443 = ":8443";
//</editor-fold>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ private TestConstantsUri() {
public static final URI HTTPS_LOOP_BACK_IP_8443 = URI.create(HTTPS_LOOP_BACK_IP + PORT_8443);
public static final URI HTTPS_LOOP_BACK_IP_80 = URI.create(HTTPS_LOOP_BACK_IP + PORT_80);
public static final URI HTTPS_LOWKEY_VAULT = URI.create(HTTPS + LOWKEY_VAULT);
public static final URI HTTPS_LOWKEY_VAULT_443 = URI.create(HTTPS + LOWKEY_VAULT + PORT_443);
public static final URI HTTPS_LOWKEY_VAULT_8443 = URI.create(HTTPS_LOWKEY_VAULT + PORT_8443);
public static final URI HTTPS_LOWKEY_VAULT_80 = URI.create(HTTPS_LOWKEY_VAULT + PORT_80);
public static final URI HTTPS_DEFAULT_LOWKEY_VAULT = URI.create(HTTPS + DEFAULT_SUB + LOWKEY_VAULT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import java.util.function.Function;

@Profile("vault")
@Configuration
public class BaseVaultConfiguration {

@Bean
public VaultService vaultService() {
return new VaultServiceImpl();
return new VaultServiceImpl(Function.identity());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.net.URI;
import java.time.OffsetDateTime;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;

import static com.github.nagyesta.lowkeyvault.TestConstants.*;
Expand Down Expand Up @@ -129,7 +130,8 @@ void testConvertShouldConvertAllFieldsWhenTheyAreSet(

//given
when(vault.baseUri()).thenReturn(keyEntityId.vault());
when(vault.matches(eq(keyEntityId.vault()))).thenReturn(true);
final URI vaultUri = eq(keyEntityId.vault());
when(vault.matches(vaultUri, eq(Function.identity()))).thenReturn(true);
final RsaKeyVaultKeyEntity input = new RsaKeyVaultKeyEntity(keyEntityId, vault, keyParam, null, false);
input.setTags(tags);

Expand Down Expand Up @@ -162,7 +164,8 @@ void testConvertDeletedShouldConvertAllFieldsWhenTheyAreSet(

//given
when(vault.baseUri()).thenReturn(keyEntityId.vault());
when(vault.matches(eq(keyEntityId.vault()))).thenReturn(true);
final URI vaultUri = eq(keyEntityId.vault());
when(vault.matches(vaultUri, eq(Function.identity()))).thenReturn(true);
final RsaKeyVaultKeyEntity input = new RsaKeyVaultKeyEntity(keyEntityId, vault, keyParam, null, false);
input.setDeletedDate(deleted);
input.setScheduledPurgeDate(scheduledPurge);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.net.URI;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;

import static com.github.nagyesta.lowkeyvault.TestConstants.*;
Expand Down Expand Up @@ -80,7 +81,8 @@ void testConvertShouldConvertAllFieldsWhenTheyAreSet(

//given
when(vault.baseUri()).thenReturn(keyEntityId.vault());
when(vault.matches(eq(keyEntityId.vault()))).thenReturn(true);
final URI vaultUri = eq(keyEntityId.vault());
when(vault.matches(vaultUri, eq(Function.identity()))).thenReturn(true);
final RsaKeyVaultKeyEntity input = new RsaKeyVaultKeyEntity(keyEntityId, vault, keyParam, null, false);
input.setTags(tags);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.net.URI;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Stream;

import static com.github.nagyesta.lowkeyvault.TestConstants.*;
Expand Down Expand Up @@ -214,7 +215,8 @@ void testConstructorShouldThrowExceptionsWhenCalledWithNull() {

private void prepareVaultMock(final URI baseUri) {
when(vault.baseUri()).thenReturn(baseUri);
when(vault.matches(eq(baseUri))).thenReturn(true);
final URI vaultUri = eq(baseUri);
when(vault.matches(vaultUri, eq(Function.identity()))).thenReturn(true);
}

private void assertCommonFieldsMatch(final Map<String, String> tags,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.net.URI;
import java.time.OffsetDateTime;
import java.util.function.Function;
import java.util.stream.Stream;

import static com.github.nagyesta.lowkeyvault.TestConstants.*;
Expand Down Expand Up @@ -60,7 +62,8 @@ void setUp() {
underTest = new KeyEntityToV72PropertiesModelConverter(registry);
when(vault.keyVaultFake()).thenReturn(keyVault);
when(vault.baseUri()).thenReturn(HTTPS_LOCALHOST);
when(vault.matches(eq(HTTPS_LOCALHOST))).thenReturn(true);
final URI vaultUri = eq(HTTPS_LOCALHOST);
when(vault.matches(vaultUri, eq(Function.identity()))).thenReturn(true);
}

@AfterEach
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.net.URI;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Stream;

import static com.github.nagyesta.lowkeyvault.TestConstants.*;
Expand Down Expand Up @@ -124,7 +125,8 @@ void testConstructorShouldThrowExceptionsWhenCalledWithNull() {

private void prepareVaultMock(final URI baseUri) {
when(vault.baseUri()).thenReturn(baseUri);
when(vault.matches(eq(baseUri))).thenReturn(true);
final URI vaultUri = eq(baseUri);
when(vault.matches(vaultUri, eq(Function.identity()))).thenReturn(true);
}

private void assertFieldsMatch(final Map<String, String> tags,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.net.URI;
import java.time.OffsetDateTime;
import java.util.function.Function;
import java.util.stream.Stream;

import static com.github.nagyesta.lowkeyvault.TestConstants.*;
Expand Down Expand Up @@ -57,7 +59,8 @@ void setUp() {
underTest = new SecretEntityToV72PropertiesModelConverter(registry);
when(vault.secretVaultFake()).thenReturn(secretVault);
when(vault.baseUri()).thenReturn(HTTPS_LOCALHOST);
when(vault.matches(eq(HTTPS_LOCALHOST))).thenReturn(true);
final URI vaultUri = eq(HTTPS_LOCALHOST);
when(vault.matches(vaultUri, eq(Function.identity()))).thenReturn(true);
}

@AfterEach
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.net.URI;
import java.time.OffsetDateTime;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;

import static com.github.nagyesta.lowkeyvault.TestConstants.*;
Expand Down Expand Up @@ -130,7 +131,8 @@ void testConvertShouldConvertAllFieldsWhenTheyAreSet(

//given
when(vault.baseUri()).thenReturn(secretEntityId.vault());
when(vault.matches(eq(secretEntityId.vault()))).thenReturn(true);
final URI vaultUri = eq(secretEntityId.vault());
when(vault.matches(vaultUri, eq(Function.identity()))).thenReturn(true);
final KeyVaultSecretEntity input = new KeyVaultSecretEntity(secretEntityId, vault, value, type);
input.setTags(tags);

Expand Down Expand Up @@ -163,7 +165,8 @@ void testConvertDeletedShouldConvertAllFieldsWhenTheyAreSet(

//given
when(vault.baseUri()).thenReturn(secretEntityId.vault());
when(vault.matches(eq(secretEntityId.vault()))).thenReturn(true);
final URI vaultUri = eq(secretEntityId.vault());
when(vault.matches(vaultUri, eq(Function.identity()))).thenReturn(true);
final KeyVaultSecretEntity input = new KeyVaultSecretEntity(secretEntityId, vault, value, type);
input.setDeletedDate(deleted);
input.setScheduledPurgeDate(scheduledPurge);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.net.URI;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;

import static com.github.nagyesta.lowkeyvault.TestConstants.*;
Expand Down Expand Up @@ -89,7 +90,8 @@ void testConvertShouldConvertAllFieldsWhenTheyAreSet(

//given
when(vault.baseUri()).thenReturn(secretEntityId.vault());
when(vault.matches(eq(secretEntityId.vault()))).thenReturn(true);
final URI vaultUri = eq(secretEntityId.vault());
when(vault.matches(vaultUri, eq(Function.identity()))).thenReturn(true);
final KeyVaultSecretEntity input = new KeyVaultSecretEntity(secretEntityId, vault, value, null);
input.setTags(tags);

Expand Down
Loading

0 comments on commit e45fab1

Please sign in to comment.