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

Commit

Permalink
feat: add GCP support (#312)
Browse files Browse the repository at this point in the history
Co-authored-by: Joel Damata <[email protected]>
  • Loading branch information
Silas Boyd-Wickizer and pcc-damatj authored Mar 27, 2020
1 parent 828d0ce commit 5b41ad0
Show file tree
Hide file tree
Showing 10 changed files with 488 additions and 8 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,27 @@ spec:
property: value
```
### GCP Secret Manager
kubernetes-external-secrets supports fetching secrets from [GCP Secret Manager](https://cloud.google.com/solutions/secrets-management)
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)
Alternatively you can create and mount a kubernetes secret containing google service account credentials and set the GOOGLE_APPLICATION_CREDENTIALS env variable.
```yml
apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
name: gcp-secrets-manager-example
spec:
backendType: gcpSecretsManager
data:
- key: projects/111122223333/secrets/my-secret/versions/latest
name: password
property: value
```
## Metrics
kubernetes-external-secrets exposes the following metrics over a prometheus endpoint:
Expand Down
7 changes: 4 additions & 3 deletions charts/kubernetes-external-secrets/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ env:
LOG_LEVEL: info
METRICS_PORT: 3001
VAULT_ADDR: http://127.0.0.1:8200
# GOOGLE_APPLICATION_CREDENTIALS: /app/gcp-creds.json

# Create environment variables from existing k8s secrets
# envVarsFromSecret:
Expand All @@ -30,9 +31,9 @@ env:

# Create files from existing k8s secrets
# filesFromSecret:
# examplefile:
# secret: secretname
# mountPath: /a/mount/point/
# gcp-creds:
# secret: gcp-creds
# mountPath: /app/gcp-creds.json

rbac:
# Specifies whether RBAC resources should be created
Expand Down
15 changes: 15 additions & 0 deletions config/gcp-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict'

const { SecretManagerServiceClient } = require('@google-cloud/secret-manager')

// First, ADC checks to see if the environment variable GOOGLE_APPLICATION_CREDENTIALS is set.
// If the variable is set, ADC uses the service account file that the variable points to
// If the environment variable isn't set, ADC uses the default service account that the Kubernetes Engine
// provides, for applications that run on those services

module.exports = {
gcpSecretsManager: () => {
const client = new SecretManagerServiceClient()
return client
}
}
9 changes: 8 additions & 1 deletion config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ const path = require('path')

const awsConfig = require('./aws-config')
const azureConfig = require('./azure-config')
const gcpConfig = require('./gcp-config')
const envConfig = require('./environment')
const CustomResourceManager = require('../lib/custom-resource-manager')
const SecretsManagerBackend = require('../lib/backends/secrets-manager-backend')
const SystemManagerBackend = require('../lib/backends/system-manager-backend')
const VaultBackend = require('../lib/backends/vault-backend')
const AzureKeyVaultBackend = require('../lib/backends/azure-keyvault-backend')
const GCPSecretsManagerBackend = require('../lib/backends/gcp-secrets-manager-backend')

// Get document, or throw exception on error
// eslint-disable-next-line security/detect-non-literal-fs-filename
Expand Down Expand Up @@ -54,12 +56,17 @@ const azureKeyVaultBackend = new AzureKeyVaultBackend({
credential: azureConfig.azureKeyVault(),
logger
})
const gcpSecretsManagerBackend = new GCPSecretsManagerBackend({
client: gcpConfig.gcpSecretsManager(),
logger
})
const backends = {
// when adding a new backend, make sure to change the CRD property too
secretsManager: secretsManagerBackend,
systemManager: systemManagerBackend,
vault: vaultBackend,
azureKeyVault: azureKeyVaultBackend
azureKeyVault: azureKeyVaultBackend,
gcpSecretsManager: gcpSecretsManagerBackend
}

// backwards compatibility
Expand Down
5 changes: 5 additions & 0 deletions crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ spec:
- systemManager
- vault
- azureKeyVault
- gcpSecretsManager
vaultRole:
type: string
vaultMountPoint:
Expand Down Expand Up @@ -92,6 +93,10 @@ spec:
- azureKeyVault
required:
- keyVaultName
- properties:
backendType:
enum:
- gcpSecretsManager
anyOf:
- required:
- data
Expand Down
10 changes: 10 additions & 0 deletions examples/gcpsecretsmanager-example copy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
name: gcp-secrets-manager-example
spec:
backendType: gcpSecretsManager
data:
- key: projects/111122223333/secrets/my-secret/versions/latest
name: password
property: value
32 changes: 32 additions & 0 deletions lib/backends/gcp-secrets-manager-backend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict'

const KVBackend = require('./kv-backend')

/** GCP Secrets Manager backend class. */
class GCPSecretsManagerBackend extends KVBackend {
/**
* Create Secrets manager backend.
* @param {Object} logger - Logger for logging stuff.
* @param {Object} client - Secrets manager client.
*/
constructor ({ logger, client }) {
super({ logger })
this._client = client
}

/**
* Get secret property value from GCP Secrets Manager.
* @param {string} key - Key used to store secret property value in GCP Secrets Manager.
* @returns {Promise} Promise object representing secret property value.
*/
async _get ({ key }) {
this._logger.info(`fetching secret ${key} from GCP Secret Manager`)
const version = await this._client.accessSecretVersion({
name: key
})
const secret = { value: version[0].payload.data.toString('utf8') }
return JSON.stringify(secret)
}
}

module.exports = GCPSecretsManagerBackend
37 changes: 37 additions & 0 deletions lib/backends/gcp-secrets-manager-backend.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-env mocha */
'use strict'

const { expect } = require('chai')
const sinon = require('sinon')

const GCPSecretsManagerBackend = require('./gcp-secrets-manager-backend')

describe('GCPSecretsManagerBackend', () => {
let loggerMock
let clientMock
let gcpSecretsManagerBackend
const key = 'password'
const version = [{ name: 'projects/111122223333/secrets/password/versions/1', payload: { data: Buffer.from('test', 'utf8') } }, null, null]
const secret = '{"value":"test"}'

beforeEach(() => {
loggerMock = sinon.mock()
loggerMock.info = sinon.stub()
clientMock = sinon.mock()
clientMock.accessSecretVersion = sinon.stub().returns(version)

gcpSecretsManagerBackend = new GCPSecretsManagerBackend({
logger: loggerMock,
client: clientMock
})
})

describe('_get', () => {
it('returns secret property value', async () => {
const secretPropertyValue = await gcpSecretsManagerBackend._get({
key: key
})
expect(secretPropertyValue).equals(secret)
})
})
})
Loading

0 comments on commit 5b41ad0

Please sign in to comment.