refact: remove type alias
Kim committed
Mar 5, 2026 at 10:10 UTC
917d33d040a6a1c73036e737015bc497419d1217
16 files changed
+119
-179
cmd/portal-tunnel/main.go
+6
-6
@@ -17,9 +17,9 @@ import (
17
"github.com/rs/zerolog"
18
"github.com/rs/zerolog/log"
19
20
- "gosuda.org/portal/portal/contracts"
20
"gosuda.org/portal/portal/netutil"
21
"gosuda.org/portal/sdk"
22
+ "gosuda.org/portal/types"
23
)
24
25
var (
@@ -86,11 +86,11 @@ func runTunnel() error {
86
87
listener, err := sdkClient.Listen(
88
flagName,
89
- contracts.WithDescription(flagDesc),
90
- contracts.WithTags(netutil.ParseURLs(flagTags)),
91
- contracts.WithOwner(flagOwner),
92
- contracts.WithThumbnail(flagThumbnail),
93
- contracts.WithHide(flagHide),
89
+ types.WithDescription(flagDesc),
90
+ types.WithTags(netutil.ParseURLs(flagTags)),
91
+ types.WithOwner(flagOwner),
92
+ types.WithThumbnail(flagThumbnail),
93
+ types.WithHide(flagHide),
94
)
95
if err != nil {
96
return fmt.Errorf("service %s: failed to register service: %w", flagName, err)
cmd/relay-server/registry.go
+3
-3
@@ -254,7 +254,7 @@ func (r *SDKRegistry) decodeRequestBody(w http.ResponseWriter, req *http.Request
254
return true
255
}
256
257
-func writeRegistryError(w http.ResponseWriter, apiErr *controlplaneregistry.APIError) bool {
257
+func writeRegistryError(w http.ResponseWriter, apiErr *types.APIError) bool {
258
if apiErr == nil {
259
return true
260
}
@@ -277,7 +277,7 @@ func (b *relayRegistryBackend) BaseHost() string {
277
return b.serv.BaseHost
278
}
279
280
-func (b *relayRegistryBackend) UpdateLease(lease *portal.Lease) bool {
280
+func (b *relayRegistryBackend) UpdateLease(lease *types.Lease) bool {
281
if b.serv == nil || b.serv.GetLeaseManager() == nil {
282
return false
283
}
@@ -291,7 +291,7 @@ func (b *relayRegistryBackend) DeleteLease(leaseID string) bool {
291
return b.serv.GetLeaseManager().DeleteLease(leaseID)
292
}
293
294
-func (b *relayRegistryBackend) GetLeaseByID(leaseID string) (*portal.LeaseEntry, bool) {
294
+func (b *relayRegistryBackend) GetLeaseByID(leaseID string) (*types.LeaseEntry, bool) {
295
if b.serv == nil || b.serv.GetLeaseManager() == nil {
296
return nil, false
297
}
cmd/relay-server/serve.go
+9
-9
@@ -16,10 +16,10 @@ import (
16
"github.com/rs/zerolog/log"
17
18
"gosuda.org/portal/portal"
19
- "gosuda.org/portal/portal/contracts"
19
"gosuda.org/portal/portal/keyless"
20
"gosuda.org/portal/portal/netutil"
21
"gosuda.org/portal/portal/policy"
22
+ "gosuda.org/portal/types"
23
)
24
25
const defaultHTTPSPort = "443"
@@ -42,21 +42,21 @@ func serveAPI(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fro
42
frontend.ServeAsset(appMux, "/favicon.svg", "favicon.svg", "image/svg+xml")
43
44
// Portal app assets (JS, CSS, etc.) - served from /app/
45
- appMux.HandleFunc(contracts.PathAppPrefix, func(w http.ResponseWriter, r *http.Request) {
45
+ appMux.HandleFunc(types.PathAppPrefix, func(w http.ResponseWriter, r *http.Request) {
46
setCORSHeaders(w)
47
if r.Method == http.MethodOptions {
48
w.WriteHeader(http.StatusOK)
49
return
50
}
51
- p := strings.TrimPrefix(r.URL.Path, contracts.PathAppPrefix)
51
+ p := strings.TrimPrefix(r.URL.Path, types.PathAppPrefix)
52
frontend.ServeAppStatic(w, r, p, serv)
53
})
54
55
// Tunnel installer script and binaries
56
- appMux.HandleFunc(contracts.PathTunnelScript, func(w http.ResponseWriter, r *http.Request) {
56
+ appMux.HandleFunc(types.PathTunnelScript, func(w http.ResponseWriter, r *http.Request) {
57
serveTunnelScript(w, r, cfg.PortalURL)
58
})
59
- appMux.HandleFunc(contracts.PathTunnelBinary, func(w http.ResponseWriter, r *http.Request) {
59
+ appMux.HandleFunc(types.PathTunnelBinary, func(w http.ResponseWriter, r *http.Request) {
60
serveTunnelBinary(w, r)
61
})
62
@@ -70,12 +70,12 @@ func serveAPI(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fro
70
portalURL: cfg.PortalURL,
71
trustProxyHeaders: cfg.TrustProxyHeaders,
72
}
73
- appMux.HandleFunc(contracts.PathSDKPrefix, func(w http.ResponseWriter, r *http.Request) {
73
+ appMux.HandleFunc(types.PathSDKPrefix, func(w http.ResponseWriter, r *http.Request) {
74
registry.HandleSDKRequest(w, r, serv)
75
})
76
77
// Keyless signer endpoint.
78
- appMux.HandleFunc(contracts.PathKeylessSign, func(w http.ResponseWriter, r *http.Request) {
78
+ appMux.HandleFunc(types.PathKeylessSign, func(w http.ResponseWriter, r *http.Request) {
79
handleKeylessSign(w, r, serv.GetKeylessSigner())
80
})
81
@@ -86,7 +86,7 @@ func serveAPI(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fro
86
frontend.ServeAppStatic(w, r, p, serv)
87
})
88
89
- appMux.HandleFunc(contracts.PathHealthz, func(w http.ResponseWriter, _ *http.Request) {
89
+ appMux.HandleFunc(types.PathHealthz, func(w http.ResponseWriter, _ *http.Request) {
90
w.WriteHeader(http.StatusOK)
91
if _, err := w.Write([]byte("{\"status\":\"ok\"}")); err != nil {
92
log.Debug().Err(err).Msg("[healthz] failed to write response")
@@ -94,7 +94,7 @@ func serveAPI(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fro
94
})
95
96
// Admin API
97
- appMux.HandleFunc(contracts.PathAdminPrefix+"/", func(w http.ResponseWriter, r *http.Request) {
97
+ appMux.HandleFunc(types.PathAdminPrefix+"/", func(w http.ResponseWriter, r *http.Request) {
98
admin.HandleAdminRequest(w, r, serv)
99
})
100
cmd/relay-server/utils.go
+6
-6
@@ -11,10 +11,10 @@ import (
11
"github.com/rs/zerolog/log"
12
13
"gosuda.org/portal/portal"
14
- "gosuda.org/portal/portal/contracts"
14
"gosuda.org/portal/portal/keyless"
15
"gosuda.org/portal/portal/netutil"
16
"gosuda.org/portal/portal/policy"
17
+ "gosuda.org/portal/types"
18
)
19
20
const (
@@ -149,7 +149,7 @@ func isLeaseConnected(since time.Duration) bool {
149
}
150
151
// fromLeaseEntry populates the leaseRow from a LeaseEntry with common fields.
152
-func (r *leaseRow) fromLeaseEntry(entry *portal.LeaseEntry, admin *Admin, portalURL string) {
152
+func (r *leaseRow) fromLeaseEntry(entry *types.LeaseEntry, admin *Admin, portalURL string) {
153
lease := entry.Lease
154
identityID := lease.ID
155
since := max(time.Since(entry.LastSeen), 0)
@@ -281,7 +281,7 @@ func convertLeaseEntriesToRows(serv *portal.RelayServer, admin *Admin, forAdmin
281
func writeAPIData(w http.ResponseWriter, status int, data any) {
282
w.Header().Set("Content-Type", "application/json")
283
w.WriteHeader(status)
284
- if err := json.NewEncoder(w).Encode(contracts.APIEnvelope{
284
+ if err := json.NewEncoder(w).Encode(types.APIEnvelope{
285
OK: true,
286
Data: data,
287
}); err != nil {
@@ -292,7 +292,7 @@ func writeAPIData(w http.ResponseWriter, status int, data any) {
292
func writeAPIOK(w http.ResponseWriter, status int) {
293
w.Header().Set("Content-Type", "application/json")
294
w.WriteHeader(status)
295
- if err := json.NewEncoder(w).Encode(contracts.APIEnvelope{OK: true}); err != nil {
295
+ if err := json.NewEncoder(w).Encode(types.APIEnvelope{OK: true}); err != nil {
296
log.Error().Err(err).Msg("[HTTP] Failed to encode API success response")
297
}
298
}
@@ -304,10 +304,10 @@ func writeAPIError(w http.ResponseWriter, status int, code, message string) {
304
func writeAPIErrorWithData(w http.ResponseWriter, status int, code, message string, data any) {
305
w.Header().Set("Content-Type", "application/json")
306
w.WriteHeader(status)
307
- if err := json.NewEncoder(w).Encode(contracts.APIEnvelope{
307
+ if err := json.NewEncoder(w).Encode(types.APIEnvelope{
308
OK: false,
309
Data: data,
310
- Error: &contracts.APIError{
310
+ Error: &types.APIError{
311
Code: code,
312
Message: message,
313
},
portal/contracts/contracts.go
deleted
-58
@@ -1,58 +0,0 @@
1
-package contracts
2
-
3
-import "gosuda.org/portal/types"
4
-
5
-// API path constants for Portal relay server.
6
-const (
7
- PathSDKPrefix = types.PathSDKPrefix
8
- PathSDKRegister = types.PathSDKRegister
9
- PathSDKUnregister = types.PathSDKUnregister
10
- PathSDKRenew = types.PathSDKRenew
11
- PathSDKDomain = types.PathSDKDomain
12
- PathSDKConnect = types.PathSDKConnect
13
-
14
- PathAdminPrefix = types.PathAdminPrefix
15
- PathAdminLogin = types.PathAdminLogin
16
- PathAdminLogout = types.PathAdminLogout
17
- PathAdminAuthStatus = types.PathAdminAuthStatus
18
- PathAdminLeases = types.PathAdminLeases
19
- PathAdminLeasesBanned = types.PathAdminLeasesBanned
20
- PathAdminStats = types.PathAdminStats
21
- PathAdminSettings = types.PathAdminSettings
22
- PathAdminApprovalMode = types.PathAdminApprovalMode
23
-
24
- PathKeylessSign = types.PathKeylessSign
25
- PathHealthz = types.PathHealthz
26
-
27
- PathTunnelScript = types.PathTunnelScript
28
- PathTunnelBinary = types.PathTunnelBinary
29
-
30
- PathAppPrefix = types.PathAppPrefix
31
-)
32
-
33
-type (
34
- APIError = types.APIError
35
- APIEnvelope = types.APIEnvelope
36
- Metadata = types.Metadata
37
- MetadataOption = types.MetadataOption
38
-)
39
-
40
-func WithDescription(description string) MetadataOption {
41
- return types.WithDescription(description)
42
-}
43
-
44
-func WithTags(tags []string) MetadataOption {
45
- return types.WithTags(tags)
46
-}
47
-
48
-func WithThumbnail(thumbnail string) MetadataOption {
49
- return types.WithThumbnail(thumbnail)
50
-}
51
-
52
-func WithOwner(owner string) MetadataOption {
53
- return types.WithOwner(owner)
54
-}
55
-
56
-func WithHide(hide bool) MetadataOption {
57
- return types.WithHide(hide)
58
-}
portal/controlplane/registry/service.go
+11
-15
@@ -10,7 +10,6 @@ import (
10
11
"github.com/rs/zerolog/log"
12
13
- "gosuda.org/portal/portal"
13
"gosuda.org/portal/portal/controlplane"
14
"gosuda.org/portal/types"
15
)
@@ -21,9 +20,9 @@ const DefaultLeaseTTL = 30 * time.Second
20
// Backend provides the relay operations needed by the control-plane registry.
21
type Backend interface {
22
BaseHost() string
24
- UpdateLease(lease *portal.Lease) bool
23
+ UpdateLease(lease *types.Lease) bool
24
DeleteLease(leaseID string) bool
26
- GetLeaseByID(leaseID string) (*portal.LeaseEntry, bool)
25
+ GetLeaseByID(leaseID string) (*types.LeaseEntry, bool)
26
ClearDropped(leaseID string)
27
DropLease(leaseID string)
28
RegisterRoute(sniName, leaseID, name string) error
@@ -37,9 +36,6 @@ type Options struct {
36
LeaseTTL time.Duration
37
}
38
40
-// APIError is an alias for types.APIError to avoid breaking existing consumers.
41
-type APIError = types.APIError
42
-
39
// AdmissionInput describes runtime context for control-plane admission checks.
40
type AdmissionInput struct {
41
ConnectionTLSState *tls.ConnectionState
@@ -52,7 +48,7 @@ type AdmissionInput struct {
48
49
// AdmissionResult returns normalized, validated admission context.
50
type AdmissionResult struct {
55
- Entry *portal.LeaseEntry
51
+ Entry *types.LeaseEntry
52
LeaseID string
53
ReverseToken string
54
ClientIP string
@@ -96,7 +92,7 @@ func NewService(backend Backend, opts Options) (*Service, error) {
92
}
93
94
// Admit validates and normalizes control-plane credentials before SDK operations.
99
-func (s *Service) Admit(input AdmissionInput) (AdmissionResult, *APIError) {
95
+func (s *Service) Admit(input AdmissionInput) (AdmissionResult, *types.APIError) {
96
leaseID, reverseToken := normalizeLeaseCredentials(input.RawLeaseID, input.RawReverseToken)
97
if err := validateLeaseCredentials(leaseID, reverseToken); err != nil {
98
return AdmissionResult{}, err
@@ -130,7 +126,7 @@ func (s *Service) Admit(input AdmissionInput) (AdmissionResult, *APIError) {
126
}
127
128
// Register creates a new lease and associated SNI route.
133
-func (s *Service) Register(input RegisterInput) (types.RegisterResponse, *APIError) {
129
+func (s *Service) Register(input RegisterInput) (types.RegisterResponse, *types.APIError) {
130
name := strings.TrimSpace(input.Name)
131
if !types.IsValidLeaseName(name) {
132
return types.RegisterResponse{}, apiError(httpStatusBadRequest, "invalid_name", "name must be a DNS label (letters, digits, hyphen; no dots or underscores)")
@@ -144,7 +140,7 @@ func (s *Service) Register(input RegisterInput) (types.RegisterResponse, *APIErr
140
metadata = *input.Metadata
141
}
142
147
- lease := &portal.Lease{
143
+ lease := &types.Lease{
144
ID: input.LeaseID,
145
Name: name,
146
Metadata: metadata,
@@ -198,7 +194,7 @@ func (s *Service) Unregister(leaseID string) {
194
}
195
196
// Renew extends lease expiry and opportunistically refreshes SNI routing.
201
-func (s *Service) Renew(entry *portal.LeaseEntry) *APIError {
197
+func (s *Service) Renew(entry *types.LeaseEntry) *types.APIError {
198
if entry == nil || entry.Lease == nil {
199
return apiError(httpStatusNotFound, "lease_not_found", "lease not found")
200
}
@@ -228,7 +224,7 @@ func (s *Service) Renew(entry *portal.LeaseEntry) *APIError {
224
}
225
226
// Domain returns the configured relay base domain.
231
-func (s *Service) Domain() (types.DomainResponse, *APIError) {
227
+func (s *Service) Domain() (types.DomainResponse, *types.APIError) {
228
baseHost := strings.TrimSpace(s.backend.BaseHost())
229
if baseHost == "" {
230
return types.DomainResponse{}, apiError(httpStatusServiceUnavailable, "base_domain_missing", "base domain not configured")
@@ -248,7 +244,7 @@ func normalizeLeaseCredentials(rawLeaseID, rawReverseToken string) (leaseID, rev
244
return strings.TrimSpace(rawLeaseID), strings.TrimSpace(rawReverseToken)
245
}
246
251
-func validateLeaseCredentials(leaseID, reverseToken string) *APIError {
247
+func validateLeaseCredentials(leaseID, reverseToken string) *types.APIError {
248
if leaseID == "" {
249
return apiError(httpStatusBadRequest, "missing_lease_id", "lease_id is required")
250
}
@@ -258,8 +254,8 @@ func validateLeaseCredentials(leaseID, reverseToken string) *APIError {
254
return nil
255
}
256
261
-func apiError(statusCode int, code, message string) *APIError {
262
- return &APIError{
257
+func apiError(statusCode int, code, message string) *types.APIError {
258
+ return &types.APIError{
259
StatusCode: statusCode,
260
Code: code,
261
Message: message,
portal/controlplane/registry/service_test.go
+24
-25
@@ -8,14 +8,13 @@ import (
8
"testing"
9
"time"
10
11
- "gosuda.org/portal/portal"
11
"gosuda.org/portal/portal/controlplane"
12
"gosuda.org/portal/types"
13
)
14
15
type fakeBackend struct {
16
registerRouteErr error
18
- leases map[string]*portal.LeaseEntry
17
+ leases map[string]*types.LeaseEntry
18
baseHost string
19
connectLeaseID string
20
connectToken string
@@ -29,7 +28,7 @@ func newFakeBackend(baseHost string) *fakeBackend {
28
return &fakeBackend{
29
baseHost: baseHost,
30
updateLeaseAllowed: true,
32
- leases: make(map[string]*portal.LeaseEntry),
31
+ leases: make(map[string]*types.LeaseEntry),
32
}
33
}
34
@@ -37,11 +36,11 @@ func (f *fakeBackend) BaseHost() string {
36
return f.baseHost
37
}
38
40
-func (f *fakeBackend) UpdateLease(lease *portal.Lease) bool {
39
+func (f *fakeBackend) UpdateLease(lease *types.Lease) bool {
40
if !f.updateLeaseAllowed {
41
return false
42
}
44
- f.leases[lease.ID] = &portal.LeaseEntry{
43
+ f.leases[lease.ID] = &types.LeaseEntry{
44
Lease: lease,
45
Expires: lease.Expires,
46
}
@@ -56,7 +55,7 @@ func (f *fakeBackend) DeleteLease(leaseID string) bool {
55
return true
56
}
57
59
-func (f *fakeBackend) GetLeaseByID(leaseID string) (*portal.LeaseEntry, bool) {
58
+func (f *fakeBackend) GetLeaseByID(leaseID string) (*types.LeaseEntry, bool) {
59
entry, ok := f.leases[leaseID]
60
return entry, ok
61
}
@@ -175,8 +174,8 @@ func TestAdmitSuccessWithMatchingToken(t *testing.T) {
174
t.Parallel()
175
176
backend := newFakeBackend("example.com")
178
- backend.leases["lease-1"] = &portal.LeaseEntry{
179
- Lease: &portal.Lease{
177
+ backend.leases["lease-1"] = &types.LeaseEntry{
178
+ Lease: &types.Lease{
179
ID: "lease-1",
180
ReverseToken: "token-1",
181
},
@@ -212,8 +211,8 @@ func TestAdmitRejectsInvalidTokenWithValidCertificate(t *testing.T) {
211
t.Parallel()
212
213
backend := newFakeBackend("example.com")
215
- backend.leases["lease-1"] = &portal.LeaseEntry{
216
- Lease: &portal.Lease{
214
+ backend.leases["lease-1"] = &types.LeaseEntry{
215
+ Lease: &types.Lease{
216
ID: "lease-1",
217
ReverseToken: "token-1",
218
},
@@ -242,8 +241,8 @@ func TestAdmit_ValidCert_Passes(t *testing.T) {
241
t.Parallel()
242
243
backend := newFakeBackend("example.com")
245
- backend.leases["lease-1"] = &portal.LeaseEntry{
246
- Lease: &portal.Lease{
244
+ backend.leases["lease-1"] = &types.LeaseEntry{
245
+ Lease: &types.Lease{
246
ID: "lease-1",
247
ReverseToken: "token-1",
248
},
@@ -272,8 +271,8 @@ func TestAdmit_InvalidCert_Rejected(t *testing.T) {
271
t.Parallel()
272
273
backend := newFakeBackend("example.com")
275
- backend.leases["lease-1"] = &portal.LeaseEntry{
276
- Lease: &portal.Lease{
274
+ backend.leases["lease-1"] = &types.LeaseEntry{
275
+ Lease: &types.Lease{
276
ID: "lease-1",
277
ReverseToken: "token-1",
278
},
@@ -303,8 +302,8 @@ func TestAdmit_NoCert_TokenValid_Passes(t *testing.T) {
302
t.Parallel()
303
304
backend := newFakeBackend("example.com")
306
- backend.leases["lease-1"] = &portal.LeaseEntry{
307
- Lease: &portal.Lease{
305
+ backend.leases["lease-1"] = &types.LeaseEntry{
306
+ Lease: &types.Lease{
307
ID: "lease-1",
308
ReverseToken: "token-1",
309
},
@@ -315,7 +314,7 @@ func TestAdmit_NoCert_TokenValid_Passes(t *testing.T) {
314
t.Fatalf("NewService returned error: %v", err)
315
}
316
318
- // Nil TLS state — CertBind skipped, token validation still applies.
317
+ // Nil TLS state ??CertBind skipped, token validation still applies.
318
result, apiErr := svc.Admit(AdmissionInput{
319
RawLeaseID: "lease-1",
320
RawReverseToken: "token-1",
@@ -334,8 +333,8 @@ func TestAdmit_NoCert_TokenInvalid_Rejected(t *testing.T) {
333
t.Parallel()
334
335
backend := newFakeBackend("example.com")
337
- backend.leases["lease-1"] = &portal.LeaseEntry{
338
- Lease: &portal.Lease{
336
+ backend.leases["lease-1"] = &types.LeaseEntry{
337
+ Lease: &types.Lease{
338
ID: "lease-1",
339
ReverseToken: "token-1",
340
},
@@ -346,7 +345,7 @@ func TestAdmit_NoCert_TokenInvalid_Rejected(t *testing.T) {
345
t.Fatalf("NewService returned error: %v", err)
346
}
347
349
- // Nil TLS state — CertBind skipped, but token does not match.
348
+ // Nil TLS state ??CertBind skipped, but token does not match.
349
_, apiErr := svc.Admit(AdmissionInput{
350
RawLeaseID: "lease-1",
351
RawReverseToken: "wrong-token",
@@ -433,8 +432,8 @@ func TestRenewExtendsLease(t *testing.T) {
432
433
now := time.Date(2026, time.March, 4, 1, 0, 0, 0, time.UTC)
434
backend := newFakeBackend("example.com")
436
- entry := &portal.LeaseEntry{
437
- Lease: &portal.Lease{
435
+ entry := &types.LeaseEntry{
436
+ Lease: &types.Lease{
437
ID: "lease-1",
438
Name: "demo",
439
Expires: now,
@@ -464,8 +463,8 @@ func TestRenewResetsFutureExpiryFromNow(t *testing.T) {
463
now := time.Date(2026, time.March, 4, 1, 0, 0, 0, time.UTC)
464
originalExpiry := now.Add(5 * time.Minute)
465
backend := newFakeBackend("example.com")
467
- entry := &portal.LeaseEntry{
468
- Lease: &portal.Lease{
466
+ entry := &types.LeaseEntry{
467
+ Lease: &types.Lease{
468
ID: "lease-1",
469
Name: "demo",
470
Expires: originalExpiry,
@@ -498,7 +497,7 @@ func TestUnregisterDropsLeaseAndRoutes(t *testing.T) {
497
t.Parallel()
498
499
backend := newFakeBackend("example.com")
501
- backend.leases["lease-1"] = &portal.LeaseEntry{Lease: &portal.Lease{ID: "lease-1"}}
500
+ backend.leases["lease-1"] = &types.LeaseEntry{Lease: &types.Lease{ID: "lease-1"}}
501
svc, err := NewService(backend, Options{})
502
if err != nil {
503
t.Fatalf("NewService returned error: %v", err)
portal/lease.go
+9
-15
@@ -9,12 +9,6 @@ import (
9
"gosuda.org/portal/types"
10
)
11
12
-// Lease is an alias for types.Lease for backward compatibility within the portal package.
13
-type Lease = types.Lease
14
-
15
-// LeaseEntry is an alias for types.LeaseEntry for backward compatibility within the portal package.
16
-type LeaseEntry = types.LeaseEntry
17
-
12
type LeaseManager struct {
13
leases map[string]*types.LeaseEntry
14
stopCh chan struct{}
@@ -31,7 +25,7 @@ type LeaseManager struct {
25
26
func NewLeaseManager(ttlInterval time.Duration) *LeaseManager {
27
return &LeaseManager{
34
- leases: make(map[string]*LeaseEntry),
28
+ leases: make(map[string]*types.LeaseEntry),
29
stopCh: make(chan struct{}),
30
ttlInterval: ttlInterval,
31
bannedLeases: make(map[string]struct{}),
@@ -86,7 +80,7 @@ func (lm *LeaseManager) cleanupExpiredLeases() {
80
}
81
}
82
89
-func (lm *LeaseManager) UpdateLease(lease *Lease) bool {
83
+func (lm *LeaseManager) UpdateLease(lease *types.Lease) bool {
84
lm.leasesLock.Lock()
85
defer lm.leasesLock.Unlock()
86
@@ -137,7 +131,7 @@ func (lm *LeaseManager) UpdateLease(lease *Lease) bool {
131
firstSeen = time.Now()
132
}
133
140
- lm.leases[identityID] = &LeaseEntry{
134
+ lm.leases[identityID] = &types.LeaseEntry{
135
Lease: lease,
136
Expires: lease.Expires,
137
LastSeen: time.Now(),
@@ -168,7 +162,7 @@ func (lm *LeaseManager) SetOnLeaseDeleted(callback func(string)) {
162
lm.onLeaseDeleted = callback
163
}
164
171
-func (lm *LeaseManager) GetLeaseByID(leaseID string) (*LeaseEntry, bool) {
165
+func (lm *LeaseManager) GetLeaseByID(leaseID string) (*types.LeaseEntry, bool) {
166
lm.leasesLock.RLock()
167
defer lm.leasesLock.RUnlock()
168
@@ -190,7 +184,7 @@ func (lm *LeaseManager) GetLeaseByID(leaseID string) (*LeaseEntry, bool) {
184
return lease, true
185
}
186
193
-func (lm *LeaseManager) GetLeaseByName(name string) (*LeaseEntry, bool) {
187
+func (lm *LeaseManager) GetLeaseByName(name string) (*types.LeaseEntry, bool) {
188
lm.leasesLock.RLock()
189
defer lm.leasesLock.RUnlock()
190
@@ -213,12 +207,12 @@ func (lm *LeaseManager) GetLeaseByName(name string) (*LeaseEntry, bool) {
207
return nil, false
208
}
209
216
-func (lm *LeaseManager) GetAllLeases() []*Lease {
210
+func (lm *LeaseManager) GetAllLeases() []*types.Lease {
211
lm.leasesLock.RLock()
212
defer lm.leasesLock.RUnlock()
213
214
now := time.Now()
221
- var validLeases []*Lease
215
+ var validLeases []*types.Lease
216
217
for _, entry := range lm.leases {
218
if now.Before(entry.Expires) {
@@ -230,12 +224,12 @@ func (lm *LeaseManager) GetAllLeases() []*Lease {
224
}
225
226
// GetAllLeaseEntries returns all lease entries from the lease manager.
233
-func (lm *LeaseManager) GetAllLeaseEntries() []*LeaseEntry {
227
+func (lm *LeaseManager) GetAllLeaseEntries() []*types.LeaseEntry {
228
lm.leasesLock.RLock()
229
defer lm.leasesLock.RUnlock()
230
231
now := time.Now()
238
- var entries []*LeaseEntry
232
+ var entries []*types.LeaseEntry
233
234
for _, entry := range lm.leases {
235
if now.Before(entry.Expires) {
portal/lease_test.go
+7
-5
@@ -4,6 +4,8 @@ import (
4
"slices"
5
"testing"
6
"time"
7
+
8
+ "gosuda.org/portal/types"
9
)
10
11
func TestLeaseManagerDeleteLeaseInvokesCallback(t *testing.T) {
@@ -14,7 +16,7 @@ func TestLeaseManagerDeleteLeaseInvokesCallback(t *testing.T) {
16
deleted = append(deleted, id)
17
})
18
17
- lease := &Lease{
19
+ lease := &types.Lease{
20
ID: "lease-1",
21
Name: "app-1",
22
Expires: time.Now().Add(30 * time.Second),
@@ -39,16 +41,16 @@ func TestLeaseManagerCleanupExpiredLeasesInvokesCallback(t *testing.T) {
41
deleted = append(deleted, id)
42
})
43
42
- lm.leases["expired-1"] = &LeaseEntry{
43
- Lease: &Lease{
44
+ lm.leases["expired-1"] = &types.LeaseEntry{
45
+ Lease: &types.Lease{
46
ID: "expired-1",
47
Name: "expired",
48
Expires: time.Now().Add(-1 * time.Second),
49
},
50
Expires: time.Now().Add(-1 * time.Second),
51
}
50
- lm.leases["active-1"] = &LeaseEntry{
51
- Lease: &Lease{
52
+ lm.leases["active-1"] = &types.LeaseEntry{
53
+ Lease: &types.Lease{
54
ID: "active-1",
55
Name: "active",
56
Expires: time.Now().Add(30 * time.Second),
portal/relay_test.go
+3
-1
@@ -6,6 +6,8 @@ import (
6
"strings"
7
"testing"
8
"time"
9
+
10
+ "gosuda.org/portal/types"
11
)
12
13
func TestRelayServerReverseHubAuthorizerTrimsToken(t *testing.T) {
@@ -14,7 +16,7 @@ func TestRelayServerReverseHubAuthorizerTrimsToken(t *testing.T) {
16
t.Fatalf("create relay server: %v", err)
17
}
18
17
- lease := &Lease{
19
+ lease := &types.Lease{
20
ID: "lease-authorizer-trim",
21
Name: "tenant",
22
TLS: true,
portal/reverse_hub.go
+3
-5
@@ -9,6 +9,8 @@ import (
9
"time"
10
11
"github.com/rs/zerolog/log"
12
+
13
+ "gosuda.org/portal/portal/sni"
14
)
15
16
const (
@@ -16,10 +18,6 @@ const (
18
// before they are activated for a real client request.
19
ReverseKeepaliveMarker = byte(0x00)
20
19
- // TLSStartMarker is sent by the relay to activate a reverse connection
20
- // for TLS reverse-connect mode.
21
- TLSStartMarker = byte(0x02)
22
-
21
// QueueSize is the maximum number of pending reverse connections per lease.
22
QueueSize = 64
23
@@ -267,7 +265,7 @@ func (h *ReverseHub) AcquireForTLS(leaseID string, timeout time.Duration) (*Reve
265
}
266
// Stop idle keepalive and signal tunnel worker to release this connection.
267
conn.Activate()
270
- err := conn.WriteControlByte(TLSStartMarker, controlWriteTimeout)
268
+ err := conn.WriteControlByte(sni.TLSStartMarker, controlWriteTimeout)
269
if err == nil {
270
return conn, nil
271
}
portal/reverse_hub_test.go
+6
-4
@@ -6,6 +6,8 @@ import (
6
"strings"
7
"testing"
8
"time"
9
+
10
+ "gosuda.org/portal/portal/sni"
11
)
12
13
func TestReverseHubAuthorization(t *testing.T) {
@@ -113,7 +115,7 @@ func TestHandleConnectTrimsLeaseIDAndToken(t *testing.T) {
115
case err := <-readErr:
116
t.Fatalf("failed to read marker: %v", err)
117
case marker := <-markerRead:
116
- if marker != TLSStartMarker {
118
+ if marker != sni.TLSStartMarker {
119
t.Fatalf("unexpected marker: %d", marker)
120
}
121
case <-time.After(500 * time.Millisecond):
@@ -168,7 +170,7 @@ func TestAcquireForTLSSendsStartMarker(t *testing.T) {
170
case err := <-readErr:
171
t.Fatalf("failed to read marker: %v", err)
172
case b := <-markerRead:
171
- if b != TLSStartMarker {
173
+ if b != sni.TLSStartMarker {
174
t.Fatalf("unexpected marker: %d", b)
175
}
176
case <-time.After(500 * time.Millisecond):
@@ -226,7 +228,7 @@ func TestAcquireForTLSPollLoopSendsStartMarker(t *testing.T) {
228
case err := <-readErr:
229
t.Fatalf("failed to read marker: %v", err)
230
case b := <-markerRead:
229
- if b != TLSStartMarker {
231
+ if b != sni.TLSStartMarker {
232
t.Fatalf("unexpected marker: %d", b)
233
}
234
case <-time.After(500 * time.Millisecond):
@@ -293,7 +295,7 @@ func TestHandleConnectOffersAuthorizedConn(t *testing.T) {
295
case err := <-readErr:
296
t.Fatalf("failed to read start marker: %v", err)
297
case b := <-markerRead:
296
- if b != TLSStartMarker {
298
+ if b != sni.TLSStartMarker {
299
t.Fatalf("unexpected marker: %d", b)
300
}
301
case <-time.After(500 * time.Millisecond):
portal/sni/parser.go
+7
@@ -10,6 +10,13 @@ import (
10
"io"
11
)
12
13
+const (
14
+ // TLSStartMarker activates a reverse TCP connection for TLS passthrough.
15
+ TLSStartMarker = byte(0x02)
16
+ // NonTLSStartMarker is reserved for non-TLS protocol marker tests.
17
+ NonTLSStartMarker = byte(0x01)
18
+)
19
+
20
var (
21
// ErrInvalidTLSRecord is returned when the TLS record is malformed.
22
ErrInvalidTLSRecord = errors.New("invalid TLS record")
sdk/client.go
+2
-3
@@ -22,7 +22,6 @@ import (
22
23
"github.com/gosuda/keyless_tls/keyless/lifecycle"
24
25
- "gosuda.org/portal/portal"
25
"gosuda.org/portal/portal/keyless"
26
"gosuda.org/portal/portal/netutil"
27
"gosuda.org/portal/types"
@@ -166,7 +165,7 @@ func (c *Client) Listen(name string, options ...types.MetadataOption) (net.Liste
165
return listener, nil
166
}
167
169
-func (c *Client) newLease(name string, options ...types.MetadataOption) (*portal.Lease, error) {
168
+func (c *Client) newLease(name string, options ...types.MetadataOption) (*types.Lease, error) {
169
var metadata types.Metadata
170
for _, option := range options {
171
option(&metadata)
@@ -182,7 +181,7 @@ func (c *Client) newLease(name string, options ...types.MetadataOption) (*portal
181
return nil, fmt.Errorf("generate reverse token: %w", err)
182
}
183
185
- lease := &portal.Lease{
184
+ lease := &types.Lease{
185
ID: hex.EncodeToString(idBytes),
186
Name: name,
187
TLS: true,
sdk/listener.go
+8
-8
@@ -19,8 +19,8 @@ import (
19
"github.com/rs/zerolog/log"
20
21
"gosuda.org/portal/portal"
22
- "gosuda.org/portal/portal/contracts"
22
"gosuda.org/portal/portal/netutil"
23
+ "gosuda.org/portal/portal/sni"
24
"gosuda.org/portal/types"
25
)
26
@@ -88,7 +88,7 @@ func (e *reverseConnectRejectionError) IsFatal() bool {
88
type Listener struct {
89
tlsConfig *tls.Config
90
controlPlaneCert tls.Certificate
91
- lease *portal.Lease
91
+ lease *types.Lease
92
httpClient *http.Client
93
stopCh chan struct{}
94
acceptCh chan net.Conn
@@ -106,7 +106,7 @@ var _ net.Listener = (*Listener)(nil)
106
107
// NewListener creates a relay-backed listener.
108
// If tlsConfig is provided, reverse workers complete TLS handshakes before enqueueing connections.
109
-func NewListener(relayAddr string, lease *portal.Lease, tlsConfig *tls.Config, controlPlaneCert tls.Certificate, reverseWorkers int, reverseDialTimeout time.Duration, closeFns ...func()) (*Listener, error) {
109
+func NewListener(relayAddr string, lease *types.Lease, tlsConfig *tls.Config, controlPlaneCert tls.Certificate, reverseWorkers int, reverseDialTimeout time.Duration, closeFns ...func()) (*Listener, error) {
110
if lease == nil {
111
return nil, errors.New("lease is required")
112
}
@@ -317,7 +317,7 @@ func (l *Listener) reverseAcceptWorker(workerID int) {
317
continue
318
}
319
320
- err = l.waitForReverseStart(conn, portal.TLSStartMarker)
320
+ err = l.waitForReverseStart(conn, sni.TLSStartMarker)
321
if err != nil {
322
if closeErr := conn.Close(); closeErr != nil {
323
log.Debug().Err(closeErr).Msg("[SDK] failed to close reverse connection")
@@ -664,7 +664,7 @@ func (l *Listener) registerWithRelay() error {
664
ReverseToken: l.lease.ReverseToken,
665
}
666
667
- return l.postJSON(contracts.PathSDKRegister, reqBody)
667
+ return l.postJSON(types.PathSDKRegister, reqBody)
668
}
669
670
func (l *Listener) unregisterFromRelay() error {
@@ -672,7 +672,7 @@ func (l *Listener) unregisterFromRelay() error {
672
LeaseID: l.lease.ID,
673
ReverseToken: l.lease.ReverseToken,
674
}
675
- return l.postJSON(contracts.PathSDKUnregister, reqBody)
675
+ return l.postJSON(types.PathSDKUnregister, reqBody)
676
}
677
678
func (l *Listener) sendKeepalive() error {
@@ -680,7 +680,7 @@ func (l *Listener) sendKeepalive() error {
680
LeaseID: l.lease.ID,
681
ReverseToken: l.lease.ReverseToken,
682
}
683
- return l.postJSON(contracts.PathSDKRenew, reqBody)
683
+ return l.postJSON(types.PathSDKRenew, reqBody)
684
}
685
686
func (l *Listener) postJSON(path string, body any) error {
@@ -759,7 +759,7 @@ func relayConnectURL(relayAddr, leaseID, token string) (string, error) {
759
if u.Scheme != "https" {
760
return "", fmt.Errorf("unsupported relay URL scheme: %q (use https)", u.Scheme)
761
}
762
- u.Path = contracts.PathSDKConnect
762
+ u.Path = types.PathSDKConnect
763
q := u.Query()
764
q.Set("lease_id", leaseID)
765
u.RawQuery = q.Encode()
sdk/listener_test.go
+15
-16
@@ -13,11 +13,10 @@ import (
13
"time"
14
15
"gosuda.org/portal/portal"
16
+ "gosuda.org/portal/portal/sni"
17
"gosuda.org/portal/types"
18
)
19
19
-const testNonTLSStartMarker = byte(0x01)
20
-
20
func TestNormalizeRelayAPIURL(t *testing.T) {
21
t.Parallel()
22
@@ -172,7 +171,7 @@ func TestOpenReverseConnection_RejectsNonHTTPSRelay(t *testing.T) {
171
172
l := &Listener{
173
relayAddr: "http://localhost:4017",
175
- lease: &portal.Lease{ID: "lease-1", ReverseToken: "token-1"},
174
+ lease: &types.Lease{ID: "lease-1", ReverseToken: "token-1"},
175
reverseDialTimeout: 2 * time.Second,
176
stopCh: make(chan struct{}),
177
}
@@ -211,7 +210,7 @@ func TestOpenReverseConnection_StopUnblocksTLSHandshake(t *testing.T) {
210
211
l := &Listener{
212
relayAddr: "https://" + ln.Addr().String(),
214
- lease: &portal.Lease{ID: "lease-1", ReverseToken: "token-1"},
213
+ lease: &types.Lease{ID: "lease-1", ReverseToken: "token-1"},
214
reverseDialTimeout: 5 * time.Second,
215
stopCh: make(chan struct{}),
216
}
@@ -256,7 +255,7 @@ func TestWriteReverseConnectRequest_RespectsWriteDeadline(t *testing.T) {
255
}
256
257
l := &Listener{
259
- lease: &portal.Lease{ReverseToken: "token-1"},
258
+ lease: &types.Lease{ReverseToken: "token-1"},
259
reverseDialTimeout: 25 * time.Millisecond,
260
stopCh: make(chan struct{}),
261
}
@@ -483,10 +482,10 @@ func TestWaitForReverseStart_HTTPMode(t *testing.T) {
482
483
done := make(chan error, 1)
484
go func() {
486
- done <- l.waitForReverseStart(local, portal.TLSStartMarker)
485
+ done <- l.waitForReverseStart(local, sni.TLSStartMarker)
486
}()
487
489
- _, err := peer.Write([]byte{portal.TLSStartMarker})
488
+ _, err := peer.Write([]byte{sni.TLSStartMarker})
489
if err != nil {
490
t.Fatalf("write marker: %v", err)
491
}
@@ -514,10 +513,10 @@ func TestWaitForReverseStart_TLSMode(t *testing.T) {
513
514
done := make(chan error, 1)
515
go func() {
517
- done <- l.waitForReverseStart(local, portal.TLSStartMarker)
516
+ done <- l.waitForReverseStart(local, sni.TLSStartMarker)
517
}()
518
520
- _, err := peer.Write([]byte{portal.TLSStartMarker})
519
+ _, err := peer.Write([]byte{sni.TLSStartMarker})
520
if err != nil {
521
t.Fatalf("write marker: %v", err)
522
}
@@ -542,14 +541,14 @@ func TestWaitForReverseStart_IgnoresKeepaliveMarker(t *testing.T) {
541
542
done := make(chan error, 1)
543
go func() {
545
- done <- l.waitForReverseStart(local, portal.TLSStartMarker)
544
+ done <- l.waitForReverseStart(local, sni.TLSStartMarker)
545
}()
546
547
_, err := peer.Write([]byte{portal.ReverseKeepaliveMarker})
548
if err != nil {
549
t.Fatalf("write keepalive marker: %v", err)
550
}
552
- _, err = peer.Write([]byte{portal.TLSStartMarker})
551
+ _, err = peer.Write([]byte{sni.TLSStartMarker})
552
if err != nil {
553
t.Fatalf("write start marker: %v", err)
554
}
@@ -577,10 +576,10 @@ func TestWaitForReverseStart_TLSRejectsHTTPMarker(t *testing.T) {
576
577
done := make(chan error, 1)
578
go func() {
580
- done <- l.waitForReverseStart(local, portal.TLSStartMarker)
579
+ done <- l.waitForReverseStart(local, sni.TLSStartMarker)
580
}()
581
583
- _, err := peer.Write([]byte{testNonTLSStartMarker})
582
+ _, err := peer.Write([]byte{sni.NonTLSStartMarker})
583
if err != nil {
584
t.Fatalf("write marker: %v", err)
585
}
@@ -605,10 +604,10 @@ func TestWaitForReverseStart_HTTPRejectsTLSMarker(t *testing.T) {
604
605
done := make(chan error, 1)
606
go func() {
608
- done <- l.waitForReverseStart(local, testNonTLSStartMarker)
607
+ done <- l.waitForReverseStart(local, sni.NonTLSStartMarker)
608
}()
609
611
- _, err := peer.Write([]byte{portal.TLSStartMarker})
610
+ _, err := peer.Write([]byte{sni.TLSStartMarker})
611
if err != nil {
612
t.Fatalf("write marker: %v", err)
613
}
@@ -632,7 +631,7 @@ func TestWaitForReverseStart_StopCancelsWait(t *testing.T) {
631
632
done := make(chan error, 1)
633
go func() {
635
- done <- l.waitForReverseStart(local, portal.TLSStartMarker)
634
+ done <- l.waitForReverseStart(local, sni.TLSStartMarker)
635
}()
636
637
close(l.stopCh)