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:
| Component | Responsibility |
|---|---|
| Prowler (in the pipeline) | Independent assessment; sends findings in ASFF |
| Security Hub | Aggregation of native and imported findings |
| EventBridge | Every Security Hub finding is an event |
| Firehose | Buffers and delivers to S3 |
| Lambda | Normalises the event into a flat, queryable record |
| Athena | SQL over the finding archive |
| QuickSight | Dashboard 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
| View | Content | Audience |
|---|---|---|
| Posture summary | Open findings by severity, trend over 90 days | Leadership and security |
| By standard | Compliance percentage per standard | Security and compliance |
| By account | Findings per account and OU | Platform |
| Ageing | Findings open beyond SLA, by owner | Delivery leads |
| Mean time to resolve | By severity and product | Security |
| Top controls failing | Most frequent control failures | Platform |
| Resource exposure | Public resources with findings | Security |
Operating rules
- One severity model. Security Hub severity is the source of truth; other tools map into it.
- Ownership is explicit. Each finding resolves to a team via the resource's
Ownertag or the account's owner. - SLA per severity. Critical same day, high 3 days, medium 30, low next cycle.
- Suppression is recorded. A suppressed finding carries a justification and an expiry.
- 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.