1
0

Caddyfile 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. # FrankenPHP Caddyfile for the api container.
  2. # Serves Slim from public/ on :8081.
  3. {
  4. frankenphp
  5. order php_server before file_server
  6. auto_https off
  7. admin off
  8. servers {
  9. trusted_proxies static private_ranges
  10. }
  11. }
  12. :8081 {
  13. root * /app/public
  14. encode zstd gzip
  15. # ── Security headers (M14) ──────────────────────────────────────────
  16. # Applied to every response. The api serves JSON + the OpenAPI YAML +
  17. # the /api/docs viewer; everything else is locked down.
  18. header {
  19. # Identify ourselves as little as possible.
  20. -Server
  21. -X-Powered-By
  22. X-Content-Type-Options "nosniff"
  23. # The api doesn't render its own pages except /api/docs which is a
  24. # single embedded viewer; SAMEORIGIN is the conservative default
  25. # that still allows future same-origin embedding.
  26. X-Frame-Options "SAMEORIGIN"
  27. Referrer-Policy "strict-origin-when-cross-origin"
  28. Permissions-Policy "geolocation=(), microphone=(), camera=()"
  29. }
  30. # HSTS: prod-only. Setting it in dev would lock you out of plain-HTTP
  31. # localhost on the same hostname (sticky for 1 year). Gate strictly.
  32. @prod expression `{env.APP_ENV} == "production"`
  33. header @prod Strict-Transport-Security "max-age=31536000; includeSubDomains"
  34. # CSP: docs viewer needs RapiDoc from jsDelivr + inline styles + the
  35. # try-it-now feature posting to /api/v1/*. Everything else is JSON.
  36. @docs path /api/docs /api/v1/openapi.yaml
  37. header @docs Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
  38. @not_docs not path /api/docs /api/v1/openapi.yaml
  39. header @not_docs Content-Security-Policy "default-src 'none'; frame-ancestors 'none'; base-uri 'none'"
  40. # Internal jobs API: only callable from loopback / RFC1918.
  41. # The PHP layer also enforces this (InternalNetworkMiddleware) — Caddy
  42. # is the first line of defence for production deployments where the
  43. # api is reachable from the public internet.
  44. @internal {
  45. path /internal/*
  46. remote_ip 127.0.0.1/32 ::1/128 172.16.0.0/12 10.0.0.0/8 192.168.0.0/16
  47. }
  48. handle @internal {
  49. php_server
  50. }
  51. @external_internal_blocked {
  52. path /internal/*
  53. not remote_ip 127.0.0.1/32 ::1/128 172.16.0.0/12 10.0.0.0/8 192.168.0.0/16
  54. }
  55. respond @external_internal_blocked 404
  56. php_server
  57. }