diff --git a/README.md b/README.md index a9350967..d807de21 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,13 @@ This create all the necessary resources and a `Deployment` in the `kubernetes-ex ### Add a secret -Add secret data in your external provider (e.g., `hello-service/password=1234` in AWS Secrets Manager), then create a `hello-service-external-secret.yml` file: +Add your secret data to your backend. For example, AWS Secrets Manager: + +``` +aws secretsmanager create-secret --name hello-service/password --secret-string "1234" +``` + +and then create a `hello-service-external-secret.yml` file: ```yml apiVersion: 'kubernetes-client.io/v1' @@ -73,7 +79,43 @@ data: password: MTIzNA== ``` -Currently we only support AWS Secrets Manager external provider. +## Backends + +kubernetes-external-secrets support only AWS Secrets Manager. + +### AWS Secrets Manager + +kubernetes-external-secrets supports both JSON objects ("Secret +key/value" in the AWS console) or strings ("Plaintext" in the AWS +console). Using JSON objects is useful when you need to atomically +update multiple values. For example, when rotating a client +certificate and private key. + +When writing an ExternalSecret for a JSON object you must specify the +properties to use. For example, if we add our hello-service +credentials as a single JSON object: + +``` +aws secretsmanager create-secret --region us-west-2 --name hello-service/credentials --secret-string '{"username":"admin","password":"1234"}' +``` + +We can declare which properties we want from hello-service/credentials: + +```yml +apiVersion: 'kubernetes-client.io/v1' +kind: ExternalSecret +metadata: + name: hello-service +secretDescriptor: + backendType: secretManager + properties: + - key: hello-service/credentials + name: password + property: password + - key: hello-service/credentials + name: username + property: username +``` ## Development diff --git a/lib/backends/kv-backend.js b/lib/backends/kv-backend.js index 47761c6c..10f8ef7c 100644 --- a/lib/backends/kv-backend.js +++ b/lib/backends/kv-backend.js @@ -16,14 +16,21 @@ class KVBackend extends AbstractBackend { /** * Fetch Kubernetes secret property values. * @param {Object[]} secretProperties - Kubernetes secret properties. - * @param {string} secretProperties[].key - Kubernetes secret property key. - * @param {string} secretProperties[].name - Kubernetes secret property name. + * @param {string} secretProperties[].key - Secret key in the backend. + * @param {string} secretProperties[].name - Kubernetes Secret property name. + * @param {string} secretProperties[].property - If the backend secret is an + * object, this is the property name of the value to use. * @returns {Promise} Promise object representing secret property values. */ _fetchSecretPropertyValues({ secretProperties }) { - return Promise.all(secretProperties.map(secretProperty => { + return Promise.all(secretProperties.map(async secretProperty => { this._logger.info(`fetching secret property ${secretProperty.name}`); - return this._get({ secretKey: secretProperty.key }); + const value = await this._get({ secretKey: secretProperty.key }); + + if ('property' in secretProperty) { + return value[secretProperty.property]; + } + return value; })); } diff --git a/lib/backends/kv-backend.test.js b/lib/backends/kv-backend.test.js index e61094de..9534c01c 100644 --- a/lib/backends/kv-backend.test.js +++ b/lib/backends/kv-backend.test.js @@ -24,6 +24,18 @@ describe('SecretsManagerBackend', () => { kvBackend._get = sinon.stub(); }); + it('handles secrets values that are objects', async () => { + kvBackend._get.onFirstCall().resolves({ foo: 'bar' }); + const secretPropertyValues = await kvBackend._fetchSecretPropertyValues({ + secretProperties: [{ + key: 'mocked-key', + name: 'mocked-name', + property: 'foo' + }] + }); + expect(secretPropertyValues).to.deep.equal(['bar']); + }); + it('fetches secret property values', async () => { kvBackend._get.onFirstCall().resolves('fakePropertyValue1'); kvBackend._get.onSecondCall().resolves('fakePropertyValue2'); diff --git a/lib/backends/secrets-manager-backend.js b/lib/backends/secrets-manager-backend.js index a76ef17e..a1baa747 100644 --- a/lib/backends/secrets-manager-backend.js +++ b/lib/backends/secrets-manager-backend.js @@ -24,9 +24,12 @@ class SecretsManagerBackend extends KVBackend { .getSecretValue({ SecretId: secretKey }) .promise(); - // NOTE(jdaeli): data.SecretString can also be valid key/value serialized object - // but for compatibility with System Manager, we store a single string value - return data.SecretString; + const secretValue = data.SecretString; + try { + return JSON.parse(secretValue); + } catch (err) { + return secretValue; + } } } diff --git a/lib/poller.js b/lib/poller.js index e1f79373..1422c165 100644 --- a/lib/poller.js +++ b/lib/poller.js @@ -6,8 +6,10 @@ * @param {string} backendType - Backend to use for fetching secret data. * @param {string} name - Kubernetes secret name. * @param {Object[]} properties - Kubernetes secret properties. - * @param {string} properties[].key - Kubernetes secret property key. - * @param {string} properties[].name - Kubernetes secret property name. + * @param {string} properties[].key - Secret key in the backend. + * @param {string} properties[].name - Kubernetes Secret property name. + * @param {string} properties[].property - If the backend secret is an + * object, this is the property name of the value to use. */ /** Poller class. */