@cubist-labs/cubesigner-sdk-secretsmanager-storage 0.4.97-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/README.md +8 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +125 -0
- package/package.json +34 -0
- package/src/index.ts +149 -0
- package/tsconfig.json +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# CubeSigner SDK session manager backed by AWS Secrets Manager
|
|
2
|
+
|
|
3
|
+
This package provides an AWS Secrets Manager-backed implementation of the
|
|
4
|
+
`SessionManager` interface from the CubeSigner SDK.
|
|
5
|
+
|
|
6
|
+
For more information, check out the
|
|
7
|
+
[@cubist-labs/cubesigner-sdk](https://www.npmjs.com/package/@cubist-labs/cubesigner-sdk)
|
|
8
|
+
NPM package.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type SessionData, type SessionManager, type SessionMetadata } from "@cubist-labs/cubesigner-sdk";
|
|
2
|
+
/** Options for AWS Secrets Manager-backed session manager */
|
|
3
|
+
interface AwsSecretSessionManagerOpts {
|
|
4
|
+
/**
|
|
5
|
+
* Limit the cache lifetime by the scheduled rotation of the secret (default: true)
|
|
6
|
+
*/
|
|
7
|
+
checkScheduledRotation?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Maximum amount of time that session data will be cached (default: use auth token lifetime to
|
|
10
|
+
* determine cache lifetime). This option is useful if the session is refreshed at some known
|
|
11
|
+
* interval (e.g., due to a secret rotation schedule).
|
|
12
|
+
*/
|
|
13
|
+
maxCacheLifetime?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A session manager that reads a token from a secret stored in AWS Secrets Manager.
|
|
17
|
+
*/
|
|
18
|
+
export declare class AwsSecretSessionManager implements SessionManager {
|
|
19
|
+
#private;
|
|
20
|
+
/**
|
|
21
|
+
* Constructor.
|
|
22
|
+
*
|
|
23
|
+
* @param secretId The name of the secret holding the token
|
|
24
|
+
* @param opts Options for the session manager
|
|
25
|
+
*/
|
|
26
|
+
constructor(secretId: string, opts?: AwsSecretSessionManagerOpts);
|
|
27
|
+
/** @inheritdoc */
|
|
28
|
+
onInvalidToken(): Promise<void>;
|
|
29
|
+
/** @inheritdoc */
|
|
30
|
+
metadata(): Promise<SessionMetadata>;
|
|
31
|
+
/** @inheritdoc */
|
|
32
|
+
token(): Promise<string>;
|
|
33
|
+
}
|
|
34
|
+
/** Manages session data stored in a secret in AWS Secrets Maanger */
|
|
35
|
+
export declare class AwsSecretManager {
|
|
36
|
+
#private;
|
|
37
|
+
/**
|
|
38
|
+
* Writes session data to the secret.
|
|
39
|
+
*
|
|
40
|
+
* @param session The session data to write
|
|
41
|
+
*/
|
|
42
|
+
update(session: SessionData): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Refreshes the session and writes the new session to AWS Secrets Manager.
|
|
45
|
+
*/
|
|
46
|
+
refresh(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Constructor.
|
|
49
|
+
*
|
|
50
|
+
* @param secretId The name of the secret holding the token
|
|
51
|
+
*/
|
|
52
|
+
constructor(secretId: string);
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,cAAc,EAEnB,KAAK,eAAe,EAIrB,MAAM,6BAA6B,CAAC;AAErC,6DAA6D;AAC7D,UAAU,2BAA2B;IACnC;;OAEG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,qBAAa,uBAAwB,YAAW,cAAc;;IAkB5D;;;;;OAKG;gBACS,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,2BAA2B;IAOhE,kBAAkB;IACZ,cAAc;IAIpB,kBAAkB;IACZ,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC;IAK1C,kBAAkB;IACZ,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;CAsC/B;AAED,qEAAqE;AACrE,qBAAa,gBAAgB;;IAM3B;;;;OAIG;IACG,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjD;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B;;;;OAIG;gBACS,QAAQ,EAAE,MAAM;CAI7B"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _AwsSecretSessionManager_instances, _AwsSecretSessionManager_opts, _AwsSecretSessionManager_sm, _AwsSecretSessionManager_secretArn, _AwsSecretSessionManager_cache, _AwsSecretSessionManager_getSessionData, _AwsSecretManager_sm, _AwsSecretManager_secretArn;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.AwsSecretManager = exports.AwsSecretSessionManager = void 0;
|
|
16
|
+
const client_secrets_manager_1 = require("@aws-sdk/client-secrets-manager");
|
|
17
|
+
const cubesigner_sdk_1 = require("@cubist-labs/cubesigner-sdk");
|
|
18
|
+
/**
|
|
19
|
+
* A session manager that reads a token from a secret stored in AWS Secrets Manager.
|
|
20
|
+
*/
|
|
21
|
+
class AwsSecretSessionManager {
|
|
22
|
+
/**
|
|
23
|
+
* Constructor.
|
|
24
|
+
*
|
|
25
|
+
* @param secretId The name of the secret holding the token
|
|
26
|
+
* @param opts Options for the session manager
|
|
27
|
+
*/
|
|
28
|
+
constructor(secretId, opts) {
|
|
29
|
+
_AwsSecretSessionManager_instances.add(this);
|
|
30
|
+
_AwsSecretSessionManager_opts.set(this, void 0);
|
|
31
|
+
/** Client for AWS Secrets Manager */
|
|
32
|
+
_AwsSecretSessionManager_sm.set(this, void 0);
|
|
33
|
+
/** ID of the secret */
|
|
34
|
+
_AwsSecretSessionManager_secretArn.set(this, void 0);
|
|
35
|
+
/** Cache for the session data */
|
|
36
|
+
_AwsSecretSessionManager_cache.set(this, void 0);
|
|
37
|
+
__classPrivateFieldSet(this, _AwsSecretSessionManager_sm, new client_secrets_manager_1.SecretsManager(), "f");
|
|
38
|
+
__classPrivateFieldSet(this, _AwsSecretSessionManager_secretArn, secretId, "f");
|
|
39
|
+
__classPrivateFieldSet(this, _AwsSecretSessionManager_cache, undefined, "f");
|
|
40
|
+
__classPrivateFieldSet(this, _AwsSecretSessionManager_opts, opts, "f");
|
|
41
|
+
}
|
|
42
|
+
/** @inheritdoc */
|
|
43
|
+
async onInvalidToken() {
|
|
44
|
+
__classPrivateFieldSet(this, _AwsSecretSessionManager_cache, undefined, "f");
|
|
45
|
+
}
|
|
46
|
+
/** @inheritdoc */
|
|
47
|
+
async metadata() {
|
|
48
|
+
const data = await __classPrivateFieldGet(this, _AwsSecretSessionManager_instances, "m", _AwsSecretSessionManager_getSessionData).call(this);
|
|
49
|
+
return (0, cubesigner_sdk_1.metadata)(data);
|
|
50
|
+
}
|
|
51
|
+
/** @inheritdoc */
|
|
52
|
+
async token() {
|
|
53
|
+
const data = await __classPrivateFieldGet(this, _AwsSecretSessionManager_instances, "m", _AwsSecretSessionManager_getSessionData).call(this);
|
|
54
|
+
return data.token;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.AwsSecretSessionManager = AwsSecretSessionManager;
|
|
58
|
+
_AwsSecretSessionManager_opts = new WeakMap(), _AwsSecretSessionManager_sm = new WeakMap(), _AwsSecretSessionManager_secretArn = new WeakMap(), _AwsSecretSessionManager_cache = new WeakMap(), _AwsSecretSessionManager_instances = new WeakSet(), _AwsSecretSessionManager_getSessionData =
|
|
59
|
+
/**
|
|
60
|
+
* Get the session data either from cache or from AWS Secrets Manager if no unexpired cached
|
|
61
|
+
* information is available.
|
|
62
|
+
*
|
|
63
|
+
* @returns The session data
|
|
64
|
+
*/
|
|
65
|
+
async function _AwsSecretSessionManager_getSessionData() {
|
|
66
|
+
if (__classPrivateFieldGet(this, _AwsSecretSessionManager_cache, "f") !== undefined && __classPrivateFieldGet(this, _AwsSecretSessionManager_cache, "f").exp > Date.now() / 1000) {
|
|
67
|
+
return __classPrivateFieldGet(this, _AwsSecretSessionManager_cache, "f").sessionData;
|
|
68
|
+
}
|
|
69
|
+
const res = await __classPrivateFieldGet(this, _AwsSecretSessionManager_sm, "f").getSecretValue({ SecretId: __classPrivateFieldGet(this, _AwsSecretSessionManager_secretArn, "f") });
|
|
70
|
+
const sessionData = (0, cubesigner_sdk_1.parseBase64SessionData)(res.SecretString);
|
|
71
|
+
const maxCacheLifetime = __classPrivateFieldGet(this, _AwsSecretSessionManager_opts, "f")?.maxCacheLifetime;
|
|
72
|
+
let exp = maxCacheLifetime !== undefined
|
|
73
|
+
? Math.min(Date.now() / 1000 + maxCacheLifetime, sessionData.session_info.auth_token_exp)
|
|
74
|
+
: sessionData.session_info.auth_token_exp;
|
|
75
|
+
// Limit cache lifetime by the next scheduled rotation if the user requested it
|
|
76
|
+
if (__classPrivateFieldGet(this, _AwsSecretSessionManager_opts, "f")?.checkScheduledRotation ?? true) {
|
|
77
|
+
const desc = await __classPrivateFieldGet(this, _AwsSecretSessionManager_sm, "f").describeSecret({ SecretId: __classPrivateFieldGet(this, _AwsSecretSessionManager_secretArn, "f") });
|
|
78
|
+
if (desc.NextRotationDate !== undefined) {
|
|
79
|
+
exp = Math.min(exp, desc.NextRotationDate.getTime() / 1000);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
__classPrivateFieldSet(this, _AwsSecretSessionManager_cache, {
|
|
83
|
+
sessionData,
|
|
84
|
+
exp,
|
|
85
|
+
}, "f");
|
|
86
|
+
return __classPrivateFieldGet(this, _AwsSecretSessionManager_cache, "f").sessionData;
|
|
87
|
+
};
|
|
88
|
+
/** Manages session data stored in a secret in AWS Secrets Maanger */
|
|
89
|
+
class AwsSecretManager {
|
|
90
|
+
/**
|
|
91
|
+
* Writes session data to the secret.
|
|
92
|
+
*
|
|
93
|
+
* @param session The session data to write
|
|
94
|
+
*/
|
|
95
|
+
async update(session) {
|
|
96
|
+
await __classPrivateFieldGet(this, _AwsSecretManager_sm, "f").updateSecret({
|
|
97
|
+
SecretId: __classPrivateFieldGet(this, _AwsSecretManager_secretArn, "f"),
|
|
98
|
+
SecretString: (0, cubesigner_sdk_1.serializeBase64SessionData)(session),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Refreshes the session and writes the new session to AWS Secrets Manager.
|
|
103
|
+
*/
|
|
104
|
+
async refresh() {
|
|
105
|
+
const res = await __classPrivateFieldGet(this, _AwsSecretManager_sm, "f").getSecretValue({ SecretId: __classPrivateFieldGet(this, _AwsSecretManager_secretArn, "f") });
|
|
106
|
+
const newSessionData = await (0, cubesigner_sdk_1.refresh)((0, cubesigner_sdk_1.parseBase64SessionData)(res.SecretString));
|
|
107
|
+
await this.update(newSessionData);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Constructor.
|
|
111
|
+
*
|
|
112
|
+
* @param secretId The name of the secret holding the token
|
|
113
|
+
*/
|
|
114
|
+
constructor(secretId) {
|
|
115
|
+
/** Client for AWS Secrets Manager */
|
|
116
|
+
_AwsSecretManager_sm.set(this, void 0);
|
|
117
|
+
/** ID of the secret */
|
|
118
|
+
_AwsSecretManager_secretArn.set(this, void 0);
|
|
119
|
+
__classPrivateFieldSet(this, _AwsSecretManager_sm, new client_secrets_manager_1.SecretsManager(), "f");
|
|
120
|
+
__classPrivateFieldSet(this, _AwsSecretManager_secretArn, secretId, "f");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.AwsSecretManager = AwsSecretManager;
|
|
124
|
+
_AwsSecretManager_sm = new WeakMap(), _AwsSecretManager_secretArn = new WeakMap();
|
|
125
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsNEVBQWlFO0FBQ2pFLGdFQVFxQztBQWdCckM7O0dBRUc7QUFDSCxNQUFhLHVCQUF1QjtJQWtCbEM7Ozs7O09BS0c7SUFDSCxZQUFZLFFBQWdCLEVBQUUsSUFBa0M7O1FBdkJoRSxnREFBK0M7UUFDL0MscUNBQXFDO1FBQ3JDLDhDQUFvQjtRQUNwQix1QkFBdUI7UUFDdkIscURBQW1CO1FBQ25CLGlDQUFpQztRQUNqQyxpREFTYztRQVNaLHVCQUFBLElBQUksK0JBQU8sSUFBSSx1Q0FBYyxFQUFFLE1BQUEsQ0FBQztRQUNoQyx1QkFBQSxJQUFJLHNDQUFjLFFBQVEsTUFBQSxDQUFDO1FBQzNCLHVCQUFBLElBQUksa0NBQVUsU0FBUyxNQUFBLENBQUM7UUFDeEIsdUJBQUEsSUFBSSxpQ0FBUyxJQUFJLE1BQUEsQ0FBQztJQUNwQixDQUFDO0lBRUQsa0JBQWtCO0lBQ2xCLEtBQUssQ0FBQyxjQUFjO1FBQ2xCLHVCQUFBLElBQUksa0NBQVUsU0FBUyxNQUFBLENBQUM7SUFDMUIsQ0FBQztJQUVELGtCQUFrQjtJQUNsQixLQUFLLENBQUMsUUFBUTtRQUNaLE1BQU0sSUFBSSxHQUFHLE1BQU0sdUJBQUEsSUFBSSxtRkFBZ0IsTUFBcEIsSUFBSSxDQUFrQixDQUFDO1FBQzFDLE9BQU8sSUFBQSx5QkFBUSxFQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3hCLENBQUM7SUFFRCxrQkFBa0I7SUFDbEIsS0FBSyxDQUFDLEtBQUs7UUFDVCxNQUFNLElBQUksR0FBRyxNQUFNLHVCQUFBLElBQUksbUZBQWdCLE1BQXBCLElBQUksQ0FBa0IsQ0FBQztRQUMxQyxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUM7SUFDcEIsQ0FBQztDQW1DRjtBQWpGRCwwREFpRkM7O0FBakNDOzs7OztHQUtHO0FBQ0gsS0FBSztJQUNI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ay9jbGllbnQtc2VjcmV0cy1tYW5hZ2VyXCI7XG5pbXBvcnQge1xuICB0eXBlIFNlc3Npb25EYXRhLFxuICB0eXBlIFNlc3Npb25NYW5hZ2VyLFxuICBtZXRhZGF0YSxcbiAgdHlwZSBTZXNzaW9uTWV0YWRhdGEsXG4gIHJlZnJlc2gsXG4gIHBhcnNlQmFzZTY0U2Vzc2lvbkRhdGEsXG4gIHNlcmlhbGl6ZUJhc2U2NFNlc3Npb25EYXRhLFxufSBmcm9tIFwiQGN1YmlzdC1sYWJzL2N1YmVzaWduZXItc2RrXCI7XG5cbi8qKiBPcHRpb25zIGZvciBBV1MgU2VjcmV0cyBNYW5hZ2VyLWJhY2tlZCBzZXNzaW9uIG1hbmFnZXIgKi9cbmludGVyZmFjZSBBd3NTZWNyZXRTZXNzaW9uTWFuYWdlck9wdHMge1xuICAvKipcbiAgICogTGltaXQgdGhlIGNhY2hlIGxpZmV0aW1lIGJ5IHRoZSBzY2hlZHVsZWQgcm90YXRpb24gb2YgdGhlIHNlY3JldCAoZGVmYXVsdDogdHJ1ZSlcbiAgICovXG4gIGNoZWNrU2NoZWR1bGVkUm90YXRpb24/OiBib29sZWFuO1xuICAvKipcbiAgICogTWF4aW11bSBhbW91bnQgb2YgdGltZSB0aGF0IHNlc3Npb24gZGF0YSB3aWxsIGJlIGNhY2hlZCAoZGVmYXVsdDogdXNlIGF1dGggdG9rZW4gbGlmZXRpbWUgdG9cbiAgICogZGV0ZXJtaW5lIGNhY2hlIGxpZmV0aW1lKS4gVGhpcyBvcHRpb24gaXMgdXNlZnVsIGlmIHRoZSBzZXNzaW9uIGlzIHJlZnJlc2hlZCBhdCBzb21lIGtub3duXG4gICAqIGludGVydmFsIChlLmcuLCBkdWUgdG8gYSBzZWNyZXQgcm90YXRpb24gc2NoZWR1bGUpLlxuICAgKi9cbiAgbWF4Q2FjaGVMaWZldGltZT86IG51bWJlcjtcbn1cblxuLyoqXG4gKiBBIHNlc3Npb24gbWFuYWdlciB0aGF0IHJlYWRzIGEgdG9rZW4gZnJvbSBhIHNlY3JldCBzdG9yZWQgaW4gQVdTIFNlY3JldHMgTWFuYWdlci5cbiAqL1xuZXhwb3J0IGNsYXNzIEF3c1NlY3JldFNlc3Npb25NYW5hZ2VyIGltcGxlbWVudHMgU2Vzc2lvbk1hbmFnZXIge1xuICAjb3B0czogQXdzU2VjcmV0U2Vzc2lvbk1hbmFnZXJPcHRzIHwgdW5kZWZpbmVkO1xuICAvKiogQ2xpZW50IGZvciBBV1MgU2VjcmV0cyBNYW5hZ2VyICovXG4gICNzbTogU2VjcmV0c01hbmFnZXI7XG4gIC8qKiBJRCBvZiB0aGUgc2VjcmV0ICovXG4gICNzZWNyZXRBcm46IHN0cmluZztcbiAgLyoqIENhY2hlIGZvciB0aGUgc2Vzc2lvbiBkYXRhICovXG4gICNjYWNoZTpcbiAgICB8IHtcbiAgICAgICAgLyoqIFRoZSBzZXNzaW9uIGRhdGEgKi9cbiAgICAgICAgc2Vzc2lvbkRhdGE6IFNlc3Npb25EYXRhO1xuICAgICAgICAvKipcbiAgICAgICAgICogVGhlIGV4cGlyYXRpb24gb2YgdGhlIGNhY2hlIChtYXkgYmUgZGlmZmVyZW50IGZyb20gdGhlIGV4cGlyYXRpb24gdGltZSBvZiB0aGUgdG9rZW4gaXRzZWxmKVxuICAgICAgICAgKi9cbiAgICAgICAgZXhwOiBudW1iZXI7XG4gICAgICB9XG4gICAgfCB1bmRlZmluZWQ7XG5cbiAgLyoqXG4gICAqIENvbnN0cnVjdG9yLlxuICAgKlxuICAgKiBAcGFyYW0gc2VjcmV0SWQgVGhlIG5hbWUgb2YgdGhlIHNlY3JldCBob2xkaW5nIHRoZSB0b2tlblxuICAgKiBAcGFyYW0gb3B0cyBPcHRpb25zIGZvciB0aGUgc2Vzc2lvbiBtYW5hZ2VyXG4gICAqL1xuICBjb25zdHJ1Y3RvcihzZWNyZXRJZDogc3RyaW5nLCBvcHRzPzogQXdzU2VjcmV0U2Vzc2lvbk1hbmFnZXJPcHRzKSB7XG4gICAgdGhpcy4jc20gPSBuZXcgU2VjcmV0c01hbmFnZXIoKTtcbiAgICB0aGlzLiNzZWNyZXRBcm4gPSBzZWNyZXRJZDtcbiAgICB0aGlzLiNjYWNoZSA9IHVuZGVmaW5lZDtcbiAgICB0aGlzLiNvcHRzID0gb3B0cztcbiAgfVxuXG4gIC8qKiBAaW5oZXJpdGRvYyAqL1xuICBhc3luYyBvbkludmFsaWRUb2tlbigpIHtcbiAgICB0aGlzLiNjYWNoZSA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIC8qKiBAaW5oZXJpdGRvYyAqL1xuICBhc3luYyBtZXRhZGF0YSgpOiBQcm9taXNlPFNlc3Npb25NZXRhZGF0YT4ge1xuICAgIGNvbnN0IGRhdGEgPSBhd2FpdCB0aGlzLiNnZXRTZXNzaW9uRGF0YSgpO1xuICAgIHJldHVybiBtZXRhZGF0YShkYXRhKTtcbiAgfVxuXG4gIC8qKiBAaW5oZXJpdGRvYyAqL1xuICBhc3luYyB0b2tlbigpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IGRhdGEgPSBhd2FpdCB0aGlzLiNnZXRTZXNzaW9uRGF0YSgpO1xuICAgIHJldHVybiBkYXRhLnRva2VuO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgc2Vzc2lvbiBkYXRhIGVpdGhlciBmcm9tIGNhY2hlIG9yIGZyb20gQVdTIFNlY3JldHMgTWFuYWdlciBpZiBubyB1bmV4cGlyZWQgY2FjaGVkXG4gICAqIGluZm9ybWF0aW9uIGlzIGF2YWlsYWJsZS5cbiAgICpcbiAgICogQHJldHVybnMgVGhlIHNlc3Npb24gZGF0YVxuICAgKi9cbiAgYXN5bmMgI2dldFNlc3Npb25EYXRhKCk6IFByb21pc2U8U2Vzc2lvbkRhdGE+IHtcbiAgICBpZiAodGhpcy4jY2FjaGUgIT09IHVuZGVmaW5lZCAmJiB0aGlzLiNjYWNoZS5leHAgPiBEYXRlLm5vdygpIC8gMTAwMCkge1xuICAgICAgcmV0dXJuIHRoaXMuI2NhY2hlLnNlc3Npb25EYXRhO1xuICAgIH1cblxuICAgIGNvbnN0IHJlcyA9IGF3YWl0IHRoaXMuI3NtLmdldFNlY3JldFZhbHVlKHsgU2VjcmV0SWQ6IHRoaXMuI3NlY3JldEFybiB9KTtcbiAgICBjb25zdCBzZXNzaW9uRGF0YSA9IHBhcnNlQmFzZTY0U2Vzc2lvbkRhdGEocmVzLlNlY3JldFN0cmluZyEpO1xuICAgIGNvbnN0IG1heENhY2hlTGlmZXRpbWUgPSB0aGlzLiNvcHRzPy5tYXhDYWNoZUxpZmV0aW1lO1xuICAgIGxldCBleHAgPVxuICAgICAgbWF4Q2FjaGVMaWZldGltZSAhPT0gdW5kZWZpbmVkXG4gICAgICAgID8gTWF0aC5taW4oRGF0ZS5ub3coKSAvIDEwMDAgKyBtYXhDYWNoZUxpZmV0aW1lLCBzZXNzaW9uRGF0YS5zZXNzaW9uX2luZm8uYXV0aF90b2tlbl9leHApXG4gICAgICAgIDogc2Vzc2lvbkRhdGEuc2Vzc2lvbl9pbmZvLmF1dGhfdG9rZW5fZXhwO1xuXG4gICAgLy8gTGltaXQgY2FjaGUgbGlmZXRpbWUgYnkgdGhlIG5leHQgc2NoZWR1bGVkIHJvdGF0aW9uIGlmIHRoZSB1c2VyIHJlcXVlc3RlZCBpdFxuICAgIGlmICh0aGlzLiNvcHRzPy5jaGVja1NjaGVkdWxlZFJvdGF0aW9uID8/IHRydWUpIHtcbiAgICAgIGNvbnN0IGRlc2MgPSBhd2FpdCB0aGlzLiNzbS5kZXNjcmliZVNlY3JldCh7IFNlY3JldElkOiB0aGlzLiNzZWNyZXRBcm4gfSk7XG4gICAgICBpZiAoZGVzYy5OZXh0Um90YXRpb25EYXRlICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgZXhwID0gTWF0aC5taW4oZXhwLCBkZXNjLk5leHRSb3RhdGlvbkRhdGUuZ2V0VGltZSgpIC8gMTAwMCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgdGhpcy4jY2FjaGUgPSB7XG4gICAgICBzZXNzaW9uRGF0YSxcbiAgICAgIGV4cCxcbiAgICB9O1xuICAgIHJldHVybiB0aGlzLiNjYWNoZS5zZXNzaW9uRGF0YTtcbiAgfVxufVxuXG4vKiogTWFuYWdlcyBzZXNzaW9uIGRhdGEgc3RvcmVkIGluIGEgc2VjcmV0IGluIEFXUyBTZWNyZXRzIE1hYW5nZXIgKi9cbmV4cG9ydCBjbGFzcyBBd3NTZWNyZXRNYW5hZ2VyIHtcbiAgLyoqIENsaWVudCBmb3IgQVdTIFNlY3JldHMgTWFuYWdlciAqL1xuICAjc206IFNlY3JldHNNYW5hZ2VyO1xuICAvKiogSUQgb2YgdGhlIHNlY3JldCAqL1xuICAjc2VjcmV0QXJuOiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFdyaXRlcyBzZXNzaW9uIGRhdGEgdG8gdGhlIHNlY3JldC5cbiAgICpcbiAgICogQHBhcmFtIHNlc3Npb24gVGhlIHNlc3Npb24gZGF0YSB0byB3cml0ZVxuICAgKi9cbiAgYXN5bmMgdXBkYXRlKHNlc3Npb246IFNlc3Npb25EYXRhKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgYXdhaXQgdGhpcy4jc20udXBkYXRlU2VjcmV0KHtcbiAgICAgIFNlY3JldElkOiB0aGlzLiNzZWNyZXRBcm4sXG4gICAgICBTZWNyZXRTdHJpbmc6IHNlcmlhbGl6ZUJhc2U2NFNlc3Npb25EYXRhKHNlc3Npb24pLFxuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlZnJlc2hlcyB0aGUgc2Vzc2lvbiBhbmQgd3JpdGVzIHRoZSBuZXcgc2Vzc2lvbiB0byBBV1MgU2VjcmV0cyBNYW5hZ2VyLlxuICAgKi9cbiAgYXN5bmMgcmVmcmVzaCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCByZXMgPSBhd2FpdCB0aGlzLiNzbS5nZXRTZWNyZXRWYWx1ZSh7IFNlY3JldElkOiB0aGlzLiNzZWNyZXRBcm4gfSk7XG4gICAgY29uc3QgbmV3U2Vzc2lvbkRhdGEgPSBhd2FpdCByZWZyZXNoKHBhcnNlQmFzZTY0U2Vzc2lvbkRhdGEocmVzLlNlY3JldFN0cmluZyEpKTtcbiAgICBhd2FpdCB0aGlzLnVwZGF0ZShuZXdTZXNzaW9uRGF0YSk7XG4gIH1cblxuICAvKipcbiAgICogQ29uc3RydWN0b3IuXG4gICAqXG4gICAqIEBwYXJhbSBzZWNyZXRJZCBUaGUgbmFtZSBvZiB0aGUgc2VjcmV0IGhvbGRpbmcgdGhlIHRva2VuXG4gICAqL1xuICBjb25zdHJ1Y3RvcihzZWNyZXRJZDogc3RyaW5nKSB7XG4gICAgdGhpcy4jc20gPSBuZXcgU2VjcmV0c01hbmFnZXIoKTtcbiAgICB0aGlzLiNzZWNyZXRBcm4gPSBzZWNyZXRJZDtcbiAgfVxufVxuIl19
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cubist-labs/cubesigner-sdk-secretsmanager-storage",
|
|
3
|
+
"version": "0.4.97-0",
|
|
4
|
+
"description": "CubeSigner SDK session manager backed by AWS Secrets Manager",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"author": "Cubist, Inc.",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"tsconfig.json",
|
|
11
|
+
"src/**",
|
|
12
|
+
"dist/**",
|
|
13
|
+
"../../NOTICE",
|
|
14
|
+
"../../LICENSE-APACHE",
|
|
15
|
+
"../../LICENSE-MIT"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"prepack": "tsc",
|
|
20
|
+
"test": "npx --node-options='--experimental-vm-modules' jest --maxWorkers=1"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@cubist-labs/cubesigner-sdk": "^0.4.97-0"
|
|
24
|
+
},
|
|
25
|
+
"directories": {
|
|
26
|
+
"test": "test"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@aws-sdk/client-secrets-manager": "^3.682.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"aws-sdk-client-mock": "^4.1.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { SecretsManager } from "@aws-sdk/client-secrets-manager";
|
|
2
|
+
import {
|
|
3
|
+
type SessionData,
|
|
4
|
+
type SessionManager,
|
|
5
|
+
metadata,
|
|
6
|
+
type SessionMetadata,
|
|
7
|
+
refresh,
|
|
8
|
+
parseBase64SessionData,
|
|
9
|
+
serializeBase64SessionData,
|
|
10
|
+
} from "@cubist-labs/cubesigner-sdk";
|
|
11
|
+
|
|
12
|
+
/** Options for AWS Secrets Manager-backed session manager */
|
|
13
|
+
interface AwsSecretSessionManagerOpts {
|
|
14
|
+
/**
|
|
15
|
+
* Limit the cache lifetime by the scheduled rotation of the secret (default: true)
|
|
16
|
+
*/
|
|
17
|
+
checkScheduledRotation?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Maximum amount of time that session data will be cached (default: use auth token lifetime to
|
|
20
|
+
* determine cache lifetime). This option is useful if the session is refreshed at some known
|
|
21
|
+
* interval (e.g., due to a secret rotation schedule).
|
|
22
|
+
*/
|
|
23
|
+
maxCacheLifetime?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A session manager that reads a token from a secret stored in AWS Secrets Manager.
|
|
28
|
+
*/
|
|
29
|
+
export class AwsSecretSessionManager implements SessionManager {
|
|
30
|
+
#opts: AwsSecretSessionManagerOpts | undefined;
|
|
31
|
+
/** Client for AWS Secrets Manager */
|
|
32
|
+
#sm: SecretsManager;
|
|
33
|
+
/** ID of the secret */
|
|
34
|
+
#secretArn: string;
|
|
35
|
+
/** Cache for the session data */
|
|
36
|
+
#cache:
|
|
37
|
+
| {
|
|
38
|
+
/** The session data */
|
|
39
|
+
sessionData: SessionData;
|
|
40
|
+
/**
|
|
41
|
+
* The expiration of the cache (may be different from the expiration time of the token itself)
|
|
42
|
+
*/
|
|
43
|
+
exp: number;
|
|
44
|
+
}
|
|
45
|
+
| undefined;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Constructor.
|
|
49
|
+
*
|
|
50
|
+
* @param secretId The name of the secret holding the token
|
|
51
|
+
* @param opts Options for the session manager
|
|
52
|
+
*/
|
|
53
|
+
constructor(secretId: string, opts?: AwsSecretSessionManagerOpts) {
|
|
54
|
+
this.#sm = new SecretsManager();
|
|
55
|
+
this.#secretArn = secretId;
|
|
56
|
+
this.#cache = undefined;
|
|
57
|
+
this.#opts = opts;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** @inheritdoc */
|
|
61
|
+
async onInvalidToken() {
|
|
62
|
+
this.#cache = undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @inheritdoc */
|
|
66
|
+
async metadata(): Promise<SessionMetadata> {
|
|
67
|
+
const data = await this.#getSessionData();
|
|
68
|
+
return metadata(data);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @inheritdoc */
|
|
72
|
+
async token(): Promise<string> {
|
|
73
|
+
const data = await this.#getSessionData();
|
|
74
|
+
return data.token;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the session data either from cache or from AWS Secrets Manager if no unexpired cached
|
|
79
|
+
* information is available.
|
|
80
|
+
*
|
|
81
|
+
* @returns The session data
|
|
82
|
+
*/
|
|
83
|
+
async #getSessionData(): Promise<SessionData> {
|
|
84
|
+
if (this.#cache !== undefined && this.#cache.exp > Date.now() / 1000) {
|
|
85
|
+
return this.#cache.sessionData;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const res = await this.#sm.getSecretValue({ SecretId: this.#secretArn });
|
|
89
|
+
const sessionData = parseBase64SessionData(res.SecretString!);
|
|
90
|
+
const maxCacheLifetime = this.#opts?.maxCacheLifetime;
|
|
91
|
+
let exp =
|
|
92
|
+
maxCacheLifetime !== undefined
|
|
93
|
+
? Math.min(Date.now() / 1000 + maxCacheLifetime, sessionData.session_info.auth_token_exp)
|
|
94
|
+
: sessionData.session_info.auth_token_exp;
|
|
95
|
+
|
|
96
|
+
// Limit cache lifetime by the next scheduled rotation if the user requested it
|
|
97
|
+
if (this.#opts?.checkScheduledRotation ?? true) {
|
|
98
|
+
const desc = await this.#sm.describeSecret({ SecretId: this.#secretArn });
|
|
99
|
+
if (desc.NextRotationDate !== undefined) {
|
|
100
|
+
exp = Math.min(exp, desc.NextRotationDate.getTime() / 1000);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.#cache = {
|
|
105
|
+
sessionData,
|
|
106
|
+
exp,
|
|
107
|
+
};
|
|
108
|
+
return this.#cache.sessionData;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Manages session data stored in a secret in AWS Secrets Maanger */
|
|
113
|
+
export class AwsSecretManager {
|
|
114
|
+
/** Client for AWS Secrets Manager */
|
|
115
|
+
#sm: SecretsManager;
|
|
116
|
+
/** ID of the secret */
|
|
117
|
+
#secretArn: string;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Writes session data to the secret.
|
|
121
|
+
*
|
|
122
|
+
* @param session The session data to write
|
|
123
|
+
*/
|
|
124
|
+
async update(session: SessionData): Promise<void> {
|
|
125
|
+
await this.#sm.updateSecret({
|
|
126
|
+
SecretId: this.#secretArn,
|
|
127
|
+
SecretString: serializeBase64SessionData(session),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Refreshes the session and writes the new session to AWS Secrets Manager.
|
|
133
|
+
*/
|
|
134
|
+
async refresh(): Promise<void> {
|
|
135
|
+
const res = await this.#sm.getSecretValue({ SecretId: this.#secretArn });
|
|
136
|
+
const newSessionData = await refresh(parseBase64SessionData(res.SecretString!));
|
|
137
|
+
await this.update(newSessionData);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Constructor.
|
|
142
|
+
*
|
|
143
|
+
* @param secretId The name of the secret holding the token
|
|
144
|
+
*/
|
|
145
|
+
constructor(secretId: string) {
|
|
146
|
+
this.#sm = new SecretsManager();
|
|
147
|
+
this.#secretArn = secretId;
|
|
148
|
+
}
|
|
149
|
+
}
|
package/tsconfig.json
ADDED