Host and Workstation Deployment
The deploy/systemd/ directory runs apf-policy-service and apf-policy-gateway as hardened systemd services under the unprivileged apf account, co-located with an OpenShell gateway and its supervisors on one Linux host. Read deploy/systemd/SECURITY.md in the repository before you deploy. It is the checklist a reviewer signs off against, and it covers host controls that a unit file cannot express.
Files
Both services run as the same apf account because each binds its socket owner-only and checks the peer’s UID.
Install Policy and Keys
Install the signed bundle, the trust roots, and the document signing key with the ownership and modes in the table above. Use your own keys. Refer to Configure a Signing Authority and Signing Keys and Trust Roots.
Set APF_DOCUMENT_KEY_ID to the same value in both env files. The shipped files use apf-prod.
Set the Audit Directory
Both services refuse to start without an audit directory, and the shipped env files do not set one. The units use ProtectSystem=strict, so the directory must be inside each unit’s writable state directory. Add one line to each env file:
Enable the Policy Service HTTP Surface
The gateway’s interceptor verifies provenance tokens with the key from the policy service’s JWKS endpoint, and the shipped gateway env file enables the interceptor and sets APF_JWKS_URL=http://127.0.0.1:19200/.well-known/jwks.json. The gateway refuses to start if it cannot fetch the JWKS, so enable the policy service’s HTTP surface on loopback:
-
In
/etc/apf/policy-service.env, setAPF_HTTP_ADDR=127.0.0.1:19200. -
In
apf-policy-service.service, uncommentLoadCredential=http-bearer-token:/etc/apf/http-bearer-token. -
In the same unit, add
AF_INETtoRestrictAddressFamilies, and record why in a comment. The shipped unit allows only Unix sockets. -
Create the token:
The service refuses to start with APF_HTTP_ADDR set and no token. /healthz, /readyz, and the JWKS route stay reachable without the token. Add a host firewall rule that drops inbound traffic to the port from anything but loopback.
Configure the OpenShell Surfaces
The shipped gateway env file enables both OpenShell listeners on loopback, 127.0.0.1:18202 for the interceptor and 127.0.0.1:9099 for the middleware, in authenticated mode. For each enabled surface, install its trust profile, certificate, and private key. The unit loads both private keys as credentials, so both key files must exist. If you do not use a surface, remove its address and file settings from the env file and its LoadCredential line and --openshell-*-tls-key-file argument from the unit.
Refer to Configure Service Authentication for the trust profile format and how to obtain its values.
Validate and Start
The gateway connects to the policy service’s socket and fetches its JWKS at startup without retrying. If it starts before the policy service is listening, it exits and systemd restarts it. The units allow five starts in 60 seconds, counting the first. If systemd gives up, run sudo systemctl reset-failed apf-policy-gateway.service and then sudo systemctl restart apf-policy-gateway.service once the policy service is ready.
Confirm both services are running and writing audit records:
Hardening in the Units
The units run each service with no capabilities, no new privileges, a read-only view of the system, a private /tmp, hidden processes, and a system call filter. MemoryDenyWriteExecute, namespace restrictions, and realtime restrictions are on. The gateway unit also restricts IP traffic to loopback with IPAddressAllow=localhost, as a backstop against a misconfigured bind address. Both units cap restarts at five in 60 seconds.
Operating the Services
- Restart through your deploy pipeline only. The polkit rule denies interactive
systemctl start,stop,restart, andreloadof both units to every local session. It does not restrictroot. Restrict how administrators reachrootwith a sudoers wrapper, as described indeploy/systemd/SECURITY.md. - Configuration is read at startup. Restart the policy service after changing its env file, trust roots, or keys. The gateway picks up a new bundle through hot swap without a restart.
- Bundle updates. With the local source, replace the bundle files and restart the policy service, or call
POST /v1/refreshwith the bundle path in the JSON body,{"bundle_path": "/etc/apf/bundle/bundle.yaml"}. Refer to Update Policies. - HTTP bundle source. Requires more unit changes. Refer to Publish and Distribute.
- Detect tampering. Add auditd watches on the unit files,
/etc/apf, and both binaries, and ship them with the units. - Keep OpenShell’s agent policy proposals off. Refer to Add APF to OpenShell.
Next Steps
- To configure caller authentication, refer to Configure Service Authentication.
- To review the full security checklist, refer to Security Best Practices.