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
74 changes: 74 additions & 0 deletions CHANGELOG.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ change ni l'un ni l'autre a sa place dans `git log`.

### Ajouté

- **Un mot pour la forme que le vocabulaire ne savait pas décrire :
`capabilities.firewall_public_when_joined`, et `/_feint/health` passe au
schéma 7 (#548).** Un consommateur qui lisait `capabilities.firewall: true`,
`enforced.firewall` et `firewall_public_only: false` en concluait que le seul
cas non couvert était la machine sans réseau privé. C'était faux, et le
correctif ci-dessous est la raison d'être du champ : une machine qui rejoint
un réseau émulé se retrouve désormais avec son adresse publiée sur
l'interface qui porte ses jeux de règles, et c'est une affirmation
différente de celle que la version 5 avait ajoutée. Les deux sont vraies en
même temps et les deux sont nécessaires : l'ancien champ reste `false`, parce
qu'une machine dont la seule interface est routée n'a nulle part où déplacer
une adresse. Le nouveau parle du runtime et non de chaque pack : un pack qui
ne nomme aucun réseau émulé pour ses adresses publiques les garde sur une NIC
routée, ce que fait Exoscale à dessein.

- **Les stacks d'exemple sont appliquées à de vraies machines chaque nuit, et
la porte qui le fait dit combien de fois (#504).** `conformance:functional`
est la seule chose ici qui applique `examples/stacks/` contre un runtime, et
Expand All @@ -38,6 +53,65 @@ change ni l'un ni l'autre a sa place dans `git log`.

### Corrigé

- **Un serveur créé avec son IP publique ne garde plus une interface non
filtrée à côté de sa filtrée (#548).** Créé *avec* un `ip_id`, un serveur
Scaleway démarre en ne portant que cette adresse : le pilote lui donne donc
une NIC routée (#202), une interface sur laquelle Incus refuse toute option
de sécurité (#337). La NIC privée arrivait ensuite, prenait le jeu de règles,
et laissait l'adresse publiée sur l'interface nue. Mesuré le 2026-08-27 sur
`examples/stacks/scaleway`, puis reproduit depuis la seule API le 2026-08-28
dans **les deux** modes du pilote : un port que la politique `drop` du groupe
n'ouvre jamais répondait depuis la station, avec un écouteur prouvé à
l'intérieur de la machine et un port nu comme contrôle négatif.

L'adresse est déplacée désormais. `RouteAddress` la retire du device routé
sans retirer le device (les deux autres remèdes ont été essayés et refusés,
et `docs/limits.md` conserve les deux : l'uplink ne peut pas prendre le `/32`
tant que la NIC routée détient la route hôte, et retirer le device démasque
l'`eth0` du profil sur le pont de l'opérateur), puis la confie à l'interface
que le pack a nommée, celle qui porte les jeux de règles. Après le
déplacement, dans les deux modes : 443 ouvert parce qu'une règle l'ouvre, le
port qu'aucune règle ne nomme fermé alors que son écouteur tourne toujours,
et le port nu comme contrôle. Le device routé reste sur l'instance sans
adresse, et une NIC routée qui ne porte rien n'est plus signalée comme une
échappée.

- **Ce sur quoi une machine répond se lit, ne se devine pas, et une machine
redémarrée retrouve son interface (#548).** `Inspect` répondait une adresse,
la première de l'interface au nom le plus bas, et la couche du dessus
décidait ensuite de quelle *espèce* elle était d'après le bloc public déclaré
par le pack. Les deux ne coïncidaient que tant qu'une NIC routée triait avant
une NIC managée, ce à quoi le déplacement ci-dessus met fin : après lui, le
runtime répond `{"eth0":[],"eth1":["10.199.0.2","203.0.113.2"]}`, et
l'ancienne lecture aurait publié l'adresse privée là où elle publiait la
publique, pour les trois packs d'un coup puisque `Binding` est la couche
partagée et que le pack Exoscale la lit. Le pilote rapporte toutes les
adresses qu'il a vues et ne tranche rien ; `Reconciler.PublicAddressOf` et
`PrivateAddressOf` choisissent dans cet ensemble d'après le bloc, qui est la
déclaration du pack. Rien ne change pour un client, et une entrée qu'un
snapshot restauré aurait posée là sans être une adresse est écartée au lieu
d'être publiée : seule la moitié publique passait jusqu'ici par un parseur.

La moitié « redémarrage » a été mesurée le même jour et devait être corrigée
pour que le déplacement survive à un reboot : une NIC attachée à une machine
qui tourne est configurée dans l'invité par le pilote, rien dans l'invité ne
s'en souvient au redémarrage, et la machine revenait sans aucune adresse sur
cette interface, l'attente de quatre-vingt-dix secondes du pilote abandonnant
sur un bail que personne n'offre, dans les deux modes. Tant que l'adresse
publiée voyageait sur sa propre NIC routée, cela ne coûtait que les routes
vers les sous-réseaux appairés (#549) ; dès qu'elle vit sur cette interface,
cela coûte l'adresse elle-même. Le chemin de démarrage restitue donc ce que
le device réserve avant d'attendre.

Et l'enregistrement laissé par un démarrage est relu à la fin du rejeu, parce
qu'il était écrit avant que le rejeu n'installe les adresses promises par le
plan : la même machine redémarrée était enregistrée avec `10.199.0.2` seule,
alors que la station atteignait 203.0.113.2 dessus dans la même passe. Le
crochet de comptabilité du pack a suivi cette relecture, de juste après le
démarrage à juste après elle : il arrivait trop tôt d'exactement ces
adresses, et c'est le test Outscale sur l'adresse privée d'une Vm arrêtée qui
l'a mesuré.

- **Un arrêt propre rend toute la plomberie qu'aucun client ne peut supprimer,
et l'exécution qui fuit est celle qui rougit (#521).** La jambe incus-ovn de
`runtime-proof.yml` a échoué au portillon de l'étape suivante sur un runner
Expand Down
70 changes: 70 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ what this project is judged on: **a response shape a client can observe**, and

### Added

- **A word for the shape the vocabulary could not describe:
`capabilities.firewall_public_when_joined`, and `/_feint/health` moves to
schema 7 (#548).** A consumer holding `capabilities.firewall: true`,
`enforced.firewall` and `firewall_public_only: false` concluded that the one
uncovered case was the machine with no private network. It was not, and the
fix below is why the field exists: a machine that *does* join an emulated
network now ends up with its published address on the interface that wears
its rule sets, and that is a different claim from the one version 5 added.
Both are true at once, and both are needed: the older field is still `false`,
because a machine whose only interface is routed has nowhere to move an
address to. The new one is a claim about the runtime and not about every
pack — a pack that names no emulated network for its public addresses keeps
them on a routed NIC, which Exoscale does on purpose.

- **The example stacks are applied to real machines every night, and the gate
that does it says how many times (#504).** `conformance:functional` is the
only thing here that applies `examples/stacks/` against a runtime, and
Expand Down Expand Up @@ -53,6 +67,62 @@ what this project is judged on: **a response shape a client can observe**, and

### Fixed

- **A server created with its public IP no longer keeps an unfiltered
interface beside its filtered one (#548).** Created *with* an `ip_id`, a
Scaleway server boots carrying only that address, so the driver gives it a
routed NIC (#202) — an interface Incus accepts no security option on at all
(#337). The private NIC arrived afterwards, took the rule set, and left the
published address on the bare one: measured 2026-08-27 on
`examples/stacks/scaleway` and reproduced from the API alone on 2026-08-28 in
**both** driver modes, a port the group's `drop` default never opened
answered from the station, with a listener proved inside the machine and a
bare port as the negative control.

The address moves now. `RouteAddress` releases it from the routed device
without removing the device — the two other remedies were tried and refused,
and `docs/limits.md` keeps both: the uplink cannot take the `/32` while the
routed NIC owns the host route, and removing the device unmasks the
profile's `eth0` on the operator's own bridge — then hands it to the
interface the pack named, which wears the rule sets. After it, in both modes:
443 open because a rule opens it, the port no rule names closed with its
listener still running, and the bare port as the control. The routed device
stays on the instance with no address, and a routed NIC that carries nothing
is no longer reported as an escape.

- **What a machine answers on is read, not guessed, and a restarted machine
gets its interface back (#548).** `Inspect` used to answer one address — the
first of the lowest-named interface — and the layer above then decided what
*kind* of address that was from the pack's declared public block. The two
agreed only while a routed NIC sorted before a managed one, which is exactly
what the move above ends: after it the runtime answers
`{"eth0":[],"eth1":["10.199.0.2","203.0.113.2"]}`, and the old reading would
have published the private address where it published the public one — for
three packs at once, since `Binding` is the shared layer and the Exoscale
pack reads it. The driver reports every address it saw and settles nothing;
`Reconciler.PublicAddressOf` and `PrivateAddressOf` pick out of that set by
the block, which is the pack's own declaration. Nothing a client sees
changes, and an entry a restored snapshot put there that is not an address is
now dropped rather than published — only the public half ever went through a
parser.

The restart half was measured the same day and had to be fixed for the move
to survive one: a NIC attached to a running machine is configured inside the
guest by the driver, nothing in the guest remembers that across a reboot, and
the machine came back with no address on that interface at all — the driver's
own ninety-second wait giving up on a lease nobody offers, in both modes.
While the published address rode a routed NIC of its own that cost only the
routes to the peered subnets (#549); once it lives on that interface it costs
the address itself. The start path restores what the device reserves before
it waits.

And the record a boot leaves is re-read at the end of the replay, because it
was written before the replay installed the addresses the plan promised: the
same rebooted machine recorded `10.199.0.2` alone while the station reached
203.0.113.2 on it in the same pass. The pack's own bookkeeping hook moved
with that read, from straight after the start to just after it — it ran early
by exactly those addresses, and the Outscale test that keeps a stopped Vm's
private address is what measured it.

- **A graceful exit gives back every piece of host plumbing no client can
delete, and a run that leaks is the run that goes red (#521).** The incus-ovn
leg of `runtime-proof.yml` failed at the doorstep of the next step on a
Expand Down
4 changes: 2 additions & 2 deletions docs/limits-acks.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
"The catalogue is a whitelist, and its values are measured": "2026-08-27",
"The contracts do not guarantee the same thing": "2026-08-27",
"The cost of DNS/TLS interception, measured (#76)": "2026-08-27",
"The firewall enforces, within stated bounds": "2026-08-27",
"The firewall enforces, within stated bounds": "2026-08-28",
"The per-parameter half: 18 Scaleway list operations, 72 parameters, each served or refused (#277)": "2026-08-27",
"The station reaches an OVN private address only via the network's router, and the posted uplink routes do not go there (#496)": "2026-08-27",
"The three packs hand their security groups to the runtime, within two measured bounds": "2026-08-27",
"The three packs hand their security groups to the runtime, within two measured bounds": "2026-08-28",
"Two Outscale filters reach the API only as a payload, and that is `octl`'s gap": "2026-08-27",
"What survives a dead emulator, in one table": "2026-08-27",
"`feint images resolve` can print a `FEINT_BOOT_IMAGES` line that cannot boot (#476)": "2026-08-27",
Expand Down
Loading
Loading