Salt pillar data is served exclusively to the minion that requested it, so it is already more secure than putting secrets in state files. However, pillar files committed to a git repository are still visible to anyone with repository access. WikiOasis solves this by keeping all secrets in a dedicatedDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/wikioasis/salt/llms.txt
Use this file to discover all available pages before exploring further.
pillar/private/ directory that is listed in .gitignore and never committed. The Salt master reads it at runtime; the git history never sees it.
How the private pillar is wired in
pillar/top.sls assigns the private pillar to every minion via the '*' catch-all rule, and additionally to db*, apps*, and monitoring* explicitly:
pillar/private/init.sls relative to the pillar root configured in /etc/salt/master. If the file is absent, any state that calls salt['pillar.get']('monitoring:icinga_api_password') (for example) will return an empty string or raise an error, depending on how the state handles missing keys.
The example template
The repository shipspillar/private/init.sls.example as a documented template. Copy it to pillar/private/init.sls on the Salt master and fill in every CHANGE_ME placeholder before running any highstate.
Secret categories
The private pillar holds four categories of secrets:Monitoring credentials
| Key | Purpose |
|---|---|
monitoring.icinga_api_host | FQDN of the Icinga 2 API endpoint |
monitoring.icinga_api_password | Password for the root Icinga API user |
monitoring.ido_db_password | MariaDB password for the icingadb IDO user |
monitoring.director_db_password | MariaDB password for the Icinga Director database user |
monitoring.monitoring_db_password | MariaDB password for the Icinga Web database user |
monitoring.grafana_admin_password | Password for the Grafana admin account |
monitoring.mysqld_exporter_password | Password for the Prometheus mysqld_exporter MariaDB user |
Notification webhooks
| Key | Purpose |
|---|---|
notifications.discord_webhook_url | Full Discord incoming webhook URL for deployment alerts |
notifications.slack_webhook_url | Full Slack incoming webhook URL for deployment alerts |
mediawiki.webhooks.discord | MediaWiki-specific Discord webhook (can differ from global) |
mediawiki.webhooks.slack | MediaWiki-specific Slack webhook |
Sentry Relay
| Key | Purpose |
|---|---|
sentry_relay.dsn | Sentry DSN for the sentry_relay state to configure error reporting |
MariaDB backup
| Key | Purpose |
|---|---|
mariadb.backup.user | Name of the MariaDB backup user created on each db* host |
mariadb.backup.password | Password for the backup user |
mariadb.backup.destination.host | IP or FQDN of the external backup server |
mariadb.backup.destination.user | SSH user on the backup server |
mariadb.backup.destination.path | Absolute path on the backup server; per-host subdirectories are created automatically |
mariadb.backup.ssh_private_key | PEM-encoded private key (multi-line YAML block scalar) for rsync authentication to the backup server |
Populating the private pillar
Create the private pillar directory
The
pillar/private/ directory is tracked in git (so that the gitignore rules apply) but no .sls files inside it are committed.Fill in all values
Open the file in your editor and replace every
CHANGE_ME and YOUR_* placeholder with real credentials. Pay particular attention to the mariadb.backup.ssh_private_key block — it must be a valid PEM-encoded private key and must use the YAML literal block scalar syntax (|) to preserve newlines.File permissions
Becauseinit.sls contains plaintext secrets, lock down its permissions on the Salt master: