Container Catalog

Per-distro serverless catalog API for Hummingbird container images.

Features

  • Image Directory - Browse all container images with metadata
  • Tag Browser - View tags, digests, architectures per image
  • Specifications - Per-architecture OCI config details (env, cmd, user, labels)
  • SBOM - Per-architecture package lists from SPDX attestations
  • Vulnerabilities - CVE scanning results via Grype
  • CVE Metrics - CloudWatch metrics for CVE exposure duration (see Error Budgets)
  • Provenance - Source traceability from SLSA attestations
  • Release History - Timeline of past builds with drill-down
  • OpenAPI Spec - Machine-readable API documentation at /v1/openapi.json
  • Swagger UI - Interactive API explorer at /v1/docs/

Architecture

Rust serverless stack deployed as two isolated per-distro stacks:

  • API Lambda - DynamoDB pass-through (~10ms response)
  • Sync Lambda - Incremental DynamoDB sync from SNS Release events (~22 registry calls per release)
  • Metrics Lambda (metrics-lambda) - DynamoDB Stream-triggered CVE exposure metrics and structured logs to CloudWatch
  • DynamoDB - Pre-computed JSON items (single-table PK/SK design, Streams: KEYS_ONLY)
  • CloudFront - CDN with per-endpoint cache TTLs
  • CloudWatch - CVE exposure metrics and structured logs
  • catalog sync - Full DynamoDB population from GitLab + Quay.io OCI v2 registry
  • catalog scan - CVE scanning via Grype from DynamoDB-stored SBOMs (no registry access), with first_seen tracking
  • Catalog SPA - Lit 3 web app served from S3 via CloudFront

Prerequisites

  • Rust 1.75+ (for building backend)
  • Node.js 22+ (for building frontend)
  • AWS credentials (for DynamoDB access and deployment)
  • SAM CLI (for deployment)

API Endpoints

Endpoint Description
GET /v1/images Image directory
GET /v1/stats Catalog statistics
GET /v1/images/{name} Image overview (README)
GET /v1/images/{name}/tags Tags for an image
GET /v1/images/{name}/specifications/{canonical} Per-arch OCI config
GET /v1/images/{name}/sbom/{canonical} Package list
GET /v1/images/{name}/provenance/{canonical} Source provenance
GET /v1/images/{name}/history/{stream}/{variant} Release timeline
GET /v1/images/{name}/history/{digest}/specifications Historical per-arch specs
GET /v1/images/{name}/history/{digest}/sbom Historical SBOM
GET /v1/images/{name}/vulnerabilities/{canonical} Vulnerability scan
GET /v1/images/{name}/releases/vulnerabilities/{digest} Vulnerability scan (hash)
GET /v1/openapi.json OpenAPI 3.1 specification
GET /v1/docs/ Interactive Swagger UI

Timestamp Fields

The oldest_created field on ImageSummary, Tag, and HistorySummary is the earliest OCI created timestamp across all architectures in the release. All architectures were built at or after this date, making it useful for conservative staleness detection.

The specifications endpoint returns per-architecture data keyed by architecture name. Each architecture’s created field is the direct OCI config root timestamp for that specific architecture.

Usage

All tools are built as a single catalog binary with subcommands (api, sync, sync-lambda, scan, metrics, metrics-lambda). The binary is built in the Rust container and CLI subcommands are run in the gitlab-ci container (which provides grype and other tools). Only make and podman are required.

Sync

# Dry run (print items to stdout)
make container-catalog/sync ARGS="--distro rawhide --dry-run"

# Populate DynamoDB
make container-catalog/sync ARGS="--distro rawhide --table-name <table>"

Sync Lambda

The sync-lambda subcommand runs as an AWS Lambda function triggered by SNS Release events from kubernetes-event-forwarder. It incrementally syncs a single image release to DynamoDB (~22 registry API calls per release vs ~104 for a full sync).

The Lambda:

  1. Decodes gzip+base64 SNS messages
  2. Filters for Succeeded releases targeting the configured Quay.io namespace
  3. Fetches OCI manifest data for the new digest
  4. Writes per-digest items (DETAILS, SBOM, RELEASE_DETAILS, RELEASE_SBOM)
  5. Merges into aggregate items (TAGS, HISTORY, OVERVIEW, DIRECTORY)
  6. Fetches README from GitLab for OVERVIEW content (uses README.redhat.md for hummingbird, README.md for rawhide)

Registry fetch errors (manifest, SBOM, attestation) propagate as hard failures so the Lambda retries automatically (up to 2 retries with backoff) before sending to the DLQ. GitLab README failures are non-fatal – the existing README is preserved if the fetch fails.

Environment Variable Description
TABLE_NAME DynamoDB table name
DISTRO rawhide or hummingbird
SENTRY_DSN Optional Sentry DSN for error tracking

Scan

The scan subcommand reads image listings, tags, and SBOMs from DynamoDB (no registry access needed) and runs Grype against each image’s stored SBOM packages. Results include a first_seen timestamp per CVE, tracked at the group+variant+stream level and carried across releases for SLI computation.

# Dry run (print items to stdout)
make container-catalog/scan ARGS="--distro hummingbird --table-name <table> --dry-run"

# Scan and write to DynamoDB (purge stale vuln data first, implies --scope=all)
make container-catalog/scan ARGS="--distro hummingbird --table-name <table> --purge"

# Scan only non-superseded (current) tags — used by CronJob
make container-catalog/scan ARGS="--distro hummingbird --table-name <table> --scope non-superseded"

# Scan all releases including historic (tagless) releases
make container-catalog/scan ARGS="--distro hummingbird --table-name <table> --scope all"

# Scan a single image
make container-catalog/scan ARGS="--distro hummingbird --table-name <table> --image caddy --dry-run"

Kubernetes CronJob

An hourly CronJob runs catalog scan --scope non-superseded to keep vulnerability data up to date for all current image tags. The CronJob infrastructure is defined in rprm-infrastructure under kubernetes/container-catalog-scan-service/.

Metrics

The metrics subcommand performs a one-shot read of all non-superseded vulnerability data from DynamoDB and outputs structured CVE exposure logs. With --dry-run, it skips the CloudWatch push (useful for local inspection).

# Dry run (print structured logs to stdout, no CloudWatch push)
make container-catalog/metrics ARGS="--distro hummingbird --table-name <table> --dry-run"

# Push metrics to CloudWatch and print structured logs
make container-catalog/metrics ARGS="--distro hummingbird --table-name <table>"

Metrics Lambda

The metrics-lambda subcommand runs as a DynamoDB Stream-triggered Lambda that emits CVE exposure duration metrics to CloudWatch. These metrics feed the SLO dashboard and alarm defined in the error-budgets stack.

How It Works

The Lambda is triggered by DynamoDB Stream events filtered on VULNERABILITIES# and TAGS changes, with a 60-second batching window and reserved concurrency of 1 (single instance). It maintains an in-memory active CVE table across warm invocations:

  • Cold start: Reads CATALOG/DIRECTORY, all TAGS, and all non-superseded VULNERABILITIES# items from DynamoDB to build the full table (~2-3s at 10000 tags)
  • Warm invocations: Incrementally updates the table from stream event keys (~50-100 GetItem calls per batch)
  • After each invocation: Recomputes and emits all metrics and structured logs

CloudWatch Metrics

Metric Type Dimensions
CveExposureDuration Distribution [Distro, Fixable], [Distro, Sev, Fix]
ActiveCveCount Count [Distro, Severity, Fixable]

CveExposureDuration values are in hours, computed as now - first_seen for each active CVE on each non-superseded canonical tag. The Fixable dimension is true when a fix version is known upstream, false otherwise.

Structured Logs

Each invocation emits one JSON log line per active CVE to stdout (captured by CloudWatch Logs). Each line includes fixable (boolean) and fixed_in (version string or null). To query fixable CVEs only:

filter message = "active_cve" and fixable = true
| fields cve, severity, exposure_hours, fixed_in, repository, stream, variant, component
| sort exposure_hours desc
Environment Variable Description
TABLE_NAME DynamoDB table name
DISTRO rawhide or hummingbird
CLOUDWATCH_NAMESPACE CloudWatch namespace for metrics
SENTRY_DSN Optional Sentry DSN

Deployment

make container-catalog/build
make container-catalog/deploy

Configuration

catalog sync

Argument Description
--distro rawhide or hummingbird
--table-name DynamoDB table name
--purge Delete all items before writing
--cache-dir Cache directory (auto-detected)
--image Sync only a specific repo
--legacy-discovery Use GitLab-based repo discovery

catalog scan

Argument Description
--distro rawhide or hummingbird
--table-name DynamoDB table name (required)
--scope non-superseded, tags (default), or all
--dry-run Print items without writing
--purge Purge vuln data before writing (implies --scope all)
--cache-dir Cache directory (auto-detected)
--parallel Number of concurrent scans (default: 4)
--image Scan only a specific image
--tag Scan only a specific tag (requires --image)
--upstream-cpe Map RPMs to upstream CPEs via CPE dictionary (default)

SAM Parameters

Parameter Description
Distro rawhide or hummingbird
CacheEnabled Enable CloudFront caching
CatalogDomainName Catalog web UI domain
ApiDomainName API domain
HostedZoneId Route53 hosted zone
CorsOrigin Override CORS origin (* to disable)
SnsTopicArn SNS topic ARN for Release events (enables sync Lambda)

Frontend

The catalog web UI is a Lit 3 SPA (Web Components) with Tailwind CSS, built per-distro with Vite. Source is in container-catalog/frontend/.

Only make and podman are required (no local Node.js needed). Defaults from .envrc.defaults are applied automatically.

# Install dependencies
make container-catalog/frontend/setup

# Development server at http://localhost:5173
make container-catalog/frontend/dev

# Production build
make container-catalog/frontend/build

Host variants (*-host) run without podman (for CI or local Node.js).

Frontend Build Variables

Variable Description
VITE_API_URL API base URL for the distro
VITE_DISTRO rawhide or hummingbird
VITE_DISTRO_LABEL Display label for current distro
VITE_OTHER_CATALOG_URL URL of the other distro’s catalog (optional, hides link if unset)
VITE_OTHER_DISTRO_LABEL Display label for other distro (optional)
VITE_VULNERABILITIES_ENABLED Show vulnerabilities tab

Development

# Backend
cargo test                    # Run tests
cargo clippy --all-targets   # Lint
cargo fmt                     # Format

# Frontend (host variants, requires local Node.js)
cd container-catalog/frontend
npm run typecheck             # Type check
npm run build                 # Production build

License

Apache-2.0