How to Generate a Hardening Bundle from the Admin Wizard

Advanced 30 minutes Platform Engineers / Security Engineers Advanced Configuration

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:

FileWhat it isWhere it goes
k8s/validating-admission-webhook.yamlWebhook that blocks any pod whose container image was not pulled via the Chainsaw Docker proxyApply to any cluster running workloads
network/egress-allowlist.txtBare hostnames of upstream registries and required intel hosts Chainsaw needs to reachPaste into Zscaler / Netskope / Palo Alto / firewall as a deny-all-except
network/zscaler-urls.txt, netskope-urls.txt, generic-deny.txtVendor-formatted variants of the same listVendor-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 attestationUpload as MDM script payloads
ci/github-actions.yml, ci/gitlab-ci.yml, ci/jenkinsfileCI snippets for chainsaw pr-scan + chainsaw doctor --strict --attestDrop into each pipeline repo
monitoring/prometheus-bypass-alert.yamlPrometheusRule that fires when chainsaw_bypass_attempts_total increasesApply to your kube-prometheus-stack
monitoring/grafana-dashboard.jsonDashboard for the bypass / compliance metricsImport 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.txt into 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.txt as a URL List, attach to a real-time block policy.
  • Palo Alto / generic firewall: import network/generic-deny.txt as 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.sh as 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:

  1. Installs the CLI, verifies the binary signature.
  2. Runs chainsaw install-hook <manager> --scope system for every enabled manager (writes to /etc/npmrc, /etc/pip.conf, /etc/docker/daemon.json — locations a non-root user cannot override).
  3. Schedules chainsaw doctor --strict --attest on 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 --strict needs a blocking-severity signal, and the offline heuristics emit none today, so summary.blocking is always 0 in offline mode. Add --strict if you want the step to fail the pipeline on warnings — and note that the sc.new_dep signal fires on every newly added dependency, so --strict fails 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

  1. The wizard preview at /admin/hardening shows the artifacts that would be produced, with site-specific values substituted.
  2. Apply the k8s webhook → a pod with a non-Chainsaw image is rejected.
  3. Apply the egress allowlist → a non-Chainsaw workstation can no longer reach registry.npmjs.org.
  4. Push the MDM script → a managed endpoint reports a fresh attestation in Admin → Enforcement within minutes.
  5. Run chainsaw doctor --strict from a CI runner → exits 0.