Files
prowler/ui/lib/attack-paths/graph-colors.ts
T

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;
};