feat(ssf): 实现安全事件一键连接与凭据托管
新增数据库驱动的 SecurityEventConnectionManager,复用 RFC 7662 机器 Client,自动完成 Discovery、Push Bearer 生成、Stream 创建、Verification、首次启用、零停机轮换和安全退役。 增加文件与 Kubernetes SecretStore、最小权限 RBAC、动态 Receiver/撤销水位/内省降级,以及系统设置管理页面。已通过全量 Go 测试、go vet、关键包竞态测试、27 个 Web 测试、类型检查、生产构建、Compose 和 Kubernetes dry-run。
This commit is contained in:
+58
-2
@@ -32,6 +32,7 @@ import type {
|
||||
PricingRuleSet,
|
||||
RateLimitWindow,
|
||||
RuntimePolicySet,
|
||||
SecurityEventConnectionResponse,
|
||||
UserGroupUpsertRequest,
|
||||
UserGroup,
|
||||
WalletRechargeRequest,
|
||||
@@ -46,6 +47,7 @@ import {
|
||||
createPlatform,
|
||||
createTenant,
|
||||
createUserGroup,
|
||||
connectSecurityEventTransmitter,
|
||||
deleteAccessRule,
|
||||
deleteApiKey,
|
||||
deleteFileStorageChannel,
|
||||
@@ -53,6 +55,7 @@ import {
|
||||
deletePlatform,
|
||||
deleteTenant,
|
||||
deleteUserGroup,
|
||||
disconnectSecurityEventConnection,
|
||||
GatewayApiError,
|
||||
getClientCustomizationSettings,
|
||||
getHealth,
|
||||
@@ -61,6 +64,7 @@ import {
|
||||
getFileStorageSettings,
|
||||
getNetworkProxyConfig,
|
||||
getRunnerPolicy,
|
||||
getSecurityEventConnection,
|
||||
getWalletSummary,
|
||||
listAccessRules,
|
||||
listAuditLogs,
|
||||
@@ -93,6 +97,7 @@ import {
|
||||
rechargeUserWalletBalance,
|
||||
replacePlatformModels,
|
||||
restoreModelRuntimeStatus,
|
||||
rotateSecurityEventConnectionCredential,
|
||||
type HealthResponse,
|
||||
updateAccessRule,
|
||||
updateApiKeyScopes,
|
||||
@@ -104,6 +109,7 @@ import {
|
||||
updatePlatformDynamicPriority,
|
||||
updateTenant,
|
||||
updateUserGroup,
|
||||
verifySecurityEventConnection,
|
||||
} from './api';
|
||||
import type { ConsoleData, StatItem } from './app-state';
|
||||
import { AppShell } from './components/layout/AppShell';
|
||||
@@ -173,6 +179,7 @@ type DataKey =
|
||||
| 'clientCustomizationSettings'
|
||||
| 'fileStorageChannels'
|
||||
| 'fileStorageSettings'
|
||||
| 'securityEventConnection'
|
||||
| 'platforms'
|
||||
| 'models'
|
||||
| 'providers'
|
||||
@@ -222,6 +229,7 @@ export function App() {
|
||||
const [clientCustomizationSettings, setClientCustomizationSettings] = useState<ClientCustomizationSettings | null>(null);
|
||||
const [fileStorageChannels, setFileStorageChannels] = useState<FileStorageChannel[]>([]);
|
||||
const [fileStorageSettings, setFileStorageSettings] = useState<FileStorageSettings | null>(null);
|
||||
const [securityEventConnection, setSecurityEventConnection] = useState<SecurityEventConnectionResponse | null>(null);
|
||||
const [providers, setProviders] = useState<CatalogProvider[]>([]);
|
||||
const [baseModels, setBaseModels] = useState<BaseModelCatalogItem[]>([]);
|
||||
const [pricingRules, setPricingRules] = useState<PricingRule[]>([]);
|
||||
@@ -383,6 +391,7 @@ export function App() {
|
||||
modelRateLimits,
|
||||
modelRateLimitsUpdatedAt,
|
||||
runtimePolicySets,
|
||||
securityEventConnection,
|
||||
taskResult,
|
||||
tasks,
|
||||
tenants,
|
||||
@@ -390,7 +399,7 @@ export function App() {
|
||||
users,
|
||||
walletAccounts,
|
||||
walletTransactions,
|
||||
}), [accessRules, apiKeys, auditLogs, baseModels, clientCustomizationSettings, currentUser, currentUserGroups, fileStorageChannels, fileStorageSettings, modelCatalog, modelRateLimits, modelRateLimitsUpdatedAt, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
|
||||
}), [accessRules, apiKeys, auditLogs, baseModels, clientCustomizationSettings, currentUser, currentUserGroups, fileStorageChannels, fileStorageSettings, modelCatalog, modelRateLimits, modelRateLimitsUpdatedAt, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, securityEventConnection, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
|
||||
|
||||
async function refresh(nextToken = token) {
|
||||
await ensureRouteData(nextToken, true);
|
||||
@@ -480,6 +489,9 @@ export function App() {
|
||||
case 'fileStorageSettings':
|
||||
setFileStorageSettings(await getFileStorageSettings(nextToken));
|
||||
return;
|
||||
case 'securityEventConnection':
|
||||
setSecurityEventConnection(await getSecurityEventConnection(nextToken));
|
||||
return;
|
||||
case 'playgroundModels':
|
||||
setPlaygroundModels((await listPlayableModels(nextToken)).items);
|
||||
return;
|
||||
@@ -1017,6 +1029,44 @@ export function App() {
|
||||
}
|
||||
}
|
||||
|
||||
async function connectSecurityEvents(transmitterIssuer: string) {
|
||||
setCoreState('loading');
|
||||
setCoreMessage('');
|
||||
try {
|
||||
const connection = await connectSecurityEventTransmitter(token, transmitterIssuer);
|
||||
setSecurityEventConnection(connection);
|
||||
setCoreState('ready');
|
||||
setCoreMessage('认证中心安全事件连接正在验证。');
|
||||
} catch (err) {
|
||||
setCoreState('error');
|
||||
setCoreMessage(err instanceof Error ? err.message : '连接认证中心失败');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshSecurityEvents() {
|
||||
const connection = await getSecurityEventConnection(token);
|
||||
setSecurityEventConnection(connection);
|
||||
}
|
||||
|
||||
async function verifySecurityEvents() {
|
||||
const connection = securityEventConnection?.connection;
|
||||
if (!connection) return;
|
||||
setSecurityEventConnection(await verifySecurityEventConnection(token, connection.version));
|
||||
}
|
||||
|
||||
async function rotateSecurityEventsCredential() {
|
||||
const connection = securityEventConnection?.connection;
|
||||
if (!connection) return;
|
||||
setSecurityEventConnection(await rotateSecurityEventConnectionCredential(token, connection.version));
|
||||
}
|
||||
|
||||
async function disconnectSecurityEvents() {
|
||||
const connection = securityEventConnection?.connection;
|
||||
if (!connection) return;
|
||||
setSecurityEventConnection(await disconnectSecurityEventConnection(token, connection.version));
|
||||
}
|
||||
|
||||
async function removeFileStorageChannel(channelId: string) {
|
||||
setCoreState('loading');
|
||||
setCoreMessage('');
|
||||
@@ -1095,6 +1145,7 @@ export function App() {
|
||||
setPlaygroundModels([]);
|
||||
setNetworkProxyConfig(null);
|
||||
setFileStorageChannels([]);
|
||||
setSecurityEventConnection(null);
|
||||
setProviders([]);
|
||||
setBaseModels([]);
|
||||
setPricingRules([]);
|
||||
@@ -1332,6 +1383,11 @@ export function App() {
|
||||
onSaveFileStorageChannel={saveFileStorageChannel}
|
||||
onSaveFileStorageSettings={saveFileStorageSettings}
|
||||
onSaveClientCustomizationSettings={saveClientCustomizationSettings}
|
||||
onConnectSecurityEvents={connectSecurityEvents}
|
||||
onDisconnectSecurityEvents={disconnectSecurityEvents}
|
||||
onRefreshSecurityEvents={refreshSecurityEvents}
|
||||
onRotateSecurityEventsCredential={rotateSecurityEventsCredential}
|
||||
onVerifySecurityEvents={verifySecurityEvents}
|
||||
onSaveTenant={saveTenant}
|
||||
onSaveUser={saveUser}
|
||||
onRechargeUserWalletBalance={rechargeUserWallet}
|
||||
@@ -1553,7 +1609,7 @@ function dataKeysForRoute(
|
||||
case 'accessRules':
|
||||
return ['accessRules', 'userGroups', 'platforms', 'models'];
|
||||
case 'systemSettings':
|
||||
return ['clientCustomizationSettings', 'fileStorageSettings', 'fileStorageChannels'];
|
||||
return ['clientCustomizationSettings', 'fileStorageSettings', 'fileStorageChannels', 'securityEventConnection'];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user