From b3b1b06aeaf5fd311b4bdc926047d4d632aabdba Mon Sep 17 00:00:00 2001 From: Emily Casey Date: Tue, 23 Jun 2020 17:45:18 -0400 Subject: [PATCH] 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() {