run attic as a swarm service with deploy.attic.enable
This commit is contained in:
parent
f59ed01353
commit
458a046b8d
1 changed files with 1242 additions and 66 deletions
165
nix/host-modules/lib/attic-init.nix
Normal file
165
nix/host-modules/lib/attic-init.nix
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
# The shell for the in-swarm cache's first start: what its two host units run
|
||||
# after they log in to the secret store under the cache's own certificate.
|
||||
# Each secret is created only while the store holds none, so a second run reads
|
||||
# what the first one stored and mints nothing.
|
||||
#
|
||||
# Pure function — NOT a NixOS module. Call it from a module's `let`:
|
||||
#
|
||||
# atticInit = import ./lib/attic-init.nix { inherit lib; } { … };
|
||||
# script = ''
|
||||
# ${atticInit.prelude}
|
||||
# ${atticInit.ensureJwt}
|
||||
# '';
|
||||
#
|
||||
# `prelude` expects `./atomic-write-secret.nix` and `./refresh-consumer.nix`
|
||||
# above it, and `bao`, `coreutils` and `systemd` on the caller's `path`.
|
||||
# `ensureJwt` also wants `openssl` there; `provision` wants `atticadm`, `attic`
|
||||
# and `curl`.
|
||||
{ lib }:
|
||||
{
|
||||
# Store paths in the form `bao kv` takes, mount first.
|
||||
jwtPath,
|
||||
pullPath,
|
||||
pushPath,
|
||||
# The cache's name on the server.
|
||||
cache,
|
||||
# Where the host reaches atticd, scheme included.
|
||||
endpoint,
|
||||
# The server's config file, which the host's `atticadm` reads as well.
|
||||
configFile,
|
||||
# The file atticd loads its signing key from, and the container that does.
|
||||
envFile,
|
||||
machine,
|
||||
unit,
|
||||
}:
|
||||
let
|
||||
q = lib.escapeShellArg;
|
||||
in
|
||||
{
|
||||
prelude = ''
|
||||
err="$(mktemp)"
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$err" "$work"' EXIT
|
||||
|
||||
if ! BAO_TOKEN="$(bao login -method=cert -token-only 2>"$err")"; then
|
||||
echo "could not log in to swarm-bao with the cache's certificate; leaving the cache as it is." >&2
|
||||
cat "$err" >&2
|
||||
exit 1
|
||||
fi
|
||||
export BAO_TOKEN
|
||||
|
||||
# <store path> <mint function>: the stored value, created with the
|
||||
# function's output first while the store holds none. `bao kv get` exits 2
|
||||
# both for "nothing stored" and for a refusal, so a failed read always
|
||||
# tries the create, which `-cas=0` refuses when the path exists, and the
|
||||
# second read decides. The value reaches `bao` on stdin, never in an
|
||||
# argument.
|
||||
ensure_secret() {
|
||||
local path="$1" mint="$2" value minted
|
||||
if ! value="$(bao kv get -field=value "$path" 2>"$err")"; then
|
||||
minted="$("$mint")" || return 1
|
||||
if [ -z "$minted" ]; then
|
||||
echo "minting $path produced nothing." >&2
|
||||
return 1
|
||||
fi
|
||||
if ! printf '%s' "$minted" | bao kv put -cas=0 "$path" value=- >/dev/null 2>"$err"; then
|
||||
echo "could not create $path; reading it back in case another run created it." >&2
|
||||
cat "$err" >&2
|
||||
fi
|
||||
if ! value="$(bao kv get -field=value "$path" 2>"$err")"; then
|
||||
echo "swarm-bao did not return $path." >&2
|
||||
cat "$err" >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
if [ -z "$value" ]; then
|
||||
echo "swarm-bao returned an empty $path." >&2
|
||||
return 1
|
||||
fi
|
||||
printf '%s' "$value"
|
||||
}
|
||||
'';
|
||||
|
||||
# The signing key atticd verifies tokens with, and the file it loads it from.
|
||||
# The key is a base64 RSA PEM in PKCS#1 form, which is what `-traditional`
|
||||
# asks for.
|
||||
ensureJwt = ''
|
||||
mint_jwt() {
|
||||
openssl genrsa -traditional 4096 2>/dev/null | base64 -w0
|
||||
}
|
||||
|
||||
jwt="$(ensure_secret ${q jwtPath} mint_jwt)" || exit 1
|
||||
line="ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=$jwt"
|
||||
if secret_differs ${q envFile} "$line"; then
|
||||
atomic_write_secret 0400 "" ${q envFile} "$line"
|
||||
fi
|
||||
|
||||
# atticd reads the key as it starts, and only then.
|
||||
refresh_consumer ${q machine} ${q unit} ${q envFile}
|
||||
'';
|
||||
|
||||
# The cache and the two tokens, once atticd answers. The tokens are signed
|
||||
# here with the key the store holds; the cache is created through the API by a
|
||||
# token that lives for five minutes and is never stored.
|
||||
provision = ''
|
||||
up=0
|
||||
for _ in $(seq 1 30); do
|
||||
if curl -s -o /dev/null --max-time 2 ${q endpoint}; then
|
||||
up=1
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "$up" != 1 ]; then
|
||||
echo "atticd did not answer at ${endpoint}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! jwt="$(bao kv get -field=value ${q jwtPath} 2>"$err")" || [ -z "$jwt" ]; then
|
||||
echo "swarm-bao did not return ${jwtPath}; the cache's tokens are not minted." >&2
|
||||
cat "$err" >&2
|
||||
exit 1
|
||||
fi
|
||||
export ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64="$jwt"
|
||||
|
||||
# <subject> <validity> <grant…>
|
||||
mint_token() {
|
||||
local sub="$1" validity="$2"
|
||||
shift 2
|
||||
atticadm -f ${q configFile} make-token --sub "$sub" --validity "$validity" "$@"
|
||||
}
|
||||
mint_pull() { mint_token swarm-hives-pull 10y --pull ${q cache}; }
|
||||
mint_push() { mint_token swarm-builder-push 10y --pull ${q cache} --push ${q cache}; }
|
||||
|
||||
pull="$(ensure_secret ${q pullPath} mint_pull)" || exit 1
|
||||
ensure_secret ${q pushPath} mint_push >/dev/null || exit 1
|
||||
|
||||
# The client reads its token from a file, so the token is in no argument.
|
||||
export XDG_CONFIG_HOME="$work"
|
||||
use_token() {
|
||||
install -d -m 0700 "$work/attic"
|
||||
(umask 077 && printf '%s' "$1" >"$work/token")
|
||||
printf 'default-server = "local"\n\n[servers.local]\nendpoint = "%s"\ntoken-file = "%s"\n' \
|
||||
${q endpoint} "$work/token" >"$work/attic/config.toml"
|
||||
}
|
||||
|
||||
# The pull token reads the cache's configuration when the cache exists, so
|
||||
# a cache that is there costs no token. The server's database can be empty
|
||||
# while the store still holds both tokens.
|
||||
use_token "$pull"
|
||||
if ! attic cache info ${q "local:${cache}"} >/dev/null 2>"$err"; then
|
||||
admin="$(mint_token swarm-attic-init 5m --create-cache ${q cache} --pull ${q cache})" || exit 1
|
||||
use_token "$admin"
|
||||
if ! attic cache create ${q "local:${cache}"} >/dev/null 2>"$err"; then
|
||||
echo "could not create the cache ${cache}; checking whether another run did." >&2
|
||||
cat "$err" >&2
|
||||
fi
|
||||
use_token "$pull"
|
||||
if ! attic cache info ${q "local:${cache}"} >/dev/null 2>"$err"; then
|
||||
echo "the cache ${cache} is not there after creating it." >&2
|
||||
cat "$err" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
}
|
||||
Loading…
Reference in a new issue