# gemma-deployment-security — Design Spec

**Date**: 2026-04-15
**Status**: Draft
**Author**: Lui Pillmann + Claude

## Problem

Gemma Analytics deploys Airflow and data tools for ~25 clients on self-hosted Docker Compose (AWS EC2, Hetzner). An internal security research report (March 2026) identified 38 prioritized security controls across 6 domains. Currently, there is no structured way to audit whether a client deployment meets these standards. Audits happen ad-hoc, inconsistently, and rely entirely on individual engineer knowledge.

Real examples of what this catches:
- Secrets baked into Dockerfile image layers via `ARG → ENV` (found in `gemma-airflow/Dockerfile`)
- Docker Compose ports bound to `0.0.0.0` instead of `127.0.0.1` (found in `airflow3-demo/docker-compose.yaml`)
- `NOPASSWD:ALL` in cloud-init user data (found in `saxonia/opentofu/main.tf`)
- Hardcoded database credentials in compose files
- GitHub Actions pinned by tag instead of SHA digest

## Solution

A Claude Code plugin — `gemma-deployment-security` — that provides 7 focused audit skills. An engineer clones a client repo, invokes the skills (individually or via a report generator), and gets a prioritized Markdown report with findings and remediation snippets.

## Design Decisions

| Decision | Choice | Rationale |
|---|---|---|
| Access model | Hybrid: repo always, SSH optional | Core skills work on files. Server audit degrades gracefully to guided checklist without SSH. |
| Scope | Layered: infrastructure + Airflow | Most checks are infra-level (Docker, CI/CD, SSH). Airflow-specific checks are a separate layer. Both run together. |
| Deployment targets | Self-hosted Docker Compose only | Matches the security report's scope. Managed Airflow (MWAA/Composer) is a different concern — add later. |
| Trigger model | Ad-hoc by Gemma engineer | Primary use. Interface kept clean enough for future scheduled runs. |
| Output | Markdown report file | Written to `security-audit-report.md`. No external tool dependencies. |
| Skill structure | One skill per domain | Independently invocable. Report generator orchestrates all of them. |
| Plugin scope | Single plugin, broad name | `gemma-deployment-security` — infra skills work for any deployment, Airflow skills are one layer inside. |
| Skill internals | Reference-checklist driven | Each skill has `references/checklist.md` with specific checks. SKILL.md provides instructions. Checklists updatable independently. |
| Severity model | All 4 levels reported | Critical, Important, Recommended, Nice-to-have. No filtering — engineer sees everything. |
| Remediation | Read-only, no auto-fix | Skills report findings with before/after snippets. Engineer decides what to fix and when. |

## Skill Inventory

### Layer 1 — Infrastructure Audit (generic, works for any Docker Compose deployment)

#### Skill 1: `audit-docker-compose`
- **Input**: Path to `docker-compose.yml` (or auto-detected from repo root)
- **Checks** (from security report §05):
  - Port bindings: `127.0.0.1` vs `0.0.0.0` (CRITICAL)
  - Internal networks for databases: `internal: true` (CRITICAL)
  - Non-root user: `user:` directive present (IMPORTANT)
  - Capability management: `cap_drop: [ALL]` + minimal `cap_add` (IMPORTANT)
  - Secrets vs env vars: `secrets:` block vs `environment:` for credentials (IMPORTANT)
  - Image pinning: SHA digest vs tag (RECOMMENDED)
  - Resource limits: `mem_limit`, `cpus`, `pids` (RECOMMENDED)
  - Log rotation: `logging.options.max-size/max-file` (RECOMMENDED)
  - Read-only filesystem: `read_only: true` + tmpfs (RECOMMENDED)
  - Security options: `no-new-privileges:true` (RECOMMENDED)
  - Health checks: `healthcheck:` present (RECOMMENDED)
  - Docker socket mounts: `/var/run/docker.sock` exposure (IMPORTANT)

#### Skill 2: `audit-dockerfile`
- **Input**: Path to Dockerfile(s) (or auto-detected)
- **Checks**:
  - Secrets in ARG/ENV: fernet keys, passwords, connection strings, API keys baked into layers (CRITICAL)
  - Hardcoded credentials: grep for patterns like AWS keys, passwords in plaintext (CRITICAL)
  - Base image pinning: SHA256 digest vs mutable tag (IMPORTANT)
  - USER directive: runs as non-root (IMPORTANT)
  - Multi-stage secret leakage: secrets in early stages leaking to final stage (IMPORTANT)
  - .dockerignore: exists and excludes `.env`, `*.key`, `*.pem`, etc. (IMPORTANT)
  - Unnecessary packages: `apt-get install` without `--no-install-recommends` (RECOMMENDED)
  - pip usage: direct `pip install` vs managed (`uv`, pinned requirements) (RECOMMENDED)
  - COPY of sensitive files: `.env`, credentials files copied into image (CRITICAL)

#### Skill 3: `audit-cicd-workflows`
- **Input**: Path to `.github/workflows/` directory
- **Checks** (from security report §07):
  - Action SHA pinning: `@sha256:...` vs `@v4` tags (IMPORTANT)
  - Environment protection rules: required reviewers, deployment branch restrictions (IMPORTANT)
  - Secrets in workflow files: hardcoded values, secrets in `env:` blocks (CRITICAL)
  - Deploy user restrictions: `ForceCommand`, minimal permissions (IMPORTANT)
  - Dependabot for action updates: configured for `github-actions` (RECOMMENDED)
  - Build provenance attestation: `actions/attest-build-provenance` (NICE)
  - GHCR for private images: not using public registries for internal images (RECOMMENDED)
  - `CODEOWNERS` file: exists for sensitive paths (RECOMMENDED)

#### Skill 4: `audit-server-config`
- **Input**: SSH host (optional) or guided-checklist mode
- **When SSH available** — reads and checks:
  - UFW rules: default-deny incoming, only 80/443/SSH allowed (CRITICAL)
  - sshd_config: `PasswordAuthentication no`, `PermitRootLogin no`, `PubkeyAuthentication yes`, `AllowGroups` (CRITICAL)
  - fail2ban: installed and configured for SSH + web services (RECOMMENDED)
  - unattended-upgrades: active, security patches auto-applied (IMPORTANT)
  - sudo scope: no `NOPASSWD: ALL`, scoped to specific commands (IMPORTANT)
  - OS users: no shared accounts, deploy user exists with restricted permissions (IMPORTANT)
- **When no SSH** — presents a guided checklist for manual verification:
  - Same items, formatted as a yes/no questionnaire
  - Engineer fills in responses, findings generated based on answers

#### Skill 5: `audit-infrastructure-as-code`
- **Input**: Path to infrastructure directory (e.g., `server/`)
- **OpenTofu checks**:
  - State backend: encryption enabled, locking configured (IMPORTANT)
  - Firewall rules: SSH restricted to specific CIDRs (not `0.0.0.0/0`), only 80/443 public (CRITICAL)
  - Cloud-init: no `NOPASSWD:ALL`, appropriate user groups (IMPORTANT)
  - OS image: pinned to specific version (RECOMMENDED)
  - Private network: used for inter-service communication (Hetzner-specific: firewall doesn't cover private nets) (RECOMMENDED)
  - Server backups: enabled (IMPORTANT)
  - Provider version constraints: pinned with `~>` or exact (RECOMMENDED)
- **Ansible checks**:
  - Secret handling: fetched from 1Password/vault at deploy time, not hardcoded (CRITICAL)
  - `no_log: true` on tasks that handle secrets (IMPORTANT)
  - File permissions: `.env` files at `0600`, configs at `0644` (IMPORTANT)
  - Deploy keys: dedicated deploy key, not personal SSH keys (IMPORTANT)
  - Docker build: uses `--target` to avoid secret leakage in image layers (IMPORTANT)
  - Health checks: post-deploy verification of service health (RECOMMENDED)
- **Note**: This is a starter skill. Checklist will expand over time as more IaC patterns emerge across clients.

### Layer 2 — Airflow-Specific

#### Skill 6: `audit-airflow-config`
- **Input**: Airflow repo root
- **Checks**:
  - Fernet key: not hardcoded in Dockerfile, compose, or code — sourced from env/secrets (CRITICAL)
  - Webserver secret key: same as above (CRITICAL)
  - Webserver auth: authentication enabled, not using default credentials (CRITICAL)
  - Default admin password: not `airflow`/`admin` or similar defaults (CRITICAL)
  - Executor security: DockerOperator Docker socket exposure flagged, Celery broker auth (IMPORTANT)
  - DAG file permissions: not world-writable, owned by airflow user (IMPORTANT)
  - Connections/Variables: secrets stored in backend (env, secrets manager), not Airflow metadata DB (IMPORTANT)
  - Remote logging: configured (S3/GCS), not just local filesystem (RECOMMENDED)
  - Webserver `expose_config`: disabled (IMPORTANT)
  - API authentication: enabled, not anonymous (IMPORTANT)

### Orchestrator

#### Skill 7: `generate-security-report`
- **Input**: Repo path (via `$ARGUMENTS` or interactive prompt)
- **Behavior**:
  1. Auto-detect which audits apply by scanning for: `docker-compose.yml`, `Dockerfile`, `.github/workflows/`, `*.tf` files, Ansible roles, Airflow configs
  2. Run applicable audit skills in sequence
  3. Merge all findings into a single report using the report template
  4. Write to `security-audit-report-YYYY-MM-DD.md` in the current working directory (or user-specified path via `$ARGUMENTS`)
- **Report structure**:
  - Header: client name, date, auditor
  - Executive summary: finding counts by severity, 1-2 sentence assessment
  - Findings by domain (Critical first, then Important, Recommended, Notes)
  - Each finding: ID, domain, location, issue, risk, remediation with before/after
  - Checks Passed section (what IS configured correctly)
  - Audit Scope section (what was and wasn't checked, any access limitations)

## Plugin Structure

```
plugins/gemma-deployment-security/
├── .claude-plugin/
│   └── plugin.json
├── skills/
│   ├── audit-docker-compose/
│   │   ├── SKILL.md
│   │   └── references/
│   │       └── checklist.md
│   ├── audit-dockerfile/
│   │   ├── SKILL.md
│   │   └── references/
│   │       └── checklist.md
│   ├── audit-cicd-workflows/
│   │   ├── SKILL.md
│   │   └── references/
│   │       └── checklist.md
│   ├── audit-server-config/
│   │   ├── SKILL.md
│   │   └── references/
│   │       ├── checklist.md
│   │       └── guided-checklist.md
│   ├── audit-infrastructure-as-code/
│   │   ├── SKILL.md
│   │   └── references/
│   │       └── checklist.md
│   ├── audit-airflow-config/
│   │   ├── SKILL.md
│   │   └── references/
│   │       └── checklist.md
│   └── generate-security-report/
│       ├── SKILL.md
│       └── assets/
│           └── report-template.md
├── CHANGELOG.md
```

## Conventions

- All skills have `disable-model-invocation: true` (side-effecting: they write reports)
- SKILL.md references checklists via `${CLAUDE_SKILL_DIR}/references/checklist.md`
- Checklists extracted from the security research report at `research/2026-03-17-server-security-data-tools/`
- Follows all `gemma-agentic-toolkit/CLAUDE.md` conventions (kebab-case, YAML frontmatter, <500 lines per SKILL.md)
- Plugin registered in `release-please-config.json` and `.release-please-manifest.json`

## Source Material

All checklists derive from:
- **Primary**: `research/2026-03-17-server-security-data-tools/report/index.html` — 38 prioritized controls across 6 domains
- **Dockerfile**: Real findings from `gemma-airflow/Dockerfile` (secrets in layers) and `airflow3-demo` Dockerfile (clean reference)
- **IaC**: Real patterns from `saxonia-data-infrastructure/server/` (OpenTofu + Ansible reference deployment)

## What Is NOT In Scope

- Automated remediation (read-only audit)
- Managed Airflow (MWAA/Cloud Composer)
- Container image scanning (Trivy — future work)
- Continuous monitoring (one-shot audit)
- Reverse proxy audit (Caddy/Nginx — could be added later)
- Non-Docker deployments (Kubernetes, ECS)
