feat(identity): 实现统一认证运行时热切换
从 Active Revision 构造并验证 OIDC、BFF Session、Introspection 与 SSF Runtime,在数据库激活成功后原子替换内存引用。请求链路使用不可变快照,失败保留当前运行时,本地管理登录不受影响。\n\n验证:go test ./apps/api/...;go vet ./apps/api/...
This commit is contained in:
@@ -31,11 +31,12 @@ type securityEventConnectionResponse struct {
|
||||
func (s *Server) getSecurityEventConnection(w http.ResponseWriter, r *http.Request) {
|
||||
ensureSecurityEventTraceID(w, r)
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
if s.securityEventManager == nil {
|
||||
manager := s.currentSecurityEventManager()
|
||||
if manager == nil {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"connected": false, "lifecycleStatus": "disconnected", "prerequisites": s.securityEventPrerequisites()})
|
||||
return
|
||||
}
|
||||
connection, err := s.securityEventManager.Get(r.Context())
|
||||
connection, err := manager.Get(r.Context())
|
||||
if errors.Is(err, store.ErrSecurityEventConnectionNotFound) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"connected": false, "lifecycleStatus": "disconnected", "prerequisites": s.securityEventPrerequisites()})
|
||||
return
|
||||
@@ -50,7 +51,8 @@ func (s *Server) getSecurityEventConnection(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
func (s *Server) putSecurityEventConnection(w http.ResponseWriter, r *http.Request) {
|
||||
traceID := ensureSecurityEventTraceID(w, r)
|
||||
if s.securityEventManager == nil {
|
||||
manager := s.currentSecurityEventManager()
|
||||
if manager == nil {
|
||||
writeError(w, http.StatusConflict, "OIDC must be configured before connecting security events", "security_event_prerequisite_missing")
|
||||
return
|
||||
}
|
||||
@@ -79,12 +81,12 @@ func (s *Server) putSecurityEventConnection(w http.ResponseWriter, r *http.Reque
|
||||
if s.replaySecurityEventOperation(w, r, "connect", idempotencyKey, requestHash) {
|
||||
return
|
||||
}
|
||||
if current, err := s.securityEventManager.Get(r.Context()); err == nil && !matchConnectionVersion(w, r, current.Version) {
|
||||
if current, err := manager.Get(r.Context()); err == nil && !matchConnectionVersion(w, r, current.Version) {
|
||||
return
|
||||
}
|
||||
connection, err := s.securityEventManager.Connect(r.Context(), request.TransmitterIssuer, request.ManagementClientID, secret, idempotencyKey)
|
||||
connection, err := manager.Connect(r.Context(), request.TransmitterIssuer, request.ManagementClientID, secret, idempotencyKey)
|
||||
if err != nil {
|
||||
s.writeSecurityEventConnectionError(w, r, "connect", traceID, err)
|
||||
s.writeSecurityEventConnectionError(w, r, manager, "connect", traceID, err)
|
||||
return
|
||||
}
|
||||
s.writeSecurityEventOperation(w, r, "connect", idempotencyKey, requestHash, traceID, connection)
|
||||
@@ -92,13 +94,14 @@ func (s *Server) putSecurityEventConnection(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
func (s *Server) verifySecurityEventConnection(w http.ResponseWriter, r *http.Request) {
|
||||
traceID := ensureSecurityEventTraceID(w, r)
|
||||
idempotencyKey, requestHash, ok := s.beginSecurityEventOperation(w, r, "verify", traceID)
|
||||
manager := s.currentSecurityEventManager()
|
||||
idempotencyKey, requestHash, ok := s.beginSecurityEventOperation(w, r, manager, "verify", traceID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
connection, err := s.securityEventManager.Verify(r.Context())
|
||||
connection, err := manager.Verify(r.Context())
|
||||
if err != nil {
|
||||
s.writeSecurityEventConnectionError(w, r, "verify", traceID, err)
|
||||
s.writeSecurityEventConnectionError(w, r, manager, "verify", traceID, err)
|
||||
return
|
||||
}
|
||||
s.writeSecurityEventOperation(w, r, "verify", idempotencyKey, requestHash, traceID, connection)
|
||||
@@ -106,13 +109,14 @@ func (s *Server) verifySecurityEventConnection(w http.ResponseWriter, r *http.Re
|
||||
|
||||
func (s *Server) rotateSecurityEventConnectionCredential(w http.ResponseWriter, r *http.Request) {
|
||||
traceID := ensureSecurityEventTraceID(w, r)
|
||||
idempotencyKey, requestHash, ok := s.beginSecurityEventOperation(w, r, "rotate", traceID)
|
||||
manager := s.currentSecurityEventManager()
|
||||
idempotencyKey, requestHash, ok := s.beginSecurityEventOperation(w, r, manager, "rotate", traceID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
connection, err := s.securityEventManager.RotateCredential(r.Context())
|
||||
connection, err := manager.RotateCredential(r.Context())
|
||||
if err != nil {
|
||||
s.writeSecurityEventConnectionError(w, r, "rotate", traceID, err)
|
||||
s.writeSecurityEventConnectionError(w, r, manager, "rotate", traceID, err)
|
||||
return
|
||||
}
|
||||
s.writeSecurityEventOperation(w, r, "rotate", idempotencyKey, requestHash, traceID, connection)
|
||||
@@ -120,25 +124,34 @@ func (s *Server) rotateSecurityEventConnectionCredential(w http.ResponseWriter,
|
||||
|
||||
func (s *Server) deleteSecurityEventConnection(w http.ResponseWriter, r *http.Request) {
|
||||
traceID := ensureSecurityEventTraceID(w, r)
|
||||
idempotencyKey, requestHash, ok := s.beginSecurityEventOperation(w, r, "disconnect", traceID)
|
||||
manager := s.currentSecurityEventManager()
|
||||
idempotencyKey, requestHash, ok := s.beginSecurityEventOperation(w, r, manager, "disconnect", traceID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
connection, err := s.securityEventManager.Disconnect(r.Context())
|
||||
connection, err := manager.Disconnect(r.Context())
|
||||
if err != nil {
|
||||
s.writeSecurityEventConnectionError(w, r, "disconnect", traceID, err)
|
||||
s.writeSecurityEventConnectionError(w, r, manager, "disconnect", traceID, err)
|
||||
return
|
||||
}
|
||||
s.writeSecurityEventOperation(w, r, "disconnect", idempotencyKey, requestHash, traceID, connection)
|
||||
}
|
||||
|
||||
func (s *Server) securityEventPrerequisites() map[string]any {
|
||||
runtime := s.currentIdentityRuntime()
|
||||
if runtime == nil {
|
||||
return map[string]any{
|
||||
"oidcConfigured": false, "introspectionClientConfigured": false,
|
||||
"publicBaseUrlConfigured": false, "managementClientId": "", "credentialInputSupported": false,
|
||||
}
|
||||
}
|
||||
revision := runtime.Revision
|
||||
return map[string]any{
|
||||
"oidcConfigured": s.cfg.OIDCEnabled && s.cfg.OIDCIssuer != "" && s.cfg.OIDCTenantID != "",
|
||||
"introspectionClientConfigured": s.cfg.OIDCIntrospectionClientID != "" && s.cfg.OIDCIntrospectionClientSecret != "",
|
||||
"publicBaseUrlConfigured": s.cfg.PublicBaseURL != "",
|
||||
"managementClientId": s.cfg.OIDCIntrospectionClientID,
|
||||
"credentialInputSupported": true,
|
||||
"oidcConfigured": revision.Issuer != "" && revision.TenantID != "",
|
||||
"introspectionClientConfigured": revision.MachineClientID != "" && revision.MachineCredentialRef != "",
|
||||
"publicBaseUrlConfigured": revision.PublicBaseURL != "",
|
||||
"managementClientId": revision.MachineClientID,
|
||||
"credentialInputSupported": false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,8 +164,8 @@ func requiredConnectionIdempotencyKey(w http.ResponseWriter, r *http.Request) (s
|
||||
return value, true
|
||||
}
|
||||
|
||||
func (s *Server) beginSecurityEventOperation(w http.ResponseWriter, r *http.Request, operation, traceID string) (string, string, bool) {
|
||||
if s.securityEventManager == nil {
|
||||
func (s *Server) beginSecurityEventOperation(w http.ResponseWriter, r *http.Request, manager *ssfreceiver.ConnectionManager, operation, traceID string) (string, string, bool) {
|
||||
if manager == nil {
|
||||
writeError(w, http.StatusNotFound, "security event connection does not exist", "security_event_connection_not_found")
|
||||
return "", "", false
|
||||
}
|
||||
@@ -164,9 +177,9 @@ func (s *Server) beginSecurityEventOperation(w http.ResponseWriter, r *http.Requ
|
||||
if s.replaySecurityEventOperation(w, r, operation, idempotencyKey, requestHash) {
|
||||
return "", "", false
|
||||
}
|
||||
connection, err := s.securityEventManager.Get(r.Context())
|
||||
connection, err := manager.Get(r.Context())
|
||||
if err != nil {
|
||||
s.writeSecurityEventConnectionError(w, r, operation, traceID, err)
|
||||
s.writeSecurityEventConnectionError(w, r, manager, operation, traceID, err)
|
||||
return "", "", false
|
||||
}
|
||||
return idempotencyKey, requestHash, matchConnectionVersion(w, r, connection.Version)
|
||||
@@ -247,11 +260,11 @@ func matchConnectionVersion(w http.ResponseWriter, r *http.Request, expected int
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Server) writeSecurityEventConnectionError(w http.ResponseWriter, r *http.Request, operation, traceID string, err error) {
|
||||
func (s *Server) writeSecurityEventConnectionError(w http.ResponseWriter, r *http.Request, manager *ssfreceiver.ConnectionManager, operation, traceID string, err error) {
|
||||
status, message, code := securityEventConnectionErrorProjection(err)
|
||||
connection := ssfreceiver.ConnectionView{}
|
||||
if s.securityEventManager != nil {
|
||||
connection, _ = s.securityEventManager.Get(r.Context())
|
||||
if manager != nil {
|
||||
connection, _ = manager.Get(r.Context())
|
||||
}
|
||||
errorCategory := code
|
||||
if connection.LastErrorCategory != nil && *connection.LastErrorCategory != "" {
|
||||
|
||||
Reference in New Issue
Block a user