diff --git a/README.md b/README.md index dd28134..e04e6ac 100644 --- a/README.md +++ b/README.md @@ -1301,6 +1301,15 @@ ansible-playbook content.yml --check --diff # dry run: reads DNSLink and the p | lith-1 | `12D3KooWQSgVevfuy33SToBfYoCexqvvhgqYp8oGZ8r3CAesguRo` | `12D3KooWRcTH1HzbFt7UCSVaYScStt5wPr4bJo3aXHuvqPk6Gmgr` | | chic-1 | `12D3KooWQ7PRw91uNk6e27NCym8CK1v39muL8omoDJzVsub3qZbV` | `12D3KooWKWfhxDGhs7DBgiqGmhCaj4SBoSbeEN2skHLbFsLZJCVS` | +**Images.** kubo and ipfs-cluster are pinned by digest in +`roles/content/defaults/main.yml` (`content_kubo_image`, +`content_cluster_image`). A box can set its own in `host_vars//content.yml` +to run a canary while the others stay on the release; deploy it with +`-l `. sing-1 runs one now: kubo v0.43.1 with the go-libp2p inbound-slot +leak fix (see the comment there). A bootstrapper instance can do the same with +`kubo_image` in its `bootstrap_extra_instances` entry; chic-1's `bootstrap-b` +runs the same canary, with `bootstrap` on the same box as its control. + **Ports.** | Port | Listener | Exposure | diff --git a/host_vars/chic-1/bootstrap.yml b/host_vars/chic-1/bootstrap.yml index 967ed0a..e90fcaf 100644 --- a/host_vars/chic-1/bootstrap.yml +++ b/host_vars/chic-1/bootstrap.yml @@ -9,5 +9,18 @@ bootstrap_legacy_name: ny5 # Further kubo instances on this box, each with its own legacy identity. # Ports come from bootstrap_instance_table (group_vars/ipfs_nodes); the # key is vault__privkey in vault.yml. +# +# bootstrap-b runs the canary for the kubo inbound-slot leak +# (libp2p/go-libp2p#3564): the same image as sing-1's content node (see +# host_vars/sing-1/content.yml), ipfs/kubo v0.43.1 plus libp2p/go-libp2p#3568 +# and nothing else. bootstrap on this box, the same traffic on the same box, +# stays on upstream v0.43.1 as its control. Remove kubo_image once a kubo +# release has the fix, or if the canary misbehaves, and re-run +# bootstrap.yml -l chic-1. bootstrap_extra_instances: - - {name: bootstrap-b, peer_id: QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN, legacy_name: sv15} + - name: bootstrap-b + peer_id: QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN + legacy_name: sv15 + kubo_image: + name: ghcr.io/ipni/kubo + digest: sha256:44f66498beffd0136a9fe619ed109b29f8961488d6a8e7a492ccf0b4b065028c diff --git a/host_vars/sing-1/content.yml b/host_vars/sing-1/content.yml index d32ca56..8101497 100644 --- a/host_vars/sing-1/content.yml +++ b/host_vars/sing-1/content.yml @@ -5,3 +5,16 @@ # vault_content_kubo_privkey in vault.yml next to this file. content_cluster_peer_id: 12D3KooWNzxaQEZCCw7c9RANwUieUbp9em6No7KrT45L3FLbAcsZ content_kubo_peer_id: 12D3KooWDkwTCdWYVxnZCq6VYWsNjAGakCrqm7Zx3G2RSTR6dbsj + +# Canary for the kubo inbound-slot leak (libp2p/go-libp2p#3564). The fix, +# libp2p/go-libp2p#3568, is merged (da148e11) but in no release. This image is +# ipfs/kubo v0.43.1 (acc53c0) built by kubo's own Dockerfile, with go-libp2p +# replaced by the module v0.43.1 already uses (lidel/go-libp2p@9c4ee60) plus +# that five-line fix, and nothing else: same dependencies, Go and build +# settings, and the same version string (0.43.1-acc53c0) as upstream. amd64 +# only, pushed by hand on 2026-11-11. lith-1 and chic-1 stay on upstream +# v0.43.1 as the control. Remove this once a kubo release has the fix, or if +# the canary misbehaves, and re-run content.yml -l sing-1. +content_kubo_image: + name: ghcr.io/ipni/kubo + digest: sha256:44f66498beffd0136a9fe619ed109b29f8961488d6a8e7a492ccf0b4b065028c diff --git a/k8s/bootstrap/kustomization.yaml.j2 b/k8s/bootstrap/kustomization.yaml.j2 index 0bccf41..2de0b2a 100644 --- a/k8s/bootstrap/kustomization.yaml.j2 +++ b/k8s/bootstrap/kustomization.yaml.j2 @@ -16,13 +16,14 @@ labels: app.kubernetes.io/name: bootstrap app.kubernetes.io/part-of: ipfs-workers -# ipfs/kubo v0.43.1 (published 2026-09-15 00:52 UTC), multi-arch index, pinned by digest. -# Shipyard stops maintaining kubo on 2026-09-30: upgrades are deliberate, and -# go-libp2p / kubo security advisories need watching. +# Pinned by digest in roles/bootstrap/defaults/main.yml +# (bootstrap_default_kubo_image). An instance can run its own (kubo_image in +# its bootstrap_extra_instances entry): that is how one instance runs a canary +# while the others stay on the release. images: - name: kubo - newName: ipfs/kubo - digest: sha256:b293923d66e490e70ced64df42ea7a6cf7eac2740e3fb29101df18070fa7be48 + newName: {{ bootstrap_kubo_image.name }} + digest: {{ bootstrap_kubo_image.digest }} # The kubo config Secret (kubo-config) is deliberately NOT here: it holds the # node's private key and is created by roles/bootstrap from ansible-vault. diff --git a/k8s/content/kustomization.yaml b/k8s/content/kustomization.yaml deleted file mode 100644 index 4cc3e4f..0000000 --- a/k8s/content/kustomization.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -namespace: content - -resources: - - namespace.yaml - - pvc.yaml - - deployment.yaml - -labels: - - pairs: - app.kubernetes.io/name: content - app.kubernetes.io/part-of: ipfs-workers - -images: - # ipfs/kubo v0.43.1 (published 2026-09-15 00:52 UTC, the current v0.43.x; - # checked 2026-09-15), multi-arch index, pinned by digest. Same image as - # k8s/bootstrap. - - name: kubo - newName: ipfs/kubo - digest: sha256:b293923d66e490e70ced64df42ea7a6cf7eac2740e3fb29101df18070fa7be48 - # ipfs/ipfs-cluster v1.1.6 (published 2026-05-11, the latest release; - # checked 2026-09-15), multi-arch index, pinned by digest. The project - # publishes to Docker Hub as ipfs/ipfs-cluster. - - name: ipfs-cluster - newName: ipfs/ipfs-cluster - digest: sha256:a83266c524f1c0bc81d14fe3c8b46c5b83a7b2d8432fb8a50300f27d3c863dcd - -# The content-cluster Secret (cluster secret, peer identity, peer lists) is -# deliberately NOT here: it is created by roles/content from ansible-vault. diff --git a/k8s/content/kustomization.yaml.j2 b/k8s/content/kustomization.yaml.j2 new file mode 100644 index 0000000..5c92eb7 --- /dev/null +++ b/k8s/content/kustomization.yaml.j2 @@ -0,0 +1,29 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: content + +resources: + - namespace.yaml + - pvc.yaml + - deployment.yaml + +labels: + - pairs: + app.kubernetes.io/name: content + app.kubernetes.io/part-of: ipfs-workers + +images: + # Pinned by digest in roles/content/defaults/main.yml (content_kubo_image, + # content_cluster_image), where a box can override them in host_vars: that + # is how one box runs a canary kubo while the others stay on the release. + - name: kubo + newName: {{ content_kubo_image.name }} + digest: {{ content_kubo_image.digest }} + - name: ipfs-cluster + newName: {{ content_cluster_image.name }} + digest: {{ content_cluster_image.digest }} + +# The content-cluster Secret (cluster secret, peer identity, peer lists) is +# deliberately NOT here: it is created by roles/content from ansible-vault. diff --git a/roles/bootstrap/defaults/main.yml b/roles/bootstrap/defaults/main.yml index 89dd5fa..44d2bd4 100644 --- a/roles/bootstrap/defaults/main.yml +++ b/roles/bootstrap/defaults/main.yml @@ -12,6 +12,18 @@ bootstrap_namespace: bootstrap # libp2p swarm port (TCP, and UDP for QUIC / WebTransport / WebRTC-direct). bootstrap_swarm_port: 4001 +# The kubo image (k8s/bootstrap/kustomization.yaml.j2): this default, or the +# instance's own kubo_image ({name, digest}) from bootstrap_extra_instances. +# +# ipfs/kubo v0.43.1 (published 2026-09-15 00:52 UTC), multi-arch index, pinned +# by digest. Same image as content (roles/content/defaults/main.yml). Shipyard +# stops maintaining kubo on 2026-09-30: upgrades are deliberate, and go-libp2p +# / kubo security advisories need watching. +bootstrap_default_kubo_image: + name: ipfs/kubo + digest: sha256:b293923d66e490e70ced64df42ea7a6cf7eac2740e3fb29101df18070fa7be48 +bootstrap_kubo_image: "{{ bootstrap_default_kubo_image }}" + # Loopback-only RPC API (probes, metrics, `ipfs` CLI via kubectl exec). bootstrap_api_port: 5011 bootstrap_api: "/ip4/127.0.0.1/tcp/{{ bootstrap_api_port }}" diff --git a/roles/bootstrap/tasks/main.yml b/roles/bootstrap/tasks/main.yml index b31bfa0..3135cee 100644 --- a/roles/bootstrap/tasks/main.yml +++ b/roles/bootstrap/tasks/main.yml @@ -32,6 +32,7 @@ bootstrap_wss_port: "{{ bs.wss_port }}" bootstrap_wss_admin_port: "{{ bs.wss_admin_port }}" bootstrap_kubo_uid: "{{ bs.uid }}" + bootstrap_kubo_image: "{{ bs.kubo_image | default(bootstrap_default_kubo_image) }}" bootstrap_instance_peer_id: "{{ bs.peer_id }}" # vault_bootstrap_privkey, vault_bootstrap_b_privkey, ... Looked up by # name so a missing one is reported by the identity preflight, not here. diff --git a/roles/content/defaults/main.yml b/roles/content/defaults/main.yml index 41ad900..b048f2d 100644 --- a/roles/content/defaults/main.yml +++ b/roles/content/defaults/main.yml @@ -8,6 +8,22 @@ content_cluster_api_port: 9094 # loopback (REST API) content_cluster_swarm_port: 9096 # TCP, the other boxes only content_pinsvc_port: 9097 # loopback (Pinning Service API, behind Envoy) +# Images, pinned by digest (k8s/content/kustomization.yaml.j2). A box may set +# its own in host_vars to run a canary; scripts/recover-upstream-pinset.sh +# always uses these defaults. +# +# ipfs/kubo v0.43.1 (published 2026-09-15 00:52 UTC, the current v0.43.x; +# checked 2026-09-15), multi-arch index. Same image as k8s/bootstrap. +content_kubo_image: + name: ipfs/kubo + digest: sha256:b293923d66e490e70ced64df42ea7a6cf7eac2740e3fb29101df18070fa7be48 +# ipfs/ipfs-cluster v1.1.6 (published 2026-05-11, the latest release; checked +# 2026-09-15), multi-arch index. The project publishes to Docker Hub as +# ipfs/ipfs-cluster. +content_cluster_image: + name: ipfs/ipfs-cluster + digest: sha256:a83266c524f1c0bc81d14fe3c8b46c5b83a7b2d8432fb8a50300f27d3c863dcd + # --------------------------------------------------------------------------- # Remote pinning: ipfs-cluster's Pinning Service API (the API `ipfs pin remote` # speaks), served over TLS by Envoy (k8s/content-pin) on every box under one diff --git a/scripts/recover-upstream-pinset.sh b/scripts/recover-upstream-pinset.sh index 421e107..744fa01 100755 --- a/scripts/recover-upstream-pinset.sh +++ b/scripts/recover-upstream-pinset.sh @@ -113,13 +113,13 @@ capture_listing() { exit 2 fi - # The same kubo and ipfs-cluster images the content cluster runs, by digest. + # The kubo and ipfs-cluster images the content cluster runs by default, by + # digest (a box's canary override in host_vars is not used here). local images kubo_image cluster_image images=$(python3 - <<'PY' import yaml -k = yaml.safe_load(open("k8s/content/kustomization.yaml")) -imgs = {i["name"]: f'{i["newName"]}@{i["digest"]}' for i in k["images"]} -print(imgs["kubo"], imgs["ipfs-cluster"]) +d = yaml.safe_load(open("roles/content/defaults/main.yml")) +print(*(f'{d[k]["name"]}@{d[k]["digest"]}' for k in ("content_kubo_image", "content_cluster_image"))) PY ) read -r kubo_image cluster_image <<<"$images"