No description
  • Go 65.3%
  • HTML 18.3%
  • JavaScript 13.2%
  • Shell 2.2%
  • CSS 0.8%
  • Other 0.2%
Find a file
Jeff Clement 79fd67e63f
All checks were successful
Build and Test / test (push) Successful in 2m30s
Release / release (push) Successful in 2m42s
feat: update Dockerfiles to support multi-architecture builds
2026-04-04 12:26:01 -06:00
.claude feat: add audit log page and update navigation 2026-04-03 17:24:16 -06:00
.forgejo/workflows feat: forgejo builds 2026-04-04 11:58:06 -06:00
.github Initial commit 2026-03-22 00:22:26 +00:00
cmd feat: implement multi-target routing for HTTP tunnels and update documentation 2026-04-04 06:27:45 -06:00
deploy feat: ensure install and config directories are owned by the service user during upgrade 2026-04-03 19:53:30 -06:00
internal feat: add server version display for clients in dashboard 2026-04-04 08:32:18 -06:00
web feat: add server version display for clients in dashboard 2026-04-04 08:32:18 -06:00
.air.toml Initial commit 2026-03-22 00:22:26 +00:00
.gitignore feat: enhance update functionality with binary name parameter 2026-04-03 19:47:52 -06:00
.goreleaser.forgejo.yaml feat: forgejo builds 2026-04-04 11:58:06 -06:00
.goreleaser.yaml feat: update Dockerfiles to support multi-architecture builds 2026-04-04 12:26:01 -06:00
.mise.toml feat: add audit log page and update navigation 2026-04-03 17:24:16 -06:00
Dockerfile feat: add audit log page and update navigation 2026-04-03 17:24:16 -06:00
Dockerfile.client feat: add audit log page and update navigation 2026-04-03 17:24:16 -06:00
Dockerfile.goreleaser feat: update Dockerfiles to support multi-architecture builds 2026-04-04 12:26:01 -06:00
Dockerfile.goreleaser.client feat: update Dockerfiles to support multi-architecture builds 2026-04-04 12:26:01 -06:00
go.mod deps: bump github.com/go-webauthn/webauthn from 0.16.1 to 0.16.2 2026-04-03 23:25:12 +00:00
go.sum deps: bump github.com/go-webauthn/webauthn from 0.16.1 to 0.16.2 2026-04-03 23:25:12 +00:00
LICENSE Initial commit 2026-03-22 00:22:26 +00:00
package-lock.json Initial commit 2026-03-22 00:22:26 +00:00
package.json Initial commit 2026-03-22 00:22:26 +00:00
README.md feat: implement multi-target routing for HTTP tunnels and update documentation 2026-04-04 06:27:45 -06:00

Gatecrash

Gatecrash

Self-hosted tunnel server. Expose local services through a public server with automatic TLS.
A self-hosted alternative to Cloudflare Tunnels, ngrok, and similar services.

InstallationQuick StartHow It WorksConfigurationOIDCDocker

Note

This project is vibe coded with Claude Code. The entire codebase — server, client, admin panel, CI/CD, and this README — was built collaboratively with AI. Use at your own risk, but it actually works pretty well.


Installation

Gatecrash ships as two separate binaries:

Binary Purpose Size
gatecrash-server Tunnel server with admin panel, TLS, OIDC ~15 MB
gatecrash Lightweight tunnel client ~7 MB

Linux Server (Quick Install)

Installs gatecrash-server, creates a systemd service, and starts it:

curl -fsSL https://raw.githubusercontent.com/jclement/gatecrash/main/deploy/install.sh | sh

Homebrew (macOS/Linux)

# Server
brew install jclement/tap/gatecrash-server

# Client
brew install jclement/tap/gatecrash

Go

# Server
go install github.com/jclement/gatecrash/cmd/gatecrash-server@latest

# Client
go install github.com/jclement/gatecrash/cmd/gatecrash@latest

Docker

# Server
docker pull ghcr.io/jclement/gatecrash-server:latest

# Client (lightweight)
docker pull ghcr.io/jclement/gatecrash:latest

Binary Releases

Download pre-built binaries for Linux, macOS, and Windows from GitHub Releases.


Quick Start

  1. Provision a server with a public IP (any Linux VPS will do)

  2. Point a hostname at your server's IP via DNS (e.g. admin.example.com)

  3. Run the installer — it downloads the binary, generates a config, creates a systemd service, and starts Gatecrash. It will ask for your admin hostname during setup.

    curl -fsSL https://raw.githubusercontent.com/jclement/gatecrash/main/deploy/install.sh | sh
    

    Or download from GitHub Releases for manual installation.

  4. Open the admin panel at https://admin.example.com, register your passkey, and click Add Tunnel to create your first tunnel

  5. Connect a client using the command shown in the admin panel (the SSH port and host key are displayed when you create a tunnel):

    gatecrash \
      --server tunnel.example.com:51234 \
      --host-key "SHA256:..." \
      --token "web-app:YOUR_SECRET" \
      --target 127.0.0.1:8000
    

    The SSH port is randomly assigned on first run (check ssh_port in gatecrash.toml). The exact connection command is shown in the admin panel when you create a tunnel or regenerate a secret.

That's it. Requests to your configured hostname now reach your local service on port 8000.


How It Works

Gatecrash uses SSH as the transport layer for tunnel connections. The server accepts SSH connections from clients, then reverse-opens channels back to the client for each incoming HTTP request or TCP connection.

sequenceDiagram
    participant User as Browser
    participant Server as Gatecrash Server
    participant Client as Gatecrash Client
    participant App as Local App

    Client->>Server: SSH connect (authenticate with token)
    Server-->>Client: Connection established

    User->>Server: HTTPS request to app.example.com
    Server->>Server: SNI routing -> find tunnel for hostname
    Server->>Client: Open HTTP channel (reverse)
    Client->>App: Forward request to 127.0.0.1:8000
    App-->>Client: Response
    Client-->>Server: Response via SSH channel
    Server-->>User: HTTPS response

HTTP Tunnels

HTTP tunnels route traffic based on the Host header. Multiple hostnames can map to a single tunnel. TLS certificates are automatically provisioned via Let's Encrypt.

TCP Tunnels

TCP tunnels forward raw TCP connections on a dedicated port. Useful for databases, game servers, or any non-HTTP protocol.

Config Live Reload

The server watches gatecrash.toml for changes. Valid changes are applied immediately without dropping existing connections. Invalid changes are rejected and the error is shown in the admin panel.

HTTP Redirects

Redirect hostnames without needing a tunnel:

[[redirect]]
from = "www.example.com"
to = "example.com"
preserve_path = true

Configuration

Server Configuration (gatecrash.toml)

The config file is auto-generated on first run with sensible defaults and inline documentation.

Server Settings

Field Default Description
server.secret auto-generated Session signing secret (do not share)
server.ssh_port random high port SSH listen port for tunnel connections
server.https_port 443 HTTPS listen port
server.http_port 80 HTTP->HTTPS redirect port (0 to disable)
server.bind_addr 0.0.0.0 Bind address
server.admin_host (disabled) Hostname for admin panel (required to enable it)

TLS Settings

Field Default Description
tls.acme_email Email for Let's Encrypt expiration notices
tls.cert_dir ./certs Certificate storage directory
tls.staging false Use Let's Encrypt staging CA

Update Settings

Field Default Description
update.enabled true Check for updates on startup
update.check_interval 6h How often to check for updates
update.github_repo jclement/gatecrash GitHub repo for update checks

Tunnel Options

Option Default Description
preserve_host false Pass the original Host header to the backend
tls_passthrough false Forward raw TLS without terminating (backend handles TLS)
require_auth false Require OIDC authentication to access this tunnel
auth_rule OIDC rule name that must match for access
auth_header x-Gatecrash-User Header name for authenticated user identity
auth_header_claim (email claim) Which OIDC claim value to put in the auth header

Example:

[[tunnel]]
id = "backend-api"
type = "http"
hostnames = ["api.example.com"]
secret_hash = "$2a$12$..."
preserve_host = true
require_auth = true
auth_rule = "Employees"
auth_header = "x-Gatecrash-User"
auth_header_claim = "email"

Forwarding Headers

For HTTP tunnels (when tls_passthrough = false), the following headers are injected:

Header Value
X-Forwarded-For Original client IP
X-Forwarded-Proto https or http
X-Forwarded-Host Original Host header
X-Real-IP Original client IP
X-Request-Id Unique request ID

When require_auth = true, the configured auth_header is also injected with the authenticated user's claim value.


OIDC Authentication

By default, Gatecrash uses passkey authentication (single admin user). When OIDC is enabled, it replaces passkeys entirely -- all admin and tunnel authentication goes through your OIDC provider.

Configuring OIDC

Add the [oidc] section to gatecrash.toml:

[oidc]
enabled = true
provider_name = "Keycloak"            # Display name on login button
client_id = "gatecrash"
client_secret = "your-client-secret"
auth_url = "https://idp.example.com/auth/realms/main/protocol/openid-connect/auth"
token_url = "https://idp.example.com/auth/realms/main/protocol/openid-connect/token"
cert_url = "https://idp.example.com/auth/realms/main/protocol/openid-connect/certs"
use_pkce = false
name_claim = "name"                   # Claim containing user's display name
email_claim = "email"                 # Claim containing user's email

# Optional: restrict admin access to users matching a specific claim.
# When empty, any authenticated OIDC user is an admin.
admin_claim_name = "role"
admin_claim_value = "admin"

Note: OIDC is configured in gatecrash.toml only. Environment variables are not supported for OIDC settings.

When OIDC is enabled:

  • The login page auto-redirects to your OIDC provider
  • Passkey authentication is disabled (the Passkeys tab is hidden)
  • Audit log entries show the OIDC user's name and email

Protecting Tunnels

HTTP tunnels can require authentication via the Require Authentication checkbox in the tunnel edit dialog.

In passkey mode: the admin must be logged in with their passkey to access the tunnel (single-user).

In OIDC mode: users are redirected to the OIDC provider. You can optionally restrict access by claim:

[[tunnel]]
id = "internal-app"
type = "http"
hostnames = ["internal.example.com"]
secret_hash = "$2a$12$..."
require_auth = true                   # Users must authenticate
auth_claim_name = "department"        # Optional: restrict by claim
auth_claim_value = "engineering"      # Only users with this claim value get access
auth_header = "x-Gatecrash-User"     # Header injected into proxied requests
auth_header_claim = "email"          # Claim value to put in the header

When auth_claim_name is empty, any authenticated OIDC user can access the tunnel. The auth_header is injected into all proxied requests with the authenticated user's claim value.

Claim filters support string and array claims (e.g. if groups is ["engineering", "ops"], filtering on engineering will match).

Callback URLs

Register these callback URLs with your OIDC provider:

  • Admin login: https://<admin_host>/oidc/callback
  • Tunnel auth: https://<tunnel_hostname>/.gatecrash/oidc/callback (one per protected tunnel hostname)

Audit Log

All admin panel changes are recorded in an audit log, viewable from the Audit Log tab.

Logged events include:

  • Tunnel create, edit, delete, and secret regeneration
  • Redirect create, edit, delete
  • Admin logins (passkey or OIDC)

Each entry records the timestamp, actor identity (Admin (passkey) or Name <email> for OIDC users), action, and detail. The log is stored at <config_dir>/audit.json and retains the most recent 1,000 entries.


CLI Reference

Server (gatecrash-server)

gatecrash-server              Start the tunnel server (default)
gatecrash-server make-config  Generate a config file
gatecrash-server update       Self-update to latest release
gatecrash-server version      Print version
gatecrash-server help         Show help
Flag Env Var Default Description
--config GATECRASH_CONFIG /etc/gatecrash/gatecrash.toml Config file path
--debug false Enable debug logging

Client (gatecrash)

The client runs directly without subcommands:

gatecrash [flags]       Connect a tunnel to the server
gatecrash update        Self-update to latest release
gatecrash version       Print version
gatecrash help          Show help
Flag Env Var Required Default Description
--server GATECRASH_SERVER Yes Server SSH address (host:port)
--token GATECRASH_TOKEN Yes Tunnel token (tunnel_id:secret)
--target GATECRASH_TARGET Yes Target address (repeatable, see below)
--host-key GATECRASH_HOST_KEY Yes Server SSH fingerprint (SHA256:...)
--count GATECRASH_COUNT No 1 Parallel tunnel connections (1-10)
--debug No false Enable debug logging

Multi-Target Routing

An HTTP tunnel with multiple hostnames can route each to a different backend using --target:

gatecrash --server host:port --token homelab:secret \
  --target git.example.com=forgejo:3000 \
  --target gist.example.com=opengist:8080

A bare --target host:port (without =) sets the default target for unmatched hostnames and TCP tunnels.

Via environment variable: GATECRASH_TARGET=git.example.com=forgejo:3000,gist.example.com=opengist:8080

Target Schemes

Route targets support scheme prefixes:

Target Behavior
localhost:8080 Plain HTTP / TCP (default)
https://localhost:8443 TLS with certificate verification
https+insecure://localhost:8443 TLS without certificate verification

Docker

Server

# Replace 51234 with the ssh_port from your gatecrash.toml
docker run -d \
  -p 443:443 -p 80:80 -p 51234:51234 \
  -v gatecrash-config:/etc/gatecrash \
  ghcr.io/jclement/gatecrash-server:latest

The SSH port is randomly generated on first run. Check ssh_port in your gatecrash.toml to find the assigned port.

Client

docker run -d \
  -e GATECRASH_SERVER=tunnel.example.com:51234 \
  -e GATECRASH_HOST_KEY=SHA256:your_host_key_fingerprint \
  -e GATECRASH_TOKEN=web-app:YOUR_SECRET \
  -e GATECRASH_TARGET=app:8000 \
  --network=app-network \
  ghcr.io/jclement/gatecrash:latest

The client image is minimal -- just the tunnel client binary with ca-certificates.

Docker Compose -- Client with Service

A typical deployment pairs the gatecrash client with your application:

services:
  # Your application
  whoami:
    image: traefik/whoami
    expose:
      - "80"

  # Gatecrash tunnel client
  tunnel:
    image: ghcr.io/jclement/gatecrash:latest
    environment:
      GATECRASH_SERVER: tunnel.example.com:51234
      GATECRASH_HOST_KEY: "SHA256:your_host_key_fingerprint"
      GATECRASH_TOKEN: "web-app:YOUR_SECRET"
      GATECRASH_TARGET: whoami:80
    depends_on:
      - whoami
    restart: unless-stopped

Docker Compose -- Multi-Service Routing

Route multiple hostnames to different containers through a single tunnel:

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:latest
    expose:
      - "3000"
      - "22"

  opengist:
    image: ghcr.io/thomiceli/opengist:latest
    expose:
      - "8080"

  tunnel:
    image: ghcr.io/jclement/gatecrash:latest
    environment:
      GATECRASH_SERVER: tunnel.example.com:51234
      GATECRASH_HOST_KEY: "SHA256:your_host_key_fingerprint"
      GATECRASH_TOKEN: "homelab:YOUR_SECRET"
      GATECRASH_TARGET: "git.example.com=forgejo:3000,gist.example.com=opengist:8080"
    depends_on:
      - forgejo
      - opengist
    restart: unless-stopped

Docker Compose -- Server

services:
  server:
    image: ghcr.io/jclement/gatecrash-server:latest
    ports:
      - "80:80"
      - "443:443"
      - "51234:51234"    # Match ssh_port in your gatecrash.toml
    volumes:
      - config:/etc/gatecrash
    restart: unless-stopped

volumes:
  config:

OIDC and other settings are configured in gatecrash.toml inside the config volume, or via the admin panel.


Development

# Install tools
mise install

# Download frontend assets
mise run setup

# Run server with hot-reload
mise run dev

# Run tests
mise run test

# Build both binaries
mise run build

# Test release
mise run release-snapshot

Architecture

gatecrash/
├── cmd/
│   ├── gatecrash/           # Client binary
│   └── gatecrash-server/    # Server binary
├── internal/
│   ├── config/              # TOML config + file watcher
│   ├── server/              # SSH server, HTTP proxy, TCP forward, vhost routing, TLS
│   ├── client/              # SSH client, HTTP/TCP handlers, reconnect logic
│   ├── admin/               # Web admin panel (passkeys, OIDC, sessions, audit log)
│   ├── protocol/            # SSH channel types and control messages (shared)
│   ├── token/               # bcrypt-based tunnel authentication
│   └── update/              # Self-update via GitHub releases (shared)
├── web/                     # HTML templates + static assets (server only)
├── deploy/                  # Install script, docker-compose examples
└── .github/                 # CI/CD, dependabot

License

MIT


Vibe coded with Claude Code