Feat/open tofu #8
@@ -26,13 +26,6 @@ resource "hcloud_firewall" "this" {
|
|||||||
source_ips = ["0.0.0.0/0", "::/0"]
|
source_ips = ["0.0.0.0/0", "::/0"]
|
||||||
}
|
}
|
||||||
|
|
||||||
rule { # mongo via Traefik tcp entrypoint
|
|
||||||
direction = "in"
|
|
||||||
protocol = "tcp"
|
|
||||||
port = "27017"
|
|
||||||
source_ips = ["0.0.0.0/0", "::/0"]
|
|
||||||
}
|
|
||||||
|
|
||||||
rule { # rustdesk hbbs
|
rule { # rustdesk hbbs
|
||||||
direction = "in"
|
direction = "in"
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
|
|||||||
@@ -60,22 +60,24 @@ architecture-beta
|
|||||||
| `prod` | Public-facing websites, Bitwarden, RustDesk, status page, prod Traefik | Private network + public firewall | `prod-storage` |
|
| `prod` | Public-facing websites, Bitwarden, RustDesk, status page, prod Traefik | Private network + public firewall | `prod-storage` |
|
||||||
| `vpn` | OpenVPN + its own Traefik | **Not** attached to the private network — kept isolated so a compromised VPN endpoint can't pivot to `dev`/`prod` | none |
|
| `vpn` | OpenVPN + its own Traefik | **Not** attached to the private network — kept isolated so a compromised VPN endpoint can't pivot to `dev`/`prod` | none |
|
||||||
|
|
||||||
`dev` and `prod` share a private Hetzner network (`10.0.1.0/24` by default) so CI/CD on `dev` can reach deployment targets on `prod` without exposing that traffic publicly. `vpn` is deliberately kept off this network. Each server has its own Hetzner Cloud Firewall (see `infra/firewalls.tf`) that only opens the ports actually used by the compose stacks running on it, plus SSH restricted to `var.allowed_ssh_source_ips`.
|
`dev` and `prod` share a private Hetzner network (`10.0.1.0/24` by default) so CI/CD on `dev` can reach deployment targets on `prod` without exposing that traffic publicly. `vpn` is deliberately kept off this network. Each server has its own Hetzner Cloud Firewall (see `infra/modules/<host>/main.tf`) that only opens the ports actually used by the compose stacks running on it, plus SSH restricted to `var.allowed_ssh_source_ips`.
|
||||||
|
|
||||||
## Repository layout
|
## Repository layout
|
||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── infra/ # OpenTofu (Terraform-compatible) config — provisions the 3 servers, network, firewalls, volumes
|
├── infra/ # OpenTofu (Terraform-compatible) config — provisions the 3 servers, network, firewalls, volumes
|
||||||
│ ├── servers.tf
|
│ ├── main.tf # root module: wires network + dev/prod/vpn modules together
|
||||||
│ ├── network.tf
|
│ ├── variables.tf # shared inputs (sizes, locations, IP ranges, SSH key name)
|
||||||
│ ├── firewalls.tf
|
│ ├── outputs.tf # pass-through outputs from each host module
|
||||||
│ ├── volumes.tf
|
│ ├── ssh.tf # looks up the SSH key already uploaded to Hetzner Cloud
|
||||||
│ ├── ssh.tf
|
│ ├── versions.tf # provider requirements
|
||||||
│ ├── variables.tf
|
│ ├── terraform.tfvars.example
|
||||||
│ ├── outputs.tf
|
│ └── modules/
|
||||||
│ ├── versions.tf
|
│ ├── network/ # shared private network + subnet (used by dev and prod)
|
||||||
│ └── terraform.tfvars.example
|
│ ├── dev/ # dev server + firewall + volume
|
||||||
|
│ ├── prod/ # prod server + firewall + volume
|
||||||
|
│ └── vpn/ # vpn server + firewall (no private network, no volume)
|
||||||
├── services/ # Docker Compose stacks, grouped by which server they run on
|
├── services/ # Docker Compose stacks, grouped by which server they run on
|
||||||
│ ├── dev/ # Gitea + CI runner + Traefik (git.luke-else.co.uk, cicd.luke-else.co.uk)
|
│ ├── dev/ # Gitea + CI runner + Traefik (git.luke-else.co.uk, cicd.luke-else.co.uk)
|
||||||
│ ├── prod/ # Websites, Bitwarden, RustDesk, status page + Traefik
|
│ ├── prod/ # Websites, Bitwarden, RustDesk, status page + Traefik
|
||||||
@@ -87,7 +89,7 @@ architecture-beta
|
|||||||
└── assets/
|
└── assets/
|
||||||
```
|
```
|
||||||
|
|
||||||
Each of `services/dev`, `services/prod`, `services/vpn` follows the same convention: one `*-docker-compose.yml` (or subdirectory, e.g. `Runners/`) per logical service, plus a `spinup.sh` / `spindown.sh` pair that brings up or tears down every stack on that host in the right order.
|
Each of `services/dev`, `services/prod`, `services/vpn` follows the same convention: one `*-docker-compose.yml` (or subdirectory, e.g. `Runners/`) per logical service, plus a `spinup.sh` / `spindown.sh` pair that brings up or tears down every stack on that host in the right order. `infra/modules/` mirrors this same dev/prod/vpn split on the provisioning side, so a given host's cloud resources and its compose stacks are easy to find side by side.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@@ -110,15 +112,14 @@ tofu plan
|
|||||||
tofu apply
|
tofu apply
|
||||||
```
|
```
|
||||||
|
|
||||||
This creates:
|
This creates, via `module.network` / `module.dev` / `module.prod` / `module.vpn` in `infra/main.tf`:
|
||||||
- `hcloud_server.dev`, `hcloud_server.prod`, `hcloud_server.vpn`
|
- `hcloud_network` + subnet, shared by `dev` and `prod` (`modules/network`)
|
||||||
- `hcloud_network.main` + subnet, shared by `dev` and `prod`
|
- one `hcloud_server` + `hcloud_firewall` per host, scoped to the ports each host actually uses (`modules/dev`, `modules/prod`, `modules/vpn`)
|
||||||
- `hcloud_firewall.dev` / `.prod` / `.vpn`, scoped to the ports each host actually uses
|
- one `hcloud_volume` each for `dev` and `prod` (`modules/dev`, `modules/prod`; `vpn` has none)
|
||||||
- `hcloud_volume.dev_storage` / `.prod_storage`
|
|
||||||
|
|
||||||
Useful outputs: `tofu output dev_ipv4`, `tofu output prod_ipv4`, `tofu output vpn_ipv4`.
|
Useful outputs: `tofu output dev_ipv4`, `tofu output prod_ipv4`, `tofu output vpn_ipv4`.
|
||||||
|
|
||||||
`terraform.tfvars` and any `*.tfvars` file are gitignored — never commit real values there. Defaults for server sizes, locations, and IP ranges live in `infra/variables.tf` and can be overridden per-environment via `terraform.tfvars`.
|
`terraform.tfvars` and any `*.tfvars` file are gitignored — never commit real values there. Defaults for server sizes, locations, and IP ranges live in `infra/variables.tf` and are passed down into the modules from `infra/main.tf`; override them per-environment via `terraform.tfvars`.
|
||||||
|
|
||||||
## Deploying the services (`services/`)
|
## Deploying the services (`services/`)
|
||||||
|
|
||||||
@@ -203,4 +204,4 @@ Then reopen the repo in VS Code with the Dev Containers extension.
|
|||||||
- Real secrets (`HCLOUD_TOKEN`, `*.tfvars`, `.env` files) must never be committed — see `.gitignore`.
|
- Real secrets (`HCLOUD_TOKEN`, `*.tfvars`, `.env` files) must never be committed — see `.gitignore`.
|
||||||
- SSH is restricted to `var.allowed_ssh_source_ips` on every host; narrow this from the default `0.0.0.0/0` once you know your own egress IP(s).
|
- SSH is restricted to `var.allowed_ssh_source_ips` on every host; narrow this from the default `0.0.0.0/0` once you know your own egress IP(s).
|
||||||
- `vpn` is intentionally excluded from the private network so that a compromised VPN endpoint cannot reach `dev` or `prod` directly.
|
- `vpn` is intentionally excluded from the private network so that a compromised VPN endpoint cannot reach `dev` or `prod` directly.
|
||||||
- Firewalls are allowlists scoped per host in `infra/firewalls.tf` — only ports actually used by that host's compose stacks (plus the cross-host private network range) are open.
|
- Firewalls are allowlists scoped per host in `infra/modules/<host>/main.tf` — only ports actually used by that host's compose stacks (plus the cross-host private network range) are open.
|
||||||
|
|||||||
Reference in New Issue
Block a user