Prox 36672d3f10
Some checks failed
Terraform / terraform (push) Failing after 8s
feat: add pilot-ivanov setup key and encrypted key export in CI
2026-02-15 19:21:41 +02:00
2026-02-15 19:11:39 +02:00
2026-02-15 19:11:39 +02:00
2026-02-15 19:11:39 +02:00
2026-02-15 19:11:39 +02:00

NetBird GitOps PoC

Proof-of-concept for managing NetBird VPN configuration via Infrastructure as Code (IaC) with GitOps workflow using Terraform.

Project Status: POC Complete

Start date: 2026-02-15
Status: Full automation implemented including peer auto-naming

What Works

  • NetBird self-hosted instance deployed (netbird-poc.networkmonitor.cc)
  • Gitea CI/CD server deployed (gitea-poc.networkmonitor.cc)
  • Gitea Actions runner for CI/CD
  • Terraform implementation - creates groups, policies, setup keys
  • CI/CD pipeline - PR shows plan, merge-to-main applies changes
  • Watcher service - automatically renames peers based on setup key names

Architecture

+-------------------+     PR/Merge      +-------------------+
|  Engineer         | ----------------> |  Gitea            |
|  (creates setup   |                   |  (CI/CD)          |
|   key: pilot-X)   |                   +-------------------+
+-------------------+                          |
                                               | terraform apply
                                               v
+-------------------+                   +-------------------+
|  Watcher Service  | <---- polls ----> |  NetBird API      |
|  (auto-rename)    |                   +-------------------+
+-------------------+                          ^
                                               | enrolls
+-------------------+                          |
|  Operator         | -------------------------+
|  (uses setup key) |   peer appears as "DESKTOP-XYZ"
+-------------------+   watcher renames to "pilot-X"

Complete Workflow

  1. Ticket arrives: "Onboard pilot Ivanov"
  2. Engineer adds to Terraform:
    resource "netbird_setup_key" "pilot_ivanov" {
      name        = "pilot-ivanov"  # <-- This becomes the peer name
      type        = "one-off"
      auto_groups = [netbird_group.pilots.id]
      usage_limit = 1
    }
    
  3. Engineer creates PR -> CI runs terraform plan
  4. PR merged -> CI runs terraform apply -> setup key created
  5. Engineer retrieves key: terraform output -raw pilot_ivanov_key
  6. Engineer sends key to operator (via secure channel)
  7. Operator enrolls -> peer appears as DESKTOP-ABC123
  8. Watcher detects consumed key, renames peer to pilot-ivanov
  9. Done - peer is correctly named, no manual intervention

Directory Structure

netbird-gitops-poc/
├── ansible/                    # Deployment playbooks
│   ├── caddy/                  # Shared reverse proxy
│   ├── gitea/                  # Standalone Gitea
│   ├── gitea-runner/           # Gitea Actions runner
│   ├── netbird/                # NetBird server
│   └── netbird-watcher/        # Peer renamer service
├── terraform/                  # Terraform configuration
│   ├── .gitea/workflows/       # CI/CD workflow
│   ├── main.tf
│   ├── groups.tf
│   ├── policies.tf
│   ├── setup_keys.tf
│   └── outputs.tf
├── watcher/                    # Watcher service source
│   ├── netbird_watcher.py
│   ├── netbird-watcher.service
│   └── README.md
├── README.md
└── PAIN_POINTS.md

Deployment

Prerequisites

  • VPS with Docker
  • DNS records pointing to VPS
  • Ansible installed locally
  • Terraform installed locally

1. Deploy Core Infrastructure

# NetBird
cd ansible/netbird
./generate-vault.sh
ansible-vault encrypt group_vars/vault.yml
ansible-playbook -i poc-inventory.yml playbook-ssl.yml --ask-vault-pass

# Gitea
cd ../gitea
ansible-playbook -i poc-inventory.yml playbook.yml

# Caddy (reverse proxy)
cd ../caddy
ansible-playbook -i poc-inventory.yml playbook.yml

# Gitea Runner
cd ../gitea-runner
ansible-playbook -i poc-inventory.yml playbook.yml -e vault_gitea_runner_token=<TOKEN>

2. Deploy Watcher Service

cd ansible/netbird-watcher
ansible-playbook -i poc-inventory.yml playbook.yml -e vault_netbird_token=<TOKEN>

3. Initialize Terraform

cd terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with NetBird PAT
terraform init
terraform apply

4. Configure Gitea

Push terraform directory to Gitea repo, configure secret NETBIRD_TOKEN.


Adding a New Operator

  1. Add setup key to terraform/setup_keys.tf:

    resource "netbird_setup_key" "pilot_ivanov" {
      name        = "pilot-ivanov"
      type        = "one-off"
      auto_groups = [netbird_group.pilots.id]
      usage_limit = 1
      ephemeral   = false
    }
    
    output "pilot_ivanov_key" {
      value     = netbird_setup_key.pilot_ivanov.key
      sensitive = true
    }
    
  2. Commit, push, merge PR

  3. Retrieve key:

    terraform output -raw pilot_ivanov_key
    
  4. Send key to operator

  5. Operator enrolls -> watcher auto-renames peer


Monitoring

Watcher Service

# Status
systemctl status netbird-watcher

# Logs
journalctl -u netbird-watcher -f

# Processed keys
cat /var/lib/netbird-watcher/state.json

Cleanup

# Destroy Terraform resources
cd terraform && terraform destroy

# Stop services on VPS
ssh root@observability-poc.networkmonitor.cc
systemctl stop netbird-watcher
cd /opt/caddy && docker compose down
cd /opt/gitea && docker compose down
cd /opt/netbird && docker compose down
Description
No description provided
Readme 169 KiB
Languages
Jinja 65.9%
Python 22.7%
HCL 6.6%
Shell 4.8%