Skip to main content

Security dashboards

Findings scattered across consoles are not a posture. This pipeline turns Security Hub and Prowler findings into a queryable dataset and a dashboard that shows trend, ownership and resolution.

Architecture​

GitHub Actions ── Prowler ──► Security Hub (ASFF)
▲
AWS native findings
│
▼
EventBridge rule
│
▼
Kinesis Data Firehose
(Lambda JSON transform)
│
▼
S3 bucket
│
Athena table + view
│
▼
QuickSight dashboard

Each component has one job:

ComponentResponsibility
Prowler (in the pipeline)Independent assessment; sends findings in ASFF
Security HubAggregation of native and imported findings
EventBridgeEvery Security Hub finding is an event
FirehoseBuffers and delivers to S3
LambdaNormalises the event into a flat, queryable record
AthenaSQL over the finding archive
QuickSightDashboard for security and leadership

Why this shape​

Security Hub has a console, and it is good for triage. It is not good at answering "how did our PCI compliance trend over the last six months, by business unit, and which findings are still open after 30 days?" That is a query, and a query needs a table.

The pipeline also gives the organisation a durable archive that survives the service's own retention window.

Implementation​

EventBridge​

{
"source": ["aws.securityhub"],
"detail-type": ["Security Hub Findings - Imported"],
"detail": {
"findings": {
"Compliance": { "Status": ["FAILED", "WARNING"] }
}
}
}

Target: Kinesis Data Firehose delivery stream.

Firehose transformation​

The Lambda receives a batch of records, extracts the fields that matter and emits one JSON object per finding:

{
"finding_id": "arn:aws:securityhub:...",
"account_id": "123456789012",
"region": "us-east-1",
"product": "Security Hub",
"standard": "aws-foundational-security-best-practices/v/1.0.0",
"control_id": "S3.2",
"severity": "HIGH",
"resource_type": "AwsS3Bucket",
"resource_id": "arn:aws:s3:::example",
"workflow_status": "NEW",
"compliance_status": "FAILED",
"first_observed": "2026-11-01T08:12:00Z",
"last_observed": "2026-11-06T09:00:00Z",
"description": "S3 Block Public Access setting should be enabled"
}

Athena​

CREATE EXTERNAL TABLE security_findings (
finding_id string,
account_id string,
region string,
product string,
standard string,
control_id string,
severity string,
resource_type string,
resource_id string,
workflow_status string,
compliance_status string,
first_observed string,
last_observed string,
description string
)
PARTITIONED BY (dt string)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://security-findings/findings/';

Partition projection so new days are queryable without a repair step.

Useful queries​

Open findings by severity and account:

SELECT account_id, severity, count(*) AS findings
FROM security_findings
WHERE dt >= date_format(current_date - interval '30' day, '%Y-%m-%d')
AND workflow_status IN ('NEW', 'NOTIFIED')
AND compliance_status = 'FAILED'
GROUP BY account_id, severity
ORDER BY findings DESC;

Mean time to resolve:

SELECT severity,
approx_percentile(date_diff('hour',
from_iso8601_timestamp(resolved_at),
from_iso8601_timestamp(first_observed)), 0.5) AS median_hours
FROM finding_lifecycle
WHERE resolved_at IS NOT NULL
GROUP BY severity;

Findings open beyond policy:

SELECT resource_id, control_id, severity,
date_diff('day', from_iso8601_timestamp(first_observed), current_timestamp) AS age_days
FROM security_findings
WHERE workflow_status IN ('NEW', 'NOTIFIED')
AND date_diff('day', from_iso8601_timestamp(first_observed), current_timestamp) > 30
ORDER BY age_days DESC;

Dashboard views​

ViewContentAudience
Posture summaryOpen findings by severity, trend over 90 daysLeadership and security
By standardCompliance percentage per standardSecurity and compliance
By accountFindings per account and OUPlatform
AgeingFindings open beyond SLA, by ownerDelivery leads
Mean time to resolveBy severity and productSecurity
Top controls failingMost frequent control failuresPlatform
Resource exposurePublic resources with findingsSecurity

Operating rules​

  1. One severity model. Security Hub severity is the source of truth; other tools map into it.
  2. Ownership is explicit. Each finding resolves to a team via the resource's Owner tag or the account's owner.
  3. SLA per severity. Critical same day, high 3 days, medium 30, low next cycle.
  4. Suppression is recorded. A suppressed finding carries a justification and an expiry.
  5. The dashboard is reviewed. A dashboard nobody opens is a data pipeline with a UI.

Access and cost​

  • QuickSight access restricted to security, platform and leadership roles.
  • Athena queries are scoped by workgroup and have a query data limit.
  • Firehose buffer tuned for cost: 5 MiB or 300 seconds, whichever comes first.
  • S3 lifecycle moves the archive to infrequent access after 90 days.

Evidence​

  • Pipeline definition (EventBridge rule, Firehose, Lambda, Athena, QuickSight).
  • Dashboard screenshots per view.
  • Query library.
  • A finding traced from detection through to the dashboard.
  • Review records showing the dashboard being used.