diff --git a/config.ts b/config.ts new file mode 100644 index 0000000..01dd663 --- /dev/null +++ b/config.ts @@ -0,0 +1,144 @@ +// ============================================================================= +// NetBird Configuration +// ============================================================================= +// Edit this file to add/modify groups, policies, and setup keys. +// Changes are applied via CI/CD when merged to main. + +export interface GroupConfig { + name: string; + description?: string; +} + +export interface PolicyRuleConfig { + name: string; + description?: string; + sources: string[]; // Group names + destinations: string[]; // Group names + bidirectional?: boolean; + protocol?: string; +} + +export interface PolicyConfig { + name: string; + description?: string; + rules: PolicyRuleConfig[]; +} + +export interface SetupKeyConfig { + name: string; + groups: string[]; // Group names + type?: "one-off" | "reusable"; + expiresInDays?: number; // 0 = never + usageLimit?: number; // 0 = unlimited +} + +// ============================================================================= +// GROUPS +// ============================================================================= +// Add new groups here. They define logical groupings of peers. + +export const groups: GroupConfig[] = [ + { name: "ground-stations", description: "Ground station devices" }, + { name: "pilots", description: "Pilot control stations" }, + { name: "operators", description: "Operator workstations" }, + { name: "fusion-servers", description: "Data fusion servers" }, + + // Add new groups below: + // { name: "maintenance", description: "Maintenance team devices" }, +]; + +// ============================================================================= +// POLICIES +// ============================================================================= +// Define access control between groups. + +export const policies: PolicyConfig[] = [ + { + name: "pilot-to-ground-station", + description: "Allow pilots to connect to ground stations", + rules: [ + { + name: "pilot-gs-access", + description: "Pilots can access ground stations", + sources: ["pilots"], + destinations: ["ground-stations"], + bidirectional: true, + protocol: "all", + }, + ], + }, + { + name: "operator-full-access", + description: "Operators can access all network resources", + rules: [ + { + name: "operator-all", + description: "Full operator access", + sources: ["operators"], + destinations: ["ground-stations", "pilots", "fusion-servers"], + bidirectional: true, + protocol: "all", + }, + ], + }, + { + name: "fusion-to-ground-station", + description: "Fusion servers coordinate with ground stations", + rules: [ + { + name: "fusion-gs", + description: "Fusion to GS access", + sources: ["fusion-servers"], + destinations: ["ground-stations"], + bidirectional: true, + protocol: "all", + }, + ], + }, + + // Add new policies below: + // { + // name: "maintenance-access", + // description: "Maintenance team can access all devices", + // rules: [ + // { + // name: "maintenance-all", + // sources: ["maintenance"], + // destinations: ["ground-stations", "pilots", "fusion-servers"], + // bidirectional: true, + // protocol: "all", + // }, + // ], + // }, +]; + +// ============================================================================= +// SETUP KEYS +// ============================================================================= +// Setup keys for enrolling new peers. + +export const setupKeys: SetupKeyConfig[] = [ + { + name: "ground-station-onboarding", + groups: ["ground-stations"], + type: "reusable", + expiresInDays: 0, // Never expires + usageLimit: 0, // Unlimited + }, + { + name: "pilot-onboarding", + groups: ["pilots"], + type: "reusable", + expiresInDays: 30, + usageLimit: 0, + }, + + // Add new setup keys below: + // { + // name: "operator-onboarding", + // groups: ["operators"], + // type: "reusable", + // expiresInDays: 7, + // usageLimit: 10, + // }, +]; diff --git a/index.ts b/index.ts index 7cbb595..7dcc125 100644 --- a/index.ts +++ b/index.ts @@ -1,5 +1,6 @@ import * as pulumi from "@pulumi/pulumi"; import { Group, Policy, SetupKey, NetBirdConfig } from "./netbird"; +import { groups as groupConfigs, policies as policyConfigs, setupKeys as setupKeyConfigs } from "./config"; // ============================================================================= // Configuration @@ -11,154 +12,91 @@ const netbirdConfig: NetBirdConfig = { }; // ============================================================================= -// Groups - Achilles Network Structure +// Create Groups from Config // ============================================================================= -const groups = { - groundStations: new Group( - "ground-stations", - { name: "ground-stations", peers: [] }, +const groups: Record = {}; + +for (const cfg of groupConfigs) { + groups[cfg.name] = new Group( + cfg.name, + { name: cfg.name, peers: [] }, netbirdConfig - ), - pilots: new Group("pilots", { name: "pilots", peers: [] }, netbirdConfig), - operators: new Group( - "operators", - { name: "operators", peers: [] }, - netbirdConfig - ), - fusionServers: new Group( - "fusion-servers", - { name: "fusion-servers", peers: [] }, - netbirdConfig - ), -}; + ); +} // ============================================================================= -// Policies - Access Control +// Create Policies from Config // ============================================================================= -const policies = { - pilotToGs: new Policy( - "pilot-to-ground-station", +const policies: Record = {}; + +for (const cfg of policyConfigs) { + // Resolve group names to group IDs + const rules = cfg.rules.map((rule) => ({ + name: rule.name, + description: rule.description || "", + enabled: true, + sources: rule.sources.map((name) => groups[name].id), + destinations: rule.destinations.map((name) => groups[name].id), + bidirectional: rule.bidirectional ?? true, + protocol: rule.protocol || "all", + action: "accept" as const, + })); + + // Collect all referenced groups for dependencies + const referencedGroups = [ + ...new Set([ + ...cfg.rules.flatMap((r) => r.sources), + ...cfg.rules.flatMap((r) => r.destinations), + ]), + ].map((name) => groups[name]); + + policies[cfg.name] = new Policy( + cfg.name, { - name: "pilot-to-ground-station", - description: "Allow pilots to connect to ground stations", + name: cfg.name, + description: cfg.description || "", enabled: true, - rules: [ - { - name: "pilot-gs-access", - description: "Pilots can access ground stations", - enabled: true, - sources: [groups.pilots.id], - destinations: [groups.groundStations.id], - bidirectional: true, - protocol: "all", - action: "accept", - }, - ], + rules, }, netbirdConfig, - { dependsOn: [groups.pilots, groups.groundStations] } - ), - - operatorFullAccess: new Policy( - "operator-full-access", - { - name: "operator-full-access", - description: "Operators can access all network resources", - enabled: true, - rules: [ - { - name: "operator-all", - description: "Full operator access", - enabled: true, - sources: [groups.operators.id], - destinations: [ - groups.groundStations.id, - groups.pilots.id, - groups.fusionServers.id, - ], - bidirectional: true, - protocol: "all", - action: "accept", - }, - ], - }, - netbirdConfig, - { - dependsOn: [ - groups.operators, - groups.groundStations, - groups.pilots, - groups.fusionServers, - ], - } - ), - - fusionToGs: new Policy( - "fusion-to-ground-station", - { - name: "fusion-to-ground-station", - description: "Fusion servers coordinate with ground stations", - enabled: true, - rules: [ - { - name: "fusion-gs", - description: "Fusion to GS access", - enabled: true, - sources: [groups.fusionServers.id], - destinations: [groups.groundStations.id], - bidirectional: true, - protocol: "all", - action: "accept", - }, - ], - }, - netbirdConfig, - { dependsOn: [groups.fusionServers, groups.groundStations] } - ), -}; + { dependsOn: referencedGroups } + ); +} // ============================================================================= -// Setup Keys - Peer Onboarding +// Create Setup Keys from Config // ============================================================================= -const setupKeys = { - gsOnboarding: new SetupKey( - "ground-station-onboarding", +const setupKeys: Record = {}; + +for (const cfg of setupKeyConfigs) { + const referencedGroups = cfg.groups.map((name) => groups[name]); + + setupKeys[cfg.name] = new SetupKey( + cfg.name, { - name: "ground-station-onboarding", - type: "reusable", - autoGroups: [groups.groundStations.id], - usageLimit: 0, - expiresIn: 0, + name: cfg.name, + type: cfg.type || "reusable", + autoGroups: cfg.groups.map((name) => groups[name].id), + usageLimit: cfg.usageLimit ?? 0, + expiresIn: (cfg.expiresInDays ?? 0) * 24 * 60 * 60, // Convert days to seconds ephemeral: false, }, netbirdConfig, - { dependsOn: [groups.groundStations] } - ), - - pilotOnboarding: new SetupKey( - "pilot-onboarding", - { - name: "pilot-onboarding", - type: "reusable", - autoGroups: [groups.pilots.id], - usageLimit: 0, - expiresIn: 2592000, // 30 days - ephemeral: false, - }, - netbirdConfig, - { dependsOn: [groups.pilots] } - ), -}; + { dependsOn: referencedGroups } + ); +} // ============================================================================= // Outputs // ============================================================================= -export const groupIds = { - groundStations: groups.groundStations.id, - pilots: groups.pilots.id, - operators: groups.operators.id, - fusionServers: groups.fusionServers.id, -}; +export const groupIds = Object.fromEntries( + Object.entries(groups).map(([name, group]) => [name, group.id]) +); -export const gsSetupKey = pulumi.secret(setupKeys.gsOnboarding.key); -export const pilotSetupKey = pulumi.secret(setupKeys.pilotOnboarding.key); +export const policyIds = Object.fromEntries( + Object.entries(policies).map(([name, policy]) => [name, policy.id]) +); + +export const setupKeyValues = Object.fromEntries( + Object.entries(setupKeys).map(([name, key]) => [name, pulumi.secret(key.key)]) +);