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

Commit

Permalink
feat: add support for IBM Cloud Secrets Manager backend (#656)
Browse files Browse the repository at this point in the history
  • Loading branch information
No9 authored Mar 27, 2021
1 parent fa070ef commit 8ff9490
Show file tree
Hide file tree
Showing 9 changed files with 1,660 additions and 420 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,43 @@ Create the policy binding:

gcloud iam service-accounts add-iam-policy-binding --role roles/iam.workloadIdentityUser --member "serviceAccount:$CLUSTER_PROJECT.svc.id.goog[$SECRETS_NAMESPACE/kubernetes-external-secrets]" my-secrets-sa@$PROJECT.iam.gserviceaccount.com

### IBM Cloud Secrets Manager

kubernetes-external-secrets supports fetching secrets from [IBM Cloud Secrets Manager](https://cloud.ibm.com/catalog/services/secrets-manager)

create username_password secret by using the [ui, cli or API](https://cloud.ibm.com/docs/secrets-manager?topic=secrets-manager-user-credentials).
The cli option is illustrated below:

```bash
# you need to configure ibm cloud cli with a valid endpoint
export IBM_CLOUD_SECRETS_MANAGER_API_URL=https://{instanceid}.{region}.secrets-manager.appdomain.cloud
ibmcloud secrets-manager secret-create --secret-type username_password \
--metadata '{"collection_type": "application/vnd.ibm.secrets-manager.secret+json", "collection_total": 1}' \
--resources '[{"name": "example-username-password-secret","description": "Extended description for my secret.","username": "user123","password": "cloudy-rainy-coffee-book"}]'
```

You will need to set these env vars in the deployment of kubernetes-external-secrets:

- IBM_CLOUD_SECRETS_MANAGER_API_APIKEY
- IBM_CLOUD_SECRETS_MANAGER_API_ENDPOINT
- IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE

```yml
apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
name: ibmcloud-secrets-manager-example
spec:
backendType: ibmcloudSecretsManager
data:
# The guid id of the secret
- key: <guid>
name: username
property: username
secretType: username_password
```


##### Deploy kubernetes-external-secrets using a service account key

Alternatively you can create and mount a kubernetes secret containing google service account credentials and set the GOOGLE_APPLICATION_CREDENTIALS env variable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ spec:
- azureKeyVault
- gcpSecretsManager
- alicloudSecretsManager
- ibmcloudSecretsManager
vaultRole:
type: string
vaultMountPoint:
Expand Down Expand Up @@ -126,6 +127,10 @@ spec:
backendType:
enum:
- alicloudSecretsManager
- properties:
backendType:
enum:
- ibmcloudSecretsManager
anyOf:
- required:
- data
Expand Down
14 changes: 14 additions & 0 deletions config/ibmcloud-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict'

// IBM Cloud automatically picks up the following credentials so they don't have to be passed in the config
// - SECRETS_MANAGER_API_AUTH_TYPE=iam
// - SECRETS_MANAGER_API_APIKEY=<apikey>
// - SECRETS_MANAGER_API_ENDPOINT= endpoint URL https://{instance-id}.{region}.secrets-manager.appdomain.cloud

module.exports = {
credential: {
apikey: process.env.IBM_CLOUD_SECRETS_MANAGER_API_APIKEY,
endpoint: process.env.IBM_CLOUD_SECRETS_MANAGER_API_ENDPOINT,
type: process.env.IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE
}
}
11 changes: 9 additions & 2 deletions config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ const awsConfig = require('./aws-config')
const azureConfig = require('./azure-config')
const alicloudConfig = require('./alicloud-config')
const gcpConfig = require('./gcp-config')
const ibmcloudConfig = require('./ibmcloud-config')
const envConfig = require('./environment')
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')
const AliCloudSecretsManagerBackend = require('../lib/backends/alicloud-secrets-manager-backend')
const IbmCloudSecretsManagerBackend = require('../lib/backends/ibmcloud-secrets-manager-backend')

// Get document, or throw exception on error
// eslint-disable-next-line security/detect-non-literal-fs-filename
Expand Down Expand Up @@ -97,15 +99,20 @@ const alicloudSecretsManagerBackend = new AliCloudSecretsManagerBackend({
credential: alicloudConfig.credential,
logger
})
const ibmcloudSecretsManagerBackend = new IbmCloudSecretsManagerBackend({
credential: ibmcloudConfig.credential,
logger
})

const backends = {
// when adding a new backend, make sure to change the CRD property too
secretsManager: secretsManagerBackend,
systemManager: systemManagerBackend,
vault: vaultBackend,
azureKeyVault: azureKeyVaultBackend,
gcpSecretsManager: gcpSecretsManagerBackend,
alicloudSecretsManager: alicloudSecretsManagerBackend

alicloudSecretsManager: alicloudSecretsManagerBackend,
ibmcloudSecretsManager: ibmcloudSecretsManagerBackend
}

// backwards compatibility
Expand Down
11 changes: 11 additions & 0 deletions examples/ibmcloud-secrets-manager-example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
name: ibmcloud-secrets-manager-example
spec:
backendType: ibmcloudSecretsManager
data:
# The guid id of the secret
- key: guid
name: username_password
secretType: username_password
54 changes: 54 additions & 0 deletions lib/backends/ibmcloud-secrets-manager-backend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use strict'

const SecretsManager = require('@ibm-cloud/secrets-manager/secrets-manager/v1')
const { getAuthenticatorFromEnvironment, IamAuthenticator } = require('@ibm-cloud/secrets-manager/auth')

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

/** Secrets Manager backend class. */
class IbmCloudSecretsManagerBackend extends KVBackend {
/**
* Create Key Vault backend.
* @param {Object} credential - Credentials for authenticating with IBM Secrets Manager.
* @param {Object} logger - Logger for logging stuff.
*/
constructor ({ credential, logger }) {
super({ logger })
this._credential = credential
}

_secretsManagerClient () {
let authenticator
if (process.env.IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE && process.env.IBM_CLOUD_SECRETS_MANAGER_API_APIKEY) {
authenticator = getAuthenticatorFromEnvironment('IBM_CLOUD_SECRETS_MANAGER_API')
} else {
authenticator = new IamAuthenticator({
apikey: this._credential.apikey
})
}
const client = new SecretsManager({
authenticator: authenticator,
serviceUrl: this._credential.endpoint
})
return client
}

/**
* Get secret_data property value from IBM Cloud Secrets Manager
* @param {string} key - Key used to store secret property value.
* @param {object} specOptions - Options for this external secret, eg role
* @param {string} specOptions.secretType - Type of secret - one of username_password, iam_credentials or arbitrary
* @returns {Promise} Promise object representing secret property value.
*/
async _get ({ key, keyOptions: { secretType } }) {
const client = this._secretsManagerClient()
this._logger.info(`fetching secret ${key} from IBM Cloud Secrets Manager ${this._credential.endpoint}`)
const secret = await client.getSecret({
secretType: secretType,
id: key
})
return JSON.stringify(secret.result.resources[0].secret_data)
}
}

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

process.env.IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE = 'noauth'
process.env.IBM_CLOUD_SECRETS_MANAGER_API_APIKEY = 'iamkey'

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

const IbmCloudSecretsManagerBackend = require('./ibmcloud-secrets-manager-backend')

describe('IbmCloudSecretsManagerBackend', () => {
let loggerMock
let clientMock
let ibmCloudSecretsManagerBackend

const username = 'fakeUserName'
const password = 'fakeSecretPropertyValue'
const secret = { result: { resources: [{ secret_data: { password: password, username: username } }] } }
const returnsecret = JSON.stringify({ password: password, username: username })
const key = 'username_password'

beforeEach(() => {
loggerMock = sinon.mock()
loggerMock.info = sinon.stub()
clientMock = sinon.mock()
clientMock.getSecret = sinon.stub().returns(secret)

ibmCloudSecretsManagerBackend = new IbmCloudSecretsManagerBackend({
credential: { endpoint: 'https//sampleendpoint' },
logger: loggerMock
})
ibmCloudSecretsManagerBackend._secretsManagerClient = sinon.stub().returns(clientMock)
})

describe('_get', () => {
it('returns secret property value', async () => {
const specOptions = {}
const keyOptions = { secretType: 'password' }
const secretPropertyValue = await ibmCloudSecretsManagerBackend._get({
key: key,
specOptions,
keyOptions
})
expect(secretPropertyValue).equals(returnsecret)
})
})
})
Loading

0 comments on commit 8ff9490

Please sign in to comment.