feat(security): add security policy management and IP intelligence operations to the ops UI

This commit is contained in:
2026-04-26 19:51:08 +00:00
parent 1567606c49
commit e5c3578163
9 changed files with 991 additions and 62 deletions
+236 -3
View File
@@ -54,6 +54,7 @@ export interface INetworkState {
topIPs: Array<{ ip: string; count: number }>;
topIPsByBandwidth: Array<{ ip: string; count: number; bwIn: number; bwOut: number }>;
throughputByIP: Array<{ ip: string; in: number; out: number }>;
ipIntelligence: interfaces.data.IIpIntelligenceRecord[];
domainActivity: interfaces.data.IDomainActivity[];
throughputHistory: Array<{ timestamp: number; in: number; out: number }>;
requestsPerSecond: number;
@@ -66,6 +67,16 @@ export interface INetworkState {
error: string | null;
}
export interface ISecurityPolicyState {
rules: interfaces.data.ISecurityBlockRule[];
ipIntelligence: interfaces.data.IIpIntelligenceRecord[];
compiledPolicy: interfaces.data.ISecurityCompiledPolicy | null;
auditEvents: interfaces.data.ISecurityPolicyAuditEvent[];
isLoading: boolean;
error: string | null;
lastUpdated: number;
}
export interface ICertificateState {
certificates: interfaces.requests.ICertificateInfo[];
summary: { total: number; valid: number; expiring: number; expired: number; failed: number; unknown: number };
@@ -164,6 +175,7 @@ export const networkStatePart = await appState.getStatePart<INetworkState>(
topIPs: [],
topIPsByBandwidth: [],
throughputByIP: [],
ipIntelligence: [],
domainActivity: [],
throughputHistory: [],
requestsPerSecond: 0,
@@ -178,6 +190,20 @@ export const networkStatePart = await appState.getStatePart<INetworkState>(
'soft'
);
export const securityPolicyStatePart = await appState.getStatePart<ISecurityPolicyState>(
'securityPolicy',
{
rules: [],
ipIntelligence: [],
compiledPolicy: null,
auditEvents: [],
isLoading: false,
error: null,
lastUpdated: 0,
},
'soft',
);
export const emailOpsStatePart = await appState.getStatePart<IEmailOpsState>(
'emailOps',
{
@@ -517,9 +543,18 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
interfaces.requests.IReq_GetNetworkStats
>('/typedrequest', 'getNetworkStats');
const networkStatsResponse = await networkStatsRequest.fire({
identity: context.identity,
});
const ipIntelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListIpIntelligence
>('/typedrequest', 'listIpIntelligence');
const [networkStatsResponse, ipIntelligenceResponse] = await Promise.all([
networkStatsRequest.fire({
identity: context.identity,
}),
ipIntelligenceRequest.fire({
identity: context.identity,
}),
]);
// Use the connections data for the connection list
// and network stats for throughput and IP analytics
@@ -561,6 +596,7 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
topIPs: networkStatsResponse.topIPs || [],
topIPsByBandwidth: networkStatsResponse.topIPsByBandwidth || [],
throughputByIP: networkStatsResponse.throughputByIP || [],
ipIntelligence: ipIntelligenceResponse.records || [],
domainActivity: networkStatsResponse.domainActivity || [],
throughputHistory: networkStatsResponse.throughputHistory || [],
requestsPerSecond: networkStatsResponse.requestsPerSecond || 0,
@@ -582,6 +618,182 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
}
});
// ============================================================================
// Security Policy Actions
// ============================================================================
export const fetchSecurityPolicyAction = securityPolicyStatePart.createAction(
async (statePartArg): Promise<ISecurityPolicyState> => {
const context = getActionContext();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
const rulesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListSecurityBlockRules
>('/typedrequest', 'listSecurityBlockRules');
const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListIpIntelligence
>('/typedrequest', 'listIpIntelligence');
const compiledPolicyRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetCompiledSecurityPolicy
>('/typedrequest', 'getCompiledSecurityPolicy');
const auditRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListSecurityPolicyAudit
>('/typedrequest', 'listSecurityPolicyAudit');
const [rulesResponse, intelligenceResponse, compiledPolicyResponse, auditResponse] = await Promise.all([
rulesRequest.fire({ identity: context.identity }),
intelligenceRequest.fire({ identity: context.identity }),
compiledPolicyRequest.fire({ identity: context.identity }),
auditRequest.fire({ identity: context.identity, limit: 100 }),
]);
return {
rules: rulesResponse.rules || [],
ipIntelligence: intelligenceResponse.records || [],
compiledPolicy: compiledPolicyResponse.policy,
auditEvents: auditResponse.events || [],
isLoading: false,
error: null,
lastUpdated: Date.now(),
};
} catch (error: unknown) {
return {
...currentState,
isLoading: false,
error: error instanceof Error ? error.message : 'Failed to fetch security policy',
};
}
},
);
export const createSecurityBlockRuleAction = securityPolicyStatePart.createAction<{
type: interfaces.data.TSecurityBlockRuleType;
value: string;
matchMode?: interfaces.data.TSecurityBlockRuleMatchMode;
reason?: string;
enabled?: boolean;
}>(async (statePartArg, dataArg, actionContext): Promise<ISecurityPolicyState> => {
const context = getActionContext();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_CreateSecurityBlockRule
>('/typedrequest', 'createSecurityBlockRule');
const response = await request.fire({
identity: context.identity,
type: dataArg.type,
value: dataArg.value,
matchMode: dataArg.matchMode,
reason: dataArg.reason,
enabled: dataArg.enabled,
});
if (!response.success) {
return { ...currentState, error: response.message || 'Failed to create security block rule' };
}
return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to create security block rule',
};
}
});
export const updateSecurityBlockRuleAction = securityPolicyStatePart.createAction<{
id: string;
value?: string;
matchMode?: interfaces.data.TSecurityBlockRuleMatchMode;
reason?: string;
enabled?: boolean;
}>(async (statePartArg, dataArg, actionContext): Promise<ISecurityPolicyState> => {
const context = getActionContext();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_UpdateSecurityBlockRule
>('/typedrequest', 'updateSecurityBlockRule');
const response = await request.fire({
identity: context.identity,
id: dataArg.id,
value: dataArg.value,
matchMode: dataArg.matchMode,
reason: dataArg.reason,
enabled: dataArg.enabled,
});
if (!response.success) {
return { ...currentState, error: response.message || 'Failed to update security block rule' };
}
return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to update security block rule',
};
}
});
export const deleteSecurityBlockRuleAction = securityPolicyStatePart.createAction<string>(
async (statePartArg, ruleId, actionContext): Promise<ISecurityPolicyState> => {
const context = getActionContext();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_DeleteSecurityBlockRule
>('/typedrequest', 'deleteSecurityBlockRule');
const response = await request.fire({ identity: context.identity, id: ruleId });
if (!response.success) {
return { ...currentState, error: response.message || 'Failed to delete security block rule' };
}
return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to delete security block rule',
};
}
},
);
export const refreshIpIntelligenceAction = securityPolicyStatePart.createAction<string>(
async (statePartArg, ipAddress, actionContext): Promise<ISecurityPolicyState> => {
const context = getActionContext();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_RefreshIpIntelligence
>('/typedrequest', 'refreshIpIntelligence');
const response = await request.fire({ identity: context.identity, ipAddress });
if (!response.success) {
return { ...currentState, error: response.message || 'Failed to refresh IP intelligence' };
}
return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to refresh IP intelligence',
};
}
},
);
// ============================================================================
// Email Operations Actions
// ============================================================================
@@ -2665,6 +2877,27 @@ async function dispatchCombinedRefreshActionInner() {
isLoading: false,
error: null,
});
try {
const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_ListIpIntelligence
>('/typedrequest', 'listIpIntelligence');
const intelligenceResponse = await intelligenceRequest.fire({ identity: context.identity });
networkStatePart.setState({
...networkStatePart.getState()!,
ipIntelligence: intelligenceResponse.records || [],
});
} catch (error) {
console.error('IP intelligence refresh failed:', error);
}
}
if (currentView === 'security') {
try {
await securityPolicyStatePart.dispatchAction(fetchSecurityPolicyAction, null);
} catch (error) {
console.error('Security policy refresh failed:', error);
}
}
// Refresh certificate data if on Domains > Certificates subview