ac-awssecrets 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.acsemver.js +9 -0
- package/.eslintrc.js +26 -0
- package/CHANGELOG.md +68 -0
- package/Makefile +16 -0
- package/README.md +104 -0
- package/index.js +185 -0
- package/package.json +23 -0
- package/test/test.js +7 -0
package/.acsemver.js
ADDED
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const config = {
|
|
2
|
+
'env': {
|
|
3
|
+
'commonjs': true,
|
|
4
|
+
'es6': true,
|
|
5
|
+
'node': true
|
|
6
|
+
},
|
|
7
|
+
'extends': 'eslint:recommended',
|
|
8
|
+
"rules": {
|
|
9
|
+
"space-before-function-paren": 0,
|
|
10
|
+
"no-extra-semi": 0,
|
|
11
|
+
"object-curly-spacing": ["error", "always"],
|
|
12
|
+
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
|
13
|
+
"no-useless-escape": 0,
|
|
14
|
+
"standard/no-callback-literal": 0,
|
|
15
|
+
"new-cap": 0
|
|
16
|
+
},
|
|
17
|
+
globals: {
|
|
18
|
+
describe: true,
|
|
19
|
+
it: true
|
|
20
|
+
},
|
|
21
|
+
'parserOptions': {
|
|
22
|
+
'ecmaVersion': 2018
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = config
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<a name="1.1.3"></a>
|
|
2
|
+
|
|
3
|
+
## [1.1.3](https://github.com/mmpro/ac-awssecrets/compare/v1.1.2..v1.1.3) (2021-09-22 11:13:39)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fix
|
|
7
|
+
|
|
8
|
+
* **App:** Package updates | MP | [82f4a90c24c4c94279bf23621dd92805e01ca4d7](https://github.com/mmpro/ac-awssecrets/commit/82f4a90c24c4c94279bf23621dd92805e01ca4d7)
|
|
9
|
+
Package updates
|
|
10
|
+
<a name="1.1.2"></a>
|
|
11
|
+
|
|
12
|
+
## [1.1.2](https://github.com/mmpro/ac-awssecrets/compare/v1.1.1..v1.1.2) (2021-05-31 06:17:24)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fix
|
|
16
|
+
|
|
17
|
+
* **App:** Package updates | MP | [466bc49d1d270b784cc35c0ab3e476b3fd6e3587](https://github.com/mmpro/ac-awssecrets/commit/466bc49d1d270b784cc35c0ab3e476b3fd6e3587)
|
|
18
|
+
Package updates
|
|
19
|
+
<a name="1.1.1"></a>
|
|
20
|
+
|
|
21
|
+
## [1.1.1](https://github.com/mmpro/ac-awssecrets/compare/v1.1.0..v1.1.1) (2020-03-29 14:10:49)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Bug Fix
|
|
25
|
+
|
|
26
|
+
* **App:** Prepare repository for AC semantic release | MP | [f8f652bc09e1581e2ec35b3be6d51045bb905576](https://github.com/mmpro/ac-awssecrets/commit/f8f652bc09e1581e2ec35b3be6d51045bb905576)
|
|
27
|
+
Cleaned up repository and use ac-semantic-release
|
|
28
|
+
### Chores
|
|
29
|
+
|
|
30
|
+
* **Misc:** Updated packages | MP | [83244d834fd80f34fbaf450fdefcdfa5b78f91f2](https://github.com/mmpro/ac-awssecrets/commit/83244d834fd80f34fbaf450fdefcdfa5b78f91f2)
|
|
31
|
+
Updated packages
|
|
32
|
+
<a name="1.1.0"></a>
|
|
33
|
+
# [1.1.0](https://github.com/mmpro/ac-awssecrets/compare/v1.0.1...v1.1.0) (2020-02-16 18:42)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Features
|
|
37
|
+
|
|
38
|
+
* **Misc:** The servers parameter now support more flexibility | mp ([01f0a798f543108ecef72771de5705764f7db82f](https://github.com/mmpro/ac-awssecrets/commit/01f0a798f543108ecef72771de5705764f7db82f))
|
|
39
|
+
You can set custom identifiers for array of objects - see README
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
<a name="1.0.1"></a>
|
|
44
|
+
## [1.0.1](https://github.com/mmpro/ac-awssecrets/compare/v1.0.0...v1.0.1) (2019-07-24 19:43)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Bug Fixes
|
|
48
|
+
|
|
49
|
+
* **Misc:** Force version bump | MP ([fbfb1ae](https://github.com/mmpro/ac-awssecrets/commit/fbfb1ae))
|
|
50
|
+
Force version bump
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
<a name="1.0.0"></a>
|
|
55
|
+
# 1.0.0 (2019-07-24 19:42)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Bug Fixes
|
|
59
|
+
|
|
60
|
+
* **Misc:** Package update | MP ([dbb4c23](https://github.com/mmpro/ac-awssecrets/commit/dbb4c23))
|
|
61
|
+
Package update and some minor adjustments for corp-release/semver
|
|
62
|
+
* **Misc:** Package update | MP ([8224bf7](https://github.com/mmpro/ac-awssecrets/commit/8224bf7))
|
|
63
|
+
Package update and some minor adjustments for corp-release/semver
|
|
64
|
+
* **Misc:** Package update | MP ([14e79bc](https://github.com/mmpro/ac-awssecrets/commit/14e79bc))
|
|
65
|
+
Package update and some minor adjustments for corp-release/semver
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
package/Makefile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
MOCHA_OPTS= --slow 0 -A
|
|
2
|
+
REPORTER = spec
|
|
3
|
+
|
|
4
|
+
lint-fix:
|
|
5
|
+
./node_modules/.bin/eslint --fix index.js test/test.js
|
|
6
|
+
|
|
7
|
+
lint-check:
|
|
8
|
+
./node_modules/.bin/eslint index.js test/test.js
|
|
9
|
+
|
|
10
|
+
commit:
|
|
11
|
+
@node ./node_modules/ac-semantic-release/lib/commit.js
|
|
12
|
+
|
|
13
|
+
release:
|
|
14
|
+
@node ./node_modules/ac-semantic-release/lib/release.js
|
|
15
|
+
|
|
16
|
+
.PHONY: check
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# AC AWS Secrets
|
|
2
|
+
Reads secrets from AWS secrets manager and adds them to the configuration of the embedding app.
|
|
3
|
+
|
|
4
|
+
## Usage
|
|
5
|
+
|
|
6
|
+
### Simple example
|
|
7
|
+
Lets assume we have the following configuration and secret
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
let existingConfig = {
|
|
11
|
+
redis: {
|
|
12
|
+
host: 'localhost'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Stored under name "redis.cacheServer" in AWS
|
|
17
|
+
let secret = {
|
|
18
|
+
host: 'my-secret-server'
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The following setup will replace the existing configuration and redis.host will be "my-secret-server"
|
|
23
|
+
```
|
|
24
|
+
const secretParams = {
|
|
25
|
+
aws: {
|
|
26
|
+
accessKeyId: 'accessKeyId',
|
|
27
|
+
secretAccessKey: 'secretAccessKey',
|
|
28
|
+
region: 'eu-central-1'
|
|
29
|
+
},
|
|
30
|
+
secrets: [
|
|
31
|
+
{ key: 'redis', name: 'redis.cacheServer', ignoreInTestMode: true }
|
|
32
|
+
],
|
|
33
|
+
config: existingConfig,
|
|
34
|
+
environment: 'development'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
awsSecrets.loadSecrets(secretParams, (err, result) => {
|
|
38
|
+
if (err) return cb(err)
|
|
39
|
+
_.forEach(result, (item) => {
|
|
40
|
+
console.log('Setting secret for', _.padEnd(_.get(item, 'key'), 25), '->', _.get(item, 'name'))
|
|
41
|
+
})
|
|
42
|
+
return cb()
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Example with array of objects
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
let existingConfig = {
|
|
50
|
+
redis: {
|
|
51
|
+
databases: [
|
|
52
|
+
{ db: 0, name: 'cache' },
|
|
53
|
+
{ db: 1, name: 'auth' }
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// secret stored under "redis.cacheServer"
|
|
59
|
+
let secret = {
|
|
60
|
+
host: 'my-secret-server'
|
|
61
|
+
|
|
62
|
+
// secert storend under "redis.authServer"
|
|
63
|
+
let secret = {
|
|
64
|
+
host: 'my-auth-server
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// now use the function
|
|
68
|
+
const secretParams = {
|
|
69
|
+
aws: {
|
|
70
|
+
accessKeyId: 'accessKeyId',
|
|
71
|
+
secretAccessKey: 'secretAccessKey',
|
|
72
|
+
region: 'eu-central-1'
|
|
73
|
+
},
|
|
74
|
+
secrets: [
|
|
75
|
+
{ key: 'redis.databases', name: 'redis.cacheServer', servers: { identifier: 'name', value: 'cache' } }
|
|
76
|
+
{ key: 'redis.databases', name: 'redis.authServer', servers: { identifier: 'name', value: 'auth' } }
|
|
77
|
+
],
|
|
78
|
+
config: existingConfig,
|
|
79
|
+
environment: 'development'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
awsSecrets.loadSecrets(secretParams, (err, result) => {
|
|
83
|
+
// now
|
|
84
|
+
redis.databases: [
|
|
85
|
+
{ db: 0, name: 'cache', host: 'my-secret-server' },
|
|
86
|
+
{ db: 1, name: 'auth', host: 'my-auth-server' }
|
|
87
|
+
]
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Parameters
|
|
93
|
+
+ aws - object with accessKeyId, secretAccessKey and region (IAM user must have permission to read the secrets)
|
|
94
|
+
+ secrets - array of secrets to fetch
|
|
95
|
+
+ config - the current config (secrets will be merged into it)
|
|
96
|
+
+ environment - the current node environment (e.g. test, production, ... defaults to development)
|
|
97
|
+
|
|
98
|
+
## Links
|
|
99
|
+
- [Website](https://www.admiralcloud.com/)
|
|
100
|
+
- [Twitter (@admiralcloud)](https://twitter.com/admiralcloud)
|
|
101
|
+
- [Facebook](https://www.facebook.com/MediaAssetManagement/)
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
[MIT License](https://opensource.org/licenses/MIT) Copyright © 2009-present, AdmiralCloud, Mark Poepping
|
package/index.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
const _ = require('lodash')
|
|
2
|
+
const async = require('async')
|
|
3
|
+
const AWS = require('aws-sdk')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param aws OBJ object with aws configuration data
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const awsSecrets = () => {
|
|
10
|
+
const loadSecrets = (params, cb) => {
|
|
11
|
+
const multiSecrets = _.get(params, 'multisecrets', [])
|
|
12
|
+
const secrets = _.get(params, 'secrets', [])
|
|
13
|
+
let config = _.get(params, 'config', {}) // the config object -> will be changed by reference
|
|
14
|
+
const environment = _.get(config, 'environment', 'development')
|
|
15
|
+
|
|
16
|
+
const region = _.get(params, 'aws.region', 'eu-central-1')
|
|
17
|
+
const endpoint = _.find(awsEndpoints, { region })
|
|
18
|
+
const client = new AWS.SecretsManager({
|
|
19
|
+
accessKeyId: _.get(params, 'aws.accessKeyId'),
|
|
20
|
+
secretAccessKey: _.get(params, 'aws.secretAccessKey'),
|
|
21
|
+
region: region,
|
|
22
|
+
endpoint: _.get(endpoint, 'endpoint')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
let result = []
|
|
26
|
+
async.series({
|
|
27
|
+
fetchPlacholders: (done) => {
|
|
28
|
+
if (!_.size(multiSecrets)) return done()
|
|
29
|
+
// some keys can have multiple entries (e.g. cloudfrontCOnfigs can have 1 - n entries)
|
|
30
|
+
// we have to fetch them first from a secret and add them to the secrets to fetch
|
|
31
|
+
|
|
32
|
+
async.each(multiSecrets, (secret, itDone) => {
|
|
33
|
+
let secretName = (config.environment === 'test' ? 'test.' : '') + _.get(secret, 'name')
|
|
34
|
+
|
|
35
|
+
client.getSecretValue({ SecretId: secretName }, function(err, data) {
|
|
36
|
+
if (err) {
|
|
37
|
+
if (_.get(secret, 'ignoreInTestMode')) return itDone()
|
|
38
|
+
if (_.get(secret, 'ignoreIfMissing')) return itDone() // this is an optional key
|
|
39
|
+
|
|
40
|
+
console.error('Fetching secret %s failed', secretName, err)
|
|
41
|
+
return itDone({ message: err, additionalInfo: { key: secretName } })
|
|
42
|
+
}
|
|
43
|
+
if (!_.get(data, 'SecretString')) {
|
|
44
|
+
console.warn('Secret %s NOT avaialble', secretName, _.get(data, 'SecretString'))
|
|
45
|
+
return itDone()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let value
|
|
49
|
+
try {
|
|
50
|
+
value = JSON.parse(_.get(data, 'SecretString'))
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
value = _.get(data, 'SecretString.values')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
value = JSON.parse(_.get(value, 'values'))
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
return done({ message: 'placeHolderSecrets_valuesInvalid', additionalInfo: { key: secret.key } })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// value should be an array of keys
|
|
64
|
+
_.forEach(value, (item) => {
|
|
65
|
+
secrets.push({
|
|
66
|
+
key: secret.key,
|
|
67
|
+
name: item,
|
|
68
|
+
type: 'arrayObject'
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
return itDone()
|
|
72
|
+
})
|
|
73
|
+
}, done)
|
|
74
|
+
},
|
|
75
|
+
fetchSecrets: (done) => {
|
|
76
|
+
if (!_.size(secrets)) return done()
|
|
77
|
+
async.each(secrets, (secret, itDone) => {
|
|
78
|
+
if (environment === 'test' && _.get(secret, 'ignoreInTestMode')) return itDone()
|
|
79
|
+
// key is the local configuration path
|
|
80
|
+
let key = _.get(secret, 'key')
|
|
81
|
+
// secret name is the name used to fetch the secret
|
|
82
|
+
let secretName = (config.environment === 'test' ? 'test.' : '') + _.get(secret, 'name') + (_.get(secret, 'suffix') ? '.' + _.get(secret, 'suffix') : '')
|
|
83
|
+
|
|
84
|
+
client.getSecretValue({ SecretId: secretName }, function(err, data) {
|
|
85
|
+
if (err) {
|
|
86
|
+
if (_.get(secret, 'ignoreIfMissing')) return itDone() // this is an optional key
|
|
87
|
+
|
|
88
|
+
console.error('Fetching secret %s failed', secretName, _.get(err, 'message', err))
|
|
89
|
+
return itDone({ message: _.get(err, 'message', err), additionalInfo: { key: secretName } })
|
|
90
|
+
}
|
|
91
|
+
if (!_.get(data, 'SecretString')) {
|
|
92
|
+
console.warn('Secret %s NOT avaialble', secretName, _.get(data, 'SecretString'))
|
|
93
|
+
return itDone()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let value
|
|
97
|
+
try {
|
|
98
|
+
value = JSON.parse(_.get(data, 'SecretString'))
|
|
99
|
+
|
|
100
|
+
// if value is prefixed with JSON -> parse the value
|
|
101
|
+
if (_.get(value, 'valueHasJSON')) {
|
|
102
|
+
_.forEach(value, (val, key) => {
|
|
103
|
+
if (_.startsWith(val, 'JSON:')) {
|
|
104
|
+
try {
|
|
105
|
+
_.set(value, key, JSON.parse(val.substr(5)))
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
throw e
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
// remove that entry
|
|
113
|
+
_.unset(value, 'valueHasJSON')
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
value = _.get(data, 'SecretString')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// make sure boolean values are converted (from string)
|
|
121
|
+
value = _.mapValues(value, (val) => {
|
|
122
|
+
if (val === 'true') return true
|
|
123
|
+
else if (val === 'false') return false
|
|
124
|
+
else return val
|
|
125
|
+
})
|
|
126
|
+
let existingValue = _.get(config, key, {})
|
|
127
|
+
|
|
128
|
+
if (secret.servers) {
|
|
129
|
+
if (_.isBoolean(secret.servers)) {
|
|
130
|
+
// LEGACY SUPPORT FOR OLD NOTATION - DEPRECATED - DO NOT USE ANY LONGER
|
|
131
|
+
existingValue = _.find(_.get(config, key + '.servers', []), { server: secret.serverName })
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// NEW NOTATION AS OBJECT
|
|
135
|
+
let match = {}
|
|
136
|
+
_.set(match, _.get(secret.servers, 'identifier'), _.get(secret.servers, 'value'))
|
|
137
|
+
existingValue = _.find(_.get(config, key, []), match)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (_.get(secret, 'type') === 'array') {
|
|
141
|
+
let array = []
|
|
142
|
+
_.forEach(value, (val) => {
|
|
143
|
+
array.push(val)
|
|
144
|
+
})
|
|
145
|
+
value = _.concat(existingValue, array)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (_.get(secret, 'type') === 'arrayObject') {
|
|
149
|
+
existingValue.push(value)
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
let setFresh
|
|
153
|
+
if (_.isEmpty(existingValue)) setFresh = true
|
|
154
|
+
_.merge(existingValue, value)
|
|
155
|
+
// setFresh -> this property/path has never existed
|
|
156
|
+
if (setFresh) _.set(config, key, existingValue)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (_.get(secret, 'log')) {
|
|
160
|
+
console.log(_.repeat('.', 90))
|
|
161
|
+
console.warn(key, existingValue)
|
|
162
|
+
console.warn(_.get(config, key))
|
|
163
|
+
console.warn(_.repeat('.', 90))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
result.push({ key, name: _.get(secret, 'name', '-') })
|
|
167
|
+
return itDone()
|
|
168
|
+
})
|
|
169
|
+
}, done)
|
|
170
|
+
}
|
|
171
|
+
}, (err) => {
|
|
172
|
+
return cb(err, _.orderBy(result, 'key'))
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const awsEndpoints = [
|
|
177
|
+
{ region: 'eu-central-1', endpoint: 'https://secretsmanager.eu-central-1.amazonaws.com' }
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
loadSecrets
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = awsSecrets()
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ac-awssecrets",
|
|
3
|
+
"author": "Mark Poepping (https://www.mmpro.de)",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": "mmpro/ac-awssecrets",
|
|
6
|
+
"version": "1.1.3",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"async": "^3.2.1",
|
|
9
|
+
"aws-sdk": "^2.992.0",
|
|
10
|
+
"lodash": "^4.17.21"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"ac-semantic-release": "^0.2.6",
|
|
14
|
+
"eslint": "7.x",
|
|
15
|
+
"mocha": "^9.1.1"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "mocha --reporter spec"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=8.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|