diff --git a/README.md b/README.md index 699cfb7..27d1f42 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ dependencies { Define your configuration: ```java -// Simplified Config +// Example Config VaultSecretManagerConfigurator configurator = VaultSecretManagerConfigurator.builder() .withProperties(VaultSecretsManagerProperties.builder() .host("localhost") @@ -163,9 +163,16 @@ You can pass the following variables to VaultSecretsManagerProperties: - **port**: port number of the vault server. The default value is 8200. The next requests to the same secret will be resolved from the cache. The default value is 0 (no cache). - **ssl**: Defines if the connection to the vault server is secure or not. The default value is false. -- **roleId** and **secretId**: credentials for authenticate with vault and obtain a Token. -- **token**: If you already have a token, you can pass it here. If you pass a token, the roleId and secretId - will be ignored. +- **token**: If you already have a token to interact with Vault API, you can pass it to the configurator. + No auth is performed. + +Authentication with vault can be done in two ways with this library: + +- **roleId** and **secretId**: If AppRole auth is enabled, you can pass the roleId and secretId to the configurator. The library will authenticate + with vault and obtain a token. +- **vaultRoleForK8sAuth**: If Kubernetes auth is enabled, you can pass here the vault role for which you would like to + receive a token in the namespace for your app. For more information please refer to + [Kubernetes Auth Method](https://developer.hashicorp.com/vault/docs/auth/kubernetes) documentation. For other configurations, you can use the `VaultSecretsManagerProperties` class. diff --git a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/config/VaultSecretsManagerProperties.java b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/config/VaultSecretsManagerProperties.java index 077f931..594a67a 100644 --- a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/config/VaultSecretsManagerProperties.java +++ b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/config/VaultSecretsManagerProperties.java @@ -33,6 +33,8 @@ public class VaultSecretsManagerProperties { private String secretId; + private String vaultRoleForK8sAuth; + @Builder.Default private int engineVersion = 2; @@ -49,11 +51,25 @@ public class VaultSecretsManagerProperties { @Builder.Default private CacheProperties secretsCacheProperties= CacheProperties.builder().expireAfter(600).maxSize(100).build(); + @Builder.Default + private String appRoleAuthPath = "/auth/approle/login"; + + @Builder.Default + private String k8sAuthPath = "/auth/kubernetes/login"; + public String buildUrl() { return String.format("%s://%s:%d%s", ssl ? "https" : "http", host, port, baseApi); } - public boolean roleCredentialsProvided() { + public boolean isRoleCredentialsProvided() { return roleId != null && !roleId.isEmpty() && secretId != null && !secretId.isEmpty(); } + + public boolean isRoleNameForK8sProvided() { + return vaultRoleForK8sAuth != null && !vaultRoleForK8sAuth.isEmpty(); + } + + public boolean isTokenProvided() { + return token != null && !token.isEmpty(); + } } diff --git a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultAuthenticator.java b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultAuthenticator.java index 2bf2031..7c94f53 100644 --- a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultAuthenticator.java +++ b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultAuthenticator.java @@ -4,73 +4,155 @@ import co.com.bancolombia.secretsmanager.commons.utils.GsonUtils; import co.com.bancolombia.secretsmanager.config.VaultSecretsManagerProperties; import co.com.bancolombia.secretsmanager.connector.auth.AuthResponse; +import co.com.bancolombia.secretsmanager.connector.auth.K8sAuth; +import co.com.bancolombia.secretsmanager.connector.auth.K8sTokenReader; +import co.com.bancolombia.secretsmanager.connector.auth.RoleAuth; import com.github.benmanes.caffeine.cache.AsyncCache; import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.gson.Gson; import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; import reactor.core.publisher.Mono; +import java.lang.reflect.Type; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +/** + * Class in charge of authenticating with vault + */ public class VaultAuthenticator { - private final Logger logger = Logger.getLogger("connector.VaultAuthenticator2"); - - private static final String BASE_AUTH_PATH = "/auth/approle/login"; + private final Logger logger = Logger.getLogger("connector.VaultAuthenticator"); private static final String CONTENT_TYPE_HEADER = "Content-Type"; - private final HttpClient httpClient; private final VaultSecretsManagerProperties properties; private final AsyncCache cache; + private final K8sTokenReader k8sTokenReader; + private final Gson gson = new Gson(); + private final Type mapType = new TypeToken>() {}.getType(); - public VaultAuthenticator(HttpClient httpClient, VaultSecretsManagerProperties properties) { + public VaultAuthenticator(HttpClient httpClient, + VaultSecretsManagerProperties properties, + K8sTokenReader k8sTokenReader) { this.httpClient = httpClient; this.properties = properties; + this.k8sTokenReader = k8sTokenReader; this.cache = initCache(); } - public Mono loginByAppRole() { - if (!properties.roleCredentialsProvided()) { - return Mono.defer(() -> - Mono.error(new SecretException("Could not perform action loginByAppRole. Role id or secret id is null, please check your configuration"))); - } else { - return Mono.fromFuture(cache.get(properties.getRoleId(), - (s, executor) -> performLoginByAppRole().toFuture().toCompletableFuture())); - } + /** + * Performs the login process with vault. If a token is provided, it will be used. If not, it will try to log in + * with the role_id and secret_id. If not, it will try to log in with k8s. + * @return the authentication response with the client token. + */ + public Mono login() { + return useTokenIfProvided() + .switchIfEmpty(loginWithRoleId()) + .switchIfEmpty(loginK8s()) + .switchIfEmpty(Mono.defer(() -> + Mono.error(new SecretException("Could not perform login with vault. Please check your configuration")))) + .doOnSuccess(this::checkLeaseDurationAgainstCacheExpTime); + } + + private Mono useTokenIfProvided() { + return Mono.just(properties.isTokenProvided()) + .filter(c -> c) + .map(c -> AuthResponse.builder() + .clientToken(properties.getToken()) + .build()); + } + + private Mono loginWithRoleId() { + return Mono.just(properties.isRoleCredentialsProvided()) + .filter(c -> c) + .flatMap(c -> Mono.fromFuture(cache.get(properties.getRoleId(), + (s, executor) -> performLoginByRoleId().toFuture().toCompletableFuture()))); } - private Mono performLoginByAppRole() { + private Mono loginK8s() { + return Mono.just(properties.isRoleNameForK8sProvided()) + .filter(c -> c) + .flatMap(c -> Mono.fromFuture(cache.get(properties.getVaultRoleForK8sAuth(), + (s, executor) -> performLoginWithK8s().toFuture().toCompletableFuture()))); + } + + private Mono performLoginByRoleId() { return Mono.fromSupplier(() -> HttpRequest.newBuilder() - .uri(URI.create(this.properties.buildUrl() + BASE_AUTH_PATH)) + .uri(URI.create(this.properties.buildUrl() + properties.getAppRoleAuthPath())) .timeout(Duration.ofSeconds(5)) .header(CONTENT_TYPE_HEADER, "application/json") - .POST(HttpRequest.BodyPublishers.ofString(buildLoginBody())) + .POST(HttpRequest.BodyPublishers.ofString( + gson.toJson(RoleAuth.builder() + .roleId(properties.getRoleId()) + .secretId(properties.getSecretId()) + .build()) + )) .build() ) - .flatMap(request -> Mono.fromFuture(httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()))) - .flatMap(response -> response.statusCode() != 200 ? - Mono.error(() -> new SecretException("Error performing authentication with vault: " + response.body())) : - Mono.just(response)) - .map(HttpResponse::body) - .map(body -> GsonUtils.getInstance().stringToModel(body, JsonObject.class)) - .map(map -> map.getAsJsonObject("auth")) - .map(auth -> AuthResponse.builder() - .clientToken(auth.get("client_token").getAsString()) - .accessor(auth.get("accessor").getAsString()) - .leaseDuration(auth.get("lease_duration").getAsLong()) - .renewable(auth.get("renewable").getAsBoolean()) - .build()) - .doOnSuccess(authResponse -> logger.info("Successfully authenticated by app-role with vault")) - .doOnError(err -> logger.severe("Error performing authentication with vault: " + err.getMessage())); + .flatMap(this::doCallAuthApi) + .doOnSuccess(authResponse -> + logger.info("Successfully authenticated via role_id with vault") + ); + } + + private Mono performLoginWithK8s() { + return k8sTokenReader.getKubernetesServiceAccountToken() + .flatMap(token -> Mono.fromSupplier(() -> + HttpRequest.newBuilder() + .uri(URI.create(this.properties.buildUrl() + properties.getK8sAuthPath())) + .timeout(Duration.ofSeconds(5)) + .header(CONTENT_TYPE_HEADER, "application/json") + .POST(HttpRequest.BodyPublishers.ofString( + gson.toJson(K8sAuth.builder() + .jwt(token) + .role(properties.getVaultRoleForK8sAuth()) + .build()) + )) + .build() + )) + .flatMap(this::doCallAuthApi) + .doOnSuccess(authResponse -> + logger.info("Successfully authenticated via k8s with vault") + ); } - private String buildLoginBody() { - return "{\"role_id\":\"" + properties.getRoleId() + "\",\"secret_id\":\"" + properties.getSecretId() + "\"}"; + private Mono doCallAuthApi(HttpRequest request) { + return Mono.fromFuture(httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())) + .flatMap(response -> response.statusCode() != 200 ? + Mono.error(() -> new SecretException("Error performing operation with vault: " + response.body())) : + Mono.just(response)) + .map(HttpResponse::body) + .map(body -> GsonUtils.getInstance().stringToModel(body, JsonObject.class)) + .map(map -> map.getAsJsonObject("auth")) + .map(auth -> AuthResponse.builder() + .clientToken(auth.get("client_token").getAsString()) + .accessor(auth.get("accessor").getAsString()) + .leaseDuration(auth.get("lease_duration").getAsLong()) + .renewable(auth.get("renewable").getAsBoolean()) + .metadata(gson.fromJson(auth.get("metadata").toString(), mapType)) + .build()) + .doOnError(err -> logger.severe("Error performing operation with vault: " + err.getMessage())); + } + + private void checkLeaseDurationAgainstCacheExpTime(AuthResponse authResponse) { + if (!authResponse.isRenewable()) { + return; + } + var leaseInSeconds = authResponse.getLeaseDuration(); + var cacheExpTime = properties.getAuthCacheProperties().getExpireAfter(); + if (cacheExpTime > leaseInSeconds) { + logger.warning("The configured token cache expiration time is greater " + + "than the maximum lease duration of the token. Calling Vault operations using such token, " + + "will fail when the token expires. Adjust your cache expiration to be similar to vault's token" + + "lease duration!!!"); + } } private AsyncCache initCache() { diff --git a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultSecretManagerConfigurator.java b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultSecretManagerConfigurator.java index 3faa003..08b59b5 100644 --- a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultSecretManagerConfigurator.java +++ b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultSecretManagerConfigurator.java @@ -4,6 +4,7 @@ import co.com.bancolombia.secretsmanager.config.VaultKeyStoreProperties; import co.com.bancolombia.secretsmanager.config.VaultSecretsManagerProperties; import co.com.bancolombia.secretsmanager.config.VaultTrustStoreProperties; +import co.com.bancolombia.secretsmanager.connector.auth.K8sTokenReader; import co.com.bancolombia.secretsmanager.connector.ssl.SslConfig; import lombok.Builder; @@ -11,12 +12,21 @@ import java.time.Duration; import java.util.logging.Logger; +/** + * This class is in charge of configuring the VaultSecretsManagerConnector + */ @Builder(setterPrefix = "with", toBuilder = true) public class VaultSecretManagerConfigurator { private static final Logger logger = Logger.getLogger("config.VaultSecretManagerConfigurator"); private final VaultSecretsManagerProperties properties; + private final K8sTokenReader k8sTokenReader; + /** + * This method is in charge of configuring the HttpClient + * @return HttpClient configured. + * @throws SecretException + */ public HttpClient getHttpClient() throws SecretException { HttpClient.Builder clientBuilder = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NORMAL) @@ -27,14 +37,25 @@ public HttpClient getHttpClient() throws SecretException { return clientBuilder.build(); } + /** + * This method is in charge of configuring the VaultAuthenticator + * @return the VaultAuthenticator configured. + * @throws SecretException + */ public VaultAuthenticator getVaultAuthenticator() throws SecretException { - return new VaultAuthenticator(getHttpClient(), properties); + return new VaultAuthenticator(getHttpClient(), properties, + k8sTokenReader != null? k8sTokenReader : new K8sTokenReader()); } + /** + * This method is in charge of configuring the VaultSecretsManagerConnector + * @return the VaultSecretsManagerConnector configured. + * @throws SecretException + */ public VaultSecretsManagerConnectorAsync getVaultClient() throws SecretException { HttpClient httpClient = getHttpClient(); return new VaultSecretsManagerConnectorAsync(httpClient, - new VaultAuthenticator(httpClient, properties), + getVaultAuthenticator(), properties); } @@ -56,7 +77,7 @@ private SslConfig setTrustConfiguration(SslConfig sslConfig, } else if (trustStoreProperties.getPemFile() != null) { sslConfig.pemFile(trustStoreProperties.getPemFile()); } else { - logger.warning("No trust store file or pem resource provided"); + throw new SecretException("VaultTrustStoreProperties was set, but no trust store file or pem resource provided"); } return sslConfig; } @@ -70,7 +91,7 @@ private SslConfig setKeystoreConfiguration(SslConfig sslConfig, sslConfig.clientPemFile(keyStoreProperties.getClientPem()); sslConfig.clientKeyPemFile(keyStoreProperties.getClientKeyPem()); } else { - logger.warning("No key store file or pem resources provided"); + throw new SecretException("VaultKeyStoreProperties was set, but no key store file or pem resources provided"); } return sslConfig; } diff --git a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultSecretsManagerConnectorAsync.java b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultSecretsManagerConnectorAsync.java index 95a5581..e8c66c1 100644 --- a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultSecretsManagerConnectorAsync.java +++ b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/VaultSecretsManagerConnectorAsync.java @@ -18,6 +18,9 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +/** + * Connector to Vault Secrets Manager for reading secrets asynchronously. + */ public class VaultSecretsManagerConnectorAsync implements GenericManagerAsync { private static final Logger logger = Logger.getLogger("connector.VaultSecretsManagerConnectorAsync"); @@ -71,14 +74,8 @@ private Mono getSecretValue(String secretName) { } private Mono getToken() { - return Mono.defer(() -> { - if (this.properties.getToken() != null) { - return Mono.just(this.properties.getToken()); - } else { - return vaultAuthenticator.loginByAppRole() - .map(AuthResponse::getClientToken); - } - }); + return vaultAuthenticator.login() + .map(AuthResponse::getClientToken); } @Override diff --git a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/AuthResponse.java b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/AuthResponse.java index 5f47d0f..b8caa03 100644 --- a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/AuthResponse.java +++ b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/AuthResponse.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; @Getter @NoArgsConstructor @@ -19,6 +20,7 @@ public class AuthResponse { private List policies = new ArrayList<>(); @Builder.Default private List tokenPolicies = new ArrayList<>(); + private Map metadata; private long leaseDuration; private boolean renewable; } diff --git a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/K8sAuth.java b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/K8sAuth.java new file mode 100644 index 0000000..cc13781 --- /dev/null +++ b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/K8sAuth.java @@ -0,0 +1,15 @@ +package co.com.bancolombia.secretsmanager.connector.auth; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +public class K8sAuth { + private String jwt; + private String role; +} diff --git a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/K8sTokenReader.java b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/K8sTokenReader.java new file mode 100644 index 0000000..6a78c7b --- /dev/null +++ b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/K8sTokenReader.java @@ -0,0 +1,39 @@ +package co.com.bancolombia.secretsmanager.connector.auth; + +import co.com.bancolombia.secretsmanager.api.exceptions.SecretException; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class K8sTokenReader { + + private final String tokenFile; + + public K8sTokenReader() { + this.tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"; + } + + public K8sTokenReader(String tokenFile) { + this.tokenFile = tokenFile; + } + + /** + * Reads the kubernetes service account token from the file system + * @return the kubernetes service account token + */ + public Mono getKubernetesServiceAccountToken() { + return Mono.fromCallable(() -> { + try { + Path path = Paths.get(this.tokenFile); + return Files.readAllLines(path).get(0); + } catch (Exception e) { + throw new SecretException("Error reading kubernetes service account token: " + e.getMessage()); + } + }) + .subscribeOn(Schedulers.boundedElastic()); + } + +} diff --git a/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/RoleAuth.java b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/RoleAuth.java new file mode 100644 index 0000000..52b579e --- /dev/null +++ b/async/vault-async/src/main/java/co/com/bancolombia/secretsmanager/connector/auth/RoleAuth.java @@ -0,0 +1,19 @@ +package co.com.bancolombia.secretsmanager.connector.auth; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +public class RoleAuth { + @SerializedName("role_id") + private String roleId; + + @SerializedName("secret_id") + private String secretId; +} diff --git a/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaulAuthenticatorTest.java b/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaulAuthenticatorTest.java index bcccfca..c679590 100644 --- a/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaulAuthenticatorTest.java +++ b/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaulAuthenticatorTest.java @@ -1,56 +1,32 @@ package co.com.bancolombia.secretsmanager.connector; import co.com.bancolombia.secretsmanager.config.VaultSecretsManagerProperties; +import co.com.bancolombia.secretsmanager.connector.auth.K8sTokenReader; import lombok.SneakyThrows; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class VaulAuthenticatorTest { @SneakyThrows @Test - public void testAuthenticate() { + public void testAuthenticateWithRoleIdAndSecretId() { MockWebServer server = new MockWebServer(); MockResponse response = new MockResponse() .addHeader("Content-Type", "application/json") - .setBody("{\n" + - " \"request_id\": \"260fa017-e8e1-e3b5-a194-5ebe86e53275\",\n" + - " \"lease_id\": \"\",\n" + - " \"renewable\": false,\n" + - " \"lease_duration\": 0,\n" + - " \"data\": null,\n" + - " \"wrap_info\": null,\n" + - " \"warnings\": null,\n" + - " \"auth\": {\n" + - " \"client_token\": \"hvs.dummytoken\",\n" + - " \"accessor\": \"accessor.dummy\",\n" + - " \"policies\": [\n" + - " \"default\"\n" + - " ],\n" + - " \"token_policies\": [\n" + - " \"default\"\n" + - " ],\n" + - " \"metadata\": {\n" + - " \"role_name\": \"my-role\"\n" + - " },\n" + - " \"lease_duration\": 600,\n" + - " \"renewable\": true,\n" + - " \"entity_id\": \"656855e4-82b7-b874-6da7-ce2dff19711e\",\n" + - " \"token_type\": \"service\",\n" + - " \"orphan\": true,\n" + - " \"mfa_requirement\": null,\n" + - " \"num_uses\": 0\n" + - " }\n" + - "}"); + .setBody(buildSuccessAuthResponse()); server.enqueue(response); server.start(); @@ -67,7 +43,7 @@ public void testAuthenticate() { VaultAuthenticator vaultAuthenticator = configurator.getVaultAuthenticator(); - StepVerifier.create(vaultAuthenticator.loginByAppRole()) + StepVerifier.create(vaultAuthenticator.login()) .expectSubscription() .expectNextMatches(authResponse -> { assertEquals("hvs.dummytoken", authResponse.getClientToken()); @@ -80,6 +56,48 @@ public void testAuthenticate() { server.shutdown(); } + @SneakyThrows + @Test + public void testAuthenticateWithK8s() { + + MockWebServer server = new MockWebServer(); + + MockResponse response = new MockResponse() + .addHeader("Content-Type", "application/json") + .setBody(buildSuccessAuthResponse()); + server.enqueue(response); + server.start(); + + VaultSecretsManagerProperties properties = VaultSecretsManagerProperties.builder() + .host("localhost") + .port(server.getPort()) + .token(null) + .vaultRoleForK8sAuth("xxxxxxxxxx") + .build(); + + K8sTokenReader k8sTokenReaderMock = Mockito.mock(K8sTokenReader.class); + when(k8sTokenReaderMock.getKubernetesServiceAccountToken()).thenReturn(Mono.just("ey...")); + + VaultSecretManagerConfigurator configurator = VaultSecretManagerConfigurator.builder() + .withProperties(properties) + .withK8sTokenReader(k8sTokenReaderMock) + .build(); + + VaultAuthenticator vaultAuthenticator = configurator.getVaultAuthenticator(); + + StepVerifier.create(vaultAuthenticator.login()) + .expectSubscription() + .expectNextMatches(authResponse -> { + assertEquals("hvs.dummytoken", authResponse.getClientToken()); + return true; + }) + .verifyComplete(); + + assertEquals("/v1/auth/kubernetes/login", server.takeRequest().getPath()); + + server.shutdown(); + } + @SneakyThrows @Test public void testHandleNoCredentials() { @@ -89,19 +107,20 @@ public void testHandleNoCredentials() { .token(null) .roleId(null) .secretId(null) + .vaultRoleForK8sAuth(null) .build(); VaultSecretManagerConfigurator configurator = VaultSecretManagerConfigurator.builder() .withProperties(properties) .build(); - VaultAuthenticator vaultAuthenticator = new VaultAuthenticator(configurator.getHttpClient(), properties); + VaultAuthenticator vaultAuthenticator = configurator.getVaultAuthenticator(); - StepVerifier.create(vaultAuthenticator.loginByAppRole()) + StepVerifier.create(vaultAuthenticator.login()) .expectSubscription() .expectErrorMatches(throwable -> { - assertEquals(throwable.getMessage(), "Could not perform action loginByAppRole. " + - "Role id or secret id is null, please check your configuration"); + assertEquals(throwable.getMessage(), "Could not perform login with vault. " + + "Please check your configuration"); return true; }) .verify(); @@ -136,9 +155,9 @@ public void testHandleFailedAuth() { .withProperties(properties) .build(); - VaultAuthenticator vaultAuthenticator = new VaultAuthenticator(configurator.getHttpClient(), properties); + VaultAuthenticator vaultAuthenticator = configurator.getVaultAuthenticator(); - StepVerifier.create(vaultAuthenticator.loginByAppRole()) + StepVerifier.create(vaultAuthenticator.login()) .expectSubscription() .expectErrorMatches(throwable -> { Assert.assertTrue(throwable.getMessage().contains("invalid role or secret ID")); @@ -147,7 +166,38 @@ public void testHandleFailedAuth() { .verify(); assertEquals("/v1/auth/approle/login", server.takeRequest().getPath()); -// server.shutdown(); } + + private String buildSuccessAuthResponse() { + return "{\n" + + " \"request_id\": \"260fa017-e8e1-e3b5-a194-5ebe86e53275\",\n" + + " \"lease_id\": \"\",\n" + + " \"renewable\": false,\n" + + " \"lease_duration\": 0,\n" + + " \"data\": null,\n" + + " \"wrap_info\": null,\n" + + " \"warnings\": null,\n" + + " \"auth\": {\n" + + " \"client_token\": \"hvs.dummytoken\",\n" + + " \"accessor\": \"accessor.dummy\",\n" + + " \"policies\": [\n" + + " \"default\"\n" + + " ],\n" + + " \"token_policies\": [\n" + + " \"default\"\n" + + " ],\n" + + " \"metadata\": {\n" + + " \"role_name\": \"my-role\"\n" + + " },\n" + + " \"lease_duration\": 600,\n" + + " \"renewable\": true,\n" + + " \"entity_id\": \"656855e4-82b7-b874-6da7-ce2dff19711e\",\n" + + " \"token_type\": \"service\",\n" + + " \"orphan\": true,\n" + + " \"mfa_requirement\": null,\n" + + " \"num_uses\": 0\n" + + " }\n" + + "}"; + } } diff --git a/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaultSecretManagerConfiguratorTest.java b/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaultSecretManagerConfiguratorTest.java index 8caabc2..bb44b87 100644 --- a/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaultSecretManagerConfiguratorTest.java +++ b/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaultSecretManagerConfiguratorTest.java @@ -1,5 +1,6 @@ package co.com.bancolombia.secretsmanager.connector; +import co.com.bancolombia.secretsmanager.api.exceptions.SecretException; import co.com.bancolombia.secretsmanager.config.VaultKeyStoreProperties; import co.com.bancolombia.secretsmanager.config.VaultSecretsManagerProperties; import co.com.bancolombia.secretsmanager.config.VaultTrustStoreProperties; @@ -75,6 +76,26 @@ public void testClientGenerationWithKeyStore() { Assert.assertNotNull(client); } + @SneakyThrows + @Test + public void testClientGenerationWithKeyStoreNoValues() { + VaultSecretsManagerProperties properties = VaultSecretsManagerProperties.builder() + .host("localhost") + .port(8200) + .roleId("x") + .secretId("y") + .keyStoreProperties(VaultKeyStoreProperties.builder() + .build() + ) + .build(); + + Assert.assertThrows(SecretException.class, () -> VaultSecretManagerConfigurator.builder() + .withProperties(properties) + .build() + .getHttpClient()); + + } + @SneakyThrows @Test public void testClientGenerationWithTrustStore() { @@ -125,4 +146,26 @@ public void testClientGenerationWithTrustPem() { Assert.assertNotNull(client); } + @SneakyThrows + @Test + public void testClientGenerationWithTrustNoValues() { + URI pemUri = getClass().getClassLoader().getResource("certificate.arm").toURI(); + File pemFile = new File(pemUri); + + VaultSecretsManagerProperties properties = VaultSecretsManagerProperties.builder() + .host("localhost") + .port(8200) + .roleId("x") + .secretId("y") + .trustStoreProperties(VaultTrustStoreProperties.builder() + .build() + ) + .build(); + + Assert.assertThrows(SecretException.class, () -> VaultSecretManagerConfigurator.builder() + .withProperties(properties) + .build() + .getHttpClient()); + } + } diff --git a/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaultSecretsManagerConnectorAsyncTest.java b/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaultSecretsManagerConnectorAsyncTest.java index 691fa59..6d311b5 100644 --- a/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaultSecretsManagerConnectorAsyncTest.java +++ b/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/VaultSecretsManagerConnectorAsyncTest.java @@ -53,7 +53,7 @@ public void testGetSecretContent() { HttpClient httpClient = configurator.getHttpClient(); - when(authenticator.loginByAppRole()).thenReturn(Mono.just(AuthResponse.builder().clientToken("hvs.dummy").build())); + when(authenticator.login()).thenReturn(Mono.just(AuthResponse.builder().clientToken("hvs.dummy").build())); VaultSecretsManagerConnectorAsync vaultSecretsManagerConnectorAsync = new VaultSecretsManagerConnectorAsync(httpClient, authenticator, properties); @@ -144,7 +144,7 @@ public void testGetSecretPojo() { HttpClient httpClient = configurator.getHttpClient(); - when(authenticator.loginByAppRole()).thenReturn(Mono.just(AuthResponse.builder().clientToken("hvs.dummy").build())); + when(authenticator.login()).thenReturn(Mono.just(AuthResponse.builder().clientToken("hvs.dummy").build())); VaultSecretsManagerConnectorAsync vaultSecretsManagerConnectorAsync = new VaultSecretsManagerConnectorAsync(httpClient, authenticator, properties); diff --git a/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/auth/K8sTokenReaderTest.java b/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/auth/K8sTokenReaderTest.java new file mode 100644 index 0000000..a628c2c --- /dev/null +++ b/async/vault-async/src/test/java/co/com/bancolombia/secretsmanager/connector/auth/K8sTokenReaderTest.java @@ -0,0 +1,24 @@ +package co.com.bancolombia.secretsmanager.connector.auth; + +import lombok.SneakyThrows; +import org.junit.Test; +import reactor.test.StepVerifier; + +public class K8sTokenReaderTest { + + @SneakyThrows + @Test + public void testReadToken() { + StepVerifier.create(new K8sTokenReader().getKubernetesServiceAccountToken()) + .expectError().verify(); + } + + @SneakyThrows + @Test + public void testReadTokenWithPath() { + StepVerifier.create(new K8sTokenReader("/tmp/file").getKubernetesServiceAccountToken()) + .expectError().verify(); + } + + +} diff --git a/gradle.properties b/gradle.properties index 2110f93..ffab0fc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=4.2.0 +version=4.3.0 springBootVersion=3.1.1 reactorCoreVersion=3.5.7 reactorExtraVersion=3.5.1 diff --git a/main.gradle b/main.gradle index 30585bf..3ce76b2 100644 --- a/main.gradle +++ b/main.gradle @@ -17,6 +17,7 @@ allprojects { testAnnotationProcessor 'org.projectlombok:lombok:1.18.28' testImplementation 'org.mockito:mockito-core:3.12.4' + testImplementation 'org.mockito:mockito-inline:3.12.4' testImplementation 'junit:junit:4.13.2' }