node-vault-client 2.0.1 → 2.1.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.
@@ -3,7 +3,6 @@
3
3
  const VaultBaseAuth = require('./VaultBaseAuth');
4
4
  const aws4 = require('aws4');
5
5
  const { fromNodeProviderChain } = require('@aws-sdk/credential-providers');
6
- const _ = require('lodash');
7
6
  const errors = require('../errors');
8
7
 
9
8
  /**
@@ -49,7 +48,8 @@ class VaultIAMAuth extends VaultBaseAuth {
49
48
  * @typedef {Object} VaultIAMAuthConfig
50
49
  * @property {String} role - Role name of the auth/{mount}/role/{name} backend.
51
50
  * @property {AWSCredentials} [credentials] - Optional. AWS Credentials
52
- * @property {String} [namespace] - Optional. Vault namespace, sent as the X-Vault-Namespace header.
51
+ * @property {String} [namespace] - Optional. Vault namespace. Applied as the X-Vault-Namespace
52
+ * header to every request by {@link VaultApiClient}; see {@link VaultClient#constructor}.
53
53
  * @property {String} [iam_server_id_header_value] - Optional. Header's value X-Vault-AWS-IAM-Server-ID.
54
54
  * @property {String} [region] - Optional. AWS region used to sign the STS GetCallerIdentity
55
55
  * request. When set, the request is signed against the regional STS endpoint
@@ -65,9 +65,12 @@ class VaultIAMAuth extends VaultBaseAuth {
65
65
  constructor(api, logger, config, mount) {
66
66
  super(api, logger, mount || 'aws');
67
67
 
68
+ if (!config || !config.role) {
69
+ throw new errors.InvalidArgumentsError('"role" should be provided for VaultIAMAuth');
70
+ }
71
+
68
72
  this.__role = config.role;
69
73
  this.__iam_server_id_header_value = config.iam_server_id_header_value;
70
- this.__namespace = config.namespace;
71
74
  this.__region = config.region;
72
75
 
73
76
 
@@ -86,29 +89,19 @@ class VaultIAMAuth extends VaultBaseAuth {
86
89
  this.__role
87
90
  );
88
91
 
89
- var headers = {};
90
-
91
- if (this.__namespace) {
92
- headers = {
93
- 'X-Vault-Namespace': this.__namespace,
94
- }
95
- }
96
-
97
-
98
92
  return Promise.resolve()
99
93
  .then(() => this.__getCredentials())
100
94
  .then((credentials) => {
101
95
  return this.__apiClient.makeRequest(
102
96
  'POST',
103
97
  `/auth/${this._mount}/login`,
104
- this.__getVaultAuthRequestBody(this.__getStsRequest(credentials)),
105
- headers
98
+ this.__getVaultAuthRequestBody(this.__getStsRequest(credentials))
106
99
  );
107
100
  })
108
101
  .then((response) => {
109
102
  this._log.debug(
110
- 'receive token: %s',
111
- response.auth.client_token
103
+ 'received token (accessor=%s)',
104
+ response.auth.accessor
112
105
  );
113
106
  return this._getTokenEntity(response.auth.client_token)
114
107
  })
@@ -194,7 +187,9 @@ class VaultIAMAuth extends VaultBaseAuth {
194
187
  * @private
195
188
  */
196
189
  __headersLikeGolangStyle(headers) {
197
- return _.mapValues(headers, (value) => [`${value}`]);
190
+ return Object.fromEntries(
191
+ Object.entries(headers).map(([key, value]) => [key, [`${value}`]])
192
+ );
198
193
  }
199
194
 
200
195
  _validateCredentialsConfig(credentials) {
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const VaultBaseAuth = require('./VaultBaseAuth');
3
+ const errors = require('../errors');
3
4
 
4
5
  class VaultKubernetesAuth extends VaultBaseAuth {
5
6
  /**
@@ -8,11 +9,17 @@ class VaultKubernetesAuth extends VaultBaseAuth {
8
9
  * @param {Object} config
9
10
  * @param {String} config.role - Role configured in Vault Kubernetes Auth backend under which we want to issue Vault token.
10
11
  * @param {String} [config.tokenPath] - Path to the Kube JWT token. If omitted - default will be used.
12
+ * @param {String} [config.namespace] - Optional. Vault namespace. Applied as the X-Vault-Namespace
13
+ * header to every request by {@link VaultApiClient}; see {@link VaultClient#constructor}.
11
14
  * @param {String} mount - Vault's mount point ("kubernetes" by default)
12
15
  */
13
16
  constructor(apiClient, logger, config, mount) {
14
17
  super(apiClient, logger, mount || 'kubernetes');
15
18
 
19
+ if (!config || !config.role) {
20
+ throw new errors.InvalidArgumentsError('"role" should be provided for VaultKubernetesAuth');
21
+ }
22
+
16
23
  this.__role = config.role;
17
24
  this.__tokenPath = '/var/run/secrets/kubernetes.io/serviceaccount/token';
18
25
  if (config.tokenPath !== undefined) {
@@ -15,7 +15,7 @@ class VaultTokenAuth extends VaultBaseAuth {
15
15
  constructor(apiClient, logger, config, mount) {
16
16
  super(apiClient, logger, mount || 'token');
17
17
 
18
- if (!config.token) {
18
+ if (!config || !config.token) {
19
19
  throw new errors.InvalidArgumentsError('Auth token should be provided for VaultTokenAuth');
20
20
  }
21
21
 
package/src/errors.js CHANGED
@@ -1,8 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  class VaultError extends Error {
4
- constructor(message) {
5
- super(message);
4
+ /**
5
+ * @param {string} message
6
+ * @param {{cause?: *}} [options] - standard `Error` options. Pass `{ cause }` to chain the
7
+ * underlying error (e.g. a wrapped HTTP/transport failure); it is exposed as `error.cause`.
8
+ */
9
+ constructor(message, options) {
10
+ super(message, options);
6
11
  this.name = this.constructor.name;
7
12
  this.message = message;
8
13
 
@@ -12,10 +17,35 @@ class VaultError extends Error {
12
17
  class InvalidArgumentsError extends VaultError {}
13
18
  class InvalidAWSCredentialsError extends InvalidArgumentsError {}
14
19
  class AuthTokenExpiredError extends VaultError {}
20
+ class UnsupportedOperationError extends VaultError {}
21
+
22
+ /**
23
+ * A non-2xx HTTP response from the Vault server.
24
+ *
25
+ * Preserves the legacy plain-`Error` shape previously thrown by
26
+ * {@link VaultApiClient#makeRequest} — the message stays `"<status> - <raw body text>"` and the
27
+ * `statusCode`/`error` properties keep their meaning — while letting callers
28
+ * `instanceof`-check HTTP failures against the `VaultError` hierarchy.
29
+ */
30
+ class VaultHttpError extends VaultError {
31
+ /**
32
+ * @param {number} statusCode - HTTP status code of the response.
33
+ * @param {string} text - raw response body text, used in the message.
34
+ * @param {*} [body] - parsed JSON body when the response was valid JSON, the raw text
35
+ * otherwise, or `undefined` for an empty body. Exposed as `error.error`.
36
+ */
37
+ constructor(statusCode, text, body) {
38
+ super(`${statusCode} - ${text}`);
39
+ this.statusCode = statusCode;
40
+ this.error = body;
41
+ }
42
+ }
15
43
 
16
44
  module.exports = {
17
45
  VaultError,
18
46
  InvalidArgumentsError,
19
47
  InvalidAWSCredentialsError,
20
48
  AuthTokenExpiredError,
49
+ UnsupportedOperationError,
50
+ VaultHttpError,
21
51
  };
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Pure KV path-rewriting and response-normalizing helpers.
5
+ * No I/O; fully testable in isolation.
6
+ *
7
+ * rewritePath(version, op, mount, logicalPath) -> apiPath
8
+ * normalizeResponse(version, op, body) -> normalized body
9
+ */
10
+
11
+ // Map of op -> v2 path segment inserted between mount and logical path.
12
+ const V2_SEGMENT = {
13
+ read: 'data',
14
+ write: 'data',
15
+ delete: 'data',
16
+ update: 'data',
17
+ list: 'metadata',
18
+ readMetadata: 'metadata',
19
+ deleteMetadata: 'metadata',
20
+ deleteVersions: 'delete',
21
+ undeleteVersions:'undelete',
22
+ destroyVersions: 'destroy',
23
+ };
24
+
25
+ /**
26
+ * Rewrite a logical path to the correct API path for the given engine version.
27
+ *
28
+ * @param {number} version - Engine version (2 = KV v2; anything else = passthrough)
29
+ * @param {string} op - Operation name (read, write, list, delete, update, …)
30
+ * @param {string} mount - Mount point (e.g. "secret" or "secret/")
31
+ * @param {string} logicalPath - Path relative to the mount (e.g. "team/svc")
32
+ * @returns {string}
33
+ */
34
+ function rewritePath(version, op, mount, logicalPath) {
35
+ // Normalise trailing slash on mount
36
+ const m = mount.replace(/\/+$/, '');
37
+
38
+ if (version !== 2) {
39
+ // v1 / non-kv: simple concatenation
40
+ return logicalPath ? `${m}/${logicalPath}` : m;
41
+ }
42
+
43
+ const segment = V2_SEGMENT[op] || 'data';
44
+ return `${m}/${segment}/${logicalPath}`;
45
+ }
46
+
47
+ /**
48
+ * Normalise a Vault API response body for the given engine version and operation.
49
+ *
50
+ * For v1 / non-kv the body is always returned as-is.
51
+ * For v2:
52
+ * - read: body.data is replaced with body.data.data; body.metadata is set to body.data.metadata
53
+ * - readMetadata: body.data is replaced with the original body.data (unwrapped one level)
54
+ * - write/delete/update/…: body returned as-is
55
+ *
56
+ * @param {number} version
57
+ * @param {string} op
58
+ * @param {*} body
59
+ * @returns {*}
60
+ */
61
+ function normalizeResponse(version, op, body) {
62
+ if (version !== 2) {
63
+ return body;
64
+ }
65
+
66
+ if (op === 'read') {
67
+ if (!body || !body.data) {
68
+ return body;
69
+ }
70
+ // Promote inner data/metadata to top-level response fields
71
+ const result = Object.assign({}, body);
72
+ result.metadata = body.data.metadata;
73
+ result.data = body.data.data;
74
+ return result;
75
+ }
76
+
77
+ if (op === 'readMetadata') {
78
+ if (!body || !body.data) {
79
+ return body;
80
+ }
81
+ const result = Object.assign({}, body);
82
+ result.data = body.data;
83
+ return result;
84
+ }
85
+
86
+ // All other v2 ops (write, list, delete, update, deleteVersions, …): passthrough
87
+ return body;
88
+ }
89
+
90
+ module.exports = { rewritePath, normalizeResponse };