Switch to terraform

This commit is contained in:
Prox
2026-02-15 18:37:15 +02:00
commit a7062b43ab
70 changed files with 6063 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
name: Terraform
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
TF_VAR_netbird_token: ${{ secrets.NETBIRD_TOKEN }}
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.7.0
- name: Terraform Init
run: terraform init
- name: Terraform Format Check
run: terraform fmt -check
continue-on-error: true
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
if: github.event_name == 'pull_request'
run: terraform plan -no-color
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve
- name: Commit state changes
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: |
git config user.name "Terraform CI"
git config user.email "ci@localhost"
git add terraform.tfstate terraform.tfstate.backup 2>/dev/null || true
if ! git diff --staged --quiet; then
git commit -m "chore: update terraform state [skip ci]"
git push
fi

12
terraform/.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
# Terraform
.terraform/
*.tfplan
crash.log
crash.*.log
# Secrets (tfvars contains the PAT token)
terraform.tfvars
*.auto.tfvars
# State files are committed for this POC (single operator)
# For production, use remote backend instead

25
terraform/.terraform.lock.hcl generated Normal file
View File

@@ -0,0 +1,25 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/netbirdio/netbird" {
version = "0.0.8"
constraints = "~> 0.0.8"
hashes = [
"h1:2EOFY+2GNGCZJKfzUxIRtC9nSzDPpaqUE2urhgaf/Ys=",
"zh:0871acec1ec4d73453f68210b2bdc7062e987f610c1fb98b9db793744df86f12",
"zh:16ab23609fa36e8fde7ffefbd2b3213abf904f5128981718eb9336895dbd1732",
"zh:18fac816d51fcf160825c2fd138aba898bc14720bd5620b916c8f969f210753a",
"zh:3c64aa00ed10c15834af8a59187268cc9cd263862c4a4f3478f8db0f8478b4f0",
"zh:503430b1fde77d01e5b8a0843f75212d414f2c5613bf3788118612ea97260f33",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:8be272b0b1600cfc7849d66921b403fef0f35b3236eba7886766724ad0693220",
"zh:99100210da2c127ad0a5440983ad3e0c3211ec2ee23d61bff334ae5e366a97fc",
"zh:9dbc18a8d15c8af494e8534eb080fea16265858733250a6f6fcaac86ab1267a7",
"zh:c8300eb51406998d72c6dced64df83739c5cb59773575c3a75badcbf6122cd34",
"zh:dcef1377ff20c18353c88e350f353418ba3faca0d1d07f3cb828146c09ca3e5f",
"zh:e0366b9ef2929fc4d7ab08e888196e2b005eb29b151d25a6644be001f62af5b9",
"zh:e8954bf072bafaf61dd3fad76aab927109e8538c1dcab93d8415be13a764e394",
"zh:edbd0560a215bec8366d239640fbd6516ff39d211f611fda1cd456f759bc1f4b",
"zh:f349d5e7ddc1f6e7d9f97bbfe7ade9cb7a3369b8d54d4389d5cb863f7a7adcb8",
]
}

86
terraform/README.md Normal file
View File

@@ -0,0 +1,86 @@
# NetBird IaC
Terraform configuration for managing NetBird VPN resources via GitOps.
## Resources Managed
- **Groups:** ground-stations, pilots, operators, fusion-servers
- **Policies:** Access control between groups
- **Setup Keys:** For peer enrollment
## Usage
### Making Changes
1. Edit the relevant `.tf` file
2. Create a PR
3. CI runs `terraform plan` - review the changes
4. Merge PR
5. CI runs `terraform apply` - changes applied
### Adding a New Group
```hcl
# groups.tf
resource "netbird_group" "new_team" {
name = "new-team"
}
```
### Adding a Setup Key (Per-Ticket)
```hcl
# setup_keys.tf
resource "netbird_setup_key" "ticket_1234_pilot" {
name = "ticket-1234-pilot-ivanov"
type = "one-off"
auto_groups = [netbird_group.pilots.id]
usage_limit = 1
ephemeral = false
}
# outputs.tf
output "ticket_1234_pilot_key" {
value = netbird_setup_key.ticket_1234_pilot.key
sensitive = true
}
```
### Retrieving Setup Keys
After apply, retrieve keys locally:
```bash
terraform output -raw gs_setup_key
terraform output -raw pilot_setup_key
```
## Local Development
```bash
# Create tfvars (copy from example)
cp terraform.tfvars.example terraform.tfvars
# Edit with your NetBird PAT
# Init and plan
terraform init
terraform plan
# Apply (be careful!)
terraform apply
```
## CI/CD
Configured in `.gitea/workflows/terraform.yml`:
- PR: `terraform plan`
- Merge to main: `terraform apply`
Required secrets in Gitea:
- `NETBIRD_TOKEN`: NetBird PAT
## State Management
State is committed to git (`terraform.tfstate`). This is acceptable for single-operator scenarios but not recommended for production with multiple operators.
For production, configure a remote backend (S3, Terraform Cloud, etc.).

16
terraform/groups.tf Normal file
View File

@@ -0,0 +1,16 @@
# Groups matching Achilles network structure
resource "netbird_group" "ground_stations" {
name = "ground-stations"
}
resource "netbird_group" "pilots" {
name = "pilots"
}
resource "netbird_group" "operators" {
name = "operators"
}
resource "netbird_group" "fusion_servers" {
name = "fusion-servers"
}

13
terraform/main.tf Normal file
View File

@@ -0,0 +1,13 @@
terraform {
required_providers {
netbird = {
source = "netbirdio/netbird"
version = "~> 0.0.8"
}
}
}
provider "netbird" {
management_url = var.netbird_management_url
token = var.netbird_token
}

18
terraform/outputs.tf Normal file
View File

@@ -0,0 +1,18 @@
output "gs_setup_key" {
value = netbird_setup_key.gs_onboarding.key
sensitive = true
}
output "pilot_setup_key" {
value = netbird_setup_key.pilot_onboarding.key
sensitive = true
}
output "group_ids" {
value = {
ground_stations = netbird_group.ground_stations.id
pilots = netbird_group.pilots.id
operators = netbird_group.operators.id
fusion_servers = netbird_group.fusion_servers.id
}
}

52
terraform/policies.tf Normal file
View File

@@ -0,0 +1,52 @@
# Access policies for Achilles network
resource "netbird_policy" "pilot_to_gs" {
name = "pilot-to-ground-station"
description = "Allow pilots to connect to ground stations"
enabled = true
rule {
name = "pilot-gs-access"
enabled = true
sources = [netbird_group.pilots.id]
destinations = [netbird_group.ground_stations.id]
bidirectional = true
protocol = "all"
action = "accept"
}
}
resource "netbird_policy" "operator_full_access" {
name = "operator-full-access"
description = "Operators can access all network resources"
enabled = true
rule {
name = "operator-all"
enabled = true
sources = [netbird_group.operators.id]
destinations = [
netbird_group.ground_stations.id,
netbird_group.pilots.id,
netbird_group.fusion_servers.id
]
bidirectional = true
protocol = "all"
action = "accept"
}
}
resource "netbird_policy" "fusion_to_gs" {
name = "fusion-to-ground-station"
description = "Fusion servers coordinate with ground stations"
enabled = true
rule {
name = "fusion-gs"
enabled = true
sources = [netbird_group.fusion_servers.id]
destinations = [netbird_group.ground_stations.id]
bidirectional = true
protocol = "all"
action = "accept"
}
}

17
terraform/setup_keys.tf Normal file
View File

@@ -0,0 +1,17 @@
# Setup keys for peer onboarding
resource "netbird_setup_key" "gs_onboarding" {
name = "ground-station-onboarding"
type = "reusable"
auto_groups = [netbird_group.ground_stations.id]
usage_limit = 0 # unlimited
ephemeral = false
}
# Comment to trigger CI
resource "netbird_setup_key" "pilot_onboarding" {
name = "pilot-onboarding"
type = "reusable"
auto_groups = [netbird_group.pilots.id]
usage_limit = 0
ephemeral = false
}

343
terraform/terraform.tfstate Normal file
View File

@@ -0,0 +1,343 @@
{
"version": 4,
"terraform_version": "1.14.4",
"serial": 17,
"lineage": "2e6257d6-c04c-6864-63e8-38721dda9040",
"outputs": {
"group_ids": {
"value": {
"fusion_servers": "d68tmmml93fs73c93ek0",
"ground_stations": "d68tmmml93fs73c93ejg",
"operators": "d68tmmml93fs73c93el0",
"pilots": "d68tmmml93fs73c93ekg"
},
"type": [
"object",
{
"fusion_servers": "string",
"ground_stations": "string",
"operators": "string",
"pilots": "string"
}
]
},
"gs_setup_key": {
"value": "A19E165B-A6EB-4494-AA1D-F85D3EA9D382",
"type": "string",
"sensitive": true
},
"pilot_setup_key": {
"value": "0267D2E9-E4A5-451C-A1BC-513F32A9FCD8",
"type": "string",
"sensitive": true
}
},
"resources": [
{
"mode": "managed",
"type": "netbird_group",
"name": "fusion_servers",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "d68tmmml93fs73c93ek0",
"issued": "api",
"name": "fusion-servers",
"peers": [],
"resources": []
},
"sensitive_attributes": [],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_group",
"name": "ground_stations",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "d68tmmml93fs73c93ejg",
"issued": "api",
"name": "ground-stations",
"peers": [],
"resources": []
},
"sensitive_attributes": [],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_group",
"name": "operators",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "d68tmmml93fs73c93el0",
"issued": "api",
"name": "operators",
"peers": [],
"resources": []
},
"sensitive_attributes": [],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_group",
"name": "pilots",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "d68tmmml93fs73c93ekg",
"issued": "api",
"name": "pilots",
"peers": [],
"resources": []
},
"sensitive_attributes": [],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_policy",
"name": "fusion_to_gs",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"description": "Fusion servers coordinate with ground stations",
"enabled": true,
"id": "d68tmmul93fs73c93en0",
"name": "fusion-to-ground-station",
"rule": [
{
"action": "accept",
"bidirectional": true,
"description": null,
"destination_resource": null,
"destinations": [
"d68tmmml93fs73c93ejg"
],
"enabled": true,
"id": "d68tmmul93fs73c93en0",
"name": "fusion-gs",
"port_ranges": null,
"ports": null,
"protocol": "all",
"source_resource": null,
"sources": [
"d68tmmml93fs73c93ek0"
]
}
],
"source_posture_checks": null
},
"sensitive_attributes": [],
"identity_schema_version": 0,
"dependencies": [
"netbird_group.fusion_servers",
"netbird_group.ground_stations"
]
}
]
},
{
"mode": "managed",
"type": "netbird_policy",
"name": "operator_full_access",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"description": "Operators can access all network resources",
"enabled": true,
"id": "d68tmmul93fs73c93eng",
"name": "operator-full-access",
"rule": [
{
"action": "accept",
"bidirectional": true,
"description": null,
"destination_resource": null,
"destinations": [
"d68tmmml93fs73c93ejg",
"d68tmmml93fs73c93ekg",
"d68tmmml93fs73c93ek0"
],
"enabled": true,
"id": "d68tmmul93fs73c93eng",
"name": "operator-all",
"port_ranges": null,
"ports": null,
"protocol": "all",
"source_resource": null,
"sources": [
"d68tmmml93fs73c93el0"
]
}
],
"source_posture_checks": null
},
"sensitive_attributes": [],
"identity_schema_version": 0,
"dependencies": [
"netbird_group.fusion_servers",
"netbird_group.ground_stations",
"netbird_group.operators",
"netbird_group.pilots"
]
}
]
},
{
"mode": "managed",
"type": "netbird_policy",
"name": "pilot_to_gs",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"description": "Allow pilots to connect to ground stations",
"enabled": true,
"id": "d68tmmul93fs73c93epg",
"name": "pilot-to-ground-station",
"rule": [
{
"action": "accept",
"bidirectional": true,
"description": null,
"destination_resource": null,
"destinations": [
"d68tmmml93fs73c93ejg"
],
"enabled": true,
"id": "d68tmmul93fs73c93epg",
"name": "pilot-gs-access",
"port_ranges": null,
"ports": null,
"protocol": "all",
"source_resource": null,
"sources": [
"d68tmmml93fs73c93ekg"
]
}
],
"source_posture_checks": null
},
"sensitive_attributes": [],
"identity_schema_version": 0,
"dependencies": [
"netbird_group.ground_stations",
"netbird_group.pilots"
]
}
]
},
{
"mode": "managed",
"type": "netbird_setup_key",
"name": "gs_onboarding",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"allow_extra_dns_labels": false,
"auto_groups": [
"d68tmmml93fs73c93ejg"
],
"ephemeral": false,
"expires": "0001-01-01T00:00:00Z",
"expiry_seconds": 0,
"id": "d68v8pml93fs73c93itg",
"key": "A19E165B-A6EB-4494-AA1D-F85D3EA9D382",
"last_used": "0001-01-01T00:00:00Z",
"name": "ground-station-onboarding",
"revoked": false,
"state": "valid",
"type": "reusable",
"updated_at": "2026-02-15T16:29:26Z",
"usage_limit": 0,
"used_times": 0,
"valid": true
},
"sensitive_attributes": [
[
{
"type": "get_attr",
"value": "key"
}
]
],
"identity_schema_version": 0,
"dependencies": [
"netbird_group.ground_stations"
]
}
]
},
{
"mode": "managed",
"type": "netbird_setup_key",
"name": "pilot_onboarding",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"allow_extra_dns_labels": false,
"auto_groups": [
"d68tmmml93fs73c93ekg"
],
"ephemeral": false,
"expires": "0001-01-01T00:00:00Z",
"expiry_seconds": 0,
"id": "d68v8pml93fs73c93isg",
"key": "0267D2E9-E4A5-451C-A1BC-513F32A9FCD8",
"last_used": "0001-01-01T00:00:00Z",
"name": "pilot-onboarding",
"revoked": false,
"state": "valid",
"type": "reusable",
"updated_at": "2026-02-15T16:29:26Z",
"usage_limit": 0,
"used_times": 0,
"valid": true
},
"sensitive_attributes": [
[
{
"type": "get_attr",
"value": "key"
}
]
],
"identity_schema_version": 0,
"dependencies": [
"netbird_group.pilots"
]
}
]
}
],
"check_results": null
}

View File

@@ -0,0 +1,299 @@
{
"version": 4,
"terraform_version": "1.14.4",
"serial": 9,
"lineage": "2e6257d6-c04c-6864-63e8-38721dda9040",
"outputs": {
"group_ids": {
"value": {
"fusion_servers": "d68tmmml93fs73c93ek0",
"ground_stations": "d68tmmml93fs73c93ejg",
"operators": "d68tmmml93fs73c93el0",
"pilots": "d68tmmml93fs73c93ekg"
},
"type": [
"object",
{
"fusion_servers": "string",
"ground_stations": "string",
"operators": "string",
"pilots": "string"
}
]
}
},
"resources": [
{
"mode": "managed",
"type": "netbird_group",
"name": "fusion_servers",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "d68tmmml93fs73c93ek0",
"issued": "api",
"name": "fusion-servers",
"peers": [],
"resources": []
},
"sensitive_attributes": [],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_group",
"name": "ground_stations",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "d68tmmml93fs73c93ejg",
"issued": "api",
"name": "ground-stations",
"peers": [],
"resources": []
},
"sensitive_attributes": [],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_group",
"name": "operators",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "d68tmmml93fs73c93el0",
"issued": "api",
"name": "operators",
"peers": [],
"resources": []
},
"sensitive_attributes": [],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_group",
"name": "pilots",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "d68tmmml93fs73c93ekg",
"issued": "api",
"name": "pilots",
"peers": [],
"resources": []
},
"sensitive_attributes": [],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_policy",
"name": "fusion_to_gs",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"description": "Fusion servers coordinate with ground stations",
"enabled": true,
"id": "d68tmmul93fs73c93en0",
"name": "fusion-to-ground-station",
"rule": [
{
"action": "accept",
"bidirectional": true,
"description": "Fusion to GS access",
"destination_resource": null,
"destinations": null,
"enabled": true,
"id": "d68tmmul93fs73c93en0",
"name": "fusion-gs",
"port_ranges": null,
"ports": null,
"protocol": "all",
"source_resource": null,
"sources": null
}
],
"source_posture_checks": null
},
"sensitive_attributes": [],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_policy",
"name": "operator_full_access",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"description": "Operators can access all network resources",
"enabled": true,
"id": "d68tmmul93fs73c93eng",
"name": "operator-full-access",
"rule": [
{
"action": "accept",
"bidirectional": true,
"description": "Full operator access",
"destination_resource": null,
"destinations": null,
"enabled": true,
"id": "d68tmmul93fs73c93eng",
"name": "operator-all",
"port_ranges": null,
"ports": null,
"protocol": "all",
"source_resource": null,
"sources": null
}
],
"source_posture_checks": null
},
"sensitive_attributes": [],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_policy",
"name": "pilot_to_gs",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"description": "Allow pilots to connect to ground stations",
"enabled": true,
"id": "d68tmmul93fs73c93epg",
"name": "pilot-to-ground-station",
"rule": [
{
"action": "accept",
"bidirectional": true,
"description": "Pilots can access ground stations",
"destination_resource": null,
"destinations": null,
"enabled": true,
"id": "d68tmmul93fs73c93epg",
"name": "pilot-gs-access",
"port_ranges": null,
"ports": null,
"protocol": "all",
"source_resource": null,
"sources": null
}
],
"source_posture_checks": null
},
"sensitive_attributes": [],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_setup_key",
"name": "gs_onboarding",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"allow_extra_dns_labels": false,
"auto_groups": [
"d68tmmml93fs73c93ejg"
],
"ephemeral": false,
"expires": "0001-01-01T00:00:00Z",
"expiry_seconds": null,
"id": "d68tmmul93fs73c93eo0",
"key": null,
"last_used": "0001-01-01T00:00:00Z",
"name": "ground-station-onboarding",
"revoked": false,
"state": "valid",
"type": "reusable",
"updated_at": "2026-02-15T14:42:35Z",
"usage_limit": 0,
"used_times": 0,
"valid": true
},
"sensitive_attributes": [
[
{
"type": "get_attr",
"value": "key"
}
]
],
"identity_schema_version": 0
}
]
},
{
"mode": "managed",
"type": "netbird_setup_key",
"name": "pilot_onboarding",
"provider": "provider[\"registry.terraform.io/netbirdio/netbird\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"allow_extra_dns_labels": false,
"auto_groups": [
"d68tmmml93fs73c93ekg"
],
"ephemeral": false,
"expires": "2026-03-17T14:42:35Z",
"expiry_seconds": null,
"id": "d68tmmul93fs73c93eq0",
"key": null,
"last_used": "0001-01-01T00:00:00Z",
"name": "pilot-onboarding",
"revoked": false,
"state": "valid",
"type": "reusable",
"updated_at": "2026-02-15T14:42:35Z",
"usage_limit": 0,
"used_times": 0,
"valid": true
},
"sensitive_attributes": [
[
{
"type": "get_attr",
"value": "key"
}
]
],
"identity_schema_version": 0
}
]
}
],
"check_results": null
}

View File

@@ -0,0 +1,4 @@
# Copy to terraform.tfvars and fill in your PAT
# Note: URL should NOT include /api suffix - provider adds it automatically
# netbird_management_url = "https://netbird-poc.networkmonitor.cc"
netbird_token = "nbp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

11
terraform/variables.tf Normal file
View File

@@ -0,0 +1,11 @@
variable "netbird_management_url" {
type = string
description = "NetBird Management API URL"
default = "https://netbird-poc.networkmonitor.cc"
}
variable "netbird_token" {
type = string
sensitive = true
description = "NetBird admin PAT"
}