Skip to content
/ cattun Public

Self-hosted alternative to Cloudflare Tunnel, expose local services, written in Rust.

License

MIT, Apache-2.0 licenses found

Licenses found

MIT
LICENSE
Apache-2.0
LICENSE-APACHE2
Notifications You must be signed in to change notification settings

ErickJ3/cattun

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cattun logo

cattun

A self-hosted tunnel server that exposes local services through a public endpoint over QUIC.

Overview

cattun routes incoming HTTPS requests to local services running behind NAT or firewalls. The server accepts tunnel client connections over QUIC (TLS 1.3), and proxies HTTP traffic through bidirectional streams. Wildcard subdomains, WebSocket passthrough, and scoped multi-tenant auth are built in.

Think of it as a self-hosted alternative to Cloudflare Tunnel.

Quick Start

Server

Create cattun-server.toml:

[server]
quic_listen = "0.0.0.0:4433"
https_listen = "0.0.0.0:443"
# http_listen = "0.0.0.0:80"

[tls]
cert_file = "/etc/cattun/cert.pem"
key_file = "/etc/cattun/key.pem"

[[auth.tokens]]
token = "tok_abcdef123456"
allowed_patterns = ["*.coolify.example.com", "api.example.com"]
cattun server -c cattun-server.toml

Client

Create cattun-client.toml:

[client]
server_addr = "vps.example.com:4433"
token = "tok_abcdef123456"

[[tunnels]]
subdomain = "*.coolify.example.com"
local_addr = "http://127.0.0.1:8080"

[[tunnels]]
subdomain = "api.example.com"
local_addr = "http://127.0.0.1:3000"
cattun client -c cattun-client.toml

Configuration

Server (cattun-server.toml)

Core

Section Field Type Description
[server] quic_listen String QUIC listen address for tunnel clients
[server] https_listen String HTTPS listen address for public traffic
[server] http_listen String? Optional HTTP listener for 80 -> 443 redirect
[tls] cert_file String Path to TLS certificate (PEM)
[tls] key_file String Path to TLS private key (PEM)
[tls] client_ca_file String? CA file for verifying client certificates (mTLS)
[[auth.tokens]] token String Pre-shared authentication token
[[auth.tokens]] allowed_patterns Vec<String> Subdomain patterns this token can register

PostgreSQL (optional)

When configured, tokens are managed in the database and an audit log is persisted. TOML tokens are seeded to the database on startup.

[database]
url = "postgres://cattun:pass@localhost/cattun"
max_connections = 10  # default: 10

Redis (optional)

Enables distributed rate limiting via sliding window sorted sets.

[redis]
url = "redis://localhost:6379"

Rate Limiting (requires Redis)

[rate_limit]
requests_per_minute = 1000  # default: 1000
burst = 100                 # default: 100

Rate limiting uses a Lua script for atomic sliding window checks. If Redis goes down, requests are allowed through (fail-open).

Admin API (optional)

[admin]
listen = "127.0.0.1:9090"
token = "admin_secret"

Client (cattun-client.toml)

Section Field Type Description
[client] server_addr String Server QUIC address (host:port)
[client] token String? Pre-shared authentication token (optional if using mTLS)
[client] client_cert_file String? Client certificate file for mTLS
[client] client_key_file String? Client private key file for mTLS
[[tunnels]] subdomain String Subdomain pattern to register
[[tunnels]] local_addr String Local service address to forward to

mTLS Authentication

Instead of (or in addition to) a token, clients can authenticate with a TLS certificate. The server maps the certificate's SHA-256 fingerprint to a token entry via the cert_mappings table, inheriting the same allowed_patterns.

# Server: add CA for client verification
[tls]
cert_file = "/etc/cattun/cert.pem"
key_file = "/etc/cattun/key.pem"
client_ca_file = "/etc/cattun/client-ca.pem"

# Client: authenticate with certificate
[client]
server_addr = "vps.example.com:4433"
client_cert_file = "/etc/cattun/client.crt"
client_key_file = "/etc/cattun/client.key"

Wildcard Matching

*.example.com matches foo.example.com and bar.example.com, but not example.com itself.

Admin API

When [admin] is configured, an HTTP API is available for managing tokens and inspecting state. All /api/ endpoints require Authorization: Bearer <admin_token>.

Method Path Description
GET /api/tokens List all tokens (id, label, patterns, active, created_at)
POST /api/tokens Create a new token. Returns the raw token once
DELETE /api/tokens/:id Revoke a token (soft-delete)
POST /api/tokens/:id/rotate Rotate: creates a new token with the same patterns and revokes the old one
GET /api/tunnels List active tunnels (subdomain, local_addr, client_addr, uptime, request_count)
GET /api/audit Query audit log (?event_type=&since=&token_id=&limit=)
GET /metrics Prometheus metrics (no auth required)

Examples

# Create a token
curl -X POST http://localhost:9090/api/tokens \
  -H "Authorization: Bearer admin_secret" \
  -H "Content-Type: application/json" \
  -d '{"label": "team-a", "allowed_patterns": ["*.a.example.com"]}'

# List active tunnels
curl http://localhost:9090/api/tunnels \
  -H "Authorization: Bearer admin_secret"

# Rotate a token (zero-downtime)
curl -X POST http://localhost:9090/api/tokens/<uuid>/rotate \
  -H "Authorization: Bearer admin_secret"

# Scrape Prometheus metrics
curl http://localhost:9090/metrics

Observability

Prometheus Metrics

Available at the /metrics endpoint on the admin API port:

Metric Type Description
cattun_requests_total counter Total proxied requests (labels: status, host)
cattun_request_duration_seconds histogram Request latency (label: host)
cattun_active_tunnels gauge Currently registered tunnels
cattun_active_connections gauge Active QUIC connections
cattun_auth_attempts_total counter Authentication attempts (label: result)
cattun_rate_limit_hits_total counter Requests rejected by rate limiter

Structured Logging

cattun uses tracing with structured fields (method, host, status, latency, conn_id, subdomain). Configure log level via RUST_LOG:

RUST_LOG=info cattun server -c cattun-server.toml
RUST_LOG=cattun_server=debug cattun server -c cattun-server.toml

Audit Log

When PostgreSQL is configured, the following events are persisted to the audit_log table:

  • auth_success / auth_failure -- with token_id, client_addr
  • tunnel_registered / tunnel_unregistered -- with subdomain, conn_id
  • token_created / token_revoked / token_rotated -- via admin API
  • rate_limited -- when a request is rejected

The audit logger uses a background channel (mpsc) with batch inserts to avoid blocking the hot path.

Building from Source

Requires Rust 1.86+ (edition 2024).

cargo build --release --package cattun

The binary is at target/release/cattun.

Docker

FROM rust:1.86 AS builder

WORKDIR /build
COPY Cargo.toml Cargo.lock ./
COPY crates/ crates/

RUN cargo build --release --package cattun

FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*

COPY --from=builder /build/target/release/cattun /usr/local/bin/cattun

ENTRYPOINT ["cattun"]
docker build -t cattun .
docker run cattun server -c /etc/cattun/server.toml

Architecture

                    Internet
                       |
              HTTPS (443/tcp)
                       |
               +-------+-------+
               |  cattun-server |
               |  HTTPS listener|
               |  + router      |
               +-------+-------+
                       |
                 QUIC (4433/udp)
                       |
               +-------+-------+
               |  cattun-client |
               |  stream handler|
               +-------+-------+
                       |
                 HTTP (localhost)
                       |
                 +-----------+
                 | local svc |
                 +-----------+

Request flow:

  1. Client connects to server over QUIC, opens a control stream, sends Register with token and tunnel specs
  2. Server validates the token (or client certificate), checks patterns against the allowlist, registers subdomain routes
  3. Incoming HTTPS request hits the server -- router looks up the Host header against registered tunnels
  4. Rate limiter checks the request against the token's quota (if Redis is configured)
  5. Server opens a new QUIC bidirectional stream to the client, sends a ProxyHeader (host + local_addr)
  6. Client reads the header, connects to the local service, and relays bytes in both directions
  7. Control stream maintains heartbeat; on disconnect, client reconnects with backoff

Framing: control messages are serialized with bincode, length-prefixed with 4-byte big-endian u32.

Database Schema

When PostgreSQL is configured, the following tables are created via migrations:

  • tokens -- token hashes, labels, allowed patterns, active status, expiry
  • cert_mappings -- SHA-256 certificate fingerprints mapped to token entries (mTLS)
  • audit_log -- timestamped events with JSONB details

Testing

# Run all tests
cargo test

# Run with logging
RUST_LOG=debug cargo test -- --nocapture

# Run a specific test
cargo test test_graceful_shutdown

License

Licensed under either of

  • Apache2
  • MIT

at your option.

About

Self-hosted alternative to Cloudflare Tunnel, expose local services, written in Rust.

Resources

License

MIT, Apache-2.0 licenses found

Licenses found

MIT
LICENSE
Apache-2.0
LICENSE-APACHE2

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published