@red-hat-developer-hub/e2e-test-utils 2.1.15 → 2.1.17

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.
Files changed (93) hide show
  1. package/dist/deployment/rhdh/config/common/dynamic-plugins.yaml +5 -0
  2. package/dist/deployment/rhdh/constants.d.ts +0 -5
  3. package/dist/deployment/rhdh/constants.d.ts.map +1 -1
  4. package/dist/deployment/rhdh/constants.js +0 -5
  5. package/dist/deployment/rhdh/deployment.d.ts +2 -2
  6. package/dist/deployment/rhdh/deployment.d.ts.map +1 -1
  7. package/dist/deployment/rhdh/deployment.js +9 -25
  8. package/dist/deployment/rhdh/deployment.test.js +9 -9
  9. package/dist/deployment/rhdh/types.d.ts +0 -4
  10. package/dist/deployment/rhdh/types.d.ts.map +1 -1
  11. package/dist/playwright/helpers/github-session.test.js +1 -1
  12. package/dist/secrets/bitwarden.d.ts +43 -0
  13. package/dist/secrets/bitwarden.d.ts.map +1 -1
  14. package/dist/secrets/bitwarden.js +465 -53
  15. package/dist/secrets/cli.d.ts +48 -1
  16. package/dist/secrets/cli.d.ts.map +1 -1
  17. package/dist/secrets/cli.js +455 -28
  18. package/dist/secrets/command.d.ts +7 -0
  19. package/dist/secrets/command.d.ts.map +1 -1
  20. package/dist/secrets/command.js +103 -26
  21. package/dist/secrets/config.d.ts +11 -1
  22. package/dist/secrets/config.d.ts.map +1 -1
  23. package/dist/secrets/config.js +32 -1
  24. package/dist/secrets/environment-name.d.ts +2 -0
  25. package/dist/secrets/environment-name.d.ts.map +1 -0
  26. package/dist/secrets/environment-name.js +4 -0
  27. package/dist/secrets/environment.d.ts +10 -0
  28. package/dist/secrets/environment.d.ts.map +1 -1
  29. package/dist/secrets/environment.js +35 -4
  30. package/dist/secrets/exec.d.ts +9 -1
  31. package/dist/secrets/exec.d.ts.map +1 -1
  32. package/dist/secrets/exec.js +318 -38
  33. package/dist/secrets/gsm-wrapper.d.ts +42 -0
  34. package/dist/secrets/gsm-wrapper.d.ts.map +1 -0
  35. package/dist/secrets/gsm-wrapper.js +238 -0
  36. package/dist/secrets/gsm.d.ts +33 -0
  37. package/dist/secrets/gsm.d.ts.map +1 -0
  38. package/dist/secrets/gsm.js +172 -0
  39. package/dist/secrets/index.d.ts +9 -4
  40. package/dist/secrets/index.d.ts.map +1 -1
  41. package/dist/secrets/index.js +7 -2
  42. package/dist/secrets/lock.d.ts +3 -0
  43. package/dist/secrets/lock.d.ts.map +1 -0
  44. package/dist/secrets/lock.js +28 -0
  45. package/dist/secrets/mutation.d.ts +71 -0
  46. package/dist/secrets/mutation.d.ts.map +1 -0
  47. package/dist/secrets/mutation.js +210 -0
  48. package/dist/secrets/secret-input.d.ts +17 -0
  49. package/dist/secrets/secret-input.d.ts.map +1 -0
  50. package/dist/secrets/secret-input.js +40 -0
  51. package/dist/secrets/stream.d.ts +9 -0
  52. package/dist/secrets/stream.d.ts.map +1 -0
  53. package/dist/secrets/stream.js +211 -0
  54. package/dist/secrets/temporary-secret.d.ts +13 -0
  55. package/dist/secrets/temporary-secret.d.ts.map +1 -0
  56. package/dist/secrets/temporary-secret.js +87 -0
  57. package/dist/secrets/tests/bitwarden.test.js +640 -0
  58. package/dist/secrets/tests/cli.integration.test.js +131 -3
  59. package/dist/secrets/tests/cli.test.js +281 -6
  60. package/dist/secrets/tests/command.test.d.ts +2 -0
  61. package/dist/secrets/tests/command.test.d.ts.map +1 -0
  62. package/dist/secrets/tests/command.test.js +39 -0
  63. package/dist/secrets/tests/config.test.js +20 -1
  64. package/dist/secrets/tests/environment.test.js +145 -3
  65. package/dist/secrets/tests/exec.test.js +469 -2
  66. package/dist/secrets/tests/gsm-wrapper.test.d.ts +2 -0
  67. package/dist/secrets/tests/gsm-wrapper.test.d.ts.map +1 -0
  68. package/dist/secrets/tests/gsm-wrapper.test.js +166 -0
  69. package/dist/secrets/tests/gsm.test.d.ts +2 -0
  70. package/dist/secrets/tests/gsm.test.d.ts.map +1 -0
  71. package/dist/secrets/tests/gsm.test.js +181 -0
  72. package/dist/secrets/tests/mutation.test.d.ts +2 -0
  73. package/dist/secrets/tests/mutation.test.d.ts.map +1 -0
  74. package/dist/secrets/tests/mutation.test.js +390 -0
  75. package/dist/secrets/tests/secret-input.test.d.ts +2 -0
  76. package/dist/secrets/tests/secret-input.test.d.ts.map +1 -0
  77. package/dist/secrets/tests/secret-input.test.js +71 -0
  78. package/dist/secrets/tests/stream.test.d.ts +2 -0
  79. package/dist/secrets/tests/stream.test.d.ts.map +1 -0
  80. package/dist/secrets/tests/stream.test.js +88 -0
  81. package/dist/secrets/tests/temporary-secret.test.d.ts +2 -0
  82. package/dist/secrets/tests/temporary-secret.test.d.ts.map +1 -0
  83. package/dist/secrets/tests/temporary-secret.test.js +76 -0
  84. package/dist/utils/plugin-metadata.d.ts +5 -0
  85. package/dist/utils/plugin-metadata.d.ts.map +1 -1
  86. package/dist/utils/plugin-metadata.js +19 -0
  87. package/dist/utils/tests/plugin-metadata.test.js +17 -1
  88. package/dist/utils/workspace-paths.d.ts +0 -2
  89. package/dist/utils/workspace-paths.d.ts.map +1 -1
  90. package/dist/utils/workspace-paths.js +0 -4
  91. package/package.json +1 -1
  92. package/dist/deployment/rhdh/config/new-frontend-system/dynamic-plugins.yaml +0 -5
  93. package/dist/deployment/rhdh/config/new-frontend-system/value_file.yaml +0 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gsm.d.ts","sourceRoot":"","sources":["../../src/secrets/gsm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGxE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAElD,qBAAa,gBAAiB,SAAQ,KAAK;aACb,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAI/C;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,CACD,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;gBAEX,OAAO,GAAE,gBAAqB;IAcpC,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IA2BtE,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUhE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IA8B3C,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIlC,MAAM,CACV,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAWV,MAAM,CACV,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAUV,MAAM,CACV,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;CASjB;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,OAAO,GACrB,KAAK,CAIP"}
@@ -0,0 +1,172 @@
1
+ import { GsmWrapper } from "./gsm-wrapper.js";
2
+ import { MAX_TIMEOUT_MS } from "./command.js";
3
+ export class GsmNotFoundError extends Error {
4
+ secretPath;
5
+ constructor(secretPath) {
6
+ super(`GSM target does not exist: ${secretPath}`);
7
+ this.secretPath = secretPath;
8
+ this.name = "GsmNotFoundError";
9
+ }
10
+ }
11
+ export class GsmClient {
12
+ runner;
13
+ metadataTimeoutMs;
14
+ wrapperSha256;
15
+ constructor(options = {}) {
16
+ this.runner = options.runner ?? new GsmWrapper();
17
+ this.metadataTimeoutMs = options.metadataTimeoutMs ?? 60_000;
18
+ if (!Number.isSafeInteger(this.metadataTimeoutMs) ||
19
+ this.metadataTimeoutMs <= 0 ||
20
+ this.metadataTimeoutMs > MAX_TIMEOUT_MS) {
21
+ throw new Error(`GSM metadata timeout must be a positive integer no greater than ${MAX_TIMEOUT_MS}ms`);
22
+ }
23
+ }
24
+ async describe(collection, secretPath) {
25
+ const result = await this.runner.run(["describe", "-c", collection, secretPath], this.metadataTimeoutMs);
26
+ this.wrapperSha256 = result.sha256;
27
+ if (result.timedOut) {
28
+ throw new Error(`GSM metadata check timed out: ${secretPath}`);
29
+ }
30
+ if (result.status !== 0) {
31
+ if (isAuthenticationResult(result)) {
32
+ throw new Error("GSM authentication is unavailable; run `rhdh-e2e-secrets gsm-login` first");
33
+ }
34
+ if (isNotFoundResult(result))
35
+ throw new GsmNotFoundError(secretPath);
36
+ throw new Error(`GSM target does not exist or is inaccessible: ${secretPath}`);
37
+ }
38
+ const metadata = parseMetadata(result.stdout);
39
+ if (!metadata) {
40
+ throw new Error(`GSM returned invalid metadata for ${secretPath}`);
41
+ }
42
+ return metadata;
43
+ }
44
+ async exists(collection, secretPath) {
45
+ try {
46
+ await this.describe(collection, secretPath);
47
+ return true;
48
+ }
49
+ catch (error) {
50
+ if (error instanceof GsmNotFoundError)
51
+ return false;
52
+ throw error;
53
+ }
54
+ }
55
+ async list(collection) {
56
+ const result = await this.runner.run(["list", "-c", collection, "-o", "json"], this.metadataTimeoutMs);
57
+ this.wrapperSha256 = result.sha256;
58
+ if (result.timedOut)
59
+ throw new Error("GSM list timed out");
60
+ if (result.status !== 0) {
61
+ if (isAuthenticationResult(result)) {
62
+ throw new Error("GSM authentication is unavailable; run `rhdh-e2e-secrets gsm-login` first");
63
+ }
64
+ throw new Error("GSM list failed");
65
+ }
66
+ let value;
67
+ try {
68
+ value = JSON.parse(result.stdout);
69
+ }
70
+ catch {
71
+ throw new Error("GSM returned invalid list output");
72
+ }
73
+ if (!Array.isArray(value) ||
74
+ value.some((entry) => typeof entry !== "string")) {
75
+ throw new Error("GSM returned invalid list output");
76
+ }
77
+ return value;
78
+ }
79
+ async ensureInteractive() {
80
+ await this.runner.ensureInteractive?.();
81
+ }
82
+ async create(collection, secretPath, snapshotPath, timeoutMs) {
83
+ validateTimeout(timeoutMs);
84
+ const result = await this.runner.run(["create", "-c", collection, secretPath, "--from-file", snapshotPath], timeoutMs, { stdio: "inherit", tty: true });
85
+ this.wrapperSha256 = result.sha256;
86
+ assertMutationResult("create", result, secretPath, timeoutMs);
87
+ }
88
+ async update(collection, secretPath, snapshotPath, timeoutMs) {
89
+ validateTimeout(timeoutMs);
90
+ const result = await this.runner.run(["update", "-c", collection, secretPath, "--from-file", snapshotPath], timeoutMs);
91
+ this.wrapperSha256 = result.sha256;
92
+ assertMutationResult("update", result, secretPath, timeoutMs);
93
+ }
94
+ async delete(collection, secretPath, timeoutMs) {
95
+ validateTimeout(timeoutMs);
96
+ const result = await this.runner.run(["delete", "-c", collection, secretPath], timeoutMs);
97
+ this.wrapperSha256 = result.sha256;
98
+ assertMutationResult("delete", result, secretPath, timeoutMs);
99
+ }
100
+ }
101
+ export function gsmMutationError(message, indeterminate) {
102
+ const error = new Error(message);
103
+ error.indeterminate = indeterminate;
104
+ return error;
105
+ }
106
+ function isIndeterminateResult(result) {
107
+ return (result.timedOut === true ||
108
+ (result.status === null && result.signal !== undefined));
109
+ }
110
+ function assertMutationResult(operation, result, secretPath, timeoutMs) {
111
+ if (isIndeterminateResult(result)) {
112
+ const reason = result.timedOut
113
+ ? `after ${timeoutMs}ms`
114
+ : `after signal ${result.signal ?? "process termination"}`;
115
+ throw gsmMutationError(`GSM ${operation} is indeterminate ${reason}: ${secretPath}`, true);
116
+ }
117
+ if (result.status !== 0) {
118
+ throw gsmMutationError(`GSM ${operation} failed: ${secretPath}`, false);
119
+ }
120
+ }
121
+ function isNotFoundResult(result) {
122
+ return /secret\s+['"][^'"]+['"]\s+does not exist(?:\s+in collection)?/i.test(`${result.stdout}\n${result.stderr}`);
123
+ }
124
+ function isAuthenticationResult(result) {
125
+ return /defaultcredentials?error|default credentials were not found|application-default credentials/i.test(`${result.stdout}\n${result.stderr}`);
126
+ }
127
+ function parseMetadata(output) {
128
+ try {
129
+ const value = JSON.parse(output);
130
+ if (isRecord(value)) {
131
+ const metadata = {};
132
+ for (const key of METADATA_KEYS) {
133
+ if (typeof value[key] === "string")
134
+ metadata[key] = value[key];
135
+ }
136
+ return Object.keys(metadata).length > 0 ? metadata : undefined;
137
+ }
138
+ }
139
+ catch {
140
+ // The deployed wrapper defaults to the stable human-readable format.
141
+ }
142
+ const fields = [
143
+ ["create-time", /^Created:\s*(.*)$/m],
144
+ ["jira-project", /^JIRA project:\s*(.*)$/m],
145
+ ["rotation-instructions", /^Rotation instructions:\s*(.*)$/m],
146
+ ["request-information", /^Request information:\s*(.*)$/m],
147
+ ];
148
+ const metadata = {};
149
+ for (const [key, pattern] of fields) {
150
+ const match = output.match(pattern);
151
+ if (!match)
152
+ return undefined;
153
+ metadata[key] = match[1].trim();
154
+ }
155
+ return metadata;
156
+ }
157
+ const METADATA_KEYS = [
158
+ "create-time",
159
+ "jira-project",
160
+ "rotation-instructions",
161
+ "request-information",
162
+ ];
163
+ function validateTimeout(timeoutMs) {
164
+ if (!Number.isSafeInteger(timeoutMs) ||
165
+ timeoutMs <= 0 ||
166
+ timeoutMs > MAX_TIMEOUT_MS) {
167
+ throw new Error(`GSM operation timeout must be a positive integer no greater than ${MAX_TIMEOUT_MS}ms`);
168
+ }
169
+ }
170
+ function isRecord(value) {
171
+ return typeof value === "object" && value !== null && !Array.isArray(value);
172
+ }
@@ -1,5 +1,10 @@
1
- export { expandProfile, getCollectionMapping, parseProfile, type CollectionMapping, type EnvironmentDestination, type ExpandedSecretProfile, type ExpandedSecretSelector, type ReadableCollectionId, type SecretProfile, type SecretSelector, } from "./config.js";
2
- export { BitwardenClient, type BitwardenClientOptions, type BitwardenAttachment, type BitwardenSecret, } from "./bitwarden.js";
3
- export { executeCommand, runChild, type ChildRunner, type ExecuteCommandOptions, type SecretReader, } from "./exec.js";
4
- export { materializeEnvironment, removeProviderEnvironmentVariables, type EnvironmentSecret, } from "./environment.js";
1
+ export { bitwardenPathFromGsmPath, expandProfile, getCollectionMapping, gsmPathFromBitwardenPath, parseProfile, COLLECTIONS, READABLE_COLLECTIONS, validateSecretPath, type CollectionMapping, type EnvironmentDestination, type ExpandedSecretProfile, type ExpandedSecretSelector, type ReadableCollectionId, type SecretProfile, type SecretSelector, } from "./config.js";
2
+ export { BitwardenClient, type BitwardenClientOptions, type BitwardenAttachment, type BitwardenSecretItem, type BitwardenSecretStorage, type BitwardenSecret, } from "./bitwarden.js";
3
+ export { executeCommand, runChild, type ChildRunner, type ChildRunnerOptions, type ExecuteCommandOptions, type SecretReader, } from "./exec.js";
4
+ export { materializeEnvironment, materializeEnvironmentWithSecrets, removeProviderEnvironmentVariables, type EnvironmentSecret, type MaterializedEnvironment, type MaterializedSecret, } from "./environment.js";
5
+ export { GsmClient, GsmNotFoundError, type GsmClientOptions, type GsmMetadata, type GsmRunner, type GsmRunOptions, } from "./gsm.js";
6
+ export { GsmWrapper, defaultCacheDir, validateWrapper, type GsmWrapperMetadata, type GsmWrapperOptions, type GsmWrapperRunResult, } from "./gsm-wrapper.js";
7
+ export { executeMutation, createPlan, type ExecuteMutationOptions, type MutationAction, type MutationBitwarden, type MutationCommand, type MutationGsm, type MutationPlan, type MutationResult, } from "./mutation.js";
8
+ export { readSecretInput, type SecretInput, type SecretInputOptions, } from "./secret-input.js";
9
+ export { decodeSecretStream, SECRET_STREAM_ENVIRONMENT_VARIABLE, SECRET_STREAM_FD, writeSecretStream, type SecretStreamEntry, } from "./stream.js";
5
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/secrets/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,cAAc,EACd,QAAQ,EACR,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,sBAAsB,EACtB,kCAAkC,EAClC,KAAK,iBAAiB,GACvB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/secrets/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,aAAa,EACb,oBAAoB,EACpB,wBAAwB,EACxB,YAAY,EACZ,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,cAAc,EACd,QAAQ,EACR,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,sBAAsB,EACtB,iCAAiC,EACjC,kCAAkC,EAClC,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,aAAa,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,UAAU,EACV,eAAe,EACf,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,eAAe,EACf,UAAU,EACV,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,cAAc,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,kBAAkB,EAClB,kCAAkC,EAClC,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAC"}
@@ -1,4 +1,9 @@
1
- export { expandProfile, getCollectionMapping, parseProfile, } from "./config.js";
1
+ export { bitwardenPathFromGsmPath, expandProfile, getCollectionMapping, gsmPathFromBitwardenPath, parseProfile, COLLECTIONS, READABLE_COLLECTIONS, validateSecretPath, } from "./config.js";
2
2
  export { BitwardenClient, } from "./bitwarden.js";
3
3
  export { executeCommand, runChild, } from "./exec.js";
4
- export { materializeEnvironment, removeProviderEnvironmentVariables, } from "./environment.js";
4
+ export { materializeEnvironment, materializeEnvironmentWithSecrets, removeProviderEnvironmentVariables, } from "./environment.js";
5
+ export { GsmClient, GsmNotFoundError, } from "./gsm.js";
6
+ export { GsmWrapper, defaultCacheDir, validateWrapper, } from "./gsm-wrapper.js";
7
+ export { executeMutation, createPlan, } from "./mutation.js";
8
+ export { readSecretInput, } from "./secret-input.js";
9
+ export { decodeSecretStream, SECRET_STREAM_ENVIRONMENT_VARIABLE, SECRET_STREAM_FD, writeSecretStream, } from "./stream.js";
@@ -0,0 +1,3 @@
1
+ export declare function withSecretLock<T>(key: string, stateDir: string, operation: () => Promise<T>): Promise<T>;
2
+ export declare function defaultLockDir(env?: NodeJS.ProcessEnv): string;
3
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/secrets/lock.ts"],"names":[],"mappings":"AAMA,wBAAsB,cAAc,CAAC,CAAC,EACpC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAC1B,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAED,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAO3E"}
@@ -0,0 +1,28 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import lockfile from "proper-lockfile";
6
+ export async function withSecretLock(key, stateDir, operation) {
7
+ const lockDir = path.join(stateDir, ".locks");
8
+ await mkdir(lockDir, { recursive: true, mode: 0o700 });
9
+ const target = path.join(lockDir, `${createHash("sha256").update(key).digest("hex")}.lock`);
10
+ await writeFile(target, "", { flag: "a", mode: 0o600 });
11
+ const release = await lockfile.lock(target, {
12
+ retries: 0,
13
+ stale: 300_000,
14
+ });
15
+ try {
16
+ return await operation();
17
+ }
18
+ finally {
19
+ await release();
20
+ }
21
+ }
22
+ export function defaultLockDir(env = process.env) {
23
+ const configuredRoot = env.XDG_STATE_HOME;
24
+ const stateRoot = configuredRoot && path.isAbsolute(configuredRoot)
25
+ ? configuredRoot
26
+ : path.join(os.homedir(), ".local", "state");
27
+ return path.join(stateRoot, "rhdh-e2e-secrets");
28
+ }
@@ -0,0 +1,71 @@
1
+ import { type BitwardenSecretItem, type BitwardenSecretStorage } from "./bitwarden.js";
2
+ import { type ReadableCollectionId } from "./config.js";
3
+ import { type SecretInput } from "./secret-input.js";
4
+ export type MutationCommand = "create" | "update" | "delete";
5
+ export type MutationAction = "create" | "update" | "delete" | "skip";
6
+ export interface MutationBitwarden {
7
+ findItem(collection: ReadableCollectionId, name: string): Promise<BitwardenSecretItem | undefined>;
8
+ createItem(collection: ReadableCollectionId, name: string, value: string, storage: BitwardenSecretStorage): Promise<BitwardenSecretItem>;
9
+ updateItem(item: BitwardenSecretItem, value: string, storage?: BitwardenSecretStorage): Promise<BitwardenSecretItem>;
10
+ deleteItem(item: BitwardenSecretItem): Promise<void>;
11
+ }
12
+ export interface MutationGsm {
13
+ wrapperSha256?: string;
14
+ exists(collection: string, path: string): Promise<boolean>;
15
+ create(collection: string, path: string, snapshotPath: string, timeoutMs: number): Promise<void>;
16
+ update(collection: string, path: string, snapshotPath: string, timeoutMs: number): Promise<void>;
17
+ delete(collection: string, path: string, timeoutMs: number): Promise<void>;
18
+ ensureInteractive?: () => Promise<void>;
19
+ }
20
+ export interface MutationPlan {
21
+ command: MutationCommand;
22
+ collection: ReadableCollectionId;
23
+ bitwardenCollection: string;
24
+ gsmCollection: string;
25
+ bitwardenPath: string;
26
+ gsmPath: string;
27
+ byteLength?: number;
28
+ storage?: BitwardenSecretStorage;
29
+ existingStorage?: BitwardenSecretStorage;
30
+ bitwardenAction: MutationAction;
31
+ gsmAction: MutationAction;
32
+ }
33
+ export interface ExecuteMutationOptions {
34
+ command: MutationCommand;
35
+ collection: string;
36
+ bitwardenPath: string;
37
+ fromFile?: string;
38
+ fromStdin?: boolean;
39
+ stdin?: Iterable<Buffer | string> | AsyncIterable<Buffer | string>;
40
+ allowEmpty?: boolean;
41
+ force?: boolean;
42
+ dryRun?: boolean;
43
+ gsmTimeoutMs?: number;
44
+ lockDirectory?: string;
45
+ readFile?: (path: string) => Promise<Buffer>;
46
+ bitwarden?: MutationBitwarden;
47
+ gsm?: MutationGsm;
48
+ }
49
+ export type MutationResult = {
50
+ state: "dry-run";
51
+ plan: MutationPlan;
52
+ } | {
53
+ state: "applied";
54
+ plan: MutationPlan;
55
+ };
56
+ export declare function executeMutation(options: ExecuteMutationOptions): Promise<MutationResult>;
57
+ interface PlanInput {
58
+ command: MutationCommand;
59
+ collection: ReadableCollectionId;
60
+ bitwardenCollection: string;
61
+ gsmCollection: string;
62
+ bitwardenPath: string;
63
+ gsmPath: string;
64
+ input?: SecretInput;
65
+ existingBitwarden?: BitwardenSecretItem;
66
+ existingGsm: boolean;
67
+ force: boolean;
68
+ }
69
+ export declare function createPlan(input: PlanInput): MutationPlan;
70
+ export {};
71
+ //# sourceMappingURL=mutation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation.d.ts","sourceRoot":"","sources":["../../src/secrets/mutation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,210 @@
1
+ import { BitwardenClient, } from "./bitwarden.js";
2
+ import { bitwardenPathFromGsmPath, getCollectionMapping, gsmPathFromBitwardenPath, } from "./config.js";
3
+ import { GsmClient } from "./gsm.js";
4
+ import { MAX_TIMEOUT_MS } from "./command.js";
5
+ import { defaultLockDir, withSecretLock } from "./lock.js";
6
+ import { readSecretInput } from "./secret-input.js";
7
+ import { createTemporarySecretFile, } from "./temporary-secret.js";
8
+ const DEFAULT_GSM_TIMEOUT_MS = 600_000;
9
+ export async function executeMutation(options) {
10
+ const bitwardenPath = bitwardenPathFromGsmPath(options.bitwardenPath);
11
+ const mapping = getCollectionMapping(options.collection);
12
+ const gsmPath = gsmPathFromBitwardenPath(bitwardenPath);
13
+ const timeoutMs = options.gsmTimeoutMs ?? DEFAULT_GSM_TIMEOUT_MS;
14
+ if (!Number.isSafeInteger(timeoutMs) ||
15
+ timeoutMs <= 0 ||
16
+ timeoutMs > MAX_TIMEOUT_MS) {
17
+ throw new Error(`GSM timeout must be a positive integer no greater than ${MAX_TIMEOUT_MS}ms`);
18
+ }
19
+ if (options.command === "update" && options.force) {
20
+ throw new Error("--force is not supported for update");
21
+ }
22
+ if (options.command === "delete" &&
23
+ (options.fromFile !== undefined ||
24
+ options.fromStdin === true ||
25
+ options.allowEmpty === true)) {
26
+ throw new Error("delete does not accept input options");
27
+ }
28
+ const bitwarden = options.bitwarden ?? new BitwardenClient({ env: process.env });
29
+ const gsm = options.gsm ?? new GsmClient();
30
+ const input = options.command === "delete"
31
+ ? undefined
32
+ : await readSecretInput({
33
+ fromFile: options.fromFile,
34
+ fromStdin: options.fromStdin,
35
+ allowEmpty: options.allowEmpty,
36
+ stdin: options.stdin,
37
+ readFile: options.readFile,
38
+ });
39
+ return withSecretLock(`${mapping.id}:${gsmPath}`, options.lockDirectory ?? defaultLockDir(), async () => {
40
+ const [existingBitwarden, existingGsm] = await Promise.all([
41
+ bitwarden.findItem(mapping.id, bitwardenPath),
42
+ gsm.exists(mapping.gsmCollection, gsmPath),
43
+ ]);
44
+ const plan = createPlan({
45
+ command: options.command,
46
+ collection: mapping.id,
47
+ bitwardenCollection: mapping.bitwardenCollection,
48
+ gsmCollection: mapping.gsmCollection,
49
+ bitwardenPath,
50
+ gsmPath,
51
+ input,
52
+ existingBitwarden,
53
+ existingGsm,
54
+ force: options.force === true,
55
+ });
56
+ if (options.dryRun === true)
57
+ return { state: "dry-run", plan };
58
+ if (plan.gsmAction === "create") {
59
+ await gsm.ensureInteractive?.();
60
+ }
61
+ const snapshot = input !== undefined &&
62
+ (plan.gsmAction === "create" || plan.gsmAction === "update")
63
+ ? await createSnapshot(input.value)
64
+ : undefined;
65
+ let bitwardenState = "not-started";
66
+ let operationError;
67
+ try {
68
+ if (plan.bitwardenAction !== "skip") {
69
+ bitwardenState = "attempted";
70
+ await executeBitwardenAction(plan, existingBitwarden, input, bitwarden);
71
+ bitwardenState = "confirmed";
72
+ }
73
+ await executeGsmAction(plan, snapshot, timeoutMs, gsm);
74
+ }
75
+ catch (error) {
76
+ operationError = error;
77
+ }
78
+ let cleanupError;
79
+ if (snapshot !== undefined) {
80
+ try {
81
+ await removeSnapshot(snapshot);
82
+ }
83
+ catch (error) {
84
+ cleanupError = error;
85
+ }
86
+ }
87
+ if (operationError !== undefined) {
88
+ const failure = mutationFailure(operationError, options.command, bitwardenState, options.force === true);
89
+ if (cleanupError !== undefined) {
90
+ throw new Error(`${failure.message}; temporary secret cleanup failed`, {
91
+ cause: new AggregateError([failure, cleanupError]),
92
+ });
93
+ }
94
+ throw failure;
95
+ }
96
+ if (cleanupError !== undefined) {
97
+ throw new Error("Secret operation may have succeeded but temporary secret cleanup failed; inspect providers before retrying", { cause: cleanupError });
98
+ }
99
+ return { state: "applied", plan };
100
+ });
101
+ }
102
+ export function createPlan(input) {
103
+ const hasBitwarden = input.existingBitwarden !== undefined;
104
+ const hasGsm = input.existingGsm;
105
+ let bitwardenAction;
106
+ let gsmAction;
107
+ if (input.command === "create") {
108
+ if ((hasBitwarden || hasGsm) && !input.force) {
109
+ throw new Error("Secret already exists in one or both providers; retry with --force to reconcile it");
110
+ }
111
+ bitwardenAction = hasBitwarden ? "update" : "create";
112
+ gsmAction = hasGsm ? "update" : "create";
113
+ }
114
+ else if (input.command === "update") {
115
+ if (!hasBitwarden || !hasGsm) {
116
+ throw new Error("Secret is absent from one or both providers; update requires both targets");
117
+ }
118
+ bitwardenAction = "update";
119
+ gsmAction = "update";
120
+ }
121
+ else {
122
+ if ((!hasBitwarden || !hasGsm) && !input.force) {
123
+ throw new Error("Secret is absent from one or both providers; retry with --force to reconcile it");
124
+ }
125
+ bitwardenAction = hasBitwarden ? "delete" : "skip";
126
+ gsmAction = hasGsm ? "delete" : "skip";
127
+ }
128
+ return {
129
+ command: input.command,
130
+ collection: input.collection,
131
+ bitwardenCollection: input.bitwardenCollection,
132
+ gsmCollection: input.gsmCollection,
133
+ bitwardenPath: input.bitwardenPath,
134
+ gsmPath: input.gsmPath,
135
+ ...(input.input === undefined
136
+ ? {}
137
+ : {
138
+ byteLength: input.input.byteLength,
139
+ storage: input.input.storage,
140
+ }),
141
+ ...(input.existingBitwarden === undefined
142
+ ? {}
143
+ : { existingStorage: input.existingBitwarden.storage }),
144
+ bitwardenAction,
145
+ gsmAction,
146
+ };
147
+ }
148
+ async function executeBitwardenAction(plan, existing, input, bitwarden) {
149
+ if (plan.bitwardenAction === "skip")
150
+ return;
151
+ if (plan.bitwardenAction === "delete") {
152
+ if (!existing)
153
+ throw new Error("Bitwarden target disappeared before delete");
154
+ await bitwarden.deleteItem(existing);
155
+ return;
156
+ }
157
+ if (!input)
158
+ throw new Error("Secret input is required for this operation");
159
+ if (plan.bitwardenAction === "create") {
160
+ await bitwarden.createItem(plan.collection, plan.bitwardenPath, input.value, input.storage);
161
+ return;
162
+ }
163
+ if (!existing)
164
+ throw new Error("Bitwarden target disappeared before update");
165
+ await bitwarden.updateItem(existing, input.value, plan.command === "create" ? input.storage : existing.storage);
166
+ }
167
+ async function executeGsmAction(plan, snapshot, timeoutMs, gsm) {
168
+ if (plan.gsmAction === "skip")
169
+ return;
170
+ if (plan.gsmAction === "delete") {
171
+ await gsm.delete(plan.gsmCollection, plan.gsmPath, timeoutMs);
172
+ return;
173
+ }
174
+ if (!snapshot)
175
+ throw new Error("Secret snapshot is required for GSM update");
176
+ if (plan.gsmAction === "create") {
177
+ await gsm.create(plan.gsmCollection, plan.gsmPath, snapshot.path, timeoutMs);
178
+ }
179
+ else {
180
+ await gsm.update(plan.gsmCollection, plan.gsmPath, snapshot.path, timeoutMs);
181
+ }
182
+ }
183
+ function mutationFailure(error, command, bitwardenState, forceRequested) {
184
+ const message = error instanceof Error ? error.message : "Secret operation failed";
185
+ const requiresForce = command === "create" || command === "delete"
186
+ ? forceRequested || bitwardenState !== "not-started"
187
+ : false;
188
+ const force = requiresForce ? " with --force" : "";
189
+ const retry = `retry the ${command} command${force}`;
190
+ const bitwardenMessage = bitwardenState === "confirmed"
191
+ ? `Bitwarden was updated first, so ${retry}`
192
+ : bitwardenState === "attempted"
193
+ ? `Bitwarden may have been updated, so ${retry}`
194
+ : retry;
195
+ const indeterminate = isIndeterminateError(error) &&
196
+ "; GSM operation may be indeterminate; inspect GSM before retrying";
197
+ return new Error(`${message}; ${bitwardenMessage}${indeterminate}`, {
198
+ cause: error,
199
+ });
200
+ }
201
+ async function createSnapshot(value) {
202
+ return createTemporarySecretFile(value, "value");
203
+ }
204
+ async function removeSnapshot(snapshot) {
205
+ await snapshot.remove();
206
+ }
207
+ function isIndeterminateError(error) {
208
+ return (error instanceof Error &&
209
+ error.indeterminate === true);
210
+ }
@@ -0,0 +1,17 @@
1
+ export interface SecretInput {
2
+ value: string;
3
+ byteLength: number;
4
+ storage: "note" | "attachment";
5
+ }
6
+ export interface SecretInputOptions {
7
+ fromFile?: string;
8
+ fromStdin?: boolean;
9
+ allowEmpty?: boolean;
10
+ readFile?: (path: string) => Promise<Buffer>;
11
+ stdin?: SecretInputStream;
12
+ }
13
+ export type SecretInputStream = (Iterable<Buffer | string> | AsyncIterable<Buffer | string>) & {
14
+ isTTY?: boolean;
15
+ };
16
+ export declare function readSecretInput(options: SecretInputOptions): Promise<SecretInput>;
17
+ //# sourceMappingURL=secret-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-input.d.ts","sourceRoot":"","sources":["../../src/secrets/secret-input.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,YAAY,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC5B,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC,GACzB,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,CACjC,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAExB,wBAAsB,eAAe,CACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,WAAW,CAAC,CAkCtB"}
@@ -0,0 +1,40 @@
1
+ import { readFile as defaultReadFile } from "node:fs/promises";
2
+ export async function readSecretInput(options) {
3
+ const sourceCount = Number(options.fromFile !== undefined) + Number(options.fromStdin === true);
4
+ if (sourceCount !== 1) {
5
+ throw new Error("Exactly one of --from-file or --from-stdin is required");
6
+ }
7
+ const fromFile = options.fromFile !== undefined;
8
+ const stdin = options.stdin ?? process.stdin;
9
+ if (!fromFile && stdin.isTTY === true) {
10
+ throw new Error("--from-stdin requires piped input; use --from-file for a file");
11
+ }
12
+ const bytes = fromFile
13
+ ? await (options.readFile ?? defaultReadFile)(options.fromFile)
14
+ : await readStdin(stdin);
15
+ let value;
16
+ try {
17
+ value = new TextDecoder("utf-8", {
18
+ fatal: true,
19
+ ignoreBOM: true,
20
+ }).decode(bytes);
21
+ }
22
+ catch {
23
+ throw new Error("Secret input must be valid UTF-8");
24
+ }
25
+ if (bytes.length === 0 && options.allowEmpty !== true) {
26
+ throw new Error("Empty secret input requires --allow-empty");
27
+ }
28
+ return {
29
+ value,
30
+ byteLength: bytes.length,
31
+ storage: fromFile ? "attachment" : "note",
32
+ };
33
+ }
34
+ async function readStdin(input) {
35
+ const chunks = [];
36
+ for await (const chunk of input) {
37
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf8"));
38
+ }
39
+ return Buffer.concat(chunks);
40
+ }
@@ -0,0 +1,9 @@
1
+ export interface SecretStreamEntry {
2
+ name: string;
3
+ value: string;
4
+ }
5
+ export declare const SECRET_STREAM_ENVIRONMENT_VARIABLE = "RHDH_E2E_SECRET_FD";
6
+ export declare const SECRET_STREAM_FD = 3;
7
+ export declare function writeSecretStream(stream: NodeJS.WritableStream, entries: readonly SecretStreamEntry[]): Promise<void>;
8
+ export declare function decodeSecretStream(input: Uint8Array): SecretStreamEntry[];
9
+ //# sourceMappingURL=stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/secrets/stream.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,kCAAkC,uBAAuB,CAAC;AACvE,eAAO,MAAM,gBAAgB,IAAI,CAAC;AA4BlC,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,CAAC,cAAc,EAC7B,OAAO,EAAE,SAAS,iBAAiB,EAAE,GACpC,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,iBAAiB,EAAE,CAuDzE"}