From 12ba505cb423caf7f28c31cb0d0728d59ae654ed Mon Sep 17 00:00:00 2001 From: psevestre Date: Thu, 14 Sep 2023 18:31:24 -0300 Subject: [PATCH] [BAEL-6694] Secure Kubernetes Secrets with Vault (#14762) * [BAEL-4849] Article code * [BAEL-4968] Article code * [BAEL-4968] Article code * [BAEL-4968] Article code * [BAEL-4968] Remove extra comments * [BAEL-5258] Article Code * [BAEL-2765] PKCE Support for Secret Clients * [BAEL-5698] Article code * [BAEL-5698] Article code * [BAEL-5905] Initial code * [BAEL-5905] Article code * [BAEL-5905] Relocate article code to new module * [BAEL-6275] PostgreSQL NOTIFY/LISTEN * [BAEL-6275] Minor correction * BAEL-6138 * [BAEL-6138] WIP - LiveTest * [BAEL-6138] Tutorial Code * [BAEL-6138] Tutorial Code * [BAEL-6694] Article Code --------- Co-authored-by: Philippe Sevestre --- spring-vault/pom.xml | 28 ++++++++- .../SpringCloudVaultTestApplication.java | 25 ++++++++ .../baeldung/springvaultk8s/VaultConfig.java | 25 ++++++++ .../springvaultk8s/VaultK8SApplication.java | 45 ++++++++++++++ .../src/main/k8s/vault-csi-example.yaml | 51 ++++++++++++++++ .../src/main/k8s/vault-injector-example.yaml | 32 ++++++++++ .../src/main/k8s/vault-operator-example.yaml | 58 +++++++++++++++++++ .../resources/vault-config-k8s.properties | 4 ++ .../SpringCloudVaultApplicationLiveTest.java | 26 +++++++++ .../resources/application-vault.properties | 15 +++++ 10 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 spring-vault/src/main/java/com/baeldung/springcloudvault/SpringCloudVaultTestApplication.java create mode 100644 spring-vault/src/main/java/com/baeldung/springvaultk8s/VaultConfig.java create mode 100644 spring-vault/src/main/java/com/baeldung/springvaultk8s/VaultK8SApplication.java create mode 100644 spring-vault/src/main/k8s/vault-csi-example.yaml create mode 100644 spring-vault/src/main/k8s/vault-injector-example.yaml create mode 100644 spring-vault/src/main/k8s/vault-operator-example.yaml create mode 100644 spring-vault/src/main/resources/vault-config-k8s.properties create mode 100644 spring-vault/src/test/java/com/baeldung/springcloudvault/SpringCloudVaultApplicationLiveTest.java create mode 100644 spring-vault/src/test/resources/application-vault.properties diff --git a/spring-vault/pom.xml b/spring-vault/pom.xml index 7b89db2302..60a5ee18f2 100644 --- a/spring-vault/pom.xml +++ b/spring-vault/pom.xml @@ -51,10 +51,36 @@ spring-boot-starter-test test + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-core + + + + org.slf4j + slf4j-api + + + + software.amazon.awssdk + auth + 2.20.140 + + + + org.springframework.cloud + spring-cloud-starter-vault-config + 3.1.3 + - 3.0.2 + 2.3.4 17 diff --git a/spring-vault/src/main/java/com/baeldung/springcloudvault/SpringCloudVaultTestApplication.java b/spring-vault/src/main/java/com/baeldung/springcloudvault/SpringCloudVaultTestApplication.java new file mode 100644 index 0000000000..cea416005d --- /dev/null +++ b/spring-vault/src/main/java/com/baeldung/springcloudvault/SpringCloudVaultTestApplication.java @@ -0,0 +1,25 @@ +package com.baeldung.springcloudvault; + + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.util.Assert; + +@SpringBootApplication +public class SpringCloudVaultTestApplication { + public static void main(String... args) { + SpringApplication.run(SpringCloudVaultTestApplication.class, args); + } + + @Bean + CommandLineRunner listSecrets(Environment env) { + return args -> { + var foo = env.getProperty("foo"); + Assert.notNull(foo, "foo must have a value"); + System.out.println("foo=" + foo); + }; + } +} diff --git a/spring-vault/src/main/java/com/baeldung/springvaultk8s/VaultConfig.java b/spring-vault/src/main/java/com/baeldung/springvaultk8s/VaultConfig.java new file mode 100644 index 0000000000..407e324a87 --- /dev/null +++ b/spring-vault/src/main/java/com/baeldung/springvaultk8s/VaultConfig.java @@ -0,0 +1,25 @@ +package com.baeldung.springvaultk8s; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.vault.authentication.ClientAuthentication; +import org.springframework.vault.authentication.KubernetesAuthentication; +import org.springframework.vault.authentication.KubernetesAuthenticationOptions; +import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.config.AbstractVaultConfiguration; +import org.springframework.vault.config.EnvironmentVaultConfiguration; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.net.URISyntaxException; + +@Configuration +@PropertySource("vault-config-k8s.properties") +@Import(EnvironmentVaultConfiguration.class) +public class VaultConfig { + +} diff --git a/spring-vault/src/main/java/com/baeldung/springvaultk8s/VaultK8SApplication.java b/spring-vault/src/main/java/com/baeldung/springvaultk8s/VaultK8SApplication.java new file mode 100644 index 0000000000..a61b67bf6e --- /dev/null +++ b/spring-vault/src/main/java/com/baeldung/springvaultk8s/VaultK8SApplication.java @@ -0,0 +1,45 @@ +package com.baeldung.springvaultk8s; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.vault.core.VaultKeyValueOperations; +import org.springframework.vault.core.VaultKeyValueOperationsSupport; +import org.springframework.vault.core.VaultTemplate; + +import java.util.List; +import java.util.Map; + +@SpringBootApplication +public class VaultK8SApplication { + + public static void main(String... args) { + SpringApplication.run(VaultK8SApplication.class, args); + } + + @Bean + CommandLineRunner listSecrets(VaultTemplate vault) { + + return args -> { + VaultKeyValueOperations ops = vault.opsForKeyValue("secrets", VaultKeyValueOperationsSupport.KeyValueBackend.KV_2); + List secrets = ops.list(""); + if (secrets == null) { + System.out.println("No secrets found"); + return; + } + + secrets.forEach(s -> { + System.out.println("secret=" + s); + var response = ops.get(s); + var data = response.getRequiredData(); + + data.entrySet() + .forEach(e -> { + System.out.println("- key=" + e.getKey() + " => " + e.getValue()); + }); + }); + }; + } +} diff --git a/spring-vault/src/main/k8s/vault-csi-example.yaml b/spring-vault/src/main/k8s/vault-csi-example.yaml new file mode 100644 index 0000000000..8136554033 --- /dev/null +++ b/spring-vault/src/main/k8s/vault-csi-example.yaml @@ -0,0 +1,51 @@ +--- +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: baeldung-csi-secrets + namespace: baeldung +spec: + # Vault CSI Provider + provider: vault + parameters: + # Vault role name to use during login + roleName: 'baeldung-test-role' + objects: | + - objectName: 'baeldung.properties' + secretPath: "secrets/data/baeldung-test" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-csi + namespace: baeldung +spec: + selector: + matchLabels: + app: nginx-csi + replicas: 1 # tells deployment to run 2 pods matching the template + template: + metadata: + labels: + app: nginx-csi + spec: + serviceAccountName: vault-test-sa + automountServiceAccountToken: true + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 + volumeMounts: + - name: vault-secrets + mountPath: /vault/secrets + readOnly: true + volumes: + - name: vault-secrets + csi: + driver: 'secrets-store.csi.k8s.io' + readOnly: true + volumeAttributes: + secretProviderClass: baeldung-csi-secrets + + diff --git a/spring-vault/src/main/k8s/vault-injector-example.yaml b/spring-vault/src/main/k8s/vault-injector-example.yaml new file mode 100644 index 0000000000..4d0aad70a1 --- /dev/null +++ b/spring-vault/src/main/k8s/vault-injector-example.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + namespace: baeldung +spec: + selector: + matchLabels: + app: nginx + replicas: 1 # tells deployment to run 2 pods matching the template + template: + metadata: + labels: + app: nginx + annotations: + vault.hashicorp.com/agent-inject: "true" + vault.hashicorp.com/agent-inject-secret-baeldung.properties: "secrets/baeldung-test" + vault.hashicorp.com/role: "baeldung-test-role" + vault.hashicorp.com/agent-inject-template-baeldung.properties: | + {{- with secret "secrets/baeldung-test" -}} + {{- range $k, $v := .Data.data }} + {{$k}}={{$v}} + {{- end -}} + {{ end }} + spec: + serviceAccountName: vault-test-sa + automountServiceAccountToken: true + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/spring-vault/src/main/k8s/vault-operator-example.yaml b/spring-vault/src/main/k8s/vault-operator-example.yaml new file mode 100644 index 0000000000..a77888905d --- /dev/null +++ b/spring-vault/src/main/k8s/vault-operator-example.yaml @@ -0,0 +1,58 @@ +--- +## +## Vault Connection +## +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultConnection +metadata: + namespace: baeldung + name: vault-local +spec: + # required configuration + # address to the Vault server. + address: http://vault.vault.svc.cluster.local:8200 +--- +## +## Vault Auth +## +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultAuth +metadata: + namespace: baeldung + name: baeldung-test +spec: + # required configuration + # VaultConnectionRef of the corresponding VaultConnection CustomResource. + # If no value is specified the Operator will default to the `default` VaultConnection, + # configured in its own Kubernetes namespace. + vaultConnectionRef: vault-local + # Method to use when authenticating to Vault. + method: kubernetes + # Mount to use when authenticating to auth method. + mount: kubernetes + # Kubernetes specific auth configuration, requires that the Method be set to kubernetes. + kubernetes: + # role to use when authenticating to Vault + role: baeldung-test-role + # ServiceAccount to use when authenticating to Vault + # it is recommended to always provide a unique serviceAccount per Pod/application + serviceAccount: vault-test-sa +--- +## +## Vault-backed secret +## +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultStaticSecret +metadata: + namespace: baeldung + name: baeldung-test +spec: + vaultAuthRef: baeldung-test + mount: secrets + type: kv-v2 + path: baeldung-test + refreshAfter: 60s + hmacSecretData: true + destination: + create: true + name: baeldung-test diff --git a/spring-vault/src/main/resources/vault-config-k8s.properties b/spring-vault/src/main/resources/vault-config-k8s.properties new file mode 100644 index 0000000000..276d735fcb --- /dev/null +++ b/spring-vault/src/main/resources/vault-config-k8s.properties @@ -0,0 +1,4 @@ +vault.uri=http://localhost:8200 +vault.authentication=KUBERNETES +vault.kubernetes.role=baeldung-test-role +vault.kubernetes.service-account-token-file=sa-token.txt \ No newline at end of file diff --git a/spring-vault/src/test/java/com/baeldung/springcloudvault/SpringCloudVaultApplicationLiveTest.java b/spring-vault/src/test/java/com/baeldung/springcloudvault/SpringCloudVaultApplicationLiveTest.java new file mode 100644 index 0000000000..b4ece472a6 --- /dev/null +++ b/spring-vault/src/test/java/com/baeldung/springcloudvault/SpringCloudVaultApplicationLiveTest.java @@ -0,0 +1,26 @@ +package com.baeldung.springcloudvault; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.env.Environment; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@ActiveProfiles("vault") +@SpringBootTest(classes = SpringCloudVaultTestApplication.class) +public class SpringCloudVaultApplicationLiveTest { + + @Autowired + Environment env; + + @Test + public void whenSpringContextIsBootstrapped_thenNoExceptions() { + assertNotNull(env.getProperty("foo")); + } + +} diff --git a/spring-vault/src/test/resources/application-vault.properties b/spring-vault/src/test/resources/application-vault.properties new file mode 100644 index 0000000000..7226a3a6e4 --- /dev/null +++ b/spring-vault/src/test/resources/application-vault.properties @@ -0,0 +1,15 @@ +# Vault Properties +spring.config.import: vault:// +spring.cloud.vault.uri=http://localhost:8200 +spring.cloud.vault.authentication=KUBERNETES +spring.cloud.vault.kubernetes.role=baeldung-test-role + +# +spring.cloud.vault.kv.backend=secrets +spring.cloud.vault.kv.application-name=baeldung-test +# + +# NOTICE: the following property is only necessary when running the application +# outside Kubernetes +# Please refer to the article for instructions on how to create this file +spring.cloud.vault.kubernetes.service-account-token-file=sa-token.txt \ No newline at end of file