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

Commit 5b41ad0

Browse files
Silas Boyd-Wickizerpcc-damatj
Silas Boyd-Wickizer
andauthored
feat: add GCP support (#312)
Co-authored-by: Joel Damata <[email protected]>
1 parent 828d0ce commit 5b41ad0

10 files changed

+488
-8
lines changed

README.md

+21
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,27 @@ spec:
389389
property: value
390390
```
391391
392+
### GCP Secret Manager
393+
394+
kubernetes-external-secrets supports fetching secrets from [GCP Secret Manager](https://cloud.google.com/solutions/secrets-management)
395+
396+
A service account is required to grant the controller access to pull secrets. Instructions are her: [Enable Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#enable_workload_identity_on_a_new_cluster)
397+
398+
Alternatively you can create and mount a kubernetes secret containing google service account credentials and set the GOOGLE_APPLICATION_CREDENTIALS env variable.
399+
400+
```yml
401+
apiVersion: kubernetes-client.io/v1
402+
kind: ExternalSecret
403+
metadata:
404+
name: gcp-secrets-manager-example
405+
spec:
406+
backendType: gcpSecretsManager
407+
data:
408+
- key: projects/111122223333/secrets/my-secret/versions/latest
409+
name: password
410+
property: value
411+
```
412+
392413
## Metrics
393414
394415
kubernetes-external-secrets exposes the following metrics over a prometheus endpoint:

charts/kubernetes-external-secrets/values.yaml

+4-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ env:
99
LOG_LEVEL: info
1010
METRICS_PORT: 3001
1111
VAULT_ADDR: http://127.0.0.1:8200
12+
# GOOGLE_APPLICATION_CREDENTIALS: /app/gcp-creds.json
1213

1314
# Create environment variables from existing k8s secrets
1415
# envVarsFromSecret:
@@ -30,9 +31,9 @@ env:
3031

3132
# Create files from existing k8s secrets
3233
# filesFromSecret:
33-
# examplefile:
34-
# secret: secretname
35-
# mountPath: /a/mount/point/
34+
# gcp-creds:
35+
# secret: gcp-creds
36+
# mountPath: /app/gcp-creds.json
3637

3738
rbac:
3839
# Specifies whether RBAC resources should be created

config/gcp-config.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict'
2+
3+
const { SecretManagerServiceClient } = require('@google-cloud/secret-manager')
4+
5+
// First, ADC checks to see if the environment variable GOOGLE_APPLICATION_CREDENTIALS is set.
6+
// If the variable is set, ADC uses the service account file that the variable points to
7+
// If the environment variable isn't set, ADC uses the default service account that the Kubernetes Engine
8+
// provides, for applications that run on those services
9+
10+
module.exports = {
11+
gcpSecretsManager: () => {
12+
const client = new SecretManagerServiceClient()
13+
return client
14+
}
15+
}

config/index.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ const path = require('path')
1010

1111
const awsConfig = require('./aws-config')
1212
const azureConfig = require('./azure-config')
13+
const gcpConfig = require('./gcp-config')
1314
const envConfig = require('./environment')
1415
const CustomResourceManager = require('../lib/custom-resource-manager')
1516
const SecretsManagerBackend = require('../lib/backends/secrets-manager-backend')
1617
const SystemManagerBackend = require('../lib/backends/system-manager-backend')
1718
const VaultBackend = require('../lib/backends/vault-backend')
1819
const AzureKeyVaultBackend = require('../lib/backends/azure-keyvault-backend')
20+
const GCPSecretsManagerBackend = require('../lib/backends/gcp-secrets-manager-backend')
1921

2022
// Get document, or throw exception on error
2123
// eslint-disable-next-line security/detect-non-literal-fs-filename
@@ -54,12 +56,17 @@ const azureKeyVaultBackend = new AzureKeyVaultBackend({
5456
credential: azureConfig.azureKeyVault(),
5557
logger
5658
})
59+
const gcpSecretsManagerBackend = new GCPSecretsManagerBackend({
60+
client: gcpConfig.gcpSecretsManager(),
61+
logger
62+
})
5763
const backends = {
5864
// when adding a new backend, make sure to change the CRD property too
5965
secretsManager: secretsManagerBackend,
6066
systemManager: systemManagerBackend,
6167
vault: vaultBackend,
62-
azureKeyVault: azureKeyVaultBackend
68+
azureKeyVault: azureKeyVaultBackend,
69+
gcpSecretsManager: gcpSecretsManagerBackend
6370
}
6471

6572
// backwards compatibility

crd.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ spec:
4343
- systemManager
4444
- vault
4545
- azureKeyVault
46+
- gcpSecretsManager
4647
vaultRole:
4748
type: string
4849
vaultMountPoint:
@@ -92,6 +93,10 @@ spec:
9293
- azureKeyVault
9394
required:
9495
- keyVaultName
96+
- properties:
97+
backendType:
98+
enum:
99+
- gcpSecretsManager
95100
anyOf:
96101
- required:
97102
- data
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: kubernetes-client.io/v1
2+
kind: ExternalSecret
3+
metadata:
4+
name: gcp-secrets-manager-example
5+
spec:
6+
backendType: gcpSecretsManager
7+
data:
8+
- key: projects/111122223333/secrets/my-secret/versions/latest
9+
name: password
10+
property: value
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict'
2+
3+
const KVBackend = require('./kv-backend')
4+
5+
/** GCP Secrets Manager backend class. */
6+
class GCPSecretsManagerBackend extends KVBackend {
7+
/**
8+
* Create Secrets manager backend.
9+
* @param {Object} logger - Logger for logging stuff.
10+
* @param {Object} client - Secrets manager client.
11+
*/
12+
constructor ({ logger, client }) {
13+
super({ logger })
14+
this._client = client
15+
}
16+
17+
/**
18+
* Get secret property value from GCP Secrets Manager.
19+
* @param {string} key - Key used to store secret property value in GCP Secrets Manager.
20+
* @returns {Promise} Promise object representing secret property value.
21+
*/
22+
async _get ({ key }) {
23+
this._logger.info(`fetching secret ${key} from GCP Secret Manager`)
24+
const version = await this._client.accessSecretVersion({
25+
name: key
26+
})
27+
const secret = { value: version[0].payload.data.toString('utf8') }
28+
return JSON.stringify(secret)
29+
}
30+
}
31+
32+
module.exports = GCPSecretsManagerBackend
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const { expect } = require('chai')
5+
const sinon = require('sinon')
6+
7+
const GCPSecretsManagerBackend = require('./gcp-secrets-manager-backend')
8+
9+
describe('GCPSecretsManagerBackend', () => {
10+
let loggerMock
11+
let clientMock
12+
let gcpSecretsManagerBackend
13+
const key = 'password'
14+
const version = [{ name: 'projects/111122223333/secrets/password/versions/1', payload: { data: Buffer.from('test', 'utf8') } }, null, null]
15+
const secret = '{"value":"test"}'
16+
17+
beforeEach(() => {
18+
loggerMock = sinon.mock()
19+
loggerMock.info = sinon.stub()
20+
clientMock = sinon.mock()
21+
clientMock.accessSecretVersion = sinon.stub().returns(version)
22+
23+
gcpSecretsManagerBackend = new GCPSecretsManagerBackend({
24+
logger: loggerMock,
25+
client: clientMock
26+
})
27+
})
28+
29+
describe('_get', () => {
30+
it('returns secret property value', async () => {
31+
const secretPropertyValue = await gcpSecretsManagerBackend._get({
32+
key: key
33+
})
34+
expect(secretPropertyValue).equals(secret)
35+
})
36+
})
37+
})

0 commit comments

Comments
 (0)