From 4d7239e645fa9ee331012ecc19d66e5a57ad71a9 Mon Sep 17 00:00:00 2001 From: Julian Sommer <115516918+juliansommer@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:50:18 +1000 Subject: [PATCH] last doc tweaks --- docs/creating-accounts/README.md | 14 -- docs/hybrid-sync/README.md | 11 - docs/iam-users-and-policies/README.md | 192 +++++++++--------- docs/moving-aws-workload/README.md | 8 - docs/spinifex-admin-cli/README.md | 8 - .../bastion-private-subnet/README.md | 3 +- docs/terraform/nginx-webserver/README.md | 3 +- docs/terraform/s3-webapp/README.md | 3 +- docs/vpc-networking/README.md | 191 ++++++++--------- 9 files changed, 192 insertions(+), 241 deletions(-) diff --git a/docs/creating-accounts/README.md b/docs/creating-accounts/README.md index d363a4efa..ed24c40d1 100644 --- a/docs/creating-accounts/README.md +++ b/docs/creating-accounts/README.md @@ -29,14 +29,6 @@ Spinifex supports multi-tenant account isolation. Each account gets its own IAM ## Instructions -## Prerequisites - -Ensure the AWS profile is set: - -```bash -export AWS_PROFILE=spinifex -``` - ## Create Account ```bash @@ -44,12 +36,6 @@ spx admin account create --name myteam export AWS_PROFILE=spinifex-myteam ``` -## Verify - -```bash -aws sts get-caller-identity -``` - ## Troubleshooting ## Credentials Not Working diff --git a/docs/hybrid-sync/README.md b/docs/hybrid-sync/README.md index 6c61e3e53..bb02a9d06 100644 --- a/docs/hybrid-sync/README.md +++ b/docs/hybrid-sync/README.md @@ -69,14 +69,3 @@ aws s3 sync s3://local-bucket/ s3://cloud-bucket/ \ ``` Only changed or missing files will be transferred on subsequent runs. - -## Permission Errors During Sync - -Verify your AWS credentials are configured for both the source and destination: - -```bash -aws sts get-caller-identity --profile spinifex -aws sts get-caller-identity --profile default -``` - -Ensure the IAM user has `s3:GetObject`, `s3:PutObject`, and `s3:ListBucket` permissions on both buckets. diff --git a/docs/iam-users-and-policies/README.md b/docs/iam-users-and-policies/README.md index 1b3d86c78..091ad0f25 100644 --- a/docs/iam-users-and-policies/README.md +++ b/docs/iam-users-and-policies/README.md @@ -53,7 +53,6 @@ When you create an account with `spx admin account create`, Spinifex bootstraps ```bash export AWS_PROFILE=spinifex-myteam -aws sts get-caller-identity ``` ## Users @@ -68,13 +67,13 @@ The response includes the user's ARN, unique ID, and creation date: ```json { - "User": { - "UserName": "alice", - "UserId": "AIDA1A2B3C4D5E6F7890", - "Arn": "arn:aws:iam::000000000001:user/alice", - "Path": "/", - "CreateDate": "2026-03-24T10:00:00Z" - } + "User": { + "UserName": "alice", + "UserId": "AIDA1A2B3C4D5E6F7890", + "Arn": "arn:aws:iam::000000000001:user/alice", + "Path": "/", + "CreateDate": "2026-03-24T10:00:00Z" + } } ``` @@ -120,7 +119,6 @@ aws iam detach-user-policy --user-name alice \ aws iam delete-user --user-name alice ``` - ## Access Keys Access keys are how users authenticate with the AWS CLI. Each user can have up to **2 access keys** at a time, allowing key rotation without downtime. @@ -133,13 +131,13 @@ aws iam create-access-key --user-name alice ```json { - "AccessKey": { - "UserName": "alice", - "AccessKeyId": "AKIA1A2B3C4D5E6F7890ABCD", - "Status": "Active", - "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "CreateDate": "2026-03-24T10:05:00Z" - } + "AccessKey": { + "UserName": "alice", + "AccessKeyId": "AKIA1A2B3C4D5E6F7890ABCD", + "Status": "Active", + "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "CreateDate": "2026-03-24T10:05:00Z" + } } ``` @@ -153,7 +151,6 @@ Add the credentials to `~/.aws/credentials` under a new profile and set `AWS_PRO aws iam list-access-keys --user-name alice ``` - ### Rotate Access Keys Create a second key, update your configuration, then delete the old one: @@ -189,7 +186,6 @@ aws iam update-access-key --user-name alice \ --status Active ``` - ## Policies Policies are JSON documents that define what actions a user can perform on which resources. @@ -198,14 +194,14 @@ Policies are JSON documents that define what actions a user can perform on which ```json { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["ec2:RunInstances", "ec2:DescribeInstances"], - "Resource": "*" - } - ] + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["ec2:RunInstances", "ec2:DescribeInstances"], + "Resource": "*" + } + ] } ``` @@ -249,32 +245,31 @@ aws iam create-policy \ ```json { - "Policy": { - "PolicyName": "EC2ReadOnly", - "PolicyId": "ANPA1A2B3C4D5E6F7890", - "Arn": "arn:aws:iam::000000000001:policy/EC2ReadOnly", - "Path": "/", - "DefaultVersionId": "v1", - "CreateDate": "2026-03-24T10:10:00Z" - } + "Policy": { + "PolicyName": "EC2ReadOnly", + "PolicyId": "ANPA1A2B3C4D5E6F7890", + "Arn": "arn:aws:iam::000000000001:policy/EC2ReadOnly", + "Path": "/", + "DefaultVersionId": "v1", + "CreateDate": "2026-03-24T10:10:00Z" + } } ``` - ### Common Policy Examples **Full administrator access:** ```json { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - } - ] + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "*", + "Resource": "*" + } + ] } ``` @@ -282,19 +277,19 @@ aws iam create-policy \ ```json { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:PutObject", - "s3:ListBucket", - "s3:DeleteObject" - ], - "Resource": "*" - } - ] + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:ListBucket", + "s3:DeleteObject" + ], + "Resource": "*" + } + ] } ``` @@ -302,21 +297,21 @@ aws iam create-policy \ ```json { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:RunInstances", - "ec2:StartInstances", - "ec2:StopInstances", - "ec2:TerminateInstances", - "ec2:DescribeInstances", - "ec2:DescribeImages" - ], - "Resource": "*" - } - ] + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:RunInstances", + "ec2:StartInstances", + "ec2:StopInstances", + "ec2:TerminateInstances", + "ec2:DescribeInstances", + "ec2:DescribeImages" + ], + "Resource": "*" + } + ] } ``` @@ -324,14 +319,14 @@ aws iam create-policy \ ```json { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Deny", - "Action": "ec2:TerminateInstances", - "Resource": "*" - } - ] + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Deny", + "Action": "ec2:TerminateInstances", + "Resource": "*" + } + ] } ``` @@ -352,7 +347,6 @@ aws iam get-policy-version \ --version-id v1 ``` - ### List Policies ```bash @@ -368,7 +362,6 @@ aws iam delete-policy \ --policy-arn arn:aws:iam::000000000001:policy/EC2ReadOnly ``` - ## Attaching Policies Policies have no effect until attached to a user. @@ -380,7 +373,6 @@ aws iam attach-user-policy --user-name alice \ --policy-arn arn:aws:iam::000000000001:policy/EC2ReadOnly ``` - ### List a User's Policies ```bash @@ -453,24 +445,24 @@ AWS_PROFILE=spinifex-carol aws ec2 terminate-instances --instance-ids i-123 ## Command Reference -| Command | Key Parameters | Notes | -|---------|---------------|-------| -| `create-user` | `--user-name`, `--path` | Path default: `/` | -| `get-user` | `--user-name` | | -| `list-users` | `--path-prefix` | | -| `delete-user` | `--user-name` | Must have no keys or policies | -| `create-access-key` | `--user-name` | Max 2 per user; secret shown once | -| `list-access-keys` | `--user-name` | | -| `delete-access-key` | `--user-name`, `--access-key-id` | | -| `update-access-key` | `--user-name`, `--access-key-id`, `--status` | `Active` or `Inactive` | -| `create-policy` | `--policy-name`, `--policy-document` | Max 6144 bytes | -| `get-policy` | `--policy-arn` | Metadata only | -| `get-policy-version` | `--policy-arn`, `--version-id` | Use `v1`; includes document | -| `list-policies` | | | -| `delete-policy` | `--policy-arn` | Must not be attached | -| `attach-user-policy` | `--user-name`, `--policy-arn` | Idempotent | -| `detach-user-policy` | `--user-name`, `--policy-arn` | | -| `list-attached-user-policies` | `--user-name` | | +| Command | Key Parameters | Notes | +| ----------------------------- | -------------------------------------------- | --------------------------------- | +| `create-user` | `--user-name`, `--path` | Path default: `/` | +| `get-user` | `--user-name` | | +| `list-users` | `--path-prefix` | | +| `delete-user` | `--user-name` | Must have no keys or policies | +| `create-access-key` | `--user-name` | Max 2 per user; secret shown once | +| `list-access-keys` | `--user-name` | | +| `delete-access-key` | `--user-name`, `--access-key-id` | | +| `update-access-key` | `--user-name`, `--access-key-id`, `--status` | `Active` or `Inactive` | +| `create-policy` | `--policy-name`, `--policy-document` | Max 6144 bytes | +| `get-policy` | `--policy-arn` | Metadata only | +| `get-policy-version` | `--policy-arn`, `--version-id` | Use `v1`; includes document | +| `list-policies` | | | +| `delete-policy` | `--policy-arn` | Must not be attached | +| `attach-user-policy` | `--user-name`, `--policy-arn` | Idempotent | +| `detach-user-policy` | `--user-name`, `--policy-arn` | | +| `list-attached-user-policies` | `--user-name` | | ## Troubleshooting diff --git a/docs/moving-aws-workload/README.md b/docs/moving-aws-workload/README.md index 3a9a5b409..af5614c28 100644 --- a/docs/moving-aws-workload/README.md +++ b/docs/moving-aws-workload/README.md @@ -37,14 +37,6 @@ Spinifex provides drop-in compatibility with AWS APIs, making it possible to mig ## Instructions -## Prerequisites - -Ensure the AWS profile is set: - -```bash -export AWS_PROFILE=spinifex -``` - ## Configure AWS CLI ```bash diff --git a/docs/spinifex-admin-cli/README.md b/docs/spinifex-admin-cli/README.md index 7b568d42c..f4619ddf0 100644 --- a/docs/spinifex-admin-cli/README.md +++ b/docs/spinifex-admin-cli/README.md @@ -33,14 +33,6 @@ All services in the Spinifex platform are managed through this single binary. ## Instructions -## Prerequisites - -Ensure the AWS profile is set: - -```bash -export AWS_PROFILE=spinifex -``` - ## Account Management ```bash diff --git a/docs/terraform/bastion-private-subnet/README.md b/docs/terraform/bastion-private-subnet/README.md index 6dff47058..ba9c4be82 100644 --- a/docs/terraform/bastion-private-subnet/README.md +++ b/docs/terraform/bastion-private-subnet/README.md @@ -1,8 +1,7 @@ --- -title: "Terraform: Bastion with Private Subnet" +title: "Bastion with Private Subnet" description: "Deploy a bastion host to access an isolated private compute instance with no internet connectivity." category: "Terraform Workbooks" -badge: "Workbook" tags: - terraform - bastion diff --git a/docs/terraform/nginx-webserver/README.md b/docs/terraform/nginx-webserver/README.md index 126fd4b11..2e8d0ad30 100644 --- a/docs/terraform/nginx-webserver/README.md +++ b/docs/terraform/nginx-webserver/README.md @@ -1,8 +1,7 @@ --- -title: "Terraform: Nginx Web Server" +title: "Nginx Web Server" description: "Deploy a VPC with a public subnet and an EC2 instance running Nginx using Terraform on Spinifex." category: "Terraform Workbooks" -badge: "Workbook" tags: - terraform - nginx diff --git a/docs/terraform/s3-webapp/README.md b/docs/terraform/s3-webapp/README.md index ff39a99f0..3ee0ae089 100644 --- a/docs/terraform/s3-webapp/README.md +++ b/docs/terraform/s3-webapp/README.md @@ -1,8 +1,7 @@ --- -title: "Terraform: S3-Backed Web App" +title: "S3-Backed Web App" description: "Deploy a Flask file-sharing web application backed by S3 (Predastore) using Terraform on Spinifex." category: "Terraform Workbooks" -badge: "Workbook" tags: - terraform - s3 diff --git a/docs/vpc-networking/README.md b/docs/vpc-networking/README.md index 131aa5469..44dfb047a 100644 --- a/docs/vpc-networking/README.md +++ b/docs/vpc-networking/README.md @@ -23,26 +23,18 @@ Instances can operate in two modes: **private** (overlay-only, no WAN access) or ## Instructions -## Prerequisites - -Ensure the AWS profile is set: - -```bash -export AWS_PROFILE=spinifex -``` - ## How It Works Spinifex maps AWS VPC concepts directly to OVN constructs: -| AWS Concept | OVN Construct | What It Does | -|-------------|---------------|--------------| -| VPC | Logical Router | Isolates tenant networks, routes between subnets | -| Subnet | Logical Switch + DHCP | L2 broadcast domain with automatic IP assignment | -| ENI | Logical Switch Port | Per-instance network interface with MAC/IP binding | -| Internet Gateway | External Switch + NAT | Connects VPC router to physical WAN | -| Security Group | Port Group + ACLs | Stateful firewall rules enforced in OVS datapath | -| Elastic IP | `dnat_and_snat` NAT rule | Static 1:1 NAT between public and private IP | +| AWS Concept | OVN Construct | What It Does | +| ---------------- | ------------------------ | -------------------------------------------------- | +| VPC | Logical Router | Isolates tenant networks, routes between subnets | +| Subnet | Logical Switch + DHCP | L2 broadcast domain with automatic IP assignment | +| ENI | Logical Switch Port | Per-instance network interface with MAC/IP binding | +| Internet Gateway | External Switch + NAT | Connects VPC router to physical WAN | +| Security Group | Port Group + ACLs | Stateful firewall rules enforced in OVS datapath | +| Elastic IP | `dnat_and_snat` NAT rule | Static 1:1 NAT between public and private IP | ## Network Path @@ -120,14 +112,14 @@ Inbound: 203.0.113.10 ──→ DNAT ──→ arrives at 10.0.1.5 ## Comparison -| | Private Subnet | Public Subnet | -|---|---|---| -| Private IP | Yes | Yes | -| Public IP | No | Auto-assigned from pool | -| Outbound internet | Only if VPC has IGW (shared SNAT) | Yes (own public IP via SNAT) | -| Inbound from WAN | No | Yes (via 1:1 NAT to public IP) | -| Instance sees public IP? | N/A | No — only sees private IP | -| Elastic IP support | Only if explicitly associated | Yes | +| | Private Subnet | Public Subnet | +| ------------------------ | --------------------------------- | ------------------------------ | +| Private IP | Yes | Yes | +| Public IP | No | Auto-assigned from pool | +| Outbound internet | Only if VPC has IGW (shared SNAT) | Yes (own public IP via SNAT) | +| Inbound from WAN | No | Yes (via 1:1 NAT to public IP) | +| Instance sees public IP? | N/A | No — only sees private IP | +| Elastic IP support | Only if explicitly associated | Yes | ## External Connectivity Modes @@ -221,14 +213,14 @@ is identical: ### Choosing Static vs DHCP -| | Static Range | DHCP Source | -|---|---|---| -| **Public IPs from** | Admin-defined `range_start`..`range_end` | Router's DHCP server | -| **IP predictability** | You know the exact range | Router assigns whatever is available | -| **Setup effort** | Must reserve range, shrink router DHCP scope | Just set `source = "dhcp"` | -| **Dependency** | None | Requires `dhclient` on host, working router DHCP | -| **Best for** | Datacenters, ISP blocks, production | Homelabs, dev environments, shared networks | -| **Capacity** | Exact: `range_end - range_start` IPs | Limited by router's DHCP pool size | +| | Static Range | DHCP Source | +| --------------------- | -------------------------------------------- | ------------------------------------------------ | +| **Public IPs from** | Admin-defined `range_start`..`range_end` | Router's DHCP server | +| **IP predictability** | You know the exact range | Router assigns whatever is available | +| **Setup effort** | Must reserve range, shrink router DHCP scope | Just set `source = "dhcp"` | +| **Dependency** | None | Requires `dhclient` on host, working router DHCP | +| **Best for** | Datacenters, ISP blocks, production | Homelabs, dev environments, shared networks | +| **Capacity** | Exact: `range_end - range_start` IPs | Limited by router's DHCP pool size | Both support the same AWS features: public subnets, Elastic IPs, security groups, DescribeInstances showing public IPs. @@ -265,16 +257,16 @@ communicate within their VPC. ## Mode Comparison -| Capability | `pool` (static) | `pool` (dhcp) | `nat` | Disabled | -|------------|-----------------|---------------|-------|----------| -| Outbound internet | Yes | Yes | Yes | No | -| Inbound from WAN | Yes (1:1 NAT) | Yes (1:1 NAT) | No | No | -| Public subnets | Yes | Yes | No | No | -| Auto-assign public IPs | Yes | Yes | No | No | -| Elastic IPs | Yes | Yes | No | No | -| DescribeInstances shows public IP | Yes | Yes | No | No | -| Admin must reserve IP range | Yes | No | No | No | -| Needs router DHCP | No | Yes | Optional | No | +| Capability | `pool` (static) | `pool` (dhcp) | `nat` | Disabled | +| --------------------------------- | --------------- | ------------- | -------- | -------- | +| Outbound internet | Yes | Yes | Yes | No | +| Inbound from WAN | Yes (1:1 NAT) | Yes (1:1 NAT) | No | No | +| Public subnets | Yes | Yes | No | No | +| Auto-assign public IPs | Yes | Yes | No | No | +| Elastic IPs | Yes | Yes | No | No | +| DescribeInstances shows public IP | Yes | Yes | No | No | +| Admin must reserve IP range | Yes | No | No | No | +| Needs router DHCP | No | Yes | Optional | No | If you start with `nat` and later need public subnets, switch to `pool` and define a range (or use `source = "dhcp"`) — no data migration needed. @@ -294,10 +286,10 @@ the best approach: Every Spinifex node has two OVS bridges: -| Bridge | Purpose | Ports | -|--------|---------|-------| -| `br-int` | VM overlay traffic (Geneve tunnels) | VM TAP devices, tunnel ports | -| `br-wan` | WAN uplink for public subnet traffic | macvlan on WAN NIC | +| Bridge | Purpose | Ports | +| -------- | ------------------------------------ | ---------------------------- | +| `br-int` | VM overlay traffic (Geneve tunnels) | VM TAP devices, tunnel ports | +| `br-wan` | WAN uplink for public subnet traffic | macvlan on WAN NIC | `br-int` is always created by `setup-ovn.sh`. `br-wan` is created only when a WAN bridge is configured (auto-detected or via `--wan-bridge`, required for public subnets / external connectivity). @@ -330,6 +322,7 @@ used for SSH or management. Datacenter servers with separate management + public NICs. Homelab hosts with 2+ NICs. **Benefits over macvlan:** + - **Distributed NAT**: DNAT processed on the VM's own chassis (no hairpin) - **No MAC workarounds**: OVS sees all frames, any MAC - **Host can reach VMs**: No macvlan parent↔child isolation @@ -362,6 +355,7 @@ WAN NIC (host IP — unchanged) edge deployments. Adding the NIC directly to OVS would break host connectivity. **Limitations:** + - Centralized NAT only (macvlan filters unicast not matching its MAC) - Host cannot reach VMs at their public IPs (macvlan isolation) - vpcd must align macvlan MAC with OVN router MAC on startup @@ -373,23 +367,23 @@ sudo setup-ovn.sh --macvlan --wan-iface=eth0 ### Mode Comparison -| Capability | Direct bridge | Macvlan | -|------------|--------------|---------| -| NAT mode | Distributed (local DNAT) | Centralized (gateway hairpin) | -| Multi-node external perf | Optimal | Hairpin through gateway chassis | -| Host → VM public IP | Works | Blocked (kernel isolation) | -| MAC alignment needed | No | Yes (fragile startup step) | -| Gratuitous ARPs needed | No | Yes (`nat-addresses=router`) | -| Works on single-NIC | No (breaks SSH) | Yes | -| DHCP lease stability | Clean | Fragile (MAC changes post-lease) | +| Capability | Direct bridge | Macvlan | +| ------------------------ | ------------------------ | -------------------------------- | +| NAT mode | Distributed (local DNAT) | Centralized (gateway hairpin) | +| Multi-node external perf | Optimal | Hairpin through gateway chassis | +| Host → VM public IP | Works | Blocked (kernel isolation) | +| MAC alignment needed | No | Yes (fragile startup step) | +| Gratuitous ARPs needed | No | Yes (`nat-addresses=router`) | +| Works on single-NIC | No (breaks SSH) | Yes | +| DHCP lease stability | Clean | Fragile (MAC changes post-lease) | ### How setup-ovn.sh Decides -| Flags | Result | -|-------|--------| -| `--wan-bridge=br-wan --wan-iface=eth1` | Direct bridge: NIC added to br-wan | -| `--macvlan --wan-iface=eth0` | Macvlan: sub-interface created, added to br-wan | -| (no WAN bridge) | Only br-int created, no WAN connectivity | +| Flags | Result | +| -------------------------------------- | ----------------------------------------------- | +| `--wan-bridge=br-wan --wan-iface=eth1` | Direct bridge: NIC added to br-wan | +| `--macvlan --wan-iface=eth0` | Macvlan: sub-interface created, added to br-wan | +| (no WAN bridge) | Only br-int created, no WAN connectivity | ### Setup (Macvlan Mode) @@ -410,12 +404,12 @@ macvlan in `bridge` mode creates a virtual interface that shares the parent NIC' physical wire but has its own MAC address. The Linux kernel blocks direct L2 frames between a parent interface and its macvlan children. -| Path | Works? | Why | -|------|--------|-----| -| VM → internet | Yes | SNAT through OVN router → macvlan → WAN NIC → WAN | -| LAN device → VM public IP | Yes | LAN → WAN NIC → macvlan → br-wan → OVN | -| Host → VM public IP | No | macvlan isolation (kernel blocks parent↔child) | -| Host → VM private IP | Yes | Overlay via br-int (unrelated to br-wan) | +| Path | Works? | Why | +| ------------------------- | ------ | ------------------------------------------------- | +| VM → internet | Yes | SNAT through OVN router → macvlan → WAN NIC → WAN | +| LAN device → VM public IP | Yes | LAN → WAN NIC → macvlan → br-wan → OVN | +| Host → VM public IP | No | macvlan isolation (kernel blocks parent↔child) | +| Host → VM private IP | Yes | Overlay via br-int (unrelated to br-wan) | ## Per-Node Configuration @@ -504,11 +498,11 @@ spinifex.toml external_mode = "pool" # "pool", "nat", or "" (disabled) ``` -| Value | Behavior | -|-------|----------| -| `"pool"` | Full public networking — public subnets, auto-assign, Elastic IPs | -| `"nat"` | Outbound-only SNAT — all VMs share one external IP | -| `""` / omitted | Overlay-only — no external connectivity | +| Value | Behavior | +| -------------- | ----------------------------------------------------------------- | +| `"pool"` | Full public networking — public subnets, auto-assign, Elastic IPs | +| `"nat"` | Outbound-only SNAT — all VMs share one external IP | +| `""` / omitted | Overlay-only — no external connectivity | ## IP Pools: network.external_pools @@ -531,18 +525,18 @@ dns_servers = ["8.8.8.8"] # DNS for VMs (optional) ### Field Details -| Field | Required | Description | -|-------|----------|-------------| -| `name` | Yes | Unique pool name. Used as NATS KV key and in `AllocateAddress`. | -| `source` | No | IP source: `"static"` (default) uses `range_start`/`range_end`. `"dhcp"` obtains IPs from the router's DHCP server on each VM launch. | -| `range_start` | Static only | First IP in the range. First IP is reserved for OVN gateway SNAT (unless `gateway_ip` overrides). | -| `range_end` | Static only | Last IP in the range. | -| `gateway` | Yes | Physical router/switch — the WAN default gateway. OVN sets `0.0.0.0/0 → gateway`. | -| `gateway_ip` | NAT mode | Static IP for OVN router SNAT. In pool mode, defaults to `range_start` (static) or first DHCP lease (dhcp). In NAT mode, this is the single external IP all VMs share. | -| `prefix_len` | Yes | Subnet mask for the external network (e.g., 24 = /24). | -| `region` | No | Scopes pool to a region. Instances in this region prefer this pool. | -| `az` | No | Scopes pool to an AZ. More specific than region. | -| `dns_servers` | No | DNS servers propagated to VMs via OVN DHCP. | +| Field | Required | Description | +| ------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Yes | Unique pool name. Used as NATS KV key and in `AllocateAddress`. | +| `source` | No | IP source: `"static"` (default) uses `range_start`/`range_end`. `"dhcp"` obtains IPs from the router's DHCP server on each VM launch. | +| `range_start` | Static only | First IP in the range. First IP is reserved for OVN gateway SNAT (unless `gateway_ip` overrides). | +| `range_end` | Static only | Last IP in the range. | +| `gateway` | Yes | Physical router/switch — the WAN default gateway. OVN sets `0.0.0.0/0 → gateway`. | +| `gateway_ip` | NAT mode | Static IP for OVN router SNAT. In pool mode, defaults to `range_start` (static) or first DHCP lease (dhcp). In NAT mode, this is the single external IP all VMs share. | +| `prefix_len` | Yes | Subnet mask for the external network (e.g., 24 = /24). | +| `region` | No | Scopes pool to a region. Instances in this region prefer this pool. | +| `az` | No | Scopes pool to an AZ. More specific than region. | +| `dns_servers` | No | DNS servers propagated to VMs via OVN DHCP. | ### Why range_start/range_end Instead of CIDR? @@ -569,8 +563,8 @@ ovn_sb_addr = "tcp:10.1.3.181:6642" # OVN Southbound DB external_interface = "eth1" # WAN NIC name ``` -| Field | Description | -|-------|-------------| +| Field | Description | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `external_interface` | Physical NIC for WAN traffic. A macvlan sub-interface is created on this NIC for br-wan. Different servers may have different names (eth1, eno2, enp3s0, bond0). | ## Pool Selection Logic @@ -596,8 +590,12 @@ name. Allocation uses CAS (Compare-And-Set) for lock-free concurrent access: "range_start": "192.168.1.150", "range_end": "192.168.1.250", "allocated": { - "192.168.1.150": {"type": "gateway"}, - "192.168.1.151": {"type": "auto_assign", "eni_id": "eni-abc", "instance_id": "i-123"} + "192.168.1.150": { "type": "gateway" }, + "192.168.1.151": { + "type": "auto_assign", + "eni_id": "eni-abc", + "instance_id": "i-123" + } } } ``` @@ -782,20 +780,21 @@ on the port group control traffic: ## Default Security Group Every VPC gets a default security group that: + - Allows all inbound from instances in the same security group - Allows all outbound - Denies all other inbound ## AWS Rule → OVN ACL Translation -| AWS Security Group Rule | OVN ACL Match | -|------------------------|---------------| -| Ingress TCP/22 from 0.0.0.0/0 | `outport == @sg && ip4 && tcp.dst == 22` | +| AWS Security Group Rule | OVN ACL Match | +| ------------------------------- | ------------------------------------------------------------------ | +| Ingress TCP/22 from 0.0.0.0/0 | `outport == @sg && ip4 && tcp.dst == 22` | | Ingress TCP/443 from 10.0.0.0/8 | `outport == @sg && ip4 && tcp.dst == 443 && ip4.src == 10.0.0.0/8` | -| Ingress ALL from sg-other | `outport == @sg && ip4 && ip4.src == $sg_other_ip4` | -| Ingress ICMP from anywhere | `outport == @sg && ip4 && icmp4` | -| Egress ALL to 0.0.0.0/0 | `inport == @sg && ip4` | -| Default deny inbound | `outport == @sg && ip4` (priority 900, action=drop) | +| Ingress ALL from sg-other | `outport == @sg && ip4 && ip4.src == $sg_other_ip4` | +| Ingress ICMP from anywhere | `outport == @sg && ip4 && icmp4` | +| Egress ALL to 0.0.0.0/0 | `inport == @sg && ip4` | +| Default deny inbound | `outport == @sg && ip4` (priority 900, action=drop) | ## Example: Allow SSH + HTTP @@ -1132,18 +1131,21 @@ ip route show ### Instance Has No Public IP 1. Check subnet has `MapPublicIpOnLaunch`: + ```bash aws ec2 describe-subnets --subnet-ids $SUBNET \ --query 'Subnets[0].MapPublicIpOnLaunch' ``` 2. Check IGW is attached: + ```bash aws ec2 describe-internet-gateways \ --filters Name=attachment.vpc-id,Values=$VPC ``` 3. Check external IP pool: + ```bash nats kv get spinifex-external-ipam wan ``` @@ -1280,6 +1282,7 @@ sudo systemctl start ovn-central ovn-controller `ovn-sbctl show`. Gateway traffic (SNAT/DNAT) doesn't work. **Cause:** Three names must align: + 1. OVS `external_ids:system-id` → what ovn-controller registers as 2. OVN SB `Chassis.name` → what actually exists in the SB DB 3. vpcd's chassis name → `chassis-{nodeName}` from config