Robert Szomolanyi
08/09/2025, 7:51 PMMarvin
08/09/2025, 7:51 PMMarvin
08/09/2025, 7:52 PMMarvin
08/09/2025, 7:54 PMPREFECT_SERVER_API_AUTH_STRING
(maps to server.api.auth_string
) so the API expects Authorization: Basic ...
from your workers/CI.
- Docs: Server security settings
- Minimal env example for the API pod:
env:
- name: PREFECT_SERVER_API_AUTH_STRING
valueFrom:
secretKeyRef:
name: prefect-api-auth
key: auth_string
2) RBAC (since OSS has no in-app RBAC)
- Enforce access via OpenShift RBAC and routes:
- Who can reach the UI/API Route (or even see it) is controlled by the auth proxy and OpenShift RBAC.
- Who can run code is controlled by namespaces, service accounts, and role bindings for workers.
- Separate teams into namespaces, with their own work pools, service accounts, and secrets.
- Consider soft multi-tenancy via multiple Prefect Server instances (one per team/env) if you need stronger isolation. OSS does not have the “workspace” isolation model Cloud does.
3) Auditing
- You won’t get application-level audit trails (e.g., “Alice promoted role X” or “Bob viewed deployment Y”) from OSS. Use platform-layer auditing:
- Reverse proxy logs: record user identity (OIDC email/subject) on every request to the UI/API. Ship to your SIEM.
- OpenShift/Kubernetes audit logs: capture who scaled, deployed, or accessed services.
- Database auditing: run Prefect on PostgreSQL with pgaudit (or equivalent) to track DDL/DML against Prefect’s tables (flows, deployments, flow runs, etc.).
- Prefect/server logs: centralize and retain server logs and flow-run logs (e.g., to OpenShift logging/EFK/Splunk), with immutable retention policies.
Hardening checklist (OSS)
- Enforce TLS end-to-end; terminate at the Route or proxy.
- Lock API access to private networks; only the SSO-protected UI is public.
- Set PREFECT_SERVER_API_AUTH_STRING
for basic auth and ensure workers/CI inject the header.
- Run the API/UI pods with dedicated service accounts, minimal permissions, and NetworkPolicies.
- Store credentials in OpenShift Secrets; optionally integrate an external secret manager.
- CI-only writes: restrict who can create/modify deployments by funneling prefect deploy
through a controlled CI pipeline and git PRs.Marvin
08/09/2025, 7:54 PMMarvin
08/09/2025, 7:54 PMMarvin
08/09/2025, 7:54 PMRobert Szomolanyi
08/09/2025, 8:20 PMMarvin
08/09/2025, 8:21 PMMarvin
08/09/2025, 8:24 PMPREFECT_SERVER_API_AUTH_STRING="gateway-user:super-secret"
- Docs: Server security settings
- Example external call (client → gateway):
POST <https://flows.prod.example.com/api/deployments/<deployment_id>/create_flow_run>
Authorization: Bearer <azure_ad_access_token>
Content-Type: application/json
{
"parameters": {"customer_id": 123},
"tags": ["external"],
"idempotency_key": "client-req-abc-123"
}
- The gateway then forwards internally:
POST <http://prefect-api.svc.cluster.local:4200/api/deployments/<deployment_id>/create_flow_run>
Authorization: Basic <base64(gateway-user:super-secret)>
Content-Type: application/json
{ ...same body... }
- Hardening tips:
- Enforce per-client rate limits and request size limits.
- Validate path strictly; deny-list every other Prefect endpoint.
- Optionally add an “allowlisted parameters” policy per deployment if you need more control.
3) Workers outside OpenShift (on-site machines)
- Give workers a separate hostname (or keep the worker API private/VPN):
- <http://api.prod.example.com|api.prod.example.com>
→ points to the Prefect API with Basic Auth and tight IP allowlists and/or mTLS.
- Configure the server and workers with the same Basic Auth pair (use a different user from the gateway one):
- Server:
PREFECT_SERVER_API_AUTH_STRING="worker-user:another-secret"
- Worker:
export PREFECT_API_URL=<https://api.prod.example.com/api>
export PREFECT_SERVER_API_AUTH_STRING=worker-user:another-secret
prefect worker start -p <your-work-pool>
- Verify the CLI command exists (it does): prefect worker start -p ...
— we checked.
4) RBAC model with Microsoft AD (practical, since OSS has no in-app RBAC)Marvin
08/09/2025, 8:24 PMRobert Szomolanyi
08/09/2025, 8:41 PMMarvin
08/09/2025, 8:41 PMMarvin
08/09/2025, 8:42 PMapiVersion: v1
kind: Secret
metadata:
name: prefect-api-auth
type: Opaque
stringData:
# format is "username:password"
auth_string: "prefect_api:REDACTED_SUPER_SECRET"
Example Helm values for Prefect Server that:
- keeps Services internal (ClusterIP),
- sets the Basic Auth env var for the API container,
- assumes you’ll expose via OpenShift Routes (below).
Note: charts evolve; if the chart uses a different key than extraEnv
for the API container, set the env var by the chart’s documented mechanism or post-render patch.
# values-prefect.yaml
# This is illustrative; adjust to your chart’s keys for image, resources, etc.
server:
# Disable any chart-provided Ingress; we’ll use OpenShift Routes
ingress:
enabled: false
# Add PREFECT_SERVER_API_AUTH_STRING to the API container
extraEnv:
- name: PREFECT_SERVER_API_AUTH_STRING
valueFrom:
secretKeyRef:
name: prefect-api-auth
key: auth_string
# If the chart splits UI/API, ensure both Services are ClusterIP
service:
type: ClusterIP
# If the chart lets you set separate service names/ports, ensure:
# - API service is addressable by krakend and workers
# - UI service is addressable by oauth2-proxy
Install:
helm upgrade --install prefect-server prefect/server \
-n prefect-prod --create-namespace \
-f values-prefect.yaml
2) UI SSO with oauth2-proxy (ADFS OIDC)
You’ll register an OIDC client in ADFS and configure claim issuance (see “ADFS setup”, below).
Values for oauth2-proxy (using the official oauth2-proxy chart or Bitnami’s; keys might differ slightly):
# values-oauth2-proxy.yaml
config:
clientID: "adfs-oidc-client-id-for-ui"
clientSecret: "REDACTED_UI_CLIENT_SECRET"
cookieSecret: "REDACTED_32B_BASE64" # 32B base64
# Use provider=oidc with the ADFS issuer
extraArgs:
provider: "oidc"
oidc-issuer-url: "<https://adfs.example.com/adfs>"
# Optionally pin endpoints if needed:
# login-url: "<https://adfs.example.com/adfs/oauth2/authorize>"
# redeem-url: "<https://adfs.example.com/adfs/oauth2/token>"
scope: "openid email profile"
email-domain: "*"
pass-authorization-header: "true"
set-authorization-header: "true"
set-xauthrequest: "true"
# Only allow certain AD groups to see the UI (requires emitting groups/roles)
allowed-group: "prefect-admins"
allowed-group: "prefect-users"
ingress:
enabled: false # We’ll create an OpenShift Route
service:
type: ClusterIP
# Upstream to Prefect UI service inside the cluster
# Many charts expose this as reverseProxy/upstreams or via config.file — if needed, set:
# extraArgs:
# upstream: "<http://prefect-server-ui.prefect-prod.svc.cluster.local:4200>"
OpenShift Route for the UI:
```
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: prefect-ui
namespace: prefect-prod
spec:Marvin
08/09/2025, 8:42 PM3) KrakenD public “flow trigger” API (ADFS JWT + allowlisted deployments)
Goal: clients call KrakenD with ADFS JWTs; KrakenD:
- validates JWT (issuer and JWKS from ADFS),
- optionally checks group/role membership,
- enforces authorization: the path’s deployment_id must be in the client’s allowed list,
- forwards to Prefect API with a Basic Auth header.
Approach:
- Use krakend-jose for JWT validation (jwk_url, issuer, audience).
- Use krakend-lua to enforce “deployment_id in token claim” (e.g., claim `prefect_deployments`).
- Set an upstream Authorization header to Basic <prefect_api:REDACTED>.
Config hints:
- ADFS OpenID well-known: <https://adfs.example.com/adfs/.well-known/openid-configuration>
- ADFS issuer: <https://adfs.example.com/adfs>
- JWKS: (from the well-known JSON) typically <https://adfs.example.com/adfs/discovery/keys>
You’ll need a KrakenD image with the lua and jose plugins available. If your default image lacks them, build one:
# Dockerfile (example)
FROM devopsfaith/krakend:2.6
# The official image usually includes jose; add lua if missing:
# RUN krakend plugin register github.com/devopsfaith/krakend-lua/proxy@latest
# Or copy compiled plugins into /usr/lib/krakend/
KrakenD Helm values example (mount krakend.json from a ConfigMap):
# values-krakend.yaml
replicaCount: 2
service:
type: ClusterIP
port: 8080
config:
configMapName: krakend-config
# If your chart supports autoscaling/affinity/resources, set them as needed
KrakenD ConfigMap (JWT validation + Lua authorization + upstream Basic Auth):
apiVersion: v1
kind: ConfigMap
metadata:
name: krakend-config
namespace: prefect-prod
data:
krakend.json: |
{
"version": 3,
"name": "prefect-trigger-gateway",
"port": 8080,
"extra_config": {
"github_com/devopsfaith/krakend-cors": {
"allow_origins": ["https://your-client.example.com"],
"allow_methods": ["POST","OPTIONS"],
"allow_headers": ["Authorization","Content-Type"],
"expose_headers": [],
"max_age": "24h",
"allow_credentials": false
}
},
"endpoints": [
{
"endpoint": "/api/deployments/{deployment_id}/create_flow_run",
"method": "POST",
"extra_config": {
"github_com/devopsfaith/krakend-jose/validator": {
"alg": "RS256",
"jwk-url": "https://adfs.example.com/adfs/discovery/keys",
"issuer": "https://adfs.example.com/adfs",
"audience": ["krakend-public-flow-trigger"],
"roles_key": "roles", // if you emit roles/groups
"roles": ["prefect-external"], // optional: require this role
"disable_jwk_security": false
},
"github_com/devopsfaith/krakend-lua/proxy-request": {
"pre": "local dep = string.match(krakend.request.path, '/api/deployments/([^/]+)/create_flow_run'); local cl = krakend.request.headers['X-JWT-Claim-Prefect_Deployments']; if cl == nil then return krakend.response.exit(403) end; local allowed = {}; for s in string.gmatch(cl, '([^,]+)') do allowed[s]=true end; if not allowed[dep] then return krakend.response.exit(403) end; return true;"
// Explanation:
// ADFS needs to emit a custom claim 'prefect_deployments' (array).
// KrakenD maps token claims into headers as X-JWT-Claim-<ClaimName>.
// Some deployments encode arrays as CSV headers; adjust parsing if needed.
},
"github_com/devopsfaith/krakend/header": {
"name": "Authorization",
"value": "Basic cHJlZmVjdF9hcGk6UkVEQUNURURfU1VQRVJfU0VDUkVU" // base64 of prefect_api:REDACTED_SUPER_SECRET
}
},
"backend": [
{Marvin
08/09/2025, 8:42 PMOpenShift Route for KrakenD (public):
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: prefect-flow-trigger
namespace: prefect-prod
annotations:
haproxy.router.openshift.io/rate-limit-connections: "50"
haproxy.router.openshift.io/ip_whitelist: "0.0.0.0/0" # tighten as desired
spec:
host: flows.prod.example.com
to:
kind: Service
name: krakend
tls:
termination: edge
insecureEdgeTerminationPolicy: Redirect
4) Worker API (off‑cluster workers)
Expose a separate Route directly to the Prefect API service and rely on Basic Auth (the same secret as above). Lock it down by IP/CIDR since no mTLS:
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: prefect-api-workers
namespace: prefect-prod
annotations:
haproxy.router.openshift.io/ip_whitelist: "203.0.113.0/24,198.51.100.10/32" # your sites
spec:
host: api.prod.example.com
to:
kind: Service
name: prefect-server-api
tls:
termination: edge
insecureEdgeTerminationPolicy: Redirect
Worker hosts (on-site machines) configure:
export PREFECT_API_URL=https://api.prod.example.com/api
export PREFECT_SERVER_API_AUTH_STRING=prefect_api:REDACTED_SUPER_SECRET
prefect worker start -p your-work-pool
5) NetworkPolicies (defense in depth)
Only allow Prefect API service to be reached by:
- KrakenD pods
- oauth2-proxy pods (if they need API)
- cluster-internal agents/workers (if any)
Example:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-gateway-and-proxy
namespace: prefect-prod
spec:
podSelector:
matchLabels:
app: prefect-server-api
policyTypes: ["Ingress"]
ingress:
- from:
- podSelector:
matchLabels:
app: krakend
- podSelector:
matchLabels:
app: prefect-ui-proxy
ports:
- protocol: TCP
port: 4200
```
6) ADFS setup (OIDC) essentials
- Create two App Registrations (Relying Parties):
- UI app (for oauth2-proxy), redirect URI: https://ui.prod.example.com/oauth2/callback
- KrakenD app (for API), audience must match aud
in KrakenD jose config (e.g., “krakend-public-flow-trigger”)
- Issuance transform rules:
- Emit email
or upn
claim (oauth2-proxy likes one of these)
- Emit roles
or groups
for UI SSO group gating (e.g., “prefect-admins”, “prefect-users”)
- Emit a custom claim prefect_deployments
(array/string list) for each client of the KrakenD app, listing the deployment IDs they may trigger
- Alternatively, emit per-client group/role names and maintain a mapping in the KrakenD Lua script instead of pushing the list in the token
- Well-known configuration should be available at:
- https://adfs.example.com/adfs/.well-known/openid-configuration
- JWKS: the jwks_uri
URL in that document
7) Auditing (since OSS has no first-class audit logs)
- KrakenD: enable access logs with user identity (subject, client_id, roles); forward to SIEM
- Prefect Server: centralize and retain logs; include X-Request-ID headers propagated by KrakenD
- PostgreSQL: enable pgaudit to record DDL/DML in Prefect DB (deployments, flow runs, states)
- OpenShift: enable audit logs for changes to Routes/Secrets/Deployments; keep retention per your policy
8) Why the gateway works for per-client control
- Prefect OSS has no RBAC. KrakenD enforces that a client can only call POST /api/deployments/{deployment_id}/create_flow_run and only for deployment IDs listed in their token (or mapped in Lua). Everything else is denied.Marvin
08/09/2025, 8:42 PMprefect_deployments
), or do you want KrakenD to maintain a static map of client_id → allowed deployments?
- Do you also want to rate-limit per client? KrakenD can apply per-endpoint quotas.
- Any additional Routes (e.g., read-only metadata) needed for your clients beyond trigger?
If you confirm the ADFS claim shape you’ll emit, I can adapt the KrakenD config (Lua snippet and header mapping) precisely. I’ll try to contain my excitement, but, well, you know me.