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

Commit 0b35441

Browse files
authored
feat(aws): add region support to ssm and sm (#475)
Signed-off-by: Moritz Johner <[email protected]>
1 parent c3c27bc commit 0b35441

10 files changed

+232
-22
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ spec:
285285
backendType: secretsManager
286286
# optional: specify role to assume when retrieving the data
287287
roleArn: arn:aws:iam::123456789012:role/test-role
288+
# optional: specify region
289+
region: us-east-1
288290
data:
289291
- key: hello-service/credentials
290292
name: password
@@ -315,6 +317,8 @@ spec:
315317
backendType: secretsManager
316318
# optional: specify role to assume when retrieving the data
317319
roleArn: arn:aws:iam::123456789012:role/test-role
320+
# optional: specify region
321+
region: us-east-1
318322
dataFrom:
319323
- hello-service/credentials
320324
```
@@ -330,6 +334,8 @@ spec:
330334
backendType: secretsManager
331335
# optional: specify role to assume when retrieving the data
332336
roleArn: arn:aws:iam::123456789012:role/test-role
337+
# optional: specify region
338+
region: us-east-1
333339
dataFrom:
334340
- hello-service/credentials
335341
data:

e2e/tests/secrets-manager.test.js

+64
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,70 @@ describe('secretsmanager', async () => {
124124
expect(secret.body.type).to.equal('kubernetes.io/tls')
125125
})
126126

127+
it('should pull existing secret from secretsmanager in the correct region', async () => {
128+
const smEU = awsConfig.secretsManagerFactory({
129+
region: 'eu-west-1'
130+
})
131+
const createSecret = util.promisify(smEU.createSecret).bind(smEU)
132+
const putSecretValue = util.promisify(smEU.putSecretValue).bind(smEU)
133+
134+
let result = await createSecret({
135+
Name: `e2e/${uuid}/x-region-credentials`,
136+
SecretString: '{"username":"foo","password":"bar"}'
137+
}).catch(err => {
138+
expect(err).to.equal(null)
139+
})
140+
141+
result = await kubeClient
142+
.apis[customResourceManifest.spec.group]
143+
.v1.namespaces('default')[customResourceManifest.spec.names.plural]
144+
.post({
145+
body: {
146+
apiVersion: 'kubernetes-client.io/v1',
147+
kind: 'ExternalSecret',
148+
metadata: {
149+
name: `e2e-secretmanager-x-region-${uuid}`
150+
},
151+
spec: {
152+
backendType: 'secretsManager',
153+
region: 'eu-west-1',
154+
data: [
155+
{
156+
key: `e2e/${uuid}/x-region-credentials`,
157+
property: 'password',
158+
name: 'password'
159+
},
160+
{
161+
key: `e2e/${uuid}/x-region-credentials`,
162+
property: 'username',
163+
name: 'username'
164+
}
165+
]
166+
}
167+
}
168+
})
169+
170+
expect(result).to.not.equal(undefined)
171+
expect(result.statusCode).to.equal(201)
172+
173+
let secret = await waitForSecret('default', `e2e-secretmanager-x-region-${uuid}`)
174+
expect(secret).to.not.equal(undefined)
175+
expect(secret.body.data.username).to.equal('Zm9v')
176+
expect(secret.body.data.password).to.equal('YmFy')
177+
178+
// update the secret value
179+
result = await putSecretValue({
180+
SecretId: `e2e/${uuid}/x-region-credentials`,
181+
SecretString: '{"username":"your mom","password":"1234"}'
182+
}).catch(err => {
183+
expect(err).to.equal(null)
184+
})
185+
await delay(2000)
186+
secret = await waitForSecret('default', `e2e-secretmanager-x-region-${uuid}`)
187+
expect(secret.body.data.username).to.equal('eW91ciBtb20=')
188+
expect(secret.body.data.password).to.equal('MTIzNA==')
189+
})
190+
127191
describe('permitted annotation', async () => {
128192
beforeEach(async () => {
129193
await kubeClient.api.v1.namespaces('default').patch({

e2e/tests/ssm.test.js

+44
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,50 @@ describe('ssm', async () => {
5353
expect(secret.body.data.name).to.equal('Zm9v')
5454
})
5555

56+
it('should pull existing secret from ssm in a different region', async () => {
57+
const ssmEU = awsConfig.systemManagerFactory({
58+
region: 'eu-west-1'
59+
})
60+
const putParameter = util.promisify(ssmEU.putParameter).bind(ssmEU)
61+
62+
let result = await putParameter({
63+
Name: `/e2e/${uuid}/x-region`,
64+
Type: 'String',
65+
Value: 'foo'
66+
}).catch(err => {
67+
expect(err).to.equal(null)
68+
})
69+
70+
result = await kubeClient
71+
.apis[customResourceManifest.spec.group]
72+
.v1.namespaces('default')[customResourceManifest.spec.names.plural]
73+
.post({
74+
body: {
75+
apiVersion: 'kubernetes-client.io/v1',
76+
kind: 'ExternalSecret',
77+
metadata: {
78+
name: `e2e-ssm-xregion-${uuid}`
79+
},
80+
spec: {
81+
backendType: 'systemManager',
82+
region: 'eu-west-1',
83+
data: [
84+
{
85+
key: `/e2e/${uuid}/x-region`,
86+
name: 'name'
87+
}
88+
]
89+
}
90+
}
91+
})
92+
93+
expect(result).to.not.equal(undefined)
94+
expect(result.statusCode).to.equal(201)
95+
96+
const secret = await waitForSecret('default', `e2e-ssm-xregion-${uuid}`)
97+
expect(secret.body.data.name).to.equal('Zm9v')
98+
})
99+
56100
describe('permitted annotation', async () => {
57101
beforeEach(async () => {
58102
await kubeClient.api.v1.namespaces('default').patch({

examples/secretsmanager-example.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ spec:
66
backendType: secretsManager
77
# optional: specify role to assume when retrieving the data
88
roleArn: arn:aws:iam::123412341234:role/let-other-account-access-secrets
9+
# optional: specify region of the secret
10+
region: eu-west-1
911
data:
1012
- key: demo-service/credentials
1113
name: password

examples/ssm-example.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ spec:
66
backendType: systemManager
77
# optional: specify role to assume when retrieving the data
88
roleArn: arn:aws:iam::123456789012:role/test-role
9+
# optional: specify region
10+
region: eu-west-1
911
data:
1012
- key: /foo/name1
1113
name: variable-name

lib/backends/kv-backend.test.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ describe('kv-backend', () => {
296296
expect(manifestData).deep.equals({})
297297
})
298298

299-
it('makes correct calls - with data and role', async () => {
299+
it('makes correct calls - with data, role and region', async () => {
300300
await kvBackend.getSecretManifestData({
301301
spec: {
302302
data: [
@@ -308,7 +308,8 @@ describe('kv-backend', () => {
308308
name: 'fakePropertyName2'
309309
}
310310
],
311-
roleArn: 'my-role'
311+
roleArn: 'my-role',
312+
region: 'my-region'
312313
}
313314
})
314315

@@ -320,12 +321,18 @@ describe('kv-backend', () => {
320321
key: 'fakePropertyKey2',
321322
name: 'fakePropertyName2'
322323
}],
323-
specOptions: { roleArn: 'my-role' }
324+
specOptions: {
325+
roleArn: 'my-role',
326+
region: 'my-region'
327+
}
324328
})).to.equal(true)
325329

326330
expect(kvBackend._fetchDataFromValues.calledWith({
327331
dataFrom: [],
328-
specOptions: { roleArn: 'my-role' }
332+
specOptions: {
333+
roleArn: 'my-role',
334+
region: 'my-region'
335+
}
329336
})).to.equal(true)
330337
})
331338

lib/backends/secrets-manager-backend.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,30 @@ class SecretsManagerBackend extends KVBackend {
2626
* @param {string} specOptions.roleArn - IAM role arn to assume
2727
* @returns {Promise} Promise object representing secret property value.
2828
*/
29-
async _get ({ key, specOptions: { roleArn }, keyOptions: { versionStage = 'AWSCURRENT', versionId = null } }) {
30-
this._logger.info(`fetching secret property ${key} with role: ${roleArn || 'pods role'}`)
29+
async _get ({ key, specOptions: { roleArn, region }, keyOptions: { versionStage = 'AWSCURRENT', versionId = null } }) {
30+
this._logger.info(`fetching secret property ${key} with role: ${roleArn || 'pods role'} in region ${region}`)
3131

3232
let client = this._client
33+
let factoryArgs = null
3334
if (roleArn) {
3435
const credentials = this._assumeRole({
3536
RoleArn: roleArn,
3637
RoleSessionName: 'k8s-external-secrets'
3738
})
38-
client = this._clientFactory({
39+
factoryArgs = {
40+
...factoryArgs,
3941
credentials
40-
})
42+
}
43+
}
44+
if (region) {
45+
factoryArgs = {
46+
...factoryArgs,
47+
region
48+
}
49+
}
50+
if (factoryArgs) {
51+
client = this._clientFactory(factoryArgs)
4152
}
42-
4353
let params
4454
if (versionId) {
4555
params = { SecretId: key, VersionId: versionId }

lib/backends/secrets-manager-backend.test.js

+35-4
Original file line numberDiff line numberDiff line change
@@ -81,32 +81,63 @@ describe('SecretsManagerBackend', () => {
8181
expect(secretPropertyValue.toString()).equals('fakeSecretPropertyValue')
8282
})
8383

84-
it('returns secret property value assuming a role', async () => {
84+
it('returns secret property value assuming a role with region', async () => {
8585
getSecretValuePromise.promise.resolves({
8686
SecretString: 'fakeAssumeRoleSecretValue'
8787
})
8888

8989
const secretPropertyValue = await secretsManagerBackend._get({
9090
key: 'fakeSecretKey',
91-
specOptions: { roleArn: 'my-role' },
91+
specOptions: {
92+
roleArn: 'my-role',
93+
region: 'foo-bar-baz'
94+
},
9295
keyOptions
9396
})
9497

9598
expect(clientFactoryMock.lastArg).deep.equals({
96-
credentials: assumeRoleCredentials
99+
credentials: assumeRoleCredentials,
100+
region: 'foo-bar-baz'
97101
})
98102
expect(clientMock.getSecretValue.calledWith({
99103
SecretId: 'fakeSecretKey',
100104
VersionStage: 'AWSCURRENT'
101105
})).to.equal(true)
102106
expect(clientFactoryMock.getCall(0).args).deep.equals([])
103107
expect(clientFactoryMock.getCall(1).args).deep.equals([{
104-
credentials: assumeRoleCredentials
108+
credentials: assumeRoleCredentials,
109+
region: 'foo-bar-baz'
105110
}])
106111
expect(assumeRoleMock.callCount).equals(1)
107112
expect(secretPropertyValue).equals('fakeAssumeRoleSecretValue')
108113
})
109114

115+
it('returns secret property value from specific region', async () => {
116+
getSecretValuePromise.promise.resolves({
117+
SecretString: 'fakeAssumeRoleSecretValue'
118+
})
119+
120+
const secretPropertyValue = await secretsManagerBackend._get({
121+
key: 'fakeSecretKey',
122+
specOptions: { region: 'my-region' },
123+
keyOptions
124+
})
125+
126+
expect(clientFactoryMock.lastArg).deep.equals({
127+
region: 'my-region'
128+
})
129+
expect(clientMock.getSecretValue.calledWith({
130+
SecretId: 'fakeSecretKey',
131+
VersionStage: 'AWSCURRENT'
132+
})).to.equal(true)
133+
expect(clientFactoryMock.getCall(0).args).deep.equals([])
134+
expect(clientFactoryMock.getCall(1).args).deep.equals([{
135+
region: 'my-region'
136+
}])
137+
expect(assumeRoleMock.callCount).equals(0)
138+
expect(secretPropertyValue).equals('fakeAssumeRoleSecretValue')
139+
})
140+
110141
it('returns secret property value with versionStage', async () => {
111142
getSecretValuePromise.promise.resolves({
112143
SecretString: 'fakeSecretPropertyValuePreviousVersion'

lib/backends/system-manager-backend.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,33 @@ class SystemManagerBackend extends KVBackend {
1919
/**
2020
* Get secret property value from System Manager.
2121
* @param {string} key - Key used to store secret property value in System Manager.
22-
* @param {object} keyOptions - Options for this specific key, eg version etc.
2322
* @param {object} specOptions - Options for this external secret, eg role
2423
* @param {string} specOptions.roleArn - IAM role arn to assume
2524
* @returns {Promise} Promise object representing secret property value.
2625
*/
27-
async _get ({ key, specOptions: { roleArn } }) {
28-
this._logger.info(`fetching secret property ${key} with role: ${roleArn || 'pods role'}`)
26+
async _get ({ key, specOptions: { roleArn, region } }) {
27+
this._logger.info(`fetching secret property ${key} with role: ${roleArn || 'pods role'} in region ${region}`)
2928

3029
let client = this._client
30+
let factoryArgs = null
3131
if (roleArn) {
3232
const credentials = this._assumeRole({
3333
RoleArn: roleArn,
3434
RoleSessionName: 'k8s-external-secrets'
3535
})
36-
client = this._clientFactory({
36+
factoryArgs = {
37+
...factoryArgs,
3738
credentials
38-
})
39+
}
40+
}
41+
if (region) {
42+
factoryArgs = {
43+
...factoryArgs,
44+
region
45+
}
46+
}
47+
if (factoryArgs) {
48+
client = this._clientFactory(factoryArgs)
3949
}
4050
try {
4151
const data = await client

0 commit comments

Comments
 (0)