Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:
- run: ./scripts/build-release.sh dev dist
- name: the checksums match the files
run: cd dist && sha256sum -c SHA256SUMS
- name: the deb and rpm install on Debian and Rocky Linux
run: ./scripts/verify-packages.sh dist

race:
name: race detector
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ go install github.com/certpilot/certpilot-agent/cmd@latest

No Go or Docker on the host? Each [release](https://github.com/certpilot/certpilot-agent/releases)
has a static binary for Linux (amd64, arm64, armv7, 386), Windows (amd64, arm64),
macOS and FreeBSD, with a `SHA256SUMS` file to check it against.
macOS and FreeBSD, a `.deb` and `.rpm` for each Linux architecture, and a
`SHA256SUMS` file to check them against.

```
docker run --rm -v certpilot-agent:/var/lib/certpilot-agent \
Expand Down
19 changes: 17 additions & 2 deletions docs/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,8 +572,23 @@ certpilot-agent run --once # one cycle, then exit — for cron or a unit
certpilot-agent status # what this host holds and when it last reported
```

As a systemd unit — see [Where it runs](#where-it-runs) for the platforms
this is supported on:
The `.deb` and `.rpm` from a release install the binary to
`/usr/bin/certpilot-agent` with two ways to run it, neither enabled until you
choose one after enrolling:

```bash
sudo certpilot-agent enrol --server=https://certpilot.example.com --token=...
sudo systemctl enable --now certpilot-agent # the daemon
# or
sudo systemctl enable --now certpilot-agent.timer # one cycle every 5 minutes
```

Both units do nothing until `/var/lib/certpilot-agent/agent.json` exists.
Removing the package stops them and leaves the identity in place, so a
reinstall does not need a new enrolment.

Installed by hand, as a systemd unit — see [Where it runs](#where-it-runs) for
the platforms this is supported on:

```ini
[Unit]
Expand Down
10 changes: 10 additions & 0 deletions packaging/certpilot-agent-once.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Unit]
Description=CertPilot agent, one cycle
Documentation=https://github.com/certpilot/certpilot-agent
After=network-online.target
Wants=network-online.target
ConditionPathExists=/var/lib/certpilot-agent/agent.json

[Service]
Type=oneshot
ExecStart=/usr/bin/certpilot-agent run --once
17 changes: 17 additions & 0 deletions packaging/certpilot-agent.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[Unit]
Description=CertPilot agent
Documentation=https://github.com/certpilot/certpilot-agent
After=network-online.target
Wants=network-online.target
# Nothing to do until this host has enrolled, and a unit that fails every 30
# seconds until then would bury the one log line that says why.
ConditionPathExists=/var/lib/certpilot-agent/agent.json

[Service]
Type=simple
ExecStart=/usr/bin/certpilot-agent run
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target
12 changes: 12 additions & 0 deletions packaging/certpilot-agent.timer
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# For hosts where a resident daemon is not allowed. Enable this or
# certpilot-agent.service, not both.
[Unit]
Description=Run the CertPilot agent every 5 minutes

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Unit=certpilot-agent-once.service

[Install]
WantedBy=timers.target
39 changes: 39 additions & 0 deletions packaging/nfpm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Built by scripts/build-release.sh, once per Linux architecture. nfpm does not
# expand variables in a src path, so the script writes the binary's path over
# @BINARY@ in a copy of this file.
name: certpilot-agent
arch: ${NFPM_ARCH}
platform: linux
version: ${NFPM_VERSION}
maintainer: CertPilot <https://github.com/certpilot>
description: |
The CertPilot host agent. Generates its own private keys, requests
certificates for this host and installs them where its services read them.
homepage: https://github.com/certpilot/certpilot-agent
license: Apache-2.0

contents:
- src: "@BINARY@"
dst: /usr/bin/certpilot-agent
file_info:
mode: 0755
- src: packaging/certpilot-agent.service
dst: /usr/lib/systemd/system/certpilot-agent.service
- src: packaging/certpilot-agent-once.service
dst: /usr/lib/systemd/system/certpilot-agent-once.service
- src: packaging/certpilot-agent.timer
dst: /usr/lib/systemd/system/certpilot-agent.timer
# The identity key lives here, so nobody else may list it.
- dst: /var/lib/certpilot-agent
type: dir
file_info:
mode: 0700
# Where installs.json is read from by default.
- dst: /etc/certpilot
type: dir
file_info:
mode: 0755

scripts:
postinstall: packaging/postinstall.sh
preremove: packaging/preremove.sh
7 changes: 7 additions & 0 deletions packaging/postinstall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh
# Make systemd see the units. Nothing is enabled or started: the host has to
# enrol first, and which of the service or the timer to use is the operator's
# choice.
if [ -d /run/systemd/system ]; then
systemctl daemon-reload >/dev/null 2>&1 || true
fi
6 changes: 6 additions & 0 deletions packaging/preremove.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
# Stop what is running before the binary goes. The identity in
# /var/lib/certpilot-agent is left: removing a package must not un-enrol a host.
if [ -d /run/systemd/system ]; then
systemctl disable --now certpilot-agent.service certpilot-agent.timer >/dev/null 2>&1 || true
fi
23 changes: 21 additions & 2 deletions scripts/build-release.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
#
# Build the agent for every platform it is released for.
# Build the agent for every platform it is released for, and a deb and an rpm
# for each Linux architecture.
#
# ./scripts/build-release.sh 0.2.4 dist stamp 0.2.4, write archives to dist/
# ./scripts/build-release.sh dev dist unstamped, as CI builds it
Expand Down Expand Up @@ -31,12 +32,23 @@ if [ "$version" != dev ]; then
ldflags="$ldflags -X github.com/certpilot/certpilot-agent.Version=$version"
fi

# Pinned, and built rather than downloaded, so the packages come from a known
# version of the tool on any machine with Go.
nfpm_version=v2.46.3

rm -rf "$out"
mkdir -p "$out"
out="$(cd "$out" && pwd)"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT

GOBIN="$work/bin" go install "github.com/goreleaser/nfpm/v2/cmd/nfpm@$nfpm_version"

# A package version has to start with a digit; an unstamped build sorts below
# every release.
pkg_version="$version"
[ "$version" = dev ] && pkg_version=0.0.0-dev

for target in "${targets[@]}"; do
IFS=/ read -r goos goarch goarm <<<"$target"
name="certpilot-agent_${version}_${goos}_${goarch}${goarm:+v$goarm}"
Expand All @@ -54,9 +66,16 @@ for target in "${targets[@]}"; do
else
tar -C "$work" -czf "$out/$name.tar.gz" "$name"
fi
if [ "$goos" = linux ]; then
sed "s|@BINARY@|$dir/$bin|" packaging/nfpm.yaml > "$work/nfpm.yaml"
for format in deb rpm; do
NFPM_ARCH="$goarch${goarm:-}" NFPM_VERSION="$pkg_version" \
"$work/bin/nfpm" package --config "$work/nfpm.yaml" --packager "$format" --target "$out/" >/dev/null
done
fi
echo "built $name"
done

sum() { if command -v sha256sum >/dev/null; then sha256sum "$@"; else shasum -a 256 "$@"; fi; }
(cd "$out" && sum certpilot-agent_* > SHA256SUMS)
(cd "$out" && sum certpilot-agent[_-]* > SHA256SUMS)
echo "wrote $(ls "$out" | wc -l | tr -d ' ') files to $out"
53 changes: 53 additions & 0 deletions scripts/verify-packages.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
#
# Install the built deb and rpm in real distributions and check what they put
# on the host.
#
# ./scripts/verify-packages.sh dist after build-release.sh wrote dist/
#
# Not run under systemd: a container has none. What is checked is what a
# package can get wrong without anyone noticing until a host is enrolled: the
# binary, the units and whether systemd accepts them, the directories and their
# modes, that nothing starts before enrolment, and that removal cleans up.
#
set -uo pipefail

cd "$(dirname "$0")/.."
dist="${1:?usage: verify-packages.sh DIST}"
dist="$(cd "$dist" && pwd)"
fail=0

check() {
local image="$1" prep="$2" install="$3" remove="$4" pkg="$5"
echo "── $image: $pkg"
if ! docker run --rm --platform linux/amd64 -v "$dist:/dist:ro" "$image" bash -euc "
$prep >/dev/null 2>&1
$install /dist/$pkg >/dev/null
v=\$(certpilot-agent version); echo \" version \$v\"
for u in certpilot-agent.service certpilot-agent-once.service certpilot-agent.timer; do
test -f /usr/lib/systemd/system/\$u || { echo \" missing \$u\"; exit 1; }
done
systemd-analyze verify /usr/lib/systemd/system/certpilot-agent*.service /usr/lib/systemd/system/certpilot-agent.timer
echo ' systemd accepts the units'
test \"\$(stat -c %a /var/lib/certpilot-agent)\" = 700 || { echo ' state dir is not 0700'; exit 1; }
test -d /etc/certpilot || { echo ' no /etc/certpilot'; exit 1; }
grep -q '^ConditionPathExists=/var/lib/certpilot-agent/agent.json' /usr/lib/systemd/system/certpilot-agent.service \
|| { echo ' the service would start before enrolment'; exit 1; }
$remove certpilot-agent >/dev/null
! command -v certpilot-agent >/dev/null || { echo ' binary left after removal'; exit 1; }
echo ' installs, and removes cleanly'
"; then
echo " FAILED"; fail=1
fi
}

deb=$(cd "$dist" && ls certpilot-agent_*_amd64.deb 2>/dev/null | head -1)
rpm=$(cd "$dist" && ls certpilot-agent-*.x86_64.rpm 2>/dev/null | head -1)
[ -n "$deb" ] || { echo "no amd64 .deb in $dist"; exit 1; }
[ -n "$rpm" ] || { echo "no x86_64 .rpm in $dist"; exit 1; }

# systemd is installed only so systemd-analyze can check the units.
check debian:12 "apt-get update -qq && apt-get install -y -qq systemd" "apt-get install -y -qq" "apt-get remove -y -qq" "$deb"
check rockylinux:9 "dnf install -y -q systemd" "dnf install -y -q" "dnf remove -y -q" "$rpm"

exit $fail
Loading