@sagentix-inc/evidence-collector 0.3.1 → 0.3.3

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.
@@ -21,5 +21,209 @@
21
21
  "url": "https://github.com/prowler-cloud/prowler"
22
22
  },
23
23
  "provider": "okta",
24
- "checks": []
24
+ "checks": [
25
+ {
26
+ "id": "prowler.okta.apitoken_not_super_admin",
27
+ "title": "Okta API tokens are not owned by Super Admin users",
28
+ "scope": "Prowler 5.47.0 check apitoken_not_super_admin (Okta apitoken, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
29
+ "evidenceType": "Configuration",
30
+ "severity": "medium"
31
+ },
32
+ {
33
+ "id": "prowler.okta.apitoken_restricted_to_network_zone",
34
+ "title": "Okta API tokens are restricted to known Network Zones",
35
+ "scope": "Prowler 5.47.0 check apitoken_restricted_to_network_zone (Okta apitoken, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
36
+ "evidenceType": "Configuration",
37
+ "severity": "medium"
38
+ },
39
+ {
40
+ "id": "prowler.okta.application_admin_console_mfa_required",
41
+ "title": "Okta Admin Console authentication policy enforces multifactor authentication",
42
+ "scope": "Prowler 5.47.0 check application_admin_console_mfa_required (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
43
+ "evidenceType": "Configuration",
44
+ "severity": "high"
45
+ },
46
+ {
47
+ "id": "prowler.okta.application_admin_console_phishing_resistant_authentication",
48
+ "title": "Okta Admin Console authentication policy enforces phishing-resistant factors",
49
+ "scope": "Prowler 5.47.0 check application_admin_console_phishing_resistant_authentication (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
50
+ "evidenceType": "Configuration",
51
+ "severity": "medium"
52
+ },
53
+ {
54
+ "id": "prowler.okta.application_admin_console_session_idle_timeout_15min",
55
+ "title": "Okta Admin Console app session idle timeout is 15 minutes or less",
56
+ "scope": "Prowler 5.47.0 check application_admin_console_session_idle_timeout_15min (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
57
+ "evidenceType": "Configuration",
58
+ "severity": "medium"
59
+ },
60
+ {
61
+ "id": "prowler.okta.application_authentication_policy_network_zone_enforced",
62
+ "title": "Okta application authentication policies enforce Network Zones",
63
+ "scope": "Prowler 5.47.0 check application_authentication_policy_network_zone_enforced (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
64
+ "evidenceType": "Configuration",
65
+ "severity": "medium"
66
+ },
67
+ {
68
+ "id": "prowler.okta.application_dashboard_mfa_required",
69
+ "title": "Okta Dashboard authentication policy enforces multifactor authentication",
70
+ "scope": "Prowler 5.47.0 check application_dashboard_mfa_required (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
71
+ "evidenceType": "Configuration",
72
+ "severity": "high"
73
+ },
74
+ {
75
+ "id": "prowler.okta.application_dashboard_phishing_resistant_authentication",
76
+ "title": "Okta Dashboard authentication policy enforces phishing-resistant factors",
77
+ "scope": "Prowler 5.47.0 check application_dashboard_phishing_resistant_authentication (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
78
+ "evidenceType": "Configuration",
79
+ "severity": "medium"
80
+ },
81
+ {
82
+ "id": "prowler.okta.authenticator_okta_verify_fips_compliant",
83
+ "title": "Okta Verify authenticator is active and restricts enrollment to FIPS-compliant devices",
84
+ "scope": "Prowler 5.47.0 check authenticator_okta_verify_fips_compliant (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
85
+ "evidenceType": "Configuration",
86
+ "severity": "medium"
87
+ },
88
+ {
89
+ "id": "prowler.okta.authenticator_password_common_password_check",
90
+ "title": "Every active Okta Password Policy rejects common passwords",
91
+ "scope": "Prowler 5.47.0 check authenticator_password_common_password_check (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
92
+ "evidenceType": "Configuration",
93
+ "severity": "medium"
94
+ },
95
+ {
96
+ "id": "prowler.okta.authenticator_password_complexity_lowercase",
97
+ "title": "Every active Okta Password Policy requires at least one lowercase character",
98
+ "scope": "Prowler 5.47.0 check authenticator_password_complexity_lowercase (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
99
+ "evidenceType": "Configuration",
100
+ "severity": "low"
101
+ },
102
+ {
103
+ "id": "prowler.okta.authenticator_password_complexity_number",
104
+ "title": "Every active Okta Password Policy requires at least one numeric character",
105
+ "scope": "Prowler 5.47.0 check authenticator_password_complexity_number (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
106
+ "evidenceType": "Configuration",
107
+ "severity": "low"
108
+ },
109
+ {
110
+ "id": "prowler.okta.authenticator_password_complexity_symbol",
111
+ "title": "Every active Okta Password Policy requires at least one symbol character",
112
+ "scope": "Prowler 5.47.0 check authenticator_password_complexity_symbol (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
113
+ "evidenceType": "Configuration",
114
+ "severity": "low"
115
+ },
116
+ {
117
+ "id": "prowler.okta.authenticator_password_complexity_uppercase",
118
+ "title": "Every active Okta Password Policy requires at least one uppercase character",
119
+ "scope": "Prowler 5.47.0 check authenticator_password_complexity_uppercase (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
120
+ "evidenceType": "Configuration",
121
+ "severity": "low"
122
+ },
123
+ {
124
+ "id": "prowler.okta.authenticator_password_history_5",
125
+ "title": "Every active Okta Password Policy remembers at least 5 previous passwords",
126
+ "scope": "Prowler 5.47.0 check authenticator_password_history_5 (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
127
+ "evidenceType": "Configuration",
128
+ "severity": "low"
129
+ },
130
+ {
131
+ "id": "prowler.okta.authenticator_password_lockout_threshold_3",
132
+ "title": "Every active Okta Password Policy locks accounts after 3 or fewer failed attempts",
133
+ "scope": "Prowler 5.47.0 check authenticator_password_lockout_threshold_3 (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
134
+ "evidenceType": "Configuration",
135
+ "severity": "medium"
136
+ },
137
+ {
138
+ "id": "prowler.okta.authenticator_password_maximum_age_60d",
139
+ "title": "Every active Okta Password Policy enforces a maximum password age of 60 days or less",
140
+ "scope": "Prowler 5.47.0 check authenticator_password_maximum_age_60d (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
141
+ "evidenceType": "Configuration",
142
+ "severity": "low"
143
+ },
144
+ {
145
+ "id": "prowler.okta.authenticator_password_minimum_age_24h",
146
+ "title": "Every active Okta Password Policy enforces a minimum password age of 24 hours",
147
+ "scope": "Prowler 5.47.0 check authenticator_password_minimum_age_24h (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
148
+ "evidenceType": "Configuration",
149
+ "severity": "low"
150
+ },
151
+ {
152
+ "id": "prowler.okta.authenticator_password_minimum_length_15",
153
+ "title": "Every active Okta Password Policy enforces a minimum length of 15 characters",
154
+ "scope": "Prowler 5.47.0 check authenticator_password_minimum_length_15 (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
155
+ "evidenceType": "Configuration",
156
+ "severity": "medium"
157
+ },
158
+ {
159
+ "id": "prowler.okta.authenticator_smart_card_active",
160
+ "title": "Okta Smart Card IdP authenticator is configured and active",
161
+ "scope": "Prowler 5.47.0 check authenticator_smart_card_active (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
162
+ "evidenceType": "Configuration",
163
+ "severity": "medium"
164
+ },
165
+ {
166
+ "id": "prowler.okta.idp_smart_card_dod_approved_ca",
167
+ "title": "Okta Smart Card (X509) Identity Provider uses a DOD-approved certificate authority",
168
+ "scope": "Prowler 5.47.0 check idp_smart_card_dod_approved_ca (Okta idp, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
169
+ "evidenceType": "Configuration",
170
+ "severity": "medium"
171
+ },
172
+ {
173
+ "id": "prowler.okta.network_zone_block_anonymized_proxies",
174
+ "title": "Okta uses active Network Zone blocklists for anonymized proxy sources",
175
+ "scope": "Prowler 5.47.0 check network_zone_block_anonymized_proxies (Okta network, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
176
+ "evidenceType": "Configuration",
177
+ "severity": "medium"
178
+ },
179
+ {
180
+ "id": "prowler.okta.signon_dod_warning_banner_configured",
181
+ "title": "Okta sign-in page displays the Standard Mandatory DOD Notice and Consent Banner",
182
+ "scope": "Prowler 5.47.0 check signon_dod_warning_banner_configured (Okta signon, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
183
+ "evidenceType": "Configuration",
184
+ "severity": "informational"
185
+ },
186
+ {
187
+ "id": "prowler.okta.signon_global_session_cookies_not_persistent",
188
+ "title": "Default Global Session Policy has a Priority 1 non-default rule disabling persistent global session cookies",
189
+ "scope": "Prowler 5.47.0 check signon_global_session_cookies_not_persistent (Okta signon, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
190
+ "evidenceType": "Configuration",
191
+ "severity": "medium"
192
+ },
193
+ {
194
+ "id": "prowler.okta.signon_global_session_idle_timeout_15min",
195
+ "title": "Default Global Session Policy has a Priority 1 non-default rule enforcing 15-minute idle timeout",
196
+ "scope": "Prowler 5.47.0 check signon_global_session_idle_timeout_15min (Okta signon, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
197
+ "evidenceType": "Configuration",
198
+ "severity": "medium"
199
+ },
200
+ {
201
+ "id": "prowler.okta.signon_global_session_lifetime_18h",
202
+ "title": "Default Global Session Policy has a Priority 1 non-default rule limiting session lifetime to 18 hours",
203
+ "scope": "Prowler 5.47.0 check signon_global_session_lifetime_18h (Okta signon, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
204
+ "evidenceType": "Configuration",
205
+ "severity": "medium"
206
+ },
207
+ {
208
+ "id": "prowler.okta.signon_global_session_policy_network_zone_enforced",
209
+ "title": "Default Global Session Policy applies a Network Zone condition aligned with the Access Control Policy",
210
+ "scope": "Prowler 5.47.0 check signon_global_session_policy_network_zone_enforced (Okta signon, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
211
+ "evidenceType": "Configuration",
212
+ "severity": "medium"
213
+ },
214
+ {
215
+ "id": "prowler.okta.systemlog_streaming_enabled",
216
+ "title": "Okta off-loads audit records to a central log server via Log Streaming",
217
+ "scope": "Prowler 5.47.0 check systemlog_streaming_enabled (Okta systemlog, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
218
+ "evidenceType": "Configuration",
219
+ "severity": "high"
220
+ },
221
+ {
222
+ "id": "prowler.okta.user_inactivity_automation_35d_enabled",
223
+ "title": "Okta automation suspends or deactivates users after 35 days of inactivity",
224
+ "scope": "Prowler 5.47.0 check user_inactivity_automation_35d_enabled (Okta user, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
225
+ "evidenceType": "Configuration",
226
+ "severity": "medium"
227
+ }
228
+ ]
25
229
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@sagentix-inc/evidence-collector",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "type": "module"
5
5
  }
@@ -11,6 +11,8 @@ import { fileURLToPath } from "node:url";
11
11
  import { parseArgs } from "node:util";
12
12
  import { CONNECT_PATH, EXIT_UPDATE, MODE_ENV, checkin, collectorInfo, detectRunner } from "./channel.js";
13
13
  import { loadConfig } from "./config.js";
14
+ import { vaultBridge } from "./deliveries.js";
15
+ import { keyHolderFor } from "./vault.js";
14
16
  import { signBody } from "./keys.js";
15
17
  export const PING_MS = 50_000;
16
18
  export const CHECKIN_MS = 6 * 3600_000;
@@ -111,6 +113,38 @@ export class AgentSession {
111
113
  /** Stops a run in progress (the agent is exiting). */
112
114
  stop() { this.busy?.child.kill(); }
113
115
  }
116
+ export const TEST_TIMEOUT_MS = 5 * 60_000;
117
+ /** A connector test asked from sos-app: this file's own cli.ts, `run --dry-run` of its sources, no check-in, no report. */
118
+ export function testRun(configPath, env, sources) {
119
+ return new Promise((done) => {
120
+ const child = spawn(process.execPath, [...process.execArgv, CLI_PATH, "run", "--config", configPath, "--dry-run", ...sources.flatMap((x) => ["--source", x])], { cwd: dirname(configPath), env: { ...env, SAGENTIX_CONNECTOR_TEST: "1" }, stdio: ["ignore", "pipe", "pipe"] });
121
+ let out = "", err = "", timedOut = false;
122
+ child.stdout.on("data", (d) => { if (out.length < 32 * 1024 * 1024)
123
+ out += d; });
124
+ child.stderr.on("data", (d) => { if (err.length < 256 * 1024)
125
+ err += d; });
126
+ const timer = setTimeout(() => { timedOut = true; child.kill("SIGTERM"); }, TEST_TIMEOUT_MS);
127
+ child.once("error", (e) => { clearTimeout(timer); done({ exit: null, passed: 0, failed: 0, skipped: 0, errors: [String(e.message)] }); });
128
+ child.once("exit", (code) => {
129
+ clearTimeout(timer);
130
+ done({ ...summaryOf(out), exit: code, errors: err.split(/\r?\n/).filter((l) => /^error in source |^collector error|vault/.test(l)), ...(timedOut ? { timedOut } : {}) });
131
+ });
132
+ });
133
+ }
134
+ /** The pass, fail and skipped counts a dry run prints (one push, or several). */
135
+ export function summaryOf(stdout) {
136
+ const z = { passed: 0, failed: 0, skipped: 0 };
137
+ try {
138
+ const j = JSON.parse(stdout);
139
+ for (const sm of j.summary ? [j.summary] : (j.pushes ?? []).map((x) => x.summary)) {
140
+ z.passed += sm?.passed ?? 0;
141
+ z.failed += sm?.failed ?? 0;
142
+ z.skipped += sm?.skipped ?? 0;
143
+ }
144
+ }
145
+ catch { /* nothing printed: the errors say why */ }
146
+ return z;
147
+ }
114
148
  /** What a run started from sos-app executes (exported for the tests). */
115
149
  export function runCommand(configPath, exists, platform = process.platform) {
116
150
  const dir = dirname(resolve(configPath));
@@ -201,9 +235,12 @@ export async function agent(argv, deps, io = realAgentIo()) {
201
235
  // A signed update written by a check-in ends the agent with 75 once no run is going: Docker's wrapper loop restarts
202
236
  // the container on the new digest, a service manager restarts the script, which checks out the ref on start.
203
237
  let restartPending = false;
238
+ const dir = dirname(configPath);
239
+ const vault = vaultBridge({ configPath, keyId, now: deps.now, log, test: (sources) => testRun(configPath, deps.env, sources),
240
+ holder: deps.vaultKey?.(dir, keyId) ?? keyHolderFor(dir, keyId, { env: deps.env, platform: process.platform, runner: detectRunner(deps.env) }) });
204
241
  const doCheckin = async () => {
205
242
  try {
206
- const out = await checkin(channel, { write: true, log });
243
+ const out = await checkin(channel, { write: true, log, vault });
207
244
  if (out.wrote && !restartPending) {
208
245
  restartPending = true;
209
246
  log(`agent: exiting with ${EXIT_UPDATE} to restart on the update${session.running ? ` once run ${session.running} ends` : ""}`);
@@ -2,7 +2,8 @@
2
2
  // collector is (version, commit, runner), the signed POST every message uses, and the check-in that may name a
3
3
  // signed update. Every connection goes out; nothing here listens, and nothing the server answers can make the
4
4
  // collector run a command or change a configuration. The most it can do is have `sagentix-collector.ref` (or
5
- // `.image`) written beside the config, which the generated script reads on its next start.
5
+ // `.image`) written beside the config, which the generated script reads on its next start, and deliver values to
6
+ // the vault for the names the config already lists (src/deliveries.ts: a secret only sealed for this collector).
6
7
  import { spawnSync } from "node:child_process";
7
8
  import { chmodSync, existsSync, writeFileSync } from "node:fs";
8
9
  import { arch, platform, release } from "node:os";
@@ -12,6 +13,7 @@ import { fileURLToPath } from "node:url";
12
13
  import { signBody } from "./keys.js";
13
14
  import { COLLECTOR_VERSION } from "./payload.js";
14
15
  import { RELEASE_PUBLIC_KEYS } from "./release_keys.js";
16
+ import { ack } from "./deliveries.js";
15
17
  export const CHECKIN_PATH = "/api/collect/checkin";
16
18
  export const REPORT_PATH = "/api/collect/report";
17
19
  export const CONNECT_PATH = "/api/collect/connect";
@@ -133,9 +135,17 @@ function targetOf(v) {
133
135
  export async function checkin(c, o) {
134
136
  const warnings = [];
135
137
  const warn = (s) => { warnings.push(s); o.log(`warning: check-in: ${s}`); };
138
+ // The vault's names, kinds, dates and sealing key (section 8): never a value.
139
+ let vault;
140
+ try {
141
+ vault = o.vault?.summary();
142
+ }
143
+ catch (e) {
144
+ warn(`the vault summary failed: ${String(e?.message ?? e)}`);
145
+ }
136
146
  const body = new TextEncoder().encode(JSON.stringify({
137
147
  format: "sagentix-checkin/1", issuedAt: c.now().toISOString(), collector: collectorInfo(c.env),
138
- runner: c.runner, mode: c.mode, os: osInfo(), node: process.version,
148
+ runner: c.runner, mode: c.mode, os: osInfo(), node: process.version, ...(vault ? { vault } : {}),
139
149
  }));
140
150
  let res;
141
151
  try {
@@ -157,6 +167,17 @@ export async function checkin(c, o) {
157
167
  warn("the answer is not JSON");
158
168
  return { action: "failed", warnings };
159
169
  }
170
+ // Values sent to the vault from sos-app (section 8), whatever the update answer. A dry run applies nothing.
171
+ if (o.vault && o.write && Array.isArray(a.deliveries) && a.deliveries.length) {
172
+ try {
173
+ const results = await o.vault.apply(a.deliveries, { agent: c.mode === "agent" });
174
+ if (results.length)
175
+ await ack(c, results, o.vault.summary(), o.log);
176
+ }
177
+ catch (e) {
178
+ warn(`the deliveries failed: ${String(e?.message ?? e)}`);
179
+ }
180
+ }
160
181
  const reason = typeof a.reason === "string" ? a.reason.slice(0, 500) : "";
161
182
  switch (a.action) {
162
183
  case "none": return { action: "none", warnings };
@@ -31,8 +31,12 @@ import { CLASSES } from "./v1/rules.js";
31
31
  import { EXIT_UPDATE, checkin, detectRunner, modeOf } from "./channel.js";
32
32
  import { RunRecorder, buildReport, deliverReport } from "./report.js";
33
33
  import { agent } from "./agent.js";
34
+ import { VAULT_FILE, keyHolderFor, runEnv } from "./vault.js";
35
+ import { credentialsIn, settingsOf, vaultBridge } from "./deliveries.js";
34
36
  export const COLLECT_PATH = "/api/collect";
35
37
  export const KEY_ENV = "SAGENTIX_COLLECTOR_KEY";
38
+ /** Set by the agent on a connector test asked from sos-app (a dry run of one connector). */
39
+ export const TEST_ENV = "SAGENTIX_CONNECTOR_TEST";
36
40
  /** `update` (75) is the agent's only: it wrote a signed update and exits so it is restarted on it. */
37
41
  export const EXIT = { ok: 0, error: 1, usage: 2, nothing: 3, refused: 4, update: EXIT_UPDATE };
38
42
  export function defaultDeps() {
@@ -215,10 +219,39 @@ export async function run(argv, deps) {
215
219
  const channel = pem && keyId
216
220
  ? { origin: new URL(url).origin, keyId, pem, fetch: deps.channel ?? deps.fetch, dir, runner, mode, env: deps.env, now: deps.now }
217
221
  : undefined;
218
- if (channel)
219
- rec.note("checkin", (await checkin(channel, { write: !dry, log: deps.err })).warnings);
222
+ // A connector test asked from sos-app runs that connector dry: no check-in, and no run report, so the last real run's
223
+ // report beside the config stays what sos-app shows.
224
+ const connectorTest = dry && deps.env[TEST_ENV] === "1";
225
+ if (connectorTest) { /* neither */ }
226
+ else if (channel) {
227
+ const vault = configPath && keyId ? vaultBridge({ configPath: resolve(configPath), keyId, now: deps.now, log: deps.err,
228
+ holder: deps.vaultKey?.(dir, keyId) ?? keyHolderFor(dir, keyId, { env: deps.env, platform: process.platform, runner }) }) : undefined;
229
+ rec.note("checkin", (await checkin(channel, { write: !dry, log: deps.err, vault })).warnings);
230
+ }
220
231
  else
221
232
  deps.err("check-in skipped: a dry run without a key cannot sign one.");
233
+ // The connectors' secrets and settings, set in sos-app and sealed for this collector (src/vault.ts), when the run has a config and a key id.
234
+ // A vault that cannot be opened is a warning: the sources that need its values fail on their own, the run goes on.
235
+ let env = deps.env, cleanup = () => { }, vaultSecrets = [];
236
+ if (configPath && keyId) {
237
+ try {
238
+ const creds = credentialsIn(resolve(configPath));
239
+ const r = runEnv(dir, deps.vaultKey?.(dir, keyId) ?? keyHolderFor(dir, keyId, { env: deps.env, platform: process.platform, runner }), deps.env, (name) => settingsOf(creds, name));
240
+ ({ env, cleanup } = r);
241
+ vaultSecrets = r.secrets;
242
+ if (r.used.length)
243
+ deps.err(`vault: ${r.used.length} value(s) from ${VAULT_FILE}`);
244
+ for (const w of r.warnings)
245
+ deps.err(`warning: vault: ${w}`);
246
+ if (r.warnings.length)
247
+ rec.note("vault", r.warnings);
248
+ }
249
+ catch (e) {
250
+ const w = `the vault could not be opened: ${String(e?.message ?? e)}`;
251
+ deps.err(w);
252
+ rec.note("vault", [w]);
253
+ }
254
+ }
222
255
  let code;
223
256
  try {
224
257
  code = await collect();
@@ -227,7 +260,13 @@ export async function run(argv, deps) {
227
260
  deps.err(`collector error: ${String(e?.message ?? e)}`);
228
261
  code = EXIT.error;
229
262
  }
230
- await deliverReport(buildReport(rec, { now: deps.now(), exitCode: code, runner, mode, env: deps.env }), { dry, channel, dir, log: deps.err });
263
+ finally {
264
+ cleanup();
265
+ }
266
+ // The report is scrubbed of every value in the run's environment, the vault's included.
267
+ if (connectorTest)
268
+ return code;
269
+ await deliverReport(buildReport(rec, { now: deps.now(), exitCode: code, runner, mode, env, secrets: vaultSecrets }), { dry, channel, dir, log: deps.err });
231
270
  return code;
232
271
  async function collect() {
233
272
  // Run every chosen source inside a guard, then keep only what the server will accept. A source that throws gives
@@ -236,7 +275,7 @@ export async function run(argv, deps) {
236
275
  const raw = [];
237
276
  const failed = [];
238
277
  for (const s of chosen) {
239
- const { value, error, report } = await rec.source(s.name, () => s.run({ opts: values, env: deps.env, clients: deps.clients, now, warn: deps.err }));
278
+ const { value, error, report } = await rec.source(s.name, () => s.run({ opts: values, env, clients: deps.clients, now, warn: deps.err }));
240
279
  let got;
241
280
  if (error !== undefined || !Array.isArray(value)) {
242
281
  const message = error !== undefined ? String(error?.message ?? error) : "the source returned no list of results";
@@ -28,6 +28,24 @@ export const CONFIG_SCHEMA = {
28
28
  out: { type: "string", minLength: 1, description: "Also write the signed payload to this file." },
29
29
  dryRun: { type: "boolean", description: "Print the payload and send nothing." },
30
30
  catalog: { type: "string", minLength: 1, description: "The check catalogue directory." },
31
+ credentials: {
32
+ type: "object",
33
+ description: "Per connector, the names of what it reads (sos-app writes them; sos-app sends a sealed value only for a name listed here). Names only, never a value.",
34
+ additionalProperties: {
35
+ type: "object",
36
+ additionalProperties: false,
37
+ required: ["sources"],
38
+ properties: {
39
+ sources: { type: "array", items: { type: "string" } },
40
+ secrets: { type: "array", items: { type: "string", pattern: "^[A-Z][A-Z0-9_]{0,99}$" } },
41
+ files: { type: "array", items: { type: "string", pattern: "^[A-Z][A-Z0-9_]{0,99}$" } },
42
+ settings: { type: "array", items: { type: "string", pattern: "^[A-Z][A-Z0-9_]{0,99}$" } },
43
+ optional: { type: "object", additionalProperties: { type: "string" } },
44
+ instead: { type: "object", additionalProperties: { type: "string" } },
45
+ signIn: { type: "boolean" },
46
+ },
47
+ },
48
+ },
31
49
  sources: {
32
50
  type: "object",
33
51
  minProperties: 1,