From 02c0c59d4b257436b4fbb3e306868452d813d822 Mon Sep 17 00:00:00 2001 From: Emily Casey Date: Fri, 19 Jun 2020 18:08:06 -0400 Subject: [PATCH 1/7] Supports Spring Cloud Vault Adds support for the following authentication types * APPROLE * CERT * CUBBYHOLE * TOKEN See https://cloud.spring.io/spring-cloud-vault/reference/html/#vault.config.authentication for more details about these authentication methods. Signed-off-by: Emily Casey --- README.md | 40 +++++ .../VaultBindingsPropertiesProcessor.java | 73 ++++++++ src/main/resources/META-INF/spring.factories | 1 + ...gSpecificEnvironmentPostProcessorTest.java | 2 +- .../boot/VaultPropertiesProcessorTest.java | 163 ++++++++++++++++++ 5 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java create mode 100644 src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java diff --git a/README.md b/README.md index 1dfd0f1..1863f30 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,46 @@ 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.uri` | `{secret/uri}` +| `spring.cloud.vault.namespace` | `{secret/namespace}` + +Provider: `approle` +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.authentication` | `APPROLE` +| `spring.cloud.vault.app-role.role-id` | `{secret/role-id}` +| `spring.cloud.vault.app-role.secret-id` | `{secret/secret-id}` +| `spring.cloud.vault.app-role.role` | `{secret/role}` +| `spring.cloud.vault.app-role.app-role-path` | `{secret/app-role-path}` + +Provider: `cert` +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.authentication` | `CERT` +| `spring.cloud.vault.ssl.key-store` | `${CNB_BINDINGS}/{name}/secret/keystore.jks` +| `spring.cloud.vault.ssl.key-store-password` | `{secret/key-store-password}` +| `spring.cloud.vault.ssl.cert-auth-path` | `{secret/cert-auth-path}` + +Provider: `cubbyhole` +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.authentication` | `CUBBYHOLE` +| `spring.cloud.vault.token` | `{secret/token}` + +Provider: `token` +| Property | Value +| -------- | ------------------ +| `spring.cloud.vault.authentication` | `TOKEN` +| `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..6a25318 --- /dev/null +++ b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java @@ -0,0 +1,73 @@ +/* + * 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.cloud.bindings.Binding; +import org.springframework.cloud.bindings.Bindings; +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 { + + /** + * The {@link Binding} kind that this processor is interested in: {@value}. + **/ + public static final String KIND = "Vault"; + + @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 provider = binding.getProvider(); + if (provider == null) { + return; + } + String authentication = provider.toUpperCase(); + properties.put("spring.cloud.vault.authentication", authentication); + switch (authentication) { + 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 "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"); + } + }); + } + +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories index 0f3c20f..ee9aa7b 100644 --- a/src/main/resources/META-INF/spring.factories +++ b/src/main/resources/META-INF/spring.factories @@ -23,4 +23,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..e957acf --- /dev/null +++ b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java @@ -0,0 +1,163 @@ +/* + * 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.Collections; +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("uri", "test-uri") + .withEntry("namespace", "test-namespace"); + } + + private FluentMap baseMetadata() { + return new FluentMap() + .withEntry("kind", KIND); + } + + private final Binding tokenBinding = new Binding( + "test-name", Paths.get("test-path"), + baseMetadata().withEntry("provider", "token"), + baseSecret().withEntry("token", "test-token") + ); + + private final Binding appRoleBinding = new Binding( + "test-name", Paths.get("test-path"), + baseMetadata().withEntry("provider", "approle"), + baseSecret() + .withEntry("role-id", "test-role-id") + .withEntry("secret-id", "test-secret-id") + .withEntry("role", "test-role") + .withEntry("app-role-path", "test-app-role-path") + ); + + private final Binding cubbyholeBinding = new Binding( + "test-name", Paths.get("test-path"), + baseMetadata().withEntry("provider", "cubbyhole"), + baseSecret().withEntry("token", "test-token") + ); + + private final Binding certBinding = new Binding( + "test-name", Paths.get("test-path"), + baseMetadata().withEntry("provider", "cert"), + baseSecret() + .withEntry("keystore.jks", "key store contents!") + .withEntry("key-store-password", "test-key-store-password") + .withEntry("cert-auth-path", "test-cert-auth-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("Handles missing provider") + void testMissingProvider() { + new VaultBindingsPropertiesProcessor().process(environment, new Bindings(new Binding( + "test-name", + Paths.get("test-path"), + baseMetadata(), + 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(); + } + +} From 5be10f27020ae722bf72cdce63d3fe6ab4eaa75f Mon Sep 17 00:00:00 2001 From: Emily Casey Date: Tue, 23 Jun 2020 10:44:29 -0400 Subject: [PATCH 2/7] Reads authentication method from secret/method Previously we used provider Signed-off-by: Emily Casey --- README.md | 8 +++--- .../VaultBindingsPropertiesProcessor.java | 10 ++++---- .../boot/VaultPropertiesProcessorTest.java | 25 +++++++++++-------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 1863f30..1973994 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ Any Provider: | `spring.cloud.vault.uri` | `{secret/uri}` | `spring.cloud.vault.namespace` | `{secret/namespace}` -Provider: `approle` +If `{secret/method}` is equal to `approle`: | Property | Value | -------- | ------------------ | `spring.cloud.vault.authentication` | `APPROLE` @@ -287,7 +287,7 @@ Provider: `approle` | `spring.cloud.vault.app-role.role` | `{secret/role}` | `spring.cloud.vault.app-role.app-role-path` | `{secret/app-role-path}` -Provider: `cert` +If `{secret/method}` is equal to `cert`: | Property | Value | -------- | ------------------ | `spring.cloud.vault.authentication` | `CERT` @@ -295,13 +295,13 @@ Provider: `cert` | `spring.cloud.vault.ssl.key-store-password` | `{secret/key-store-password}` | `spring.cloud.vault.ssl.cert-auth-path` | `{secret/cert-auth-path}` -Provider: `cubbyhole` +If `{secret/method}` is equal to `cubbyhole`: | Property | Value | -------- | ------------------ | `spring.cloud.vault.authentication` | `CUBBYHOLE` | `spring.cloud.vault.token` | `{secret/token}` -Provider: `token` +If `{secret/method}` is equal to `token`: | Property | Value | -------- | ------------------ | `spring.cloud.vault.authentication` | `TOKEN` diff --git a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java index 6a25318..bf94e17 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java @@ -45,13 +45,13 @@ public final class VaultBindingsPropertiesProcessor implements BindingsPropertie map.from("uri").to("spring.cloud.vault.uri"); map.from("namespace").to("spring.cloud.vault.namespace"); // vault enterprise feature - String provider = binding.getProvider(); - if (provider == null) { + String method = binding.getSecret().get("method"); + if (method == null) { return; } - String authentication = provider.toUpperCase(); - properties.put("spring.cloud.vault.authentication", authentication); - switch (authentication) { + String authenticationMethod = method.toUpperCase(); + properties.put("spring.cloud.vault.authentication", authenticationMethod); + switch (authenticationMethod) { case "TOKEN": case "CUBBYHOLE": map.from("token").to("spring.cloud.vault.token"); diff --git a/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java index e957acf..9b80426 100644 --- a/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java +++ b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java @@ -24,7 +24,6 @@ import org.springframework.cloud.bindings.FluentMap; import org.springframework.mock.env.MockEnvironment; import java.nio.file.Paths; -import java.util.Collections; import java.util.HashMap; import static org.assertj.core.api.Assertions.assertThat; @@ -39,21 +38,24 @@ final class VaultPropertiesProcessorTest { .withEntry("namespace", "test-namespace"); } - private FluentMap baseMetadata() { + private FluentMap metadata() { return new FluentMap() .withEntry("kind", KIND); } private final Binding tokenBinding = new Binding( "test-name", Paths.get("test-path"), - baseMetadata().withEntry("provider", "token"), - baseSecret().withEntry("token", "test-token") + metadata(), + baseSecret() + .withEntry("method", "token") + .withEntry("token", "test-token") ); private final Binding appRoleBinding = new Binding( "test-name", Paths.get("test-path"), - baseMetadata().withEntry("provider", "approle"), + metadata(), baseSecret() + .withEntry("method", "approle") .withEntry("role-id", "test-role-id") .withEntry("secret-id", "test-secret-id") .withEntry("role", "test-role") @@ -62,14 +64,17 @@ final class VaultPropertiesProcessorTest { private final Binding cubbyholeBinding = new Binding( "test-name", Paths.get("test-path"), - baseMetadata().withEntry("provider", "cubbyhole"), - baseSecret().withEntry("token", "test-token") + metadata(), + baseSecret() + .withEntry("method", "cubbyhole") + .withEntry("token", "test-token") ); private final Binding certBinding = new Binding( "test-name", Paths.get("test-path"), - baseMetadata().withEntry("provider", "cert"), + metadata(), baseSecret() + .withEntry("method", "cert") .withEntry("keystore.jks", "key store contents!") .withEntry("key-store-password", "test-key-store-password") .withEntry("cert-auth-path", "test-cert-auth-path") @@ -129,12 +134,12 @@ final class VaultPropertiesProcessorTest { } @Test - @DisplayName("Handles missing provider") + @DisplayName("Doesn't fail when method is missing") void testMissingProvider() { new VaultBindingsPropertiesProcessor().process(environment, new Bindings(new Binding( "test-name", Paths.get("test-path"), - baseMetadata(), + metadata(), baseSecret() )), properties); assertThat(properties) From b3b1b06aeaf5fd311b4bdc926047d4d632aabdba Mon Sep 17 00:00:00 2001 From: Emily Casey Date: Tue, 23 Jun 2020 17:45:18 -0400 Subject: [PATCH 3/7] Adds more Spring Cloud Vault authentication methods * AWS_EC2 * AWS_IAM * AZURE_MSI * GCP_GCE * GCP_IAM * KUBERNETS Signed-off-by: Emily Casey --- README.md | 65 ++++++-- .../VaultBindingsPropertiesProcessor.java | 37 +++++ .../boot/VaultPropertiesProcessorTest.java | 146 ++++++++++++++++++ 3 files changed, 238 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1973994..26e40a6 100644 --- a/README.md +++ b/README.md @@ -275,38 +275,83 @@ Disable Property: `org.springframework.cloud.bindings.boot.vault.enable` Any Provider: | Property | Value | -------- | ------------------ -| `spring.cloud.vault.uri` | `{secret/uri}` +| `spring.cloud.vault.authentication` | `{secret/method}` | `spring.cloud.vault.namespace` | `{secret/namespace}` +| `spring.cloud.vault.uri` | `{secret/uri}` If `{secret/method}` is equal to `approle`: | Property | Value | -------- | ------------------ -| `spring.cloud.vault.authentication` | `APPROLE` -| `spring.cloud.vault.app-role.role-id` | `{secret/role-id}` -| `spring.cloud.vault.app-role.secret-id` | `{secret/secret-id}` -| `spring.cloud.vault.app-role.role` | `{secret/role}` | `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/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-identity-document}` +| `spring.cloud.vault.aws-ec2.nonce` | `{secret/nonce}` +| `spring.cloud.vault.aws-ec2.role` | `{secret/role}` + +If `{secret/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/endpoint-uri}` +| `spring.cloud.vault.aws-iam.role` | `{secret/token}` +| `spring.cloud.vault.aws-iam.server-id` | `{secret/server-id}` + +If `{secret/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/method}` is equal to `cert`: | Property | Value | -------- | ------------------ -| `spring.cloud.vault.authentication` | `CERT` -| `spring.cloud.vault.ssl.key-store` | `${CNB_BINDINGS}/{name}/secret/keystore.jks` -| `spring.cloud.vault.ssl.key-store-password` | `{secret/key-store-password}` | `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/method}` is equal to `cubbyhole`: | Property | Value | -------- | ------------------ -| `spring.cloud.vault.authentication` | `CUBBYHOLE` | `spring.cloud.vault.token` | `{secret/token}` +If `{secret/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/service-account}` + + +If `{secret/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/project-id}` +| `spring.cloud.vault.gcp-iam.role` | `{secret/role}` +| `spring.cloud.vault.gcp-iam.service-account-id` | `{secret/service-account-id}` + +If `{secret/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/method}` is equal to `token`: | Property | Value | -------- | ------------------ -| `spring.cloud.vault.authentication` | `TOKEN` | `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 index bf94e17..11032a6 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java @@ -62,10 +62,47 @@ public final class VaultBindingsPropertiesProcessor implements BindingsPropertie 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("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("server-id").to("spring.cloud.vault.aws-iam.server-id"); + map.from("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("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("project-id").to("spring.cloud.vault.gcp-iam.project-id"); + map.from("service-account-id").to("spring.cloud.vault.gcp-iam.service-account-id"); + break; + case "KUBERNETES": + map.from("role").to("spring.cloud.vault.kubernetes.role"); + map.from("kubernetes-path").to("spring.cloud.vault.kubernetes.kubernetes-path"); + break; } }); } diff --git a/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java index 9b80426..6318873 100644 --- a/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java +++ b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java @@ -80,6 +80,70 @@ final class VaultPropertiesProcessorTest { .withEntry("cert-auth-path", "test-cert-auth-path") ); + private final Binding awsEc2Binding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("method", "aws_ec2") + .withEntry("role", "test-role") + .withEntry("aws-ec2-path", "test-aws-ec2-path") + .withEntry("identity-document", "test-identity-document") + .withEntry("nonce", "test-nonce") + ); + + private final Binding awsIamBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("method", "aws_iam") + .withEntry("role", "test-role") + .withEntry("aws-path", "test-aws-path") + .withEntry("server-id", "test-server-id") + .withEntry("endpoint-uri", "test-endpoint-uri") + ); + + private final Binding azureMsiBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("method", "azure_msi") + .withEntry("role", "test-role") + .withEntry("azure-path", "test-azure-path") + ); + + private final Binding gcpGceBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("method", "gcp_gce") + .withEntry("role", "test-role") + .withEntry("gcp-path", "test-gcp-path") + .withEntry("service-account", "test-service-account") + ); + + private final Binding gcpIamBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("method", "gcp_iam") + .withEntry("credentials.json", "credentials JSON contents!") + .withEntry("encoded-key", "test-encoded-key") + .withEntry("gcp-path", "test-gcp-path") + .withEntry("jwt-validity", "test-jwt-validity") + .withEntry("project-id", "test-project-id") + .withEntry("role", "test-role") + .withEntry("service-account-id", "test-service-account-id") + ); + + private final Binding k8sBinding = new Binding( + "test-name", Paths.get("test-path"), + metadata(), + baseSecret() + .withEntry("method", "kubernetes") + .withEntry("role", "test-role") + .withEntry("kubernetes-path", "test-kubernetes-path") + ); + private final MockEnvironment environment = new MockEnvironment(); private final HashMap properties = new HashMap<>(); @@ -133,6 +197,88 @@ final class VaultPropertiesProcessorTest { .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-id", "test-service-account-id"); + } + + @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() { From 99574f365328af2460f721a129a87a9d94a87592 Mon Sep 17 00:00:00 2001 From: Emily Casey Date: Tue, 23 Jun 2020 17:52:51 -0400 Subject: [PATCH 4/7] pass-through exact vault authentication method case instead of standardizing case Signed-off-by: Emily Casey --- .../VaultBindingsPropertiesProcessor.java | 7 +++---- .../boot/VaultPropertiesProcessorTest.java | 20 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java index 11032a6..63d0393 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java @@ -45,13 +45,12 @@ public final class VaultBindingsPropertiesProcessor implements BindingsPropertie map.from("uri").to("spring.cloud.vault.uri"); map.from("namespace").to("spring.cloud.vault.namespace"); // vault enterprise feature - String method = binding.getSecret().get("method"); - if (method == null) { + String authenticationMethod = binding.getSecret().get("method"); + if (authenticationMethod == null) { return; } - String authenticationMethod = method.toUpperCase(); properties.put("spring.cloud.vault.authentication", authenticationMethod); - switch (authenticationMethod) { + switch (authenticationMethod.toUpperCase()) { case "TOKEN": case "CUBBYHOLE": map.from("token").to("spring.cloud.vault.token"); diff --git a/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java index 6318873..16d0553 100644 --- a/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java +++ b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java @@ -155,7 +155,7 @@ final class VaultPropertiesProcessorTest { 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.authentication", "token") .containsEntry("spring.cloud.vault.token", "test-token"); } @@ -166,7 +166,7 @@ final class VaultPropertiesProcessorTest { 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.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") @@ -180,7 +180,7 @@ final class VaultPropertiesProcessorTest { 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.authentication", "cubbyhole") .containsEntry("spring.cloud.vault.token", "test-token"); } @@ -191,7 +191,7 @@ final class VaultPropertiesProcessorTest { 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.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"); @@ -204,7 +204,7 @@ final class VaultPropertiesProcessorTest { 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.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") @@ -218,7 +218,7 @@ final class VaultPropertiesProcessorTest { 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.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") @@ -232,7 +232,7 @@ final class VaultPropertiesProcessorTest { 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.authentication", "azure_msi") .containsEntry("spring.cloud.vault.azure-msi.role", "test-role") .containsEntry("spring.cloud.vault.azure-msi.azure-path", "test-azure-path"); } @@ -244,7 +244,7 @@ final class VaultPropertiesProcessorTest { 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.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"); @@ -257,7 +257,7 @@ final class VaultPropertiesProcessorTest { 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.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") @@ -274,7 +274,7 @@ final class VaultPropertiesProcessorTest { 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.authentication", "kubernetes") .containsEntry("spring.cloud.vault.kubernetes.role", "test-role") .containsEntry("spring.cloud.vault.kubernetes.kubernetes-path", "test-kubernetes-path"); } From 663c8095c24e6f5988fa8d3898a199aed2b8a173 Mon Sep 17 00:00:00 2001 From: Emily Casey Date: Tue, 23 Jun 2020 19:04:43 -0400 Subject: [PATCH 5/7] Warns when vault binding has missing or unrecognized method Signed-off-by: Emily Casey --- .../boot/VaultBindingsPropertiesProcessor.java | 14 +++++++++++++- src/main/resources/META-INF/spring.factories | 3 ++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java index 63d0393..388440b 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java @@ -16,8 +16,11 @@ 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; @@ -27,13 +30,15 @@ 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 { +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)) { @@ -47,6 +52,7 @@ public final class VaultBindingsPropertiesProcessor implements BindingsPropertie String authenticationMethod = binding.getSecret().get("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); @@ -102,8 +108,14 @@ public final class VaultBindingsPropertiesProcessor implements BindingsPropertie 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 ee9aa7b..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 From 10ace42f07c91f6c113b7bd3b19916cef9e1cc18 Mon Sep 17 00:00:00 2001 From: Emily Casey Date: Tue, 23 Jun 2020 19:09:55 -0400 Subject: [PATCH 6/7] vault: secret/method -> secret/authentication-method Signed-off-by: Emily Casey --- README.md | 22 +++++++++---------- .../VaultBindingsPropertiesProcessor.java | 2 +- .../boot/VaultPropertiesProcessorTest.java | 20 ++++++++--------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 26e40a6..fba6aa8 100644 --- a/README.md +++ b/README.md @@ -275,11 +275,11 @@ Disable Property: `org.springframework.cloud.bindings.boot.vault.enable` Any Provider: | Property | Value | -------- | ------------------ -| `spring.cloud.vault.authentication` | `{secret/method}` +| `spring.cloud.vault.authentication` | `{secret/authentication-method}` | `spring.cloud.vault.namespace` | `{secret/namespace}` | `spring.cloud.vault.uri` | `{secret/uri}` -If `{secret/method}` is equal to `approle`: +If `{secret/authentication-method}` is equal to `approle`: | Property | Value | -------- | ------------------ | `spring.cloud.vault.app-role.app-role-path` | `{secret/app-role-path}` @@ -287,7 +287,7 @@ If `{secret/method}` is equal to `approle`: | `spring.cloud.vault.app-role.role` | `{secret/role}` | `spring.cloud.vault.app-role.secret-id` | `{secret/secret-id}` -If `{secret/method}` is equal to `aws_ec2`: +If `{secret/authentication-method}` is equal to `aws_ec2`: | Property | Value | -------- | ------------------ | `spring.cloud.vault.aws-ec2.aws-ec2-path` | `{secret/aws-ec2-path}` @@ -295,7 +295,7 @@ If `{secret/method}` is equal to `aws_ec2`: | `spring.cloud.vault.aws-ec2.nonce` | `{secret/nonce}` | `spring.cloud.vault.aws-ec2.role` | `{secret/role}` -If `{secret/method}` is equal to `aws_iam`: +If `{secret/authentication-method}` is equal to `aws_iam`: | Property | Value | -------- | ------------------ | `spring.cloud.vault.aws-iam.aws-path` | `{secret/aws-path}` @@ -303,25 +303,25 @@ If `{secret/method}` is equal to `aws_iam`: | `spring.cloud.vault.aws-iam.role` | `{secret/token}` | `spring.cloud.vault.aws-iam.server-id` | `{secret/server-id}` -If `{secret/method}` is equal to `azure_msi`: +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/method}` is equal to `cert`: +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/method}` is equal to `cubbyhole`: +If `{secret/authentication-method}` is equal to `cubbyhole`: | Property | Value | -------- | ------------------ | `spring.cloud.vault.token` | `{secret/token}` -If `{secret/method}` is equal to `gcp_gce`: +If `{secret/authentication-method}` is equal to `gcp_gce`: | Property | Value | -------- | ------------------ | `spring.cloud.vault.gcp-gce.gcp-path` | `{secret/gcp-path}` @@ -329,7 +329,7 @@ If `{secret/method}` is equal to `gcp_gce`: | `spring.cloud.vault.gcp-gce.service-account` | `{secret/service-account}` -If `{secret/method}` is equal to `gcp_iam`: +If `{secret/authentication-method}` is equal to `gcp_iam`: | Property | Value | -------- | ------------------ | `spring.cloud.vault.gcp-iam.credentials.encoded-key` | `{secret/encoded-key}` @@ -340,13 +340,13 @@ If `{secret/method}` is equal to `gcp_iam`: | `spring.cloud.vault.gcp-iam.role` | `{secret/role}` | `spring.cloud.vault.gcp-iam.service-account-id` | `{secret/service-account-id}` -If `{secret/method}` is equal to `kubernetes`: +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/method}` is equal to `token`: +If `{secret/authentication-method}` is equal to `token`: | Property | Value | -------- | ------------------ | `spring.cloud.vault.token` | `{secret/token}` diff --git a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java index 388440b..fdca0d8 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java @@ -50,7 +50,7 @@ public final class VaultBindingsPropertiesProcessor implements BindingsPropertie map.from("uri").to("spring.cloud.vault.uri"); map.from("namespace").to("spring.cloud.vault.namespace"); // vault enterprise feature - String authenticationMethod = binding.getSecret().get("method"); + 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; diff --git a/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java index 16d0553..17bc5c0 100644 --- a/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java +++ b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java @@ -47,7 +47,7 @@ final class VaultPropertiesProcessorTest { "test-name", Paths.get("test-path"), metadata(), baseSecret() - .withEntry("method", "token") + .withEntry("authentication-method", "token") .withEntry("token", "test-token") ); @@ -55,7 +55,7 @@ final class VaultPropertiesProcessorTest { "test-name", Paths.get("test-path"), metadata(), baseSecret() - .withEntry("method", "approle") + .withEntry("authentication-method", "approle") .withEntry("role-id", "test-role-id") .withEntry("secret-id", "test-secret-id") .withEntry("role", "test-role") @@ -66,7 +66,7 @@ final class VaultPropertiesProcessorTest { "test-name", Paths.get("test-path"), metadata(), baseSecret() - .withEntry("method", "cubbyhole") + .withEntry("authentication-method", "cubbyhole") .withEntry("token", "test-token") ); @@ -74,7 +74,7 @@ final class VaultPropertiesProcessorTest { "test-name", Paths.get("test-path"), metadata(), baseSecret() - .withEntry("method", "cert") + .withEntry("authentication-method", "cert") .withEntry("keystore.jks", "key store contents!") .withEntry("key-store-password", "test-key-store-password") .withEntry("cert-auth-path", "test-cert-auth-path") @@ -84,7 +84,7 @@ final class VaultPropertiesProcessorTest { "test-name", Paths.get("test-path"), metadata(), baseSecret() - .withEntry("method", "aws_ec2") + .withEntry("authentication-method", "aws_ec2") .withEntry("role", "test-role") .withEntry("aws-ec2-path", "test-aws-ec2-path") .withEntry("identity-document", "test-identity-document") @@ -95,7 +95,7 @@ final class VaultPropertiesProcessorTest { "test-name", Paths.get("test-path"), metadata(), baseSecret() - .withEntry("method", "aws_iam") + .withEntry("authentication-method", "aws_iam") .withEntry("role", "test-role") .withEntry("aws-path", "test-aws-path") .withEntry("server-id", "test-server-id") @@ -106,7 +106,7 @@ final class VaultPropertiesProcessorTest { "test-name", Paths.get("test-path"), metadata(), baseSecret() - .withEntry("method", "azure_msi") + .withEntry("authentication-method", "azure_msi") .withEntry("role", "test-role") .withEntry("azure-path", "test-azure-path") ); @@ -115,7 +115,7 @@ final class VaultPropertiesProcessorTest { "test-name", Paths.get("test-path"), metadata(), baseSecret() - .withEntry("method", "gcp_gce") + .withEntry("authentication-method", "gcp_gce") .withEntry("role", "test-role") .withEntry("gcp-path", "test-gcp-path") .withEntry("service-account", "test-service-account") @@ -125,7 +125,7 @@ final class VaultPropertiesProcessorTest { "test-name", Paths.get("test-path"), metadata(), baseSecret() - .withEntry("method", "gcp_iam") + .withEntry("authentication-method", "gcp_iam") .withEntry("credentials.json", "credentials JSON contents!") .withEntry("encoded-key", "test-encoded-key") .withEntry("gcp-path", "test-gcp-path") @@ -139,7 +139,7 @@ final class VaultPropertiesProcessorTest { "test-name", Paths.get("test-path"), metadata(), baseSecret() - .withEntry("method", "kubernetes") + .withEntry("authentication-method", "kubernetes") .withEntry("role", "test-role") .withEntry("kubernetes-path", "test-kubernetes-path") ); From e4ed9a3a766a32071cf9bb37004c2d19d7e022ec Mon Sep 17 00:00:00 2001 From: Emily Casey Date: Thu, 25 Jun 2020 16:06:04 -0400 Subject: [PATCH 7/7] Adds more context to ambiguous secret key names When key names are potentially ambiguous (does this server-id refer to the vault server?) prefer longer more explicit key names. When there is no ambiguity stick with the existing shorter names. Signed-off-by: Emily Casey --- README.md | 14 ++++---- .../VaultBindingsPropertiesProcessor.java | 12 +++---- .../boot/VaultPropertiesProcessorTest.java | 36 +++++++++---------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index fba6aa8..bb86112 100644 --- a/README.md +++ b/README.md @@ -291,7 +291,7 @@ 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-identity-document}` +| `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}` @@ -299,9 +299,9 @@ 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/endpoint-uri}` -| `spring.cloud.vault.aws-iam.role` | `{secret/token}` -| `spring.cloud.vault.aws-iam.server-id` | `{secret/server-id}` +| `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 @@ -326,7 +326,7 @@ If `{secret/authentication-method}` is equal to `gcp_gce`: | -------- | ------------------ | `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/service-account}` +| `spring.cloud.vault.gcp-gce.service-account` | `{secret/gcp-service-account}` If `{secret/authentication-method}` is equal to `gcp_iam`: @@ -336,9 +336,9 @@ If `{secret/authentication-method}` is equal to `gcp_iam`: | `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/project-id}` +| `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-id` | `{secret/service-account-id}` +| `spring.cloud.vault.gcp-iam.service-account` | `{secret/gcp-service-account}` If `{secret/authentication-method}` is equal to `kubernetes`: | Property | Value diff --git a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java index fdca0d8..88b99b3 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/VaultBindingsPropertiesProcessor.java @@ -70,14 +70,14 @@ public final class VaultBindingsPropertiesProcessor implements BindingsPropertie 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("identity-document").to("spring.cloud.vault.aws-ec2.identity-document"); + 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("server-id").to("spring.cloud.vault.aws-iam.server-id"); - map.from("endpoint-uri").to("spring.cloud.vault.aws-iam.endpoint-uri"); + 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"); @@ -91,7 +91,7 @@ public final class VaultBindingsPropertiesProcessor implements BindingsPropertie 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("service-account").to("spring.cloud.vault.gcp-gce.service-account"); + map.from("gcp-service-account").to("spring.cloud.vault.gcp-gce.service-account"); break; case "GCP_IAM": if (binding.getSecret().containsKey("credentials.json")) { @@ -101,8 +101,8 @@ public final class VaultBindingsPropertiesProcessor implements BindingsPropertie 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("project-id").to("spring.cloud.vault.gcp-iam.project-id"); - map.from("service-account-id").to("spring.cloud.vault.gcp-iam.service-account-id"); + 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"); diff --git a/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java index 17bc5c0..f5c9de3 100644 --- a/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java +++ b/src/test/java/org/springframework/cloud/bindings/boot/VaultPropertiesProcessorTest.java @@ -34,8 +34,8 @@ final class VaultPropertiesProcessorTest { private FluentMap baseSecret() { return new FluentMap() - .withEntry("uri", "test-uri") - .withEntry("namespace", "test-namespace"); + .withEntry("namespace", "test-namespace") + .withEntry("uri", "test-uri"); } private FluentMap metadata() { @@ -55,11 +55,11 @@ final class VaultPropertiesProcessorTest { "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") - .withEntry("role", "test-role") - .withEntry("app-role-path", "test-app-role-path") ); private final Binding cubbyholeBinding = new Binding( @@ -75,9 +75,9 @@ final class VaultPropertiesProcessorTest { metadata(), baseSecret() .withEntry("authentication-method", "cert") - .withEntry("keystore.jks", "key store contents!") - .withEntry("key-store-password", "test-key-store-password") .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( @@ -85,10 +85,10 @@ final class VaultPropertiesProcessorTest { metadata(), baseSecret() .withEntry("authentication-method", "aws_ec2") - .withEntry("role", "test-role") - .withEntry("aws-ec2-path", "test-aws-ec2-path") - .withEntry("identity-document", "test-identity-document") + .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( @@ -96,10 +96,10 @@ final class VaultPropertiesProcessorTest { metadata(), baseSecret() .withEntry("authentication-method", "aws_iam") - .withEntry("role", "test-role") + .withEntry("aws-iam-server-id", "test-server-id") .withEntry("aws-path", "test-aws-path") - .withEntry("server-id", "test-server-id") - .withEntry("endpoint-uri", "test-endpoint-uri") + .withEntry("aws-sts-endpoint-uri", "test-endpoint-uri") + .withEntry("role", "test-role") ); private final Binding azureMsiBinding = new Binding( @@ -107,8 +107,8 @@ final class VaultPropertiesProcessorTest { metadata(), baseSecret() .withEntry("authentication-method", "azure_msi") - .withEntry("role", "test-role") .withEntry("azure-path", "test-azure-path") + .withEntry("role", "test-role") ); private final Binding gcpGceBinding = new Binding( @@ -116,9 +116,9 @@ final class VaultPropertiesProcessorTest { metadata(), baseSecret() .withEntry("authentication-method", "gcp_gce") - .withEntry("role", "test-role") .withEntry("gcp-path", "test-gcp-path") - .withEntry("service-account", "test-service-account") + .withEntry("gcp-service-account", "test-service-account") + .withEntry("role", "test-role") ); private final Binding gcpIamBinding = new Binding( @@ -129,10 +129,10 @@ final class VaultPropertiesProcessorTest { .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("project-id", "test-project-id") .withEntry("role", "test-role") - .withEntry("service-account-id", "test-service-account-id") ); private final Binding k8sBinding = new Binding( @@ -264,7 +264,7 @@ final class VaultPropertiesProcessorTest { .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-id", "test-service-account-id"); + .containsEntry("spring.cloud.vault.gcp-iam.service-account", "test-service-account"); } @Test