[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 <psevestre@gmail.com>
This commit is contained in:
psevestre 2023-09-14 18:31:24 -03:00 committed by GitHub
parent 8c400720c0
commit 12ba505cb4
10 changed files with 308 additions and 1 deletions

View File

@ -51,10 +51,36 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>auth</artifactId>
<version>2.20.140</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
<version>3.1.3</version>
</dependency>
</dependencies>
<properties>
<spring.vault.core.version>3.0.2</spring.vault.core.version>
<spring.vault.core.version>2.3.4</spring.vault.core.version>
<java.version>17</java.version>
</properties>

View File

@ -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);
};
}
}

View File

@ -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 {
}

View File

@ -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<String> 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());
});
});
};
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"));
}
}

View File

@ -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