Skip to content
This repository was archived by the owner on Jul 26, 2022. It is now read-only.

Commit 5215090

Browse files
feat: Cluster level default settings for Hashicorp Vault (#472)
1 parent 9318f68 commit 5215090

File tree

6 files changed

+65
-22
lines changed

6 files changed

+65
-22
lines changed

README.md

+22-13
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,11 @@ by default an `ExternalSecret` may access arbitrary keys from the backend e.g.
227227
name: password
228228
```
229229

230-
An enforced naming convention helps to keep the structure tidy and limits the access according
231-
to your naming schema.
230+
An enforced naming convention helps to keep the structure tidy and limits the access according
231+
to your naming schema.
232232

233-
Configure the schema as regular expression in the namespace using an annotation.
234-
This allows `ExternalSecrets` in `core-namespace` just to access secrets that start with
233+
Configure the schema as regular expression in the namespace using an annotation.
234+
This allows `ExternalSecrets` in `core-namespace` just to access secrets that start with
235235
`/dev/cluster1/core-namespace/`:
236236

237237
```yaml
@@ -342,6 +342,13 @@ spec:
342342

343343
kubernetes-external-secrets supports fetching secrets from [Hashicorp Vault](https://www.vaultproject.io/), using the [Kubernetes authentication method](https://www.vaultproject.io/docs/auth/kubernetes).
344344

345+
```yml
346+
env:
347+
VAULT_ADDR: https://vault.domain.tld
348+
DEFAULT_VAULT_MOUNT_POINT: "k8s-auth" # optional, default value to be used if not specified in the ExternalSecret
349+
DEFAULT_VAULT_ROLE: "k8s-auth-role" # optional, default value to be used if not specified in the ExternalSecret
350+
```
351+
345352
You will need to set the `VAULT_ADDR` environment variables so that kubernetes-external-secrets knows which endpoint to connect to, then create `ExternalSecret` definitions as follows:
346353

347354
```yml
@@ -352,10 +359,12 @@ metadata:
352359
spec:
353360
backendType: vault
354361
# Your authentication mount point, e.g. "kubernetes"
362+
# Overrides cluster DEFAULT_VAULT_MOUNT_POINT
355363
vaultMountPoint: my-kubernetes-vault-mount-point
356364
# The vault role that will be used to fetch the secrets
357365
# This role will need to be bound to kubernetes-external-secret's ServiceAccount; see Vault's documentation:
358366
# https://www.vaultproject.io/docs/auth/kubernetes.html
367+
# Overrides cluster DEFAULT_VAULT_ROLE
359368
vaultRole: my-vault-role
360369
data:
361370
- name: password
@@ -474,7 +483,7 @@ kubernetes-external-secrets supports fetching secrets from [GCP Secret Manager](
474483

475484
The external secret will poll for changes to the secret according to the value set for POLLER_INTERVAL_MILLISECONDS in env. Depending on the time interval this is set to you may incur additional charges as Google Secret Manager [charges](https://cloud.google.com/secret-manager/pricing) per a set number of API calls.
476485

477-
A service account is required to grant the controller access to pull secrets.
486+
A service account is required to grant the controller access to pull secrets.
478487

479488

480489
#### Add a secret
@@ -493,7 +502,7 @@ echo -n '{"value": "my-secret-value-with-update"}' | gcloud secrets versions
493502
Instructions are here: [Enable Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable_workload_identity_on_a_new_cluster). To enable workload identity on an existing cluster (which is not covered in that document), first enable it on the cluster like so:
494503

495504
gcloud container clusters update $CLUSTER_NAME --workload-pool=$PROJECT_NAME.svc.id.goog
496-
505+
497506
Next enable workload metadata config on the node pool in which the pod will run:
498507

499508
gcloud beta container node-pools update $POOL --cluster $CLUSTER_NAME --workload-metadata-from-node=GKE_METADATA_SERVER
@@ -509,7 +518,7 @@ If enabling it only for a particular pool, make sure to add any relevant tolerat
509518
operator: "Equal"
510519
effect: "NoSchedule"
511520
value: "node-pool-taint"
512-
521+
513522
affinity:
514523
nodeAffinity:
515524
requiredDuringSchedulingIgnoredDuringExecution:
@@ -519,11 +528,11 @@ If enabling it only for a particular pool, make sure to add any relevant tolerat
519528
operator: In
520529
values:
521530
- node-pool
522-
531+
523532
You can add an annotation which is needed for workload identity by passing it in via Helm:
524533

525534
serviceAccount:
526-
annotations:
535+
annotations:
527536
iam.gke.io/gcp-service-account: my-secrets-sa@$PROJECT.iam.gserviceaccount.com
528537

529538
Create the policy binding:
@@ -559,10 +568,10 @@ Uncomment GOOGLE_APPLICATION_CREDENTIALS in the values file as well as the follo
559568
gcp-creds:
560569
secret: gcp-creds
561570
mountPath: /app/gcp-creds
562-
571+
563572
This will mount the secret at /app/gcp-creds/gcp-creds.json and make it available via the GOOGLE_APPLICATION_CREDENTIALS environment variable.
564573

565-
#### Usage
574+
#### Usage
566575
Once you have kubernetes-external-secrets installed, you can create an external secret with YAML like the following:
567576

568577
```yml
@@ -585,11 +594,11 @@ The field "key" is the name of the secret in Google Secret Manager. The field "
585594
To retrieve external secrets, you can use the following command:
586595

587596
kubectl get externalsecrets -n $NAMESPACE
588-
597+
589598
To retrieve the secrets themselves, you can use the regular:
590599

591600
kubectl get secrets -n $NAMESPACE
592-
601+
593602
To retrieve an individual secret's content, use the following where "mysecret" is the key to the secret content under the "data" field:
594603

595604
kubectl get secret my-secret -o 'go-template={{index .data "mysecret"}}' | base64 -D

charts/kubernetes-external-secrets/crds/kubernetes-client.io_externalsecrets_crd.yaml

-3
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,6 @@ spec:
102102
backendType:
103103
enum:
104104
- vault
105-
required:
106-
- vaultRole
107-
- vaultMountPoint
108105
- properties:
109106
backendType:
110107
enum:

config/environment.js

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const vaultEndpoint = process.env.VAULT_ADDR || 'http://127.0.0.1:8200'
2020
// Grab the vault namespace from the environment
2121
const vaultNamespace = process.env.VAULT_NAMESPACE || null
2222
const vaultTokenRenewThreshold = process.env.VAULT_TOKEN_RENEW_THRESHOLD || null
23+
const defaultVaultMountPoint = process.env.DEFAULT_VAULT_MOUNT_POINT || null
24+
const defaultVaultRole = process.env.DEFAULT_VAULT_ROLE || null
2325

2426
const pollerIntervalMilliseconds = process.env.POLLER_INTERVAL_MILLISECONDS
2527
? Number(process.env.POLLER_INTERVAL_MILLISECONDS) : 10000
@@ -42,6 +44,8 @@ module.exports = {
4244
vaultEndpoint,
4345
vaultNamespace,
4446
vaultTokenRenewThreshold,
47+
defaultVaultMountPoint,
48+
defaultVaultRole,
4549
environment,
4650
pollerIntervalMilliseconds,
4751
metricsPort,

config/index.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,13 @@ const vaultClient = vault(vaultOptions)
8282
// expires and with at least one remaining poll opportunty to retry renewal if it fails.
8383
const vaultTokenRenewThreshold = envConfig.vaultTokenRenewThreshold
8484
? Number(envConfig.vaultTokenRenewThreshold) : 3 * envConfig.pollerIntervalMilliseconds / 1000
85-
const vaultBackend = new VaultBackend({ client: vaultClient, tokenRenewThreshold: vaultTokenRenewThreshold, logger })
85+
const vaultBackend = new VaultBackend({
86+
client: vaultClient,
87+
tokenRenewThreshold: vaultTokenRenewThreshold,
88+
logger: logger,
89+
defaultVaultMountPoint: envConfig.defaultVaultMountPoint,
90+
defaultVaultRole: envConfig.defaultVaultRole
91+
})
8692
const azureKeyVaultBackend = new AzureKeyVaultBackend({
8793
credential: azureConfig.azureKeyVault(),
8894
logger

lib/backends/vault-backend.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ class VaultBackend extends KVBackend {
1010
* @param {Number} tokenRenewThreshold - tokens are renewed when ttl reaches this threshold
1111
* @param {Object} logger - Logger for logging stuff.
1212
*/
13-
constructor ({ client, tokenRenewThreshold, logger }) {
13+
constructor ({ client, tokenRenewThreshold, logger, defaultVaultMountPoint, defaultVaultRole }) {
1414
super({ logger })
1515
this._client = client
1616
this._tokenRenewThreshold = tokenRenewThreshold
17+
this.defaultVaultMountPoint = defaultVaultMountPoint
18+
this.defaultVaultRole = defaultVaultRole
1719
}
1820

1921
/**
@@ -38,13 +40,13 @@ class VaultBackend extends KVBackend {
3840
* @param {number} specOptions.kvVersion - K/V Version 1 or 2
3941
* @returns {Promise} Promise object representing secret property values.
4042
*/
41-
async _get ({ key, specOptions: { vaultMountPoint, vaultRole, kvVersion = 2 } }) {
43+
async _get ({ key, specOptions: { vaultMountPoint = null, vaultRole = null, kvVersion = 2 } }) {
4244
if (!this._client.token) {
4345
const jwt = this._fetchServiceAccountToken()
4446
this._logger.debug('fetching new token from vault')
4547
await this._client.kubernetesLogin({
46-
mount_point: vaultMountPoint,
47-
role: vaultRole,
48+
mount_point: vaultMountPoint || this.defaultVaultMountPoint,
49+
role: vaultRole || this.defaultVaultRole,
4850
jwt: jwt
4951
})
5052
} else {

lib/backends/vault-backend.test.js

+26-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const logger = pino({
1515
describe('VaultBackend', () => {
1616
let clientMock
1717
let vaultBackend
18+
const defaultFakeMountPoint = 'defaultFakeMountPoint'
19+
const defaultFakeRole = 'defaultFakeRole'
1820
const mountPoint = 'fakeMountPoint'
1921
const role = 'fakeRole'
2022
const secretKey = 'fakeSecretKey'
@@ -52,7 +54,9 @@ describe('VaultBackend', () => {
5254
vaultBackend = new VaultBackend({
5355
client: clientMock,
5456
tokenRenewThreshold: vaultTokenRenewThreshold,
55-
logger
57+
logger: logger,
58+
defaultVaultMountPoint: defaultFakeMountPoint,
59+
defaultVaultRole: defaultFakeRole
5660
})
5761
})
5862

@@ -95,6 +99,27 @@ describe('VaultBackend', () => {
9599
expect(secretPropertyValue).equals(quotedSecretValue)
96100
})
97101

102+
it('if vaultRole and vaultMountPoint not specified use the default one', async () => {
103+
const secretPropertyValue = await vaultBackend._get({
104+
specOptions: {
105+
},
106+
key: secretKey
107+
})
108+
109+
// First, we log into Vault...
110+
sinon.assert.calledWith(clientMock.kubernetesLogin, {
111+
mount_point: defaultFakeMountPoint,
112+
role: defaultFakeRole,
113+
jwt: jwt
114+
})
115+
116+
// ... then we fetch the secret ...
117+
sinon.assert.calledWith(clientMock.read, secretKey)
118+
119+
// ... and expect to get its proper value
120+
expect(secretPropertyValue).equals(quotedSecretValue)
121+
})
122+
98123
it('logs in and returns secret property value - kv version 1', async () => {
99124
clientMock.read = sinon.stub().returns(kv1Secret)
100125

0 commit comments

Comments
 (0)