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

Commit

Permalink
feat(json): support JSON objects in AWS Secret Manager (#13)
Browse files Browse the repository at this point in the history
This is useful (required?) to safely rotate secrets atomically, like a client
certificate and private key.
  • Loading branch information
silasbw authored Feb 18, 2019
1 parent e9c4392 commit cd7130f
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 11 deletions.
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down
15 changes: 11 additions & 4 deletions lib/backends/kv-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}));
}

Expand Down
12 changes: 12 additions & 0 deletions lib/backends/kv-backend.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
9 changes: 6 additions & 3 deletions lib/backends/secrets-manager-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

Expand Down
6 changes: 4 additions & 2 deletions lib/poller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down

0 comments on commit cd7130f

Please sign in to comment.