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
|
Every version published to a channel appears here. Nothing is written from memory afterwards: a build
|
||||||
cannot be published without its entry.
|
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`
|
## 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,
|
**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_ACCESS_SECRET: "REPLACE_ME"
|
||||||
JWT_REFRESH_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
|
# The value is the moment you generate the key and the key's fingerprint, one space apart:
|
||||||
# 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
|
# FP=$(printf %s "$VAULT_MASTER_KEY" | sha256sum | cut -c1-16)
|
||||||
# POD's age and every pod reports a key created seconds ago however old it is.
|
# 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
|
# 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
|
# 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
|
# 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,
|
# 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.
|
# 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"
|
VAULT_KEY_CREATED_AT: "REPLACE_ME"
|
||||||
|
|
||||||
# `connection_limit` is not optional here, and it is the reason this URL is in the Secret rather
|
# `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
|
## Backups
|
||||||
|
|
||||||
Everything in [the main README](../README.md#backups-and-restoring-one) applies, with one
|
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
|
```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_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
|
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.
|
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