Lesson: Salt GPG Renderer + GPG Agent Configuration

lesson-salt-gpg-agent-config Doc

active lesson

Lesson: Salt GPG Renderer + GPG Agent Configuration

Discovered: 2026-02-27 during Phase 2b of plan-2026-02-26-salt-host-management

Problem

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.items hangs for 180 seconds then returns NO_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

  • batch and no-tty in gpg.conf are picked up by any gpg invocation using --homedir /etc/salt/gpgkeys, so Salt's renderer gets them automatically
  • pinentry-mode loopback tells gpg to send the passphrase request through the loopback pipe instead of a GUI pinentry — crucial for no-passphrase keys in daemon contexts
  • disable-scdaemon prevents the smart card daemon from launching (causes "Broken pipe" errors when spawned by Salt)
  • no-autostart does 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 uses pubring.kbx + keyboxd

Symptoms to Watch For

  • salt-call pillar.items hangs then returns Pillar timed out after 180 seconds
  • Salt master log shows: gpg: public key decryption failed: No secret key or Broken pipe
  • gpgconf --homedir /etc/salt/gpgkeys --kill all is 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|gpg shebang tells Salt to pipe through yaml renderer first, then gpg renderer.
  • python-gnupg 0.5.2 is already bundled with salt-onedir 3007.13 — no separate install needed.
  • plan-2026-02-26-salt-host-management — Phase 2b
  • issue-pal-e-platform-salt-phase-2b-gpg-secrets — the issue where this was discovered