Add enroll diff
parent
973e6756e3
commit
ebe49553e9
1 changed files with 260 additions and 0 deletions
260
enroll-diff.md
Normal file
260
enroll-diff.md
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
The Enroll `diff` subcommand will compare two 'harvests' and see if there are any differences.
|
||||
|
||||
# What does diff do?
|
||||
|
||||
These differences include:
|
||||
|
||||
* Packages added/removed
|
||||
* Files added/removed/changed
|
||||
* Users added/removed/changed
|
||||
* Services added/removed/changed
|
||||
|
||||
The `diff` command can also understand SOPS-encrypted harvests, assume the user executing the command is able to decrypt the SOPS files.
|
||||
|
||||
# How do I run it?
|
||||
|
||||
Assuming you have two harvests on your filesystem:
|
||||
|
||||
```
|
||||
enroll diff \
|
||||
--old harvest.v1 \
|
||||
--new harvest.v2
|
||||
```
|
||||
|
||||
# Output formats
|
||||
|
||||
The default output is just a plain text format, which is the same as passing `--format text`.
|
||||
|
||||
You can pass `--format json` or `--format markdown` to get structured formats instead.
|
||||
|
||||
# --exit-code mode
|
||||
|
||||
You can pass `--exit-code` as an argument, which will return a return code of 2 if there was any 'diff'.
|
||||
|
||||
You can use this to script any further action your cron service might take, such as sending e-mail output, or rely on it to trigger failures in CI builds.
|
||||
|
||||
# Webhooks
|
||||
|
||||
You can also send the output to a webhook under your control. Here is an example:
|
||||
|
||||
```
|
||||
enroll diff \
|
||||
--old harvest.v1 \
|
||||
--new harvest.v2 \
|
||||
--webhook https://example.com/some/endpoint \
|
||||
--webhook-format json \
|
||||
--webhook-header 'X-Enroll-Secret: Some-Secret-Here'
|
||||
```
|
||||
|
||||
You can pass `--webhook-format markdown` if you wanted the Markdown format in the payload, but most people probably want JSON structured data for consumption at the other end.
|
||||
|
||||
Use the `--webhook-header` to send some sort of authentication/authorization attribute (e.g an OAuth2.0 bearer token, static secret, etc) to help lock down your webhook from unauthorised requests. You can send multiple headers.
|
||||
|
||||
# Email
|
||||
|
||||
You can send the output via email using email and smtp arguments. See 'Full usage info' below for more information.
|
||||
|
||||
# Example: Running the diff on a systemd timer
|
||||
|
||||
In the example below, we set up a systemd timer to run on a periodic basis and trigger the webhook.
|
||||
|
||||
It will set up a 'golden' harvest if it didn't already exist, then periodically run a second harvest to compare against it.
|
||||
|
||||
You can optionally promote the new harvest to become the new 'golden' harvest for future invocations if you want a continuous, rolling 'diff' scenario.
|
||||
|
||||
## Create script
|
||||
|
||||
Store in `/usr/local/bin/enroll-harvest-diff.sh` and make it `chmod 0700`
|
||||
|
||||
```
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Required env
|
||||
: "${WEBHOOK_URL:?Set WEBHOOK_URL in /etc/enroll/enroll-harvest-diff.env}"
|
||||
: "${ENROLL_SECRET:?Set ENROLL_SECRET in /etc/enroll/enroll-harvest-diff.env}"
|
||||
|
||||
# Optional env
|
||||
STATE_DIR="${ENROLL_STATE_DIR:-/var/lib/enroll}"
|
||||
GOLDEN_DIR="${STATE_DIR}/golden"
|
||||
PROMOTE_NEW="${PROMOTE_NEW:-1}" # 1=promote new->golden; 0=keep golden fixed
|
||||
KEEP_BACKUPS="${KEEP_BACKUPS:-7}" # only used if PROMOTE_NEW=1
|
||||
LOCKFILE="${STATE_DIR}/.enroll-harvest-diff.lock"
|
||||
|
||||
mkdir -p "${STATE_DIR}"
|
||||
chmod 700 "${STATE_DIR}" || true
|
||||
|
||||
# single-instance lock (avoid overlapping timer runs)
|
||||
exec 9>"${LOCKFILE}"
|
||||
flock -n 9 || exit 0
|
||||
|
||||
tmp_new=""
|
||||
cleanup() {
|
||||
if [[ -n "${tmp_new}" && -d "${tmp_new}" ]]; then
|
||||
rm -rf "${tmp_new}"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
make_tmp_dir() {
|
||||
mktemp -d "${STATE_DIR}/.harvest.XXXXXX"
|
||||
}
|
||||
|
||||
run_harvest() {
|
||||
local out_dir="$1"
|
||||
rm -rf "${out_dir}"
|
||||
mkdir -p "${out_dir}"
|
||||
chmod 700 "${out_dir}" || true
|
||||
enroll harvest --out "${out_dir}" >/dev/null
|
||||
}
|
||||
|
||||
# A) create golden if missing
|
||||
if [[ ! -f "${GOLDEN_DIR}/state.json" ]]; then
|
||||
tmp="$(make_tmp_dir)"
|
||||
run_harvest "${tmp}"
|
||||
rm -rf "${GOLDEN_DIR}"
|
||||
mv "${tmp}" "${GOLDEN_DIR}"
|
||||
echo "Golden harvest created at ${GOLDEN_DIR}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# B) create new harvest
|
||||
tmp_new="$(make_tmp_dir)"
|
||||
run_harvest "${tmp_new}"
|
||||
|
||||
# C) diff + webhook notify
|
||||
enroll diff \
|
||||
--old "${GOLDEN_DIR}" \
|
||||
--new "${tmp_new}" \
|
||||
--webhook "${WEBHOOK_URL}" \
|
||||
--webhook-format json \
|
||||
--webhook-header "X-Enroll-Secret: ${ENROLL_SECRET}"
|
||||
|
||||
# Promote or discard new harvest
|
||||
if [[ "${PROMOTE_NEW}" == "1" || "${PROMOTE_NEW,,}" == "true" || "${PROMOTE_NEW}" == "yes" ]]; then
|
||||
ts="$(date -u +%Y%m%d-%H%M%S)"
|
||||
backup="${STATE_DIR}/golden.prev.${ts}"
|
||||
mv "${GOLDEN_DIR}" "${backup}"
|
||||
mv "${tmp_new}" "${GOLDEN_DIR}"
|
||||
tmp_new="" # don't delete it in trap
|
||||
|
||||
# Keep only latest N backups
|
||||
if [[ "${KEEP_BACKUPS}" =~ ^[0-9]+$ ]] && (( KEEP_BACKUPS > 0 )); then
|
||||
ls -1dt "${STATE_DIR}"/golden.prev.* 2>/dev/null | tail -n +"$((KEEP_BACKUPS+1))" | xargs -r rm -rf
|
||||
fi
|
||||
|
||||
echo "Diff complete; baseline updated."
|
||||
else
|
||||
# tmp_new will be deleted by trap
|
||||
echo "Diff complete; baseline unchanged (PROMOTE_NEW=${PROMOTE_NEW})."
|
||||
fi
|
||||
```
|
||||
|
||||
## Environment file
|
||||
|
||||
Save as: `/etc/enroll/enroll-harvest-diff` and `chmod 0600` it.
|
||||
|
||||
```
|
||||
# Where to store golden + temp harvests
|
||||
ENROLL_STATE_DIR=/var/lib/enroll
|
||||
|
||||
# 1 = each run becomes new baseline ("since last harvest")
|
||||
# 0 = compare against a fixed baseline ("since golden")
|
||||
PROMOTE_NEW=1
|
||||
|
||||
# If PROMOTE_NEW=1, keep this many old baselines
|
||||
KEEP_BACKUPS=7
|
||||
|
||||
WEBHOOK_URL=https://example.com/webhook/xxxxxxxx
|
||||
ENROLL_SECRET=xxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
|
||||
## Systemd service
|
||||
|
||||
`/etc/systemd/system/enroll-harvest-diff.service`:
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=Enroll harvest + diff + webhook notify
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
ConditionPathExists=/etc/enroll/enroll-harvest-diff
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
EnvironmentFile=/etc/enroll/enroll-harvest-diff
|
||||
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
UMask=0077
|
||||
|
||||
# Create /var/lib/enroll automatically
|
||||
StateDirectory=enroll
|
||||
|
||||
ExecStart=/usr/local/bin/enroll-harvest-diff.sh
|
||||
```
|
||||
|
||||
## Systemd timer
|
||||
|
||||
`/etc/systemd/system/enroll-harvest-diff.timer`
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=Run Enroll harvest diff hourly
|
||||
|
||||
[Timer]
|
||||
OnCalendar=hourly
|
||||
RandomizedDelaySec=10m
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
## Enable + test
|
||||
|
||||
```
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now enroll-harvest-diff.timer
|
||||
|
||||
# run once now
|
||||
sudo systemctl start enroll-harvest-diff.service
|
||||
sudo journalctl -u enroll-harvest-diff.service -n 200 --no-pager
|
||||
```
|
||||
|
||||
If you want the “fixed baseline” behavior (always diff against the original golden snapshot), set `PROMOTE_NEW=0` and you're done.
|
||||
|
||||
|
||||
# Full usage info
|
||||
|
||||
```
|
||||
usage: enroll diff [-h] --old OLD --new NEW [--sops] [--format {text,markdown,json}] [--out OUT] [--exit-code] [--notify-always] [--webhook WEBHOOK]
|
||||
[--webhook-format {json,text,markdown}] [--webhook-header K:V] [--email-to EMAIL_TO] [--email-from EMAIL_FROM] [--email-subject EMAIL_SUBJECT] [--smtp SMTP]
|
||||
[--smtp-user SMTP_USER] [--smtp-password-env SMTP_PASSWORD_ENV]
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--old OLD Old/baseline harvest (directory, a path to state.json, a tarball, or a SOPS-encrypted bundle).
|
||||
--new NEW New/current harvest (directory, a path to state.json, a tarball, or a SOPS-encrypted bundle).
|
||||
--sops Allow SOPS-encrypted harvest bundle inputs (requires `sops` on PATH).
|
||||
--format {text,markdown,json}
|
||||
Report output format (default: text).
|
||||
--out OUT Write the report to this file instead of stdout.
|
||||
--exit-code Exit with status 2 if differences are detected.
|
||||
--notify-always Send webhook/email even when there are no differences.
|
||||
--webhook WEBHOOK POST the report to this URL (only when differences are detected, unless --notify-always).
|
||||
--webhook-format {json,text,markdown}
|
||||
Payload format for --webhook (default: json).
|
||||
--webhook-header K:V Extra HTTP header for --webhook (repeatable), e.g. 'Authorization: Bearer ...'.
|
||||
--email-to EMAIL_TO Email the report to this address (repeatable; only when differences are detected unless --notify-always).
|
||||
--email-from EMAIL_FROM
|
||||
From address for --email-to (default: enroll@<hostname>).
|
||||
--email-subject EMAIL_SUBJECT
|
||||
Subject for --email-to (default: 'enroll diff report').
|
||||
--smtp SMTP SMTP server host[:port] for --email-to. If omitted, uses local sendmail.
|
||||
--smtp-user SMTP_USER
|
||||
SMTP username (optional).
|
||||
--smtp-password-env SMTP_PASSWORD_ENV
|
||||
Environment variable containing SMTP password (optional).
|
||||
```
|
||||
|
||||
The main purpose of the `diff` command
|
||||
Loading…
Add table
Add a link
Reference in a new issue