feat: remove self tls mode
Kim committed
Mar 3, 2026 at 12:08 UTC
2a3b1e742668982ecdf831c1b044ae6ac08b915e
18 files changed
+215
-421
AGENTS.md
+5
-8
@@ -87,9 +87,7 @@ Portal uses environment variables for domain and TLS configuration:
87
| Variable | Description |
88
|----------|-------------|
89
| `RELAYS` | Relay API URLs for tunnel client (comma-separated) |
90
-| `TLS_MODE` | `no-tls`, `self`, or `keyless` |
91
-| `TLS_CERT_FILE` | Self TLS certificate chain path (self mode only) |
92
-| `TLS_KEY_FILE` | Self TLS private key path (self mode only) |
90
+| `TLS` | Enable TLS keyless mode (`1`/`true`) |
91
| `TLS_BASE_DOMAIN` | Base domain used for keyless certificate hostname validation (keyless mode) |
92
93
### Domain Derivation
@@ -98,12 +96,11 @@ Portal uses environment variables for domain and TLS configuration:
96
- Base domain extracted from `PORTAL_URL` via `extractBaseDomain()` in `cmd/relay-server/utils.go`
97
- SNI routes registered in `portal/sni/router.go`
98
101
-### TLS Modes
99
+### TLS
100
103
-1. **`no-tls`**: HTTP proxy mode for development.
104
-2. **`self`**: Tunnel uses locally provided certificate and key (`TLS_CERT_FILE` + `TLS_KEY_FILE`).
105
-3. **`keyless`**: Tunnel uses SDK keyless mode with auto defaults.
106
- - Keyless signer endpoint defaults to relay URL unless explicitly overridden in SDK options.
101
+1. **`TLS` disabled**: HTTP proxy mode for development.
102
+2. **`TLS` enabled**: Tunnel uses SDK keyless TLS mode.
103
+ - Keyless signer endpoint defaults to relay URL.
104
- Certificate chain/root trust are auto-discovered by SDK from signer endpoint when not explicitly provided.
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.
cmd/demo-app/main.go
+5
-19
@@ -34,9 +34,7 @@ var (
34
flagTags string
35
flagOwner string
36
flagHide bool
37
- flagTLSMode string
38
- flagTLSCert string
39
- flagTLSKey string
37
+ flagTLS bool
38
)
39
40
func main() {
@@ -47,13 +45,8 @@ func main() {
45
flag.StringVar(&flagTags, "tags", "demo,connectivity,activity,cloud,sun,moning", "comma-separated lease tags")
46
flag.StringVar(&flagOwner, "owner", "PortalApp Developer", "lease owner")
47
flag.BoolVar(&flagHide, "hide", false, "hide this lease from listings")
50
- defaultTLSMode := os.Getenv("TLS_MODE")
51
- if defaultTLSMode == "" {
52
- defaultTLSMode = string(sdk.TLSModeNoTLS)
53
- }
54
- flag.StringVar(&flagTLSMode, "tls-mode", defaultTLSMode, "TLS mode: no-tls, self, or keyless [env: TLS_MODE]")
55
- flag.StringVar(&flagTLSCert, "tls-cert-file", os.Getenv("TLS_CERT_FILE"), "certificate chain PEM file for --tls-mode self [env: TLS_CERT_FILE]")
56
- flag.StringVar(&flagTLSKey, "tls-key-file", os.Getenv("TLS_KEY_FILE"), "private key PEM file for --tls-mode self [env: TLS_KEY_FILE]")
48
+ defaultTLS := strings.EqualFold(strings.TrimSpace(os.Getenv("TLS")), "true") || strings.TrimSpace(os.Getenv("TLS")) == "1"
49
+ flag.BoolVar(&flagTLS, "tls", defaultTLS, "Enable TLS (keyless) [env: TLS]")
50
51
flag.Parse()
52
@@ -65,15 +58,8 @@ func main() {
58
func runDemo() error {
59
// 1) Create SDK client and connect to relay(s)
60
opts := []sdk.ClientOption{sdk.WithBootstrapServers([]string{flagServerURL})}
68
- mode := sdk.TLSMode(flagTLSMode)
69
- switch mode {
70
- case sdk.TLSModeNoTLS:
71
- case sdk.TLSModeSelf:
72
- opts = append(opts, sdk.WithTLSSelfCertificateFiles(flagTLSCert, flagTLSKey))
73
- case sdk.TLSModeKeyless:
74
- opts = append(opts, sdk.WithTLSKeylessDefaults())
75
- default:
76
- return fmt.Errorf("unsupported tls mode: %s", flagTLSMode)
61
+ if flagTLS {
62
+ opts = append(opts, sdk.WithTLS())
63
}
64
client, err := sdk.NewClient(opts...)
65
if err != nil {
cmd/portal-tunnel/README.md
+9
-29
@@ -25,19 +25,17 @@ Enable TLS for end-to-end encryption from client to your local service:
25
./bin/portal-tunnel --host localhost:8080 \
26
--relay https://portal.example.com \
27
--name myapp \
28
- --tls-mode keyless
28
+ --tls
29
```
30
31
-TLS modes:
32
-- No TLS mode (`--tls-mode no-tls`): plain TCP/HTTP proxying without TLS termination
33
-- Self mode (`--tls-mode self`): tunnel uses local certificate and key files
34
-- Keyless mode (`--tls-mode keyless`): tunnel auto-discovers certificate chain and delegates signing to external signer API
31
+TLS options:
32
+- TLS disabled (default): plain TCP/HTTP proxying without TLS termination
33
+- TLS enabled (`--tls`): keyless TLS mode with auto-discovered certificate chain and remote signing
34
- TLS is terminated at the tunnel, then proxied to your local service via TCP
35
- Access via `https://myapp.example.com` directly on port 443
36
37
**Requirements:**
39
-- Self mode: set `TLS_CERT_FILE` and `TLS_KEY_FILE` (or use `--tls-cert-file`, `--tls-key-file`)
40
-- Keyless mode: no local cert/key required in default mode (SDK auto-discovers signer certificate chain)
38
+- TLS enabled: no local cert/key required in default mode (SDK auto-discovers signer certificate chain)
39
- Keyless auto-discovery expects an HTTPS signer endpoint.
40
41
## Flags
@@ -50,10 +48,7 @@ Options:
48
--relay Portal relay server API URLs (comma-separated, http/https) [default: http://localhost:4017] [env: RELAYS]
49
--host Target host to proxy to (host:port or URL) [env: APP_HOST]
50
--name Service name [env: APP_NAME]
53
- --tls-mode TLS mode: no-tls, self, or keyless [default: no-tls] [env: TLS_MODE]
54
- --tls-cert-file PEM certificate chain for --tls-mode self [env: TLS_CERT_FILE]
55
- --tls-key-file PEM private key for --tls-mode self [env: TLS_KEY_FILE]
56
- --tls-base-domain Base domain for keyless certificate validation (e.g., example.com) [env: TLS_BASE_DOMAIN]
51
+ --tls Enable keyless TLS mode [env: TLS]
52
--description Service description metadata [env: APP_DESCRIPTION]
53
--tags Service tags metadata (comma-separated) [env: APP_TAGS]
54
--thumbnail Service thumbnail URL metadata [env: APP_THUMBNAIL]
@@ -80,21 +75,7 @@ $env:APP_HOST="localhost:3000"; $env:APP_NAME="myapp"; irm https://portal.exampl
75
export RELAYS=https://portal.example.com
76
export APP_HOST=localhost:3000
77
export APP_NAME=myapp
83
-export TLS_MODE=keyless
84
-export TLS_BASE_DOMAIN=example.com
85
-
86
-./bin/portal-tunnel
87
-```
88
-
89
-### Production (Self TLS)
90
-
91
-```bash
92
-export RELAYS=https://portal.example.com
93
-export APP_HOST=localhost:3000
94
-export APP_NAME=myapp
95
-export TLS_MODE=self
96
-export TLS_CERT_FILE=/etc/ssl/myapp/fullchain.pem
97
-export TLS_KEY_FILE=/etc/ssl/myapp/privkey.pem
78
+export TLS=1
79
80
./bin/portal-tunnel
81
```
@@ -105,8 +86,7 @@ export TLS_KEY_FILE=/etc/ssl/myapp/privkey.pem
86
export RELAYS=https://portal.example.com
87
export APP_HOST=localhost:3000
88
export APP_NAME=myapp
108
-export TLS_MODE=keyless
109
-export TLS_BASE_DOMAIN=example.com
89
+export TLS=1
90
91
./bin/portal-tunnel
92
```
@@ -138,5 +118,5 @@ Expected signer API contract (`/v1/sign`):
118
--host localhost:3000 \
119
--name myapp \
120
--relay https://portal1.example.com,https://portal2.example.com \
141
- --tls-mode keyless
121
+ --tls
122
```
cmd/portal-tunnel/main.go
+8
-23
@@ -28,9 +28,7 @@ var (
28
flagThumbnail string
29
flagOwner string
30
flagHide bool
31
- flagTLSMode string
32
- flagTLSCert string
33
- flagTLSKey string
31
+ flagTLS bool
32
)
33
34
func main() {
@@ -45,13 +43,8 @@ func main() {
43
flag.StringVar(&flagHost, "host", os.Getenv("APP_HOST"), "Target host to proxy to (host:port or URL) [env: APP_HOST]")
44
flag.StringVar(&flagName, "name", os.Getenv("APP_NAME"), "Service name [env: APP_NAME]")
45
48
- defaultTLSMode := os.Getenv("TLS_MODE")
49
- if defaultTLSMode == "" {
50
- defaultTLSMode = string(sdk.TLSModeNoTLS)
51
- }
52
- flag.StringVar(&flagTLSMode, "tls-mode", defaultTLSMode, "TLS mode: no-tls, self, or keyless [env: TLS_MODE]")
53
- flag.StringVar(&flagTLSCert, "tls-cert-file", os.Getenv("TLS_CERT_FILE"), "PEM certificate chain for --tls-mode self [env: TLS_CERT_FILE]")
54
- flag.StringVar(&flagTLSKey, "tls-key-file", os.Getenv("TLS_KEY_FILE"), "PEM private key for --tls-mode self [env: TLS_KEY_FILE]")
46
+ defaultTLS := strings.EqualFold(strings.TrimSpace(os.Getenv("TLS")), "true") || strings.TrimSpace(os.Getenv("TLS")) == "1"
47
+ flag.BoolVar(&flagTLS, "tls", defaultTLS, "Enable TLS (keyless) [env: TLS]")
48
49
flag.StringVar(&flagDesc, "description", os.Getenv("APP_DESCRIPTION"), "Service description metadata [env: APP_DESCRIPTION]")
50
flag.StringVar(&flagTags, "tags", os.Getenv("APP_TAGS"), "Service tags metadata (comma-separated) [env: APP_TAGS]")
@@ -81,20 +74,12 @@ func runTunnel() error {
74
log.Info().Msg("Starting Portal Tunnel...")
75
log.Info().Msgf(" Local: %s", flagHost)
76
log.Info().Msgf(" Relays: %s", strings.Join(relayURLs, ", "))
84
- log.Info().Msgf(" TLS Mode: %s", flagTLSMode)
77
+ log.Info().Msgf(" TLS: %t", flagTLS)
78
79
opts := []sdk.ClientOption{sdk.WithBootstrapServers(relayURLs)}
87
- mode := sdk.TLSMode(flagTLSMode)
88
- switch mode {
89
- case sdk.TLSModeNoTLS:
90
- case sdk.TLSModeSelf:
91
- opts = append(opts, sdk.WithTLSSelfCertificateFiles(flagTLSCert, flagTLSKey))
92
- case sdk.TLSModeKeyless:
93
- opts = append(opts, sdk.WithTLSKeylessDefaults())
94
- default:
95
- return fmt.Errorf("unsupported tls mode: %s", flagTLSMode)
80
+ if flagTLS {
81
+ opts = append(opts, sdk.WithTLS())
82
}
97
-
83
client, err := sdk.NewClient(opts...)
84
if err != nil {
85
return fmt.Errorf("service %s: failed to create client: %w", flagName, err)
@@ -125,7 +110,7 @@ func runTunnel() error {
110
if leaseAware, ok := listener.(interface{ LeaseID() string }); ok {
111
log.Info().Msgf("- Lease ID: %s", leaseAware.LeaseID())
112
}
128
- if flagTLSMode != string(sdk.TLSModeNoTLS) {
113
+ if flagTLS {
114
log.Info().Msg("- TLS: Enabled")
115
}
116
@@ -160,7 +145,7 @@ loop:
145
connWG.Add(1)
146
go func(relayConn net.Conn) {
147
defer connWG.Done()
163
- tlsEnabled := flagTLSMode != string(sdk.TLSModeNoTLS)
148
+ tlsEnabled := flagTLS
149
proxyType := "TCP"
150
if tlsEnabled {
151
proxyType = "TLS→TCP"
cmd/relay-server/frontend/src/components/TunnelCommandModal.tsx
+20
-130
@@ -1,4 +1,4 @@
1
-import { useState, useMemo, useRef, useEffect } from "react";
1
+import { useState, useMemo, useEffect } from "react";
2
import { Copy, Check, Terminal, X } from "lucide-react";
3
import { cn } from "@/lib/utils";
4
import {
@@ -16,8 +16,6 @@ interface TunnelCommandModalProps {
16
trigger?: React.ReactNode;
17
}
18
19
-type TLSMode = "no-tls" | "self" | "keyless";
20
-
19
export function TunnelCommandModal({ trigger }: TunnelCommandModalProps) {
20
const defaultHost = "localhost:3000";
21
const defaultName = "your-app-name";
@@ -36,11 +34,8 @@ export function TunnelCommandModal({ trigger }: TunnelCommandModalProps) {
34
const [urlInput, setUrlInput] = useState("");
35
const [copied, setCopied] = useState(false);
36
const [os, setOs] = useState<"unix" | "windows">("unix");
39
- const [tlsMode, setTlsMode] = useState<TLSMode>("keyless");
40
- const [tlsCertFile, setTlsCertFile] = useState("");
41
- const [tlsKeyFile, setTlsKeyFile] = useState("");
42
- const urlInputRef = useRef<HTMLInputElement>(null);
43
- const keylessAvailable = useMemo(() => {
37
+ const [tls, setTls] = useState(true);
38
+ const tlsAvailable = useMemo(() => {
39
if (relayUrls.length === 0) {
40
return false;
41
}
@@ -54,10 +49,10 @@ export function TunnelCommandModal({ trigger }: TunnelCommandModalProps) {
49
}, [relayUrls]);
50
51
useEffect(() => {
57
- if (!keylessAvailable && tlsMode === "keyless") {
58
- setTlsMode("no-tls");
52
+ if (!tlsAvailable && tls) {
53
+ setTls(false);
54
}
60
- }, [keylessAvailable, tlsMode]);
55
+ }, [tlsAvailable, tls]);
56
57
const addRelayUrl = (url: string) => {
58
const trimmed = url.trim();
@@ -98,34 +93,13 @@ export function TunnelCommandModal({ trigger }: TunnelCommandModalProps) {
93
relayUrls.length > 0 ? relayUrls.join(",") : currentOrigin;
94
95
if (os === "windows") {
101
- let tlsEnv = `$env:TLS_MODE="${tlsMode}"; `;
102
- if (tlsMode === "self" && tlsCertFile.trim() !== "") {
103
- tlsEnv += `$env:TLS_CERT_FILE="${tlsCertFile}"; `;
104
- }
105
- if (tlsMode === "self" && tlsKeyFile.trim() !== "") {
106
- tlsEnv += `$env:TLS_KEY_FILE="${tlsKeyFile}"; `;
107
- }
96
+ const tlsEnv = tls ? `$env:TLS="1"; ` : "";
97
return `$ProgressPreference = 'SilentlyContinue'; ${tlsEnv}$env:APP_HOST="${hostVal}"; $env:APP_NAME="${nameVal}"; $env:RELAYS="${relayUrlVal}"; irm ${currentOrigin}/tunnel?os=windows | iex`;
98
}
99
111
- let tlsEnv = `TLS_MODE=${tlsMode} `;
112
- if (tlsMode === "self" && tlsCertFile.trim() !== "") {
113
- tlsEnv += `TLS_CERT_FILE="${tlsCertFile}" `;
114
- }
115
- if (tlsMode === "self" && tlsKeyFile.trim() !== "") {
116
- tlsEnv += `TLS_KEY_FILE="${tlsKeyFile}" `;
117
- }
100
+ const tlsEnv = tls ? "TLS=1 " : "";
101
return `curl -fsSL ${currentOrigin}/tunnel | ${tlsEnv}APP_HOST=${hostVal} APP_NAME=${nameVal} RELAYS="${relayUrlVal}" sh`;
119
- }, [
120
- currentOrigin,
121
- host,
122
- name,
123
- relayUrls,
124
- os,
125
- tlsMode,
126
- tlsCertFile,
127
- tlsKeyFile,
128
- ]);
102
+ }, [currentOrigin, host, name, relayUrls, os, tls]);
103
104
const handleCopy = async () => {
105
try {
@@ -137,29 +111,6 @@ export function TunnelCommandModal({ trigger }: TunnelCommandModalProps) {
111
}
112
};
113
140
- const isNoTLS = tlsMode === "no-tls";
141
- const isSelfTLS = tlsMode === "self";
142
-
143
- const onNoTLSToggle = (checked: boolean) => {
144
- if (checked) {
145
- setTlsMode("no-tls");
146
- return;
147
- }
148
- if (tlsMode === "no-tls") {
149
- setTlsMode("keyless");
150
- }
151
- };
152
-
153
- const onSelfTLSToggle = (checked: boolean) => {
154
- if (checked) {
155
- setTlsMode("self");
156
- return;
157
- }
158
- if (tlsMode === "self") {
159
- setTlsMode("keyless");
160
- }
161
- };
162
-
114
return (
115
<Dialog>
116
<DialogTrigger asChild>
@@ -252,7 +203,6 @@ export function TunnelCommandModal({ trigger }: TunnelCommandModalProps) {
203
</span>
204
))}
205
<input
255
- ref={urlInputRef}
206
type="text"
207
value={urlInput}
208
onChange={(e) => setUrlInput(e.target.value)}
@@ -307,87 +257,27 @@ export function TunnelCommandModal({ trigger }: TunnelCommandModalProps) {
257
<span className="flex items-center gap-2">
258
<input
259
type="checkbox"
310
- checked={isNoTLS}
311
- onChange={(e) => onNoTLSToggle(e.target.checked)}
312
- className="h-4 w-4"
260
+ checked={tls}
261
+ onChange={(e) => setTls(e.target.checked)}
262
+ disabled={!tlsAvailable}
263
+ className="h-4 w-4 disabled:opacity-50 disabled:cursor-not-allowed"
264
/>
314
- No TLS
265
+ TLS Enabled
266
</span>
267
<span className="text-xs text-text-muted text-right">
317
- Local-only testing mode.
318
- </span>
319
- </label>
320
- <label className="flex items-center justify-between gap-3 text-sm text-foreground">
321
- <span className="flex items-center gap-2">
322
- <input
323
- type="checkbox"
324
- checked={isSelfTLS}
325
- onChange={(e) => onSelfTLSToggle(e.target.checked)}
326
- className="h-4 w-4"
327
- />
328
- Self TLS
329
- </span>
330
- <span className="text-xs text-text-muted text-right">
331
- Bring your own cert and key.
268
+ {tlsAvailable
269
+ ? "Recommended: keyless TLS."
270
+ : "TLS is not available on this relay."}
271
</span>
272
</label>
273
</div>
274
<p className="text-xs text-text-muted">
336
- {tlsMode === "no-tls"
337
- ? "Local testing only (no TLS)."
338
- : tlsMode === "keyless"
339
- ? "Recommended: Keyless TLS."
340
- : "Advanced: Self-managed TLS cert/key."}
275
+ {tls
276
+ ? "TLS is enabled (keyless mode)."
277
+ : "TLS is disabled (no-tls mode)."}
278
</p>
279
</div>
280
344
- {tlsMode === "self" && (
345
- <div className="space-y-3 rounded-md border border-input/80 p-3">
346
- <p className="text-xs font-medium text-foreground">Self TLS Files</p>
347
- <div className="space-y-2">
348
- <label
349
- htmlFor="tls-cert-file"
350
- className="text-sm font-medium text-foreground"
351
- >
352
- TLS Cert File
353
- </label>
354
- <div className="flex items-center rounded-md bg-border">
355
- <span className="px-3 text-sm text-text-muted">TLS_CERT_FILE=</span>
356
- <Input
357
- id="tls-cert-file"
358
- type="text"
359
- value={tlsCertFile}
360
- onChange={(e) => setTlsCertFile(e.target.value)}
361
- placeholder="/path/to/fullchain.pem"
362
- className="rounded-l-none"
363
- />
364
- </div>
365
- </div>
366
- <div className="space-y-2">
367
- <label
368
- htmlFor="tls-key-file"
369
- className="text-sm font-medium text-foreground"
370
- >
371
- TLS Key File
372
- </label>
373
- <div className="flex items-center rounded-md bg-border">
374
- <span className="px-3 text-sm text-text-muted">TLS_KEY_FILE=</span>
375
- <Input
376
- id="tls-key-file"
377
- type="text"
378
- value={tlsKeyFile}
379
- onChange={(e) => setTlsKeyFile(e.target.value)}
380
- placeholder="/path/to/privkey.pem"
381
- className="rounded-l-none"
382
- />
383
- </div>
384
- </div>
385
- <p className="text-xs text-text-muted">
386
- Self TLS selected. These files are included in the command.
387
- </p>
388
- </div>
389
- )}
390
-
281
{/* Generated Command */}
282
<div className="space-y-2">
283
<label className="text-sm font-medium text-foreground">
cmd/relay-server/registry.go
+11
-11
@@ -20,18 +20,18 @@ type SDKRegistry struct{}
20
21
// HandleSDKRequest routes /sdk/* requests.
22
func (r *SDKRegistry) HandleSDKRequest(w http.ResponseWriter, req *http.Request, serv *portal.RelayServer) {
23
- route := strings.Trim(strings.TrimPrefix(req.URL.Path, "/sdk"), "/")
23
+ path := strings.TrimSuffix(req.URL.Path, "/")
24
25
- switch route {
26
- case "register":
25
+ switch path {
26
+ case sdk.SDKPathRegister:
27
r.handleRegister(w, req, serv)
28
- case "unregister":
28
+ case sdk.SDKPathUnregister:
29
r.handleUnregister(w, req, serv)
30
- case "renew":
30
+ case sdk.SDKPathRenew:
31
r.handleRenew(w, req, serv)
32
- case "domain":
32
+ case sdk.SDKPathDomain:
33
r.handleDomain(w, req, serv)
34
- case "connect":
34
+ case sdk.SDKPathConnect:
35
r.handleConnect(w, req, serv)
36
default:
37
http.NotFound(w, req)
@@ -97,7 +97,7 @@ func (r *SDKRegistry) handleRegister(w http.ResponseWriter, req *http.Request, s
97
Name: registerReq.Name,
98
Metadata: registerReq.Metadata,
99
Expires: time.Now().Add(30 * time.Second),
100
- TLSMode: string(registerReq.TLSMode),
100
+ TLS: registerReq.TLS,
101
ReverseToken: registerReq.ReverseToken,
102
}
103
@@ -114,7 +114,7 @@ func (r *SDKRegistry) handleRegister(w http.ResponseWriter, req *http.Request, s
114
serv.GetReverseHub().ClearDropped(registerReq.LeaseID)
115
116
// Only register SNI route for TLS leases.
117
- if registerReq.TLSMode != sdk.TLSModeNoTLS {
117
+ if registerReq.TLS {
118
sniName := strings.ToLower(strings.TrimSpace(registerReq.Name)) + "." + serv.BaseHost
119
if err := serv.GetSNIRouter().RegisterRoute(sniName, registerReq.LeaseID, registerReq.Name); err != nil {
120
// Keep lease and route state consistent on partial failure.
@@ -130,7 +130,7 @@ func (r *SDKRegistry) handleRegister(w http.ResponseWriter, req *http.Request, s
130
log.Info().
131
Str("lease_id", registerReq.LeaseID).
132
Str("name", registerReq.Name).
133
- Str("tls_mode", string(registerReq.TLSMode)).
133
+ Bool("tls", registerReq.TLS).
134
Msg("[Registry] Lease registered")
135
136
// Build public URL
@@ -230,7 +230,7 @@ func (r *SDKRegistry) handleRenew(w http.ResponseWriter, req *http.Request, serv
230
231
// Re-register route if needed (e.g., router restarted while lease remained active).
232
// Only TLS leases need SNI routes.
233
- if sdk.TLSMode(entry.Lease.TLSMode) != sdk.TLSModeNoTLS {
233
+ if entry.Lease.TLS {
234
sniName := strings.ToLower(strings.TrimSpace(entry.Lease.Name)) + "." + serv.BaseHost
235
if err := serv.GetSNIRouter().RegisterRoute(sniName, entry.Lease.ID, entry.Lease.Name); err != nil {
236
log.Warn().
cmd/relay-server/serve.go
+4
-5
@@ -17,7 +17,6 @@ import (
17
18
"gosuda.org/portal/portal"
19
"gosuda.org/portal/portal/keyless"
20
- "gosuda.org/portal/sdk"
20
)
21
22
//go:embed dist/*
@@ -169,11 +168,11 @@ func shouldProxyHTTP(host string, serv *portal.RelayServer) (string, *portal.Lea
168
return leaseName, nil, true
169
}
170
172
- // If TLS mode is no-tls, we can proxy via HTTP.
173
- shouldProxy := sdk.TLSMode(entry.Lease.TLSMode) == sdk.TLSModeNoTLS
171
+ // If TLS is disabled, we can proxy via HTTP.
172
+ shouldProxy := !entry.Lease.TLS
173
log.Debug().
174
Str("lease_name", leaseName).
176
- Str("tls_mode", entry.Lease.TLSMode).
175
+ Bool("tls", entry.Lease.TLS).
176
Msg("[proxy] shouldProxyHTTP")
177
return leaseName, entry, shouldProxy
178
}
@@ -189,7 +188,7 @@ func proxyToHTTP(w http.ResponseWriter, r *http.Request, serv *portal.RelayServe
188
return
189
}
190
192
- if sdk.TLSMode(entry.Lease.TLSMode) != sdk.TLSModeNoTLS {
191
+ if entry.Lease.TLS {
192
http.Error(w, "TLS enabled requires HTTPS access", http.StatusBadRequest)
193
return
194
}
cmd/relay-server/tunnel.go
+3
-9
@@ -54,10 +54,8 @@ set -- "$BIN_PATH" --relay "$RELAYS" --host "${APP_HOST:-localhost:3000}"
54
if [ "${APP_HIDE:-}" = "1" ] || [ "${APP_HIDE:-}" = "true" ]; then
55
set -- "$@" --hide
56
fi
57
-[ -n "${TLS_MODE:-}" ] && set -- "$@" --tls-mode "$TLS_MODE"
58
-if [ "${TLS_MODE:-}" = "self" ]; then
59
- [ -n "${TLS_CERT_FILE:-}" ] && set -- "$@" --tls-cert-file "$TLS_CERT_FILE"
60
- [ -n "${TLS_KEY_FILE:-}" ] && set -- "$@" --tls-key-file "$TLS_KEY_FILE"
57
+if [ "${TLS:-}" = "1" ] || [ "${TLS:-}" = "true" ]; then
58
+ set -- "$@" --tls
59
fi
60
61
echo "Starting portal-tunnel..." >&2
@@ -103,11 +101,7 @@ if ($env:APP_TAGS) { $ArgsList += "--tags", $env:APP_TAGS }
101
if ($env:APP_THUMBNAIL) { $ArgsList += "--thumbnail", $env:APP_THUMBNAIL }
102
if ($env:APP_OWNER) { $ArgsList += "--owner", $env:APP_OWNER }
103
if ($env:APP_HIDE -eq "1" -or $env:APP_HIDE -eq "true") { $ArgsList += "--hide" }
106
-if ($env:TLS_MODE) { $ArgsList += "--tls-mode", $env:TLS_MODE }
107
-if ($env:TLS_MODE -eq "self") {
108
- if ($env:TLS_CERT_FILE) { $ArgsList += "--tls-cert-file", $env:TLS_CERT_FILE }
109
- if ($env:TLS_KEY_FILE) { $ArgsList += "--tls-key-file", $env:TLS_KEY_FILE }
110
-}
104
+if ($env:TLS -eq "1" -or $env:TLS -eq "true") { $ArgsList += "--tls" }
105
106
Write-Host "Starting portal-tunnel..."
107
try {
cmd/relay-server/utils.go
+1
-2
@@ -16,7 +16,6 @@ import (
16
"gosuda.org/portal/cmd/relay-server/manager"
17
"gosuda.org/portal/portal"
18
"gosuda.org/portal/portal/keyless"
19
- "gosuda.org/portal/sdk"
19
)
20
21
func isSecureRequest(r *http.Request) bool {
@@ -386,7 +385,7 @@ func (r *leaseRow) fromLeaseEntry(entry *portal.LeaseEntry, admin *Admin, portal
385
}
386
387
kind := "http"
389
- if sdk.TLSMode(lease.TLSMode) != sdk.TLSModeNoTLS {
388
+ if lease.TLS {
389
kind = "https"
390
}
391
portal/keyless/client.go
renamed
+48
@@ -11,9 +11,57 @@ import (
11
"strings"
12
"time"
13
14
+ keylesstls "github.com/gosuda/keyless_tls/keyless"
15
"github.com/rs/zerolog/log"
16
)
17
18
+// BuildClientTLSConfig builds a keyless TLS server config for tunnel-side TLS termination.
19
+// It returns the TLS config and a close callback for signer resources.
20
+func BuildClientTLSConfig(relayAddr, keylessServerName, domain string) (*tls.Config, func(), error) {
21
+ if keylessServerName == "" {
22
+ return nil, nil, fmt.Errorf("keyless server name is required")
23
+ }
24
+ if domain == "" {
25
+ return nil, nil, fmt.Errorf("tls domain is required")
26
+ }
27
+ certPEM, rootCAPEM, err := ResolveMaterials(
28
+ context.Background(),
29
+ relayAddr,
30
+ keylessServerName,
31
+ nil,
32
+ nil,
33
+ )
34
+ if err != nil {
35
+ return nil, nil, fmt.Errorf("prepare keyless materials: %w", err)
36
+ }
37
+
38
+ if err := VerifyCertificateHostname(certPEM, domain); err != nil {
39
+ return nil, nil, fmt.Errorf("keyless certificate does not cover %s: %w", domain, err)
40
+ }
41
+
42
+ remoteSigner, err := keylesstls.NewRemoteSigner(keylesstls.RemoteSignerConfig{
43
+ Endpoint: relayAddr,
44
+ ServerName: keylessServerName,
45
+ KeyID: RelayKeyID,
46
+ RootCAPEM: rootCAPEM,
47
+ }, certPEM)
48
+ if err != nil {
49
+ return nil, nil, fmt.Errorf("create keyless remote signer: %w", err)
50
+ }
51
+
52
+ tlsConfig, err := keylesstls.NewServerTLSConfig(keylesstls.ServerTLSConfig{
53
+ CertPEM: certPEM,
54
+ Signer: remoteSigner,
55
+ })
56
+ if err != nil {
57
+ _ = remoteSigner.Close()
58
+ return nil, nil, fmt.Errorf("create keyless TLS config: %w", err)
59
+ }
60
+ tlsConfig.NextProtos = []string{"http/1.1"}
61
+
62
+ return tlsConfig, func() { _ = remoteSigner.Close() }, nil
63
+}
64
+
65
// ResolveMaterials prepares certificate chain and root CAs for keyless TLS mode.
66
func ResolveMaterials(
67
ctx context.Context,
portal/keyless/signer.go
+3
-3
@@ -13,7 +13,7 @@ import (
13
)
14
15
const (
16
- defaultKeyID = "relay-cert"
16
+ RelayKeyID = "relay-cert"
17
defaultAllowedSkew = 30 * time.Second
18
)
19
@@ -57,7 +57,7 @@ func NewSigner(cfg Config) (*Signer, error) {
57
}
58
59
store := ksigner.NewStaticKeyStore()
60
- if err := store.Put(defaultKeyID, signingKey); err != nil {
60
+ if err := store.Put(RelayKeyID, signingKey); err != nil {
61
return nil, fmt.Errorf("register keyless signing key: %w", err)
62
}
63
@@ -68,7 +68,7 @@ func NewSigner(cfg Config) (*Signer, error) {
68
69
return &Signer{
70
service: svc,
71
- keyID: defaultKeyID,
71
+ keyID: RelayKeyID,
72
}, nil
73
}
74
portal/lease.go
+2
-2
@@ -23,8 +23,8 @@ type Lease struct {
23
Name string `json:"name"`
24
Metadata Metadata `json:"metadata"`
25
Expires time.Time `json:"expires"`
26
- TLSMode string `json:"tls_mode"` // no-tls, self, keyless
27
- ReverseToken string `json:"-"` // shared secret for reverse connect authentication
26
+ TLS bool `json:"tls"`
27
+ ReverseToken string `json:"-"` // shared secret for reverse connect authentication
28
}
29
30
// Metadata holds service metadata
portal/reverse_hub.go
+4
-1
@@ -44,6 +44,9 @@ const (
44
// ReverseIdleKeepaliveInterval sends an idle keepalive byte to reduce
45
// reverse websocket disconnections from intermediate idle timeouts.
46
ReverseIdleKeepaliveInterval = 25 * time.Second
47
+
48
+ // ReverseConnectTokenHeader is the websocket handshake header carrying reverse auth token.
49
+ ReverseConnectTokenHeader = "X-Portal-Reverse-Token"
50
)
51
52
// ReverseConn wraps a net.Conn with lifecycle management for the connection pool.
@@ -328,7 +331,7 @@ func parseReverseConnectCredentials(req *http.Request) (leaseID, token string) {
331
return "", ""
332
}
333
leaseID = strings.TrimSpace(req.URL.Query().Get("lease_id"))
331
- token = strings.TrimSpace(req.URL.Query().Get("token"))
334
+ token = strings.TrimSpace(req.Header.Get(ReverseConnectTokenHeader))
335
return leaseID, token
336
}
337
portal/reverse_hub_test.go
+26
@@ -3,6 +3,7 @@ package portal
3
import (
4
"io"
5
"net"
6
+ "net/http/httptest"
7
"testing"
8
"time"
9
)
@@ -119,3 +120,28 @@ func TestAcquireForHTTPSendsStartMarker(t *testing.T) {
120
t.Fatal("timed out waiting for start marker")
121
}
122
}
123
+
124
+func TestParseReverseConnectCredentials_HeaderTokenPreferred(t *testing.T) {
125
+ req := httptest.NewRequest("GET", "/sdk/connect?lease_id=lease-1&token=query-token", nil)
126
+ req.Header.Set(ReverseConnectTokenHeader, "header-token")
127
+
128
+ leaseID, token := parseReverseConnectCredentials(req)
129
+ if leaseID != "lease-1" {
130
+ t.Fatalf("unexpected lease_id: %q", leaseID)
131
+ }
132
+ if token != "header-token" {
133
+ t.Fatalf("expected header token, got %q", token)
134
+ }
135
+}
136
+
137
+func TestParseReverseConnectCredentials_QueryTokenIgnored(t *testing.T) {
138
+ req := httptest.NewRequest("GET", "/sdk/connect?lease_id=lease-2&token=query-token", nil)
139
+
140
+ leaseID, token := parseReverseConnectCredentials(req)
141
+ if leaseID != "lease-2" {
142
+ t.Fatalf("unexpected lease_id: %q", leaseID)
143
+ }
144
+ if token != "" {
145
+ t.Fatalf("expected empty token when header is missing, got %q", token)
146
+ }
147
+}
sdk/client.go
+44
-124
@@ -2,7 +2,6 @@
2
package sdk
3
4
import (
5
- "context"
5
"crypto/rand"
6
"crypto/tls"
7
"encoding/hex"
@@ -14,7 +13,6 @@ import (
13
"sync"
14
"time"
15
17
- keylesstls "github.com/gosuda/keyless_tls/keyless"
16
"github.com/rs/zerolog/log"
17
18
"gosuda.org/portal/portal"
@@ -32,7 +30,7 @@ func NewClient(opt ...ClientOption) (*Client, error) {
30
config := &ClientConfig{
31
BootstrapServers: []string{},
32
ReverseDialTimeout: 5 * time.Second,
35
- TLSMode: TLSModeNoTLS,
33
+ TLS: false,
34
}
35
36
for _, o := range opt {
@@ -118,18 +116,17 @@ func (c *Client) Listen(name string, options ...MetadataOption) (net.Listener, e
116
listener = newMultiRelayListener(lease.ID, listeners)
117
}
118
121
- if c.config.TLSMode != TLSModeNoTLS {
119
+ if c.config.TLS {
120
log.Info().
121
Str("lease_id", lease.ID).
122
Str("name", name).
123
Bool("tls", true).
126
- Str("tls_mode", string(c.config.TLSMode)).
124
Msg("[SDK] Lease registered with TLS")
125
} else {
126
log.Info().
127
Str("lease_id", lease.ID).
128
Str("name", name).
132
- Str("tls_mode", string(TLSModeNoTLS)).
129
+ Bool("tls", false).
130
Msg("[SDK] Lease registered")
131
}
132
@@ -142,16 +139,21 @@ func (c *Client) newLease(name string, options ...MetadataOption) (*portal.Lease
139
option(&metadata)
140
}
141
145
- reverseToken, err := generateToken(16)
146
- if err != nil {
142
+ idBytes := make([]byte, 16)
143
+ if _, err := rand.Read(idBytes); err != nil {
144
+ return nil, fmt.Errorf("generate lease ID: %w", err)
145
+ }
146
+
147
+ tokenBytes := make([]byte, 16)
148
+ if _, err := rand.Read(tokenBytes); err != nil {
149
return nil, fmt.Errorf("generate reverse token: %w", err)
150
}
151
152
lease := &portal.Lease{
151
- ID: generateID(),
153
+ ID: hex.EncodeToString(idBytes),
154
Name: name,
153
- TLSMode: string(c.config.TLSMode),
154
- ReverseToken: reverseToken,
155
+ TLS: c.config.TLS,
156
+ ReverseToken: hex.EncodeToString(tokenBytes),
157
Metadata: portal.Metadata{
158
Description: metadata.Description,
159
Tags: metadata.Tags,
@@ -164,6 +166,37 @@ func (c *Client) newLease(name string, options ...MetadataOption) (*portal.Lease
166
return lease, nil
167
}
168
169
+func (c *Client) buildTLSConfig(relayAddr, leaseName string) (*tls.Config, []func(), error) {
170
+ if !c.config.TLS {
171
+ return nil, nil, nil
172
+ }
173
+
174
+ parsed, err := url.Parse(relayAddr)
175
+ if err != nil {
176
+ return nil, nil, fmt.Errorf("invalid relay address: %s, %w", relayAddr, err)
177
+ }
178
+ keylessServerName := parsed.Hostname()
179
+ if keylessServerName == "" {
180
+ return nil, nil, fmt.Errorf("relay hostname is required: %s", relayAddr)
181
+ }
182
+ baseDomain := ExtractBaseDomain(relayAddr)
183
+ if baseDomain == "" {
184
+ return nil, nil, fmt.Errorf("keyless base domain is required for relay %s", relayAddr)
185
+ }
186
+ domain := leaseName + "." + baseDomain
187
+
188
+ tlsConfig, closeFn, err := keyless.BuildClientTLSConfig(relayAddr, keylessServerName, domain)
189
+ if err != nil {
190
+ return nil, nil, err
191
+ }
192
+ return tlsConfig, []func(){closeFn}, nil
193
+}
194
+
195
+// Close closes the client.
196
+func (c *Client) Close() error {
197
+ return nil
198
+}
199
+
200
func ExtractBaseDomain(rawURL string) string {
201
trimmed := strings.TrimSpace(rawURL)
202
if trimmed == "" {
@@ -210,116 +243,3 @@ func normalizeRelayAPIURLs(bootstrapServers []string) ([]string, error) {
243
}
244
return out, nil
245
}
213
-
214
-func (c *Client) buildTLSConfig(relayAddr, leaseName string) (*tls.Config, []func(), error) {
215
- tlsMode := c.config.TLSMode
216
- if tlsMode == TLSModeNoTLS {
217
- return nil, nil, nil
218
- }
219
-
220
- switch tlsMode {
221
- case TLSModeSelf:
222
- if c.config.TLSSelfCertFile == "" || c.config.TLSSelfKeyFile == "" {
223
- return nil, nil, fmt.Errorf("self TLS mode requires certificate/key files (WithTLSSelfCertificateFiles)")
224
- }
225
- cert, err := tls.LoadX509KeyPair(c.config.TLSSelfCertFile, c.config.TLSSelfKeyFile)
226
- if err != nil {
227
- return nil, nil, fmt.Errorf("load self TLS certificate files: %w", err)
228
- }
229
-
230
- tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12}
231
- tlsConfig.NextProtos = []string{"http/1.1"}
232
- tlsConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
233
- return &cert, nil
234
- }
235
- return tlsConfig, nil, nil
236
-
237
- case TLSModeKeyless:
238
- keylessEndpoint := c.config.TLSKeylessEndpoint
239
- if keylessEndpoint == "" {
240
- keylessEndpoint = relayAddr
241
- }
242
-
243
- keylessKeyID := "relay-cert"
244
-
245
- keylessServerName := ""
246
- if parsed, err := url.Parse(keylessEndpoint); err == nil {
247
- keylessServerName = parsed.Hostname()
248
- }
249
-
250
- certPEM, rootCAPEM, err := keyless.ResolveMaterials(
251
- context.Background(),
252
- keylessEndpoint,
253
- keylessServerName,
254
- nil,
255
- nil,
256
- )
257
- if err != nil {
258
- return nil, nil, fmt.Errorf("prepare keyless materials: %w", err)
259
- }
260
-
261
- baseDomain := c.config.TLSKeylessBaseDomain
262
- if baseDomain == "" {
263
- baseDomain = ExtractBaseDomain(relayAddr)
264
- }
265
- if baseDomain == "" {
266
- baseDomain = ExtractBaseDomain(keylessEndpoint)
267
- }
268
- if baseDomain == "" {
269
- return nil, nil, fmt.Errorf("keyless base domain is required for relay %s", relayAddr)
270
- }
271
- domain := leaseName + "." + baseDomain
272
- if err := keyless.VerifyCertificateHostname(certPEM, domain); err != nil {
273
- return nil, nil, fmt.Errorf("keyless certificate does not cover %s: %w", domain, err)
274
- }
275
-
276
- remoteSigner, err := keylesstls.NewRemoteSigner(keylesstls.RemoteSignerConfig{
277
- Endpoint: keylessEndpoint,
278
- ServerName: keylessServerName,
279
- KeyID: keylessKeyID,
280
- RootCAPEM: rootCAPEM,
281
- }, certPEM)
282
- if err != nil {
283
- return nil, nil, fmt.Errorf("create keyless remote signer: %w", err)
284
- }
285
-
286
- tlsConfig, err := keylesstls.NewServerTLSConfig(keylesstls.ServerTLSConfig{
287
- CertPEM: certPEM,
288
- Signer: remoteSigner,
289
- })
290
- if err != nil {
291
- _ = remoteSigner.Close()
292
- return nil, nil, fmt.Errorf("create keyless TLS config: %w", err)
293
- }
294
- tlsConfig.NextProtos = []string{"http/1.1"}
295
-
296
- return tlsConfig, []func(){
297
- func() { _ = remoteSigner.Close() },
298
- }, nil
299
- default:
300
- return nil, nil, fmt.Errorf("unsupported TLS mode: %s", tlsMode)
301
- }
302
-}
303
-
304
-// Close closes the client.
305
-func (c *Client) Close() error {
306
- return nil
307
-}
308
-
309
-// generateID generates a unique ID for the lease.
310
-func generateID() string {
311
- b := make([]byte, 16)
312
- rand.Read(b)
313
- return hex.EncodeToString(b)
314
-}
315
-
316
-func generateToken(size int) (string, error) {
317
- if size <= 0 {
318
- size = 16
319
- }
320
- b := make([]byte, size)
321
- if _, err := rand.Read(b); err != nil {
322
- return "", err
323
- }
324
- return hex.EncodeToString(b), nil
325
-}
sdk/listener.go
+7
-11
@@ -208,7 +208,7 @@ func (l *Listener) keepaliveLoop() {
208
case <-ticker.C:
209
if err := l.sendKeepalive(); err != nil {
210
if isLeaseNotFoundError(err) {
211
- if rerr := l.reRegisterLease(); rerr != nil {
211
+ if rerr := l.registerWithRelay(); rerr != nil {
212
log.Warn().
213
Err(rerr).
214
Str("lease_id", l.lease.ID).
@@ -287,6 +287,7 @@ func (l *Listener) openReverseConnection() (net.Conn, error) {
287
if err != nil {
288
return nil, fmt.Errorf("new reverse websocket config: %w", err)
289
}
290
+ cfg.Header.Set(portal.ReverseConnectTokenHeader, l.lease.ReverseToken)
291
cfg.Dialer = &net.Dialer{
292
Timeout: l.reverseDialTimeout,
293
}
@@ -340,18 +341,18 @@ func (l *Listener) registerWithRelay() error {
341
LeaseID: l.lease.ID,
342
Name: l.lease.Name,
343
Metadata: l.lease.Metadata,
343
- TLSMode: TLSMode(l.lease.TLSMode),
344
+ TLS: l.lease.TLS,
345
ReverseToken: l.lease.ReverseToken,
346
}
347
347
- return l.postJSON("/sdk/register", reqBody)
348
+ return l.postJSON(SDKPathRegister, reqBody)
349
}
350
351
func (l *Listener) unregisterFromRelay() error {
352
reqBody := UnregisterRequest{
353
LeaseID: l.lease.ID,
354
}
354
- return l.postJSON("/sdk/unregister", reqBody)
355
+ return l.postJSON(SDKPathUnregister, reqBody)
356
}
357
358
func (l *Listener) sendKeepalive() error {
@@ -359,11 +360,7 @@ func (l *Listener) sendKeepalive() error {
360
LeaseID: l.lease.ID,
361
ReverseToken: l.lease.ReverseToken,
362
}
362
- return l.postJSON("/sdk/renew", reqBody)
363
-}
364
-
365
-func (l *Listener) reRegisterLease() error {
366
- return l.registerWithRelay()
363
+ return l.postJSON(SDKPathRenew, reqBody)
364
}
365
366
func (l *Listener) postJSON(path string, body any) error {
@@ -477,10 +474,9 @@ func relayConnectURL(relayAddr, leaseID, token string) (string, error) {
474
default:
475
return "", fmt.Errorf("unsupported relay URL scheme: %q", u.Scheme)
476
}
480
- u.Path = "/sdk/connect"
477
+ u.Path = SDKPathConnect
478
q := u.Query()
479
q.Set("lease_id", leaseID)
483
- q.Set("token", token)
480
u.RawQuery = q.Encode()
481
u.Fragment = ""
482
return u.String(), nil
sdk/listener_test.go
+2
-2
@@ -65,8 +65,8 @@ func TestRelayConnectURL(t *testing.T) {
65
if !strings.Contains(got, "lease_id=lease-1") {
66
t.Fatalf("missing lease_id in URL: %q", got)
67
}
68
- if !strings.Contains(got, "token=token-1") {
69
- t.Fatalf("missing token in URL: %q", got)
68
+ if strings.Contains(got, "token=token-1") {
69
+ t.Fatalf("token must not be present in URL query: %q", got)
70
}
71
72
if _, err := relayConnectURL("http://localhost:4017", "", "token-1"); err == nil {
sdk/types.go
+13
-42
@@ -18,29 +18,11 @@ var (
18
ErrInvalidMetadata = errors.New("invalid metadata")
19
)
20
21
-type TLSMode string
22
-
23
-const (
24
- TLSModeNoTLS TLSMode = "no-tls"
25
- TLSModeSelf TLSMode = "self"
26
- TLSModeKeyless TLSMode = "keyless"
27
-)
28
-
21
type ClientConfig struct {
22
BootstrapServers []string
23
ReverseDialTimeout time.Duration // Reverse websocket dial timeout (default: 5 seconds)
24
33
- TLSMode TLSMode
34
-
35
- // Self TLS mode certificate/key file paths.
36
- TLSSelfCertFile string
37
- TLSSelfKeyFile string
38
-
39
- // Optional keyless overrides.
40
- // If endpoint is empty, SDK uses the relay URL.
41
- TLSKeylessEndpoint string
42
- // If base domain is empty, SDK derives it from relay or signer endpoint.
43
- TLSKeylessBaseDomain string
25
+ TLS bool
26
}
27
28
type ClientOption func(*ClientConfig)
@@ -57,29 +39,10 @@ func WithReverseDialTimeout(timeout time.Duration) ClientOption {
39
}
40
}
41
60
-// WithTLSSelfCertificateFiles enables self TLS mode using certificate/key file paths.
61
-func WithTLSSelfCertificateFiles(certFile, keyFile string) ClientOption {
42
+// WithTLS enables keyless TLS mode using relay-derived defaults.
43
+func WithTLS() ClientOption {
44
return func(c *ClientConfig) {
63
- c.TLSMode = TLSModeSelf
64
- c.TLSSelfCertFile = certFile
65
- c.TLSSelfKeyFile = keyFile
66
- }
67
-}
68
-
69
-// WithTLSKeyless enables keyless TLS mode with optional signer overrides.
70
-func WithTLSKeyless(endpoint, baseDomain string) ClientOption {
71
- return func(c *ClientConfig) {
72
- c.TLSMode = TLSModeKeyless
73
- c.TLSKeylessEndpoint = endpoint
74
- c.TLSKeylessBaseDomain = baseDomain
75
- }
76
-}
77
-
78
-// WithTLSKeylessDefaults enables keyless TLS mode with SDK-managed defaults.
79
-// Certificate chain and signer trust are auto-discovered from signer endpoint when not explicitly provided.
80
-func WithTLSKeylessDefaults() ClientOption {
81
- return func(c *ClientConfig) {
82
- c.TLSMode = TLSModeKeyless
45
+ c.TLS = true
46
}
47
}
48
@@ -122,7 +85,7 @@ type RegisterRequest struct {
85
LeaseID string `json:"lease_id"`
86
Name string `json:"name"`
87
Metadata portal.Metadata `json:"metadata"`
125
- TLSMode TLSMode `json:"tls_mode"` // no-tls, self, keyless
88
+ TLS bool `json:"tls"`
89
ReverseToken string `json:"reverse_token"`
90
}
91
@@ -146,3 +109,11 @@ type APIResponse struct {
109
Success bool `json:"success"`
110
Message string `json:"message,omitempty"`
111
}
112
+
113
+const (
114
+ SDKPathRegister = "/sdk/register"
115
+ SDKPathUnregister = "/sdk/unregister"
116
+ SDKPathRenew = "/sdk/renew"
117
+ SDKPathDomain = "/sdk/domain"
118
+ SDKPathConnect = "/sdk/connect"
119
+)