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

Commit 8ff9490

Browse files
authored
feat: add support for IBM Cloud Secrets Manager backend (#656)
1 parent fa070ef commit 8ff9490

9 files changed

+1660
-420
lines changed

README.md

+37
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,43 @@ Create the policy binding:
713713

714714
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
715715

716+
### IBM Cloud Secrets Manager
717+
718+
kubernetes-external-secrets supports fetching secrets from [IBM Cloud Secrets Manager](https://cloud.ibm.com/catalog/services/secrets-manager)
719+
720+
create username_password secret by using the [ui, cli or API](https://cloud.ibm.com/docs/secrets-manager?topic=secrets-manager-user-credentials).
721+
The cli option is illustrated below:
722+
723+
```bash
724+
# you need to configure ibm cloud cli with a valid endpoint
725+
export IBM_CLOUD_SECRETS_MANAGER_API_URL=https://{instanceid}.{region}.secrets-manager.appdomain.cloud
726+
ibmcloud secrets-manager secret-create --secret-type username_password \
727+
--metadata '{"collection_type": "application/vnd.ibm.secrets-manager.secret+json", "collection_total": 1}' \
728+
--resources '[{"name": "example-username-password-secret","description": "Extended description for my secret.","username": "user123","password": "cloudy-rainy-coffee-book"}]'
729+
```
730+
731+
You will need to set these env vars in the deployment of kubernetes-external-secrets:
732+
733+
- IBM_CLOUD_SECRETS_MANAGER_API_APIKEY
734+
- IBM_CLOUD_SECRETS_MANAGER_API_ENDPOINT
735+
- IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE
736+
737+
```yml
738+
apiVersion: kubernetes-client.io/v1
739+
kind: ExternalSecret
740+
metadata:
741+
name: ibmcloud-secrets-manager-example
742+
spec:
743+
backendType: ibmcloudSecretsManager
744+
data:
745+
# The guid id of the secret
746+
- key: <guid>
747+
name: username
748+
property: username
749+
secretType: username_password
750+
```
751+
752+
716753
##### Deploy kubernetes-external-secrets using a service account key
717754

718755
Alternatively you can create and mount a kubernetes secret containing google service account credentials and set the GOOGLE_APPLICATION_CREDENTIALS env variable.

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

+5
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ spec:
4949
- azureKeyVault
5050
- gcpSecretsManager
5151
- alicloudSecretsManager
52+
- ibmcloudSecretsManager
5253
vaultRole:
5354
type: string
5455
vaultMountPoint:
@@ -126,6 +127,10 @@ spec:
126127
backendType:
127128
enum:
128129
- alicloudSecretsManager
130+
- properties:
131+
backendType:
132+
enum:
133+
- ibmcloudSecretsManager
129134
anyOf:
130135
- required:
131136
- data

config/ibmcloud-config.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict'
2+
3+
// IBM Cloud automatically picks up the following credentials so they don't have to be passed in the config
4+
// - SECRETS_MANAGER_API_AUTH_TYPE=iam
5+
// - SECRETS_MANAGER_API_APIKEY=<apikey>
6+
// - SECRETS_MANAGER_API_ENDPOINT= endpoint URL https://{instance-id}.{region}.secrets-manager.appdomain.cloud
7+
8+
module.exports = {
9+
credential: {
10+
apikey: process.env.IBM_CLOUD_SECRETS_MANAGER_API_APIKEY,
11+
endpoint: process.env.IBM_CLOUD_SECRETS_MANAGER_API_ENDPOINT,
12+
type: process.env.IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE
13+
}
14+
}

config/index.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ const awsConfig = require('./aws-config')
1212
const azureConfig = require('./azure-config')
1313
const alicloudConfig = require('./alicloud-config')
1414
const gcpConfig = require('./gcp-config')
15+
const ibmcloudConfig = require('./ibmcloud-config')
1516
const envConfig = require('./environment')
1617
const SecretsManagerBackend = require('../lib/backends/secrets-manager-backend')
1718
const SystemManagerBackend = require('../lib/backends/system-manager-backend')
1819
const VaultBackend = require('../lib/backends/vault-backend')
1920
const AzureKeyVaultBackend = require('../lib/backends/azure-keyvault-backend')
2021
const GCPSecretsManagerBackend = require('../lib/backends/gcp-secrets-manager-backend')
2122
const AliCloudSecretsManagerBackend = require('../lib/backends/alicloud-secrets-manager-backend')
23+
const IbmCloudSecretsManagerBackend = require('../lib/backends/ibmcloud-secrets-manager-backend')
2224

2325
// Get document, or throw exception on error
2426
// eslint-disable-next-line security/detect-non-literal-fs-filename
@@ -97,15 +99,20 @@ const alicloudSecretsManagerBackend = new AliCloudSecretsManagerBackend({
9799
credential: alicloudConfig.credential,
98100
logger
99101
})
102+
const ibmcloudSecretsManagerBackend = new IbmCloudSecretsManagerBackend({
103+
credential: ibmcloudConfig.credential,
104+
logger
105+
})
106+
100107
const backends = {
101108
// when adding a new backend, make sure to change the CRD property too
102109
secretsManager: secretsManagerBackend,
103110
systemManager: systemManagerBackend,
104111
vault: vaultBackend,
105112
azureKeyVault: azureKeyVaultBackend,
106113
gcpSecretsManager: gcpSecretsManagerBackend,
107-
alicloudSecretsManager: alicloudSecretsManagerBackend
108-
114+
alicloudSecretsManager: alicloudSecretsManagerBackend,
115+
ibmcloudSecretsManager: ibmcloudSecretsManagerBackend
109116
}
110117

111118
// backwards compatibility
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: kubernetes-client.io/v1
2+
kind: ExternalSecret
3+
metadata:
4+
name: ibmcloud-secrets-manager-example
5+
spec:
6+
backendType: ibmcloudSecretsManager
7+
data:
8+
# The guid id of the secret
9+
- key: guid
10+
name: username_password
11+
secretType: username_password
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict'
2+
3+
const SecretsManager = require('@ibm-cloud/secrets-manager/secrets-manager/v1')
4+
const { getAuthenticatorFromEnvironment, IamAuthenticator } = require('@ibm-cloud/secrets-manager/auth')
5+
6+
const KVBackend = require('./kv-backend')
7+
8+
/** Secrets Manager backend class. */
9+
class IbmCloudSecretsManagerBackend extends KVBackend {
10+
/**
11+
* Create Key Vault backend.
12+
* @param {Object} credential - Credentials for authenticating with IBM Secrets Manager.
13+
* @param {Object} logger - Logger for logging stuff.
14+
*/
15+
constructor ({ credential, logger }) {
16+
super({ logger })
17+
this._credential = credential
18+
}
19+
20+
_secretsManagerClient () {
21+
let authenticator
22+
if (process.env.IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE && process.env.IBM_CLOUD_SECRETS_MANAGER_API_APIKEY) {
23+
authenticator = getAuthenticatorFromEnvironment('IBM_CLOUD_SECRETS_MANAGER_API')
24+
} else {
25+
authenticator = new IamAuthenticator({
26+
apikey: this._credential.apikey
27+
})
28+
}
29+
const client = new SecretsManager({
30+
authenticator: authenticator,
31+
serviceUrl: this._credential.endpoint
32+
})
33+
return client
34+
}
35+
36+
/**
37+
* Get secret_data property value from IBM Cloud Secrets Manager
38+
* @param {string} key - Key used to store secret property value.
39+
* @param {object} specOptions - Options for this external secret, eg role
40+
* @param {string} specOptions.secretType - Type of secret - one of username_password, iam_credentials or arbitrary
41+
* @returns {Promise} Promise object representing secret property value.
42+
*/
43+
async _get ({ key, keyOptions: { secretType } }) {
44+
const client = this._secretsManagerClient()
45+
this._logger.info(`fetching secret ${key} from IBM Cloud Secrets Manager ${this._credential.endpoint}`)
46+
const secret = await client.getSecret({
47+
secretType: secretType,
48+
id: key
49+
})
50+
return JSON.stringify(secret.result.resources[0].secret_data)
51+
}
52+
}
53+
54+
module.exports = IbmCloudSecretsManagerBackend
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
process.env.IBM_CLOUD_SECRETS_MANAGER_API_AUTH_TYPE = 'noauth'
5+
process.env.IBM_CLOUD_SECRETS_MANAGER_API_APIKEY = 'iamkey'
6+
7+
const { expect } = require('chai')
8+
const sinon = require('sinon')
9+
10+
const IbmCloudSecretsManagerBackend = require('./ibmcloud-secrets-manager-backend')
11+
12+
describe('IbmCloudSecretsManagerBackend', () => {
13+
let loggerMock
14+
let clientMock
15+
let ibmCloudSecretsManagerBackend
16+
17+
const username = 'fakeUserName'
18+
const password = 'fakeSecretPropertyValue'
19+
const secret = { result: { resources: [{ secret_data: { password: password, username: username } }] } }
20+
const returnsecret = JSON.stringify({ password: password, username: username })
21+
const key = 'username_password'
22+
23+
beforeEach(() => {
24+
loggerMock = sinon.mock()
25+
loggerMock.info = sinon.stub()
26+
clientMock = sinon.mock()
27+
clientMock.getSecret = sinon.stub().returns(secret)
28+
29+
ibmCloudSecretsManagerBackend = new IbmCloudSecretsManagerBackend({
30+
credential: { endpoint: 'https//sampleendpoint' },
31+
logger: loggerMock
32+
})
33+
ibmCloudSecretsManagerBackend._secretsManagerClient = sinon.stub().returns(clientMock)
34+
})
35+
36+
describe('_get', () => {
37+
it('returns secret property value', async () => {
38+
const specOptions = {}
39+
const keyOptions = { secretType: 'password' }
40+
const secretPropertyValue = await ibmCloudSecretsManagerBackend._get({
41+
key: key,
42+
specOptions,
43+
keyOptions
44+
})
45+
expect(secretPropertyValue).equals(returnsecret)
46+
})
47+
})
48+
})

0 commit comments

Comments
 (0)