[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:
parent
8c400720c0
commit
12ba505cb4
@ -51,10 +51,36 @@
|
|||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<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>
|
<java.version>17</java.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
@ -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());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
51
spring-vault/src/main/k8s/vault-csi-example.yaml
Normal file
51
spring-vault/src/main/k8s/vault-csi-example.yaml
Normal 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
|
||||||
|
|
||||||
|
|
32
spring-vault/src/main/k8s/vault-injector-example.yaml
Normal file
32
spring-vault/src/main/k8s/vault-injector-example.yaml
Normal 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
|
58
spring-vault/src/main/k8s/vault-operator-example.yaml
Normal file
58
spring-vault/src/main/k8s/vault-operator-example.yaml
Normal 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
|
@ -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
|
@ -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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
15
spring-vault/src/test/resources/application-vault.properties
Normal file
15
spring-vault/src/test/resources/application-vault.properties
Normal 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
|
Loading…
x
Reference in New Issue
Block a user