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

Commit cd7130f

Browse files
authored
feat(json): support JSON objects in AWS Secret Manager (#13)
This is useful (required?) to safely rotate secrets atomically, like a client certificate and private key.
1 parent e9c4392 commit cd7130f

File tree

5 files changed

+77
-11
lines changed

5 files changed

+77
-11
lines changed

README.md

+44-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ This create all the necessary resources and a `Deployment` in the `kubernetes-ex
3535

3636
### Add a secret
3737

38-
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:
38+
Add your secret data to your backend. For example, AWS Secrets Manager:
39+
40+
```
41+
aws secretsmanager create-secret --name hello-service/password --secret-string "1234"
42+
```
43+
44+
and then create a `hello-service-external-secret.yml` file:
3945

4046
```yml
4147
apiVersion: 'kubernetes-client.io/v1'
@@ -73,7 +79,43 @@ data:
7379
password: MTIzNA==
7480
```
7581
76-
Currently we only support AWS Secrets Manager external provider.
82+
## Backends
83+
84+
kubernetes-external-secrets support only AWS Secrets Manager.
85+
86+
### AWS Secrets Manager
87+
88+
kubernetes-external-secrets supports both JSON objects ("Secret
89+
key/value" in the AWS console) or strings ("Plaintext" in the AWS
90+
console). Using JSON objects is useful when you need to atomically
91+
update multiple values. For example, when rotating a client
92+
certificate and private key.
93+
94+
When writing an ExternalSecret for a JSON object you must specify the
95+
properties to use. For example, if we add our hello-service
96+
credentials as a single JSON object:
97+
98+
```
99+
aws secretsmanager create-secret --region us-west-2 --name hello-service/credentials --secret-string '{"username":"admin","password":"1234"}'
100+
```
101+
102+
We can declare which properties we want from hello-service/credentials:
103+
104+
```yml
105+
apiVersion: 'kubernetes-client.io/v1'
106+
kind: ExternalSecret
107+
metadata:
108+
name: hello-service
109+
secretDescriptor:
110+
backendType: secretManager
111+
properties:
112+
- key: hello-service/credentials
113+
name: password
114+
property: password
115+
- key: hello-service/credentials
116+
name: username
117+
property: username
118+
```
77119
78120
## Development
79121

lib/backends/kv-backend.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@ class KVBackend extends AbstractBackend {
1616
/**
1717
* Fetch Kubernetes secret property values.
1818
* @param {Object[]} secretProperties - Kubernetes secret properties.
19-
* @param {string} secretProperties[].key - Kubernetes secret property key.
20-
* @param {string} secretProperties[].name - Kubernetes secret property name.
19+
* @param {string} secretProperties[].key - Secret key in the backend.
20+
* @param {string} secretProperties[].name - Kubernetes Secret property name.
21+
* @param {string} secretProperties[].property - If the backend secret is an
22+
* object, this is the property name of the value to use.
2123
* @returns {Promise} Promise object representing secret property values.
2224
*/
2325
_fetchSecretPropertyValues({ secretProperties }) {
24-
return Promise.all(secretProperties.map(secretProperty => {
26+
return Promise.all(secretProperties.map(async secretProperty => {
2527
this._logger.info(`fetching secret property ${secretProperty.name}`);
26-
return this._get({ secretKey: secretProperty.key });
28+
const value = await this._get({ secretKey: secretProperty.key });
29+
30+
if ('property' in secretProperty) {
31+
return value[secretProperty.property];
32+
}
33+
return value;
2734
}));
2835
}
2936

lib/backends/kv-backend.test.js

+12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ describe('SecretsManagerBackend', () => {
2424
kvBackend._get = sinon.stub();
2525
});
2626

27+
it('handles secrets values that are objects', async () => {
28+
kvBackend._get.onFirstCall().resolves({ foo: 'bar' });
29+
const secretPropertyValues = await kvBackend._fetchSecretPropertyValues({
30+
secretProperties: [{
31+
key: 'mocked-key',
32+
name: 'mocked-name',
33+
property: 'foo'
34+
}]
35+
});
36+
expect(secretPropertyValues).to.deep.equal(['bar']);
37+
});
38+
2739
it('fetches secret property values', async () => {
2840
kvBackend._get.onFirstCall().resolves('fakePropertyValue1');
2941
kvBackend._get.onSecondCall().resolves('fakePropertyValue2');

lib/backends/secrets-manager-backend.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ class SecretsManagerBackend extends KVBackend {
2424
.getSecretValue({ SecretId: secretKey })
2525
.promise();
2626

27-
// NOTE(jdaeli): data.SecretString can also be valid key/value serialized object
28-
// but for compatibility with System Manager, we store a single string value
29-
return data.SecretString;
27+
const secretValue = data.SecretString;
28+
try {
29+
return JSON.parse(secretValue);
30+
} catch (err) {
31+
return secretValue;
32+
}
3033
}
3134
}
3235

lib/poller.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
* @param {string} backendType - Backend to use for fetching secret data.
77
* @param {string} name - Kubernetes secret name.
88
* @param {Object[]} properties - Kubernetes secret properties.
9-
* @param {string} properties[].key - Kubernetes secret property key.
10-
* @param {string} properties[].name - Kubernetes secret property name.
9+
* @param {string} properties[].key - Secret key in the backend.
10+
* @param {string} properties[].name - Kubernetes Secret property name.
11+
* @param {string} properties[].property - If the backend secret is an
12+
* object, this is the property name of the value to use.
1113
*/
1214

1315
/** Poller class. */

0 commit comments

Comments
 (0)