Security model
This page summarises the controls built into Gardience. For assessors and security reviewers, the enterprise documents go into more depth:
- NIST SP 800-53 mapping: which controls Gardience provides or supports.
- Security questionnaire answers: ready answers for HECVAT and SIG Lite style forms.
- Security testing report: how each release is tested and what was found.
- Disaster recovery runbook and sizing guide.
- Support and severity policy, including the security fix timeline.
Self-hosted, with nothing sent out
Gardience runs on your server. The license is checked on the server with no call to the internet, and the console's fonts and API documentation assets ship inside the product, so pages load nothing from outside. It runs on air-gapped networks from an offline bundle.
Network exposure
Only these ports are published on the server:
- the console's HTTPS port (443 by default) and an HTTP port (80 by default) that only redirects to HTTPS;
- the two Salt ports agents connect to (4505 and 4506 by default), on all-in-one installs.
salt-api, the Gardience service and the playbook runner listen only on the internal container network.
Of salt-api's own endpoints, only those the console needs are published. Endpoints that would let a session skip Gardience's change control (/run, /token, /hook, /jobs, /keys, /minions, /ws) stay internal. The live event stream, which carries every job's arguments and output, is published only to admins, and Gardience checks that before the connection opens.
The update cache answers under /updates/ on the console's HTTPS port only while an admin has it on. It fetches only from the official Debian, Ubuntu, Rocky, Alma and EPEL sources (never an address a request names), passes no cookies or credentials upstream, and verifies each source's certificate. Packages stay signed by their publishers and every machine checks them.
See Ports and files.
Encryption in transit
- The console accepts TLS 1.2 and 1.3 only, with forward-secret AEAD ciphers and HSTS.
- The installer creates a self-signed certificate. Use your own with
--tls-certand--tls-key. - Agent traffic uses Salt's own encrypted transport.
- SIEM forwarding can use TLS. See Forward the audit log to a SIEM.
Browser protections
Every page carries a strict Content-Security-Policy (scripts only from the server or with a per-response nonce), plus X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, cross-origin isolation headers and a restrictive Permissions-Policy.
Sign-in and sessions
- Sign-in uses local accounts, LDAP or Active Directory (StartTLS on by default), or single sign-on with OpenID Connect or SAML 2.0. Single sign-on users get multi-factor sign-in from your identity provider; local and directory accounts can use one-time codes from an authenticator app, and roles can be required to (
gardience mfa require admin). - Sign-in attempts are rate limited per client address (10 a minute, with a burst of 5) and per account (10 failures in 15 minutes pause that account, from any address).
- The session cookie is Secure, HttpOnly and SameSite=Strict, and any change sent to Salt's API must carry the console's token header, so another site cannot make a signed-in browser act (CSRF).
- Every sign-in, failed sign-in, single sign-on sign-in, and sign-out is recorded in the audit log with the time, the client address, and the browser. A sign-in from a second address while the person already has a session open says so in the entry. The address is set by Gardience's front door from the connection itself, so a request cannot supply its own. Admins, approvers, and operators read those details; viewers and helpdesk read the same entries without other people's addresses and browsers.
- Each person can see where they are signed in and sign out everywhere else under Your account; admins can see and end anyone's sessions.
- Sessions expire after 8 hours for local accounts and 1 hour for directory and single sign-on accounts (both configurable). Removing a user, changing a role or resetting a password ends that person's open sessions immediately (
gardience user signoutdoes it on demand). - Names Gardience uses for itself are reserved and cannot be used to sign in.
See Users and roles and Single sign-on.
Least privilege and change control
- Changes that cannot be undone need a second person, admins included: wiping or formatting a disk, removing or stopping the Salt agent (Gardience would lose the machine), deleting a whole drive, and clearing the audit or security logs. Asking creates a request under Automation > Workflows and approvals; a different admin or approver approves it, and the person who asked runs it from there once, within 24 hours, for exactly the command and machines approved. This applies to the console, the command center, runbooks, the AI assistant, and API keys, and such changes cannot be scheduled. A restart cancels a waiting request (ask again). Applying a saved state counts by what the state contains (for example a
service.deadstep for the Salt agent), whether it was saved in the console or published from Git, and automation checks it again each time it would run. An approval holds only for what was approved: if the runbook or a saved state in it changes afterwards, the person asks again. The limit: Gardience recognises these changes by what the command or state says, including the usual shell spellings. A command assembled while it runs (from variables,evalor encoded text) cannot be read in advance. That is why running arbitrary commands is a separate switch that root controls on the server (gardience automation commands off, the default on new installs): with it off, those commands cannot run at all. - API keys cannot start work on the fleet by themselves. A schedule made with a key starts turned off until an admin turns it on, and when a key changes a runbook or a saved state that a schedule runs, that schedule turns off again until an admin looks.
- Risky but reversible changes (stopping a firewall or anti-malware, opening a port, turning SELinux off, deleting accounts, files, or folders, shutting down, downloading a script and running it) are shown with a warning and run at once, because they are sometimes urgent: a full disk is a real reason to delete things.
- No person holds raw Salt rights day to day: admins, like everyone else, change machines only through Gardience. Root on the server can open a time-limited, audited break-glass account (
gardience breakglass on). - Operators, approvers and help desk users hold
gdn:grants that Salt itself never honours. Their changes go through Gardience, which checks the person's permission on every target node, then approvals, change windows and locks, and only then runs the change as its automation identity. The audit log records the person. - Runs can require a second person's approval. The person who launches a run can never approve it.
- New agents are never accepted automatically. An admin approves each agent key, or agents enroll with a token.
- The web service cannot change who may do what: it reads users, Salt permissions and TLS settings but cannot write them. Only the network-less
config-keepercontainer and thegardiencecommand on the server write them, so a flaw in the web service cannot grant itself or anyone else more rights. - Saved states follow the same rule: the web service only queues them; the config keeper checks each one (plain YAML, and no command-running states or options while command execution is off) and is the only writer of the folder the Salt master reads them from. A flaw in the web service therefore cannot use a state to run commands that Salt's own permissions refuse.
- The optional AI assistant only proposes; a person runs the proposal under their own permissions. It has no Salt access, and its proposals pass the same checks as a person's input.
- Gardience's automation identity may run only the functions Gardience itself calls on agents (patching, reboots, baselines and hardening, inventory, service and package actions, state runs, grains), plus node-key approval and two Gardience runners; never "any function". Arbitrary command execution (Run a command, Ansible controller mode, PowerShell) is a switch root controls with
gardience automation commands on|off, and it is off on a new install (1.3.0 and later): with it off, even a compromised web service cannot run commands on the fleet. Root turns it on only if the team uses those features. Upgrades keep the setting they had. Further functions for custom runbooks are allowed by root only (gardience automation allow FUNCTION).
API keys
- Only a SHA-256 hash of each key is stored. The key is shown once.
- Read keys change nothing. Operate keys can only request predefined runbooks with checked inputs, never arbitrary Salt functions, and can never do more than their owner.
- Operate requests need a second person's approval unless an admin created the key without it.
- Configure keys (for Terraform) are made by admins only and stop the moment their owner is no longer an admin. They manage configuration (enrollments, groups, saved states, event rules) and remove machines, and cannot request runs on the existing fleet. On new machines they decide what happens: an enrollment lets them join and a join rule applies a saved state, which still passes the configuration keeper's checks (no commands while command execution is off). Treat them like admin credentials.
- Gardience's own automation identity cannot delete agent keys. A machine removal is a request that the network-less config keeper checks and limits (a set number an hour), and that the Salt master then carries out by moving the key to a holding folder for 30 days, so a compromised web service cannot strip the fleet from management. Each step checks the machine name again, and request ids are reduced to letters, digits, dashes, and underscores before they become file names.
- Keys expire, pause when their owner stops signing in, and stop at once when their owner is removed.
See API keys and the REST API.
Audit log
- Every entry is chained to the one before it with a keyed hash (HMAC-SHA256). The key lives in the config directory, outside the data volume.
- A signed checkpoint records how many entries the log holds, so deleting or cutting the log short is detected.
- The chain is verified at start and every 15 minutes. A failure raises a lasting alarm in the console, the readiness check and the metrics. Clearing it takes two people.
- If the log cannot be written, Gardience refuses changes until it can.
- Entries can be forwarded to your SIEM as they are written, with delivery that resumes after an outage.
Secrets vault
Secrets for states, runbooks and playbooks are encrypted at rest with AES-256-GCM. They are write-only (never shown again in the console or API), released only to nodes in the groups each secret names, and delivered through Salt pillar rather than job arguments. Every change and release is audited, without the value.
Containers
- The Gardience service runs with a read-only file system, all Linux capabilities dropped except the few it needs to take ownership of its files at start, and
no-new-privileges. It then runs as an unprivileged user. - Ansible playbooks run in a separate runner container with no access to Gardience's data or secrets, a 2 GB memory limit, and a separate unprivileged user for each run.
- Each container's logs are capped, so they cannot fill the disk.
- Gardience's own Salt master image refuses job results that one agent forges for another.
Backups, upgrades and high availability
- Backups are encrypted by default (AES-256, PBKDF2 with 200,000 iterations), with your passphrase file or the server's own backup key, and carry a keyed integrity code and a checksum. Restore refuses an archive that fails either check. See Backup and restore.
gardience upgraderefuses a release whose signature or checksum does not verify, and refuses to go to an older version unless told to.- The two servers of a primary and standby pair prove their role to each other with signatures from the key they share, and an out-of-date primary fences itself. See High availability.