diff --git a/README.md b/README.md index 1dfd0f1..bb86112 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,91 @@ Disable Property: `org.springframework.cloud.bindings.boot.sqlserver.enable` | `spring.r2dbc.password` | `{secret/password}` | `spring.r2dbc.username` | `{secret/username}` + +### Vault +Kind: `Vault` +Disable Property: `org.springframework.cloud.bindings.boot.vault.enable` + +Any Provider: +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.authentication` | `{secret/authentication-method}` +| `spring.cloud.vault.namespace` | `{secret/namespace}` +| `spring.cloud.vault.uri` | `{secret/uri}` + +If `{secret/authentication-method}` is equal to `approle`: +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.app-role.app-role-path` | `{secret/app-role-path}` +| `spring.cloud.vault.app-role.role-id` | `{secret/role-id}` +| `spring.cloud.vault.app-role.role` | `{secret/role}` +| `spring.cloud.vault.app-role.secret-id` | `{secret/secret-id}` + +If `{secret/authentication-method}` is equal to `aws_ec2`: +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.aws-ec2.aws-ec2-path` | `{secret/aws-ec2-path}` +| `spring.cloud.vault.aws-ec2.identity-document` | `{secret/aws-ec2-instance-identity-document}` +| `spring.cloud.vault.aws-ec2.nonce` | `{secret/nonce}` +| `spring.cloud.vault.aws-ec2.role` | `{secret/role}` + +If `{secret/authentication-method}` is equal to `aws_iam`: +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.aws-iam.aws-path` | `{secret/aws-path}` +| `spring.cloud.vault.aws-iam.endpoint-uri` | `{secret/aws-sts-endpoint-uri}` +| `spring.cloud.vault.aws-iam.role` | `{secret/role}` +| `spring.cloud.vault.aws-iam.server-id` | `{secret/aws-iam-server-id}` + +If `{secret/authentication-method}` is equal to `azure_msi`: +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.azure-msi.azure-path` | `{secret/azure-path}` +| `spring.cloud.vault.azure-msi.role` | `{secret/role}` + +If `{secret/authentication-method}` is equal to `cert`: +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.ssl.cert-auth-path` | `{secret/cert-auth-path}` +| `spring.cloud.vault.ssl.key-store-password` | `{secret/key-store-password}` +| `spring.cloud.vault.ssl.key-store` | `${CNB_BINDINGS}/{name}/secret/keystore.jks` + +If `{secret/authentication-method}` is equal to `cubbyhole`: +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.token` | `{secret/token}` + +If `{secret/authentication-method}` is equal to `gcp_gce`: +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.gcp-gce.gcp-path` | `{secret/gcp-path}` +| `spring.cloud.vault.gcp-gce.role` | `{secret/role}` +| `spring.cloud.vault.gcp-gce.service-account` | `{secret/gcp-service-account}` + + +If `{secret/authentication-method}` is equal to `gcp_iam`: +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.gcp-iam.credentials.encoded-key` | `{secret/encoded-key}` +| `spring.cloud.vault.gcp-iam.credentials.location` | `${CNB_BINDINGS}/{name}/secret/credentials.json` +| `spring.cloud.vault.gcp-iam.gcp-path` | `{secret/gcp-path}` +| `spring.cloud.vault.gcp-iam.jwt-validity` | `{secret/jwt-validity}` +| `spring.cloud.vault.gcp-iam.project-id` | `{secret/gcp-project-id}` +| `spring.cloud.vault.gcp-iam.role` | `{secret/role}` +| `spring.cloud.vault.gcp-iam.service-account` | `{secret/gcp-service-account}` + +If `{secret/authentication-method}` is equal to `kubernetes`: +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.kubernetes.kubernetes-path` | `{secret/kubernetes-path}` +| `spring.cloud.vault.kubernetes.role` | `{secret/role}` + +If `{secret/authentication-method}` is equal to `token`: +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.token` | `{secret/token}` + + ### Wavefront Kind: `Wavefront` diff --git a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java new file mode 100644 index 0000000..88b99b3 --- /dev/null +++ b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java @@ -0,0 +1,121 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.bindings.boot; + +import org.springframework.boot.context.event.ApplicationPreparedEvent; +import org.springframework.boot.logging.DeferredLog; +import org.springframework.cloud.bindings.Binding; +import org.springframework.cloud.bindings.Bindings; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.Environment; + +import java.util.Map; + +import static org.springframework.cloud.bindings.boot.Guards.isKindEnabled; + +/** + * An implementation of {@link BindingsPropertiesProcessor} that detects {@link Binding}s of kind: {@value KIND}. + */ +public final class VaultBindingsPropertiesProcessor implements BindingsPropertiesProcessor, ApplicationListener { + + /** + * The {@link Binding} kind that this processor is interested in: {@value}. + **/ + public static final String KIND = "Vault"; + + private static final DeferredLog LOG = new DeferredLog(); + + @Override + public void process(Environment environment, Bindings bindings, Map properties) { + if (!isKindEnabled(environment, KIND)) { + return; + } + + bindings.filterBindings(KIND).forEach(binding -> { + MapMapper map = new MapMapper(binding.getSecret(), properties); + map.from("uri").to("spring.cloud.vault.uri"); + map.from("namespace").to("spring.cloud.vault.namespace"); // vault enterprise feature + + String authenticationMethod = binding.getSecret().get("authentication-method"); + if (authenticationMethod == null) { + LOG.warn(String.format("Key 'method' is missing from secret of binding '%s'", binding.getName())); + return; + } + properties.put("spring.cloud.vault.authentication", authenticationMethod); + switch (authenticationMethod.toUpperCase()) { + case "TOKEN": + case "CUBBYHOLE": + map.from("token").to("spring.cloud.vault.token"); + break; + case "APPROLE": + map.from("role-id").to("spring.cloud.vault.app-role.role-id"); + map.from("secret-id").to("spring.cloud.vault.app-role.secret-id"); + map.from("role").to("spring.cloud.vault.app-role.role"); + map.from("app-role-path").to("spring.cloud.vault.app-role.app-role-path"); + break; + case "AWS_EC2": + map.from("role").to("spring.cloud.vault.aws-ec2.role"); + map.from("aws-ec2-path").to("spring.cloud.vault.aws-ec2.aws-ec2-path"); + map.from("aws-ec2-instance-identity-document").to("spring.cloud.vault.aws-ec2.identity-document"); + map.from("nonce").to("spring.cloud.vault.aws-ec2.nonce"); + break; + case "AWS_IAM": + map.from("role").to("spring.cloud.vault.aws-iam.role"); + map.from("aws-path").to("spring.cloud.vault.aws-iam.aws-path"); + map.from("aws-iam-server-id").to("spring.cloud.vault.aws-iam.server-id"); + map.from("aws-sts-endpoint-uri").to("spring.cloud.vault.aws-iam.endpoint-uri"); + break; + case "AZURE_MSI": + map.from("role").to("spring.cloud.vault.azure-msi.role"); + map.from("azure-path").to("spring.cloud.vault.azure-msi.azure-path"); + break; + case "CERT": + properties.put("spring.cloud.vault.ssl.key-store", binding.getSecretFilePath("keystore.jks").toString()); + map.from("key-store-password").to("spring.cloud.vault.ssl.key-store-password"); + map.from("cert-auth-path").to("spring.cloud.vault.ssl.cert-auth-path"); + break; + case "GCP_GCE": + map.from("role").to("spring.cloud.vault.gcp-gce.role"); + map.from("gcp-path").to("spring.cloud.vault.gcp-gce.gcp-path"); + map.from("gcp-service-account").to("spring.cloud.vault.gcp-gce.service-account"); + break; + case "GCP_IAM": + if (binding.getSecret().containsKey("credentials.json")) { + properties.put("spring.cloud.vault.gcp-iam.credentials.location", binding.getSecretFilePath("credentials.json").toString()); + } + map.from("role").to("spring.cloud.vault.gcp-iam.role"); + map.from("encoded-key").to("spring.cloud.vault.gcp-iam.credentials.encoded-key"); + map.from("gcp-path").to("spring.cloud.vault.gcp-iam.gcp-path"); + map.from("jwt-validity").to("spring.cloud.vault.gcp-iam.jwt-validity"); + map.from("gcp-project-id").to("spring.cloud.vault.gcp-iam.project-id"); + map.from("gcp-service-account").to("spring.cloud.vault.gcp-iam.service-account"); + break; + case "KUBERNETES": + map.from("role").to("spring.cloud.vault.kubernetes.role"); + map.from("kubernetes-path").to("spring.cloud.vault.kubernetes.kubernetes-path"); + break; + default: + LOG.warn(String.format("Binding '%s' contains unrecognized 'method'", binding.getName())); + } + }); + } + + @Override + public void onApplicationEvent(ApplicationPreparedEvent event) { + LOG.switchTo(getClass()); + } +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories index 0f3c20f..e1aa3cb 100644 --- a/src/main/resources/META-INF/spring.factories +++ b/src/main/resources/META-INF/spring.factories @@ -1,6 +1,7 @@ org.springframework.context.ApplicationListener=\ org.springframework.cloud.bindings.boot.BindingFlattenedEnvironmentPostProcessor, \ - org.springframework.cloud.bindings.boot.BindingSpecificEnvironmentPostProcessor + org.springframework.cloud.bindings.boot.BindingSpecificEnvironmentPostProcessor, \ + org.springframework.cloud.bindings.boot.VaultBindingsPropertiesProcessor org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.cloud.bindings.boot.BindingFlattenedEnvironmentPostProcessor, \ org.springframework.cloud.bindings.boot.BindingSpecificEnvironmentPostProcessor @@ -23,4 +24,5 @@ org.springframework.cloud.bindings.boot.BindingsPropertiesProcessor=\ org.springframework.cloud.bindings.boot.RedisBindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.SpringSecurityOAuth2BindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.SqlServerBindingsPropertiesProcessor, \ + org.springframework.cloud.bindings.boot.VaultBindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.WavefrontBindingsPropertiesProcessor \ No newline at end of file diff --git a/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java index bc1eb60..cc04d7b 100644 --- a/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java +++ b/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java @@ -98,7 +98,7 @@ final class BindingSpecificEnvironmentPostProcessorTest { @Test @DisplayName("included implementations are registered") void includedImplementations() { - assertThat(new BindingSpecificEnvironmentPostProcessor().processors).hasSize(18); + assertThat(new BindingSpecificEnvironmentPostProcessor().processors).hasSize(19); } } diff --git a/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java new file mode 100644 index 0000000..f5c9de3 --- /dev/null +++ b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java @@ -0,0 +1,314 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.bindings.boot; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.cloud.bindings.Binding; +import org.springframework.cloud.bindings.Bindings; +import org.springframework.cloud.bindings.FluentMap; +import org.springframework.mock.env.MockEnvironment; + +import java.nio.file.Paths; +import java.util.HashMap; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.bindings.boot.VaultBindingsPropertiesProcessor.KIND; + +@DisplayName("Vault BindingsPropertiesProcessor") +final class VaultPropertiesProcessorTest { + + private FluentMap baseSecret() { + return new FluentMap() + .withEntry("namespace", "test-namespace") + .withEntry("uri", "test-uri"); + } + + private FluentMap metadata() { + return new FluentMap() + .withEntry("kind", KIND); + } + + private final Binding tokenBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("authentication-method", "token") + .withEntry("token", "test-token") + ); + + private final Binding appRoleBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("app-role-path", "test-app-role-path") + .withEntry("authentication-method", "approle") + .withEntry("role", "test-role") + .withEntry("role-id", "test-role-id") + .withEntry("secret-id", "test-secret-id") + ); + + private final Binding cubbyholeBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("authentication-method", "cubbyhole") + .withEntry("token", "test-token") + ); + + private final Binding certBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("authentication-method", "cert") + .withEntry("cert-auth-path", "test-cert-auth-path") + .withEntry("key-store-password", "test-key-store-password") + .withEntry("keystore.jks", "key store contents!") + ); + + private final Binding awsEc2Binding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("authentication-method", "aws_ec2") + .withEntry("aws-ec2-instance-identity-document", "test-identity-document") + .withEntry("nonce", "test-nonce") + .withEntry("aws-ec2-path", "test-aws-ec2-path") + .withEntry("role", "test-role") + ); + + private final Binding awsIamBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("authentication-method", "aws_iam") + .withEntry("aws-iam-server-id", "test-server-id") + .withEntry("aws-path", "test-aws-path") + .withEntry("aws-sts-endpoint-uri", "test-endpoint-uri") + .withEntry("role", "test-role") + ); + + private final Binding azureMsiBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("authentication-method", "azure_msi") + .withEntry("azure-path", "test-azure-path") + .withEntry("role", "test-role") + ); + + private final Binding gcpGceBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("authentication-method", "gcp_gce") + .withEntry("gcp-path", "test-gcp-path") + .withEntry("gcp-service-account", "test-service-account") + .withEntry("role", "test-role") + ); + + private final Binding gcpIamBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("authentication-method", "gcp_iam") + .withEntry("credentials.json", "credentials JSON contents!") + .withEntry("encoded-key", "test-encoded-key") + .withEntry("gcp-path", "test-gcp-path") + .withEntry("gcp-project-id", "test-project-id") + .withEntry("gcp-service-account", "test-service-account") + .withEntry("jwt-validity", "test-jwt-validity") + .withEntry("role", "test-role") + ); + + private final Binding k8sBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("authentication-method", "kubernetes") + .withEntry("role", "test-role") + .withEntry("kubernetes-path", "test-kubernetes-path") + ); + + private final MockEnvironment environment = new MockEnvironment(); + + private final HashMap properties = new HashMap<>(); + + @Test + @DisplayName("Supports token authentication") + void testTokenAuthentication() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(tokenBinding), properties); + assertThat(properties) + .containsEntry("spring.cloud.vault.uri", "test-uri") + .containsEntry("spring.cloud.vault.namespace", "test-namespace") + .containsEntry("spring.cloud.vault.authentication", "token") + .containsEntry("spring.cloud.vault.token", "test-token"); + } + + @Test + @DisplayName("Supports AppRole authentication") + void testAppRoleAuthentication() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(appRoleBinding), properties); + assertThat(properties) + .containsEntry("spring.cloud.vault.uri", "test-uri") + .containsEntry("spring.cloud.vault.namespace", "test-namespace") + .containsEntry("spring.cloud.vault.authentication", "approle") + .containsEntry("spring.cloud.vault.app-role.role-id", "test-role-id") + .containsEntry("spring.cloud.vault.app-role.secret-id", "test-secret-id") + .containsEntry("spring.cloud.vault.app-role.role", "test-role") + .containsEntry("spring.cloud.vault.app-role.app-role-path", "test-app-role-path"); + } + + @Test + @DisplayName("Supports cubbyhole authentication") + void testCubbyholeAuthentication() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(cubbyholeBinding), properties); + assertThat(properties) + .containsEntry("spring.cloud.vault.uri", "test-uri") + .containsEntry("spring.cloud.vault.namespace", "test-namespace") + .containsEntry("spring.cloud.vault.authentication", "cubbyhole") + .containsEntry("spring.cloud.vault.token", "test-token"); + } + + @Test + @DisplayName("Supports TLS certificate authentication") + void testCertAuthentication() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(certBinding), properties); + assertThat(properties) + .containsEntry("spring.cloud.vault.uri", "test-uri") + .containsEntry("spring.cloud.vault.namespace", "test-namespace") + .containsEntry("spring.cloud.vault.authentication", "cert") + .containsEntry("spring.cloud.vault.ssl.key-store", "test-path/secret/keystore.jks") + .containsEntry("spring.cloud.vault.ssl.key-store-password", "test-key-store-password") + .containsEntry("spring.cloud.vault.ssl.cert-auth-path", "test-cert-auth-path"); + } + + @Test + @DisplayName("Supports AWS EC2 authentication") + void testAwsEc2Authentication() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(awsEc2Binding), properties); + assertThat(properties) + .containsEntry("spring.cloud.vault.uri", "test-uri") + .containsEntry("spring.cloud.vault.namespace", "test-namespace") + .containsEntry("spring.cloud.vault.authentication", "aws_ec2") + .containsEntry("spring.cloud.vault.aws-ec2.role", "test-role") + .containsEntry("spring.cloud.vault.aws-ec2.aws-ec2-path", "test-aws-ec2-path") + .containsEntry("spring.cloud.vault.aws-ec2.identity-document", "test-identity-document") + .containsEntry("spring.cloud.vault.aws-ec2.nonce", "test-nonce"); + } + + @Test + @DisplayName("Supports AWS IAM authentication") + void testAwsIamAuthentication() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(awsIamBinding), properties); + assertThat(properties) + .containsEntry("spring.cloud.vault.uri", "test-uri") + .containsEntry("spring.cloud.vault.namespace", "test-namespace") + .containsEntry("spring.cloud.vault.authentication", "aws_iam") + .containsEntry("spring.cloud.vault.aws-iam.role", "test-role") + .containsEntry("spring.cloud.vault.aws-iam.aws-path", "test-aws-path") + .containsEntry("spring.cloud.vault.aws-iam.server-id", "test-server-id") + .containsEntry("spring.cloud.vault.aws-iam.endpoint-uri", "test-endpoint-uri"); + } + + @Test + @DisplayName("Supports Azure MSI authentication") + void testAzureMsiAuthentication() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(azureMsiBinding), properties); + assertThat(properties) + .containsEntry("spring.cloud.vault.uri", "test-uri") + .containsEntry("spring.cloud.vault.namespace", "test-namespace") + .containsEntry("spring.cloud.vault.authentication", "azure_msi") + .containsEntry("spring.cloud.vault.azure-msi.role", "test-role") + .containsEntry("spring.cloud.vault.azure-msi.azure-path", "test-azure-path"); + } + + @Test + @DisplayName("Supports GCP GCE authentication") + void testGcpGceAuthentication() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(gcpGceBinding), properties); + assertThat(properties) + .containsEntry("spring.cloud.vault.uri", "test-uri") + .containsEntry("spring.cloud.vault.namespace", "test-namespace") + .containsEntry("spring.cloud.vault.authentication", "gcp_gce") + .containsEntry("spring.cloud.vault.gcp-gce.role", "test-role") + .containsEntry("spring.cloud.vault.gcp-gce.gcp-path", "test-gcp-path") + .containsEntry("spring.cloud.vault.gcp-gce.service-account", "test-service-account"); + } + + @Test + @DisplayName("Supports GCP IAM authentication") + void testGcpIamAuthentication() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(gcpIamBinding), properties); + assertThat(properties) + .containsEntry("spring.cloud.vault.uri", "test-uri") + .containsEntry("spring.cloud.vault.namespace", "test-namespace") + .containsEntry("spring.cloud.vault.authentication", "gcp_iam") + .containsEntry("spring.cloud.vault.gcp-iam.role", "test-role") + .containsEntry("spring.cloud.vault.gcp-iam.credentials.location", "test-path/secret/credentials.json") + .containsEntry("spring.cloud.vault.gcp-iam.credentials.encoded-key", "test-encoded-key") + .containsEntry("spring.cloud.vault.gcp-iam.gcp-path", "test-gcp-path") + .containsEntry("spring.cloud.vault.gcp-iam.jwt-validity", "test-jwt-validity") + .containsEntry("spring.cloud.vault.gcp-iam.project-id", "test-project-id") + .containsEntry("spring.cloud.vault.gcp-iam.service-account", "test-service-account"); + } + + @Test + @DisplayName("Supports Kubernetes authentication") + void testK8sAuthentication() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(k8sBinding), properties); + assertThat(properties) + .containsEntry("spring.cloud.vault.uri", "test-uri") + .containsEntry("spring.cloud.vault.namespace", "test-namespace") + .containsEntry("spring.cloud.vault.authentication", "kubernetes") + .containsEntry("spring.cloud.vault.kubernetes.role", "test-role") + .containsEntry("spring.cloud.vault.kubernetes.kubernetes-path", "test-kubernetes-path"); + } + + @Test + @DisplayName("Doesn't fail when method is missing") + void testMissingProvider() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(new Binding( + "test-name", + Paths.get("test-path"), + metadata(), + baseSecret() + )), properties); + assertThat(properties) + .containsEntry("spring.cloud.vault.uri", "test-uri") + .containsEntry("spring.cloud.vault.namespace", "test-namespace") + .doesNotContainKey("spring.cloud.vault.authentication"); + } + + //Vault agent authentication can be configured using a sidecar and should not require a binding + + @Test + @DisplayName("can be disabled") + void disabled() { + environment.setProperty("org.springframework.cloud.bindings.boot.vault.enable", "false"); + + new VaultBindingsPropertiesProcessor().process(environment, new Bindings( + tokenBinding, + appRoleBinding, + cubbyholeBinding, + certBinding + ), properties); + + assertThat(properties).isEmpty(); + } + +}