Skip to content

ZentriaMC/syringe

Repository files navigation

Syringe

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.

Features

  • 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 updatesyringe-update re-fetches credentials for a running service via mount namespace jumping (used as ExecReload=), working around systemd/systemd#21099
  • D-Bus service — exposes GetSocketPaths, GetGlobalDebug, and Reload methods on the system bus
  • systemd socket activation — can be started on-demand via syringe.socket

Configuration

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 }}

Template functions

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_)

Template options

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

Usage

# 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-update

Deployment

Syringe 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 file

On 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-update

Credential reloading

For 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 reload

Running 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.

Security

  • The credential socket is root:root mode 0600 — only root (systemd) can connect
  • D-Bus method calls are restricted: Reload is root-only, GetSocketPaths is available to all users
  • syringe-update is a separate SUID/SGID binary for credential reloading — no audit has been done, use at your own risk
  • File reads are sandboxed via sandbox_path per template

See also

About

systemd credential service implementation

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors