mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-24 20:34:16 +00:00
208 lines
7.3 KiB
TypeScript
208 lines
7.3 KiB
TypeScript
import {
|
|
GROUP_NODE_LABEL,
|
|
GROUP_PROPS,
|
|
OUTCOME_NODE_LABEL,
|
|
} from "@/lib/attack-paths/group-graph";
|
|
import { isProwlerFindingNode } from "@/lib/attack-paths/node-types";
|
|
|
|
/**
|
|
* Color constants for attack path graph visualization
|
|
* Colors chosen to work well in both light and dark themes
|
|
*/
|
|
|
|
/**
|
|
* Node fill colors - darker versions of design system severity colors
|
|
* Darkened to ensure white text has proper contrast (WCAG AA)
|
|
*/
|
|
export const GRAPH_NODE_COLORS = {
|
|
// Finding severities - darkened versions for white text readability
|
|
critical: "#cc0055", // Darker pink (from #ff006a)
|
|
high: "#c45a3a", // Darker coral (from #f77852)
|
|
medium: "#b8860b", // Dark goldenrod (from #fec94d)
|
|
low: "#8b9a3e", // Olive/dark yellow-green (from #fdfbd4)
|
|
info: "#2563eb", // Darker blue (from #3c8dff)
|
|
// Node types
|
|
prowlerFinding: "#ea580c",
|
|
awsAccount: "#f59e0b", // Amber 500 - AWS orange
|
|
attackPattern: "#16a34a",
|
|
summary: "#16a34a",
|
|
// Infrastructure
|
|
ec2Instance: "#0891b2", // Cyan 600
|
|
s3Bucket: "#0284c7", // Sky 600
|
|
iamRole: "#7c3aed", // Violet 600
|
|
iamPolicy: "#7c3aed",
|
|
lambdaFunction: "#d97706", // Amber 600
|
|
securityGroup: "#0891b2",
|
|
default: "#0891b2",
|
|
} as const;
|
|
|
|
/**
|
|
* Node border colors - using original design system colors as borders (lighter than fill)
|
|
*/
|
|
export const GRAPH_NODE_BORDER_COLORS = {
|
|
critical: "#ff006a", // Original --bg-data-critical
|
|
high: "#f77852", // Original --bg-data-high
|
|
medium: "#fec94d", // Original --bg-data-medium
|
|
low: "#c4d4a0", // Lighter olive
|
|
info: "#3c8dff", // Original --bg-data-info
|
|
prowlerFinding: "#fb923c",
|
|
awsAccount: "#fbbf24", // Amber 400
|
|
attackPattern: "#4ade80",
|
|
summary: "#4ade80",
|
|
ec2Instance: "#22d3ee", // Cyan 400
|
|
s3Bucket: "#38bdf8", // Sky 400
|
|
iamRole: "#a78bfa", // Violet 400
|
|
iamPolicy: "#a78bfa",
|
|
lambdaFunction: "#fbbf24",
|
|
securityGroup: "#22d3ee",
|
|
default: "#22d3ee",
|
|
} as const;
|
|
|
|
// Edge colors per theme
|
|
export const GRAPH_EDGE_COLOR_DARK = "#ffffff"; // White for dark theme
|
|
export const GRAPH_EDGE_COLOR_LIGHT = "#1e293b"; // Slate 800 for light theme
|
|
export const GRAPH_EDGE_HIGHLIGHT_COLOR = "#34d399"; // Prowler green (hover/selection)
|
|
export const GRAPH_EDGE_GLOW_COLOR = "#6ee7b7";
|
|
export const GRAPH_SELECTION_COLOR = "#ffffff";
|
|
export const GRAPH_BORDER_COLOR = "#374151";
|
|
export const GRAPH_ALERT_BORDER_COLOR = "#ef4444"; // Red 500 - for resources with findings
|
|
|
|
// Terminal outcome node: distinct orange, independent of the resource palette.
|
|
export const GRAPH_OUTCOME_FILL_COLOR = "#c2410c"; // Orange 700
|
|
export const GRAPH_OUTCOME_BORDER_COLOR = "#f97316"; // Orange 500
|
|
// Outline behind a group node's count badge, for contrast on any fill.
|
|
export const GRAPH_COUNT_BADGE_STROKE_COLOR = "#0b1120"; // Slate 950
|
|
|
|
/**
|
|
* Get node fill color based on labels and properties
|
|
*/
|
|
export const getNodeColor = (
|
|
labels: string[],
|
|
properties?: Record<string, unknown>,
|
|
): string => {
|
|
if (labels.includes(OUTCOME_NODE_LABEL)) {
|
|
return GRAPH_OUTCOME_FILL_COLOR;
|
|
}
|
|
|
|
if (labels.includes(GROUP_NODE_LABEL)) {
|
|
const classLabel = String(properties?.[GROUP_PROPS.CLASS] ?? "");
|
|
return getNodeColor([classLabel]);
|
|
}
|
|
|
|
const isFinding = isProwlerFindingNode(labels);
|
|
if (isFinding && properties?.severity) {
|
|
const severity = String(properties.severity).toLowerCase();
|
|
if (severity === "critical") return GRAPH_NODE_COLORS.critical;
|
|
if (severity === "high") return GRAPH_NODE_COLORS.high;
|
|
if (severity === "medium") return GRAPH_NODE_COLORS.medium;
|
|
if (severity === "low") return GRAPH_NODE_COLORS.low;
|
|
if (severity === "informational" || severity === "info")
|
|
return GRAPH_NODE_COLORS.info;
|
|
return GRAPH_NODE_COLORS.prowlerFinding;
|
|
}
|
|
|
|
if (labels.some((l) => l.toLowerCase().includes("attackpattern")))
|
|
return GRAPH_NODE_COLORS.attackPattern;
|
|
if (labels.includes("AWSAccount")) return GRAPH_NODE_COLORS.awsAccount;
|
|
if (labels.includes("EC2Instance")) return GRAPH_NODE_COLORS.ec2Instance;
|
|
if (labels.includes("S3Bucket")) return GRAPH_NODE_COLORS.s3Bucket;
|
|
if (labels.includes("IAMRole")) return GRAPH_NODE_COLORS.iamRole;
|
|
if (labels.includes("IAMPolicy")) return GRAPH_NODE_COLORS.iamPolicy;
|
|
if (labels.includes("LambdaFunction"))
|
|
return GRAPH_NODE_COLORS.lambdaFunction;
|
|
if (labels.includes("SecurityGroup")) return GRAPH_NODE_COLORS.securityGroup;
|
|
|
|
return GRAPH_NODE_COLORS.default;
|
|
};
|
|
|
|
/**
|
|
* Get node border color based on labels and properties
|
|
*/
|
|
export const getNodeBorderColor = (
|
|
labels: string[],
|
|
properties?: Record<string, unknown>,
|
|
): string => {
|
|
if (labels.includes(OUTCOME_NODE_LABEL)) {
|
|
return GRAPH_OUTCOME_BORDER_COLOR;
|
|
}
|
|
|
|
if (labels.includes(GROUP_NODE_LABEL)) {
|
|
if (properties?.[GROUP_PROPS.HAS_FINDINGS]) {
|
|
return GRAPH_ALERT_BORDER_COLOR;
|
|
}
|
|
|
|
const classLabel = String(properties?.[GROUP_PROPS.CLASS] ?? "");
|
|
return getNodeBorderColor([classLabel]);
|
|
}
|
|
|
|
const isFinding = isProwlerFindingNode(labels);
|
|
if (isFinding && properties?.severity) {
|
|
const severity = String(properties.severity).toLowerCase();
|
|
if (severity === "critical") return GRAPH_NODE_BORDER_COLORS.critical;
|
|
if (severity === "high") return GRAPH_NODE_BORDER_COLORS.high;
|
|
if (severity === "medium") return GRAPH_NODE_BORDER_COLORS.medium;
|
|
if (severity === "low") return GRAPH_NODE_BORDER_COLORS.low;
|
|
if (severity === "informational" || severity === "info")
|
|
return GRAPH_NODE_BORDER_COLORS.info;
|
|
return GRAPH_NODE_BORDER_COLORS.prowlerFinding;
|
|
}
|
|
|
|
if (labels.some((l) => l.toLowerCase().includes("attackpattern")))
|
|
return GRAPH_NODE_BORDER_COLORS.attackPattern;
|
|
if (labels.includes("AWSAccount")) return GRAPH_NODE_BORDER_COLORS.awsAccount;
|
|
if (labels.includes("EC2Instance"))
|
|
return GRAPH_NODE_BORDER_COLORS.ec2Instance;
|
|
if (labels.includes("S3Bucket")) return GRAPH_NODE_BORDER_COLORS.s3Bucket;
|
|
if (labels.includes("IAMRole")) return GRAPH_NODE_BORDER_COLORS.iamRole;
|
|
if (labels.includes("IAMPolicy")) return GRAPH_NODE_BORDER_COLORS.iamPolicy;
|
|
if (labels.includes("LambdaFunction"))
|
|
return GRAPH_NODE_BORDER_COLORS.lambdaFunction;
|
|
if (labels.includes("SecurityGroup"))
|
|
return GRAPH_NODE_BORDER_COLORS.securityGroup;
|
|
|
|
return GRAPH_NODE_BORDER_COLORS.default;
|
|
};
|
|
|
|
interface ResolveNodeColorsParams {
|
|
labels: string[];
|
|
properties?: Record<string, unknown>;
|
|
selected?: boolean;
|
|
hasFindings?: boolean;
|
|
}
|
|
|
|
interface NodeColorResult {
|
|
fillColor: string;
|
|
borderColor: string;
|
|
}
|
|
|
|
/**
|
|
* Resolve fill and border colors for a graph node, layering selection and
|
|
* finding-alert state on top of the label/severity defaults.
|
|
*/
|
|
export const resolveNodeColors = ({
|
|
labels,
|
|
properties,
|
|
selected,
|
|
hasFindings,
|
|
}: ResolveNodeColorsParams): NodeColorResult => {
|
|
const fillColor = getNodeColor(labels, properties);
|
|
const borderColor = selected
|
|
? GRAPH_EDGE_HIGHLIGHT_COLOR
|
|
: hasFindings
|
|
? GRAPH_ALERT_BORDER_COLOR
|
|
: getNodeBorderColor(labels, properties);
|
|
return { fillColor, borderColor };
|
|
};
|
|
|
|
/**
|
|
* Check if a background color is light (for determining text color)
|
|
*/
|
|
export const isLightBackground = (backgroundColor: string): boolean => {
|
|
const hex = backgroundColor.replace("#", "");
|
|
const r = parseInt(hex.substring(0, 2), 16);
|
|
const g = parseInt(hex.substring(2, 4), 16);
|
|
const b = parseInt(hex.substring(4, 6), 16);
|
|
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
return luminance > 0.5;
|
|
};
|