fix documents
Kim committed
Mar 3, 2026 at 15:02 UTC
b2b69c0b842c597ff00fc9c2b2fa0198b23cee49
8 files changed
+282
-448
.env.example
new
+15
@@ -0,0 +1,15 @@
1
+# Public base URL for relay/admin
2
+PORTAL_URL=http://localhost:4017
3
+
4
+# Admin login secret
5
+ADMIN_SECRET_KEY=
6
+
7
+# SNI router port
8
+SNI_PORT=443
9
+
10
+# Directory for keyless/ACME materials
11
+KEYLESS_DIR=/etc/portal/keyless
12
+
13
+# Cloudflare API token
14
+# Leave empty for local/no-TLS usage
15
+CLOUDFLARE_TOKEN=
AGENTS.md
+1
-1
@@ -105,7 +105,7 @@ Portal uses environment variables for domain and TLS configuration:
105
- Auto-discovery requires an HTTPS signer endpoint.
106
- Relay signer key comes from `KEYLESS_DIR/privatekey.pem`; when missing and `CLOUDFLARE_TOKEN` is set, relay auto-issues via ACME DNS-01.
107
108
-See `docs/portal-deploy-guide.md` for full deployment documentation.
108
+See `docs/deployment.md` for full deployment documentation.
109
110
## Repo Basics
111
README.md
+38
-51
@@ -4,87 +4,73 @@
4
<img src="/portal.jpg" alt="Portal logo" width="540" />
5
</p>
6
7
-Portal is a permissionless, open hosting network that transforms your local project into a public web endpoint. [Learn more.](https://gosuda.org/portal/)
7
+Expose your local application to the public internet —
8
+no ports, no NAT, no DNS setup.
9
9
-## Overview
10
+Portal is an open, self-hosted, permissionless relay network — not a SaaS tunnel.
11
+Connect to any relay to publish your service — or run your own relay.
12
11
-Portal connects local applications to web users through a secure relay layer.
12
-Each application is assigned a subdomain within Portal, and all traffic between endpoints is end-to-end encrypted.
13
-This enables developers to publish local services globally without managing servers or cloud infrastructure.
13
+## Why Portal?
14
+
15
+Publishing a local service typically requires:
16
+
17
+- Opening inbound ports
18
+- Configuring NAT or firewall rules
19
+- Managing DNS records
20
+- Terminating TLS at a gateway
21
+
22
+Portal removes these steps by inverting the connection model.
23
+Applications establish outbound connections to a relay.
24
+The relay runs on a public base domain, assigns each service a subdomain,
25
+and routes incoming traffic while preserving end-to-end TLS.
26
27
## Features
28
17
-- 🔄 **Connection Relay**: Connects clients behind NAT or firewalls through the Portal network
18
-- **Subdomain NAT Routing**: lease names to public subdomains (myapp.<base-domain>) and routes traffic to your local service without opening inbound ports
29
+- 🔄 **Connection Behind NAT**: Works behind NAT or firewalls without opening inbound ports
30
+- 🌐 **Automatic Subdomain Routing**: Give each app its own subdomain ( your-app.<base-domain> )
31
- 🔐 **End-to-End Encryption**: TLS passthrough with relay keyless certificates
32
- 🕊️ **Permissionless Hosting**: Anyone can run their own Portal — no approval needed
21
-- ⚙️ **Simple Setup**: Quick start with Tunnel client
33
+- ⚙️ **One-Command Setup**: Expose any local app with a single command
34
+
35
+## Components
36
+
37
+- **Relay**: A server that routes public requests to the right connected app.
38
+- **Tunnel**: A CLI agent that proxies your local app through the relay.
39
+
40
+For details, see [docs/glossary.md](docs/glossary.md).
41
42
## Quick Start
43
44
### Run Portal Relay
45
46
```bash
28
-# Start with Docker Compose
47
+git clone https://github.com/gosuda/portal
48
+cd portal
49
docker compose up
30
-
31
-# Access at http://localhost:4017
32
-# Admin panel at http://localhost:4017/admin
33
-# Set your own admin key (recommended):
34
-ADMIN_SECRET_KEY=your-secret-key docker compose up
35
-
36
-# Keyless auto-issuance (optional):
37
-# if KEYLESS_DIR is missing and CLOUDFLARE_TOKEN is set,
38
-# relay issues keyless certs via ACME DNS-01.
39
-# relay uses one unified cert/key pair:
40
-# KEYLESS_DIR/fullchain.pem
41
-# KEYLESS_DIR/privatekey.pem
42
-# when both files exist, admin/API listener on --adminport auto-switches to HTTPS (HTTP/1.1 only).
43
-CLOUDFLARE_TOKEN=your-cloudflare-dns-token docker compose up
50
```
51
46
-```bash
47
-# Run relay binary directly
48
-./bin/relay-server --adminport 4017
49
-```
50
-
51
-For production deployment (DNS, TLS, reverse proxy), see [docs/portal-deploy-guide.md](docs/portal-deploy-guide.md).
52
+For deployment to a public domain, see [docs/deployment.md](docs/deployment.md).
53
54
### Expose Local Service via Tunnel
55
55
-```bash
56
-# Windows PowerShell
57
-$env:APP_HOST="localhost:3000"; $env:APP_NAME="myapp"; irm http://localhost:4017/tunnel | iex
58
-
59
-# macOS/Linux
60
-curl -fsSL http://localhost:4017/tunnel | APP_HOST=localhost:3000 APP_NAME=myapp sh
61
-```
62
-
63
-### Use Go SDK
56
+1. Open the relay page in your browser.
57
+2. Click `Add your server`.
58
+3. Use the generated command to connect your local service.
59
65
-```go
66
-import "gosuda.org/portal/sdk"
60
+With Tunnel, you can expose almost any local app in one step, without changing your app code.
61
+Run your app locally, execute the generated command, and Portal publishes it on a public subdomain.
62
68
-client, _ := sdk.NewClient(sdk.WithBootstrapServers([]string{"http://localhost:4017"}))
69
-listener, _ := client.Listen("myapp")
70
-http.Serve(listener, handler)
71
-```
63
+### Use Go SDK (Advanced)
64
65
See [portal-toys](https://github.com/gosuda/portal-toys) for more examples.
66
67
## Architecture
68
77
-- **Relay Server**: TLS passthrough relay with SNI routing, admin UI, lease management
78
-- **SDK**: Go library for native app integration
79
-- **Tunnel**: CLI client for exposing local services without code changes
80
-
81
-For details, see [docs/architecture.md](docs/architecture.md).
69
+See [docs/architecture.md](docs/architecture.md).
70
71
## Contributing
72
73
We welcome contributions from the community!
86
-Before getting started, please check the [development guide](docs/development.md)
87
- for setup instructions and best practices.
74
75
### Steps to Contribute
76
1. Fork the repository
@@ -92,6 +78,7 @@ Before getting started, please check the [development guide](docs/development.md
78
3. Commit your changes (`git commit -m 'Add amazing feature'`)
79
4. Push to the branch (`git push origin feature/amazing-feature`)
80
5. Open a Pull Request
81
+
82
## License
83
84
MIT License — see [LICENSE](LICENSE)
docs/architecture.md
+87
-49
@@ -2,76 +2,114 @@
2
3
## Overview
4
5
-Portal connects local applications to web users through a secure relay layer with end-to-end encryption.
5
+Portal is a relay network for publishing local services on public subdomains.
6
+The relay is the control and routing plane; service backends connect outward to the relay (NAT-friendly), and clients connect to the relay domain.
7
7
-```
8
-┌─────────────┐ ┌──────────────┐ ┌─────────────┐
9
-│ Browser │◄────►│ Relay Server │◄────►│ App/Tunnel │
10
-│ │ TLS │ (:4017/443) │ TCP │ (SDK/CLI) │
11
-└─────────────┘ └──────────────┘ └─────────────┘
12
- │
13
- ▼
14
- ┌──────────────┐
15
- │ Local HTTP │
16
- │ (:3000 etc) │
17
- └──────────────┘
8
+High-level path:
9
+
10
+```text
11
+Client (Browser)
12
+ -> Relay (:443 SNI router or :4017 HTTP/API)
13
+ -> Reverse tunnel connection
14
+ -> Local service (App/Tunnel host)
15
```
16
20
-## Components
17
+## Core Components
18
19
### Relay Server (`cmd/relay-server`)
20
24
-- **HTTP Server**: Static files, admin UI, API endpoints
25
-- **Reverse Hub**: Connection management for tunnel backends
26
-- **Lease Manager**: Registration, TTL, banning
27
-- **SNI Router**: TLS passthrough routing
21
+- API/Admin server on `--adminport` (default `:4017`)
22
+- SNI router on `--sni-port` (default `:443`)
23
+- SDK registry endpoints under `/sdk/*`
24
+- Keyless signer endpoint at `/v1/sign` (when signer is configured)
25
+
26
+### Relay Core (`portal/`)
27
+
28
+- `LeaseManager`: lease registration, renew TTL, ban/list policy
29
+- `ReverseHub`: authenticated reverse connection pool per lease
30
+- `sni.Router`: TCP listener that peeks SNI and routes to lease backends
31
+- `acme` + `keyless`: ACME provisioning and remote signing support
32
33
### SDK (`sdk/`)
34
31
-- **Client**: Bootstrap, health checks, reconnection
32
-- **Listener**: `net.Listener` implementation for tunnel connections
33
-- **Types**: Shared API types (`RegisterRequest`, `Metadata`, etc.)
35
+- `Client`: bootstrap relay URLs and optional TLS/keyless setup
36
+- `Listener`: relay-backed `net.Listener` used by apps/tunnel clients
37
+- Shared API types and paths (`/sdk/register`, `/sdk/renew`, etc.)
38
39
### Tunnel (`cmd/portal-tunnel`)
40
37
-- TCP proxy between relay and local service
38
-- No code changes required to expose existing services
41
+- CLI proxy for existing local apps without code changes
42
+- Registers lease via SDK and forwards relay traffic to local `--host`
43
40
-## Connection Flow
44
+## Data Plane Modes
45
42
-1. **Register**: App/Tunnel → Relay (`POST /sdk/register`)
43
-2. **Reverse Connect**: App/Tunnel ← Relay (`TCP reverse tunnel`)
44
-3. **Client Request**: Browser → Relay (`GET *.localhost:4017`)
45
-4. **Proxy**: Relay ↔ App/Tunnel ↔ Local Service
46
+### TLS mode (`lease.TLS=true`)
47
47
-## Security
48
+1. Lease is registered with TLS enabled.
49
+2. Relay registers SNI route (`<lease>.<base-domain>`).
50
+3. Client connects via HTTPS to relay SNI port.
51
+4. Relay selects route by SNI and acquires reverse connection from `ReverseHub`.
52
+5. Tunnel-side listener performs TLS handshake (keyless-backed signer), relay forwards raw TCP.
53
49
-- **E2EE**: TLS passthrough with keyless certificate (`*.example.com` + `example.com` SAN) - relay routes TLS by SNI without termination
50
-- **Tokens**: Per-lease reverse connection tokens
51
-- **SNI Routing**: TLS passthrough without decryption
54
+Result: relay does SNI-based routing and forwarding; app payload stays end-to-end encrypted.
55
53
-### SNI Routing Logic
56
+### Non-TLS mode (`lease.TLS=false`)
57
55
-The SNI router (`portal/sni/router.go`) matches routes in this order:
58
+1. Client connects over HTTP to relay/admin port.
59
+2. Relay resolves lease by subdomain and acquires reverse connection.
60
+3. Relay proxies HTTP request/response through reverse tunnel.
61
57
-1. **Exact match**: `foo.example.com` → registered route
58
-2. **Wildcard match**: `*.example.com` matches `foo.example.com`, `bar.example.com`, etc.
59
-3. **No match**: Falls through to `onNoRoute` handler (e.g., apex domain `example.com`)
62
+Result: simple HTTP proxy path for development or non-TLS services.
63
61
-Note: `*.example.com` does NOT match `example.com` (apex) - wildcards only match a single DNS label.
64
+## Control Plane Flow
65
63
-## Protocol Stack
66
+### 1. Register
67
65
-```
66
-TLS (E2EE via keyless certificate)
67
- └── TCP
68
- └── Application Data
69
-```
68
+- App/tunnel posts to `POST /sdk/register` with:
69
+ - `lease_id`
70
+ - `name`
71
+ - `metadata`
72
+ - `tls`
73
+ - `reverse_token`
74
+- Relay stores lease and (TLS only) registers SNI route.
75
71
-Or for non-TLS (development only):
76
+### 2. Reverse Connect
77
73
-```
74
-HTTP
75
- └── TCP
76
- └── Application Data
77
-```
78
+- Backend opens websocket to `GET /sdk/connect?lease_id=...`
79
+- `X-Portal-Reverse-Token` is validated server-side.
80
+- Connection is pooled in `ReverseHub`.
81
+
82
+### 3. Renew
83
+
84
+- Backend sends `POST /sdk/renew` keepalive.
85
+- Relay refreshes lease TTL and keeps route state current.
86
+
87
+### 4. Unregister
88
+
89
+- Backend sends `POST /sdk/unregister`.
90
+- Relay removes lease, route, and reverse pool.
91
+
92
+## Routing Behavior
93
+
94
+`sni.Router` route lookup order:
95
+
96
+1. Exact host match
97
+2. Single-label wildcard (`*.example.com`)
98
+3. No-route handler (used for portal root-domain fallback)
99
+
100
+Note: wildcard does not match apex domain (`example.com`).
101
+
102
+## Keyless and Certificates
103
+
104
+- Relay keyless materials are stored in `KEYLESS_DIR`:
105
+ - `fullchain.pem`
106
+ - `privatekey.pem`
107
+- If materials are missing and Cloudflare token is configured, relay can provision via ACME DNS-01.
108
+- SDK/tunnel TLS mode uses keyless signer workflow and `/v1/sign` for remote signatures.
109
+
110
+## Important Design Properties
111
+
112
+- Reverse-only backend connectivity (no inbound port on app host required)
113
+- Per-lease reverse token authorization
114
+- Separation of control plane (`/sdk/*`) and data plane (SNI/HTTP forwarding)
115
+- Unified lease abstraction for routing, metadata, and lifecycle
docs/deployment.md
new
+89
@@ -0,0 +1,89 @@
1
+# Portal Relay Deploy Guide
2
+
3
+## Prerequisites
4
+
5
+- Public domain (example: `example.com`)
6
+- Public server with public IP
7
+- Docker, Docker Compose
8
+
9
+## Quick Start
10
+
11
+### 1. Register Domain in Cloudflare
12
+
13
+In Cloudflare Dashboard:
14
+1. Go to `Websites`
15
+2. Click `Add a Site`
16
+3. Enter your domain (example: `example.com`)
17
+4. Select a plan and complete setup
18
+5. Update nameservers at your registrar to the nameservers provided by Cloudflare
19
+6. Confirm the zone status is `Active`
20
+
21
+### 2. Register DNS Records
22
+
23
+In Cloudflare Dashboard:
24
+1. Open your domain dashboard (`example.com`)
25
+2. Go to `DNS` -> `Records`
26
+3. Click `Add record`
27
+
28
+Create record 1 (root domain):
29
+- Type: `A`
30
+- Name: `@`
31
+- IPv4 address: `<server-ip>`
32
+- Proxy status: `DNS only`
33
+- Save
34
+
35
+Create record 2 (wildcard subdomain):
36
+- Type: `A`
37
+- Name: `*`
38
+- IPv4 address: `<server-ip>`
39
+- Proxy status: `DNS only`
40
+- Save
41
+
42
+Example result:
43
+- `example.com -> <server-ip>`
44
+- `*.example.com -> <server-ip>`
45
+
46
+### 3. Create Cloudflare API Token
47
+
48
+Create token in Cloudflare Dashboard:
49
+1. `My Profile` -> `API Tokens` -> `Create Token`
50
+2. Custom permissions:
51
+ - `Zone:Read`
52
+ - `DNS:Edit`
53
+3. Zone resources: include your target zone (`example.com`)
54
+4. Copy and save the token value (you will use it as `CLOUDFLARE_TOKEN`)
55
+
56
+### 4. Run the Relay Server
57
+
58
+Create `.env` in repository root:
59
+
60
+```bash
61
+PORTAL_URL=https://example.com
62
+ADMIN_SECRET_KEY=your-admin-key
63
+SNI_PORT=443
64
+KEYLESS_DIR=/etc/portal/keyless
65
+CLOUDFLARE_TOKEN=cf_xxxxxxxxxxxxxxxxx
66
+```
67
+
68
+Run Docker Compose
69
+
70
+```bash
71
+docker compose up -d
72
+docker compose logs -f
73
+```
74
+
75
+## Troubleshooting
76
+
77
+### Inbound ports are blocked
78
+
79
+Required inbound ports:
80
+- `443/tcp`
81
+- `4017/tcp`
82
+
83
+Linux example (UFW):
84
+
85
+```bash
86
+sudo ufw allow 443/tcp
87
+sudo ufw allow 4017/tcp
88
+sudo ufw status
89
+```
docs/development.md
deleted
-51
@@ -1,51 +0,0 @@
1
-# Development Guide
2
-
3
-## Quick Start
4
-
5
-```bash
6
-# Clone and build
7
-git clone https://github.com/gosuda/portal.git
8
-cd portal
9
-make build
10
-
11
-# Run tests
12
-make test
13
-
14
-# Run relay server locally
15
-make run
16
-```
17
-
18
-## Project Structure
19
-
20
-```
21
-cmd/
22
- relay-server/ # Relay server entrypoint
23
- portal-tunnel/ # Tunnel CLI client
24
- demo-app/ # Demo application
25
-portal/ # Core relay logic
26
-sdk/ # Go SDK for apps
27
-utils/ # Shared utilities
28
-```
29
-
30
-## Key Commands
31
-
32
-| Command | Description |
33
-|---------|-------------|
34
-| `make build` | Build all components |
35
-| `make test` | Run tests with race detector |
36
-| `make lint` | Run golangci-lint |
37
-| `make fmt` | Format code |
38
-| `make run` | Run relay server |
39
-
40
-## Guidelines
41
-
42
-1. **No breaking changes** to existing workflows without team discussion
43
-2. **Test before merging** — all features must be verified in a branch
44
-3. **Follow existing patterns** — check similar code before adding new features
45
-4. **Run linters** before committing: `make fmt && make lint`
46
-
47
-## Architecture Decisions
48
-
49
-- **E2EE**: TLS passthrough with keyless certificates
50
-- **SNI Routing**: TLS routed by hostname without termination
51
-- **No CGO**: pure Go for cross-platform builds
docs/glossary.md
+52
-23
@@ -1,36 +1,65 @@
1
-Portal is unlike traditional server–client architectures, both the App and the Client act as clients within the Portal network, which can easily cause terminology confusion. In addition, names such as "Portal" (relay server) and legacy RD-related function names often overlap. This glossary clarifies those terms.
1
+# Glossary
2
3
-### Portal (Relay Server)
3
+This glossary defines key terms used in Portal.
4
5
-The Portal is the relay hub provided by this project.
6
-It acts as a central mediator, while other components either advertise themselves to the Portal or discover their counterparts through it.
5
+## Portal / Relay
6
8
-* Handles lease registration/deletion/query, connection forwarding, and traffic control (BPS).
9
-* Does not decrypt TLS payloads and only provides SNI-based routing. TLS mode provides end-to-end encryption between client and tunnel.
7
+The central server that handles lease registration, routing, and reverse connection brokering.
8
+It does not terminate end-to-end app payload in TLS passthrough mode.
9
11
-### App (Service Publisher)
10
+## App (Service Publisher)
11
13
-An App is a service-providing entity that publishes services to the Portal.
14
-In practice, server-side code that uses the Portal SDK to communicate with the Portal is considered an App.
12
+A service provider connected to Portal through Tunnel or Native SDK.
13
+An app publishes one or more leases and serves traffic from local services.
14
16
-* Holds at least one credential, connects to the Portal with it, and registers a Lease that represents the App.
17
-* Maintains a TCP connection with the Portal and proxies incoming connection requests to a local service (TCP/HTTP, etc.).
18
-* Updates metadata, ALPN, and other advertising attributes via the Portal Frontend or API to improve discoverability.
15
+## Client (Service Consumer)
16
20
-### Client (Service Consumer)
17
+The external user or browser that accesses a published service through the relay domain.
18
22
-A Client is the end-user entity that attempts to access services published by an App through the Portal.
19
+## Tunnel
20
24
-* Currently, this is primarily the browser connecting via TLS.
25
-* A Client requests a connection to the Portal by specifying a Lease ID or name. The Portal matches this request to the App owning that Lease, then routes the TLS connection by SNI.
26
-* TLS mode provides end-to-end encryption between client and tunnel.
21
+The CLI-based publisher path (`cmd/portal-tunnel`).
22
+It exposes existing local apps without code changes by forwarding relay traffic to a local host/port.
23
28
-### Lease (Advertising Slot)
24
+## Native SDK
25
30
-A Lease is the advertisement unit an App publishes to the Portal.
26
+The code integration path (`sdk/`) for Go applications.
27
+It provides relay-backed listener APIs and metadata control for direct app integration.
28
32
-* Consists of an identity (App credential), expiration time, display name, allowed ALPN list, and optional metadata (JSON).
33
-* Managed by the Portal’s LeaseManager, which handles registration, renewal, expiration, deletion, and enforces name conflict rules, banned IDs, and TTL/BPS policies.
34
-* A Client requests a connection from the Portal using a Lease ID (= credential ID) or name, and the Portal uses this information to locate the corresponding App’s RelayClient.
29
+## Lease
30
36
-A Lease is the fundamental routing unit: **one Lease equals one public endpoint.**
31
+The routing and advertisement unit in Portal.
32
+Each lease maps to one public endpoint and includes identity, name, metadata, TLS flag, and reverse token.
33
+
34
+## Lease Name
35
+
36
+The human-readable lease identifier used for subdomain routing (for example, `myapp` -> `myapp.example.com`).
37
+
38
+## Reverse Token
39
+
40
+A per-lease secret used to authenticate reverse connections (`/sdk/connect`) from backend to relay.
41
+
42
+## ReverseHub
43
+
44
+The relay-side pool of authenticated reverse connections, keyed by lease ID.
45
+It provides connections for TLS SNI forwarding and HTTP proxy forwarding.
46
+
47
+## SNI Router
48
+
49
+The TCP router on relay SNI port (default `443`) that selects lease routes by TLS Server Name Indication (SNI).
50
+
51
+## Keyless TLS
52
+
53
+A mode where the backend handles TLS with remote signing support via relay signer endpoint (`/v1/sign`), without local private key distribution.
54
+
55
+## ACME DNS-01
56
+
57
+The certificate issuance/renewal method used with Cloudflare DNS API token when keyless materials are missing.
58
+
59
+## Base Domain
60
+
61
+The root domain derived from `PORTAL_URL` (for example, `example.com`) used to build service subdomains.
62
+
63
+## Admin/API Server
64
+
65
+The relay HTTP server (default `:4017`) that serves admin UI and SDK/control endpoints such as `/sdk/*`, `/admin`, and `/healthz`.
docs/portal-deploy-guide.md
deleted
-273
@@ -1,273 +0,0 @@
1
-# Portal Deploy Guide
2
-
3
-How to run a public Portal relay with TLS passthrough.
4
-
5
-## Overview
6
-
7
-Portal uses SNI-based TLS passthrough: the relay routes TLS by SNI to tunnel backends.
8
-
9
-TLS certificate mode:
10
-- `self`: tunnel uses locally managed certificate and key files.
11
-- `keyless`: tunnel delegates TLS signing to relay keyless signer (`/v1/sign`). Relay uses `KEYLESS_DIR` and can auto-issue key/cert via ACME DNS-01 when `CLOUDFLARE_TOKEN` is set.
12
- Relay uses one unified cert/key pair at `KEYLESS_DIR/fullchain.pem` and `KEYLESS_DIR/privatekey.pem`.
13
- The certificate covers both `*.example.com` and `example.com` (SAN), and relay admin/API on `--adminport` is served over HTTPS automatically.
14
- Admin/API HTTPS is served as HTTP/1.1 only.
15
-
16
-```
17
-Client ──TLS──► Relay (SNI Router :443) ──TLS──► Tunnel Backend (TLS mode)
18
- │
19
- └── Self or keyless mode at tunnel
20
-```
21
-
22
-## Prerequisites
23
-
24
-- A public server (VPS / cloud VM / on-prem with port forwarding)
25
-- A domain you can manage
26
-- Ports 80, 443, and 4017 open to the Internet
27
-- DNS A/AAAA records pointing to your server:
28
- - `example.com -> <server IP>`
29
- - `*.example.com -> <server IP>`
30
-- For `self` mode: wildcard TLS certificate and private key for `*.example.com` on the tunnel host.
31
-- For `keyless` mode: either an existing relay signing key at `KEYLESS_DIR`, or `CLOUDFLARE_TOKEN` for ACME DNS-01 auto issuance.
32
-
33
-## Quick Start
34
-
35
-### 1. DNS Configuration
36
-
37
-```
38
-# A records (IPv4)
39
-example.com. A 203.0.113.10
40
-*.example.com. A 203.0.113.10
41
-
42
-# AAAA records (IPv6, if applicable)
43
-example.com. AAAA 2001:db8::1
44
-*.example.com. AAAA 2001:db8::1
45
-```
46
-
47
-### 2. Create .env File
48
-
49
-```bash
50
-# Core
51
-PORTAL_URL=https://example.com
52
-ADMIN_SECRET_KEY=your-secure-key-here
53
-CLOUDFLARE_TOKEN=your-cloudflare-dns-token
54
-# Optional (default: /etc/portal/keyless)
55
-# KEYLESS_DIR=/etc/portal/keyless
56
-```
57
-
58
-### 3. Run Portal
59
-
60
-```bash
61
-docker compose up -d
62
-```
63
-
64
-### 4. Run Tunnel
65
-
66
-```bash
67
-portal-tunnel --host localhost:3000 --name myapp --relay https://example.com --tls-mode keyless
68
-```
69
-
70
-### 5. Access
71
-
72
-```
73
-https://myapp.example.com
74
-```
75
-
76
-## Environment Variables
77
-
78
-### Core
79
-
80
-| Variable | Default | Description |
81
-|----------|---------|-------------|
82
-| `PORTAL_URL` | `http://localhost:4017` | Base URL (e.g., `https://example.com`) |
83
-| `BOOTSTRAP_URIS` | (derived) | Relay API URLs |
84
-| `ADMIN_SECRET_KEY` | (auto-generated) | Admin authentication key |
85
-| `SNI_PORT` | `443` | SNI router port |
86
-| `KEYLESS_DIR` | `/etc/portal/keyless` | Relay keyless materials directory (`fullchain.pem` + `privatekey.pem` used for signer and admin/API HTTPS, HTTP/1.1 only) |
87
-| `CLOUDFLARE_TOKEN` | (empty) | Cloudflare DNS API token used for ACME DNS-01 auto issuance |
88
-
89
-## docker-compose.yml
90
-
91
-```yaml
92
-services:
93
- portal:
94
- image: ghcr.io/gosuda/portal:1
95
- command:
96
- - "--adminport"
97
- - "4017"
98
- environment:
99
- PORTAL_URL: ${PORTAL_URL}
100
- ADMIN_SECRET_KEY: ${ADMIN_SECRET_KEY}
101
- SNI_PORT: ${SNI_PORT:-443}
102
- KEYLESS_DIR: ${KEYLESS_DIR:-/etc/portal/keyless}
103
- CLOUDFLARE_TOKEN: ${CLOUDFLARE_TOKEN:-}
104
- ports:
105
- - "4017:4017"
106
- - "443:443"
107
- - "80:80"
108
- restart: unless-stopped
109
-```
110
-
111
-## Tunnel Client
112
-
113
-### TLS Mode (Production)
114
-
115
-```bash
116
-portal-tunnel --host localhost:3000 --name myapp --relay https://example.com --tls-mode keyless
117
-```
118
-
119
-- `--host`: Local service address
120
-- `--name`: Subdomain name (becomes `myapp.example.com`)
121
-- `--relay`: Portal relay URL
122
-- `--tls-mode`: `no-tls`, `self`, or `keyless`
123
-
124
-How it works:
125
-1. Tunnel performs TLS handshake on reverse tunnel connections
126
-2. In `self` mode, tunnel signs locally with certificate key
127
-3. In `keyless` mode, tunnel requests signatures from external signer API
128
-4. TLS connections go directly to tunnel on port 443
129
-
130
-### TLS Self Mode (Tunnel-Managed Certificate)
131
-
132
-```bash
133
-portal-tunnel \
134
- --host localhost:3000 \
135
- --name myapp \
136
- --relay https://example.com \
137
- --tls-mode self \
138
- --tls-cert-file /etc/ssl/myapp/fullchain.pem \
139
- --tls-key-file /etc/ssl/myapp/privkey.pem
140
-```
141
-
142
-- Relay keyless endpoints are not used in self mode.
143
-- Tunnel must have direct access to certificate and key files.
144
-
145
-### TLS Keyless Mode (External Signer)
146
-
147
-```bash
148
-portal-tunnel \
149
- --host localhost:3000 \
150
- --name myapp \
151
- --relay https://example.com \
152
- --tls-mode keyless
153
-```
154
-
155
-- Keyless signer endpoint, key id, trust roots, and certificate chain are auto-configured by SDK defaults.
156
-- Auto-discovery expects an HTTPS signer endpoint.
157
-- External signer API must return TLS signature responses for the requested digest.
158
-- Relay keyless signer key path is configured by `KEYLESS_DIR`.
159
-- When key/cert files are missing and `CLOUDFLARE_TOKEN` is set, relay auto-issues a unified certificate (`*.example.com` + `example.com`) via ACME DNS-01.
160
-
161
-Signer API request/response example:
162
-
163
-```json
164
-{
165
- "key_id": "relay-cert",
166
- "algorithm": "RSA_PSS_SHA256",
167
- "digest": "<base64>",
168
- "timestamp_unix": 1735628400,
169
- "nonce": "c4d76ad40f5d8f95a1fe4b2f1c922f4a"
170
-}
171
-```
172
-
173
-```json
174
-{
175
- "key_id": "relay-cert",
176
- "algorithm": "RSA_PSS_SHA256",
177
- "signature": "<base64>"
178
-}
179
-```
180
-
181
-### Non-TLS Mode (Development Only)
182
-
183
-```bash
184
-portal-tunnel --host localhost:3000 --name myapp --relay https://example.com
185
-```
186
-
187
-**Not recommended for production.** Use TLS mode instead.
188
-
189
-- No TLS certificate issued
190
-- HTTP requests proxied through relay on port 4017
191
-- No end-to-end encryption between client and tunnel
192
-
193
-## Port Summary
194
-
195
-| Port | Service | Description |
196
-|------|---------|-------------|
197
-| 443 | SNI Router | TLS passthrough to tunnel backends |
198
-| 4017 | HTTP Relay | Admin UI, API, HTTP proxy |
199
-| 80 | HTTP | Redirect to HTTPS (optional) |
200
-
201
-## Validate
202
-
203
-```bash
204
-# Health check
205
-curl https://example.com/healthz
206
-# Expected: {"status":"ok"}
207
-
208
-# Domain API
209
-curl https://example.com/sdk/domain
210
-# Expected: {"success":true,"base_domain":"example.com"}
211
-
212
-# Tunnel script
213
-curl -fsSL https://example.com/tunnel | APP_HOST=localhost:3000 APP_NAME=test sh
214
-```
215
-
216
-## Architecture
217
-
218
-```
219
- ┌─────────────────────────────────────────────────────┐
220
- │ Portal Relay │
221
- │ │
222
- :443 TLS ──────►│ ┌─────────────┐ ┌─────────────────────────────┐ │
223
- │ │ SNI Router │───►│ Tunnel Backend │ │
224
- │ │ │ │ w/ keyless certificate │ │
225
- │ └─────────────┘ └─────────────────────────────┘ │
226
- │ │
227
- ├─────────────────────────────────────────────────────┤
228
- │ │
229
- :4017 HTTP ────►│ ┌─────────────┐ ┌─────────────────────────────┐ │
230
- │ │ HTTP Server │───►│ Tunnel (dev mode, no TLS) │ │
231
- │ │ │ │ or redirect to HTTPS │ │
232
- │ └─────────────┘ └─────────────────────────────┘ │
233
- │ │ │
234
- │ ▼ │
235
- │ ┌─────────────┐ ┌─────────────┐ │
236
- │ │ Admin UI │ │ API │ │
237
- │ │ /admin │ │ /sdk/* │ │
238
- │ └─────────────┘ └─────────────┘ │
239
- │ │
240
- └─────────────────────────────────────────────────────┘
241
-```
242
-
243
-## Troubleshooting
244
-
245
-### SNI Router Fails to Start
246
-
247
-```bash
248
-# Check if port 443 is in use
249
-sudo netstat -tlnp | grep :443
250
-
251
-# Grant capability to bind privileged port
252
-sudo setcap 'cap_net_bind_service=+ep' ./bin/relay-server
253
-```
254
-
255
-### TLS Certificate Load Fails
256
-
257
-```bash
258
-# Verify self TLS certificate files
259
-ls -l /etc/ssl/myapp/fullchain.pem /etc/ssl/myapp/privkey.pem
260
-
261
-# Check logs
262
-docker compose logs portal
263
-```
264
-
265
-### Tunnel Cannot Connect
266
-
267
-```bash
268
-# Check relay is running
269
-curl https://example.com/healthz
270
-
271
-# Run tunnel with verbose logging
272
-portal-tunnel --host localhost:3000 --name test --relay https://example.com --tls-mode keyless
273
-```