node-vault-client 1.0.1 → 2.0.0
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/LICENSE.txt +200 -200
- package/README.md +337 -154
- package/package.json +64 -44
- package/src/Lease.js +57 -57
- package/src/VaultApiClient.js +94 -55
- package/src/VaultClient.js +288 -263
- package/src/VaultNodeConfig.js +104 -105
- package/src/auth/AuthToken.js +141 -94
- package/src/auth/VaultAppRoleAuth.js +49 -40
- package/src/auth/VaultBaseAuth.js +162 -140
- package/src/auth/VaultIAMAuth.js +212 -187
- package/src/auth/VaultKubernetesAuth.js +49 -48
- package/src/auth/VaultTokenAuth.js +35 -34
- package/src/errors.js +21 -21
package/src/VaultNodeConfig.js
CHANGED
|
@@ -1,105 +1,104 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const splitRes =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const splitRes =
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
module.exports = VaultNodeConfig;
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const Bluebird = require('bluebird');
|
|
5
|
+
const _ = require('lodash');
|
|
6
|
+
const assignDeep = require('assign-deep');
|
|
7
|
+
|
|
8
|
+
const errors = require('./errors');
|
|
9
|
+
|
|
10
|
+
class VaultNodeConfig {
|
|
11
|
+
|
|
12
|
+
constructor(vault) {
|
|
13
|
+
this.__vault = vault;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
require.resolve('config');
|
|
17
|
+
} catch {
|
|
18
|
+
throw new errors.VaultError(`NPM package "config" isn't installed`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.__nodeConfig = require('config');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Populates Vault's values to "node-config"
|
|
26
|
+
*/
|
|
27
|
+
populate() {
|
|
28
|
+
const substitutionMap = this.__getSubstitutionMap();
|
|
29
|
+
let requiredData = {};
|
|
30
|
+
|
|
31
|
+
this.__traverse(substitutionMap, (key, val) => {
|
|
32
|
+
const splitRes = val.split('#');
|
|
33
|
+
const path = splitRes[0], value = splitRes[1];
|
|
34
|
+
if (path === undefined || value === undefined) {
|
|
35
|
+
throw new errors.InvalidArgumentsError('Invalid format of substitution value');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
requiredData[path] = null;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
let promises = _.mapValues(requiredData, (value, path) => this.__vault.read(path));
|
|
42
|
+
|
|
43
|
+
return Bluebird.props(promises).then(results => {
|
|
44
|
+
requiredData = _.mapValues(requiredData, (value, path) => results[path].getData());
|
|
45
|
+
|
|
46
|
+
this.__traverse(substitutionMap, (key, val, obj) => {
|
|
47
|
+
const splitRes = val.split('#');
|
|
48
|
+
const path = splitRes[0], value = splitRes[1];
|
|
49
|
+
const res = requiredData[path][value];
|
|
50
|
+
if (res === undefined) {
|
|
51
|
+
throw new errors.VaultError(`Can't find substitution for "${val}"`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
obj[key] = requiredData[path][value];
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return assignDeep(this.__nodeConfig, substitutionMap);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
__getSubstitutionMap() {
|
|
65
|
+
let config_dir = this.__nodeConfig.util.initParam('NODE_CONFIG_DIR', path.join( process.cwd(), 'config') );
|
|
66
|
+
if (config_dir.indexOf('.') === 0) {
|
|
67
|
+
config_dir = path.join(process.cwd() , config_dir);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fullFilename = path.join(config_dir, 'custom-vault-variables.js');
|
|
71
|
+
|
|
72
|
+
let fileContent;
|
|
73
|
+
try {
|
|
74
|
+
fileContent = require(fullFilename);
|
|
75
|
+
} catch {
|
|
76
|
+
throw new errors.VaultError('Config file ' + fullFilename + ' cannot be read');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!_.isPlainObject(fileContent)) {
|
|
80
|
+
throw new errors.VaultError('Config file ' + fullFilename + ' should return plain object');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return _.cloneDeep(fileContent);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
__traverse(o, func) {
|
|
87
|
+
for (let i in o) {
|
|
88
|
+
if (!Object.hasOwn(o, i)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (o[i] !== null && typeof(o[i]) === "object") {
|
|
93
|
+
//going one step down in the object tree!!
|
|
94
|
+
this.__traverse(o[i], func);
|
|
95
|
+
} else if (typeof o[i] === 'string') {
|
|
96
|
+
func(i, o[i], o);
|
|
97
|
+
} else {
|
|
98
|
+
throw new errors.InvalidArgumentsError('Illegal key type for substitution map');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = VaultNodeConfig;
|
package/src/auth/AuthToken.js
CHANGED
|
@@ -1,94 +1,141 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
* @param {string}
|
|
10
|
-
* @param {
|
|
11
|
-
* @param {int}
|
|
12
|
-
* @param {int
|
|
13
|
-
* @param {int}
|
|
14
|
-
* @param {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
28
|
-
this.
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
31
|
-
this.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
* @
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
* @
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AuthToken {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param {string} id
|
|
9
|
+
* @param {string} accessor
|
|
10
|
+
* @param {int} createdAt - UNIX timestamp
|
|
11
|
+
* @param {int|null} expiresAt - UNIX timestamp
|
|
12
|
+
* @param {int} explicitMaxTtl - in seconds
|
|
13
|
+
* @param {int} numUses
|
|
14
|
+
* @param {boolean} isRenewable
|
|
15
|
+
*/
|
|
16
|
+
constructor(
|
|
17
|
+
id,
|
|
18
|
+
accessor,
|
|
19
|
+
createdAt,
|
|
20
|
+
expiresAt,
|
|
21
|
+
explicitMaxTtl,
|
|
22
|
+
numUses,
|
|
23
|
+
isRenewable
|
|
24
|
+
) {
|
|
25
|
+
this.__id = id;
|
|
26
|
+
this.__accessor = accessor;
|
|
27
|
+
this.__createdAt = createdAt;
|
|
28
|
+
this.__expiresAt = expiresAt;
|
|
29
|
+
this.__explicitMaxTtl = explicitMaxTtl;
|
|
30
|
+
this.__numUses = numUses;
|
|
31
|
+
this.__isRenewable = isRenewable;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static fromResponse(response) {
|
|
35
|
+
const data = response.data;
|
|
36
|
+
|
|
37
|
+
return new AuthToken(
|
|
38
|
+
data.id,
|
|
39
|
+
data.accessor,
|
|
40
|
+
data.creation_time,
|
|
41
|
+
AuthToken.__resolveExpiresAt(data),
|
|
42
|
+
data.explicit_max_ttl,
|
|
43
|
+
data.num_uses,
|
|
44
|
+
data.renewable !== undefined ? data.renewable : false
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the UNIX timestamp (seconds) at which the client should treat the token
|
|
50
|
+
* as expired, shifted earlier by a network-latency safety margin so renewal happens
|
|
51
|
+
* before the real expiry.
|
|
52
|
+
*
|
|
53
|
+
* Prefers Vault's authoritative `expire_time` (RFC3339), which is the documented
|
|
54
|
+
* absolute expiry. Falls back to `(last_renewal_time || creation_time) + ttl` for
|
|
55
|
+
* responses that don't carry `expire_time` — note `ttl` is the *remaining* lifetime,
|
|
56
|
+
* so that sum is only accurate when the lookup happens right after issuance/renewal.
|
|
57
|
+
*
|
|
58
|
+
* @private
|
|
59
|
+
* @param {Object} data - the `data` object of a token lookup response
|
|
60
|
+
* @returns {int|null} null when the token never expires (ttl === 0)
|
|
61
|
+
*/
|
|
62
|
+
static __resolveExpiresAt(data) {
|
|
63
|
+
if (data.ttl === 0) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const networkLatency = 60;
|
|
68
|
+
const margin = data.ttl > networkLatency ? networkLatency : 0;
|
|
69
|
+
|
|
70
|
+
const expireTime = AuthToken.__parseRfc3339Seconds(data.expire_time);
|
|
71
|
+
if (expireTime !== null) {
|
|
72
|
+
return expireTime - margin;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fallback for responses without a usable `expire_time`.
|
|
76
|
+
const creationTime = parseInt(data.last_renewal_time !== undefined ? data.last_renewal_time : data.creation_time, 10);
|
|
77
|
+
const ttl = data.ttl > networkLatency ? data.ttl - networkLatency : data.ttl;
|
|
78
|
+
return creationTime + parseInt(ttl, 10);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parse an RFC3339 timestamp (as returned by Vault, up to nanosecond precision with
|
|
83
|
+
* a timezone offset) into whole UNIX seconds. Returns null for missing, empty,
|
|
84
|
+
* unparseable, or non-positive (e.g. Vault's "0001-01-01T00:00:00Z" zero value) input.
|
|
85
|
+
*
|
|
86
|
+
* @private
|
|
87
|
+
* @param {String} value
|
|
88
|
+
* @returns {int|null}
|
|
89
|
+
*/
|
|
90
|
+
static __parseRfc3339Seconds(value) {
|
|
91
|
+
if (typeof value !== 'string' || value === '') {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const ms = Date.parse(value);
|
|
96
|
+
if (isNaN(ms)) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const seconds = Math.floor(ms / 1000);
|
|
101
|
+
return seconds > 0 ? seconds : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
getId() {
|
|
108
|
+
return this.__id;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getAccessor() {
|
|
112
|
+
return this.__accessor;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @returns {boolean}
|
|
117
|
+
*/
|
|
118
|
+
isExpired() {
|
|
119
|
+
if (this.__expiresAt === null) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return Math.floor(Date.now() / 1000) > this.__expiresAt;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @returns {boolean}
|
|
128
|
+
*/
|
|
129
|
+
isRenewable() {
|
|
130
|
+
return this.__isRenewable && this.getExpiresAt() !== null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @returns {int|null} UNIX timestamp
|
|
135
|
+
*/
|
|
136
|
+
getExpiresAt() {
|
|
137
|
+
return this.__expiresAt;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = AuthToken;
|
|
@@ -1,40 +1,49 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const VaultBaseAuth = require('./VaultBaseAuth');
|
|
4
|
-
|
|
5
|
-
class VaultAppRoleAuth extends VaultBaseAuth {
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @param {VaultApiClient} apiClient - see {@link VaultBaseAuth#constructor}
|
|
9
|
-
* @param {Object}
|
|
10
|
-
* @param {
|
|
11
|
-
* @param {String}
|
|
12
|
-
* @param {String}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const VaultBaseAuth = require('./VaultBaseAuth');
|
|
4
|
+
|
|
5
|
+
class VaultAppRoleAuth extends VaultBaseAuth {
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {VaultApiClient} apiClient - see {@link VaultBaseAuth#constructor}
|
|
9
|
+
* @param {Object} logger
|
|
10
|
+
* @param {Object} config
|
|
11
|
+
* @param {String} config.role_id - RoleID of the AppRole.
|
|
12
|
+
* @param {String} [config.secret_id] - required when bind_secret_id is enabled SecretID belonging to AppRole.
|
|
13
|
+
* @param {String} [config.namespace] - Optional. Vault namespace, sent as the X-Vault-Namespace header.
|
|
14
|
+
* @param {String} mount - Vault's mount point ("approle" by default)
|
|
15
|
+
*/
|
|
16
|
+
constructor(apiClient, logger, config, mount) {
|
|
17
|
+
super(apiClient, logger, mount || 'approle');
|
|
18
|
+
|
|
19
|
+
this.__roleId = config.role_id;
|
|
20
|
+
this.__secretId = config.secret_id;
|
|
21
|
+
this.__namespace = config.namespace;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_authenticate() {
|
|
25
|
+
this._log.info(
|
|
26
|
+
'making authentication request: role_id=%s',
|
|
27
|
+
this.__roleId
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const headers = {};
|
|
31
|
+
if (this.__namespace) {
|
|
32
|
+
headers['X-Vault-Namespace'] = this.__namespace;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return this.__apiClient.makeRequest('POST', `/auth/${this._mount}/login`, {
|
|
36
|
+
role_id: this.__roleId,
|
|
37
|
+
secret_id: this.__secretId,
|
|
38
|
+
}, headers).then(res => {
|
|
39
|
+
this._log.debug(
|
|
40
|
+
'receive token: %s',
|
|
41
|
+
res.auth.client_token
|
|
42
|
+
);
|
|
43
|
+
return this._getTokenEntity(res.auth.client_token);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = VaultAppRoleAuth;
|