updating-secrets 0.3.0 → 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.
- package/README.md +3 -4
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -5
- package/dist/secrets-definition/define-secrets.d.ts +4 -0
- package/package.json +9 -46
- package/dist/adapters/all-adapters.d.ts +0 -13
- package/dist/adapters/all-adapters.js +0 -13
- package/dist/adapters/aws-secrets-manager.adapter.d.ts +0 -32
- package/dist/adapters/aws-secrets-manager.adapter.js +0 -71
- package/dist/adapters/infisical.adapter.d.ts +0 -70
- package/dist/adapters/infisical.adapter.js +0 -168
- package/dist/public-mocks/mock-aws-secrets-manager.d.ts +0 -41
- package/dist/public-mocks/mock-aws-secrets-manager.js +0 -39
- package/dist/public-mocks/mock-infisical-sdk.d.ts +0 -70
- package/dist/public-mocks/mock-infisical-sdk.js +0 -84
package/README.md
CHANGED
|
@@ -65,7 +65,8 @@ export const mySecrets = defineSecrets({
|
|
|
65
65
|
|
|
66
66
|
Second, choose your secrets adapters:
|
|
67
67
|
|
|
68
|
-
-
|
|
68
|
+
- https://www.npmjs.com/package/@updating-secrets/infisical-adapter
|
|
69
|
+
- https://www.npmjs.com/package/@updating-secrets/aws-secrets-manager-adapter
|
|
69
70
|
- [`SecretsJsonFileAdapter`](https://electrovir.github.io/updating-secrets/classes/SecretsJsonFileAdapter.html): loads secrets from a JSON file.
|
|
70
71
|
- [`StaticSecretsAdapter`](https://electrovir.github.io/updating-secrets/classes/StaticSecretsAdapter.html): allows you to define all secrets values in-place.
|
|
71
72
|
|
|
@@ -90,11 +91,9 @@ export class MyCustomSecretsAdapter extends BaseSecretsAdapter {
|
|
|
90
91
|
|
|
91
92
|
Lastly, create an instance of [`UpdatingSecrets`](https://electrovir.github.io/updating-secrets/classes/UpdatingSecrets.html) (with [`createUpdatingSecrets`](https://electrovir.github.io/updating-secrets/functions/createUpdatingSecrets.html)):
|
|
92
93
|
|
|
93
|
-
<!-- example-link: src/examples/updating-secrets.example.ts -->
|
|
94
|
-
|
|
95
94
|
```TypeScript
|
|
96
95
|
import {SecretsManager} from '@aws-sdk/client-secrets-manager';
|
|
97
|
-
import {AwsSecretsManagerAdapter, createUpdatingSecrets} from 'updating-secrets';
|
|
96
|
+
import {AwsSecretsManagerAdapter, createUpdatingSecrets} from '@updating-secrets/aws-secrets-manager-adapter';
|
|
98
97
|
import {mySecrets} from './define-secrets.example.js';
|
|
99
98
|
|
|
100
99
|
const updatingSecrets = await createUpdatingSecrets(mySecrets, [
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
export * from './adapters/all-adapters.js';
|
|
2
|
-
export * from './adapters/aws-secrets-manager.adapter.js';
|
|
3
1
|
export * from './adapters/base.adapter.js';
|
|
4
|
-
export * from './adapters/infisical.adapter.js';
|
|
5
2
|
export * from './adapters/secrets-json-file.adapter.js';
|
|
6
3
|
export * from './adapters/static-secrets.adapter.js';
|
|
7
|
-
export * from './public-mocks/mock-aws-secrets-manager.js';
|
|
8
4
|
export * from './public-mocks/mock-fs.js';
|
|
9
|
-
export * from './public-mocks/mock-infisical-sdk.js';
|
|
10
5
|
export * from './secret-load.error.js';
|
|
11
6
|
export * from './secrets-definition/define-secrets.js';
|
|
12
7
|
export * from './updating-secrets.js';
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
export * from './adapters/all-adapters.js';
|
|
2
|
-
export * from './adapters/aws-secrets-manager.adapter.js';
|
|
3
1
|
export * from './adapters/base.adapter.js';
|
|
4
|
-
export * from './adapters/infisical.adapter.js';
|
|
5
2
|
export * from './adapters/secrets-json-file.adapter.js';
|
|
6
3
|
export * from './adapters/static-secrets.adapter.js';
|
|
7
|
-
export * from './public-mocks/mock-aws-secrets-manager.js';
|
|
8
4
|
export * from './public-mocks/mock-fs.js';
|
|
9
|
-
export * from './public-mocks/mock-infisical-sdk.js';
|
|
10
5
|
export * from './secret-load.error.js';
|
|
11
6
|
export * from './secrets-definition/define-secrets.js';
|
|
12
7
|
export * from './updating-secrets.js';
|
|
@@ -83,6 +83,10 @@ export type SecretDefinitions = {
|
|
|
83
83
|
*/
|
|
84
84
|
useWholeFolder: true;
|
|
85
85
|
}>;
|
|
86
|
+
onePassword: {
|
|
87
|
+
/** A URL generated from 1Password's "copy private link" action. */
|
|
88
|
+
secretUrl: string;
|
|
89
|
+
};
|
|
86
90
|
/**
|
|
87
91
|
* Configuration for loading this secret from AWS. This is required if you're using the
|
|
88
92
|
* AWS SecretsManager adapter, otherwise the secret will fail to load.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "updating-secrets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Automatically update secrets on an interval with support for seamless secret rotation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"secrets",
|
|
@@ -43,58 +43,21 @@
|
|
|
43
43
|
"test:spelling": "virmator spellcheck",
|
|
44
44
|
"test:update": "npm run test update"
|
|
45
45
|
},
|
|
46
|
-
"overrides": {
|
|
47
|
-
"xml2js": "^0.6.2"
|
|
48
|
-
},
|
|
49
46
|
"dependencies": {
|
|
50
|
-
"@augment-vir/assert": "^31.
|
|
51
|
-
"@augment-vir/common": "^31.
|
|
52
|
-
"date-vir": "^7.3.
|
|
53
|
-
"object-shape-tester": "^5.1.
|
|
47
|
+
"@augment-vir/assert": "^31.26.0",
|
|
48
|
+
"@augment-vir/common": "^31.26.0",
|
|
49
|
+
"date-vir": "^7.3.2",
|
|
50
|
+
"object-shape-tester": "^5.1.7",
|
|
54
51
|
"type-fest": "^4.41.0"
|
|
55
52
|
},
|
|
56
53
|
"devDependencies": {
|
|
57
|
-
"@augment-vir/test": "^31.
|
|
58
|
-
"@
|
|
59
|
-
"@eslint/eslintrc": "^3.3.1",
|
|
60
|
-
"@eslint/js": "^9.26.0",
|
|
61
|
-
"@infisical/sdk": "^4.0.0",
|
|
62
|
-
"@stylistic/eslint-plugin": "^4.2.0",
|
|
63
|
-
"@stylistic/eslint-plugin-ts": "^4.2.0",
|
|
64
|
-
"@types/node": "^22.15.17",
|
|
65
|
-
"@typescript-eslint/eslint-plugin": "^8.32.0",
|
|
54
|
+
"@augment-vir/test": "^31.26.0",
|
|
55
|
+
"@types/node": "^24.0.10",
|
|
66
56
|
"c8": "^10.1.3",
|
|
67
|
-
"cspell": "^9.0.1",
|
|
68
|
-
"dependency-cruiser": "^16.10.2",
|
|
69
|
-
"esbuild": "^0.25.4",
|
|
70
|
-
"eslint": "^9.26.0",
|
|
71
|
-
"eslint-config-prettier": "^10.1.5",
|
|
72
|
-
"eslint-plugin-jsdoc": "^50.6.11",
|
|
73
|
-
"eslint-plugin-monorepo-cop": "^1.0.2",
|
|
74
|
-
"eslint-plugin-playwright": "^2.2.0",
|
|
75
|
-
"eslint-plugin-prettier": "^5.4.0",
|
|
76
|
-
"eslint-plugin-require-extensions": "^0.1.3",
|
|
77
|
-
"eslint-plugin-sonarjs": "^3.0.2",
|
|
78
|
-
"eslint-plugin-unicorn": "^59.0.1",
|
|
79
57
|
"istanbul-smart-text-reporter": "^1.1.5",
|
|
80
58
|
"markdown-code-example-inserter": "^3.0.3",
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"prettier-plugin-interpolated-html-tags": "^2.0.1",
|
|
84
|
-
"prettier-plugin-jsdoc": "^1.3.2",
|
|
85
|
-
"prettier-plugin-multiline-arrays": "^4.0.3",
|
|
86
|
-
"prettier-plugin-organize-imports": "^4.1.0",
|
|
87
|
-
"prettier-plugin-packagejson": "^2.5.12",
|
|
88
|
-
"prettier-plugin-sort-json": "^4.1.1",
|
|
89
|
-
"prettier-plugin-toml": "^2.0.5",
|
|
90
|
-
"typedoc": "^0.28.4",
|
|
91
|
-
"typescript": "^5.8.3",
|
|
92
|
-
"typescript-eslint": "^8.32.0",
|
|
93
|
-
"virmator": "^13.15.1"
|
|
94
|
-
},
|
|
95
|
-
"peerDependencies": {
|
|
96
|
-
"@aws-sdk/client-secrets-manager": ">=3",
|
|
97
|
-
"@infisical/sdk": ">=4"
|
|
59
|
+
"typedoc": "^0.28.7",
|
|
60
|
+
"typescript": "^5.8.3"
|
|
98
61
|
},
|
|
99
62
|
"engines": {
|
|
100
63
|
"node": ">=22"
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { AwsSecretsManagerAdapter } from './aws-secrets-manager.adapter.js';
|
|
2
|
-
import { SecretsJsonFileAdapter } from './secrets-json-file.adapter.js';
|
|
3
|
-
import { StaticSecretsAdapter } from './static-secrets.adapter.js';
|
|
4
|
-
/**
|
|
5
|
-
* All built-in secrets adapters.
|
|
6
|
-
*
|
|
7
|
-
* @category Internal
|
|
8
|
-
*/
|
|
9
|
-
export declare const allSecretAdapters: {
|
|
10
|
-
AwsSecretsManagerAdapter: typeof AwsSecretsManagerAdapter;
|
|
11
|
-
SecretsJsonFileAdapter: typeof SecretsJsonFileAdapter;
|
|
12
|
-
StaticSecretsAdapter: typeof StaticSecretsAdapter;
|
|
13
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { AwsSecretsManagerAdapter } from './aws-secrets-manager.adapter.js';
|
|
2
|
-
import { SecretsJsonFileAdapter } from './secrets-json-file.adapter.js';
|
|
3
|
-
import { StaticSecretsAdapter } from './static-secrets.adapter.js';
|
|
4
|
-
/**
|
|
5
|
-
* All built-in secrets adapters.
|
|
6
|
-
*
|
|
7
|
-
* @category Internal
|
|
8
|
-
*/
|
|
9
|
-
export const allSecretAdapters = {
|
|
10
|
-
AwsSecretsManagerAdapter,
|
|
11
|
-
SecretsJsonFileAdapter,
|
|
12
|
-
StaticSecretsAdapter,
|
|
13
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { type GetSecretValueCommand, type GetSecretValueCommandOutput } from '@aws-sdk/client-secrets-manager';
|
|
2
|
-
import { type ProcessedSecretDefinitions } from '../secrets-definition/define-secrets.js';
|
|
3
|
-
import { BaseSecretsAdapter } from './base.adapter.js';
|
|
4
|
-
/**
|
|
5
|
-
* Minimal subset of AWS's `SecretsManagerClient` from the
|
|
6
|
-
* [`@aws-sdk/client-secrets-manager`](https://www.npmjs.com/package/@aws-sdk/client-secrets-manager)
|
|
7
|
-
* package required for {@link AwsSecretsManagerAdapter}.
|
|
8
|
-
*
|
|
9
|
-
* For testing purposes use `MockAwsSecretsManagerClient` to create a mock instance of this.
|
|
10
|
-
*
|
|
11
|
-
* @category Internal
|
|
12
|
-
*/
|
|
13
|
-
export type NeededAwsSecretsManagerClient = {
|
|
14
|
-
/**
|
|
15
|
-
* Same as AWS's `SecretsManagerClient.send()` method but this only accepts the
|
|
16
|
-
* `GetSecretValueCommand` command.
|
|
17
|
-
*/
|
|
18
|
-
send(command: GetSecretValueCommand): Promise<GetSecretValueCommandOutput>;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Loads secrets from AWS Secrets Manager. A `SecretsManagerClient` instance must be provided.
|
|
22
|
-
*
|
|
23
|
-
* @category Adapters
|
|
24
|
-
*/
|
|
25
|
-
export declare class AwsSecretsManagerAdapter extends BaseSecretsAdapter {
|
|
26
|
-
protected readonly awsSecretsManager: Readonly<NeededAwsSecretsManagerClient>;
|
|
27
|
-
constructor(awsSecretsManager: Readonly<NeededAwsSecretsManagerClient>);
|
|
28
|
-
/** Loads secrets from the provided `SecretsManagerClient`. */
|
|
29
|
-
loadSecrets(secrets: ProcessedSecretDefinitions): Promise<{
|
|
30
|
-
[x: string]: Error | Promise<any>;
|
|
31
|
-
}>;
|
|
32
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { assert } from '@augment-vir/assert';
|
|
2
|
-
import { ensureError, getOrSet, mapObjectValuesSync, parseWithJson5, wrapInTry, } from '@augment-vir/common';
|
|
3
|
-
import { BaseSecretsAdapter } from './base.adapter.js';
|
|
4
|
-
/**
|
|
5
|
-
* Loads secrets from AWS Secrets Manager. A `SecretsManagerClient` instance must be provided.
|
|
6
|
-
*
|
|
7
|
-
* @category Adapters
|
|
8
|
-
*/
|
|
9
|
-
export class AwsSecretsManagerAdapter extends BaseSecretsAdapter {
|
|
10
|
-
awsSecretsManager;
|
|
11
|
-
constructor(awsSecretsManager) {
|
|
12
|
-
super('AwsSecretsManagerAdapter');
|
|
13
|
-
this.awsSecretsManager = awsSecretsManager;
|
|
14
|
-
}
|
|
15
|
-
/** Loads secrets from the provided `SecretsManagerClient`. */
|
|
16
|
-
async loadSecrets(secrets) {
|
|
17
|
-
/* node:coverage ignore next 1: dynamic imports are not branches */
|
|
18
|
-
const { GetSecretValueCommand } = await import('@aws-sdk/client-secrets-manager');
|
|
19
|
-
const cachedSecrets = {};
|
|
20
|
-
return mapObjectValuesSync(secrets, (secretName, secretDefinition) => {
|
|
21
|
-
const awsConfig = secretDefinition.adapterConfig.aws;
|
|
22
|
-
if (!awsConfig) {
|
|
23
|
-
return new Error(`No AWS adapter config (required for using AwsSecretsManagerAdapter) defined for secret '${secretDefinition.secretName}'.`);
|
|
24
|
-
}
|
|
25
|
-
const awsSecretName = awsConfig.keyIn || awsConfig.rootOf;
|
|
26
|
-
if (!awsSecretName) {
|
|
27
|
-
return new Error(`Invalid AWS adapter key config for '${secretDefinition.secretName}'.`);
|
|
28
|
-
}
|
|
29
|
-
const secretValue = getOrSet(cachedSecrets, awsSecretName, () => {
|
|
30
|
-
const sendCommand = new GetSecretValueCommand({
|
|
31
|
-
SecretId: awsSecretName,
|
|
32
|
-
});
|
|
33
|
-
return this.awsSecretsManager.send(sendCommand).then((result) => {
|
|
34
|
-
try {
|
|
35
|
-
const secretValue = result.SecretString;
|
|
36
|
-
if (secretValue) {
|
|
37
|
-
return wrapInTry(() => parseWithJson5(secretValue), {
|
|
38
|
-
fallbackValue: secretValue,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
throw new Error(`AWS SecretsManager secret '${awsSecretName}' has no string value.`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
return ensureError(error);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
return secretValue
|
|
51
|
-
.then((awsSecretValue) => {
|
|
52
|
-
if (awsSecretValue instanceof Error) {
|
|
53
|
-
throw awsSecretValue;
|
|
54
|
-
}
|
|
55
|
-
else if (awsConfig.keyIn) {
|
|
56
|
-
assert.isObject(awsSecretValue, `AWS secret at '${awsSecretName}' is not an object.`);
|
|
57
|
-
return awsSecretValue[secretDefinition.secretName];
|
|
58
|
-
}
|
|
59
|
-
else if (secretDefinition.shapeDefinition) {
|
|
60
|
-
return wrapInTry(() => parseWithJson5(awsSecretValue), {
|
|
61
|
-
fallbackValue: awsSecretValue,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
return awsSecretValue;
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
.catch((reason) => ensureError(reason));
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { type Values } from '@augment-vir/common';
|
|
2
|
-
import { type ListSecretsOptions, type Secret } from '@infisical/sdk';
|
|
3
|
-
import { type ProcessedSecretDefinitions } from '../secrets-definition/define-secrets.js';
|
|
4
|
-
import { BaseSecretsAdapter, type RawSecrets } from './base.adapter.js';
|
|
5
|
-
/**
|
|
6
|
-
* Intermediate type for processing secrets from Infisical. Used in {@link InfisicalAdapter} and
|
|
7
|
-
* helper functions.
|
|
8
|
-
*
|
|
9
|
-
* @category Internal
|
|
10
|
-
*/
|
|
11
|
-
export type MappedInfisicalSecrets = {
|
|
12
|
-
[SecretKey in string]: string | MappedInfisicalSecrets | Error;
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* The necessary subset of the `InfisicalSDK` API that {@link InfisicalAdapter} requires.
|
|
16
|
-
*
|
|
17
|
-
* @category Internal
|
|
18
|
-
*/
|
|
19
|
-
export type NeededInfisicalSdk = {
|
|
20
|
-
/** Get the Infisical secrets client. */
|
|
21
|
-
secrets(): {
|
|
22
|
-
/** List the Infisical secrets. */
|
|
23
|
-
listSecrets(options: ListSecretsOptions): Promise<{
|
|
24
|
-
secrets: NeededInfisicalSecret[];
|
|
25
|
-
}>;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* The necessary subset of the Infisical `Secret` type that {@link InfisicalAdapter} requires.
|
|
30
|
-
*
|
|
31
|
-
* @category Internal
|
|
32
|
-
*/
|
|
33
|
-
export type NeededInfisicalSecret = Pick<Secret, 'secretKey' | 'secretPath' | 'secretValue'>;
|
|
34
|
-
/**
|
|
35
|
-
* Loads secrets from Infisical. A `InfisicalSDK` instance must be provided. The `InfisicalSDK`
|
|
36
|
-
* instance must also be authorized _before_ passing it into this.
|
|
37
|
-
*
|
|
38
|
-
* @category Adapters
|
|
39
|
-
*/
|
|
40
|
-
export declare class InfisicalAdapter extends BaseSecretsAdapter {
|
|
41
|
-
/**
|
|
42
|
-
* Make sure that you've already authenticated this client.
|
|
43
|
-
* (`client.auth().universalAuth.login()`)
|
|
44
|
-
*/
|
|
45
|
-
protected readonly infisicalClient: Readonly<NeededInfisicalSdk>;
|
|
46
|
-
/** `'dev'`, `'staging'`, `'prod'`, etc. */
|
|
47
|
-
protected readonly infisicalEnvironment: string;
|
|
48
|
-
constructor(
|
|
49
|
-
/**
|
|
50
|
-
* Make sure that you've already authenticated this client.
|
|
51
|
-
* (`client.auth().universalAuth.login()`)
|
|
52
|
-
*/
|
|
53
|
-
infisicalClient: Readonly<NeededInfisicalSdk>,
|
|
54
|
-
/** `'dev'`, `'staging'`, `'prod'`, etc. */
|
|
55
|
-
infisicalEnvironment: string);
|
|
56
|
-
/** Load secrets from the provided `InfisicalSDK`. */
|
|
57
|
-
loadSecrets(secrets: ProcessedSecretDefinitions): RawSecrets;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Get an Infisical secret value nested within an object.
|
|
61
|
-
*
|
|
62
|
-
* @category Internal
|
|
63
|
-
*/
|
|
64
|
-
export declare function getNested(parent: MappedInfisicalSecrets, keys: string[]): Values<MappedInfisicalSecrets>;
|
|
65
|
-
/**
|
|
66
|
-
* Set an Infisical secret value nested within an object.
|
|
67
|
-
*
|
|
68
|
-
* @category Internal
|
|
69
|
-
*/
|
|
70
|
-
export declare function setNested(parent: MappedInfisicalSecrets, keys: string[], value: string): void;
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { check } from '@augment-vir/assert';
|
|
2
|
-
import { ensureError, getOrSet, mapObjectValuesSync, parseWithJson5, removePrefix, removeSuffix, stringify, wrapInTry, } from '@augment-vir/common';
|
|
3
|
-
import { BaseSecretsAdapter } from './base.adapter.js';
|
|
4
|
-
/**
|
|
5
|
-
* Loads secrets from Infisical. A `InfisicalSDK` instance must be provided. The `InfisicalSDK`
|
|
6
|
-
* instance must also be authorized _before_ passing it into this.
|
|
7
|
-
*
|
|
8
|
-
* @category Adapters
|
|
9
|
-
*/
|
|
10
|
-
export class InfisicalAdapter extends BaseSecretsAdapter {
|
|
11
|
-
infisicalClient;
|
|
12
|
-
infisicalEnvironment;
|
|
13
|
-
constructor(
|
|
14
|
-
/**
|
|
15
|
-
* Make sure that you've already authenticated this client.
|
|
16
|
-
* (`client.auth().universalAuth.login()`)
|
|
17
|
-
*/
|
|
18
|
-
infisicalClient,
|
|
19
|
-
/** `'dev'`, `'staging'`, `'prod'`, etc. */
|
|
20
|
-
infisicalEnvironment) {
|
|
21
|
-
super('InfisicalAdapter');
|
|
22
|
-
this.infisicalClient = infisicalClient;
|
|
23
|
-
this.infisicalEnvironment = infisicalEnvironment;
|
|
24
|
-
}
|
|
25
|
-
/** Load secrets from the provided `InfisicalSDK`. */
|
|
26
|
-
loadSecrets(secrets) {
|
|
27
|
-
const secretsCache = {};
|
|
28
|
-
return mapObjectValuesSync(secrets, (secretName, secretDefinition) => {
|
|
29
|
-
const infisicalConfig = secretDefinition.adapterConfig.infisical;
|
|
30
|
-
if (!infisicalConfig) {
|
|
31
|
-
return new Error(`No Infisical adapter config (required for using InfisicalAdapter) defined for secret '${secretDefinition.secretName}'.`);
|
|
32
|
-
}
|
|
33
|
-
const projectSecretsPromise = getOrSet(secretsCache, infisicalConfig.projectId, () => {
|
|
34
|
-
try {
|
|
35
|
-
return (this.infisicalClient
|
|
36
|
-
.secrets()
|
|
37
|
-
.listSecrets({
|
|
38
|
-
recursive: true,
|
|
39
|
-
environment: this.infisicalEnvironment,
|
|
40
|
-
projectId: infisicalConfig.projectId,
|
|
41
|
-
viewSecretValue: true,
|
|
42
|
-
})
|
|
43
|
-
.then(({ secrets }) => {
|
|
44
|
-
return mapInfisicalSecrets(secrets);
|
|
45
|
-
})
|
|
46
|
-
/* node:coverage ignore next 3 */
|
|
47
|
-
.catch((error) => {
|
|
48
|
-
return ensureError(error);
|
|
49
|
-
}));
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
return Promise.reject(ensureError(error));
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
return projectSecretsPromise
|
|
56
|
-
.then((projectSecrets) => {
|
|
57
|
-
/* node:coverage ignore next 3 */
|
|
58
|
-
if (projectSecrets instanceof Error) {
|
|
59
|
-
return projectSecrets;
|
|
60
|
-
}
|
|
61
|
-
const folderValue = infisicalConfig.folderPath === '/' || !infisicalConfig.folderPath
|
|
62
|
-
? projectSecrets
|
|
63
|
-
: getNested(projectSecrets, removeSuffix({
|
|
64
|
-
value: removePrefix({
|
|
65
|
-
value: infisicalConfig.folderPath,
|
|
66
|
-
prefix: '/',
|
|
67
|
-
}),
|
|
68
|
-
suffix: '/',
|
|
69
|
-
}).split('/'));
|
|
70
|
-
/* node:coverage ignore next 3 */
|
|
71
|
-
if (check.isError(folderValue)) {
|
|
72
|
-
return folderValue;
|
|
73
|
-
}
|
|
74
|
-
if (infisicalConfig.keyInFolder) {
|
|
75
|
-
if (check.isString(folderValue)) {
|
|
76
|
-
throw new TypeError(`Cannot get keyInFolder of a non-folder.`);
|
|
77
|
-
}
|
|
78
|
-
else if (check.hasKey(folderValue, infisicalConfig.keyInFolder)) {
|
|
79
|
-
const value = folderValue[infisicalConfig.keyInFolder];
|
|
80
|
-
if (secretDefinition.shapeDefinition && check.isString(value)) {
|
|
81
|
-
return wrapInTry(() => parseWithJson5(value), {
|
|
82
|
-
fallbackValue: folderValue,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
return value;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
throw new Error(`Secret key not in folder '${infisicalConfig.folderPath}'`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
return folderValue;
|
|
95
|
-
}
|
|
96
|
-
})
|
|
97
|
-
.catch((error) => {
|
|
98
|
-
return ensureError(error);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function mapInfisicalSecrets(secrets) {
|
|
104
|
-
const secretsMap = {};
|
|
105
|
-
secrets.forEach((secret) => {
|
|
106
|
-
if (!secret.secretPath || secret.secretPath === '/') {
|
|
107
|
-
secretsMap[secret.secretKey] = secret.secretValue;
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
setNested(secretsMap, removeSuffix({
|
|
111
|
-
value: removePrefix({ value: secret.secretPath, prefix: '/' }),
|
|
112
|
-
suffix: '/',
|
|
113
|
-
})
|
|
114
|
-
.split('/')
|
|
115
|
-
.concat(secret.secretKey), secret.secretValue);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
return secretsMap;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Get an Infisical secret value nested within an object.
|
|
122
|
-
*
|
|
123
|
-
* @category Internal
|
|
124
|
-
*/
|
|
125
|
-
export function getNested(parent, keys) {
|
|
126
|
-
const nextKey = keys[0];
|
|
127
|
-
if (nextKey == undefined) {
|
|
128
|
-
throw new Error('Invalid key or ran out of keys.');
|
|
129
|
-
}
|
|
130
|
-
const nextParent = parent[nextKey];
|
|
131
|
-
if (!nextParent) {
|
|
132
|
-
throw new Error(`Nothing at key '${nextKey}'`);
|
|
133
|
-
}
|
|
134
|
-
else if (nextParent instanceof Error) {
|
|
135
|
-
throw nextParent;
|
|
136
|
-
}
|
|
137
|
-
if (keys.length > 1) {
|
|
138
|
-
if (check.isString(nextParent)) {
|
|
139
|
-
throw new TypeError(`Keys still remain but received string value: ${stringify({ keys })}`);
|
|
140
|
-
}
|
|
141
|
-
return getNested(nextParent, keys.slice(1));
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
return nextParent;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Set an Infisical secret value nested within an object.
|
|
149
|
-
*
|
|
150
|
-
* @category Internal
|
|
151
|
-
*/
|
|
152
|
-
export function setNested(parent, keys, value) {
|
|
153
|
-
const nextKey = keys[0];
|
|
154
|
-
if (nextKey == undefined) {
|
|
155
|
-
throw new Error('Invalid key or ran out of keys.');
|
|
156
|
-
}
|
|
157
|
-
if (keys.length === 1) {
|
|
158
|
-
parent[nextKey] = value;
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const nextParent = getOrSet(parent, nextKey, () => {
|
|
162
|
-
return {};
|
|
163
|
-
});
|
|
164
|
-
if (check.isString(nextParent) || check.isError(nextParent)) {
|
|
165
|
-
throw new TypeError(`Cannot set key '${nextKey}'; it's already set to a non-object.`);
|
|
166
|
-
}
|
|
167
|
-
return setNested(nextParent, keys.slice(1), value);
|
|
168
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { type JsonCompatibleValue } from '@augment-vir/common';
|
|
2
|
-
import { GetSecretValueCommand, type GetSecretValueCommandOutput } from '@aws-sdk/client-secrets-manager';
|
|
3
|
-
import { type RequireExactlyOne } from 'type-fest';
|
|
4
|
-
/**
|
|
5
|
-
* Mock secrets setup for {@link MockAwsSecretsManagerClient}.
|
|
6
|
-
*
|
|
7
|
-
* @category Internal
|
|
8
|
-
*/
|
|
9
|
-
export type MockAwsSecrets = {
|
|
10
|
-
[AwsSecretId in string]: RequireExactlyOne<{
|
|
11
|
-
/**
|
|
12
|
-
* A value that will be JSON stringified for the `.SecretString` output of AWS Secrets
|
|
13
|
-
* Manager.
|
|
14
|
-
*/
|
|
15
|
-
preJson: JsonCompatibleValue;
|
|
16
|
-
/** Set the raw `.SecretString` output of AWS Secrets Manager directly. */
|
|
17
|
-
rawString: string;
|
|
18
|
-
}>;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* A mock implementation of `SecretsManagerClient` from the
|
|
22
|
-
* [`@aws-sdk/client-secrets-manager`](https://www.npmjs.com/package/@aws-sdk/client-secrets-manager)
|
|
23
|
-
* package.
|
|
24
|
-
*
|
|
25
|
-
* This only mocks the following:
|
|
26
|
-
*
|
|
27
|
-
* - The `.send()` method
|
|
28
|
-
* - Sending a `GetSecretValueCommand` command
|
|
29
|
-
* - Returning a `.SecretString` value from that command
|
|
30
|
-
*
|
|
31
|
-
* @category Mocks
|
|
32
|
-
*/
|
|
33
|
-
export declare class MockAwsSecretsManagerClient {
|
|
34
|
-
private readonly mockSecrets;
|
|
35
|
-
constructor(mockSecrets: MockAwsSecrets);
|
|
36
|
-
/**
|
|
37
|
-
* A mock implementation of `SecretsManagerClient.send()`. Only the first parameter (the
|
|
38
|
-
* command) is used.
|
|
39
|
-
*/
|
|
40
|
-
send(command: GetSecretValueCommand): Promise<GetSecretValueCommandOutput>;
|
|
41
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { assert } from '@augment-vir/assert';
|
|
2
|
-
import { GetSecretValueCommand, } from '@aws-sdk/client-secrets-manager';
|
|
3
|
-
/**
|
|
4
|
-
* A mock implementation of `SecretsManagerClient` from the
|
|
5
|
-
* [`@aws-sdk/client-secrets-manager`](https://www.npmjs.com/package/@aws-sdk/client-secrets-manager)
|
|
6
|
-
* package.
|
|
7
|
-
*
|
|
8
|
-
* This only mocks the following:
|
|
9
|
-
*
|
|
10
|
-
* - The `.send()` method
|
|
11
|
-
* - Sending a `GetSecretValueCommand` command
|
|
12
|
-
* - Returning a `.SecretString` value from that command
|
|
13
|
-
*
|
|
14
|
-
* @category Mocks
|
|
15
|
-
*/
|
|
16
|
-
export class MockAwsSecretsManagerClient {
|
|
17
|
-
mockSecrets;
|
|
18
|
-
constructor(mockSecrets) {
|
|
19
|
-
this.mockSecrets = mockSecrets;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* A mock implementation of `SecretsManagerClient.send()`. Only the first parameter (the
|
|
23
|
-
* command) is used.
|
|
24
|
-
*/
|
|
25
|
-
send(command) {
|
|
26
|
-
if (!(command instanceof GetSecretValueCommand)) {
|
|
27
|
-
throw new TypeError(`AWS Secrets Manager command not mocked: '${command.constructor.name}'`);
|
|
28
|
-
}
|
|
29
|
-
assert.isDefined(command.input.SecretId, 'No SecretId given.');
|
|
30
|
-
const value = this.mockSecrets[command.input.SecretId];
|
|
31
|
-
return Promise.resolve({
|
|
32
|
-
SecretString: value
|
|
33
|
-
? value.preJson
|
|
34
|
-
? JSON.stringify(value.preJson)
|
|
35
|
-
: value.rawString
|
|
36
|
-
: undefined,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { type JsonCompatibleValue } from '@augment-vir/common';
|
|
2
|
-
import { type InfisicalSDK, type ListSecretsOptions, type Secret } from '@infisical/sdk';
|
|
3
|
-
import { type RequireExactlyOne } from 'type-fest';
|
|
4
|
-
/**
|
|
5
|
-
* Mock secrets setup for {@link MockAwsSecretsManagerClient}.
|
|
6
|
-
*
|
|
7
|
-
* @category Internal
|
|
8
|
-
*/
|
|
9
|
-
export type MockInfisicalSecrets = {
|
|
10
|
-
projectId: string;
|
|
11
|
-
/** `'dev'`, `'staging'`, `'prod'`, etc. */
|
|
12
|
-
env: string;
|
|
13
|
-
secrets: {
|
|
14
|
-
[SecretKey in string]: {
|
|
15
|
-
folderPath?: string | undefined;
|
|
16
|
-
} & RequireExactlyOne<{
|
|
17
|
-
/**
|
|
18
|
-
* A value that will be JSON stringified for the `.SecretString` output of AWS Secrets
|
|
19
|
-
* Manager.
|
|
20
|
-
*/
|
|
21
|
-
preJson: JsonCompatibleValue;
|
|
22
|
-
/** Set the raw `.SecretString` output of AWS Secrets Manager directly. */
|
|
23
|
-
rawString: string;
|
|
24
|
-
}>;
|
|
25
|
-
};
|
|
26
|
-
}[];
|
|
27
|
-
/**
|
|
28
|
-
* A mock implementation of `InfisicalSDK` from the
|
|
29
|
-
* [@infisical/sdk](https://www.npmjs.com/package/@infisical/sdk) package. This only mocks what is
|
|
30
|
-
* necessary for the infisical adapter to work.
|
|
31
|
-
*
|
|
32
|
-
* @category Mocks
|
|
33
|
-
*/
|
|
34
|
-
export declare class MockInfisicalSdk {
|
|
35
|
-
/** Mock secrets that will be used in `secrets().listSecrets()` */
|
|
36
|
-
readonly mockSecrets: Readonly<MockInfisicalSecrets>;
|
|
37
|
-
/** Keeps track of whether this SDK has been authorized or not. */
|
|
38
|
-
protected isAuthorized: boolean;
|
|
39
|
-
constructor(
|
|
40
|
-
/** Mock secrets that will be used in `secrets().listSecrets()` */
|
|
41
|
-
mockSecrets: Readonly<MockInfisicalSecrets>);
|
|
42
|
-
/** Mock of `InfisicalSDK.auth()` */
|
|
43
|
-
auth(): {
|
|
44
|
-
/** Mock of `InfisicalSDK.auth().universalAuth` */
|
|
45
|
-
universalAuth: {
|
|
46
|
-
/** Mock of `InfisicalSDK.auth().universalAuth.login()` */
|
|
47
|
-
login: (options: import("@infisical/sdk").UniversalAuthLoginRequest) => Promise<InfisicalSDK>;
|
|
48
|
-
/** Mock of `InfisicalSDK.auth().universalAuth.renew()` */
|
|
49
|
-
renew(): never;
|
|
50
|
-
};
|
|
51
|
-
/** Mock of `InfisicalSDK.auth().accessToken` */
|
|
52
|
-
accessToken: (token: string) => InfisicalSDK;
|
|
53
|
-
/** Mock of `InfisicalSDK.auth().awsIamAuth` */
|
|
54
|
-
awsIamAuth: {
|
|
55
|
-
/** Mock of `InfisicalSDK.auth().awsIamAuth.login()` */
|
|
56
|
-
login: (options?: {
|
|
57
|
-
identityId?: string;
|
|
58
|
-
} | undefined) => Promise<InfisicalSDK>;
|
|
59
|
-
/** Mock of `InfisicalSDK.auth().awsIamAuth.renew()` */
|
|
60
|
-
renew(): never;
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
|
-
/** Mock of `InfisicalSDK.secrets()` */
|
|
64
|
-
secrets(): {
|
|
65
|
-
/** Mock of `InfisicalSDK.secrets().listSecrets()` */
|
|
66
|
-
listSecrets: ({ projectId, environment }: ListSecretsOptions) => Promise<{
|
|
67
|
-
secrets: Partial<Secret>[];
|
|
68
|
-
}>;
|
|
69
|
-
};
|
|
70
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { getObjectTypedEntries, stringifyWithJson5, } from '@augment-vir/common';
|
|
2
|
-
/**
|
|
3
|
-
* A mock implementation of `InfisicalSDK` from the
|
|
4
|
-
* [@infisical/sdk](https://www.npmjs.com/package/@infisical/sdk) package. This only mocks what is
|
|
5
|
-
* necessary for the infisical adapter to work.
|
|
6
|
-
*
|
|
7
|
-
* @category Mocks
|
|
8
|
-
*/
|
|
9
|
-
export class MockInfisicalSdk {
|
|
10
|
-
mockSecrets;
|
|
11
|
-
/** Keeps track of whether this SDK has been authorized or not. */
|
|
12
|
-
isAuthorized = false;
|
|
13
|
-
constructor(
|
|
14
|
-
/** Mock secrets that will be used in `secrets().listSecrets()` */
|
|
15
|
-
mockSecrets) {
|
|
16
|
-
this.mockSecrets = mockSecrets;
|
|
17
|
-
}
|
|
18
|
-
/** Mock of `InfisicalSDK.auth()` */
|
|
19
|
-
auth() {
|
|
20
|
-
return {
|
|
21
|
-
/** Mock of `InfisicalSDK.auth().universalAuth` */
|
|
22
|
-
universalAuth: {
|
|
23
|
-
/** Mock of `InfisicalSDK.auth().universalAuth.login()` */
|
|
24
|
-
login: (
|
|
25
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
26
|
-
...params) => {
|
|
27
|
-
this.isAuthorized = true;
|
|
28
|
-
return Promise.resolve(this);
|
|
29
|
-
},
|
|
30
|
-
/** Mock of `InfisicalSDK.auth().universalAuth.renew()` */
|
|
31
|
-
renew() {
|
|
32
|
-
throw new Error('Not mocked.');
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
/** Mock of `InfisicalSDK.auth().accessToken` */
|
|
36
|
-
accessToken: (
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
38
|
-
...params) => {
|
|
39
|
-
this.isAuthorized = true;
|
|
40
|
-
return this;
|
|
41
|
-
},
|
|
42
|
-
/** Mock of `InfisicalSDK.auth().awsIamAuth` */
|
|
43
|
-
awsIamAuth: {
|
|
44
|
-
/** Mock of `InfisicalSDK.auth().awsIamAuth.login()` */
|
|
45
|
-
login: (
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
47
|
-
...params) => {
|
|
48
|
-
this.isAuthorized = true;
|
|
49
|
-
return Promise.resolve(this);
|
|
50
|
-
},
|
|
51
|
-
/** Mock of `InfisicalSDK.auth().awsIamAuth.renew()` */
|
|
52
|
-
renew() {
|
|
53
|
-
throw new Error('Not mocked.');
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
/** Mock of `InfisicalSDK.secrets()` */
|
|
59
|
-
secrets() {
|
|
60
|
-
return {
|
|
61
|
-
/** Mock of `InfisicalSDK.secrets().listSecrets()` */
|
|
62
|
-
listSecrets: ({ projectId, environment }) => {
|
|
63
|
-
if (!this.isAuthorized) {
|
|
64
|
-
throw new Error('Mock Infisical SDK client not authorized.');
|
|
65
|
-
}
|
|
66
|
-
const secrets = this.mockSecrets.find((mock) => mock.projectId === projectId && mock.env === environment)?.secrets;
|
|
67
|
-
if (!secrets) {
|
|
68
|
-
throw new Error('Invalid project.');
|
|
69
|
-
}
|
|
70
|
-
return Promise.resolve({
|
|
71
|
-
secrets: getObjectTypedEntries(secrets).map(([secretKey, mockDefinition,]) => {
|
|
72
|
-
return {
|
|
73
|
-
secretPath: mockDefinition.folderPath || '/',
|
|
74
|
-
secretKey,
|
|
75
|
-
secretValue: mockDefinition.preJson
|
|
76
|
-
? stringifyWithJson5(mockDefinition.preJson)
|
|
77
|
-
: mockDefinition.rawString || '',
|
|
78
|
-
};
|
|
79
|
-
}),
|
|
80
|
-
});
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}
|