Skip to content

Commit 0d32761

Browse files
authored
feat: context support (#4)
1 parent 99fe2f7 commit 0d32761

File tree

2 files changed

+66
-23
lines changed

2 files changed

+66
-23
lines changed

README.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,29 @@ Inject secrets from AWS Secrets Manager into the Netlify build process.
1515
"Version": "2012-10-17",
1616
"Statement": [
1717
{
18-
"Sid": "VisualEditor0",
19-
"Effect": "Allow",
20-
"Action": "secretsmanager:GetSecretValue",
21-
"Resource": "arn:aws:secretsmanager:<region>:<account-id>:secret:<secret-path>"
18+
"Sid": "VisualEditor0",
19+
"Effect": "Allow",
20+
"Action": "secretsmanager:GetSecretValue",
21+
"Resource": "arn:aws:secretsmanager:us-east-1:534156574994:secret:netlify/plugin/*"
2222
},
2323
{
24-
"Sid": "VisualEditor1",
25-
"Effect": "Allow",
26-
"Action": "secretsmanager:ListSecrets",
27-
"Resource": "*"
24+
"Sid": "VisualEditor1",
25+
"Effect": "Allow",
26+
"Action": "secretsmanager:DescribeSecret",
27+
"Resource": "arn:aws:secretsmanager:us-east-1:534156574994:secret:netlify/plugin/*"
28+
},
29+
{
30+
"Sid": "VisualEditor2",
31+
"Effect": "Allow",
32+
"Action": "secretsmanager:ListSecrets",
33+
"Resource": "*"
2834
}
2935
]
3036
}
3137
```
3238

33-
> You can scope the `GetSecretValue` permission to a path, but the `ListSecrets` must be a wildcard `*`
39+
> You can scope the `GetSecretValue` permission to a path, but the `ListSecrets` must be a wildcard `*`.
40+
> `DescribeSecret` is required for context based secrets (we use secret tags to get the context)
3441
3542
## Usage
3643

src/main.js

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
const process = require('process')
22

3-
const { SecretsManagerClient, ListSecretsCommand, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager')
3+
const {
4+
SecretsManagerClient,
5+
ListSecretsCommand,
6+
GetSecretValueCommand,
7+
DescribeSecretCommand,
8+
} = require('@aws-sdk/client-secrets-manager')
49
const chalk = require('chalk')
510

611
const listSecrets = async ({ client, nextToken, secrets = [] }) => {
@@ -14,23 +19,39 @@ const listSecrets = async ({ client, nextToken, secrets = [] }) => {
1419
return newSecrets
1520
}
1621

17-
const normalizeSecretValue = async ({ client, secret }) => {
22+
const getSecretContext = async ({ client, secret }) => {
23+
try {
24+
const { Tags: tags = [] } = await client.send(new DescribeSecretCommand({ SecretId: secret.ARN }))
25+
const context = tags.find(({ Key: key }) => key === 'NETLIFY_CONTEXT')
26+
return context && context.Value
27+
} catch (error) {
28+
if (error.name === 'AccessDeniedException') {
29+
return console.log(
30+
chalk.dim(`Does not have permissions to retrieve context for secret ${chalk.yellow(secret.Name)}`),
31+
)
32+
}
33+
}
34+
}
35+
36+
const getSecretsValue = async ({ client, secret }) => {
1837
try {
1938
// SecretString is a JSON string representation of the secret, e.g. '{"SECRET_NAME":"SECRET_VALUE"}'
2039
const { SecretString: secretString } = await client.send(new GetSecretValueCommand({ SecretId: secret.ARN }))
40+
const context = await getSecretContext({ client, secret })
2141
const parsedValue = JSON.parse(secretString)
22-
return parsedValue
42+
return Object.entries(parsedValue).map(([key, value]) => ({ key, value, context }))
2343
} catch (error) {
2444
if (error.name === 'AccessDeniedException') {
25-
return console.log(chalk.dim(`Skipping restricted AWS secret ${chalk.yellow(secret.Name)}`))
45+
console.log(chalk.dim(`Skipping restricted secret ${chalk.yellow(secret.Name)}`))
46+
return []
2647
}
2748
throw error
2849
}
2950
}
3051

31-
const normalizeSecrets = async ({ client, secrets }) => {
32-
const values = await Promise.all(secrets.map((secret) => normalizeSecretValue({ client, secret })))
33-
return Object.assign({}, ...values)
52+
const getSecretsValues = async ({ client, secrets }) => {
53+
const secretsWithValues = await Promise.all(secrets.map((secret) => getSecretsValue({ client, secret })))
54+
return secretsWithValues.flat()
3455
}
3556

3657
const SECRET_PREFIX = process.env.NETLIFY_AWS_SECRET_PREFIX || 'NETLIFY_AWS_SECRET_'
@@ -43,6 +64,7 @@ module.exports = {
4364
NETLIFY_AWS_ACCESS_KEY_ID: accessKeyId,
4465
NETLIFY_AWS_SECRET_ACCESS_KEY: secretAccessKey,
4566
NETLIFY_AWS_DEFAULT_REGION: region = 'us-east-1',
67+
CONTEXT,
4668
} = process.env
4769
if (!accessKeyId) {
4870
return utils.build.failBuild(`Missing environment variable NETLIFY_AWS_ACCESS_KEY_ID`)
@@ -57,14 +79,28 @@ module.exports = {
5779
credentials: { accessKeyId, secretAccessKey },
5880
})
5981
const secrets = await listSecrets({ client })
60-
const normalizedSecrets = await normalizeSecrets({ client, secrets })
61-
62-
const entries = Object.entries(normalizedSecrets)
63-
entries.forEach(([key, value]) => {
82+
const secretsWithValues = await getSecretsValues({ client, secrets })
83+
secretsWithValues.forEach(({ key, value, context }) => {
6484
const prefixedKey = getPrefixedKey(key)
65-
console.log(`${chalk.bold('Injecting AWS secret')} ${chalk.magenta(`${key}`)} as ${chalk.green(prefixedKey)}`)
66-
// eslint-disable-next-line no-param-reassign
67-
netlifyConfig.build.environment[prefixedKey] = value
85+
86+
// no context, inject to all
87+
if (!context) {
88+
console.log(`${chalk.bold('Injecting AWS secret')} ${chalk.magenta(`${key}`)} as ${chalk.green(prefixedKey)}`)
89+
// eslint-disable-next-line no-param-reassign
90+
netlifyConfig.build.environment[prefixedKey] = value
91+
return
92+
}
93+
94+
// inject only to matching context
95+
if (CONTEXT === context) {
96+
console.log(
97+
`${chalk.bold('Injecting AWS secret')} ${chalk.magenta(`${key}`)} as ${chalk.green(
98+
prefixedKey,
99+
)} to context ${chalk.yellow(context)}`,
100+
)
101+
/* eslint-disable-next-line no-param-reassign */
102+
netlifyConfig.build.environment[prefixedKey] = value
103+
}
68104
})
69105
},
70106
}

0 commit comments

Comments
 (0)