Sync the self-hosting stack
This commit is contained in:
@@ -9,6 +9,43 @@ one of them changed. An entry says which artefact and which version, so `web 0.1
|
||||
Every version published to a channel appears here. Nothing is written from memory afterwards: a build
|
||||
cannot be published without its entry.
|
||||
|
||||
## 2026-09-25 — A fresh Kubernetes install no longer asks for its own vault key `api 0.114.1`
|
||||
|
||||
**On the published Kubernetes stack, creating the first administrator asked for the last characters
|
||||
of the vault master key, every time.** That proof exists for one case: an instance whose database has
|
||||
gone missing while its key survived, which looks brand new from the outside. A genuine first
|
||||
installation is never meant to be asked. On Kubernetes it always was. The minting Job writes
|
||||
`VAULT_KEY_CREATED_AT` beside the key it generates, but the api read that date only when the key came
|
||||
from a file, and this stack passes the key as an environment variable. The date went unread, the
|
||||
key's age was unknown, and an unknown age counts as "cannot prove this instance is new".
|
||||
|
||||
**The api now reads `VAULT_KEY_CREATED_AT` wherever the key comes from.** A fresh install whose api
|
||||
starts within fifteen minutes of the key being minted is claimed without it. The case the proof
|
||||
exists for is unchanged: a `jarvis-generated` Secret that outlives its database still carries its
|
||||
old date, and the claim still asks for the key.
|
||||
|
||||
**The Job now binds its date to its key**, with a fingerprint in the same value
|
||||
(`<date> sha256:<16 hex>`), and on this stack the api counts a date only when it is bound to the key
|
||||
in use. The api merges `jarvis-generated` and `jarvis-secrets` name by name, so a key you put in
|
||||
`jarvis-secrets` yourself would otherwise meet the date the Job minted for another key; a key
|
||||
restored before its database would then read as brand new, and its first administrator would be
|
||||
free. A date without the fingerprint, or bound to another key, is ignored, and the claim asks for
|
||||
the key as before. To have a first install on the manual path claimed without it, bind the date as
|
||||
`01-secret.example.yaml` shows. A date more than five minutes in the future is refused too.
|
||||
|
||||
**If you apply the files one by one, move `01-secret-job.yaml` to the same api digest as the rest.**
|
||||
A Job on an older image writes a date without the fingerprint, and a first install keeps asking for
|
||||
the key. Kustomize and Argo CD already give the Job the api's image, from the one `images:` entry.
|
||||
|
||||
**An instance deployed before this version and not installed yet will still ask.** Its
|
||||
`jarvis-generated` was minted by an older Job, whose date carries no fingerprint and does not count.
|
||||
Give the key's last characters. The key is in `jarvis-secrets` if you set
|
||||
`VAULT_MASTER_KEY` there, and in `jarvis-generated` otherwise:
|
||||
`kubectl -n <namespace> get secret jarvis-generated -o jsonpath='{.data.VAULT_MASTER_KEY}' | base64 -d`.
|
||||
|
||||
**The published Compose stack is not affected.** There the key is a file, and its age was already read
|
||||
from it.
|
||||
|
||||
## 2026-09-25 — The licence report counts operators, and the paid plans are Business and MSP `api 0.114.0` `web 0.139.0`
|
||||
|
||||
**The licence report sends one more number.** Next to organizations, enabled users, agents and assets,
|
||||
|
||||
@@ -44,18 +44,30 @@ stringData:
|
||||
JWT_ACCESS_SECRET: "REPLACE_ME"
|
||||
JWT_REFRESH_SECRET: "REPLACE_ME"
|
||||
|
||||
# WHEN THE VAULT KEY WAS CREATED, as an ISO 8601 timestamp or epoch seconds.
|
||||
# WHEN THE VAULT KEY WAS CREATED, bound to that key. Manual path only: if `01-secret-job.yaml`
|
||||
# mints your key, delete this line with the other `VAULT_*` ones — left here, even as
|
||||
# "REPLACE_ME", it overrides the value the Job wrote beside its key.
|
||||
#
|
||||
# Set it when you mint the key, and never move it afterwards. On Compose this is inferred from the
|
||||
# key file's modification time, which is a good answer on a volume nobody rewrites. Here it is a
|
||||
# wrong one: the kubelet writes a projected Secret into every pod at start, so the mtime is the
|
||||
# POD's age and every pod reports a key created seconds ago however old it is.
|
||||
# The value is the moment you generate the key and the key's fingerprint, one space apart:
|
||||
#
|
||||
# FP=$(printf %s "$VAULT_MASTER_KEY" | sha256sum | cut -c1-16)
|
||||
# echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) sha256:$FP"
|
||||
#
|
||||
# Restoring a key you backed up? Restore the value saved with it, word for word, and never compute
|
||||
# a new one: today's date bound to an old key is the one mistake this line can make dangerous. A
|
||||
# date without the fingerprint, or bound to another key, is ignored.
|
||||
#
|
||||
# On Compose this is inferred from the key file's modification time. Here there is no file: the
|
||||
# api receives the key through `envFrom`, so this line is the only evidence of the key's age.
|
||||
# (Mounting the Secret as a file would not do better — the kubelet writes it into every pod at
|
||||
# start, so its mtime is the POD's age.)
|
||||
#
|
||||
# What that number decides is narrow and serious. An instance with no accounts can have its first
|
||||
# super-admin claimed by whoever reaches the install screen — unless the vault key is old, which
|
||||
# means the database has gone missing from an instance that already existed (a bad restore, a PVC
|
||||
# pointed at the wrong volume) and the claim needs proof of possession instead. Without this line,
|
||||
# that check reads "brand new instance" on exactly the estate it exists to protect.
|
||||
# pointed at the wrong volume) and the claim needs proof of possession instead. Without this line
|
||||
# the age is unknown and the claim always asks for the key, which costs you nothing but its last
|
||||
# characters.
|
||||
VAULT_KEY_CREATED_AT: "REPLACE_ME"
|
||||
|
||||
# `connection_limit` is not optional here, and it is the reason this URL is in the Secret rather
|
||||
|
||||
+13
-1
@@ -280,11 +280,23 @@ outcome. Run it again.
|
||||
## Backups
|
||||
|
||||
Everything in [the main README](../README.md#backups-and-restoring-one) applies, with one
|
||||
substitution: the vault key is not in a Docker volume, it is in the `jarvis-generated` Secret.
|
||||
substitution: the vault key is not in a Docker volume, it is in a Secret — `jarvis-secrets` if you
|
||||
set `VAULT_MASTER_KEY` there yourself, `jarvis-generated` otherwise. The kustomization mints
|
||||
`jarvis-generated` either way, so on the manual path it holds a key nothing uses: back up the one
|
||||
the api reads.
|
||||
|
||||
```sh
|
||||
kubectl -n jarvis get secret jarvis-generated -o jsonpath='{.data.VAULT_MASTER_KEY}' | base64 -d
|
||||
kubectl -n jarvis get secret jarvis-generated -o jsonpath='{.data.VAULT_KEY_CREATED_AT}' | base64 -d
|
||||
```
|
||||
|
||||
Back that up somewhere that is not this cluster, and not alongside your database dump. A database
|
||||
backup that travelled with its own key is a backup that decrypts itself.
|
||||
|
||||
The second line is when the key was created, bound to that key by its fingerprint — on a Secret
|
||||
minted from api 0.114.1 on. An older one carries a bare date, which the api ignores here, so a
|
||||
restored copy of it changes nothing and the claim asks for the key. If you ever restore the key by
|
||||
hand into `jarvis-secrets`, restore that value beside it word for word, or leave it out — never
|
||||
compute a new one. It is what tells the install screen whether an empty database is a
|
||||
first install or a lost one, and an old key dated today makes the first administrator free to
|
||||
whoever reaches the address before your dump is loaded.
|
||||
|
||||
Reference in New Issue
Block a user