Lesson: Salt GPG Renderer + GPG Agent Configuration
Lesson: Salt GPG Renderer + GPG Agent Configuration
Discovered: 2026-02-27 during Phase 2b of
plan-2026-02-26-salt-host-managementProblem
Salt's GPG renderer shells out to
gpg --homedir /etc/salt/gpgkeys --status-fd 2 --no-tty -d (see /opt/salt/lib/python3.10/site-packages/salt/renderers/gpg.py line 421-430). It does NOT pass --batch or --pinentry-mode loopback. This means:- The gpg-agent is auto-started by GPG 2.x (mandatory for private key operations)
- Without
pinentry-mode loopback, the agent tries to use a pinentry program interactively - Without
disable-scdaemon, the scdaemon subprocess causes "Broken pipe" errors - Result:
salt-call pillar.itemshangs for 180 seconds then returnsNO_SECKEY/Pillar timed out
Root Cause
GPG 2.x requires the gpg-agent for all private key operations (unlike GPG 1.x). When Salt spawns
gpg -d as a subprocess, the agent must be configured for non-interactive operation. Salt doesn't configure this — it's the operator's responsibility to set up /etc/salt/gpgkeys/gpg.conf and gpg-agent.conf.Solution
Two config files are required in
/etc/salt/gpgkeys/:gpg.conf:
gpg-agent.conf:
After writing these files:
sudo gpgconf --homedir /etc/salt/gpgkeys --kill all(kill stale agents)sudo systemctl restart salt-master
Key Details
batchandno-ttyin gpg.conf are picked up by any gpg invocation using--homedir /etc/salt/gpgkeys, so Salt's renderer gets them automaticallypinentry-mode loopbacktells gpg to send the passphrase request through the loopback pipe instead of a GUI pinentry — crucial for no-passphrase keys in daemon contextsdisable-scdaemonprevents the smart card daemon from launching (causes "Broken pipe" errors when spawned by Salt)no-autostartdoes NOT work — GPG 2.x refuses to decrypt without an agent. The agent must be allowed to start, just configured non-interactively.- The legacy keyring format (
pubring.gpg/secring.gpg) is NOT available in GPG 2.4.x — it always usespubring.kbx+ keyboxd
Symptoms to Watch For
salt-call pillar.itemshangs then returnsPillar timed out after 180 seconds- Salt master log shows:
gpg: public key decryption failed: No secret keyorBroken pipe gpgconf --homedir /etc/salt/gpgkeys --kill allis safe and useful for clearing stale agent state
Also Learned
- YAML block scalars (
|) require the content indented at least one more level than the key. A key at 4-space indent needs PGP blocks at 6-space indent. - Salt's GPG renderer finds PGP blocks via regex in the YAML values — the
#!yaml|gpgshebang tells Salt to pipe through yaml renderer first, then gpg renderer. python-gnupg 0.5.2is already bundled with salt-onedir 3007.13 — no separate install needed.
Related
plan-2026-02-26-salt-host-management— Phase 2bissue-pal-e-platform-salt-phase-2b-gpg-secrets— the issue where this was discovered