Posted on 

External Secrets Operator with vault provider

The core logic is

  1. install external-secrets operator
  2. install vault server
  3. set SecretStore connect to a existing vault server
  4. apply a incomplete Secret , some vaule with fetch from the vault system.
sequenceDiagram
    [Secert] ->>+ kubernetes: apply a `example-sync` secert to cluster
    external-secrets ->>+ kubernetes: watch all secert
    external-secrets ->>+ [ExternalSecret]: found the secert 
    [ExternalSecret] ->>- external-secrets: yes, this secert should maintain by operator
    external-secrets ->>+ [SecretStore]: confirm vault connection info
    external-secrets ->>+ valut: get the value by `remoteRef`
    valut ->>- external-secrets: return the vaule
    external-secrets ->>+ kubernetes: use vaule from vault to replace secert `example-sync`

install external-secrets

install external-secrets by helm.

The default install options will automatically install and manage the CRDs as part of your helm release. If you do not want the CRDs to be automatically upgraded and managed, you must set the installCRDs option to false. (e.g. --set installCRDs=false)

1
2
3
4
5
6
$ helm repo add external-secrets https://charts.external-secrets.io
$ helm install external-secrets \
external-secrets/external-secrets \
-n external-secrets \
--create-namespace \
# --set installCRDs=false

check the pod status

1
2
3
4
5
bash-3.2$ kubectl -n external-secrets get pod
NAME READY STATUS RESTARTS AGE
external-secrets-58c87697d7-hkpvg 1/1 Running 0 24h
external-secrets-cert-controller-846d855458-wwsh9 1/1 Running 0 24h
external-secrets-webhook-6d489f5bdb-ff62j 1/1 Running 0 24h

vault example

install vault with helm, the "server.dev.enabled=true" option means run a vault dev server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ helm repo add hashicorp https://helm.releases.hashicorp.com

$ helm install vault hashicorp/vault --set "server.dev.enabled=true"
NAME: vault
LAST DEPLOYED: Thu Aug 1 14:34:17 2024
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!

Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:

https://developer.hashicorp.com/vault/docs


Your release is named vault. To learn more about the release, try:

$ helm status vault
$ helm get manifest vault

set a key/value pair .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ kubectl exec -it vault-0 -c vault sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.

/ $ vault kv put secret/foo my-value=guohaotestpassword
= Secret Path =
secret/data/foo

======= Metadata =======
Key Value
--- -----
created_time 2024-08-01T06:59:58.205546636Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 2

user case of externalsecret

the SecretStore.yaml of content, this file (include SecretStore + Secret) help the externalsecret connect to vault server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "http://10.102.9.200:8200"
path: "secret"
# Version is the Vault KV secret engine version.
# This can be either "v1" or "v2", defaults to "v2"
version: "v2"
auth:
# points to a secret that contains a vault token
# https://www.vaultproject.io/docs/auth/token
tokenSecretRef:
name: "vault-token"
key: "token"
---
apiVersion: v1
kind: Secret
metadata:
name: vault-token
data:
token: cm9vdA== # "root"

apply the file to kubernetes, take a look the value of CAPABILITIES of vault-backend it should be ReadWrite. this status means the operator can get/put the vaule to vault server.

1
2
3
4
5
6
7
$ kubectl apply -f SecretStore.yaml
secretstore.external-secrets.io/vault-backend created
secret/vault-token created

$ kubectl get secretstores
NAME AGE STATUS CAPABILITIES READY
vault-backend 1s Valid ReadWrite True

install ExternalSecret.yaml. actually, this is a user case. the ExternalSecret help the cluster to render to secert file with true Secret vaule.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: vault-example
spec:
refreshInterval: "15s"
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: example-sync
data:
- secretKey: foobar
remoteRef:
key: foo
property: my-value

# metadataPolicy to fetch all the labels in JSON format
- secretKey: tags
remoteRef:
metadataPolicy: Fetch
key: foo
---
apiVersion: v1
kind: Secret
metadata:
name: example-sync
data:
foobar: czNjcjN0

test the result

verify the kubernetes Secret file

confirm the reference again

1
2
3
4
- secretKey: foobar
remoteRef:
key: foo
property: my-value

get the vault from value with specified path.

1
2
$ vault kv get -field=my-value secret/foo
guohaotestpassword

check the vault from the secrets again.

1
2
$ echo -n $(kubectl get secrets example-sync -o json | jq .data.foobar) | tr -d '"' | base64  -d
guohaotestpassword⏎

update the vault of foobar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ kubectl exec -it vault-0 -c vault sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.

/ $ vault kv put secret/foo my-value=guohaotestpasswordv2
= Secret Path =
secret/data/foo

======= Metadata =======
Key Value
--- -----
created_time 2024-08-01T07:41:40.562127877Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 2

$ echo -n $(kubectl get secrets example-sync -o json | jq .data.foobar) | tr -d '"' | base64 -d
guohaotestpassword⏎

# the refresh with 15s (this option configured with file)

$ echo -n $(kubectl get secrets example-sync -o json | jq .data.foobar) | tr -d '"' | base64 -d
guohaotestpasswordv2⏎

add new key/vault pair

aha. I found the secret include two one vaule.

1
2
3
4
5
$ kubectl get secrets example-sync -o json | jq .data
{
"foobar": "Z3VvaGFvdGVzdHBhc3N3b3JkdjI=",
"tags": ""
}

inspect the vault reference

1
2
3
4
5
6
7
8
9
10
- secretKey: foobar
remoteRef:
key: foo
property: my-value

# metadataPolicy to fetch all the labels in JSON format
- secretKey: tags
remoteRef:
metadataPolicy: Fetch
key: foo

set the data to vault

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

/ $ vault kv metadata put -custom-metadata=foo=test-wgh-tags secret/foo
Success! Data written to: secret/metadata/foo

/ $ vault kv get -format=json secret/foo
{
"request_id": "178dc077-286d-6b83-b2ec-839919f02cd2",
"lease_id": "",
"lease_duration": 0,
"renewable": false,
"data": {
"data": {
"my-value": "guohaotestpasswordv2"
},
"metadata": {
"created_time": "2024-08-01T08:42:32.452605096Z",
"custom_metadata": {
"foo": "test-wgh-tags"
},
"deletion_time": "",
"destroyed": false,
"version": 4
}
},
"warnings": null,
"mount_type": "kv"
}

the the secret again

1
2
3
4
5
6
7
8
9
$ kubectl get secrets example-sync -o json | jq .data
{
"foobar": "Z3VvaGFvdGVzdHBhc3N3b3Jk",
"tags": "eyJmb28iOiJ0ZXN0LXdnaC10YWdzIn0="
}

# metadataPolicy to fetch all the labels in JSON format
$ echo "eyJmb28iOiJ0ZXN0LXdnaC10YWdzIn0=" | base64 -d
{"foo":"test-wgh-tags"}⏎