node-vault-client 0.6.1 → 1.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.
@@ -1,140 +1,140 @@
1
- 'use strict';
2
-
3
- const lt = require('long-timeout');
4
- const prettyMs = require('pretty-ms');
5
- const AuthToken = require('./AuthToken');
6
- const errors = require('../errors');
7
-
8
- class VaultBaseAuth {
9
-
10
- /**
11
- * @param {VaultApiClient} apiClient
12
- * @param {Object} logger
13
- * @param {String} mount - Vault's mount point
14
- */
15
- constructor(apiClient, logger, mount) {
16
- this.__apiClient = apiClient;
17
- /** @protected */
18
- this._log = logger;
19
- this._mount = mount;
20
-
21
- /** @type AuthToken */
22
- this.__authToken = null;
23
- this.__refreshTimeout = null;
24
- }
25
-
26
- /**
27
- * @protected
28
- * @returns {Promise<AuthToken>}
29
- */
30
- _authenticate() {
31
- throw new Error('Method should be overridden');
32
- }
33
-
34
- getAuthToken() {
35
- if (this.__authToken === null || (this.__authToken instanceof AuthToken && this.__authToken.isExpired() && this._reauthenticationAllowed())) {
36
- if (this.__authToken !== null && this.__authToken.isExpired() && !this._reauthenticationAllowed()) {
37
- throw new errors.AuthTokenExpiredError('Auth token has expired & cannot be refreshed since auth method doesn\'t support this.');
38
- }
39
-
40
- this._log.info('getting auth token (mount=%s)', this._mount);
41
-
42
- const tokenPromise = this._authenticate().then(authToken => {
43
- this.__authToken = authToken;
44
-
45
- if (this.__authToken.isRenewable()) {
46
- this._log.debug(
47
- 'setting refresh timer for token %s',
48
- authToken.getId()
49
- );
50
- this.__setupTokenRefreshTimer(this.__authToken);
51
- }
52
-
53
- return this.__authToken;
54
- }).catch(e => {
55
- this.__authToken = null;
56
- throw e;
57
- });
58
-
59
- this.__authToken = tokenPromise;
60
- return tokenPromise;
61
- }
62
-
63
- this._log.debug('token already exist');
64
- return Promise.resolve(this.__authToken);
65
- }
66
-
67
- /**
68
- * @protected
69
- * @returns {Promise<AuthToken>}
70
- */
71
- _getTokenEntity(tokenId) {
72
- return this.__apiClient.makeRequest('GET', '/auth/token/lookup-self', null, {'X-Vault-Token': tokenId})
73
- .then(res => {
74
- return AuthToken.fromResponse(res);
75
- });
76
- }
77
-
78
- /**
79
- * @protected
80
- * @returns {boolean}
81
- */
82
- _reauthenticationAllowed() {
83
- return true;
84
- }
85
-
86
- /**
87
- * @param {AuthToken} authToken
88
- * @private
89
- */
90
- __setupTokenRefreshTimer(authToken) {
91
- if (this.__refreshTimeout !== null) {
92
- lt.clearTimeout(this.__refreshTimeout);
93
- this.__refreshTimeout = null;
94
- }
95
-
96
- if (!authToken.isRenewable() || authToken.isExpired()) {
97
- return;
98
- }
99
-
100
- const timer = Math.max((authToken.getExpiresAt() - Math.floor(Date.now() / 1000)) / 2, 1) * 1000;
101
-
102
- this.__refreshTimeout = lt.setTimeout(() => {
103
- this.__renewToken(authToken).then(authToken => {
104
- this.__authToken = authToken;
105
- this.__setupTokenRefreshTimer(authToken);
106
- }).catch(err => {
107
- this.__setupTokenRefreshTimer(authToken);
108
-
109
- this._log.error(`Cannot refresh auth token with "${authToken.getAccessor()}" accessor. Error: ${err.message}`);
110
- this._log.error(err);
111
- });
112
- }, timer);
113
-
114
- this._log.debug(
115
- 'sleeping for %s',
116
- prettyMs(timer)
117
- );
118
- }
119
-
120
- /**
121
- * @param {AuthToken} authToken
122
- * @returns {Promise.<AuthToken>}
123
- * @private
124
- */
125
- __renewToken(authToken) {
126
- this._log.debug('renewing vault token');
127
-
128
- return this.__apiClient.makeRequest('POST', '/auth/token/renew-self', null, {'X-Vault-Token': authToken.getId()})
129
- .then(() => {
130
- this._log.info('successfully renewed token');
131
- return this._getTokenEntity(authToken.getId());
132
- })
133
- .catch((reason) => {
134
- this._log.error('token renew failed: %s', reason.message);
135
- throw reason;
136
- });
137
- }
138
- }
139
-
140
- module.exports = VaultBaseAuth;
1
+ 'use strict';
2
+
3
+ const lt = require('long-timeout');
4
+ const prettyMs = require('pretty-ms');
5
+ const AuthToken = require('./AuthToken');
6
+ const errors = require('../errors');
7
+
8
+ class VaultBaseAuth {
9
+
10
+ /**
11
+ * @param {VaultApiClient} apiClient
12
+ * @param {Object} logger
13
+ * @param {String} mount - Vault's mount point
14
+ */
15
+ constructor(apiClient, logger, mount) {
16
+ this.__apiClient = apiClient;
17
+ /** @protected */
18
+ this._log = logger;
19
+ this._mount = mount;
20
+
21
+ /** @type AuthToken */
22
+ this.__authToken = null;
23
+ this.__refreshTimeout = null;
24
+ }
25
+
26
+ /**
27
+ * @protected
28
+ * @returns {Promise<AuthToken>}
29
+ */
30
+ _authenticate() {
31
+ throw new Error('Method should be overridden');
32
+ }
33
+
34
+ getAuthToken() {
35
+ if (this.__authToken === null || (this.__authToken instanceof AuthToken && this.__authToken.isExpired() && this._reauthenticationAllowed())) {
36
+ if (this.__authToken !== null && this.__authToken.isExpired() && !this._reauthenticationAllowed()) {
37
+ throw new errors.AuthTokenExpiredError('Auth token has expired & cannot be refreshed since auth method doesn\'t support this.');
38
+ }
39
+
40
+ this._log.info('getting auth token (mount=%s)', this._mount);
41
+
42
+ const tokenPromise = this._authenticate().then(authToken => {
43
+ this.__authToken = authToken;
44
+
45
+ if (this.__authToken.isRenewable()) {
46
+ this._log.debug(
47
+ 'setting refresh timer for token %s',
48
+ authToken.getId()
49
+ );
50
+ this.__setupTokenRefreshTimer(this.__authToken);
51
+ }
52
+
53
+ return this.__authToken;
54
+ }).catch(e => {
55
+ this.__authToken = null;
56
+ throw e;
57
+ });
58
+
59
+ this.__authToken = tokenPromise;
60
+ return tokenPromise;
61
+ }
62
+
63
+ this._log.debug('token already exist');
64
+ return Promise.resolve(this.__authToken);
65
+ }
66
+
67
+ /**
68
+ * @protected
69
+ * @returns {Promise<AuthToken>}
70
+ */
71
+ _getTokenEntity(tokenId) {
72
+ return this.__apiClient.makeRequest('GET', '/auth/token/lookup-self', null, {'X-Vault-Token': tokenId})
73
+ .then(res => {
74
+ return AuthToken.fromResponse(res);
75
+ });
76
+ }
77
+
78
+ /**
79
+ * @protected
80
+ * @returns {boolean}
81
+ */
82
+ _reauthenticationAllowed() {
83
+ return true;
84
+ }
85
+
86
+ /**
87
+ * @param {AuthToken} authToken
88
+ * @private
89
+ */
90
+ __setupTokenRefreshTimer(authToken) {
91
+ if (this.__refreshTimeout !== null) {
92
+ lt.clearTimeout(this.__refreshTimeout);
93
+ this.__refreshTimeout = null;
94
+ }
95
+
96
+ if (!authToken.isRenewable() || authToken.isExpired()) {
97
+ return;
98
+ }
99
+
100
+ const timer = Math.max((authToken.getExpiresAt() - Math.floor(Date.now() / 1000)) / 2, 1) * 1000;
101
+
102
+ this.__refreshTimeout = lt.setTimeout(() => {
103
+ this.__renewToken(authToken).then(authToken => {
104
+ this.__authToken = authToken;
105
+ this.__setupTokenRefreshTimer(authToken);
106
+ }).catch(err => {
107
+ this.__setupTokenRefreshTimer(authToken);
108
+
109
+ this._log.error(`Cannot refresh auth token with "${authToken.getAccessor()}" accessor. Error: ${err.message}`);
110
+ this._log.error(err);
111
+ });
112
+ }, timer);
113
+
114
+ this._log.debug(
115
+ 'sleeping for %s',
116
+ prettyMs(timer)
117
+ );
118
+ }
119
+
120
+ /**
121
+ * @param {AuthToken} authToken
122
+ * @returns {Promise.<AuthToken>}
123
+ * @private
124
+ */
125
+ __renewToken(authToken) {
126
+ this._log.debug('renewing vault token');
127
+
128
+ return this.__apiClient.makeRequest('POST', '/auth/token/renew-self', null, {'X-Vault-Token': authToken.getId()})
129
+ .then(() => {
130
+ this._log.info('successfully renewed token');
131
+ return this._getTokenEntity(authToken.getId());
132
+ })
133
+ .catch((reason) => {
134
+ this._log.error('token renew failed: %s', reason.message);
135
+ throw reason;
136
+ });
137
+ }
138
+ }
139
+
140
+ module.exports = VaultBaseAuth;
@@ -1,182 +1,187 @@
1
- 'use strict';
2
-
3
- const VaultBaseAuth = require('./VaultBaseAuth');
4
- const aws4 = require('aws4');
5
- const _ = require('lodash');
6
- const errors = require('../errors');
7
-
8
- /**
9
- * Implementation of AWS Auth Backend :: IAM Authentication Method
10
- * @link https://www.vaultproject.io/docs/auth/aws.html#iam-authentication-method
11
- *
12
- * @usage
13
- *
14
- * ```bash
15
- * vault write auth/aws/config/client secret_key=AWS_SECRET_KEY access_key=AWS_ACCESS_KEY
16
- * vault write auth/aws/config/client iam_server_id_header_value=VAULT_ADDR
17
- * vault write auth/aws/role/iam_name_of_role auth_type=iam bound_iam_principal_arn=arn:aws:iam::.... max_ttl=500h
18
- * ```
19
- *
20
- * ```js
21
- *
22
- * VaultClient.boot('main', {
23
- * api: { url: VAULT_ADDR },
24
- * auth: {
25
- * type: 'iam',
26
- * mount: 'some_other_aws_mount_point', // Optional
27
- * config: {
28
- * role: 'my_iam_role',
29
- * iam_server_id_header_value: VAULT_ADDR, // Optional
30
- * namespace: 'some_namespace', // Optional
31
- * credentials: new AWS.Credentials({
32
- * accessKeyId: AWS_ACCESS_KEY,
33
- * secretAccessKey: AWS_SECRET_KEY,
34
- * })
35
- * }
36
- * }
37
- * })
38
- *
39
- * ```
40
- *
41
- */
42
- class VaultIAMAuth extends VaultBaseAuth {
43
- /**
44
- * @param {VaultApiClient} api - see {@link VaultBaseAuth#constructor}
45
- * @param {Object} logger
46
- * @param {Object} config
47
- * @param {String} config.role - Role name of the auth/{mount}/role/{name} backend.
48
- * @param {AWS.Credentials|AWS.Credentials[]} config.credentials {@see AWS.CredentialProviderChain providers}
49
- * @param {String} [config.iam_server_id_header_value] - Optional. Header's value X-Vault-AWS-IAM-Server-ID.
50
- * @param {String} mount - Vault's AWS Auth Backend mount point ("aws" by default)
51
- */
52
- constructor(api, logger, config, mount) {
53
- super(api, logger, mount || 'aws');
54
-
55
- const AWS = require('aws-sdk');
56
-
57
- this.__role = config.role;
58
- this.__iam_server_id_header_value = config.iam_server_id_header_value;
59
- this.__namespace = config.namespace;
60
-
61
- if (!(config.credentials instanceof AWS.Credentials) && !_.isArray(config.credentials)) {
62
- throw new errors.InvalidAWSCredentialsError('Credentials must be provided. {AWS.Credentials|AWS.Credentials[]} or function-providers, which return them.')
63
- }
64
-
65
- const credentialsProviders = _.isArray(config.credentials)
66
- ? config.credentials
67
- : [config.credentials];
68
-
69
- this.__credentialChain = new AWS.CredentialProviderChain(
70
- credentialsProviders
71
- );
72
- }
73
-
74
- /**
75
- * @inheritDoc
76
- */
77
- _authenticate() {
78
- this._log.info(
79
- 'making authentication request: role=%s',
80
- this.__role
81
- );
82
-
83
- var headers = {};
84
-
85
- if (this.__namespace) {
86
- headers = {
87
- 'X-Vault-Namespace': this.__namespace,
88
- }
89
- }
90
-
91
-
92
- return Promise.resolve()
93
- .then(() => this.__getCredentials())
94
- .then((credentials) => {
95
- return this.__apiClient.makeRequest(
96
- 'POST',
97
- `/auth/${this._mount}/login`,
98
- this.__getVaultAuthRequestBody(this.__getStsRequest(credentials)),
99
- headers
100
- );
101
- })
102
- .then((response) => {
103
- this._log.debug(
104
- 'receive token: %s',
105
- response.auth.client_token
106
- );
107
- return this._getTokenEntity(response.auth.client_token)
108
- })
109
- }
110
-
111
- /**
112
- * Credentials resolved by {@see AWS.CredentialProviderChain}
113
- *
114
- * @returns {Promise<AWS.Credentials>}
115
- * @private
116
- */
117
- __getCredentials() {
118
- return new Promise((resolve, reject) =>
119
- this.__credentialChain.resolve((err, credentials) =>
120
- err ? reject(err) : resolve(credentials)
121
- )
122
- );
123
- }
124
-
125
- /**
126
- * Prepare vault auth request body
127
- *
128
- * @param stsRequest
129
- * @returns {Object} {@link https://www.vaultproject.io/docs/auth/aws.html#via-the-api}
130
- * @private
131
- */
132
- __getVaultAuthRequestBody(stsRequest) {
133
- return {
134
- iam_http_request_method: stsRequest.method,
135
- iam_request_headers: this.__base64encode(
136
- JSON.stringify(this.__headersLikeGolangStyle(stsRequest.headers))
137
- ),
138
- iam_request_body: this.__base64encode(stsRequest.body),
139
- iam_request_url: this.__base64encode(`https://${stsRequest.hostname}${stsRequest.path}`),
140
- role: this.__role
141
- }
142
- }
143
-
144
- /**
145
- * Prepare signed request to AWS STS :: GetCallerIdentity
146
- *
147
- * @param credentials
148
- * @private
149
- */
150
- __getStsRequest(credentials) {
151
- return aws4.sign({
152
- service: 'sts',
153
- method: 'POST',
154
- body: 'Action=GetCallerIdentity&Version=2011-06-15',
155
- headers: this.__iam_server_id_header_value ? {
156
- 'X-Vault-AWS-IAM-Server-ID': this.__iam_server_id_header_value,
157
- } : {}
158
- }, credentials);
159
- }
160
-
161
- /**
162
- * @param string
163
- * @private
164
- */
165
- __base64encode(string) {
166
- return new Buffer(string).toString('base64')
167
- }
168
-
169
- /**
170
- * @link https://github.com/hashicorp/vault/issues/2810
171
- * @link https://golang.org/pkg/net/http/#Header
172
- *
173
- * @param {Object} headers
174
- * @returns {Object}
175
- * @private
176
- */
177
- __headersLikeGolangStyle(headers) {
178
- return _.mapValues(headers, (value) => [`${value}`]);
179
- }
180
- }
181
-
182
- module.exports = VaultIAMAuth;
1
+ 'use strict';
2
+
3
+ const VaultBaseAuth = require('./VaultBaseAuth');
4
+ const aws4 = require('aws4');
5
+ const { fromNodeProviderChain } = require('@aws-sdk/credential-providers');
6
+ const _ = require('lodash');
7
+ const errors = require('../errors');
8
+
9
+ /**
10
+ * Implementation of AWS Auth Backend :: IAM Authentication Method
11
+ * @link https://www.vaultproject.io/docs/auth/aws.html#iam-authentication-method
12
+ *
13
+ * @usage
14
+ *
15
+ * ```bash
16
+ * vault write auth/aws/config/client secret_key=AWS_SECRET_KEY access_key=AWS_ACCESS_KEY
17
+ * vault write auth/aws/config/client iam_server_id_header_value=VAULT_ADDR
18
+ * vault write auth/aws/role/iam_name_of_role auth_type=iam bound_iam_principal_arn=arn:aws:iam::.... max_ttl=500h
19
+ * ```
20
+ *
21
+ * ```js
22
+ *
23
+ * VaultClient.boot('main', {
24
+ * api: { url: VAULT_ADDR },
25
+ * auth: {
26
+ * type: 'iam',
27
+ * mount: 'some_other_aws_mount_point', // Optional
28
+ * config: {
29
+ * role: 'my_iam_role',
30
+ * iam_server_id_header_value: VAULT_ADDR, // Optional
31
+ * namespace: 'some_namespace', // Optional
32
+ * credentials: { // Optional
33
+ * accessKeyId: AWS_ACCESS_KEY,
34
+ * secretAccessKey: AWS_SECRET_KEY,
35
+ * },
36
+ * }
37
+ * }
38
+ * })
39
+ * ```
40
+ *
41
+ */
42
+ class VaultIAMAuth extends VaultBaseAuth {
43
+ /**
44
+ * @typedef AWSCredentials
45
+ * @property {String} accessKeyId
46
+ * @property {String} secretAccessKey
47
+ *
48
+ * @typedef {Object} VaultIAMAuthConfig
49
+ * @property {String} role - Role name of the auth/{mount}/role/{name} backend.
50
+ * @property [AWSCredentials] [credentials] - Optional. AWS Credentials
51
+ * @property {String} [iam_server_id_header_value] - Optional. Header's value X-Vault-AWS-IAM-Server-ID.
52
+ *
53
+ * @param {VaultApiClient} api - see {@link VaultBaseAuth#constructor}
54
+ * @param {Object} logger
55
+ * @param {VaultIAMAuthConfig} config
56
+ * @param {String} mount - Vault's AWS Auth Backend mount point ("aws" by default)
57
+ */
58
+ constructor(api, logger, config, mount) {
59
+ super(api, logger, mount || 'aws');
60
+
61
+ this.__role = config.role;
62
+ this.__iam_server_id_header_value = config.iam_server_id_header_value;
63
+ this.__namespace = config.namespace;
64
+
65
+
66
+ const { credentials } = config;
67
+ this._validateCredentialsConfig(credentials);
68
+
69
+ this.__credentialChain = credentials ? () => Promise.resolve(credentials) : fromNodeProviderChain();
70
+ }
71
+
72
+ /**
73
+ * @inheritDoc
74
+ */
75
+ _authenticate() {
76
+ this._log.info(
77
+ 'making authentication request: role=%s',
78
+ this.__role
79
+ );
80
+
81
+ var headers = {};
82
+
83
+ if (this.__namespace) {
84
+ headers = {
85
+ 'X-Vault-Namespace': this.__namespace,
86
+ }
87
+ }
88
+
89
+
90
+ return Promise.resolve()
91
+ .then(() => this.__getCredentials())
92
+ .then((credentials) => {
93
+ return this.__apiClient.makeRequest(
94
+ 'POST',
95
+ `/auth/${this._mount}/login`,
96
+ this.__getVaultAuthRequestBody(this.__getStsRequest(credentials)),
97
+ headers
98
+ );
99
+ })
100
+ .then((response) => {
101
+ this._log.debug(
102
+ 'receive token: %s',
103
+ response.auth.client_token
104
+ );
105
+ return this._getTokenEntity(response.auth.client_token)
106
+ })
107
+ }
108
+
109
+ /**
110
+ * Credentials resolved by {@see @aws-sdk/credential-providers}
111
+ *
112
+ * @returns {Promise<AwsCredentialIdentity>}
113
+ * @private
114
+ */
115
+ __getCredentials() {
116
+ return this.__credentialChain();
117
+ }
118
+
119
+ /**
120
+ * Prepare vault auth request body
121
+ *
122
+ * @param stsRequest
123
+ * @returns {Object} {@link https://www.vaultproject.io/docs/auth/aws.html#via-the-api}
124
+ * @private
125
+ */
126
+ __getVaultAuthRequestBody(stsRequest) {
127
+ return {
128
+ iam_http_request_method: stsRequest.method,
129
+ iam_request_headers: this.__base64encode(
130
+ JSON.stringify(this.__headersLikeGolangStyle(stsRequest.headers))
131
+ ),
132
+ iam_request_body: this.__base64encode(stsRequest.body),
133
+ iam_request_url: this.__base64encode(`https://${stsRequest.hostname}${stsRequest.path}`),
134
+ role: this.__role
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Prepare signed request to AWS STS :: GetCallerIdentity
140
+ *
141
+ * @param credentials
142
+ * @private
143
+ */
144
+ __getStsRequest(credentials) {
145
+ return aws4.sign({
146
+ service: 'sts',
147
+ method: 'POST',
148
+ body: 'Action=GetCallerIdentity&Version=2011-06-15',
149
+ headers: this.__iam_server_id_header_value ? {
150
+ 'X-Vault-AWS-IAM-Server-ID': this.__iam_server_id_header_value,
151
+ } : {}
152
+ }, credentials);
153
+ }
154
+
155
+ /**
156
+ * @param string
157
+ * @private
158
+ */
159
+ __base64encode(string) {
160
+ return new Buffer(string).toString('base64')
161
+ }
162
+
163
+ /**
164
+ * @link https://github.com/hashicorp/vault/issues/2810
165
+ * @link https://golang.org/pkg/net/http/#Header
166
+ *
167
+ * @param {Object} headers
168
+ * @returns {Object}
169
+ * @private
170
+ */
171
+ __headersLikeGolangStyle(headers) {
172
+ return _.mapValues(headers, (value) => [`${value}`]);
173
+ }
174
+
175
+ _validateCredentialsConfig(credentials) {
176
+ if (Array.isArray(credentials)) {
177
+ throw new errors.InvalidAWSCredentialsError('Invalid AWS credentials provided in config. See CHANGELOG if migating from 0.x.x');
178
+ }
179
+ if (credentials && typeof credentials === 'object') {
180
+ if (!credentials.secretAccessKey || !credentials.accessKeyId) {
181
+ throw new errors.InvalidAWSCredentialsError('Invalid AWS credentials provided in config: accessKeyId and secretAccessKey are required.');
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ module.exports = VaultIAMAuth;