diff --git a/README.md b/README.md index 2c68e2d9..1a52b3ab 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Ansible](https://img.shields.io/badge/Ansible-2.14+-blue.svg)](https://www.ansible.com/) [![Multi-OS](https://img.shields.io/badge/OS-Debian%20%7C%20Ubuntu-orange.svg)](https://www.debian.org/) -Automated, hardened installation of [OpenClaw](https://github.com/openclaw/openclaw) with Docker and Tailscale VPN support for Debian/Ubuntu Linux. +Automated, hardened installation of [OpenClaw](https://github.com/openclaw/openclaw) with Docker and VPN support (Tailscale or Netbird) for Debian/Ubuntu Linux. ## ⚠️ macOS Support: Deprecated & Disabled @@ -24,7 +24,7 @@ The underlying project currently requires system-level permissions and configura - 🔒 **Firewall-first**: UFW firewall + Docker isolation - 🛡️ **Fail2ban**: SSH brute-force protection out of the box - 🔄 **Auto-updates**: Automatic security patches via unattended-upgrades -- 🔐 **Tailscale VPN**: Secure remote access without exposing services +- 🔐 **VPN Support**: Tailscale or Netbird for secure remote access - 🐳 **Docker**: Docker CE with security hardening - 🚀 **One-command install**: Complete setup in minutes - 🔧 **Auto-configuration**: DBus, systemd, environment setup @@ -55,8 +55,8 @@ cd openclaw-ansible ## What Gets Installed -- Tailscale (mesh VPN) -- UFW firewall (SSH + Tailscale ports only) +- VPN: Tailscale or Netbird (optional, choose one) +- UFW firewall (SSH + VPN ports only) - Docker CE + Compose V2 (for sandboxes) - Node.js 22.x + pnpm - OpenClaw on host (not containerized) @@ -241,7 +241,7 @@ Enable with: `-e openclaw_install_mode=development` ## Security -- **Public ports**: SSH (22), Tailscale (41641/udp) only +- **Public ports**: SSH (22) + VPN port only (Tailscale 41641/udp or Netbird 51820/udp) - **Fail2ban**: SSH brute-force protection (5 attempts → 1 hour ban) - **Automatic updates**: Security patches via unattended-upgrades - **Docker isolation**: Containers can't expose ports externally (DOCKER-USER chain) @@ -303,7 +303,8 @@ openclaw_ssh_keys: - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... user@host" openclaw_repo_url: "https://github.com/YOUR_USERNAME/openclaw.git" openclaw_repo_branch: "feature-branch" -tailscale_authkey: "tskey-auth-xxxxxxxxxxxxx" +vpn_provider: "netbird" +netbird_setup_key: "your-setup-key" EOF # Use it @@ -324,7 +325,10 @@ Edit `roles/openclaw/defaults/main.yml` before running the playbook. | `openclaw_ssh_keys` | `[]` | List of SSH public keys | | `openclaw_repo_url` | `https://github.com/openclaw/openclaw.git` | Git repository (dev mode) | | `openclaw_repo_branch` | `main` | Git branch (dev mode) | +| `vpn_provider` | `""` | VPN provider: `"tailscale"`, `"netbird"`, or `""` (none) | | `tailscale_authkey` | `""` | Tailscale auth key for auto-connect | +| `netbird_setup_key` | `""` | Netbird setup key for auto-connect | +| `netbird_management_url` | `""` | Netbird self-hosted management URL (optional) | | `nodejs_version` | `22.x` | Node.js version to install | See [`roles/openclaw/defaults/main.yml`](roles/openclaw/defaults/main.yml) for the complete list. @@ -347,11 +351,24 @@ ansible-playbook playbook.yml --ask-become-pass \ -e openclaw_repo_branch=feature-branch ``` -#### Tailscale Auto-Connect +#### VPN Auto-Connect ```bash +# Tailscale ansible-playbook playbook.yml --ask-become-pass \ + -e vpn_provider=tailscale \ -e tailscale_authkey=tskey-auth-xxxxxxxxxxxxx + +# Netbird +ansible-playbook playbook.yml --ask-become-pass \ + -e vpn_provider=netbird \ + -e netbird_setup_key=your-setup-key + +# Netbird (self-hosted) +ansible-playbook playbook.yml --ask-become-pass \ + -e vpn_provider=netbird \ + -e netbird_setup_key=your-setup-key \ + -e netbird_management_url=https://netbird.example.com ``` ## License diff --git a/docs/architecture.md b/docs/architecture.md index 10346605..74e4973d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -63,10 +63,11 @@ systemd → docker compose → openclaw container ## Installation Flow -1. **Tailscale Setup** (`tailscale.yml`) - - Add Tailscale repository - - Install Tailscale package - - Display connection instructions +1. **VPN Setup** (`tailscale-linux.yml` or `netbird-linux.yml`) + - Add VPN provider repository + - Install VPN package + - Auto-connect if auth/setup key provided + - Display connection instructions otherwise 2. **User Creation** (`user.yml`) - Create `openclaw` system user @@ -80,7 +81,7 @@ systemd → docker compose → openclaw container - Install UFW - Configure DOCKER-USER chain - Configure Docker daemon (`/etc/docker/daemon.json`) - - Allow SSH (22/tcp) and Tailscale (41641/udp) + - Allow SSH (22/tcp) and VPN port (Tailscale 41641/udp or Netbird 51820/udp) 5. **Node.js Installation** (`nodejs.yml`) - Add NodeSource repository @@ -119,7 +120,7 @@ Principle of least privilege. If container is compromised, attacker has limited ``` main.yml -├── tailscale.yml (VPN setup) +├── tailscale-linux.yml / netbird-linux.yml (VPN setup, conditional on vpn_provider) ├── user.yml (create openclaw user) ├── docker.yml (install Docker, create /etc/docker) ├── firewall.yml (configure UFW + Docker daemon) diff --git a/docs/configuration.md b/docs/configuration.md index 2bcdd154..5ddfe6ba 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -31,7 +31,8 @@ openclaw_ssh_keys: - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... admin@laptop" openclaw_repo_url: "https://github.com/YOUR_USERNAME/openclaw.git" openclaw_repo_branch: "main" -tailscale_authkey: "tskey-auth-xxxxxxxxxxxxx" +vpn_provider: "netbird" +netbird_setup_key: "your-setup-key" nodejs_version: "22.x" ``` @@ -167,18 +168,46 @@ These variables only apply when `openclaw_install_mode: development` -e nodejs_version=20.x ``` -### Tailscale Configuration +### VPN Configuration + +#### `vpn_provider` +- **Type**: String (`"tailscale"`, `"netbird"`, or `""`) +- **Default**: `""` (disabled) +- **Description**: VPN provider to install and configure +- **Example**: + ```bash + -e vpn_provider=netbird + ``` #### `tailscale_authkey` - **Type**: String - **Default**: `""` (empty - manual setup required) -- **Description**: Tailscale authentication key for automatic connection +- **Description**: Tailscale authentication key for automatic connection (when `vpn_provider: "tailscale"`) - **Example**: ```bash -e tailscale_authkey=tskey-auth-k1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6 ``` - **Get Key**: https://login.tailscale.com/admin/settings/keys +#### `netbird_setup_key` +- **Type**: String +- **Default**: `""` (empty - manual setup required) +- **Description**: Netbird setup key for automatic connection (when `vpn_provider: "netbird"`) +- **Example**: + ```bash + -e netbird_setup_key=your-setup-key + ``` +- **Get Key**: https://app.netbird.io/setup-keys + +#### `netbird_management_url` +- **Type**: String +- **Default**: `""` (empty - uses Netbird Cloud) +- **Description**: Self-hosted Netbird management server URL (when `vpn_provider: "netbird"`) +- **Example**: + ```bash + -e netbird_management_url=https://netbird.example.com + ``` + ### OS-Specific Settings These are automatically set based on the detected OS: @@ -225,11 +254,23 @@ openclaw_ssh_keys: ansible-playbook playbook.yml --ask-become-pass -e @vars-dev.yml ``` -### Production Setup with Tailscale +### Production Setup with VPN ```yaml -# vars-prod.yml +# vars-prod.yml (Netbird example) openclaw_install_mode: release +vpn_provider: "netbird" +netbird_setup_key: "your-setup-key" +netbird_management_url: "https://netbird.example.com" # optional, for self-hosted +openclaw_ssh_keys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxx admin@mgmt-server" +nodejs_version: "22.x" +``` + +```yaml +# vars-prod.yml (Tailscale example) +openclaw_install_mode: release +vpn_provider: "tailscale" tailscale_authkey: "tskey-auth-k1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6" openclaw_ssh_keys: - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxx admin@mgmt-server" @@ -286,7 +327,8 @@ openclaw_ssh_keys: ```yaml # environments/staging.yml openclaw_install_mode: release -tailscale_authkey: "{{ lookup('env', 'TAILSCALE_AUTHKEY_STAGING') }}" +vpn_provider: "netbird" +netbird_setup_key: "{{ lookup('env', 'NETBIRD_SETUP_KEY_STAGING') }}" openclaw_ssh_keys: - "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}" ``` @@ -296,7 +338,9 @@ openclaw_ssh_keys: ```yaml # environments/prod.yml openclaw_install_mode: release -tailscale_authkey: "{{ lookup('env', 'TAILSCALE_AUTHKEY_PROD') }}" +vpn_provider: "netbird" +netbird_setup_key: "{{ lookup('env', 'NETBIRD_SETUP_KEY_PROD') }}" +netbird_management_url: "{{ lookup('env', 'NETBIRD_MGMT_URL') }}" openclaw_ssh_keys: - "ssh-ed25519 AAAAC3... ops@prod-mgmt" - "ssh-ed25519 AAAAC3... admin@backup-server" @@ -323,16 +367,23 @@ nodejs_version: "22.x" -e "openclaw_ssh_keys=['$(cat ~/.ssh/new_key.pub)']" ``` -### Tailscale Auth Keys +### VPN Auth/Setup Keys 1. **Use ephemeral keys** for temporary access -2. **Set expiration times** for auth keys +2. **Set expiration times** for auth/setup keys 3. **Use reusable keys** only for automation 4. **Store in secrets manager**: Don't commit to git ```bash - # Use environment variable + # Netbird example + export NETBIRD_SETUP_KEY=$(vault read -field=key secret/netbird) + ansible-playbook playbook.yml --ask-become-pass \ + -e vpn_provider=netbird \ + -e netbird_setup_key="$NETBIRD_SETUP_KEY" + + # Tailscale example export TAILSCALE_AUTHKEY=$(vault read -field=key secret/tailscale) ansible-playbook playbook.yml --ask-become-pass \ + -e vpn_provider=tailscale \ -e tailscale_authkey="$TAILSCALE_AUTHKEY" ``` @@ -342,19 +393,23 @@ Never commit sensitive data to git: ```yaml # ❌ BAD - Don't do this +netbird_setup_key: "actual-key-here" tailscale_authkey: "tskey-auth-actual-key-here" # ✅ GOOD - Use environment variables or vault +netbird_setup_key: "{{ lookup('env', 'NETBIRD_SETUP_KEY') }}" tailscale_authkey: "{{ lookup('env', 'TAILSCALE_AUTHKEY') }}" # ✅ GOOD - Use Ansible Vault +netbird_setup_key: "{{ vault_netbird_setup_key }}" tailscale_authkey: "{{ vault_tailscale_authkey }}" ``` Create encrypted vault: ```bash ansible-vault create secrets.yml -# Add: vault_tailscale_authkey: tskey-auth-xxxxx +# Add: vault_netbird_setup_key: your-setup-key +# Or: vault_tailscale_authkey: tskey-auth-xxxxx ansible-playbook playbook.yml --ask-become-pass \ -e @secrets.yml --ask-vault-pass @@ -385,13 +440,19 @@ sudo ls -la /home/openclaw/.ssh/ sudo cat /home/openclaw/.ssh/authorized_keys ``` -### Tailscale Not Connecting +### VPN Not Connecting -Verify auth key is valid: +Tailscale - verify auth key is valid: ```bash sudo tailscale up --authkey=YOUR_KEY --verbose ``` +Netbird - verify setup key and connectivity: +```bash +sudo netbird up --setup-key YOUR_KEY +sudo netbird status +``` + ### Installation Mode Issues Check which mode is active: diff --git a/docs/installation.md b/docs/installation.md index 8ed01564..7c494279 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -35,21 +35,24 @@ ansible-playbook playbook.yml --ask-become-pass ## Post-Installation -### 1. Connect to Tailscale +### 1. Connect VPN (if not auto-connected) +If you set `vpn_provider` but didn't provide an auth/setup key, connect manually: + +**Tailscale:** ```bash -# Interactive login sudo tailscale up - -# Or with auth key for automation -sudo tailscale up --authkey tskey-auth-xxxxx - -# Check status sudo tailscale status ``` - Get auth keys from: https://login.tailscale.com/admin/settings/keys +**Netbird:** +```bash +sudo netbird up +sudo netbird status +``` +Get setup keys from: https://app.netbird.io/setup-keys + ### 2. Configure OpenClaw ```bash @@ -126,11 +129,11 @@ sudo iptables -L DOCKER-USER -n -v OpenClaw's web interface runs on port 3000 (localhost only). -### Via Tailscale (Recommended) +### Via VPN (Recommended) ```bash -# After connecting Tailscale, browse to: -http://TAILSCALE_IP:3000 +# After connecting your VPN (Tailscale or Netbird), browse to: +http://VPN_IP:3000 ``` Wait, port 3000 is bound to localhost, so this won't work directly. Need to update the compose file or use SSH tunnel. @@ -147,7 +150,7 @@ ssh -L 3000:localhost:3000 user@server ### Security Check ```bash -# Check open ports (should show only SSH + Tailscale) +# Check open ports (should show only SSH + VPN) sudo ss -tlnp # External port scan (only port 22 should be open) @@ -173,13 +176,16 @@ sudo ufw status verbose # 41641/udp ALLOW IN Anywhere ``` -### Tailscale Status +### VPN Status +**Tailscale:** ```bash sudo tailscale status +``` -# Expected output: -# 100.x.x.x hostname user@ linux - +**Netbird:** +```bash +sudo netbird status ``` ## Uninstall @@ -188,7 +194,8 @@ sudo tailscale status # Stop services sudo systemctl stop openclaw sudo systemctl disable openclaw -sudo tailscale down +# sudo tailscale down (if using Tailscale) +# sudo netbird down (if using Netbird) # Remove containers and data sudo docker compose -f /opt/openclaw/docker-compose.yml down @@ -198,7 +205,7 @@ sudo rm /etc/systemd/system/openclaw.service sudo systemctl daemon-reload # Remove packages (optional) -sudo apt remove --purge tailscale docker-ce docker-ce-cli containerd.io docker-compose-plugin nodejs +sudo apt remove --purge tailscale netbird docker-ce docker-ce-cli containerd.io docker-compose-plugin nodejs # Remove user (optional) sudo userdel -r openclaw @@ -250,19 +257,27 @@ volumes: ### Unattended Install ```bash -# Set Tailscale auth key in playbook vars +# Netbird example +ansible-playbook playbook.yml \ + --ask-become-pass \ + -e vpn_provider=netbird \ + -e "netbird_setup_key=your-setup-key" + +# Tailscale example ansible-playbook playbook.yml \ --ask-become-pass \ + -e vpn_provider=tailscale \ -e "tailscale_authkey=tskey-auth-xxxxx" ``` ### CI/CD Integration ```yaml -# Example GitHub Actions +# Example GitHub Actions (Netbird) - name: Deploy OpenClaw run: | ansible-playbook playbook.yml \ - -e "tailscale_authkey=${{ secrets.TAILSCALE_KEY }}" \ + -e vpn_provider=netbird \ + -e "netbird_setup_key=${{ secrets.NETBIRD_SETUP_KEY }}" \ --become ``` diff --git a/docs/security.md b/docs/security.md index 1840b0fe..1e1cc71c 100644 --- a/docs/security.md +++ b/docs/security.md @@ -21,7 +21,8 @@ Routed: DENY # Allowed SSH (22/tcp): ALLOW -Tailscale (41641/udp): ALLOW +Tailscale (41641/udp): ALLOW (when vpn_provider: "tailscale") +Netbird (51820/udp): ALLOW (when vpn_provider: "netbird") ``` ### Layer 2: Fail2ban (SSH Protection) @@ -87,7 +88,7 @@ The openclaw user has limited sudo permissions (not full root): # Allowed commands only: - systemctl start/stop/restart/status openclaw - systemctl daemon-reload -- tailscale commands +- VPN commands (tailscale or netbird, depending on vpn_provider) - journalctl for openclaw logs ``` @@ -117,13 +118,14 @@ sudo ufw status verbose # Check fail2ban sudo fail2ban-client status -# Check Tailscale status -sudo tailscale status +# Check VPN status +sudo tailscale status # if using Tailscale +sudo netbird status # if using Netbird # Check Docker isolation sudo iptables -L DOCKER-USER -n -v -# Port scan from external machine (only SSH + Tailscale should be open) +# Port scan from external machine (only SSH + VPN port should be open) nmap -p- YOUR_SERVER_IP # Test container isolation @@ -136,7 +138,7 @@ sudo docker rm -f test-nginx sudo systemctl status unattended-upgrades ``` -## Tailscale Access +## VPN Access OpenClaw's web interface (port 3000) is bound to localhost. Access it via: @@ -146,13 +148,10 @@ OpenClaw's web interface (port 3000) is bound to localhost. Access it via: # Then browse to http://localhost:3000 ``` -2. **Tailscale** (recommended): +2. **VPN** (recommended): ```bash - # On server: already done by playbook - sudo tailscale up - - # From your machine: - # Browse to http://TAILSCALE_IP:3000 + # After connecting via Tailscale or Netbird: + # Browse to http://VPN_IP:3000 ``` ## Network Flow @@ -182,12 +181,12 @@ Container → NAT → Internet (outbound allowed) After installation, verify: -- [ ] `sudo ufw status` shows only SSH and Tailscale allowed +- [ ] `sudo ufw status` shows only SSH and VPN port allowed - [ ] `sudo fail2ban-client status sshd` shows jail active - [ ] `sudo iptables -L DOCKER-USER -n` shows DROP rule - [ ] `nmap -p- YOUR_IP` from external shows only port 22 - [ ] `docker run -p 80:80 nginx` + `curl YOUR_IP:80` times out -- [ ] Tailscale access works for web UI +- [ ] VPN access works for web UI (if vpn_provider configured) ## Reporting Security Issues diff --git a/inventory-sample.yml b/inventory-sample.yml index 3636ec06..a8c7de84 100644 --- a/inventory-sample.yml +++ b/inventory-sample.yml @@ -21,5 +21,12 @@ all: # openclaw_ssh_keys: # - "ssh-ed25519 AAAAC3Nz..." - # Tailscale Auth Key (Optional) - Leave empty to skip auto-connect + # VPN Provider (Optional) - "tailscale", "netbird", or "" (none) + # vpn_provider: "netbird" + + # Tailscale Auth Key (when vpn_provider: "tailscale") # tailscale_authkey: "tskey-auth-..." + + # Netbird Setup Key (when vpn_provider: "netbird") + # netbird_setup_key: "your-setup-key" + # netbird_management_url: "https://netbird.example.com" # For self-hosted diff --git a/playbooks/install.yml b/playbooks/install.yml index 90cb89ba..0ac2a397 100644 --- a/playbooks/install.yml +++ b/playbooks/install.yml @@ -162,13 +162,20 @@ echo " • Troubleshoot: openclaw doctor" echo " • List agents: openclaw agents list" echo "" - {% if tailscale_enabled | default(false) %}echo "────────────────────────────────────────────────────────" + {% if vpn_provider | default("") == "tailscale" and tailscale_authkey | default("") | length == 0 %}echo "────────────────────────────────────────────────────────" echo "🌐 Connect Tailscale VPN (optional):" echo "────────────────────────────────────────────────────────" echo "" echo " exit" echo " sudo tailscale up" echo "" + {% elif vpn_provider | default("") == "netbird" and netbird_setup_key | default("") | length == 0 %}echo "────────────────────────────────────────────────────────" + echo "🌐 Connect Netbird VPN (optional):" + echo "────────────────────────────────────────────────────────" + echo "" + echo " exit" + echo " sudo netbird up" + echo "" {% endif %}echo "────────────────────────────────────────────────────────" echo "" echo "Type 'exit' to return to your previous user" diff --git a/roles/openclaw/defaults/main.yml b/roles/openclaw/defaults/main.yml index 243cc6d4..c9ecf5bc 100644 --- a/roles/openclaw/defaults/main.yml +++ b/roles/openclaw/defaults/main.yml @@ -4,10 +4,18 @@ # CI testing mode - skips tasks that require systemd, Docker-in-Docker, or kernel access ci_test: false -# Tailscale settings +# VPN settings (choose one or none) +# Options: "tailscale", "netbird", or "" (disabled) +vpn_provider: "" + +# Tailscale settings (when vpn_provider: "tailscale") # WARNING: Tasks using tailscale_authkey MUST set no_log: true to prevent credential exposure -tailscale_enabled: false # Set to true to install and configure Tailscale -tailscale_authkey: "" # Optional: set to auto-connect during installation +tailscale_authkey: "" # Optional: auto-connect with auth key + +# Netbird settings (when vpn_provider: "netbird") +# WARNING: Tasks using netbird_setup_key MUST set no_log: true to prevent credential exposure +netbird_setup_key: "" # Optional: auto-connect with setup key +netbird_management_url: "" # Optional: self-hosted management URL (leave empty for cloud) # Node.js version nodejs_version: "22.x" diff --git a/roles/openclaw/tasks/firewall-linux.yml b/roles/openclaw/tasks/firewall-linux.yml index 068b4a58..f6c4d201 100644 --- a/roles/openclaw/tasks/firewall-linux.yml +++ b/roles/openclaw/tasks/firewall-linux.yml @@ -103,7 +103,15 @@ port: '41641' proto: udp comment: 'Tailscale' - when: tailscale_enabled | bool + when: vpn_provider == "tailscale" + +- name: Allow Netbird WireGuard UDP port 51820 + community.general.ufw: + rule: allow + port: '51820' + proto: udp + comment: 'Netbird WireGuard' + when: vpn_provider == "netbird" - name: Get default network interface ansible.builtin.shell: diff --git a/roles/openclaw/tasks/main.yml b/roles/openclaw/tasks/main.yml index 81a5dd95..d2d3970e 100644 --- a/roles/openclaw/tasks/main.yml +++ b/roles/openclaw/tasks/main.yml @@ -4,7 +4,11 @@ - name: Include Tailscale installation tasks ansible.builtin.include_tasks: tailscale-linux.yml - when: tailscale_enabled | bool + when: vpn_provider == "tailscale" + +- name: Include Netbird installation tasks + ansible.builtin.include_tasks: netbird-linux.yml + when: vpn_provider == "netbird" - name: Include user creation tasks ansible.builtin.include_tasks: user.yml diff --git a/roles/openclaw/tasks/netbird-linux.yml b/roles/openclaw/tasks/netbird-linux.yml new file mode 100644 index 00000000..2f243750 --- /dev/null +++ b/roles/openclaw/tasks/netbird-linux.yml @@ -0,0 +1,79 @@ +--- +# Linux-specific Netbird installation (Debian/Ubuntu) + +- name: Install required packages for Netbird repo setup + ansible.builtin.apt: + name: + - gnupg + - curl + state: present + +- name: Add Netbird GPG key + ansible.builtin.shell: + cmd: | + set -o pipefail + curl -sSL https://pkgs.netbird.io/debian/public.key | \ + gpg --dearmor --output /usr/share/keyrings/netbird-archive-keyring.gpg + creates: /usr/share/keyrings/netbird-archive-keyring.gpg + executable: /bin/bash + +- name: Add Netbird repository + ansible.builtin.copy: + dest: /etc/apt/sources.list.d/netbird.list + content: "deb [signed-by=/usr/share/keyrings/netbird-archive-keyring.gpg] https://pkgs.netbird.io/debian stable main\n" + owner: root + group: root + mode: '0644' + +- name: Update apt cache after adding Netbird repo + ansible.builtin.apt: + update_cache: true + +- name: Install Netbird + ansible.builtin.apt: + name: netbird + state: present + +- name: Enable Netbird service (Linux) + ansible.builtin.systemd: + name: netbird + enabled: true + state: started + +- name: Check if Netbird is already connected (Linux) + ansible.builtin.command: netbird status + register: netbird_status_linux + changed_when: false + failed_when: false + +- name: Auto-connect Netbird with setup key + ansible.builtin.command: >- + netbird up + --setup-key {{ netbird_setup_key }} + {% if netbird_management_url | length > 0 %}--management-url {{ netbird_management_url }}{% endif %} + when: + - netbird_setup_key | length > 0 + - "'Connected' not in netbird_status_linux.stdout" + no_log: true + changed_when: true + +- name: Display Netbird connection instructions if not connected (Linux) + ansible.builtin.debug: + msg: + - "============================================" + - "Netbird installed but not connected yet" + - "============================================" + - "" + - "To connect this machine to your Netbird network:" + - "Run: sudo netbird up" + - "" + - "For unattended installation, use a setup key:" + - "sudo netbird up --setup-key " + - "" + - "For self-hosted Netbird:" + - "sudo netbird up --setup-key --management-url https://netbird.example.com" + - "" + - "Get setup keys from: https://app.netbird.io/setup-keys" + when: + - "'Connected' not in netbird_status_linux.stdout" + - netbird_setup_key | length == 0 diff --git a/roles/openclaw/tasks/tailscale-linux.yml b/roles/openclaw/tasks/tailscale-linux.yml index 04a0005d..62d79940 100644 --- a/roles/openclaw/tasks/tailscale-linux.yml +++ b/roles/openclaw/tasks/tailscale-linux.yml @@ -44,7 +44,15 @@ changed_when: false failed_when: false -- name: Display Tailscale auth URL if not connected (Linux) +- name: Auto-connect Tailscale with auth key + ansible.builtin.command: "tailscale up --authkey {{ tailscale_authkey }}" + when: + - tailscale_authkey | length > 0 + - tailscale_status_linux.rc != 0 + no_log: true + changed_when: true + +- name: Display Tailscale connection instructions if not connected (Linux) ansible.builtin.debug: msg: - "============================================" @@ -58,4 +66,6 @@ - "sudo tailscale up --authkey tskey-auth-xxxxx" - "" - "Get auth key from: https://login.tailscale.com/admin/settings/keys" - when: tailscale_status_linux.rc != 0 + when: + - tailscale_status_linux.rc != 0 + - tailscale_authkey | length == 0 diff --git a/roles/openclaw/tasks/user.yml b/roles/openclaw/tasks/user.yml index ef669e45..f0f6c748 100644 --- a/roles/openclaw/tasks/user.yml +++ b/roles/openclaw/tasks/user.yml @@ -44,49 +44,12 @@ mode: '0644' - name: Add openclaw user to sudoers with scoped NOPASSWD - ansible.builtin.copy: + ansible.builtin.template: + src: sudoers-openclaw.j2 dest: "/etc/sudoers.d/{{ openclaw_user }}" mode: '0440' owner: root group: root - content: | - # OpenClaw sudo permissions (scoped for security) - # - # SECURITY NOTE: These permissions are intentionally limited. - # If openclaw is compromised, attackers can only: - # - Manage the openclaw service - # - Run basic tailscale diagnostics - # - View openclaw logs - # - # To grant full tailscale control (e.g., for self-healing VPN): - # {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale * - # - # To grant full sudo (NOT RECOMMENDED): - # {{ openclaw_user }} ALL=(ALL) NOPASSWD: ALL - - # Service control - openclaw service only - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl start openclaw - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop openclaw - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart openclaw - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl status openclaw - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl enable openclaw - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl disable openclaw - # daemon-reload affects all units (required after service file changes) - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload - - # Tailscale - diagnostics + connect/disconnect - # NOTE: 'up' allows flags like --advertise-exit-node. For tighter control, - # remove 'up' and 'down' lines - operator must then manage VPN manually. - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale status - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale up * - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale down - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale ip * - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale version - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale ping * - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale whois * - - # Journal access - openclaw logs only - {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/journalctl -u openclaw * validate: /usr/sbin/visudo -cf %s - name: Set openclaw user as primary user for installation diff --git a/roles/openclaw/templates/show-lobster.sh.j2 b/roles/openclaw/templates/show-lobster.sh.j2 index 66456bea..85971f51 100644 --- a/roles/openclaw/templates/show-lobster.sh.j2 +++ b/roles/openclaw/templates/show-lobster.sh.j2 @@ -29,8 +29,10 @@ echo "" echo "🔒 Security Status:" echo " - UFW Firewall: ENABLED" {% endraw %} -{% if tailscale_enabled | default(false) %} +{% if vpn_provider | default("") == "tailscale" %} echo " - Open Ports: SSH (22) + Tailscale (41641/udp)" +{% elif vpn_provider | default("") == "netbird" %} +echo " - Open Ports: SSH (22) + Netbird (51820/udp)" {% else %} echo " - Open Ports: SSH (22)" {% endif %} diff --git a/roles/openclaw/templates/sudoers-openclaw.j2 b/roles/openclaw/templates/sudoers-openclaw.j2 new file mode 100644 index 00000000..e597ebdf --- /dev/null +++ b/roles/openclaw/templates/sudoers-openclaw.j2 @@ -0,0 +1,42 @@ +# OpenClaw sudo permissions (scoped for security) +# +# SECURITY NOTE: These permissions are intentionally limited. +# If openclaw is compromised, attackers can only: +# - Manage the openclaw service +# - Run basic VPN diagnostics +# - View openclaw logs + +# Service control - openclaw service only +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl start openclaw +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop openclaw +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart openclaw +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl status openclaw +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl enable openclaw +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl disable openclaw +# daemon-reload affects all units (required after service file changes) +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload + +{% if vpn_provider == "tailscale" %} +# Tailscale - diagnostics + connect/disconnect +# NOTE: 'up' allows flags like --advertise-exit-node. For tighter control, +# remove 'up' and 'down' lines - operator must then manage VPN manually. +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale status +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale up * +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale down +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale ip * +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale version +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale ping * +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale whois * +{% endif %} + +{% if vpn_provider == "netbird" %} +# Netbird - diagnostics + connect/disconnect +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/netbird status +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/netbird up * +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/netbird down +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/netbird version +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/netbird routes * +{% endif %} + +# Journal access - openclaw logs only +{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/journalctl -u openclaw *