從 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>
78 lines
2.9 KiB
Go
78 lines
2.9 KiB
Go
package oidc
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"crypto/sha256"
|
||
"encoding/base64"
|
||
"fmt"
|
||
)
|
||
|
||
// 隨機值長度常數(位元組數)。
|
||
//
|
||
// RFC 7636 §4.1 規定 code_verifier 是 43-128 個字元([A-Z a-z 0-9 - . _ ~])。
|
||
// 32 bytes 經 base64url(無 padding)編碼後 = ceil(32 * 4 / 3) = 43 字元,
|
||
// 剛好等於最小邊界 43,落在合規範圍內,且提供 256 bits 熵。
|
||
//
|
||
// state 與 nonce 沒有 RFC 規範長度,採同樣 32 bytes(256 bits)足以抵抗暴力猜測。
|
||
const (
|
||
// pkceVerifierBytes = 32 bytes → base64url 後 43 字元,符合 RFC 7636 範圍下界。
|
||
pkceVerifierBytes = 32
|
||
|
||
// stateNonceBytes = 32 bytes → 256 bits 隨機性。
|
||
stateNonceBytes = 32
|
||
)
|
||
|
||
// GenerateCodeVerifier 產生 RFC 7636 PKCE code_verifier。
|
||
//
|
||
// 回傳值是 base64url(無 padding)編碼的 32 byte 隨機值,等於 43 個字元,
|
||
// 落在 RFC 7636 §4.1 規定的 43-128 字元範圍內(剛好踩在下界)。
|
||
//
|
||
// 字元集為 [A-Z a-z 0-9 - _](base64url 規範),符合 RFC 7636 §4.1 的
|
||
// unreserved characters 子集(少了 `.` 和 `~`,但仍合規)。
|
||
func GenerateCodeVerifier() (string, error) {
|
||
return randomBase64URL(pkceVerifierBytes)
|
||
}
|
||
|
||
// CodeChallenge 由 code_verifier 算出 RFC 7636 §4.2 規定的 code_challenge:
|
||
//
|
||
// BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
|
||
//
|
||
// challenge_method 固定 S256(不支援 plain,因 OAuth 2.1 已標 plain 為 deprecated)。
|
||
//
|
||
// caller 應將回傳值放在 authorization request 的 code_challenge 參數,
|
||
// 並把 GenerateCodeVerifier() 的原值(verifier)安全地存在 server-side pending session,
|
||
// 後續 ExchangeCode 時帶 verifier 給 token endpoint 完成 proof。
|
||
func CodeChallenge(verifier string) string {
|
||
sum := sha256.Sum256([]byte(verifier))
|
||
return base64.RawURLEncoding.EncodeToString(sum[:])
|
||
}
|
||
|
||
// GenerateState 產生 OAuth 2.0 state 值,用於 CSRF 防護。
|
||
//
|
||
// caller 應將其存在 server-side pending session,並在 callback 收到後比對。
|
||
func GenerateState() (string, error) {
|
||
return randomBase64URL(stateNonceBytes)
|
||
}
|
||
|
||
// GenerateNonce 產生 OIDC nonce 值,用於 id_token replay 防護。
|
||
//
|
||
// caller 應將其存在 server-side pending session,並在 VerifyIDToken 時比對 claims.Nonce。
|
||
func GenerateNonce() (string, error) {
|
||
return randomBase64URL(stateNonceBytes)
|
||
}
|
||
|
||
// randomBase64URL 是內部 helper:產生 n bytes 隨機值並做 base64url(無 padding)編碼。
|
||
//
|
||
// 使用 crypto/rand 為密碼學安全亂數源;任何讀取錯誤都向上傳遞給 caller 處理
|
||
// (通常代表系統 entropy 出問題,應該讓請求 fail 而非回退到不安全的預設值)。
|
||
func randomBase64URL(n int) (string, error) {
|
||
if n <= 0 {
|
||
return "", fmt.Errorf("oidc: random length must be positive, got %d", n)
|
||
}
|
||
b := make([]byte, n)
|
||
if _, err := rand.Read(b); err != nil {
|
||
return "", fmt.Errorf("oidc: read random bytes: %w", err)
|
||
}
|
||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||
}
|