Skip to content

Commit 37c850a

Browse files
committed
refactor(admin): extract admin logic to portal/admin
- Move admin HTTP handlers & state from cmd/relay-server/admin.go - Introduce portaladmin.Service & portaladmin.Handler - cmd/relay-server/admin.go now thin adapter - Remove admin_settings.json persistence from Admin - Consolidate frontend path to frontend/ - Update Dockerfile & Makefile references
1 parent 4224cd2 commit 37c850a

1 file changed

Lines changed: 17 additions & 29 deletions

File tree

AGENTS.md

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ Source of truth for architecture decisions: `docs/adr/README.md` and linked ADRs
2121
4. **Keep explicit root-domain fallback behavior through SNI no-route handling to admin/API listener.**
2222
- Why: preserves intended control-plane vs tenant routing split (ADR-0001).
2323

24+
5. **All leases require TLS=true.** The register endpoint rejects `TLS=false`. The `RegisterRequest.TLS` field exists but non-TLS leases are not permitted.
25+
- Why: enforces end-to-end transport security for all tenant routes.
26+
2427
## Security and Anti-Abuse Invariants
2528

2629
1. **Admin-managed policy is authoritative for runtime security controls.**
@@ -37,16 +40,17 @@ Source of truth for architecture decisions: `docs/adr/README.md` and linked ADRs
3740

3841
## TLS and Identity Invariants
3942

40-
1. **Relay holds the TLS private key; SDK/tunnel never does.** SDK calls `/v1/sign` on the relay via `RemoteSigner` for all private key operations.
41-
- Why: prevents key material leakage to untrusted tunnel endpoints.
43+
1. **Relay holds the TLS private key for admin/API (root domain) only.** SDK calls `/v1/sign` on the relay via `RemoteSigner` for admin/API TLS termination. For SNI-passthrough routes, the relay peeks the ClientHello for SNI then bridges the raw encrypted connection — the backend/tunnel endpoint terminates TLS and holds those keys, not the relay.
44+
- Why: admin/API key material stays on the relay; tenant TLS passthrough avoids key distribution to the relay entirely.
4245

4346
2. **mTLS is implicit (optional) for `/sdk/*` control-plane paths.** When a client cert is presented, the relay validates it (CertBind stage). When absent, CertBind is skipped and token auth alone is used.
4447
- `KEYLESS_DIR` env var presence triggers SDK lifecycle identity issuance and client cert presentation. When unset, the SDK operates in token-only mode.
45-
- Keyless TLS (`RemoteSigner` for `/v1/sign`) is independent of mTLS — always used for TLS termination regardless of client cert presence.
48+
- Keyless TLS (`RemoteSigner` for `/v1/sign`) is independent of mTLS — always used for admin/API TLS termination regardless of client cert presence.
4649
- Why: ADR-0003 admission order is IP ban → Lease → [CertBind if cert present] → Token. Invalid certs are still rejected; absent certs skip CertBind.
4750

4851
3. **All relay URLs must be `https://`.** `NormalizeRelayAPIURL` rejects non-HTTPS. SDK and tunnel hard-fail on `http://`.
4952
- Why: enforces transport security without opt-out.
53+
5054
4. **`keyless_tls/` is published to `github.com/gosuda/keyless_tls`** and vendored as a directory for co-development. Root `go.mod` pins a specific pseudo-version or tag — no `replace` directive.
5155
- Why: enables external consumers while keeping co-development convenient. Pin version after pushing upstream changes.
5256

@@ -55,38 +59,28 @@ Source of truth for architecture decisions: `docs/adr/README.md` and linked ADRs
5559
1. **SNI wildcard matching is one-level only.** `sni.Router.GetRoute()` checks `*.parent.example.com` for `foo.parent.example.com` — not arbitrary depth.
5660
- Why: matches RFC TLS wildcard semantics.
5761

58-
2. **Protocol markers on reverse TCP connections:** `0x00` = keepalive, `0x02` = TLS passthrough activation.
62+
2. **Protocol markers on reverse TCP connections:** `0x00` = keepalive, `0x01` = non-TLS start, `0x02` = TLS passthrough activation. The SNI router does true TLS passthrough — it peeks the ClientHello for routing, then bridges the raw encrypted connection without terminating TLS.
5963
- Why: binary protocol, not discoverable from HTTP-layer code.
6064

6165
3. **HTTP/2 is intentionally disabled on the admin HTTP server** (`TLSNextProto: make(…)`).
6266
- Why: the server hijacks connections for `/sdk/connect`; HTTP/2 multiplexing breaks hijack semantics.
6367

64-
## Operational Truths (CI-Aligned, Minimal)
65-
66-
1. **Local lint workflow: run `make lint-auto` first, then `make lint`.**
67-
- Why: `lint-auto` applies safe rewrites locally, while `lint` is the strict non-mutating gate that matches CI.
68+
## API Response Contract
6869

69-
2. **Use CI-equivalent verification when validating high-risk changes:**
70-
- `make vet`
71-
- `make lint`
72-
- `make test`
73-
- `make vuln`
74-
- Why: these are the enforced checks in `.github/workflows/ci.yml`.
75-
- Note: `make tidy` is a local maintenance/pre-release step and is not currently part of the CI workflow.
70+
1. **All HTTP responses use the `APIEnvelope` wrapper:** `{ ok: bool, data?: any, error?: { code, message } }` (defined in `types/api.go`). Write responses through `writeAPIData()`, `writeAPIOK()`, or `writeAPIError()` helpers — never raw JSON.
71+
- Why: cross-cutting contract across all endpoints; inconsistent envelopes break SDK and frontend parsing.
7672

77-
3. **Assume Go toolchain baseline from `go.mod`.**
78-
- Why: CI resolves Go from `go.mod`; avoid stale version assumptions.
73+
## Operational Truths (CI-Aligned, Minimal)
7974

80-
4. **Use `Makefile` as build and verification authority; do not reference absent tooling (for example, no `justfile` in this repo).**
81-
- Why: reduces operational drift and broken command guidance.
75+
1. **CI verification commands:** `make vet`, `make lint`, `make test`, `make vuln`. These are the enforced checks in `.github/workflows/ci.yml`. Note: `make tidy` is a local maintenance/pre-release step, not part of CI.
8276

83-
5. **`make build-server` does NOT call `make build-frontend`.** If called alone, `//go:embed dist/*` will be stale or empty. The Dockerfile calls both explicitly in order.
77+
2. **`make build-server` does NOT call `make build-frontend`.** If called alone, `//go:embed dist/*` will be stale or empty. The Dockerfile calls both explicitly in order.
8478
- Why: prevents silent broken builds with missing frontend assets.
8579

86-
6. **`admin_settings.json` persists in the process CWD**, not in `KEYLESS_DIR`. State is lost on container restart unless CWD is a mounted volume.
80+
3. **`admin_settings.json` persists in the process CWD**, not in `KEYLESS_DIR`. State is lost on container restart unless CWD is a mounted volume.
8781
- Why: prevents state-loss surprises in production.
8882

89-
7. **`onLeaseDeleted` has dual registration.** `portal/relay.go` registers one callback; `cmd/relay-server/main.go` overwrites it with a broader one (adds IP/BPS cleanup). The outer callback supersedes.
83+
4. **`onLeaseDeleted` has dual registration.** `portal/relay.go` registers one callback; `cmd/relay-server/main.go` overwrites it with a broader one (adds IP/BPS cleanup). The outer callback supersedes.
9084
- Why: coupling hazard — modifying either registration without understanding both breaks cleanup.
9185

9286
## Change Discipline
@@ -99,13 +93,7 @@ Source of truth for architecture decisions: `docs/adr/README.md` and linked ADRs
9993

10094
## Go Conventions
10195

102-
**Format:** `gofmt -w . && goimports -w .` before every commit. Imports: stdlib → external → internal (blank-line separated), local prefix `github.com/gosuda`.
103-
104-
**CGo:** always disabled — `CGO_ENABLED=0`. Pure Go only.
105-
106-
**Module:** commit `go.mod`+`go.sum`, never `go.work` · pin toolchain in `go.mod` · `go mod tidy && go mod verify && govulncheck ./...` pre-release · `os.Root` (Go 1.24+) for directory-scoped I/O.
107-
108-
**Concurrency:** `errgroup.Group` over `WaitGroup` · `errgroup.SetLimit` for bounded work · `context.WithTimeout` over `time.After` in loops (timer leak) · no bare `go func()` — creator owns lifecycle.
96+
**Imports:** stdlib → external → internal (blank-line separated), local prefix `github.com/gosuda`. **Concurrency:** `errgroup.Group` over `WaitGroup` · `errgroup.SetLimit` for bounded work · `context.WithTimeout` over `time.After` in loops (timer leak) · no bare `go func()` — creator owns lifecycle. **I/O:** `os.Root` (Go 1.24+) for directory-scoped file operations.
10997

11098
---
11199

0 commit comments

Comments
 (0)