photoncloud-monorepo/nix/modules/ultracloud-tenant-networking.nix
2026-04-04 16:33:03 +09:00

373 lines
9.4 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.ultracloud.tenantNetworking;
jsonFormat = pkgs.formats.json {};
serviceIpPoolType = types.submodule {
options = {
name = mkOption {
type = types.str;
description = "Service IP pool name";
};
cidr_block = mkOption {
type = types.str;
description = "Service IP pool CIDR";
};
description = mkOption {
type = types.nullOr types.str;
default = null;
description = "Service IP pool description";
};
pool_type = mkOption {
type = types.nullOr (types.enum [ "cluster_ip" "load_balancer" "node_port" ]);
default = null;
description = "Service IP pool type";
};
};
};
portType = types.submodule {
options = {
name = mkOption {
type = types.str;
description = "Port name";
};
description = mkOption {
type = types.nullOr types.str;
default = null;
description = "Port description";
};
ip_address = mkOption {
type = types.nullOr types.str;
default = null;
description = "Requested fixed IP address";
};
security_groups = mkOption {
type = types.listOf types.str;
default = [];
description = "Security group names attached to the port";
};
admin_state_up = mkOption {
type = types.nullOr types.bool;
default = null;
description = "Administrative state for the port";
};
};
};
subnetType = types.submodule {
options = {
name = mkOption {
type = types.str;
description = "Subnet name";
};
cidr_block = mkOption {
type = types.str;
description = "Subnet CIDR";
};
gateway_ip = mkOption {
type = types.nullOr types.str;
default = null;
description = "Gateway IP";
};
description = mkOption {
type = types.nullOr types.str;
default = null;
description = "Subnet description";
};
dhcp_enabled = mkOption {
type = types.nullOr types.bool;
default = null;
description = "Enable DHCP for the subnet";
};
ports = mkOption {
type = types.listOf portType;
default = [];
description = "Ports within the subnet";
};
};
};
routerType = types.submodule {
options = {
name = mkOption {
type = types.str;
description = "Router name";
};
gateway_cidr = mkOption {
type = types.str;
description = "Gateway interface CIDR attached to the VPC";
};
mac_address = mkOption {
type = types.str;
description = "Router interface MAC address";
};
external_ip = mkOption {
type = types.str;
description = "SNAT external IPv4 address";
};
description = mkOption {
type = types.nullOr types.str;
default = null;
description = "Router description";
};
};
};
vpcType = types.submodule {
options = {
name = mkOption {
type = types.str;
description = "VPC name";
};
cidr_block = mkOption {
type = types.str;
description = "VPC CIDR";
};
description = mkOption {
type = types.nullOr types.str;
default = null;
description = "VPC description";
};
router = mkOption {
type = types.nullOr routerType;
default = null;
description = "Optional tenant edge router for the VPC";
};
subnets = mkOption {
type = types.listOf subnetType;
default = [];
description = "Subnets within the VPC";
};
};
};
securityGroupRuleType = types.submodule {
options = {
direction = mkOption {
type = types.enum [ "ingress" "egress" ];
description = "Rule direction";
};
protocol = mkOption {
type = types.nullOr (types.enum [ "any" "tcp" "udp" "icmp" "icmpv6" ]);
default = null;
description = "IP protocol";
};
port_range_min = mkOption {
type = types.nullOr types.int;
default = null;
description = "Minimum port in range";
};
port_range_max = mkOption {
type = types.nullOr types.int;
default = null;
description = "Maximum port in range";
};
remote_cidr = mkOption {
type = types.nullOr types.str;
default = null;
description = "Remote CIDR";
};
remote_group = mkOption {
type = types.nullOr types.str;
default = null;
description = "Remote security group name";
};
description = mkOption {
type = types.nullOr types.str;
default = null;
description = "Rule description";
};
};
};
securityGroupType = types.submodule {
options = {
name = mkOption {
type = types.str;
description = "Security group name";
};
description = mkOption {
type = types.nullOr types.str;
default = null;
description = "Security group description";
};
rules = mkOption {
type = types.listOf securityGroupRuleType;
default = [];
description = "Security group rules";
};
};
};
tenantType = types.submodule {
options = {
org_id = mkOption {
type = types.str;
description = "Tenant organization ID";
};
project_id = mkOption {
type = types.str;
description = "Tenant project ID";
};
security_groups = mkOption {
type = types.listOf securityGroupType;
default = [];
description = "Tenant-scoped security groups";
};
service_ip_pools = mkOption {
type = types.listOf serviceIpPoolType;
default = [];
description = "Tenant-scoped Service IP pools";
};
vpcs = mkOption {
type = types.listOf vpcType;
default = [];
description = "Tenant-scoped VPCs and their nested resources";
};
};
};
configFile = jsonFormat.generate "ultracloud-tenant-networking.json" {
inherit (cfg) tenants;
};
configPath = cfg.configPath;
configRelative = removePrefix "/etc/" configPath;
in {
options.ultracloud.tenantNetworking = {
enable = mkEnableOption "tenant-scoped PrismNET declarations";
endpoint = mkOption {
type = types.str;
default = "http://127.0.0.1:50081";
description = "PrismNET gRPC endpoint";
};
iamEndpoint = mkOption {
type = types.str;
default = "http://127.0.0.1:50080";
description = "IAM gRPC endpoint used to mint tenant-scoped controller tokens";
};
controllerPrincipalId = mkOption {
type = types.str;
default = "ultracloud-reconciler";
description = "Service account used by the reconciler when applying tenant declarations";
};
tenants = mkOption {
type = types.listOf tenantType;
default = [];
description = "Tenant-scoped network declarations. This is separate from platform networking under ultracloud.network.";
};
configPath = mkOption {
type = types.str;
default = "/etc/ultracloud/tenant-networking.json";
description = "Path for rendered tenant networking config";
};
applyOnBoot = mkOption {
type = types.bool;
default = true;
description = "Apply declarations at boot";
};
applyOnChange = mkOption {
type = types.bool;
default = true;
description = "Apply declarations when the config file changes";
};
prune = mkOption {
type = types.bool;
default = false;
description = "Delete tenant network resources not declared for managed tenants";
};
package = mkOption {
type = types.package;
default = pkgs.ultracloud-reconciler or (throw "ultracloud-reconciler package not found");
description = "Reconciler package for tenant networking declarations";
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = hasPrefix "/etc/" configPath;
message = "ultracloud.tenantNetworking.configPath must be under /etc";
}
];
environment.etc."${configRelative}".source = configFile;
systemd.services.ultracloud-tenant-networking-apply = {
description = "Apply UltraCloud tenant networking declarations";
after =
[ "network-online.target" ]
++ optional config.services.prismnet.enable "prismnet.service"
++ optional config.services.iam.enable "iam.service";
wants =
[ "network-online.target" ]
++ optional config.services.prismnet.enable "prismnet.service"
++ optional config.services.iam.enable "iam.service";
wantedBy = optional cfg.applyOnBoot "multi-user.target";
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart =
"${cfg.package}/bin/ultracloud-reconciler tenant-network"
+ " --config ${configPath}"
+ " --endpoint ${cfg.endpoint}"
+ " --iam-endpoint ${cfg.iamEndpoint}"
+ " --controller-principal-id ${cfg.controllerPrincipalId}"
+ optionalString cfg.prune " --prune";
};
};
systemd.paths.ultracloud-tenant-networking-apply = mkIf cfg.applyOnChange {
wantedBy = [ "multi-user.target" ];
pathConfig = {
PathChanged = configPath;
};
};
};
}