Skip to main content

Security

Workload hardening (always on)

Every rendered pod, without opt-in:

  • non-root, read-only root filesystem, all capabilities dropped, seccomp RuntimeDefault, no service-account token;
  • namespaces labeled for Pod Security Admission restricted;
  • a default-deny NetworkPolicy admitting only the ingress-controller namespace and same-namespace peers — selecting the application label so canary and blue/green tracks are covered too;
  • builds run rootless (BuildKit without privilege, no Docker socket), with path-traversal- and symlink-rejecting artifact extraction.

Tenant isolation

One namespace per tenant per environment (pgf-<tenant>-<env>). API tokens map to tenants and every read and write is tenant-scoped; the tenant label travels on all resources. Every state-changing API call lands in the audit table.

Security header profiles

security: strict in a project expands to a header bundle from the config bucket:

# security/strict.yaml
headers:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'; base-uri 'self'

Profiles ship for basic, recommended (the platform default), strict, internal, and pci. Header values are escaped when rendered into nginx configuration, so a header value can never inject server directives.

Edge authentication

authentication: oidc | jwt | oauth2-proxy is enforced at the ingress via platform-owned annotation sets (middleware/authentication-<type>.yaml). Two properties are guaranteed:

  • a missing middleware definition fails the upload with the exact object key to fix — never a silently unprotected app;
  • middleware annotations override project annotations, so an app cannot annotate its way around authentication.

authentication: basic is enforced inside nginx with an htpasswd secret the team controls.

Supply chain

Artifact signing (upload side). A tenant policy at security/signing/<tenant>.yaml can require uploads to carry a cosign sign-blob signature over app.zip:

required: true
publicKey: |
-----BEGIN PUBLIC KEY-----
...

Unsigned or wrongly-signed uploads are rejected with 403. The artifact digest is recorded at upload and re-verified inside the build job before anything runs.

Image signing and provenance (push side). With the platform signing secret configured (signingSecret chart value), every built image is signed with cosign and carries a SLSA provenance attestation recording the artifact digest, build commands, base images, and timestamps:

cosign verify --key cosign.pub registry/acme/frontend:b-abc123…
cosign verify-attestation --key cosign.pub --type slsaprovenance registry/acme/frontend:b-abc123…

Both steps are hard failures — a signing tenant can never receive an unsigned image. Admission controllers (Kyverno, sigstore policy-controller) can then require Pagifier's signature cluster-wide.