從 edge-ai-platform POC 轉為正式產品的雲端後端,含以下整合階段:
- Phase 0:雛形骨架 — `cmd/api-server` (REST :3721) + `cmd/remote-proxy`
(tunnel :3800 / internal :3801) 雙 binary 共用 internal/,沿用 POC 的
WebSocket+yamux tunnel 協定但解耦 relay 與 API
- Phase 0.6:OIDC BFF 接 Innovedus Member Center
- internal/oidc package(coreos/go-oidc + PKCE S256 + state + nonce)
- internal/usersession package(HMAC-SHA256 cookie + RotateSessionID
防 session fixation, OWASP ASVS V3.2.1)
- 4 個 OIDC handler(/api/auth/login|callback|me|logout)+ AuthMiddleware
- 完全拔除 StaticAuthProvider,OIDC 是唯一認證路徑
- 9 個 ADR(含 ADR-010 BFF / ADR-011 取代 static auth /
ADR-012 pending session shared cookie / ADR-013 PKCE-only public client)
- Phase 0.7:A1 改造 + security audit 修復
- OIDC ClientSecret 變選填,支援 stage MC 的 public PKCE-only client
(AuthStyleInParams 強制 token endpoint 不送 client_secret)
- 預留 ServiceClient* 欄位給未來 client_credentials grant
- 移除 13+ 處 resolveUserID(uc, StaticUserID) fallback 改 strict mode
(Audit C1:multi-tenant 隔離破口)
- Pairing exchange MarkUsed 失敗 abort + revoke session token(Audit M3)
- 新增 all_endpoints_require_auth_test 整合測試(51 endpoint × 401)
驗證:go test -race -count=3 ./... 17 packages 全綠 / go vet 0 warning
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
102 lines
2.9 KiB
Go
102 lines
2.9 KiB
Go
// remote_handle.go — RemoteHandle 是 ProxyClientStore.Lookup 回傳的 Handle 實作。
|
||
//
|
||
// 它代表「session 在 remote-proxy 那邊」。OpenStream 走 Forwarder 的 raw forward。
|
||
//
|
||
// 注意:RemoteHandle 不持有 yamux session(那在 remote-proxy 的記憶體裡),
|
||
// 所以許多語意(Close / IsClosed)行為上跟 LocalHandle 不太一樣:
|
||
// - Close 走 ProxyClient.CloseSession(HTTP 通知 remote-proxy 關閉)
|
||
// - IsClosed 只能根據 Lookup 時的 Summary 判斷,無即時感知能力
|
||
//
|
||
// 對 api-server handler 來說,這些差異是透明的 — 只要拿 handle.OpenStream
|
||
// 來開 stream 就好。
|
||
|
||
package session
|
||
|
||
import (
|
||
"context"
|
||
"net"
|
||
"sync"
|
||
"sync/atomic"
|
||
"time"
|
||
)
|
||
|
||
// RemoteHandle 是 api-server 端的 Handle 實作。
|
||
type RemoteHandle struct {
|
||
client ProxyClient
|
||
forwarder *Forwarder
|
||
|
||
mu sync.Mutex
|
||
summary Summary
|
||
|
||
// closed 用 atomic 避免每次 IsClosed 都 lock
|
||
closed atomic.Bool
|
||
}
|
||
|
||
// newRemoteHandle 建立一個 RemoteHandle;package-internal,由 ProxyClientStore 用。
|
||
func newRemoteHandle(client ProxyClient, forwarder *Forwarder, sum *Summary) *RemoteHandle {
|
||
h := &RemoteHandle{
|
||
client: client,
|
||
forwarder: forwarder,
|
||
}
|
||
if sum != nil {
|
||
h.summary = *sum
|
||
}
|
||
return h
|
||
}
|
||
|
||
// OpenStream 走 Forwarder 開一條 raw TCP(hijack)連線。
|
||
//
|
||
// 若 Forwarder 為 nil(僅 metadata-only 場景)回明確錯誤。
|
||
func (h *RemoteHandle) OpenStream(ctx context.Context) (net.Conn, error) {
|
||
if h.closed.Load() {
|
||
return nil, ErrSessionClosed
|
||
}
|
||
if h.forwarder == nil {
|
||
return nil, ErrNotSupported
|
||
}
|
||
return h.forwarder.OpenStream(ctx, h.summary.Token)
|
||
}
|
||
|
||
// Close 透過 ProxyClient 通知 remote-proxy 關閉這個 session。
|
||
//
|
||
// 冪等:多次呼叫只會打一次 HTTP(後續直接回 nil)。
|
||
func (h *RemoteHandle) Close() error {
|
||
if !h.closed.CompareAndSwap(false, true) {
|
||
return nil // 已經 close 過
|
||
}
|
||
if h.client == nil {
|
||
return nil
|
||
}
|
||
// 用獨立的 ctx 避免 caller 取消後 close 半路斷掉
|
||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||
defer cancel()
|
||
return h.client.CloseSession(ctx, h.summary.Token)
|
||
}
|
||
|
||
// IsClosed 回報是否已被本地 Close。
|
||
//
|
||
// 注意:不會即時感知 remote-proxy 那邊的斷線;
|
||
// 真正想確認 session 還在的話,應該再呼叫一次 Lookup。
|
||
func (h *RemoteHandle) IsClosed() bool {
|
||
return h.closed.Load()
|
||
}
|
||
|
||
// Summary 回傳 session metadata 的 snapshot。
|
||
func (h *RemoteHandle) Summary() *Summary {
|
||
h.mu.Lock()
|
||
defer h.mu.Unlock()
|
||
cp := h.summary
|
||
return &cp
|
||
}
|
||
|
||
// RecordHeartbeat — RemoteHandle 不主動記錄心跳(心跳由 yamux 在 remote-proxy
|
||
// 端維護);本方法純粹更新本地 summary 的 LastHeartbeat 欄位以維持 interface 契約。
|
||
func (h *RemoteHandle) RecordHeartbeat(t time.Time) {
|
||
h.mu.Lock()
|
||
defer h.mu.Unlock()
|
||
h.summary.LastHeartbeat = t
|
||
}
|
||
|
||
// 編譯時檢查
|
||
var _ Handle = (*RemoteHandle)(nil)
|