@veltrixsecops/app-sdk 3.2.0 → 3.5.1
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 +68 -1
- package/dist/byol/index.cjs +3802 -0
- package/dist/byol/index.cjs.map +1 -0
- package/dist/byol/index.d.cts +578 -0
- package/dist/byol/index.d.ts +578 -0
- package/dist/byol/index.js +2587 -0
- package/dist/byol/index.js.map +1 -0
- package/dist/chunk-3WK4EVQ3.js +842 -0
- package/dist/chunk-3WK4EVQ3.js.map +1 -0
- package/dist/{chunk-EOOEHZGC.js → chunk-HIL6RIDG.js} +107 -3
- package/dist/chunk-HIL6RIDG.js.map +1 -0
- package/dist/{chunk-VWFTOFTI.js → chunk-Y6XBNZGR.js} +8 -2
- package/dist/chunk-Y6XBNZGR.js.map +1 -0
- package/dist/client/index.cjs +112 -2
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +118 -6
- package/dist/client/index.d.ts +118 -6
- package/dist/client/index.js +13 -1
- package/dist/connections/index.cjs +1787 -0
- package/dist/connections/index.cjs.map +1 -0
- package/dist/connections/index.d.cts +78 -0
- package/dist/connections/index.d.ts +78 -0
- package/dist/connections/index.js +754 -0
- package/dist/connections/index.js.map +1 -0
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +2 -2
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/opentofu/index.cjs +138 -0
- package/dist/opentofu/index.cjs.map +1 -0
- package/dist/opentofu/index.d.cts +131 -0
- package/dist/opentofu/index.d.ts +131 -0
- package/dist/opentofu/index.js +109 -0
- package/dist/opentofu/index.js.map +1 -0
- package/dist/pipeline/index.cjs +7 -0
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.d.cts +9 -2
- package/dist/pipeline/index.d.ts +9 -2
- package/dist/pipeline/index.js +3 -1
- package/dist/pipeline-beeT8dje.d.cts +408 -0
- package/dist/pipeline-beeT8dje.d.ts +408 -0
- package/dist/ui/index.cjs +136 -0
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +109 -1
- package/dist/ui/index.d.ts +109 -1
- package/dist/ui/index.js +35 -677
- package/dist/ui/index.js.map +1 -1
- package/dist/{use-app-context-OQlcmq7t.d.cts → use-app-context-Byv_fam0.d.cts} +206 -4
- package/dist/{use-app-context-OQlcmq7t.d.ts → use-app-context-Byv_fam0.d.ts} +206 -4
- package/opentofu/README.md +114 -0
- package/opentofu/modules/aws/main.tf +1008 -0
- package/opentofu/modules/aws/outputs.tf +90 -0
- package/opentofu/modules/aws/variables.tf +437 -0
- package/opentofu/modules/aws/versions.tf +23 -0
- package/opentofu/modules/azure/main.tf +920 -0
- package/opentofu/modules/azure/outputs.tf +72 -0
- package/opentofu/modules/azure/variables.tf +498 -0
- package/opentofu/modules/azure/versions.tf +27 -0
- package/opentofu/modules/gcp/main.tf +645 -0
- package/opentofu/modules/gcp/outputs.tf +73 -0
- package/opentofu/modules/gcp/variables.tf +428 -0
- package/opentofu/modules/gcp/versions.tf +27 -0
- package/opentofu/modules/hetzner/main.tf +432 -0
- package/opentofu/modules/hetzner/outputs.tf +72 -0
- package/opentofu/modules/hetzner/variables.tf +423 -0
- package/opentofu/modules/hetzner/versions.tf +26 -0
- package/package.json +33 -1
- package/dist/chunk-EOOEHZGC.js.map +0 -1
- package/dist/chunk-VWFTOFTI.js.map +0 -1
- package/dist/pipeline-A-gSsPRR.d.cts +0 -189
- package/dist/pipeline-A-gSsPRR.d.ts +0 -189
|
@@ -24,6 +24,20 @@ interface AppManifest {
|
|
|
24
24
|
database?: {
|
|
25
25
|
migrations: string;
|
|
26
26
|
tablePrefix: string;
|
|
27
|
+
/**
|
|
28
|
+
* How the app's tables are namespaced in Postgres.
|
|
29
|
+
* - 'shared' (default): prefixed tables in the shared `public` schema.
|
|
30
|
+
* Reserved for trusted first-party apps whose SQL the platform ships.
|
|
31
|
+
* - 'schema': the app gets its own Postgres schema (+ least-privilege
|
|
32
|
+
* role); its migrations run with search_path pinned to it.
|
|
33
|
+
* - 'database': the app gets its own Postgres database — hard,
|
|
34
|
+
* cross-database-query-proof isolation.
|
|
35
|
+
* - 'external': the app owns its datastore entirely; the platform manages
|
|
36
|
+
* no schema for it (connection supplied at runtime via app settings).
|
|
37
|
+
* The platform forces at least 'schema' for marketplace and customer-
|
|
38
|
+
* authored (self-managed) apps, which may opt up to 'database'/'external'.
|
|
39
|
+
*/
|
|
40
|
+
isolation?: 'shared' | 'schema' | 'database' | 'external';
|
|
27
41
|
};
|
|
28
42
|
pipeline: {
|
|
29
43
|
configurationTypes: AppConfigurationTypeManifest[];
|
|
@@ -61,10 +75,101 @@ interface AppManifest {
|
|
|
61
75
|
onEnable?: string;
|
|
62
76
|
onDisable?: string;
|
|
63
77
|
onUpgrade?: string;
|
|
78
|
+
/** Handler the platform invokes for inbound webhooks routed to this app. */
|
|
79
|
+
onWebhook?: string;
|
|
80
|
+
/** Handler the platform invokes for inbound message-bus events routed to this app. */
|
|
81
|
+
onEvent?: string;
|
|
64
82
|
};
|
|
65
83
|
events?: string[];
|
|
84
|
+
/**
|
|
85
|
+
* Connection-level connectivity testing. `testHandler` is an extensionless path
|
|
86
|
+
* to a module whose default export is a `testConnection` handler — the platform
|
|
87
|
+
* runs it in-process (with the decrypted credential) to verify a Connection's
|
|
88
|
+
* endpoint + credentials. Optional: apps without it report "test not supported".
|
|
89
|
+
*/
|
|
90
|
+
connectivity?: {
|
|
91
|
+
testHandler?: string;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Connection lifecycle declarations. `onboarding` opts the app into the
|
|
95
|
+
* platform's one-click connection onboarding: the app declares what it needs
|
|
96
|
+
* (a named onboarding adapter + parameters) and the platform drives it.
|
|
97
|
+
* Microsoft Entra admin-consent (`provider: 'entra-admin-consent'`) is the
|
|
98
|
+
* first adapter; nothing here is provider-specific to the platform core.
|
|
99
|
+
*/
|
|
100
|
+
connection?: AppConnectionDeclaration;
|
|
66
101
|
settings?: AppSettingDeclaration[];
|
|
67
102
|
}
|
|
103
|
+
/** App-declared connection lifecycle capabilities. */
|
|
104
|
+
interface AppConnectionDeclaration {
|
|
105
|
+
onboarding?: ConnectionOnboardingDescriptor;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Declarative "one-click connect" descriptor. The platform reads it to render a
|
|
109
|
+
* "Connect …" button and to drive a named onboarding adapter; the app supplies
|
|
110
|
+
* only data, never platform code.
|
|
111
|
+
*/
|
|
112
|
+
interface ConnectionOnboardingDescriptor {
|
|
113
|
+
/** Names a platform onboarding adapter (e.g. `entra-admin-consent`). */
|
|
114
|
+
provider: string;
|
|
115
|
+
/** Button label in the Connections UI (e.g. "Connect Microsoft Defender"). */
|
|
116
|
+
label: string;
|
|
117
|
+
params?: ConnectionOnboardingParams;
|
|
118
|
+
/**
|
|
119
|
+
* Optional app-provided finalize hook (extensionless path). Run in-process
|
|
120
|
+
* after a successful onboarding, exactly like `connectivity.testHandler`.
|
|
121
|
+
*/
|
|
122
|
+
onboardingHandler?: string;
|
|
123
|
+
}
|
|
124
|
+
interface ConnectionOnboardingParams {
|
|
125
|
+
/** App-setting key whose value selects the sovereign cloud (e.g. `azure_cloud`). */
|
|
126
|
+
cloudSetting?: string;
|
|
127
|
+
/**
|
|
128
|
+
* App permissions this connection needs — for display + audience selection.
|
|
129
|
+
* The effective grant is fixed on the connector app registration, not here.
|
|
130
|
+
*/
|
|
131
|
+
requiredResourceAccess?: OnboardingRequiredResource[];
|
|
132
|
+
/** What the flow captures and where it maps back onto the connection. */
|
|
133
|
+
capture?: OnboardingCapture;
|
|
134
|
+
/** True → the connection uses the platform token broker and stores NO secret. */
|
|
135
|
+
brokered?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* App settings the admin must supply BEFORE the consent click (they cannot be
|
|
138
|
+
* derived from consent), e.g. Sentinel's subscription/resource-group/workspace.
|
|
139
|
+
*/
|
|
140
|
+
requiredSettings?: string[];
|
|
141
|
+
/** Post-consent provisioning steps the adapter runs (e.g. Sentinel ARM RBAC). */
|
|
142
|
+
provisioning?: OnboardingProvisioningStep[];
|
|
143
|
+
}
|
|
144
|
+
interface OnboardingRequiredResource {
|
|
145
|
+
/** Well-known resource name or appId (e.g. `WindowsDefenderATP`, `Graph`). */
|
|
146
|
+
resource: string;
|
|
147
|
+
/** Application permissions requested on that resource (display only). */
|
|
148
|
+
appPermissions: string[];
|
|
149
|
+
}
|
|
150
|
+
interface OnboardingCapture {
|
|
151
|
+
/**
|
|
152
|
+
* Where to write the consented tenant id. `setting:<key>` writes it into the
|
|
153
|
+
* named app setting (the app libs read it as their `tenant_id`).
|
|
154
|
+
*/
|
|
155
|
+
tenantId?: string;
|
|
156
|
+
}
|
|
157
|
+
/** A post-consent provisioning step. Only ARM role assignment exists today. */
|
|
158
|
+
interface OnboardingProvisioningStep {
|
|
159
|
+
type: 'arm-role-assignment';
|
|
160
|
+
/** Well-known built-in role name (resolved to a role-definition id by the adapter). */
|
|
161
|
+
role: string;
|
|
162
|
+
/** ARM scope granularity for the assignment. */
|
|
163
|
+
scope: 'resourceGroup' | 'subscription';
|
|
164
|
+
/**
|
|
165
|
+
* How the ARM token for the assignment is obtained:
|
|
166
|
+
* - `manual` (default): show a portal deep-link/CLI + a verify probe. No
|
|
167
|
+
* extra platform privilege — consent does not grant ARM RBAC.
|
|
168
|
+
* - `delegated`: opt-in second delegated-ARM leg (requires the admin to hold
|
|
169
|
+
* Owner / User Access Administrator). Not implemented in the first cut.
|
|
170
|
+
*/
|
|
171
|
+
armToken?: 'manual' | 'delegated';
|
|
172
|
+
}
|
|
68
173
|
/**
|
|
69
174
|
* App brand identity. The platform renders it in a per-app navbar above the
|
|
70
175
|
* app's pages and exposes the colors to app pages as scoped CSS variables
|
|
@@ -76,9 +181,10 @@ interface AppBrandingDeclaration {
|
|
|
76
181
|
/** Optional secondary color as #RGB or #RRGGBB hex. */
|
|
77
182
|
accentColor?: string;
|
|
78
183
|
/**
|
|
79
|
-
* Vendor logo shown in the app navbar
|
|
80
|
-
* .png
|
|
81
|
-
*
|
|
184
|
+
* Vendor logo shown in the app navbar and on the marketplace card. Either a
|
|
185
|
+
* repo-relative .svg (preferred) or .png at most 128 KB, OR an absolute
|
|
186
|
+
* https:// URL to an externally hosted asset. Rendered at ~28px height, so
|
|
187
|
+
* use a wide/landscape mark with a transparent background.
|
|
82
188
|
*/
|
|
83
189
|
logo?: string;
|
|
84
190
|
/** Optional logo variant for dark backgrounds; same constraints as logo. */
|
|
@@ -97,6 +203,12 @@ interface AppConfigurationTypeManifest {
|
|
|
97
203
|
healthCheck: string;
|
|
98
204
|
driftDetect?: string | null;
|
|
99
205
|
getStatus: string;
|
|
206
|
+
/**
|
|
207
|
+
* Optional options provider — powers live `remote-multiselect` config fields
|
|
208
|
+
* (returns {value,label} options fetched from the target system). Loaded like
|
|
209
|
+
* driftDetect (optional); omit when the config type has no live-option fields.
|
|
210
|
+
*/
|
|
211
|
+
options?: string | null;
|
|
100
212
|
};
|
|
101
213
|
targets: {
|
|
102
214
|
componentTypes: string[];
|
|
@@ -232,6 +344,12 @@ interface InventoryItem {
|
|
|
232
344
|
hostname: string;
|
|
233
345
|
/** Management/API port, as a string (e.g. "8089"). */
|
|
234
346
|
port?: string;
|
|
347
|
+
/** Optional secondary service port, e.g. a web UI (Splunk Web "8000"). */
|
|
348
|
+
webPort?: string | null;
|
|
349
|
+
/** Optional OS login user for SSH to this target (e.g. "root", "ubuntu"). */
|
|
350
|
+
sshUser?: string | null;
|
|
351
|
+
/** Optional $SPLUNK_HOME on the target (e.g. "/opt/splunk", "/opt/splunkforwarder"). */
|
|
352
|
+
splunkHome?: string | null;
|
|
235
353
|
/** Component type tags (e.g. ["server"]) that classify the target. */
|
|
236
354
|
type?: string[];
|
|
237
355
|
/** DNS names this target is reachable at. */
|
|
@@ -258,6 +376,12 @@ interface InventoryItem {
|
|
|
258
376
|
interface InventoryItemInput {
|
|
259
377
|
hostname: string;
|
|
260
378
|
port?: string;
|
|
379
|
+
/** Optional secondary service port, e.g. a web UI (Splunk Web "8000"). */
|
|
380
|
+
webPort?: string | null;
|
|
381
|
+
/** Optional OS login user for SSH to this target (e.g. "root", "ubuntu"). */
|
|
382
|
+
sshUser?: string | null;
|
|
383
|
+
/** Optional $SPLUNK_HOME on the target. */
|
|
384
|
+
splunkHome?: string | null;
|
|
261
385
|
type?: string[];
|
|
262
386
|
domains?: string[];
|
|
263
387
|
ipRanges?: string[];
|
|
@@ -309,6 +433,8 @@ interface CredentialSummary {
|
|
|
309
433
|
type: string | null;
|
|
310
434
|
/** API base URL / endpoint this connection authenticates to, or null. */
|
|
311
435
|
endpoint: string | null;
|
|
436
|
+
/** Environment type these creds apply to (from the env's types), or null = any. */
|
|
437
|
+
environmentType: string | null;
|
|
312
438
|
/** The tool this credential belongs to. */
|
|
313
439
|
toolId: string;
|
|
314
440
|
/** True when a write-only secret (apiToken or password) is stored. */
|
|
@@ -334,6 +460,8 @@ interface CredentialInput {
|
|
|
334
460
|
type?: string;
|
|
335
461
|
/** API base URL / endpoint this connection authenticates to. Not a secret. */
|
|
336
462
|
endpoint?: string;
|
|
463
|
+
/** Environment type these creds apply to (from the env's environmentTypes); null = any. */
|
|
464
|
+
environmentType?: string | null;
|
|
337
465
|
/** The tool this credential belongs to. Required by the platform on create. */
|
|
338
466
|
toolId?: string;
|
|
339
467
|
/** IDs of existing platform tags to attach to the credential. */
|
|
@@ -416,6 +544,67 @@ interface AppHookContext {
|
|
|
416
544
|
appId: string;
|
|
417
545
|
customerId?: string;
|
|
418
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* Context passed to an app's `onWebhook` hook when the platform receives an
|
|
549
|
+
* inbound webhook. The platform stays ignorant of the app's semantics — the
|
|
550
|
+
* app inspects `source`/`event` and decides whether and how to act (e.g. update
|
|
551
|
+
* its own tables via `db`).
|
|
552
|
+
*/
|
|
553
|
+
interface AppWebhookContext {
|
|
554
|
+
db: PlatformDatabaseClient;
|
|
555
|
+
appId: string;
|
|
556
|
+
/** Origin of the webhook, e.g. 'github'. */
|
|
557
|
+
source: string;
|
|
558
|
+
/** Event name within the source, e.g. 'deployment'. */
|
|
559
|
+
event: string;
|
|
560
|
+
/** Normalized webhook payload. */
|
|
561
|
+
payload: any;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Context passed to an app's `onEvent` hook when the platform routes a message-
|
|
565
|
+
* bus event to it. Symmetric with `events.publish` (outbound): the app inspects
|
|
566
|
+
* `topic` and acts on its own data.
|
|
567
|
+
*/
|
|
568
|
+
interface AppEventContext {
|
|
569
|
+
db: PlatformDatabaseClient;
|
|
570
|
+
appId: string;
|
|
571
|
+
/** Event topic, e.g. 'deployment.status'. */
|
|
572
|
+
topic: string;
|
|
573
|
+
/** Event payload. */
|
|
574
|
+
payload: any;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Publish an event/message from an app to the platform's message bus. The
|
|
578
|
+
* platform routes it on a topic exchange keyed by `<appId>.<topic>`; consumers
|
|
579
|
+
* (provisioning workers, other apps) subscribe by routing key. The platform is
|
|
580
|
+
* ignorant of the payload's meaning — the app owns the message shape.
|
|
581
|
+
*/
|
|
582
|
+
interface AppEventPublisher {
|
|
583
|
+
publish(topic: string, payload: unknown): Promise<void>;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* A tenant Connection (a stored {@link Credential}) resolved to its DECRYPTED
|
|
587
|
+
* secret + endpoint, returned by {@link AppRouteContext.resolveConnection}.
|
|
588
|
+
*
|
|
589
|
+
* SERVER-SIDE ONLY. The secret fields (`password` / `apiToken` / `certificate`)
|
|
590
|
+
* are in the clear — use them to reach the connected system from a route, and
|
|
591
|
+
* NEVER return them to the client or log them.
|
|
592
|
+
*/
|
|
593
|
+
interface ResolvedConnection {
|
|
594
|
+
id: string;
|
|
595
|
+
/** Human-readable connection label. */
|
|
596
|
+
name: string;
|
|
597
|
+
/** API base URL / management endpoint (non-secret), or null. */
|
|
598
|
+
endpoint: string | null;
|
|
599
|
+
/** Account / client id the credential authenticates as. */
|
|
600
|
+
username: string;
|
|
601
|
+
/** Decrypted password (empty string for token-only connections). */
|
|
602
|
+
password: string;
|
|
603
|
+
/** Decrypted API token, or null. */
|
|
604
|
+
apiToken: string | null;
|
|
605
|
+
/** Decrypted client certificate, or null. */
|
|
606
|
+
certificate: string | null;
|
|
607
|
+
}
|
|
419
608
|
/**
|
|
420
609
|
* Context passed to an app's server route module (the `server.entry`
|
|
421
610
|
* declared in manifest.yaml). The platform mounts the module as a Fastify
|
|
@@ -427,12 +616,25 @@ interface AppRouteContext {
|
|
|
427
616
|
appDir: string;
|
|
428
617
|
manifest: AppManifest;
|
|
429
618
|
db: PlatformDatabaseClient;
|
|
619
|
+
/** Publish events/messages from this app to the platform message bus. */
|
|
620
|
+
events: AppEventPublisher;
|
|
430
621
|
/**
|
|
431
622
|
* Returns a Fastify preHandler enforcing an app-scoped permission.
|
|
432
623
|
* Typed `any` so the SDK does not depend on Fastify's hook types —
|
|
433
624
|
* pass it straight into a route's `preHandler` array.
|
|
434
625
|
*/
|
|
435
626
|
hasPermission: (resource: string, action: string) => any;
|
|
627
|
+
/**
|
|
628
|
+
* Resolve one of the tenant's Connections (a stored Credential, by id) to its
|
|
629
|
+
* DECRYPTED secret + endpoint, so a route can reach the connected system
|
|
630
|
+
* WITHOUT re-implementing the platform's credential decryption or querying
|
|
631
|
+
* platform tables directly. Scoped to `customerId` (the tenant boundary);
|
|
632
|
+
* returns null when no matching connection exists. The decrypted secret stays
|
|
633
|
+
* server-side — never send it to the client. Pass a `credentialId` the app
|
|
634
|
+
* obtained from its own connections list (e.g. the SDK `listCredentials`
|
|
635
|
+
* helper), mirroring the platform's own "test connection" resolution.
|
|
636
|
+
*/
|
|
637
|
+
resolveConnection: (customerId: string, credentialId: string) => Promise<ResolvedConnection | null>;
|
|
436
638
|
}
|
|
437
639
|
|
|
438
640
|
interface AppContextValue {
|
|
@@ -470,4 +672,4 @@ declare const AppContext: Context<AppContextValue | null>;
|
|
|
470
672
|
*/
|
|
471
673
|
declare function useAppContext(): AppContextValue;
|
|
472
674
|
|
|
473
|
-
export { APP_PAGE_LAYOUTS as A, type
|
|
675
|
+
export { APP_PAGE_LAYOUTS as A, type CredentialInput as B, type Component as C, type CredentialSummary as D, type Customer as E, type InventoryItemInput as F, type PermissionEntry as G, type PlatformDatabaseClient as H, type InventoryItem as I, AppContext as J, useAppContext as K, type PermissionCheckOptions as P, type Tag as T, type User as U, APP_PAGE_NAV as a, type AppBrandingDeclaration as b, type AppConfigurationTypeManifest as c, type AppContextValue as d, type AppDetail as e, type AppEventContext as f, type AppEventPublisher as g, type AppHookContext as h, type AppInstallationDetail as i, type AppInstallationStatus as j, type AppListItem as k, type AppManifest as l, type AppPageDeclaration as m, type AppPageLayout as n, type AppPageNav as o, type AppPagePermission as p, type AppPermissionDeclaration as q, type AppPermissionsApi as r, type AppRouteContext as s, type AppSettingDeclaration as t, type AppSource as u, type AppStatusType as v, type AppWebhookContext as w, type Connectivity as x, type ConnectivityProviderRef as y, type Credential as z };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @veltrixsecops/app-sdk — OpenTofu (generic BYOI provisioning)
|
|
2
|
+
|
|
3
|
+
Tool-agnostic OpenTofu modules that every BYOI app composes to provision a fully
|
|
4
|
+
Veltrix-hosted environment. **There is no per-tool HCL** — an app declares its
|
|
5
|
+
shape as data (an `InfraSpec`) and the SDK renders it into these modules' tfvars.
|
|
6
|
+
Any tool — a log/analytics platform, a sensor fleet, a clustered database, and so
|
|
7
|
+
on — reuses the same modules.
|
|
8
|
+
|
|
9
|
+
## The three layers
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
sdk/opentofu/modules/<cloud>/ ← generic HCL (this dir). Nothing tool-specific.
|
|
13
|
+
sdk/src/opentofu/ ← InfraSpec types + renderInfraVars() + validateInfraSpec()
|
|
14
|
+
apps/<app>/infra/spec.ts ← the app's InfraSpec (its ports/LB/DNS/roles as DATA)
|
|
15
|
+
apps/<app>/infra/bringup/ ← the app's config management + readiness gate
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The platform's render+apply **worker** (in the Veltrix platform repo) builds the
|
|
19
|
+
base tfvars from the deploy request, merges `renderInfraVars(spec)` on top,
|
|
20
|
+
`tofu apply`s the pinned module, then runs the app's `spec.bringup` entrypoint.
|
|
21
|
+
|
|
22
|
+
## What the module does (and what the app supplies)
|
|
23
|
+
|
|
24
|
+
| Concern | Module (generic) | App (via InfraSpec) |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Compute nodes | one instance per non-foundation plan item | roles are just plan `kind`s |
|
|
27
|
+
| Security groups | SG-to-SG, one rule per `(port, source)` | `securityRules[]` |
|
|
28
|
+
| Front door | ALB + WAF + optional Cognito MFA + target group | `loadBalancer` (port, health path, target kinds) |
|
|
29
|
+
| DNS | public ALB record + private zone + per-node FQDNs | `dnsPrefixes` (kind → label) |
|
|
30
|
+
| Object storage | private S3 bucket | `storage[]` |
|
|
31
|
+
| TLS | ACM public wildcard (ALB); Private-CA per node (fabric) | — |
|
|
32
|
+
| Tags | every resource merges the canonical Veltrix tag set | — |
|
|
33
|
+
|
|
34
|
+
Compute detection is **foundation-exclusion**: any plan item whose `kind` is not
|
|
35
|
+
in `foundation_kinds` (network/storage/secrets/tls/load-balancer/dns/license-file/
|
|
36
|
+
hec) is a compute node — so a new tool's roles are compute automatically.
|
|
37
|
+
|
|
38
|
+
## Authoring an app's infra (example)
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// apps/<app>/infra/spec.ts
|
|
42
|
+
import type { InfraSpec } from '@veltrixsecops/app-sdk/opentofu';
|
|
43
|
+
|
|
44
|
+
export const spec: InfraSpec = {
|
|
45
|
+
securityRules: [
|
|
46
|
+
{ port: 443, sources: ['alb'], description: 'Web UI' },
|
|
47
|
+
{ port: 8089, sources: ['self', 'admin'] },
|
|
48
|
+
],
|
|
49
|
+
loadBalancer: { targetPort: 443, healthCheckPath: '/healthz', targetKinds: ['web'] },
|
|
50
|
+
dnsPrefixes: { web: 'web', sensor: 'sensor', manager: 'mgr' },
|
|
51
|
+
bringup: './bringup/run.mjs',
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Validate it in a test with `validateInfraSpec(spec)` (returns `[]` when valid).
|
|
56
|
+
|
|
57
|
+
## Deployment modes — Veltrix-hosted vs BYOC
|
|
58
|
+
|
|
59
|
+
The same modules deploy into Veltrix's account *or* the customer's own cloud
|
|
60
|
+
account (Bring Your Own Cloud). This is controlled by two **worker-set**
|
|
61
|
+
variables — they are NOT part of the app's `InfraSpec` (which describes only the
|
|
62
|
+
tool), so one app spec serves both:
|
|
63
|
+
|
|
64
|
+
- **`network_mode`**
|
|
65
|
+
- `shared` — Veltrix-hosted: data-source the shared VPC + the IPAM-allocated
|
|
66
|
+
per-stack subnet.
|
|
67
|
+
- `dedicated` — BYOC: **create** a fresh VPC/VNet/network + multi-AZ
|
|
68
|
+
public/private subnets + gateway + NAT in the customer's account.
|
|
69
|
+
- `existing` — BYOC: data-source a customer-designated network + create
|
|
70
|
+
subnets in it.
|
|
71
|
+
- **`dns_mode`** (private intra-cluster DNS is always created in the deploy
|
|
72
|
+
account's network regardless)
|
|
73
|
+
- `managed` — module creates the public record + cert in-account (Veltrix zone
|
|
74
|
+
for hosted, customer zone for BYOC customer-owned).
|
|
75
|
+
- `delegated` — BYOC cross-account: the **worker** writes the public record +
|
|
76
|
+
cert-validation into Veltrix's zone; the module takes a pre-validated
|
|
77
|
+
`certificate_arn` and creates no public record/cert.
|
|
78
|
+
- `private-only` — no public DNS; reached via the customer's network.
|
|
79
|
+
|
|
80
|
+
**Credentials/trust** are worker-level (the customer registers a least-privilege
|
|
81
|
+
role/SP/SA/token per cloud via Connections; the worker configures the provider
|
|
82
|
+
with it) — the module body is credential-agnostic.
|
|
83
|
+
|
|
84
|
+
## Contract notes / invariants
|
|
85
|
+
|
|
86
|
+
- **`plan_key` is the status-back key.** Every compute node and foundation tier
|
|
87
|
+
maps `plan_key → cloud ref` via `output "resource_refs"`; keep it 1:1.
|
|
88
|
+
- **`node_fqdns` output** (`plan_key → function FQDN`) is what an app's bring-up
|
|
89
|
+
inventory consumes.
|
|
90
|
+
- A `securityRules` entry using source `"alb"` requires a `loadBalancer`
|
|
91
|
+
(enforced by `validateInfraSpec`).
|
|
92
|
+
- `alb_auth` (Cognito MFA) is off by default; when enabled all three
|
|
93
|
+
`userPool*` fields are required.
|
|
94
|
+
- The module is **pinned + shipped in the package** (`files: ["opentofu"]`);
|
|
95
|
+
the worker resolves it by path from the installed SDK.
|
|
96
|
+
|
|
97
|
+
## Status
|
|
98
|
+
|
|
99
|
+
All four clouds implement the identical spec-derived variable + output contract
|
|
100
|
+
and both deployment modes (`network_mode`, `dns_mode`). None is `tofu validate`-
|
|
101
|
+
gated yet — no OpenTofu binary in this environment, so they were built by close
|
|
102
|
+
translation of the AWS reference + manual review; **run `tofu validate` per
|
|
103
|
+
module in CI before first apply.**
|
|
104
|
+
|
|
105
|
+
| Cloud | Front door / WAF | SG-to-SG | Private DNS | Notable gaps (documented, no-op) |
|
|
106
|
+
|---|---|---|---|---|
|
|
107
|
+
| **aws** (reference) | ALB + WAFv2 + Cognito MFA | SG self-ref | Route53 private zone | — |
|
|
108
|
+
| **azure** | App Gateway + WAF_v2 | NSG + ASG | Private DNS zone | MFA (→ AAD App Proxy); TLS needs a KV cert + identity (no in-module ACM); `web_ingress_cidr` not wired |
|
|
109
|
+
| **gcp** | Global HTTPS LB + Cloud Armor | network tag + firewall | Cloud DNS private zone | MFA (→ IAP); no HTTP→HTTPS redirect; single-zone compute |
|
|
110
|
+
| **hetzner** | hcloud LB (no WAF) | firewall (CIDR) | **none in-provider** — FQDNs emitted for the bring-up / `hetznerdns` | no WAF; no managed DNS; object store → block volume; secrets → platform vault; no MFA |
|
|
111
|
+
|
|
112
|
+
Tags map to **labels** (sanitized) on GCP and Hetzner; Azure and AWS use tags
|
|
113
|
+
directly. Provider-specific vars (image, sizing, zone, public-zone refs) differ
|
|
114
|
+
per cloud by name; the **spec-derived** vars are identical everywhere.
|