Credential provider for systemd's LoadCredential= Unix socket protocol.
Syringe renders Go templates and serves the output over a Unix socket. When a systemd unit specifies LoadCredential=name:/run/syringe/syringe.sock, systemd connects to the socket and receives the rendered credential.
- Template-based credentials — Go templates with built-in functions for files, Vault secrets, age decryption, Sprig, sockaddr, hashing, base64, and more
- HashiCorp Vault integration — read secrets from Vault and render them into credentials
- age decryption — decrypt age-encrypted files using SSH host ed25519 keys or native age identities
- File-based credentials — read from local files with path sandboxing
- Configuration reload — SIGHUP reloads the config without restarting; also exposed as a D-Bus method (
ee.zentria.syringe1.Syringe.Reload) - Credential update —
syringe-updatere-fetches credentials for a running service via mount namespace jumping (used asExecReload=), working around systemd/systemd#21099 - D-Bus service — exposes
GetSocketPaths,GetGlobalDebug, andReloadmethods on the system bus - systemd socket activation — can be started on-demand via
syringe.socket
Syringe uses a YAML configuration file (default: /etc/syringe/config.yml):
age:
identities:
- /etc/ssh/ssh_host_ed25519_key
templates:
- unit: "myapp.service"
credential:
- "db-password"
options:
sandbox_path: "/etc/syringe/secrets"
contents: |
{{ (age "/etc/syringe/secrets/app.json.age" | sprig_fromJson).db_password }}
- unit: "myapp.service"
credential:
- "hostname"
options:
sandbox_path: "/etc"
contents: |
{{ file "/etc/hostname" }}
- unit: "myapp.service"
# omit credential list for catch-all (matches any credential name)
contents: |
unit={{ unitname }}, credential={{ credentialname }}| Function | Description |
|---|---|
unitname |
Requesting unit name |
credentialname |
Requested credential name |
file "<path>" |
Read a file (subject to sandbox_path) |
age "<path>" |
Decrypt an age-encrypted file (subject to sandbox_path) |
vault_read "<path>" |
Read a Vault secret |
b64encode / b64decode |
Base64 encoding/decoding |
sha256sum / sha1sum / md5sum |
Hash functions |
time [format] [modifier] |
Current time (unix, rfc3339, or Go format) |
sockaddr "<expr>" |
go-sockaddr template |
sprig_* |
All Sprig functions (prefixed with sprig_) |
| Option | Description |
|---|---|
delim_left / delim_right |
Custom template delimiters (default: {{ / }}) |
sandbox_path |
Restrict file reads to this directory |
allow_missing |
Treat missing template keys as empty instead of erroring |
# Run the server
syringe server --config /etc/syringe/config.yml
# Request a credential manually
syringe request --socket /run/syringe/syringe.sock --unit myapp.service --credential db-password
# Update credentials for the calling service (used as ExecReload=)
syringe-updateSyringe runs as a systemd service with socket activation:
# /usr/lib/systemd/system/syringe.socket
[Socket]
ListenStream=/run/syringe/syringe.sock
SocketMode=0600
SocketUser=root
SocketGroup=root
[Install]
WantedBy=sockets.target# /usr/lib/systemd/system/syringe.service
[Service]
Type=dbus
BusName=ee.zentria.syringe1.Syringe
ExecStart=/usr/bin/syringe server# /usr/share/dbus-1/system.d/ee.zentria.syringe1.Syringe.conf
# D-Bus policy — see dbus/ directory for the full file# /usr/share/dbus-1/system-services/ee.zentria.syringe1.Syringe.service
# D-Bus activation service — see dbus/ directory for the full fileOn Red Hat derived SELinux-enabled systems, the binaries need the initrc_exec_t label for LoadCredential socket access:
chcon -t initrc_exec_t /usr/bin/syringe /usr/bin/syringe-updateFor services that need updated credentials without a full restart, use syringe-update as ExecReload=. It first re-fetches credentials, then execs into any trailing arguments — allowing you to chain the service's own reload command:
[Service]
LoadCredential=tls-cert:/run/syringe/syringe.sock
LoadCredential=tls-key:/run/syringe/syringe.sock
ExecReload=/usr/bin/syringe-update nginx -s reloadRunning systemctl reload nginx.service will re-fetch the TLS cert and key from syringe, atomically replace them in the service's credentials directory, then exec nginx -s reload so nginx picks up the new files. This works by jumping into the service's mount namespace, remounting the credentials directory read-write, and performing the update.
syringe-update is a separate SUID binary so the main syringe server does not need elevated permissions.
Note: this is a best-effort workaround for systemd/systemd#21099. systemd v260+ will include RefreshOnReload= as a native solution.
- The credential socket is
root:rootmode0600— only root (systemd) can connect - D-Bus method calls are restricted:
Reloadis root-only,GetSocketPathsis available to all users syringe-updateis a separate SUID/SGID binary for credential reloading — no audit has been done, use at your own risk- File reads are sandboxed via
sandbox_pathper template