@celilo/cli 0.1.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 +1566 -0
- package/bin/celilo +16 -0
- package/drizzle/0000_complex_puma.sql +179 -0
- package/drizzle/0001_dizzy_wolfpack.sql +2 -0
- package/drizzle/0002_web_routes.sql +16 -0
- package/drizzle/0003_backup_storage.sql +32 -0
- package/drizzle/meta/0000_snapshot.json +1151 -0
- package/drizzle/meta/0001_snapshot.json +1167 -0
- package/drizzle/meta/0002_snapshot.json +1257 -0
- package/drizzle/meta/_journal.json +27 -0
- package/package.json +64 -0
- package/schemas/system_config.json +106 -0
- package/src/__integration__/container-services-cli.integration.test.ts +246 -0
- package/src/ansible/dependencies.test.ts +309 -0
- package/src/ansible/dependencies.ts +896 -0
- package/src/ansible/inventory.test.ts +463 -0
- package/src/ansible/inventory.ts +445 -0
- package/src/ansible/secrets.ts +222 -0
- package/src/ansible/validation.test.ts +92 -0
- package/src/ansible/validation.ts +272 -0
- package/src/api-clients/digitalocean.ts +94 -0
- package/src/api-clients/proxmox.ts +655 -0
- package/src/capabilities/logging-wrapper.test.ts +217 -0
- package/src/capabilities/lookup.test.ts +149 -0
- package/src/capabilities/lookup.ts +89 -0
- package/src/capabilities/public-web-helpers.test.ts +198 -0
- package/src/capabilities/public-web-publish.test.ts +458 -0
- package/src/capabilities/registration.test.ts +395 -0
- package/src/capabilities/registration.ts +200 -0
- package/src/capabilities/route-validation.test.ts +121 -0
- package/src/capabilities/route-validation.ts +96 -0
- package/src/capabilities/secret-ref.test.ts +313 -0
- package/src/capabilities/secret-validation.ts +157 -0
- package/src/capabilities/secrets.test.ts +750 -0
- package/src/capabilities/secrets.ts +244 -0
- package/src/capabilities/validation.test.ts +613 -0
- package/src/capabilities/validation.ts +160 -0
- package/src/capabilities/well-known.test.ts +238 -0
- package/src/capabilities/well-known.ts +222 -0
- package/src/cli/cli.test.ts +654 -0
- package/src/cli/command-registry.ts +742 -0
- package/src/cli/command-tree-parser.test.ts +180 -0
- package/src/cli/command-tree-parser.ts +193 -0
- package/src/cli/commands/backup-create.ts +137 -0
- package/src/cli/commands/backup-delete.ts +74 -0
- package/src/cli/commands/backup-import.ts +97 -0
- package/src/cli/commands/backup-list.ts +132 -0
- package/src/cli/commands/backup-name.ts +73 -0
- package/src/cli/commands/backup-prune.ts +98 -0
- package/src/cli/commands/backup-restore.ts +122 -0
- package/src/cli/commands/capability-info.ts +121 -0
- package/src/cli/commands/capability-list.ts +47 -0
- package/src/cli/commands/completion.ts +87 -0
- package/src/cli/commands/hook-run.ts +176 -0
- package/src/cli/commands/ipam.ts +607 -0
- package/src/cli/commands/machine-add.ts +235 -0
- package/src/cli/commands/machine-earmark.ts +82 -0
- package/src/cli/commands/machine-list.ts +77 -0
- package/src/cli/commands/machine-remove.ts +90 -0
- package/src/cli/commands/machine-status.ts +131 -0
- package/src/cli/commands/module-audit.ts +51 -0
- package/src/cli/commands/module-build.ts +60 -0
- package/src/cli/commands/module-config.ts +170 -0
- package/src/cli/commands/module-deploy.ts +71 -0
- package/src/cli/commands/module-generate.ts +236 -0
- package/src/cli/commands/module-health.ts +108 -0
- package/src/cli/commands/module-import.ts +80 -0
- package/src/cli/commands/module-list.ts +43 -0
- package/src/cli/commands/module-logs.ts +73 -0
- package/src/cli/commands/module-remove.ts +162 -0
- package/src/cli/commands/module-show.ts +208 -0
- package/src/cli/commands/module-status.ts +131 -0
- package/src/cli/commands/module-types.ts +189 -0
- package/src/cli/commands/module-upgrade.ts +192 -0
- package/src/cli/commands/package.ts +68 -0
- package/src/cli/commands/secret-list.ts +99 -0
- package/src/cli/commands/secret-set.ts +134 -0
- package/src/cli/commands/service-add-digitalocean.ts +133 -0
- package/src/cli/commands/service-add-proxmox.ts +342 -0
- package/src/cli/commands/service-config-get.ts +83 -0
- package/src/cli/commands/service-config-set.ts +145 -0
- package/src/cli/commands/service-list.ts +74 -0
- package/src/cli/commands/service-reconfigure.ts +230 -0
- package/src/cli/commands/service-remove.ts +103 -0
- package/src/cli/commands/service-verify.ts +240 -0
- package/src/cli/commands/status.ts +216 -0
- package/src/cli/commands/storage-add-local.ts +106 -0
- package/src/cli/commands/storage-add-s3.ts +114 -0
- package/src/cli/commands/storage-list.ts +72 -0
- package/src/cli/commands/storage-remove.ts +54 -0
- package/src/cli/commands/storage-set-default.ts +44 -0
- package/src/cli/commands/storage-verify.ts +54 -0
- package/src/cli/commands/system-config.ts +168 -0
- package/src/cli/commands/system-init.ts +314 -0
- package/src/cli/commands/system-secret-get.ts +98 -0
- package/src/cli/commands/system-secret-set.ts +76 -0
- package/src/cli/commands/system-vault-password.ts +34 -0
- package/src/cli/completion.test.ts +37 -0
- package/src/cli/completion.ts +482 -0
- package/src/cli/fuel-gauge.test.ts +208 -0
- package/src/cli/fuel-gauge.ts +405 -0
- package/src/cli/generate-zsh-completion.test.ts +95 -0
- package/src/cli/generate-zsh-completion.ts +497 -0
- package/src/cli/index.ts +1583 -0
- package/src/cli/interactive-config.test.ts +201 -0
- package/src/cli/interactive-config.ts +62 -0
- package/src/cli/parser.test.ts +227 -0
- package/src/cli/parser.ts +244 -0
- package/src/cli/prompts.test.ts +33 -0
- package/src/cli/prompts.ts +121 -0
- package/src/cli/types.ts +38 -0
- package/src/cli/validators.test.ts +235 -0
- package/src/cli/validators.ts +188 -0
- package/src/config/env.ts +41 -0
- package/src/config/paths.test.ts +172 -0
- package/src/config/paths.ts +108 -0
- package/src/db/client.ts +190 -0
- package/src/db/migrate.ts +30 -0
- package/src/db/schema.test.ts +221 -0
- package/src/db/schema.ts +434 -0
- package/src/hooks/capability-loader-firewall.test.ts +246 -0
- package/src/hooks/capability-loader.test.ts +100 -0
- package/src/hooks/capability-loader.ts +520 -0
- package/src/hooks/define-hook.test.ts +488 -0
- package/src/hooks/executor.test.ts +462 -0
- package/src/hooks/executor.ts +469 -0
- package/src/hooks/logger.test.ts +54 -0
- package/src/hooks/logger.ts +95 -0
- package/src/hooks/test-fixtures/failing-hook.ts +13 -0
- package/src/hooks/test-fixtures/no-default-hook.ts +6 -0
- package/src/hooks/test-fixtures/success-hook.ts +20 -0
- package/src/hooks/test-fixtures/unbranded-hook.ts +11 -0
- package/src/hooks/test-fixtures/void-hook.ts +13 -0
- package/src/hooks/types.ts +89 -0
- package/src/infrastructure/property-extractor.test.ts +194 -0
- package/src/infrastructure/property-extractor.ts +151 -0
- package/src/ipam/allocator.test.ts +442 -0
- package/src/ipam/allocator.ts +369 -0
- package/src/ipam/auto-allocator.test.ts +247 -0
- package/src/ipam/auto-allocator.ts +270 -0
- package/src/ipam/subnet-parser.test.ts +107 -0
- package/src/ipam/subnet-parser.ts +136 -0
- package/src/manifest/contracts/index.ts +61 -0
- package/src/manifest/contracts/v1.ts +118 -0
- package/src/manifest/json-schema-roundtrip.test.ts +99 -0
- package/src/manifest/schema.ts +367 -0
- package/src/manifest/template-validator.test.ts +231 -0
- package/src/manifest/template-validator.ts +322 -0
- package/src/manifest/validate.test.ts +1180 -0
- package/src/manifest/validate.ts +415 -0
- package/src/module/import.test.ts +355 -0
- package/src/module/import.ts +676 -0
- package/src/module/packaging/audit.ts +169 -0
- package/src/module/packaging/build.ts +228 -0
- package/src/module/packaging/checksum.ts +41 -0
- package/src/module/packaging/extract.ts +234 -0
- package/src/module/packaging/signature.ts +47 -0
- package/src/secrets/encryption.test.ts +284 -0
- package/src/secrets/encryption.ts +162 -0
- package/src/secrets/generators.test.ts +112 -0
- package/src/secrets/generators.ts +127 -0
- package/src/secrets/master-key.test.ts +159 -0
- package/src/secrets/master-key.ts +114 -0
- package/src/secrets/storage.test.ts +115 -0
- package/src/secrets/storage.ts +106 -0
- package/src/secrets/vault.test.ts +35 -0
- package/src/secrets/vault.ts +42 -0
- package/src/services/backup-create.ts +532 -0
- package/src/services/backup-metadata.ts +198 -0
- package/src/services/backup-restore.ts +229 -0
- package/src/services/backup-retention.ts +84 -0
- package/src/services/backup-storage.ts +281 -0
- package/src/services/build-stream.test.ts +122 -0
- package/src/services/build-stream.ts +201 -0
- package/src/services/config-interview.ts +694 -0
- package/src/services/container-service.test.ts +298 -0
- package/src/services/container-service.ts +401 -0
- package/src/services/cross-module-data-manager.test.ts +405 -0
- package/src/services/cross-module-data-manager.ts +412 -0
- package/src/services/deploy-ansible.ts +88 -0
- package/src/services/deploy-planner.ts +153 -0
- package/src/services/deploy-preflight.ts +274 -0
- package/src/services/deploy-ssh.ts +131 -0
- package/src/services/deploy-terraform.test.ts +55 -0
- package/src/services/deploy-terraform.ts +445 -0
- package/src/services/deploy-validation.ts +311 -0
- package/src/services/dns-auto-register.ts +211 -0
- package/src/services/health-runner.ts +184 -0
- package/src/services/infrastructure-selector.test.ts +485 -0
- package/src/services/infrastructure-selector.ts +245 -0
- package/src/services/infrastructure-variable-resolver.test.ts +751 -0
- package/src/services/infrastructure-variable-resolver.ts +234 -0
- package/src/services/machine-detector.ts +328 -0
- package/src/services/machine-pool.test.ts +405 -0
- package/src/services/machine-pool.ts +316 -0
- package/src/services/manifest-validation.ts +120 -0
- package/src/services/module-build.test.ts +290 -0
- package/src/services/module-build.ts +431 -0
- package/src/services/module-config.test.ts +237 -0
- package/src/services/module-config.ts +298 -0
- package/src/services/module-deploy.ts +862 -0
- package/src/services/module-types-drift.test.ts +73 -0
- package/src/services/module-types-generator.test.ts +288 -0
- package/src/services/module-types-generator.ts +189 -0
- package/src/services/proxmox-state-recovery.ts +140 -0
- package/src/services/schema-validation.ts +155 -0
- package/src/services/secret-schema-loader.test.ts +311 -0
- package/src/services/secret-schema-loader.ts +239 -0
- package/src/services/ssh-key-manager.test.ts +283 -0
- package/src/services/ssh-key-manager.ts +193 -0
- package/src/services/storage-providers/local.ts +105 -0
- package/src/services/storage-providers/s3.ts +182 -0
- package/src/services/storage-providers/types.ts +24 -0
- package/src/services/system-config-schema-types.ts +25 -0
- package/src/services/system-config-validator.test.ts +160 -0
- package/src/services/system-config-validator.ts +74 -0
- package/src/services/system-init.test.ts +153 -0
- package/src/services/system-init.ts +253 -0
- package/src/services/terraform-safety.ts +174 -0
- package/src/services/zone-detector.test.ts +110 -0
- package/src/services/zone-detector.ts +102 -0
- package/src/services/zone-policy.test.ts +97 -0
- package/src/services/zone-policy.ts +126 -0
- package/src/templates/generator.test.ts +645 -0
- package/src/templates/generator.ts +1119 -0
- package/src/templates/types.ts +62 -0
- package/src/test-utils/INTERACTIVE_PROMPTS.md +167 -0
- package/src/test-utils/cli-context-interactive.test.ts +152 -0
- package/src/test-utils/cli-context-server.test.ts +66 -0
- package/src/test-utils/cli-context.test.ts +273 -0
- package/src/test-utils/cli-context.ts +677 -0
- package/src/test-utils/cli-result.test.ts +282 -0
- package/src/test-utils/cli-result.ts +241 -0
- package/src/test-utils/cli.ts +55 -0
- package/src/test-utils/completion-harness.test.ts +126 -0
- package/src/test-utils/completion-harness.ts +82 -0
- package/src/test-utils/database.test.ts +182 -0
- package/src/test-utils/database.ts +126 -0
- package/src/test-utils/filesystem.test.ts +208 -0
- package/src/test-utils/filesystem.ts +142 -0
- package/src/test-utils/fixtures.test.ts +123 -0
- package/src/test-utils/fixtures.ts +160 -0
- package/src/test-utils/golden-diff.ts +197 -0
- package/src/test-utils/index.ts +77 -0
- package/src/test-utils/integration.ts +81 -0
- package/src/test-utils/module-fixtures.ts +468 -0
- package/src/test-utils/modules.test.ts +144 -0
- package/src/test-utils/modules.ts +183 -0
- package/src/test-utils/setup-test-db.ts +90 -0
- package/src/test-utils/value-extractor.test.ts +231 -0
- package/src/test-utils/value-extractor.ts +228 -0
- package/src/types/infrastructure.ts +157 -0
- package/src/utils/shell.test.ts +365 -0
- package/src/utils/shell.ts +159 -0
- package/src/validation/schemas.ts +166 -0
- package/src/variables/ansible-resolver.test.ts +142 -0
- package/src/variables/ansible-resolver.ts +69 -0
- package/src/variables/capability-self-ref.test.ts +220 -0
- package/src/variables/context.test.ts +1265 -0
- package/src/variables/context.ts +624 -0
- package/src/variables/declarative-derivation.test.ts +743 -0
- package/src/variables/declarative-derivation.ts +200 -0
- package/src/variables/parser.test.ts +231 -0
- package/src/variables/parser.ts +76 -0
- package/src/variables/resolver.test.ts +458 -0
- package/src/variables/resolver.ts +282 -0
- package/src/variables/types.ts +59 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for firewall chain building in capability loader
|
|
3
|
+
*
|
|
4
|
+
* Tests the buildFirewallChain logic that wires iptables → greenwave.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
8
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import type { HookLogger } from '@celilo/capabilities';
|
|
12
|
+
import type { DbClient } from '../db/client';
|
|
13
|
+
import { cleanupTestDatabase, setupTestDatabase } from '../test-utils/database';
|
|
14
|
+
import { loadCapabilityFunctions } from './capability-loader';
|
|
15
|
+
|
|
16
|
+
const noopLogger: HookLogger = {
|
|
17
|
+
info() {},
|
|
18
|
+
warn() {},
|
|
19
|
+
error() {},
|
|
20
|
+
success() {},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Mock greenwave firewall factory using defineCapabilityFunction (the
|
|
24
|
+
// post-Phase-8 pattern). The compiled factory takes a single
|
|
25
|
+
// `{ config, secrets, logger }` context and the framework wraps the
|
|
26
|
+
// returned method table with auto-logging.
|
|
27
|
+
//
|
|
28
|
+
// We use an absolute import path to the workspace package because the
|
|
29
|
+
// temp dir where the mock is dropped has no node_modules link to
|
|
30
|
+
// resolve the bare `@celilo/capabilities` specifier.
|
|
31
|
+
const CAPABILITIES_PKG_PATH = join(__dirname, '../../../..', 'packages/capabilities/src/index.ts');
|
|
32
|
+
const MOCK_GREENWAVE_MODULE = `
|
|
33
|
+
import { defineCapabilityFunction } from '${CAPABILITIES_PKG_PATH}';
|
|
34
|
+
|
|
35
|
+
export default defineCapabilityFunction({
|
|
36
|
+
capability: 'firewall',
|
|
37
|
+
handler: ({ config, secrets }) => ({
|
|
38
|
+
exposeService: async (opts) => ({
|
|
39
|
+
externalIp: '71.36.99.96',
|
|
40
|
+
natIp: opts.internalIp,
|
|
41
|
+
}),
|
|
42
|
+
unexposeService: async () => {},
|
|
43
|
+
listExposedServices: async () => [],
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
// Mock iptables firewall factory — kept on the legacy
|
|
49
|
+
// `createFirewall(config, upstreamFirewall)` shape because its
|
|
50
|
+
// upstream-injection signature doesn't fit the
|
|
51
|
+
// defineCapabilityFunction `{ config, secrets, logger }` context.
|
|
52
|
+
// buildFirewallChain detects this and wires the upstream manually.
|
|
53
|
+
const MOCK_IPTABLES_MODULE = `
|
|
54
|
+
export function createFirewall(config, upstreamFirewall) {
|
|
55
|
+
return {
|
|
56
|
+
exposeService: async (opts) => {
|
|
57
|
+
if (!upstreamFirewall) {
|
|
58
|
+
throw new Error('No upstream firewall');
|
|
59
|
+
}
|
|
60
|
+
// Delegate upstream first (with NAT IP)
|
|
61
|
+
const upstream = await upstreamFirewall.exposeService({
|
|
62
|
+
...opts,
|
|
63
|
+
internalIp: config.natIp,
|
|
64
|
+
});
|
|
65
|
+
// Then "create" local rules (just tracking for test)
|
|
66
|
+
return {
|
|
67
|
+
externalIp: upstream.externalIp,
|
|
68
|
+
natIp: config.natIp,
|
|
69
|
+
localRulesCreated: true,
|
|
70
|
+
originalIp: opts.internalIp,
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
unexposeService: async () => {},
|
|
74
|
+
listExposedServices: async () => [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
describe('Firewall Chain Building', () => {
|
|
80
|
+
let db: DbClient;
|
|
81
|
+
let tempDir: string;
|
|
82
|
+
|
|
83
|
+
beforeEach(async () => {
|
|
84
|
+
db = await setupTestDatabase();
|
|
85
|
+
tempDir = join(tmpdir(), `celilo-fw-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
86
|
+
mkdirSync(tempDir, { recursive: true });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
afterEach(async () => {
|
|
90
|
+
await cleanupTestDatabase(db);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('loads single firewall provider (greenwave only)', async () => {
|
|
94
|
+
const gwPath = join(tempDir, 'greenwave');
|
|
95
|
+
const gwScripts = join(gwPath, 'scripts');
|
|
96
|
+
mkdirSync(gwScripts, { recursive: true });
|
|
97
|
+
writeFileSync(join(gwScripts, 'firewall-functions.ts'), MOCK_GREENWAVE_MODULE);
|
|
98
|
+
|
|
99
|
+
db.$client.run(
|
|
100
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('greenwave', 'GreenWave', '1.0.0', '${gwPath}', '{}')`,
|
|
101
|
+
);
|
|
102
|
+
db.$client.run(
|
|
103
|
+
`INSERT INTO capabilities (module_id, capability_name, version, data, zones) VALUES ('greenwave', 'firewall', '1.0.0', '{"has_external":true}', '["internal"]')`,
|
|
104
|
+
);
|
|
105
|
+
db.$client.run(
|
|
106
|
+
`INSERT INTO module_configs (module_id, key, value) VALUES ('greenwave', 'router_ip', '192.168.0.1')`,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Set up encrypted secrets
|
|
110
|
+
const { encryptSecret } = await import('../secrets/encryption');
|
|
111
|
+
const { getOrCreateMasterKey } = await import('../secrets/master-key');
|
|
112
|
+
const masterKey = await getOrCreateMasterKey();
|
|
113
|
+
for (const [name, value] of [
|
|
114
|
+
['router_username', 'admin'],
|
|
115
|
+
['router_password', 'test'],
|
|
116
|
+
]) {
|
|
117
|
+
const enc = encryptSecret(value, masterKey);
|
|
118
|
+
db.$client.run(
|
|
119
|
+
`INSERT INTO secrets (module_id, name, encrypted_value, iv, auth_tag) VALUES ('greenwave', '${name}', '${enc.encryptedValue}', '${enc.iv}', '${enc.authTag}')`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const result = await loadCapabilityFunctions('test-consumer', db, noopLogger);
|
|
124
|
+
expect(result.firewall).toBeTruthy();
|
|
125
|
+
|
|
126
|
+
// Test the interface
|
|
127
|
+
const fw = result.firewall as {
|
|
128
|
+
exposeService: (opts: {
|
|
129
|
+
internalIp: string;
|
|
130
|
+
ports: number[];
|
|
131
|
+
description: string;
|
|
132
|
+
}) => Promise<{ externalIp: string }>;
|
|
133
|
+
};
|
|
134
|
+
const exposed = await fw.exposeService({
|
|
135
|
+
internalIp: '10.0.10.10',
|
|
136
|
+
ports: [80],
|
|
137
|
+
description: 'test',
|
|
138
|
+
});
|
|
139
|
+
expect(exposed.externalIp).toBe('71.36.99.96');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('builds chain with two providers (iptables → greenwave)', async () => {
|
|
143
|
+
// Set up greenwave
|
|
144
|
+
const gwPath = join(tempDir, 'greenwave');
|
|
145
|
+
const gwScripts = join(gwPath, 'scripts');
|
|
146
|
+
mkdirSync(gwScripts, { recursive: true });
|
|
147
|
+
writeFileSync(join(gwScripts, 'firewall-functions.ts'), MOCK_GREENWAVE_MODULE);
|
|
148
|
+
|
|
149
|
+
db.$client.run(
|
|
150
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('greenwave', 'GreenWave', '1.0.0', '${gwPath}', '{}')`,
|
|
151
|
+
);
|
|
152
|
+
db.$client.run(
|
|
153
|
+
`INSERT INTO capabilities (module_id, capability_name, version, data, zones) VALUES ('greenwave', 'firewall', '1.0.0', '{"has_external":true}', '["internal"]')`,
|
|
154
|
+
);
|
|
155
|
+
db.$client.run(
|
|
156
|
+
`INSERT INTO module_configs (module_id, key, value) VALUES ('greenwave', 'router_ip', '192.168.0.1')`,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const { encryptSecret } = await import('../secrets/encryption');
|
|
160
|
+
const { getOrCreateMasterKey } = await import('../secrets/master-key');
|
|
161
|
+
const masterKey = await getOrCreateMasterKey();
|
|
162
|
+
for (const [name, value] of [
|
|
163
|
+
['router_username', 'admin'],
|
|
164
|
+
['router_password', 'test'],
|
|
165
|
+
]) {
|
|
166
|
+
const enc = encryptSecret(value, masterKey);
|
|
167
|
+
db.$client.run(
|
|
168
|
+
`INSERT INTO secrets (module_id, name, encrypted_value, iv, auth_tag) VALUES ('greenwave', '${name}', '${enc.encryptedValue}', '${enc.iv}', '${enc.authTag}')`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Set up iptables
|
|
173
|
+
const iptPath = join(tempDir, 'iptables');
|
|
174
|
+
const iptScripts = join(iptPath, 'scripts');
|
|
175
|
+
mkdirSync(iptScripts, { recursive: true });
|
|
176
|
+
writeFileSync(join(iptScripts, 'firewall-functions.ts'), MOCK_IPTABLES_MODULE);
|
|
177
|
+
|
|
178
|
+
db.$client.run(
|
|
179
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('iptables', 'iptables', '1.0.0', '${iptPath}', '{}')`,
|
|
180
|
+
);
|
|
181
|
+
db.$client.run(
|
|
182
|
+
`INSERT INTO capabilities (module_id, capability_name, version, data, zones) VALUES ('iptables', 'firewall', '1.0.0', '{}', '["dmz","app","secure"]')`,
|
|
183
|
+
);
|
|
184
|
+
db.$client.run(
|
|
185
|
+
`INSERT INTO module_configs (module_id, key, value) VALUES ('iptables', 'firewall_ip', '192.168.0.254')`,
|
|
186
|
+
);
|
|
187
|
+
db.$client.run(
|
|
188
|
+
`INSERT INTO module_configs (module_id, key, value) VALUES ('iptables', 'nat_ip', '192.168.0.253')`,
|
|
189
|
+
);
|
|
190
|
+
db.$client.run(
|
|
191
|
+
`INSERT INTO module_configs (module_id, key, value) VALUES ('iptables', 'upstream_zone', 'internal')`,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const result = await loadCapabilityFunctions('test-consumer', db, noopLogger);
|
|
195
|
+
expect(result.firewall).toBeTruthy();
|
|
196
|
+
|
|
197
|
+
// Test the chain: iptables delegates to greenwave
|
|
198
|
+
const fw = result.firewall as {
|
|
199
|
+
exposeService: (opts: {
|
|
200
|
+
internalIp: string;
|
|
201
|
+
ports: number[];
|
|
202
|
+
description: string;
|
|
203
|
+
}) => Promise<{
|
|
204
|
+
externalIp: string;
|
|
205
|
+
natIp: string;
|
|
206
|
+
localRulesCreated: boolean;
|
|
207
|
+
originalIp: string;
|
|
208
|
+
}>;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const exposed = await fw.exposeService({
|
|
212
|
+
internalIp: '10.0.10.10',
|
|
213
|
+
ports: [80],
|
|
214
|
+
description: 'Caddy',
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// External IP came from greenwave (leaf)
|
|
218
|
+
expect(exposed.externalIp).toBe('71.36.99.96');
|
|
219
|
+
// NAT IP is iptables' NAT address
|
|
220
|
+
expect(exposed.natIp).toBe('192.168.0.253');
|
|
221
|
+
// iptables created local rules
|
|
222
|
+
expect(exposed.localRulesCreated).toBe(true);
|
|
223
|
+
// iptables tracked the original internal IP
|
|
224
|
+
expect(exposed.originalIp).toBe('10.0.10.10');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('returns null when no firewall has external interface', async () => {
|
|
228
|
+
const iptPath = join(tempDir, 'iptables');
|
|
229
|
+
const iptScripts = join(iptPath, 'scripts');
|
|
230
|
+
mkdirSync(iptScripts, { recursive: true });
|
|
231
|
+
writeFileSync(join(iptScripts, 'firewall-functions.ts'), MOCK_IPTABLES_MODULE);
|
|
232
|
+
|
|
233
|
+
db.$client.run(
|
|
234
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('iptables', 'iptables', '1.0.0', '${iptPath}', '{}')`,
|
|
235
|
+
);
|
|
236
|
+
// Note: has_external is NOT set
|
|
237
|
+
db.$client.run(
|
|
238
|
+
`INSERT INTO capabilities (module_id, capability_name, version, data, zones) VALUES ('iptables', 'firewall', '1.0.0', '{}', '["dmz"]')`,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const _result = await loadCapabilityFunctions('test-consumer', db, noopLogger);
|
|
242
|
+
// Single provider without has_external — still loads (just can't delegate)
|
|
243
|
+
// But buildFirewallChain is only called with >1 providers
|
|
244
|
+
// Single provider loads normally via the standard path
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for capability function loader
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
6
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import type { HookLogger } from '@celilo/capabilities';
|
|
10
|
+
import type { DbClient } from '../db/client';
|
|
11
|
+
import { cleanupTestDatabase, setupTestDatabase } from '../test-utils/database';
|
|
12
|
+
import { loadCapabilityFunctions } from './capability-loader';
|
|
13
|
+
|
|
14
|
+
const noopLogger: HookLogger = {
|
|
15
|
+
info() {},
|
|
16
|
+
warn() {},
|
|
17
|
+
error() {},
|
|
18
|
+
success() {},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Minimal test module that exports a default factory
|
|
22
|
+
const TEST_REGISTER_HOST_MODULE = `
|
|
23
|
+
export default function registerHost(context) {
|
|
24
|
+
return {
|
|
25
|
+
host: context.config.primary_domain,
|
|
26
|
+
hasSecrets: !!context.secrets.ddns_password,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
describe('Capability Loader', () => {
|
|
32
|
+
let db: DbClient;
|
|
33
|
+
let tempDir: string;
|
|
34
|
+
|
|
35
|
+
beforeEach(async () => {
|
|
36
|
+
db = await setupTestDatabase();
|
|
37
|
+
tempDir = join(
|
|
38
|
+
tmpdir(),
|
|
39
|
+
`celilo-cap-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
40
|
+
);
|
|
41
|
+
mkdirSync(tempDir, { recursive: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(async () => {
|
|
45
|
+
await cleanupTestDatabase(db);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('returns empty object when no capabilities are registered', async () => {
|
|
49
|
+
const result = await loadCapabilityFunctions('test-module', db, noopLogger);
|
|
50
|
+
expect(result).toEqual({});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('returns empty object when capability is registered but module does not exist', async () => {
|
|
54
|
+
const modulePath = join(tempDir, 'namecheap');
|
|
55
|
+
mkdirSync(modulePath, { recursive: true });
|
|
56
|
+
|
|
57
|
+
db.$client.run(
|
|
58
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('namecheap', 'Namecheap', '2.0.0', '${modulePath}', '{}')`,
|
|
59
|
+
);
|
|
60
|
+
db.$client.run(
|
|
61
|
+
`INSERT INTO capabilities (module_id, capability_name, version, data, registered_at) VALUES ('namecheap', 'dns_registrar', '2.0.0', '{}', unixepoch())`,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const result = await loadCapabilityFunctions('caddy', db, noopLogger);
|
|
65
|
+
expect(result).toEqual({});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('loads dns_registrar capability when module, config, and secrets exist', async () => {
|
|
69
|
+
// Create module directory with register-host module
|
|
70
|
+
const modulePath = join(tempDir, 'namecheap');
|
|
71
|
+
const scriptsDir = join(modulePath, 'scripts');
|
|
72
|
+
mkdirSync(scriptsDir, { recursive: true });
|
|
73
|
+
writeFileSync(join(scriptsDir, 'register-host.ts'), TEST_REGISTER_HOST_MODULE);
|
|
74
|
+
|
|
75
|
+
db.$client.run(
|
|
76
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('namecheap', 'Namecheap', '2.0.0', '${modulePath}', '{}')`,
|
|
77
|
+
);
|
|
78
|
+
db.$client.run(
|
|
79
|
+
`INSERT INTO capabilities (module_id, capability_name, version, data, registered_at) VALUES ('namecheap', 'dns_registrar', '2.0.0', '{}', unixepoch())`,
|
|
80
|
+
);
|
|
81
|
+
db.$client.run(
|
|
82
|
+
`INSERT INTO module_configs (module_id, key, value) VALUES ('namecheap', 'primary_domain', 'example.com')`,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Set encrypted secrets
|
|
86
|
+
const { encryptSecret } = await import('../secrets/encryption');
|
|
87
|
+
const { getOrCreateMasterKey } = await import('../secrets/master-key');
|
|
88
|
+
const masterKey = await getOrCreateMasterKey();
|
|
89
|
+
|
|
90
|
+
const ddnsPasswordEnc = encryptSecret('test-ddns-password', masterKey);
|
|
91
|
+
db.$client.run(
|
|
92
|
+
`INSERT INTO secrets (module_id, name, encrypted_value, iv, auth_tag) VALUES ('namecheap', 'ddns_password', '${ddnsPasswordEnc.encryptedValue}', '${ddnsPasswordEnc.iv}', '${ddnsPasswordEnc.authTag}')`,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const result = await loadCapabilityFunctions('caddy', db, noopLogger);
|
|
96
|
+
|
|
97
|
+
expect(result).toHaveProperty('dns_registrar');
|
|
98
|
+
expect(result.dns_registrar).toBeTruthy();
|
|
99
|
+
});
|
|
100
|
+
});
|