note: i just got claude to generate this. it might be Very wrong
Self-hosted Tailscale control server implementation in Rust with grants-based access control.
+------------------+
| Tailscale |
| Clients |
+--------+---------+
|
HTTPS (ts2021 protocol)
|
v
+-------------------+ +-------------------+
| Admin CLI |----(gRPC)-->| railscale |
| (railscale-admin)| | (main binary) |
+-------------------+ +--------+----------+
|
+------------+------------+-------+-------+------------+------------+
| | | | | |
v v v v v v
+------+----+ +-----+------+ +---+----+ +-------+------+ +----+-----+ +----+-----+
| -types | | -db | | -proto | | -grants | | -admin | | -tka |
| (domain) | | (storage) | | (wire) | | (policy) | | (grpc) | | (lock) |
+-----------+ +------------+ +--------+ +--------------+ +----------+ +----------+
graph TD
railscale[railscale<br/><i>binary, HTTP handlers</i>]
types[railscale-types<br/><i>domain types</i>]
db[railscale-db<br/><i>persistence</i>]
proto[railscale-proto<br/><i>protocol types</i>]
grants[railscale-grants<br/><i>access control</i>]
admin[railscale-admin<br/><i>gRPC admin API</i>]
tka[railscale-tka<br/><i>tailnet lock</i>]
railscale --> types
railscale --> db
railscale --> proto
railscale --> grants
railscale --> tka
db --> types
grants --> types
grants --> proto
admin --> types
admin --> db
admin --> grants
tka --> types
The HTTP server and CLI interface. Built with axum.
Key components:
handlers/- HTTP request handlers for Tailscale protocolmap.rs- MapRequest/MapResponse (network state sync)register.rs- Node registrationoidc.rs- OpenID Connect authentication flowts2021.rs- Noise protocol WebSocket upgradeapi_v1/- REST admin API endpoints
cli/- Command-line interface (serve, users, nodes, policy, lock)resolver.rs- User/group resolution for grants evaluationderp.rs/derp_server.rs- DERP relay integrationpresence.rs- Tracks connected nodes for online statusephemeral.rs- Garbage collector for inactive ephemeral nodes
Application state (AppState):
pub struct AppState {
pub db: RailscaleDb,
pub grants: Arc<RwLock<GrantsEngine>>, // Hot-reloadable
pub config: Config,
pub oidc: Option<AuthProviderOidc>,
pub notifier: StateNotifier, // Broadcasts to clients
pub ip_allocator: Arc<Mutex<IpAllocator>>,
pub derp_map: Arc<RwLock<DerpMap>>,
pub presence: PresenceTracker,
pub ephemeral_gc: EphemeralGarbageCollector,
// ... caches, keys
}Domain types shared across all crates. No external service dependencies.
Key types:
Node- Tailscale device (machine key, node key, IPs, tags, etc.)User- User account with optional OIDC bindingPreAuthKey- Pre-authentication keys for automated registrationApiKey- API keys with split-token pattern for secure lookupConfig- Application configurationTag- Validated tag strings (tag:name)NodeKey,MachineKey,DiscoKey- Cryptographic key wrappers
Test utilities:
TestNodeBuilder- Fluent builder for test fixtures
Database layer using sea-orm. Supports SQLite and PostgreSQL.
Key components:
Databasetrait - Abstract interface for storage operationsRailscaleDb- sea-orm implementationIpAllocator- Sequential or random IP allocation from configured prefixesentity/- sea-orm entity definitionsmigration/- Database migrations
Operations:
- Soft-delete semantics (records marked with
deleted_at) - Split-token lookup for API keys
- Hash-based lookup for preauth keys
- TKA state and AUM storage
Tailscale wire protocol types and Noise cryptography.
Key types:
MapRequest/MapResponse- Client state synchronisationFilterRule- Packet filter rules sent to clientsDerpMap,DerpRegion,DerpNode- Relay configurationSshPolicy,SshRule- Tailscale SSH configurationNoiseHandshake/NoiseTransport- ts2021 encryption
Serde conventions:
- Go PascalCase for JSON field names (client compatibility)
- Skip serialising None/empty fields
Grants-based access control engine (Tailscale's ACL replacement).
Key types:
Policy- Complete policy document (grants + SSH rules + groups + postures)Grant- Single access rule (src -> dst with capabilities)Selector- Node matcher (*,tag:x,user@,group:, CIDR, autogroups)NetworkCapability- Port/protocol permissionsGrantsEngine- Thread-safe policy evaluatorPostureExpr- Device posture conditionsGeoIpResolver- Geolocation lookup for ip:country posture checks
Selectors:
* - All nodes
tag:web - Nodes with tag:web
autogroup:tagged - Any tagged node
autogroup:member - Any user-owned (untagged) node
autogroup:self - Same user's other devices
user@example.com - Nodes owned by this user
group:engineering - Nodes owned by group members
100.64.0.0/24 - Nodes with IP in CIDR
Evaluation:
- Deny-by-default
- Union composition (multiple matching grants combine)
- Directional (src -> dst, not bidirectional)
Tailnet Key Authority (tailnet lock) implementation.
Key types:
Authority- TKA state machineAum- Authority Update Message (signed state changes)Key- TKA signing key (ed25519)
Features:
- Genesis AUM creation with configurable disablement secrets
- Full AUM chain storage and sync
- Node signing with
nlpriv:key format - TKA disable with disablement secret
gRPC admin service for CLI and remote administration.
Transport: Unix socket (/run/railscale/admin.sock)
Services:
- Policy management (get, set, reload)
- User CRUD
- Node management (list, delete, expire, tags, routes)
- PreAuth key management
- API key management
- TKA operations (init, sign, disable)
sequenceDiagram
participant C as Client
participant R as railscale
participant DB as Database
participant OIDC as OIDC Provider
C->>R: POST /ts2021 (Noise handshake)
R->>C: Noise session established
alt PreAuth Key Registration
C->>R: RegisterRequest (auth_key)
R->>DB: Validate preauth key
R->>DB: Create/update node
R->>C: RegisterResponse (node_id, user)
else OIDC Registration
C->>R: RegisterRequest (no auth)
R->>C: RegisterResponse (auth_url)
C->>OIDC: User authenticates
OIDC->>R: Callback with tokens
R->>DB: Create/update user
R->>DB: Create/update node
R->>C: Next MapRequest succeeds
end
sequenceDiagram
participant C as Client
participant R as railscale
participant GE as GrantsEngine
participant DB as Database
C->>R: MapRequest (via Noise)
R->>DB: Load all nodes, users
R->>GE: Get visible peers for this node
GE->>GE: Evaluate grants (src->dst)
R->>GE: Generate filter rules
R->>C: MapResponse (peers, filters, DERP, DNS)
loop Long-poll / Stream
R->>C: MapResponse (on state change)
end
Railscale uses Tailscale's newer grants system instead of legacy ACLs:
- More expressive selectors (groups, autogroups, posture)
- Clearer composition (union semantics)
- Better support for app-level permissions
The GrantsEngine is wrapped in Arc<RwLock<>> allowing policy updates without restart:
pub struct PolicyHandle {
engine: Arc<RwLock<GrantsEngine>>,
}Reload via SIGHUP, admin API, or CLI command.
When api.listen_host is configured, REST API runs on separate port:
- Protocol endpoints (ts2021, map, register) on public port
- Admin API on internal/localhost port
- Better security boundary
Modern Tailscale clients use Noise IK protocol over WebSocket:
- Mutual authentication via machine keys
- Forward secrecy
- Replay protection
Legacy HTTP-based protocol not supported.
Sequential or random allocation from configured prefixes:
- IPv4: Default
100.64.0.0/10(CGNAT range) - IPv6: Default
fd7a:115c:a1e0::/48(ULA range) - Allocator tracks used IPs, loads from DB on startup
All entities use soft-delete with deleted_at timestamp:
- Preserves audit trail
- Allows recovery
- Simplifies foreign key handling
Nodes created with ephemeral preauth keys are automatically deleted after inactivity:
EphemeralGarbageCollectortracks disconnects via presence- Configurable timeout (default 120s)
- Deletion cancelled if node reconnects
snow- Noise protocol implementation (vendored with patches)- Standard library crypto for key generation
openidconnect- OIDC client implementation- Supports any OIDC-compliant provider (Keycloak, Auth0, etc.)
sea-orm- Async ORM with migration support- SQLite for development/small deployments (with WAL mode)
- PostgreSQL for production
axum- Web frameworktower-governor- Rate limitingtonic- gRPC (admin service)
maxminddb- GeoLite2-Country database for ip:country posture checks
Each crate has unit tests for core logic:
- Grants evaluation
- Selector parsing
- Posture expression parsing
- Database operations
- Protocol encoding
crates/railscale/tests/ contains protocol-level tests:
ts2021_*_test.rs- Noise handshake testsmap_*_test.rs- MapRequest/Response testsregister_test.rs- Registration flow testsoidc_test.rs- OIDC authentication tests
Full end-to-end tests in NixOS VMs:
- Real Tailscale client connecting to railscale
- Multi-node scenarios
- Policy enforcement verification
- Taildrop file transfers
- Tailnet lock operations
Run via nix flake check (can take 5-10 minutes).