headscale/docker-dev/NETWORK-ARCHITECTURE.md
Ryan Malloy 5abc3c87b2 OIDC groups implementation
- Add Groups field to User struct with JSON storage
- Include GetGroups() and SetGroups() helper methods
- Extract groups from OIDC claims in FromClaim()
- Add database migration 202509161200 for groups column
- Update config-example.yaml with groups scope
- Add comprehensive documentation and testing
2026-05-21 17:55:31 -06:00

217 lines
7.1 KiB
Markdown

# Network Architecture Documentation
This document provides a detailed explanation of the networking setup in the Headscale Docker development environment.
## Overview
The setup uses **two distinct networking layers** that work together to provide a complete Tailscale network simulation:
1. **Docker Bridge Network** - Infrastructure layer for container communication
2. **Tailscale Overlay Network** - Encrypted VPN layer for secure data transmission
## Layer 1: Docker Bridge Network
### Network Configuration
- **Subnet**: `10.99.0.0/24`
- **Gateway**: `10.99.0.1`
- **Network Name**: `headscale-dev_headscale-net`
### Container IP Assignments
| Container | IP Address | Role |
|-----------|------------|------|
| headscale-server | `10.99.0.10` | Control plane server |
| tailscale-client1 | `10.99.0.21` | Tailscale node 1 |
| tailscale-client2 | `10.99.0.22` | Tailscale node 2 |
| test-webserver | `10.99.0.30` | HTTP test server |
### Port Mappings
#### Headscale Server
| Host Port | Container Port | Service |
|-----------|----------------|---------|
| 8180 | 8080 | HTTP API |
| 9090 | 9090 | Metrics |
| 50443 | 50443 | gRPC |
**Note**: Port 8180 is used on the host instead of 8080 to avoid conflicts with other services.
#### Other Services
| Container | Exposed Ports | Purpose |
|-----------|---------------|---------|
| test-webserver | 80 (internal only) | HTTP test content |
| tailscale-client1 | None exposed | VPN endpoint |
| tailscale-client2 | None exposed | VPN endpoint |
## Layer 2: Tailscale Overlay Network
### Network Configuration
- **IPv4 Subnet**: `100.64.0.0/10` (Tailscale CGNAT range)
- **IPv6 Subnet**: `fd7a:115c:a1e0::/48` (Tailscale IPv6 range)
- **Allocation Strategy**: Sequential
### Tailscale IP Assignments
| Node | IPv4 Address | IPv6 Address | Hostname |
|------|-------------|--------------|----------|
| client1 | `100.64.0.1` | `fd7a:115c:a1e0::1` | client1 |
| client2 | `100.64.0.2` | `fd7a:115c:a1e0::2` | client2 |
## Network Communication Flow
### 1. Control Plane Communication
```
Tailscale Client → Docker Network → Headscale Server
│ │ │
100.64.0.1 10.99.0.21 10.99.0.10
│ │ │
└─────── HTTP/GRPC over ──────────┘
headscale:8080
```
- Clients connect to Headscale using Docker's internal DNS (`headscale:8080`)
- Authentication happens via pre-auth keys
- Headscale assigns Tailscale IP addresses from the `100.64.0.0/10` range
- Policy enforcement and network map distribution
### 2. Data Plane Communication
```
Client1 ←→ Encrypted Tailscale Tunnel ←→ Client2
│ │
100.64.0.1 100.64.0.2
│ │
10.99.0.21 ←── Docker Bridge Network ──→ 10.99.0.22
```
When `client1` pings `client2`:
1. **Application layer**: Uses Tailscale IP `100.64.0.2`
2. **Encryption layer**: Tailscale encrypts the packet using WireGuard
3. **Transport layer**: Encrypted packet travels via Docker network `10.99.0.22:port`
4. **Decryption layer**: `client2` decrypts and processes the packet
**Key insight**: The ping result shows:
```
pong from client2 (100.64.0.2) via 10.99.0.22:49212 in 0s
```
This demonstrates how Tailscale IPs are used at the application level while Docker IPs handle the actual transport.
## Security Architecture
### 1. Noise Protocol (Tailscale v2)
- **Purpose**: Encrypts control plane communication between clients and Headscale
- **Key Storage**: `/var/lib/headscale/noise_private.key`
- **Protocol**: Noise_IK_25519_ChaChaPoly_BLAKE2s
### 2. WireGuard Encryption
- **Purpose**: Encrypts data plane communication between Tailscale nodes
- **Key Exchange**: Managed by Headscale control plane
- **Cipher**: ChaCha20Poly1305
### 3. Access Control Lists (ACL)
```json
{
"acls": [
{
"action": "accept",
"src": ["testuser@headscale"],
"dst": ["testuser@headscale:*"]
}
]
}
```
- Controls which nodes can communicate with each other
- Applied at the Tailscale overlay network level
- Independent of Docker network security
## DNS Resolution
### Docker Internal DNS
- `headscale``10.99.0.10` (Control plane access)
- `client1``10.99.0.21` (Container hostname)
- `client2``10.99.0.22` (Container hostname)
- `webserver``10.99.0.30` (Test web server)
### Tailscale MagicDNS
- `client1``100.64.0.1` (Tailscale hostname)
- `client2``100.64.0.2` (Tailscale hostname)
- Domain: `headscale.local` (configured in Headscale)
## Network Isolation and Security
### Container Isolation
- Each container has its own network namespace
- Containers can only communicate via the Docker bridge network
- No direct host network access (except through port mappings)
### Tailscale Overlay Isolation
- Encrypted tunnels between authorized nodes only
- ACL policies enforce access control
- Zero-trust architecture: containers on same Docker network still use encrypted communication
### Firewall Considerations
- Docker bridge network: Internal communication only
- Host ports: Only Headscale API (8180) exposed to host
- Tailscale network: Controlled by ACL policies
## Debugging Network Issues
### Check Docker Network
```bash
# View network configuration
docker network inspect headscale-dev_headscale-net
# Test Docker-level connectivity
docker exec tailscale-client1 ping headscale
docker exec tailscale-client1 ping 10.99.0.22
```
### Check Tailscale Network
```bash
# View Tailscale status
docker exec tailscale-client1 tailscale status
# Test Tailscale connectivity
docker exec tailscale-client1 tailscale ping client2
docker exec tailscale-client1 ping 100.64.0.2
```
### Verify Control Plane
```bash
# Test Headscale API
curl http://localhost:8180/health
# View registered nodes
docker exec headscale-server headscale nodes list
```
## Performance Characteristics
### Latency
- **Docker bridge**: Sub-millisecond latency (same host)
- **Tailscale overlay**: Minimal additional latency due to encryption
- **Control plane**: Periodic updates, not in data path
### Throughput
- **Limited by**: Docker bridge network bandwidth and CPU encryption
- **Typical**: Near-native performance for local container communication
- **Encryption overhead**: Minimal with modern ChaCha20 implementation
### Scalability
- **Current setup**: 2 clients, easily expandable
- **Docker limitations**: Network MTU, container limits
- **Headscale limitations**: Database backend (SQLite vs PostgreSQL)
## Real-World Mapping
This setup simulates real Tailscale deployments:
| Docker Environment | Real World |
|--------------------|------------|
| Docker bridge network | Internet infrastructure |
| Container IP addresses | Public/private IP addresses |
| Headscale control server | Tailscale SaaS control plane |
| ACL policies | Corporate network policies |
| Pre-auth keys | Device enrollment tokens |
| Encrypted tunnels | WireGuard VPN connections |
The key difference is that in production, nodes are typically on different networks (home, office, cloud) rather than the same Docker host, but the Tailscale protocol behavior is identical.