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

Commit

Permalink
feat(aws): add region support to ssm and sm (#475)
Browse files Browse the repository at this point in the history
Signed-off-by: Moritz Johner <[email protected]>
  • Loading branch information
moolen authored Oct 7, 2020
1 parent c3c27bc commit 0b35441
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 22 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ spec:
backendType: secretsManager
# optional: specify role to assume when retrieving the data
roleArn: arn:aws:iam::123456789012:role/test-role
# optional: specify region
region: us-east-1
data:
- key: hello-service/credentials
name: password
Expand Down Expand Up @@ -315,6 +317,8 @@ spec:
backendType: secretsManager
# optional: specify role to assume when retrieving the data
roleArn: arn:aws:iam::123456789012:role/test-role
# optional: specify region
region: us-east-1
dataFrom:
- hello-service/credentials
```
Expand All @@ -330,6 +334,8 @@ spec:
backendType: secretsManager
# optional: specify role to assume when retrieving the data
roleArn: arn:aws:iam::123456789012:role/test-role
# optional: specify region
region: us-east-1
dataFrom:
- hello-service/credentials
data:
Expand Down
64 changes: 64 additions & 0 deletions e2e/tests/secrets-manager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,70 @@ describe('secretsmanager', async () => {
expect(secret.body.type).to.equal('kubernetes.io/tls')
})

it('should pull existing secret from secretsmanager in the correct region', async () => {
const smEU = awsConfig.secretsManagerFactory({
region: 'eu-west-1'
})
const createSecret = util.promisify(smEU.createSecret).bind(smEU)
const putSecretValue = util.promisify(smEU.putSecretValue).bind(smEU)

let result = await createSecret({
Name: `e2e/${uuid}/x-region-credentials`,
SecretString: '{"username":"foo","password":"bar"}'
}).catch(err => {
expect(err).to.equal(null)
})

result = await kubeClient
.apis[customResourceManifest.spec.group]
.v1.namespaces('default')[customResourceManifest.spec.names.plural]
.post({
body: {
apiVersion: 'kubernetes-client.io/v1',
kind: 'ExternalSecret',
metadata: {
name: `e2e-secretmanager-x-region-${uuid}`
},
spec: {
backendType: 'secretsManager',
region: 'eu-west-1',
data: [
{
key: `e2e/${uuid}/x-region-credentials`,
property: 'password',
name: 'password'
},
{
key: `e2e/${uuid}/x-region-credentials`,
property: 'username',
name: 'username'
}
]
}
}
})

expect(result).to.not.equal(undefined)
expect(result.statusCode).to.equal(201)

let secret = await waitForSecret('default', `e2e-secretmanager-x-region-${uuid}`)
expect(secret).to.not.equal(undefined)
expect(secret.body.data.username).to.equal('Zm9v')
expect(secret.body.data.password).to.equal('YmFy')

// update the secret value
result = await putSecretValue({
SecretId: `e2e/${uuid}/x-region-credentials`,
SecretString: '{"username":"your mom","password":"1234"}'
}).catch(err => {
expect(err).to.equal(null)
})
await delay(2000)
secret = await waitForSecret('default', `e2e-secretmanager-x-region-${uuid}`)
expect(secret.body.data.username).to.equal('eW91ciBtb20=')
expect(secret.body.data.password).to.equal('MTIzNA==')
})

describe('permitted annotation', async () => {
beforeEach(async () => {
await kubeClient.api.v1.namespaces('default').patch({
Expand Down
44 changes: 44 additions & 0 deletions e2e/tests/ssm.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,50 @@ describe('ssm', async () => {
expect(secret.body.data.name).to.equal('Zm9v')
})

it('should pull existing secret from ssm in a different region', async () => {
const ssmEU = awsConfig.systemManagerFactory({
region: 'eu-west-1'
})
const putParameter = util.promisify(ssmEU.putParameter).bind(ssmEU)

let result = await putParameter({
Name: `/e2e/${uuid}/x-region`,
Type: 'String',
Value: 'foo'
}).catch(err => {
expect(err).to.equal(null)
})

result = await kubeClient
.apis[customResourceManifest.spec.group]
.v1.namespaces('default')[customResourceManifest.spec.names.plural]
.post({
body: {
apiVersion: 'kubernetes-client.io/v1',
kind: 'ExternalSecret',
metadata: {
name: `e2e-ssm-xregion-${uuid}`
},
spec: {
backendType: 'systemManager',
region: 'eu-west-1',
data: [
{
key: `/e2e/${uuid}/x-region`,
name: 'name'
}
]
}
}
})

expect(result).to.not.equal(undefined)
expect(result.statusCode).to.equal(201)

const secret = await waitForSecret('default', `e2e-ssm-xregion-${uuid}`)
expect(secret.body.data.name).to.equal('Zm9v')
})

describe('permitted annotation', async () => {
beforeEach(async () => {
await kubeClient.api.v1.namespaces('default').patch({
Expand Down
2 changes: 2 additions & 0 deletions examples/secretsmanager-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ spec:
backendType: secretsManager
# optional: specify role to assume when retrieving the data
roleArn: arn:aws:iam::123412341234:role/let-other-account-access-secrets
# optional: specify region of the secret
region: eu-west-1
data:
- key: demo-service/credentials
name: password
Expand Down
2 changes: 2 additions & 0 deletions examples/ssm-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ spec:
backendType: systemManager
# optional: specify role to assume when retrieving the data
roleArn: arn:aws:iam::123456789012:role/test-role
# optional: specify region
region: eu-west-1
data:
- key: /foo/name1
name: variable-name
15 changes: 11 additions & 4 deletions lib/backends/kv-backend.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ describe('kv-backend', () => {
expect(manifestData).deep.equals({})
})

it('makes correct calls - with data and role', async () => {
it('makes correct calls - with data, role and region', async () => {
await kvBackend.getSecretManifestData({
spec: {
data: [
Expand All @@ -308,7 +308,8 @@ describe('kv-backend', () => {
name: 'fakePropertyName2'
}
],
roleArn: 'my-role'
roleArn: 'my-role',
region: 'my-region'
}
})

Expand All @@ -320,12 +321,18 @@ describe('kv-backend', () => {
key: 'fakePropertyKey2',
name: 'fakePropertyName2'
}],
specOptions: { roleArn: 'my-role' }
specOptions: {
roleArn: 'my-role',
region: 'my-region'
}
})).to.equal(true)

expect(kvBackend._fetchDataFromValues.calledWith({
dataFrom: [],
specOptions: { roleArn: 'my-role' }
specOptions: {
roleArn: 'my-role',
region: 'my-region'
}
})).to.equal(true)
})

Expand Down
20 changes: 15 additions & 5 deletions lib/backends/secrets-manager-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,30 @@ class SecretsManagerBackend extends KVBackend {
* @param {string} specOptions.roleArn - IAM role arn to assume
* @returns {Promise} Promise object representing secret property value.
*/
async _get ({ key, specOptions: { roleArn }, keyOptions: { versionStage = 'AWSCURRENT', versionId = null } }) {
this._logger.info(`fetching secret property ${key} with role: ${roleArn || 'pods role'}`)
async _get ({ key, specOptions: { roleArn, region }, keyOptions: { versionStage = 'AWSCURRENT', versionId = null } }) {
this._logger.info(`fetching secret property ${key} with role: ${roleArn || 'pods role'} in region ${region}`)

let client = this._client
let factoryArgs = null
if (roleArn) {
const credentials = this._assumeRole({
RoleArn: roleArn,
RoleSessionName: 'k8s-external-secrets'
})
client = this._clientFactory({
factoryArgs = {
...factoryArgs,
credentials
})
}
}
if (region) {
factoryArgs = {
...factoryArgs,
region
}
}
if (factoryArgs) {
client = this._clientFactory(factoryArgs)
}

let params
if (versionId) {
params = { SecretId: key, VersionId: versionId }
Expand Down
39 changes: 35 additions & 4 deletions lib/backends/secrets-manager-backend.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,32 +81,63 @@ describe('SecretsManagerBackend', () => {
expect(secretPropertyValue.toString()).equals('fakeSecretPropertyValue')
})

it('returns secret property value assuming a role', async () => {
it('returns secret property value assuming a role with region', async () => {
getSecretValuePromise.promise.resolves({
SecretString: 'fakeAssumeRoleSecretValue'
})

const secretPropertyValue = await secretsManagerBackend._get({
key: 'fakeSecretKey',
specOptions: { roleArn: 'my-role' },
specOptions: {
roleArn: 'my-role',
region: 'foo-bar-baz'
},
keyOptions
})

expect(clientFactoryMock.lastArg).deep.equals({
credentials: assumeRoleCredentials
credentials: assumeRoleCredentials,
region: 'foo-bar-baz'
})
expect(clientMock.getSecretValue.calledWith({
SecretId: 'fakeSecretKey',
VersionStage: 'AWSCURRENT'
})).to.equal(true)
expect(clientFactoryMock.getCall(0).args).deep.equals([])
expect(clientFactoryMock.getCall(1).args).deep.equals([{
credentials: assumeRoleCredentials
credentials: assumeRoleCredentials,
region: 'foo-bar-baz'
}])
expect(assumeRoleMock.callCount).equals(1)
expect(secretPropertyValue).equals('fakeAssumeRoleSecretValue')
})

it('returns secret property value from specific region', async () => {
getSecretValuePromise.promise.resolves({
SecretString: 'fakeAssumeRoleSecretValue'
})

const secretPropertyValue = await secretsManagerBackend._get({
key: 'fakeSecretKey',
specOptions: { region: 'my-region' },
keyOptions
})

expect(clientFactoryMock.lastArg).deep.equals({
region: 'my-region'
})
expect(clientMock.getSecretValue.calledWith({
SecretId: 'fakeSecretKey',
VersionStage: 'AWSCURRENT'
})).to.equal(true)
expect(clientFactoryMock.getCall(0).args).deep.equals([])
expect(clientFactoryMock.getCall(1).args).deep.equals([{
region: 'my-region'
}])
expect(assumeRoleMock.callCount).equals(0)
expect(secretPropertyValue).equals('fakeAssumeRoleSecretValue')
})

it('returns secret property value with versionStage', async () => {
getSecretValuePromise.promise.resolves({
SecretString: 'fakeSecretPropertyValuePreviousVersion'
Expand Down
20 changes: 15 additions & 5 deletions lib/backends/system-manager-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,33 @@ class SystemManagerBackend extends KVBackend {
/**
* Get secret property value from System Manager.
* @param {string} key - Key used to store secret property value in System Manager.
* @param {object} keyOptions - Options for this specific key, eg version etc.
* @param {object} specOptions - Options for this external secret, eg role
* @param {string} specOptions.roleArn - IAM role arn to assume
* @returns {Promise} Promise object representing secret property value.
*/
async _get ({ key, specOptions: { roleArn } }) {
this._logger.info(`fetching secret property ${key} with role: ${roleArn || 'pods role'}`)
async _get ({ key, specOptions: { roleArn, region } }) {
this._logger.info(`fetching secret property ${key} with role: ${roleArn || 'pods role'} in region ${region}`)

let client = this._client
let factoryArgs = null
if (roleArn) {
const credentials = this._assumeRole({
RoleArn: roleArn,
RoleSessionName: 'k8s-external-secrets'
})
client = this._clientFactory({
factoryArgs = {
...factoryArgs,
credentials
})
}
}
if (region) {
factoryArgs = {
...factoryArgs,
region
}
}
if (factoryArgs) {
client = this._clientFactory(factoryArgs)
}
try {
const data = await client
Expand Down
Loading

0 comments on commit 0b35441

Please sign in to comment.