How to Generate a Hardening Bundle from the Admin Wizard
Produce a single zip that contains a Kubernetes admission webhook manifest, network egress allowlist, MDM payloads for Jamf/Intune, CI snippets, and a Prometheus bypass alert — wired to your specific deployment, not a generic example.
What the Hardening Wizard Generates
Tutorial 43 walked through the concepts of a layered enforcement: CI checks, MDM policy, and network egress. The admin hardening wizard at /admin/hardening produces the artifacts for each of those layers, pre-wired with your control-plane URL, your ingress IPs, and your egress allowlist. You generate a bundle once, download a zip of files, and apply each file to the corresponding system.
The bundle contains:
| File | What it is | Where it goes |
|---|---|---|
k8s/validating-admission-webhook.yaml | Webhook that blocks any pod whose container image was not pulled via the Chainsaw Docker proxy | Apply to any cluster running workloads |
network/egress-allowlist.txt | Bare hostnames of upstream registries and required intel hosts Chainsaw needs to reach | Paste into Zscaler / Netskope / Palo Alto / firewall as a deny-all-except |
network/zscaler-urls.txt, netskope-urls.txt, generic-deny.txt | Vendor-formatted variants of the same list | Vendor-specific deny rules |
mdm/jamf/macos.sh, mdm/intune/{linux.sh,macos.sh,windows.ps1} | Endpoint enrollment scripts that install the CLI, configure hooks, and schedule attestation | Upload as MDM script payloads |
ci/github-actions.yml, ci/gitlab-ci.yml, ci/jenkinsfile | CI snippets for chainsaw pr-scan + chainsaw doctor --strict --attest | Drop into each pipeline repo |
monitoring/prometheus-bypass-alert.yaml | PrometheusRule that fires when chainsaw_bypass_attempts_total increases | Apply to your kube-prometheus-stack |
monitoring/grafana-dashboard.json | Dashboard for the bypass / compliance metrics | Import into Grafana |
This is the “Wave 5” hardening surface. Without it, you have a working proxy. With it, you have a proxy that the rest of your security stack actively defends.
Prerequisites
- A deployed Chainsaw control plane — see How to Set Up Chainsaw.
- Admin role on the control plane (the wizard reads site-specific config to wire the bundle).
- Access to whatever you intend to apply the bundle against — a kubectl context, your MDM admin console, your firewall config, etc.
Step 1: Open the Hardening Wizard and Preview What It Will Produce
In the dashboard, navigate to /admin/hardening. The wizard shows a preview of every artifact it would generate, with the values it would substitute (your control-plane URL, your ingress IPs, your configured egress allowlist, and the required intel hosts the bundle emitter injects at generation time). Read it carefully — this is the moment to catch a misconfigured allowlist or a wrong ingress IP.
If you prefer to script it, the same preview is available from the API:
curl -H "Authorization: Bearer $CHAINSAW_TOKEN" \
"https://chain305.com/chainproxy/api/hardening/bundle?preview=1" | jq .
Step 2: Generate the Bundle
Click Generate bundle in the wizard to produce and download the zip. To do it from the API instead:
curl -X POST -H "Authorization: Bearer $CHAINSAW_TOKEN" \
"https://chain305.com/chainproxy/api/hardening/bundle" \
-o hardening-bundle.zip
unzip hardening-bundle.zip -d hardening-bundle/
If your deployment gates bundle generation behind a second approver, the wizard routes the request through the approval endpoints (/api/hardening/approval/*) before the zip is released. Approve it from the wizard, or hit the approval API directly with an admin token.
The directory now contains every file from the table above. Inspect a few:
ls -la hardening-bundle/
cat hardening-bundle/network/egress-allowlist.txt
cat hardening-bundle/k8s/validating-admission-webhook.yaml
The webhook YAML has your TLS cert pre-baked (Chainsaw mints it at generation time and rotates the cert in the same call). The egress allowlist has the upstream hosts your specific repositories actually use plus the required intel hosts, injected at generation time, not a generic list.
Step 3: Apply the Kubernetes Webhook
kubectl apply -f hardening-bundle/k8s/validating-admission-webhook.yaml
The webhook intercepts every pod creation and rejects any pod whose spec.containers[].image is not registered as having been pulled through the Chainsaw Docker proxy. The check is a presence-of-attestation lookup against the control plane.
The webhook fails open on control-plane unreachability by default — a Chainsaw outage should not stop your cluster from scheduling pods. To fail closed (stricter, but adds Chainsaw to your k8s critical path), edit the manifest’s failurePolicy: Fail.
# Test the webhook is enforcing
kubectl run test-image --image=nginx:alpine
# → admission webhook denied: image not registered with Chainsaw
kubectl run test-image --image=chain305.com/library/nginx:alpine
# → pod created
Step 4: Apply the Network Egress Allowlist
The bundle ships vendor-formatted files. Pick the one matching your SWG / firewall:
- Zscaler ZIA: paste
network/zscaler-urls.txtinto a custom URL category, then add a URL Filtering rule that blocks the category for everyone except Chainsaw’s egress identity. - Netskope: import
network/netskope-urls.txtas a URL List, attach to a real-time block policy. - Palo Alto / generic firewall: import
network/generic-deny.txtas an External Dynamic List or static address group, deny in your outbound security policy.
After applying, validate from a non-Chainsaw workstation:
curl -I https://registry.npmjs.org/ # should time out or be refused
Step 5: Push the MDM Scripts
Upload the appropriate platform script as an MDM script payload:
- Jamf → upload
mdm/jamf/macos.shas a policy script. - Intune → upload
mdm/intune/<platform>.{sh,ps1}as a script policy. - Workspace ONE / Kandji / etc. → use the custom-script payload.
Pass CHAINSAW_SERVER, CHAINSAW_CLIENT_ID, CHAINSAW_CLIENT_SECRET via the MDM’s secret store (Jamf parameters $4/$5/$6, Intune script secrets, Workspace ONE custom attributes).
The script:
- Installs the CLI, verifies the binary signature.
- Runs
chainsaw install-hook <manager> --scope systemfor every enabled manager (writes to/etc/npmrc,/etc/pip.conf,/etc/docker/daemon.json— locations a non-root user cannot override). - Schedules
chainsaw doctor --strict --atteston a daily timer.
Step 6: Drop CI Snippets Into Each Pipeline Repo
ci/github-actions.yml, ci/gitlab-ci.yml, and ci/jenkinsfile are templates — you copy them into each pipeline’s repo, adjust paths if needed, and commit. They run two checks:
chainsaw pr-scan— diffs the lockfiles and emits per-package signals; exits 10 (warn), or 20 with--strict. Exit 20 without--strictneeds a blocking-severity signal, and the offline heuristics emit none today, sosummary.blockingis always0in offline mode. Add--strictif you want the step to fail the pipeline on warnings — and note that thesc.new_depsignal fires on every newly added dependency, so--strictfails essentially every dependency-adding PR. Exit 30 means a monitored manifest failed to parse and its dependencies were dropped from the scan.chainsaw doctor --strict --attest— confirms the runner is configured against Chainsaw and posts an attestation.
For GitHub, mark chainsaw-preflight as a required status check on protected branches. From that point, any PR that commits a bypass file or whose runner has drifted blocks merge.
Step 7: Wire the Prometheus Alert
kubectl apply -f hardening-bundle/monitoring/prometheus-bypass-alert.yaml
The rule fires on increase(chainsaw_bypass_attempts_total[15m]) > 0. The metric counts every endpoint that reported direct_registry_egress: reachable after the network-mandatory layer was supposed to be in place.
Pair the alert with the bundled Grafana dashboard for the visual context — see what’s drifting, when it started, and which endpoints are involved.
Step 8: Run chainsaw doctor --bypass-check From the Control Plane
A complementary detection surface — runs from the control plane out, not from the endpoint in. See How to Detect Bypass Attempts.
Step 9: Regenerate the Bundle On Every Material Change
Add a routine: any time you change the egress allowlist, the ingress IP, or the TLS cert lifetime, regenerate a fresh bundle from the wizard and re-apply. Because the bundle emitter injects the required intel hosts and current values at generation time, the right move is always to regenerate rather than to lint or patch an old bundle by hand. Re-applying a bundle generated from the same control-plane state is a no-op.
A common cadence is “regenerate quarterly, plus on any allowlist change”. For the network layer specifically, drift between what Chainsaw thinks the allowlist is and what your firewall has is a frequent source of mysterious build failures; regenerating the bundle is the cheap way to keep them aligned.
Verification
- The wizard preview at /admin/hardening shows the artifacts that would be produced, with site-specific values substituted.
- Apply the k8s webhook → a pod with a non-Chainsaw image is rejected.
- Apply the egress allowlist → a non-Chainsaw workstation can no longer reach
registry.npmjs.org. - Push the MDM script → a managed endpoint reports a fresh attestation in Admin → Enforcement within minutes.
- Run
chainsaw doctor --strictfrom a CI runner → exits 0.
Related Topics
- How to Enforce Chainsaw Org-Wide — the layered enforcement strategy this bundle implements.
- How to Detect Bypass Attempts — the detection surface that fires when one of these layers fails.
- How to Configure Chainsaw with YAML — the bootstrap config that determines what the wizard writes into the bundle.