diff --git a/.github/actions/deploy-prod/action.yml b/.github/actions/deploy-prod/action.yml index 11670a98e..1576cabd1 100644 --- a/.github/actions/deploy-prod/action.yml +++ b/.github/actions/deploy-prod/action.yml @@ -66,7 +66,7 @@ runs: shell: bash env: # renovate: datasource=github-releases depName=devantler-tech/ksail extractVersion=^v(?.+)$ - KSAIL_VERSION: "7.183.1" + KSAIL_VERSION: "7.184.1" GITHUB_TOKEN: ${{ github.token }} run: | .github/scripts/setup-ksail.sh diff --git a/.github/scripts/setup-talosctl.sh b/.github/scripts/setup-talosctl.sh index b5d2108cc..9a2f78020 100755 --- a/.github/scripts/setup-talosctl.sh +++ b/.github/scripts/setup-talosctl.sh @@ -20,10 +20,10 @@ set -euo pipefail # inspects a single node, which reads as a fleet-wide fault rather than a stale # pin. # renovate: datasource=github-releases depName=siderolabs/talos extractVersion=^v(?.+)$ -TALOS_VERSION="1.13.9" +TALOS_VERSION="1.14.0" # SHA-256 of talosctl-linux-amd64 for the TALOS_VERSION above, from that # release's sha256sum.txt. UPDATE BOTH TOGETHER. -TALOSCTL_SHA256="7e1d4b7d5846964bdcf63a794e3c8161bb6ef2983d5ace58ea5322f3bf32a27e" +TALOSCTL_SHA256="2c147c4a99d124c95bd5c190fe054e0b3c93495f2243fd652ebd423adb8377c7" asset_name="talosctl-linux-amd64" release_base="https://github.com/siderolabs/talos/releases/download/v${TALOS_VERSION}" diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 9c706a597..353168c7d 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -170,7 +170,7 @@ jobs: shell: bash env: # renovate: datasource=github-releases depName=devantler-tech/ksail extractVersion=^v(?.+)$ - KSAIL_VERSION: "7.183.1" + KSAIL_VERSION: "7.184.1" GITHUB_TOKEN: ${{ github.token }} run: .github/scripts/setup-ksail.sh diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 41bf40a14..80d3112a2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -796,7 +796,7 @@ jobs: shell: bash env: # renovate: datasource=github-releases depName=devantler-tech/ksail extractVersion=^v(?.+)$ - KSAIL_VERSION: "7.183.1" + KSAIL_VERSION: "7.184.1" GITHUB_TOKEN: ${{ github.token }} run: .github/scripts/setup-ksail.sh @@ -1524,7 +1524,7 @@ jobs: shell: bash env: # renovate: datasource=github-releases depName=devantler-tech/ksail extractVersion=^v(?.+)$ - KSAIL_VERSION: "7.183.1" + KSAIL_VERSION: "7.184.1" GITHUB_TOKEN: ${{ github.token }} run: | .github/scripts/setup-ksail.sh @@ -1947,6 +1947,14 @@ jobs: patches=() for f in "${dir}"/cluster/*.yaml "${dir}/${role}"/*.yaml; do [ -f "${f}" ] || continue + # KSail 7.184.1 consumes the AppArmor patch before rendering: + # it folds install.extraKernelArgs into the Image Factory + # schematic, removes the deprecated field, and pins the UKI + # command line. Raw talosctl has no schematic stage, so applying + # that input patch directly would validate an intermediate state. + if [ "${f}" = "talos/cluster/enable-apparmor.yaml" ]; then + continue + fi expanded="${out}/$(echo "${f}" | tr '/' '_')" # Allowlist-scoped substitution: only the pre-checked vars are # expanded, so an unexpected placeholder shape survives for the @@ -1968,6 +1976,44 @@ jobs: cp "${base}" "${out}/${role}-patched.yaml" fi talosctl validate -c "${out}/${role}-patched.yaml" -m "${mode}" >/dev/null + + # Validation proves the document set is accepted; these checks + # prove the migration preserved the platform-specific behavior. + local rendered="${out}/${role}-patched.yaml" + if grep -qE '^kind: (KubeFlannelCNIConfig|KubeProxyConfig)$' "${rendered}"; then + echo "::error::${dir}/${role}: default CNI or kube-proxy remained enabled" + exit 1 + fi + grep -qF 'defaultRuntimeSeccompProfileEnabled: true' "${rendered}" + grep -qF 'disableManifestsDirectory: true' "${rendered}" + if [ "${dir}" = "talos" ]; then + grep -qF 'image: ghcr.io/siderolabs/kubelet:v1.36.4' "${rendered}" + fi + if [ "${dir}/${role}" = "talos/control-planes" ]; then + grep -qF 'url: https://dex.platform.devantler.tech' "${rendered}" + grep -qF 'audit-log-maxbackup: "3"' "${rendered}" + if grep -qF 'secure: false' "${rendered}"; then + echo "::error::${dir}/${role}: Longhorn mount exception leaked onto control planes" + exit 1 + fi + elif [ "${dir}/${role}" = "talos/workers" ]; then + grep -qF 'destination: /var/lib/longhorn' "${rendered}" + grep -qF 'node.longhorn.io/create-default-disk: "true"' "${rendered}" + grep -qF 'secure: false' "${rendered}" + if grep -qF 'url: https://dex.platform.devantler.tech' "${rendered}"; then + echo "::error::${dir}/${role}: control-plane OIDC config leaked onto workers" + exit 1 + fi + elif [ "${dir}/${role}" = "talos-local/control-planes" ]; then + grep -qF 'url: https://dex.platform.lan' "${rendered}" + if grep -qF 'node-role.kubernetes.io/control-plane: NoSchedule' "${rendered}"; then + echo "::error::${dir}/${role}: local control plane remained unschedulable" + exit 1 + fi + elif grep -qF 'url: https://dex.platform.lan' "${rendered}"; then + echo "::error::${dir}/${role}: control-plane OIDC config leaked onto workers" + exit 1 + fi echo "${dir}/${role}: rendered + validated (${mode})" done echo "::endgroup::" @@ -2172,7 +2218,7 @@ jobs: shell: bash env: # renovate: datasource=github-releases depName=devantler-tech/ksail extractVersion=^v(?.+)$ - KSAIL_VERSION: "7.183.1" + KSAIL_VERSION: "7.184.1" GITHUB_TOKEN: ${{ github.token }} run: .github/scripts/setup-ksail.sh diff --git a/.github/workflows/dr-rebuild.yaml b/.github/workflows/dr-rebuild.yaml index 3a6d7008d..27a19a51f 100644 --- a/.github/workflows/dr-rebuild.yaml +++ b/.github/workflows/dr-rebuild.yaml @@ -173,7 +173,7 @@ jobs: # deploy-prod action; renovate keeps the pin current in all places. env: # renovate: datasource=github-releases depName=devantler-tech/ksail extractVersion=^v(?.+)$ - KSAIL_VERSION: "7.183.1" + KSAIL_VERSION: "7.184.1" GITHUB_TOKEN: ${{ github.token }} run: | .github/scripts/setup-ksail.sh diff --git a/.github/workflows/prove-scoped-package-access.yaml b/.github/workflows/prove-scoped-package-access.yaml index 8fc830ab3..e87d60e31 100644 --- a/.github/workflows/prove-scoped-package-access.yaml +++ b/.github/workflows/prove-scoped-package-access.yaml @@ -42,7 +42,7 @@ jobs: - name: Setup KSail for the existing encrypted pull credential env: # renovate: datasource=github-releases depName=devantler-tech/ksail extractVersion=^v(?.+)$ - KSAIL_VERSION: "7.183.1" + KSAIL_VERSION: "7.184.1" GITHUB_TOKEN: ${{ github.token }} run: .github/scripts/setup-ksail.sh diff --git a/.github/workflows/validate-image-verifier-liveness.yaml b/.github/workflows/validate-image-verifier-liveness.yaml index 40b8c77fc..9e3bfd8f6 100644 --- a/.github/workflows/validate-image-verifier-liveness.yaml +++ b/.github/workflows/validate-image-verifier-liveness.yaml @@ -148,7 +148,7 @@ jobs: shell: bash env: # renovate: datasource=github-releases depName=devantler-tech/ksail extractVersion=^v(?.+)$ - KSAIL_VERSION: "7.183.1" + KSAIL_VERSION: "7.184.1" GITHUB_TOKEN: ${{ github.token }} run: .github/scripts/setup-ksail.sh diff --git a/.github/workflows/validate-main.yaml b/.github/workflows/validate-main.yaml index 08acc1ea8..0c10a121e 100644 --- a/.github/workflows/validate-main.yaml +++ b/.github/workflows/validate-main.yaml @@ -80,7 +80,7 @@ jobs: shell: bash env: # renovate: datasource=github-releases depName=devantler-tech/ksail extractVersion=^v(?.+)$ - KSAIL_VERSION: "7.183.1" + KSAIL_VERSION: "7.184.1" GITHUB_TOKEN: ${{ github.token }} run: .github/scripts/setup-ksail.sh @@ -252,7 +252,7 @@ jobs: shell: bash env: # renovate: datasource=github-releases depName=devantler-tech/ksail extractVersion=^v(?.+)$ - KSAIL_VERSION: "7.183.1" + KSAIL_VERSION: "7.184.1" GITHUB_TOKEN: ${{ github.token }} run: | .github/scripts/setup-ksail.sh diff --git a/AGENTS.md b/AGENTS.md index 32ac18497..c0a7d92a5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -375,7 +375,9 @@ The platform uses a hierarchical kustomization structure: **base** configuration - **Workaround:** fork the repository and use your own Age keys; re-encrypt every `*.enc.yaml` with your key. ### CNI Configuration -- The Talos cluster starts with its default CNI disabled (via `talos-local/cluster/disable-default-cni-and-kube-proxy.yaml`). +- The Talos cluster starts with its default CNI and kube-proxy disabled (via + `talos-local/cluster/disable-default-cni.yaml` and + `talos-local/cluster/disable-kube-proxy.yaml`). - Nodes stay `NotReady` until Cilium is installed by KSail. - This is expected — KSail handles CNI installation automatically. diff --git a/docs/dr/alerting.md b/docs/dr/alerting.md index c028b0048..6c95a5b92 100644 --- a/docs/dr/alerting.md +++ b/docs/dr/alerting.md @@ -134,7 +134,7 @@ stays quiet by design, exactly as the old Alertmanager did. capability against Coroot: it tails `/var/log/audit/kube/audit.log` and ships it to the Coroot OTLP logs endpoint as the `kube-apiserver-audit` application. Retention in Coroot follows the Coroot CR's `logsTTL` (3d in the base, 7d in - prod); the on-node file backend (`talos/cluster/enable-audit-logging.yaml`, + prod); the on-node file backend (`talos/control-planes/enable-audit-logging.yaml`, 30-day rotation) remains the resilient primary for forensics beyond that window. diff --git a/docs/dr/runbook.md b/docs/dr/runbook.md index 4ca513830..dd5954d66 100644 --- a/docs/dr/runbook.md +++ b/docs/dr/runbook.md @@ -129,7 +129,12 @@ before upgrading. > ```bash > # Read the installer image KSail derived (from spec.cluster.talos.version + > # .extensions) off a healthy, already-upgraded node, then reuse it: -> IMAGE=$(talosctl --nodes get machineconfig -o jsonpath='{.spec.machine.install.image}') +> # Talos 1.14 configs carry it in the UnattendedInstallConfig document; older +> # configs keep it in the v1alpha1 document's machine.install.image. +> IMAGE=$(talosctl --nodes read /system/state/config.yaml \ +> | yq -r 'select(.kind == "UnattendedInstallConfig") | .installer.image') +> [ -n "$IMAGE" ] || IMAGE=$(talosctl --nodes read /system/state/config.yaml \ +> | yq -r 'select(.machine != null) | .machine.install.image') > talosctl --nodes upgrade --image "$IMAGE" > ``` > diff --git a/docs/runtime-security.md b/docs/runtime-security.md index 1084bd14b..f64c6b8ed 100644 --- a/docs/runtime-security.md +++ b/docs/runtime-security.md @@ -30,7 +30,7 @@ they sit inside a wider set of controls: | Network | **Cilium + Hubble** | L3–L7 flow visibility and default-deny [CiliumNetworkPolicy](../k8s/bases/infrastructure/cluster-policies/best-practices/add-default-deny.yaml) per namespace | | Runtime detection | **Kubescape node-agent** | Learned-behaviour anomaly detection, correlated with config/CVE/compliance posture | | Runtime enforcement | **Tetragon** | Declarative kernel-hook policies that **terminate the offending process** (SIGKILL) on a policy match | -| Forensics | **API audit log** ([`talos/cluster/enable-audit-logging.yaml`](../talos/cluster/enable-audit-logging.yaml)) | Who-did-what record of control-plane mutations | +| Forensics | **API audit log** ([`talos/control-planes/enable-audit-logging.yaml`](../talos/control-planes/enable-audit-logging.yaml)) | Who-did-what record of control-plane mutations | This document focuses on the two middle-to-bottom rows — the eBPF sensors. diff --git a/docs/rwx-storage.md b/docs/rwx-storage.md index e094c2791..70aadf688 100644 --- a/docs/rwx-storage.md +++ b/docs/rwx-storage.md @@ -1,15 +1,17 @@ # RWX Storage with Longhorn -Longhorn provides ReadWriteMany (RWX) storage on Hetzner clusters (prod) using dedicated Hetzner Cloud Volumes attached to each worker node. It replaces the `hcloud` StorageClass as the cluster default. +Longhorn provides ReadWriteMany (RWX) storage on Hetzner clusters (prod) using +replicated data on the worker nodes' EPHEMERAL filesystems. It replaces the +`hcloud` StorageClass as the cluster default. > **Local Docker clusters do not support Longhorn** — the iSCSI kernel modules required by Longhorn are not available in Docker-based Talos containers. ## Architecture ``` -Hetzner Cloud Volume (per worker) - └── mounted at /var/lib/longhorn (Talos machine config) - └── Longhorn engine +Worker EPHEMERAL filesystem + └── /var/lib/longhorn (shared into kubelet) + └── Longhorn engine and replicas ├── longhorn StorageClass (default — RWO + RWX) └── hcloud StorageClass (non-default — Hetzner block only) ``` @@ -45,42 +47,39 @@ fell behind a roll), read the derived installer image off a healthy node and reuse it: ```bash -# The installer image KSail derived for the cluster -IMAGE=$(talosctl --nodes get machineconfig -o jsonpath='{.spec.machine.install.image}') +# The installer image KSail derived for the cluster. Talos 1.14 configs carry it +# in the UnattendedInstallConfig document; older configs keep it in the v1alpha1 +# document's machine.install.image. +IMAGE=$(talosctl --nodes read /system/state/config.yaml \ + | yq -r 'select(.kind == "UnattendedInstallConfig") | .installer.image') +[ -n "$IMAGE" ] || IMAGE=$(talosctl --nodes read /system/state/config.yaml \ + | yq -r 'select(.machine != null) | .machine.install.image') talosctl upgrade --nodes --image "$IMAGE" --preserve ``` -### 2. Hetzner Cloud Volumes for workers +### 2. Worker EPHEMERAL storage -Each worker node needs a dedicated Hetzner Cloud Volume mounted at `/var/lib/longhorn`. +Longhorn stores replicas at `/var/lib/longhorn` on each worker's EPHEMERAL +filesystem. The `talos/workers/mount-longhorn-data.yaml` patch exposes that +host path to kubelet with the recursive shared propagation Longhorn's CSI node +plugin requires. It does not provision or mount a separate block device. -**Create and attach volumes** (repeat for each worker): +Talos 1.14 makes new EPHEMERAL volumes `noexec` by default, while Longhorn v1 +executes its engine binaries below `/var/lib/longhorn`. The worker-only +`talos/workers/allow-longhorn-execution.yaml` patch disables the secure mount +bundle for EPHEMERAL on storage workers so fresh and rebuilt nodes can start +Longhorn. Control planes retain Talos's secure mount defaults. -```bash -# List servers to find worker names -hcloud server list - -# Create a volume and attach it to a worker -# Do NOT use --format — Talos expects to partition/format the disk itself -# The volume appears as /dev/sdb on the worker -hcloud volume create \ - --name -worker--longhorn \ - --size 50 \ - --server -``` - -The Talos machine config patch (`talos/workers/mount-longhorn-data.yaml`) handles mounting `/dev/sdb` at `/var/lib/longhorn`. - -> **Verify the device path** after attaching: on Hetzner Cloud, the first attached volume -> consistently appears as `/dev/sdb`. Confirm with `talosctl disks --nodes `. -> If the volume shows a different path, update `talos/workers/mount-longhorn-data.yaml` accordingly. +Do not partition `/dev/sdb` for Longhorn: Hetzner's CSI driver dynamically +attaches Cloud Volumes at `/dev/sdb`, `/dev/sdc`, and later device names for +PVCs using the separate `hcloud` StorageClass. ## StorageClasses | StorageClass | Default | Access Modes | Backing | | --- | --- | --- | --- | -| `longhorn` | ✅ Yes | RWO, RWX | Longhorn on Hetzner volumes | +| `longhorn` | ✅ Yes | RWO, RWX | Replicated worker EPHEMERAL storage | | `hcloud` | ❌ No | RWO only | Hetzner Cloud Block Storage | ### Using RWX volumes @@ -140,20 +139,8 @@ See [Longhorn Talos Linux Support](https://longhorn.io/docs/advanced-resources/o ## Scaling -To change the Hetzner volume size: - -```bash -# Volumes can only be resized up, not down -hcloud volume resize --size 50 -``` - -After resizing, the Hetzner block device grows immediately but the XFS partition and filesystem must be expanded: - -```bash -# From a privileged pod on the worker (or via talosctl debug container): -sgdisk -e /dev/sdb # Fix GPT to use all space -growpart /dev/sdb 1 # Grow partition 1 to fill disk -xfs_growfs /var/lib/longhorn # Expand XFS filesystem online -``` - -Longhorn detects the additional space automatically once the filesystem is grown. +Longhorn capacity scales with the EPHEMERAL storage available across the +labelled baseline workers. Add or replace a storage worker with sufficient root +disk capacity, wait for replicas to become healthy, and only then drain the old +worker. Hetzner Cloud Volumes are independent PVC backends for the `hcloud` +StorageClass; resizing one does not add capacity to Longhorn. diff --git a/go.sum b/go.sum index 148806c8f..c9e6b0f13 100644 --- a/go.sum +++ b/go.sum @@ -6,41 +6,45 @@ github.com/containerd/go-cni v1.1.13 h1:eFSGOKlhoYNxpJ51KRIMHZNlg5UgocXEIEBGkY7H github.com/containerd/go-cni v1.1.13/go.mod h1:nTieub0XDRmvCZ9VI/SBG6PyqT95N4FIhxsauF1vSBI= github.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEmnuFjskwo= github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo= +github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/petermattis/goid v0.0.0-20260713124913-97594f28f5ca h1:GHSUVE4yOgX4E7kTRzpxCPbCOYkd3Kj8Dgdod30OI1E= github.com/petermattis/goid v0.0.0-20260713124913-97594f28f5ca/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25 h1:S1hI5JiKP7883xBzZAr1ydcxrKNSVNm7+3+JwjxZEsg= -github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25/go.mod h1:ZQntvDG8TkPgljxtA0R9frDoND4QORU1VXz015N5Ks4= github.com/planetscale/vtprotobuf v0.6.1-0.20260702190614-8ae5a48058df h1:x2ymdov8jnZLDPfI+VVcf/ZvzuZ2u36ieXTuQASMkWI= github.com/planetscale/vtprotobuf v0.6.1-0.20260702190614-8ae5a48058df/go.mod h1:araspv2uYKozbi5lrKaqpv1/Uei7eQSml8JCw2A3IRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sasha-s/go-deadlock v0.3.9 h1:fiaT9rB7g5sr5ddNZvlwheclN9IP86eFW9WgqlEQV+w= github.com/sasha-s/go-deadlock v0.3.9/go.mod h1:KuZj51ZFmx42q/mPaYbRk0P1xcwe697zsJKE03vD4/Y= github.com/siderolabs/crypto v0.6.5 h1:Elq5tpWP2ApZ4Y+Kg+eIDiiWbmriCPI1mjYIMwvsYkw= github.com/siderolabs/crypto v0.6.5/go.mod h1:QjVcrdJQE1sxhjHqieCgwGdlIYq/xCP2DL53Up8nbU4= github.com/siderolabs/gen v0.8.7 h1:Nu31kL0ln/facRHBfNX7zcB7w9VZ9tifXKsP4lUtWHw= github.com/siderolabs/gen v0.8.7/go.mod h1:J9IbusbES2W6QWjtSHpDV9iPGZHc978h1+KJ4oQRspQ= -github.com/siderolabs/talos/pkg/machinery v1.13.9 h1:4Qch4CVfhT3C48PfiIFXYa1LAhSWA6o7NmrEO2bsLlg= -github.com/siderolabs/talos/pkg/machinery v1.13.9/go.mod h1:Ksw0+dndjKS0t1hJlkOfzNdv8KWY0g8En1rJZ0zB9YY= github.com/siderolabs/talos/pkg/machinery v1.14.0 h1:wVaf0IZhJLaOsr8OHvYR8RTDN4Gh9qqStd904eM5RlE= github.com/siderolabs/talos/pkg/machinery v1.14.0/go.mod h1:8rhGLm+Se7X/VnTG6InEhagg7zCDUFRwfJjLcqGNw7w= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= @@ -53,22 +57,26 @@ go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRk go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= +go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 h1:qLvzZeaANDgyVOA8pyHCOStGlXn0rseXma+GQjeuv2g= +golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754 h1:k5CJw9e5ONCcA/u0webKt092npXuY+KeGh3Q8NAVf0g= google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= -google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= -google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/k8s/bases/infrastructure/audit-log-forwarder/README.md b/k8s/bases/infrastructure/audit-log-forwarder/README.md index 1ab951c7a..0c62b8382 100644 --- a/k8s/bases/infrastructure/audit-log-forwarder/README.md +++ b/k8s/bases/infrastructure/audit-log-forwarder/README.md @@ -2,7 +2,7 @@ A control-plane-only OpenTelemetry Collector DaemonSet that tails the kube-apiserver audit log (`/var/log/audit/kube/audit.log`, written by -`talos/cluster/enable-audit-logging.yaml`) and ships it to Coroot's OTLP logs +`talos/control-planes/enable-audit-logging.yaml`) and ships it to Coroot's OTLP logs endpoint, making audit events searchable in the Coroot UI alongside container logs and traces. diff --git a/k8s/bases/infrastructure/audit-log-forwarder/helm-release.yaml b/k8s/bases/infrastructure/audit-log-forwarder/helm-release.yaml index 94062d02f..1eda8d1a4 100644 --- a/k8s/bases/infrastructure/audit-log-forwarder/helm-release.yaml +++ b/k8s/bases/infrastructure/audit-log-forwarder/helm-release.yaml @@ -19,7 +19,7 @@ spec: # # Control-plane-only DaemonSet tailing the kube-apiserver audit log # (/var/log/audit/kube/audit.log, written per - # talos/cluster/enable-audit-logging.yaml) and shipping it to Coroot's OTLP + # talos/control-planes/enable-audit-logging.yaml) and shipping it to Coroot's OTLP # logs endpoint, so audit events are searchable in the Coroot UI. The on-node # file (30-day rotation) stays the resilient primary; this forwarder only # copies. Runs as root because the apiserver writes the file 0600 root-owned; diff --git a/k8s/bases/infrastructure/controllers/ksail-operator/helm-release.yaml b/k8s/bases/infrastructure/controllers/ksail-operator/helm-release.yaml index b9599c7f9..3a6416400 100644 --- a/k8s/bases/infrastructure/controllers/ksail-operator/helm-release.yaml +++ b/k8s/bases/infrastructure/controllers/ksail-operator/helm-release.yaml @@ -18,7 +18,7 @@ spec: chart: spec: chart: ksail-operator - version: 7.183.1 + version: 7.184.1 sourceRef: kind: HelmRepository name: ksail-operator diff --git a/ksail.prod.yaml b/ksail.prod.yaml index db6d306ea..7638c0075 100644 --- a/ksail.prod.yaml +++ b/ksail.prod.yaml @@ -155,7 +155,7 @@ spec: talos: # Pin Talos to match the ISO so 'ksail cluster update' doesn't attempt # an unwanted in-place upgrade to ksail's default version. - version: v1.13.9 + version: v1.14.0 # Hetzner Talos ISO (x86_64). The v1.11.2 ISO (122630) was deprecated # and removed from Hetzner on 2026-03-18. Use v1.12.4 (125127). iso: 125127 diff --git a/scripts/validate-talos-kubernetes-compatibility/main_test.go b/scripts/validate-talos-kubernetes-compatibility/main_test.go index a86d65ef0..3529a9088 100644 --- a/scripts/validate-talos-kubernetes-compatibility/main_test.go +++ b/scripts/validate-talos-kubernetes-compatibility/main_test.go @@ -20,8 +20,8 @@ func fixture(t *testing.T, body string) string { func TestVersionPairings(t *testing.T) { for _, tc := range []struct{ name, talos, kubernetes, diagnostic string }{ - {"current production", "v1.13.9", "v1.36.4", ""}, - {"in-range patch bump", "v1.13.9", "v1.36.5", ""}, + {"current production", "v1.14.0", "v1.36.4", ""}, + {"in-range patch bump", "v1.14.0", "v1.36.5", ""}, {"rejected PR 3534", "v1.13.9", "v1.37.0", "too new"}, {"too old", "v1.13.9", "v1.30.0", "too old"}, {"unknown Talos release", "v99.0.0", "v1.36.4", "not supported"}, @@ -69,6 +69,47 @@ func TestMalformedConfigurationFailsClosed(t *testing.T) { } } +// Talos 1.14's structured KubeletConfig cannot represent extraMounts, so the +// production overlay keeps the legacy kubelet representation for Longhorn. +// Deleting the generated document would otherwise make the legacy provider +// fall back to Talos's default Kubernetes version instead of the production pin. +func TestLegacyKubeletImageMatchesProductionPin(t *testing.T) { + configData, err := os.ReadFile("../../ksail.prod.yaml") + if err != nil { + t.Fatal(err) + } + var config struct { + Spec struct { + Cluster struct { + Kubernetes string `yaml:"kubernetesVersion"` + } `yaml:"cluster"` + } `yaml:"spec"` + } + if err := yaml.Unmarshal(configData, &config); err != nil { + t.Fatal(err) + } + + patchData, err := os.ReadFile("../../talos/cluster/rotate-server-certificates.yaml") + if err != nil { + t.Fatal(err) + } + var patch struct { + Machine struct { + Kubelet struct { + Image string `yaml:"image"` + } `yaml:"kubelet"` + } `yaml:"machine"` + } + if err := yaml.Unmarshal(patchData, &patch); err != nil { + t.Fatal(err) + } + + want := "ghcr.io/siderolabs/kubelet:" + config.Spec.Cluster.Kubernetes + if patch.Machine.Kubelet.Image != want { + t.Fatalf("legacy kubelet image %q does not match production Kubernetes pin %q", patch.Machine.Kubelet.Image, want) + } +} + // Removing a version input from the actual path-filter registration must not // turn a rejected pin into a skipped required job. func TestProductionPinChangesSelectTalosValidation(t *testing.T) { diff --git a/talos-local/cluster/allow-scheduling-on-control-planes.yaml b/talos-local/cluster/allow-scheduling-on-control-planes.yaml deleted file mode 100644 index 78ee123cf..000000000 --- a/talos-local/cluster/allow-scheduling-on-control-planes.yaml +++ /dev/null @@ -1,2 +0,0 @@ -cluster: - allowSchedulingOnControlPlanes: true diff --git a/talos-local/cluster/disable-default-cni-and-kube-proxy.yaml b/talos-local/cluster/disable-default-cni-and-kube-proxy.yaml deleted file mode 100644 index 2b7b65d99..000000000 --- a/talos-local/cluster/disable-default-cni-and-kube-proxy.yaml +++ /dev/null @@ -1,6 +0,0 @@ -cluster: - network: - cni: - name: none - proxy: - disabled: true diff --git a/talos-local/cluster/disable-default-cni.yaml b/talos-local/cluster/disable-default-cni.yaml new file mode 100644 index 000000000..1c3ff94e7 --- /dev/null +++ b/talos-local/cluster/disable-default-cni.yaml @@ -0,0 +1,3 @@ +apiVersion: v1alpha1 +kind: KubeFlannelCNIConfig +$patch: delete diff --git a/talos-local/cluster/disable-kube-proxy.yaml b/talos-local/cluster/disable-kube-proxy.yaml new file mode 100644 index 000000000..cf6f4f7d6 --- /dev/null +++ b/talos-local/cluster/disable-kube-proxy.yaml @@ -0,0 +1,3 @@ +apiVersion: v1alpha1 +kind: KubeProxyConfig +$patch: delete diff --git a/talos-local/cluster/enable-dex-oidc.yaml b/talos-local/cluster/enable-dex-oidc.yaml deleted file mode 100644 index 53189e5b3..000000000 --- a/talos-local/cluster/enable-dex-oidc.yaml +++ /dev/null @@ -1,60 +0,0 @@ -# OIDC configuration for the kube-apiserver. -# Enables Dex as an OIDC identity provider so that tokens issued by Dex -# are accepted as valid Kubernetes authentication credentials. -# Requires Cilium Envoy to be running on the host network (hostNetwork: true) -# so the API server can reach dex.platform.lan via 127.0.0.1. -cluster: - apiServer: - extraArgs: - oidc-issuer-url: https://dex.platform.lan - oidc-client-id: public-client - oidc-username-claim: email - oidc-username-prefix: "oidc:" - oidc-groups-claim: groups - oidc-groups-prefix: "oidc:" - oidc-ca-file: /etc/kubernetes/oidc/ca.crt - extraVolumes: - - hostPath: /var/etc/kubernetes/oidc - mountPath: /etc/kubernetes/oidc - readonly: true -machine: - network: - extraHostEntries: - - ip: 127.0.0.1 - aliases: - - dex.platform.lan - files: - - content: | - -----BEGIN CERTIFICATE----- - MIIFFzCCA3+gAwIBAgIQCQd+7ktoyMuVs7/miaxUBzANBgkqhkiG9w0BAQsFADCB - ozEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTwwOgYDVQQLDDNuaWtv - bGFpZW1pbGRhbW1ATWFjLmxvY2FsZG9tYWluIChOaWtvbGFpIEVtaWwgRGFtbSkx - QzBBBgNVBAMMOm1rY2VydCBuaWtvbGFpZW1pbGRhbW1ATWFjLmxvY2FsZG9tYWlu - IChOaWtvbGFpIEVtaWwgRGFtbSkwHhcNMjUwNTIzMTcxNzQ4WhcNMzUwNTIzMTcx - NzQ4WjCBozEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTwwOgYDVQQL - DDNuaWtvbGFpZW1pbGRhbW1ATWFjLmxvY2FsZG9tYWluIChOaWtvbGFpIEVtaWwg - RGFtbSkxQzBBBgNVBAMMOm1rY2VydCBuaWtvbGFpZW1pbGRhbW1ATWFjLmxvY2Fs - ZG9tYWluIChOaWtvbGFpIEVtaWwgRGFtbSkwggGiMA0GCSqGSIb3DQEBAQUAA4IB - jwAwggGKAoIBgQC6p2RNTwjhDdEhtfQkabSZQQnWx/Rigmo41FjlsocD2lKbtgVd - 9yzkaqwbd4vRLWKJm1cFrdl4ROoWDglCGVFBL10jSvksHXG4v1diz408T9tGeCs/ - cj+cl9G7BtfKdBUIju8Ixdf0DYX1e5FaXE18ORScR55dICjdat1Zm+ewa2vvM+Nn - /gfLD48/mHulFaYDU+NiFiA5C4kkWoRLGGcOKKaomoBkByTl9SycQLOeg2tZBfl4 - id/1rOIpupAv22DIojn+YyTxEFW9q5V+zVVGvSzjl9RTTT78A+EgHOByhqzK8ROB - mf2aNatmXflaZsRjdKTF42BD1lCb3LpsFPOTyUOahjyMaT5EIYPaEw59r9EyMPCy - wZViwoCnkQ4rtfNLHObpd8VNq1TvsHURmWITFlEjAlwU18JuM7h/pPYEPIqE1hg2 - wd5nFBg17qHIa+8Om4haANCiKf5sOw5XWXK4P1MY4gbzm2aBK2+y9yFoTJC/WTJN - /pMayauYxD6/lZkCAwEAAaNFMEMwDgYDVR0PAQH/BAQDAgIEMBIGA1UdEwEB/wQI - MAYBAf8CAQAwHQYDVR0OBBYEFIsBHrMZLcmr+aPEYGpECIrMnDAZMA0GCSqGSIb3 - DQEBCwUAA4IBgQCEFmWX0eoQwhXdctCXmyLgyf/orYpHM8+djgT9VcDvWSwcMcQj - UTTaeXoLkJUPd+Vx7XVUVkcH19L4EBeEwMA6j42m4SnjQbTrzVKNDSAOiZSUC+x8 - BytNETJKGqmDXnN9mhgjqZsi0t0Ccd/dH2nIZGMuyqr6cxqbBTDmRvi7z8h0BXz+ - q/A/swnHT+0o10HazJjPRrz0MFPx12+sQMR6XwDRE1Ak5QA2gAuVb5gVDP+nfNZ0 - Eh2Tmxw3p//VjR92K4Chx818VTO0ZRYBEzmHlJlwVjgNQCaaygouuI9TyaaMr6Na - uJo9lR4FjIF9LcSq6MwkHqH3J44sa24bsURSUAoD2LoVmyRi8SsKdGmy9dFBQ0Mh - q45AJJ95ZdSVrL+5NJTnirTUgX5CtYlizv++yQSbUJnP1k+5SoYzEEeUXPpewtFD - /SLHwemdLxkoz7XlNGUO4v149IsfxWciFb7Zdglm7agcsjidye02z/n0D7rVCfr/ - unz6rtNZ2OHYe+4= - -----END CERTIFICATE----- - permissions: 0o644 - path: /var/etc/kubernetes/oidc/ca.crt - op: create diff --git a/talos-local/cluster/map-dex-host.yaml b/talos-local/cluster/map-dex-host.yaml new file mode 100644 index 000000000..edd9026a0 --- /dev/null +++ b/talos-local/cluster/map-dex-host.yaml @@ -0,0 +1,6 @@ +# Resolve the local Dex issuer to the host-network Envoy listener. +apiVersion: v1alpha1 +kind: StaticHostConfig +name: 127.0.0.1 +hostnames: + - dex.platform.lan diff --git a/talos-local/cluster/rotate-server-certificates.yaml b/talos-local/cluster/rotate-server-certificates.yaml index ad3efa53a..e7a8ae81f 100644 --- a/talos-local/cluster/rotate-server-certificates.yaml +++ b/talos-local/cluster/rotate-server-certificates.yaml @@ -1,4 +1,6 @@ machine: kubelet: + defaultRuntimeSeccompProfileEnabled: true + disableManifestsDirectory: true extraArgs: rotate-server-certificates: "true" diff --git a/talos-local/cluster/use-legacy-kubelet-config.yaml b/talos-local/cluster/use-legacy-kubelet-config.yaml new file mode 100644 index 000000000..205fe9636 --- /dev/null +++ b/talos-local/cluster/use-legacy-kubelet-config.yaml @@ -0,0 +1,8 @@ +# Talos 1.14's KubeletConfig document cannot represent extraMounts. Use the +# same legacy kubelet representation as production so local kubelet patches do +# not conflict with the generated structured document. The local patch below +# preserves the generated document's security defaults; the kubelet image still +# follows Talos's matching Kubernetes default. +apiVersion: v1alpha1 +kind: KubeletConfig +$patch: delete diff --git a/talos-local/control-planes/allow-scheduling-on-control-planes.yaml b/talos-local/control-planes/allow-scheduling-on-control-planes.yaml new file mode 100644 index 000000000..43556bc3d --- /dev/null +++ b/talos-local/control-planes/allow-scheduling-on-control-planes.yaml @@ -0,0 +1,5 @@ +# Remove Talos's generated control-plane node document before replacing it +# with the same registration labels and no NoSchedule taint. +apiVersion: v1alpha1 +kind: KubeNodeConfig +$patch: delete diff --git a/talos-local/control-planes/configure-schedulable-control-plane.yaml b/talos-local/control-planes/configure-schedulable-control-plane.yaml new file mode 100644 index 000000000..6f3120650 --- /dev/null +++ b/talos-local/control-planes/configure-schedulable-control-plane.yaml @@ -0,0 +1,8 @@ +# Retain Talos's control-plane labels while omitting the default NoSchedule +# taint, matching the local cluster's single-node scheduling contract. +apiVersion: v1alpha1 +kind: KubeNodeConfig +nodeIP: {} +labels: + node-role.kubernetes.io/control-plane: "" + node.kubernetes.io/exclude-from-external-load-balancers: "" diff --git a/talos-local/control-planes/enable-dex-oidc.yaml b/talos-local/control-planes/enable-dex-oidc.yaml new file mode 100644 index 000000000..fe076b3a2 --- /dev/null +++ b/talos-local/control-planes/enable-dex-oidc.yaml @@ -0,0 +1,57 @@ +# Configure local Dex through Talos 1.14's structured Kubernetes +# authentication document. The development CA is embedded in the issuer so +# Talos owns the authentication-config file and kube-apiserver mount. +apiVersion: v1alpha1 +kind: KubeAuthenticationConfig +configuration: + apiVersion: apiserver.config.k8s.io/v1beta1 + kind: AuthenticationConfiguration + anonymous: + enabled: true + conditions: + - path: /livez + - path: /readyz + - path: /healthz + jwt: + - issuer: + url: "https://dex.platform.lan" + audiences: + - "public-client" + certificateAuthority: | + -----BEGIN CERTIFICATE----- + MIIFFzCCA3+gAwIBAgIQCQd+7ktoyMuVs7/miaxUBzANBgkqhkiG9w0BAQsFADCB + ozEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTwwOgYDVQQLDDNuaWtv + bGFpZW1pbGRhbW1ATWFjLmxvY2FsZG9tYWluIChOaWtvbGFpIEVtaWwgRGFtbSkx + QzBBBgNVBAMMOm1rY2VydCBuaWtvbGFpZW1pbGRhbW1ATWFjLmxvY2FsZG9tYWlu + IChOaWtvbGFpIEVtaWwgRGFtbSkwHhcNMjUwNTIzMTcxNzQ4WhcNMzUwNTIzMTcx + NzQ4WjCBozEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTwwOgYDVQQL + DDNuaWtvbGFpZW1pbGRhbW1ATWFjLmxvY2FsZG9tYWluIChOaWtvbGFpIEVtaWwg + RGFtbSkxQzBBBgNVBAMMOm1rY2VydCBuaWtvbGFpZW1pbGRhbW1ATWFjLmxvY2Fs + ZG9tYWluIChOaWtvbGFpIEVtaWwgRGFtbSkwggGiMA0GCSqGSIb3DQEBAQUAA4IB + jwAwggGKAoIBgQC6p2RNTwjhDdEhtfQkabSZQQnWx/Rigmo41FjlsocD2lKbtgVd + 9yzkaqwbd4vRLWKJm1cFrdl4ROoWDglCGVFBL10jSvksHXG4v1diz408T9tGeCs/ + cj+cl9G7BtfKdBUIju8Ixdf0DYX1e5FaXE18ORScR55dICjdat1Zm+ewa2vvM+Nn + /gfLD48/mHulFaYDU+NiFiA5C4kkWoRLGGcOKKaomoBkByTl9SycQLOeg2tZBfl4 + id/1rOIpupAv22DIojn+YyTxEFW9q5V+zVVGvSzjl9RTTT78A+EgHOByhqzK8ROB + mf2aNatmXflaZsRjdKTF42BD1lCb3LpsFPOTyUOahjyMaT5EIYPaEw59r9EyMPCy + wZViwoCnkQ4rtfNLHObpd8VNq1TvsHURmWITFlEjAlwU18JuM7h/pPYEPIqE1hg2 + wd5nFBg17qHIa+8Om4haANCiKf5sOw5XWXK4P1MY4gbzm2aBK2+y9yFoTJC/WTJN + /pMayauYxD6/lZkCAwEAAaNFMEMwDgYDVR0PAQH/BAQDAgIEMBIGA1UdEwEB/wQI + MAYBAf8CAQAwHQYDVR0OBBYEFIsBHrMZLcmr+aPEYGpECIrMnDAZMA0GCSqGSIb3 + DQEBCwUAA4IBgQCEFmWX0eoQwhXdctCXmyLgyf/orYpHM8+djgT9VcDvWSwcMcQj + UTTaeXoLkJUPd+Vx7XVUVkcH19L4EBeEwMA6j42m4SnjQbTrzVKNDSAOiZSUC+x8 + BytNETJKGqmDXnN9mhgjqZsi0t0Ccd/dH2nIZGMuyqr6cxqbBTDmRvi7z8h0BXz+ + q/A/swnHT+0o10HazJjPRrz0MFPx12+sQMR6XwDRE1Ak5QA2gAuVb5gVDP+nfNZ0 + Eh2Tmxw3p//VjR92K4Chx818VTO0ZRYBEzmHlJlwVjgNQCaaygouuI9TyaaMr6Na + uJo9lR4FjIF9LcSq6MwkHqH3J44sa24bsURSUAoD2LoVmyRi8SsKdGmy9dFBQ0Mh + q45AJJ95ZdSVrL+5NJTnirTUgX5CtYlizv++yQSbUJnP1k+5SoYzEEeUXPpewtFD + /SLHwemdLxkoz7XlNGUO4v149IsfxWciFb7Zdglm7agcsjidye02z/n0D7rVCfr/ + unz6rtNZ2OHYe+4= + -----END CERTIFICATE----- + claimMappings: + username: + claim: "email" + prefix: "oidc:" + groups: + claim: "groups" + prefix: "oidc:" diff --git a/talos/cluster/disable-default-cni-and-kube-proxy.yaml b/talos/cluster/disable-default-cni-and-kube-proxy.yaml deleted file mode 100644 index 2b7b65d99..000000000 --- a/talos/cluster/disable-default-cni-and-kube-proxy.yaml +++ /dev/null @@ -1,6 +0,0 @@ -cluster: - network: - cni: - name: none - proxy: - disabled: true diff --git a/talos/cluster/disable-default-cni.yaml b/talos/cluster/disable-default-cni.yaml new file mode 100644 index 000000000..1c3ff94e7 --- /dev/null +++ b/talos/cluster/disable-default-cni.yaml @@ -0,0 +1,3 @@ +apiVersion: v1alpha1 +kind: KubeFlannelCNIConfig +$patch: delete diff --git a/talos/cluster/disable-kube-proxy.yaml b/talos/cluster/disable-kube-proxy.yaml new file mode 100644 index 000000000..cf6f4f7d6 --- /dev/null +++ b/talos/cluster/disable-kube-proxy.yaml @@ -0,0 +1,3 @@ +apiVersion: v1alpha1 +kind: KubeProxyConfig +$patch: delete diff --git a/talos/cluster/enable-audit-logging.yaml b/talos/cluster/enable-audit-logging.yaml deleted file mode 100644 index ecf164272..000000000 --- a/talos/cluster/enable-audit-logging.yaml +++ /dev/null @@ -1,88 +0,0 @@ -# Kubernetes API server audit logging. -# -# Places an audit policy file on disk and configures the API server to -# write audit events to a log file on the EPHEMERAL partition. -# -# Policy highlights: -# - Secret access logged at Metadata (content never recorded) -# - RBAC mutations logged at RequestResponse (full detail) -# - Workload mutations logged at Request level -# - Health/metrics endpoints and watch noise suppressed -# -# Log rotation: 30 days, 3 backups, 100 MB max per file. -# -# Reference: https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/ -machine: - files: - - content: | - apiVersion: audit.k8s.io/v1 - kind: Policy - rules: - # Skip noisy health/metrics endpoints - - level: None - nonResourceURLs: - - /healthz* - - /readyz* - - /livez* - - /metrics - - /openapi/* - - # Skip watch on high-volume resources to reduce noise - - level: None - verbs: ["watch"] - resources: - - group: "" - resources: ["events", "endpoints"] - - # Secret access — always Metadata (never log secret content) - - level: Metadata - resources: - - group: "" - resources: ["secrets"] - - # RBAC mutations — full detail for forensics - - level: RequestResponse - resources: - - group: "rbac.authorization.k8s.io" - verbs: ["create", "update", "patch", "delete"] - - # Workload mutations — request body for change tracking - - level: Request - resources: - - group: "" - resources: - - pods - - services - - namespaces - - serviceaccounts - - persistentvolumeclaims - - group: "apps" - resources: - - deployments - - daemonsets - - statefulsets - - group: "batch" - resources: - - jobs - - cronjobs - verbs: ["create", "update", "patch", "delete"] - - # Catch-all — Metadata for everything else (skip RequestReceived stage) - - level: Metadata - omitStages: - - RequestReceived - permissions: 0o644 - path: /var/etc/kubernetes/audit/audit-policy.yaml - op: create -cluster: - apiServer: - extraArgs: - audit-log-path: /var/log/audit/kube/audit.log - audit-log-maxage: "30" - audit-log-maxbackup: "3" - audit-log-maxsize: "100" - audit-policy-file: /etc/kubernetes/audit/audit-policy.yaml - extraVolumes: - - hostPath: /var/etc/kubernetes/audit - mountPath: /etc/kubernetes/audit - readonly: true diff --git a/talos/cluster/enable-dex-oidc.yaml b/talos/cluster/enable-dex-oidc.yaml deleted file mode 100644 index 6c5df200a..000000000 --- a/talos/cluster/enable-dex-oidc.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# OIDC configuration for the kube-apiserver on Hetzner clusters. -# Enables Dex as an OIDC identity provider so that tokens issued by Dex -# are accepted as valid Kubernetes authentication credentials. -# -# On Hetzner, kube-apiserver reaches Dex through Cloudflare's proxy, so -# the TLS cert is publicly trusted and no custom oidc-ca-file is needed. -# If the DNS record is switched to DNS-only (grey-cloud), add the -# Cloudflare Origin ECC CA root cert via machine.files and set oidc-ca-file. -# -# NOTE: This file lives in the talos/ directory used by ksail.prod.yaml. -# The issuer URL below targets production. If a dev environment is added -# in the future (e.g. ksail.dev.yaml), create a separate talos-dev/ -# directory with a dev-specific enable-dex-oidc.yaml (issuer URL: -# https://dex.dev.platform.devantler.tech) and point the dev config's -# distributionConfig to talos-dev. -cluster: - apiServer: - extraArgs: - oidc-issuer-url: https://dex.platform.devantler.tech - oidc-client-id: public-client - oidc-username-claim: email - oidc-username-prefix: "oidc:" - oidc-groups-claim: groups - oidc-groups-prefix: "oidc:" diff --git a/talos/cluster/mark-ghcr-pull-revision.yaml b/talos/cluster/mark-ghcr-pull-revision.yaml index f9913983b..c6847c07b 100644 --- a/talos/cluster/mark-ghcr-pull-revision.yaml +++ b/talos/cluster/mark-ghcr-pull-revision.yaml @@ -9,6 +9,7 @@ # newly created/autoscaled node must not claim a successful pull before it has # rebooted containerd and proved an exact private KSail image pull. The helper # records that second marker only after both steps succeed. -machine: - nodeAnnotations: - platform.devantler.tech/ghcr-pull-desired-revision: ${GHCR_PULL_REVISION} +apiVersion: v1alpha1 +kind: KubeNodeConfig +annotations: + platform.devantler.tech/ghcr-pull-desired-revision: ${GHCR_PULL_REVISION} diff --git a/talos/cluster/rotate-server-certificates.yaml b/talos/cluster/rotate-server-certificates.yaml index ad3efa53a..9d1f34219 100644 --- a/talos/cluster/rotate-server-certificates.yaml +++ b/talos/cluster/rotate-server-certificates.yaml @@ -1,4 +1,9 @@ machine: kubelet: + # Keep this image in lockstep with spec.cluster.kubernetesVersion in + # ksail.prod.yaml while the legacy kubelet representation is required. + image: ghcr.io/siderolabs/kubelet:v1.36.4 + defaultRuntimeSeccompProfileEnabled: true + disableManifestsDirectory: true extraArgs: rotate-server-certificates: "true" diff --git a/talos/cluster/use-legacy-kubelet-config.yaml b/talos/cluster/use-legacy-kubelet-config.yaml new file mode 100644 index 000000000..d4f410e8c --- /dev/null +++ b/talos/cluster/use-legacy-kubelet-config.yaml @@ -0,0 +1,10 @@ +# Talos 1.14's KubeletConfig document cannot represent extraMounts. Keep the +# legacy kubelet section as the single source for all kubelet configuration so +# the Longhorn CSI bind mount remains available while the other kubelet tuning +# patches migrate with it. Talos accepts either representation, but rejects a +# generated KubeletConfig document alongside machine.kubelet. The legacy +# replacement below preserves the generated document's security defaults and +# pins the kubelet image to ksail.prod.yaml's Kubernetes version. +apiVersion: v1alpha1 +kind: KubeletConfig +$patch: delete diff --git a/talos/control-planes/enable-audit-logging.yaml b/talos/control-planes/enable-audit-logging.yaml new file mode 100644 index 000000000..4eea7a01a --- /dev/null +++ b/talos/control-planes/enable-audit-logging.yaml @@ -0,0 +1,61 @@ +# Kubernetes API server audit policy. Talos 1.14 owns the policy file, mount, +# log path, and the 30-day/100-MB rotation defaults for this document. +apiVersion: v1alpha1 +kind: KubeAuditPolicyConfig +configuration: + apiVersion: audit.k8s.io/v1 + kind: Policy + rules: + # Skip noisy health/metrics endpoints. + - level: None + nonResourceURLs: + - /healthz* + - /readyz* + - /livez* + - /metrics + - /openapi/* + + # Skip watch on high-volume resources to reduce noise. + - level: None + verbs: ["watch"] + resources: + - group: "" + resources: ["events", "endpoints"] + + # Secret access is metadata-only so secret content is never recorded. + - level: Metadata + resources: + - group: "" + resources: ["secrets"] + + # RBAC mutations retain full forensic detail. + - level: RequestResponse + resources: + - group: rbac.authorization.k8s.io + verbs: ["create", "update", "patch", "delete"] + + # Workload mutations retain the request body for change tracking. + - level: Request + resources: + - group: "" + resources: + - pods + - services + - namespaces + - serviceaccounts + - persistentvolumeclaims + - group: apps + resources: + - deployments + - daemonsets + - statefulsets + - group: batch + resources: + - jobs + - cronjobs + verbs: ["create", "update", "patch", "delete"] + + # Catch all other requests at metadata level. + - level: Metadata + omitStages: + - RequestReceived diff --git a/talos/control-planes/enable-dex-oidc.yaml b/talos/control-planes/enable-dex-oidc.yaml new file mode 100644 index 000000000..a2210184d --- /dev/null +++ b/talos/control-planes/enable-dex-oidc.yaml @@ -0,0 +1,25 @@ +# Configure production Dex through Talos 1.14's structured Kubernetes +# authentication document. The issuer uses a publicly trusted certificate. +apiVersion: v1alpha1 +kind: KubeAuthenticationConfig +configuration: + apiVersion: apiserver.config.k8s.io/v1beta1 + kind: AuthenticationConfiguration + anonymous: + enabled: true + conditions: + - path: /livez + - path: /readyz + - path: /healthz + jwt: + - issuer: + url: "https://dex.platform.devantler.tech" + audiences: + - "public-client" + claimMappings: + username: + claim: "email" + prefix: "oidc:" + groups: + claim: "groups" + prefix: "oidc:" diff --git a/talos/cluster/gc-terminated-pods-sooner.yaml b/talos/control-planes/gc-terminated-pods-sooner.yaml similarity index 78% rename from talos/cluster/gc-terminated-pods-sooner.yaml rename to talos/control-planes/gc-terminated-pods-sooner.yaml index 0cc5393e7..7c74a959c 100644 --- a/talos/cluster/gc-terminated-pods-sooner.yaml +++ b/talos/control-planes/gc-terminated-pods-sooner.yaml @@ -17,12 +17,13 @@ # still-useful recent Failed pods mid-roll, while keeping steady-state tombstone # clutter low. PodGC deletes oldest-first, so recent failures stay inspectable. # -# cluster.* config only takes effect on control-plane nodes (where -# kube-controller-manager runs), so this lives in talos/cluster/ alongside the -# other control-plane component tuning (see enable-audit-logging.yaml). +# KubeControllerManagerConfig configures the kube-controller-manager static pod, +# which runs only on control-plane nodes, so this lives in talos/control-planes/ +# alongside the other control-plane component tuning (see +# limit-audit-log-backups.yaml). # # Reference: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-garbage-collection -cluster: - controllerManager: - extraArgs: - terminated-pod-gc-threshold: "100" +apiVersion: v1alpha1 +kind: KubeControllerManagerConfig +extraArgs: + terminated-pod-gc-threshold: "100" diff --git a/talos/control-planes/limit-audit-log-backups.yaml b/talos/control-planes/limit-audit-log-backups.yaml new file mode 100644 index 000000000..eba705266 --- /dev/null +++ b/talos/control-planes/limit-audit-log-backups.yaml @@ -0,0 +1,6 @@ +# Retain the platform's three-file audit-log backup bound. Talos 1.14 supplies +# the policy path, log path, 30-day age, and 100-MB file-size arguments. +apiVersion: v1alpha1 +kind: KubeAPIServerConfig +extraArgs: + audit-log-maxbackup: "3" diff --git a/talos/workers/allow-longhorn-execution.yaml b/talos/workers/allow-longhorn-execution.yaml new file mode 100644 index 000000000..6358d4a67 --- /dev/null +++ b/talos/workers/allow-longhorn-execution.yaml @@ -0,0 +1,9 @@ +# Talos 1.14 mounts new EPHEMERAL volumes noexec by default. Longhorn v1 +# executes engine binaries under /var/lib/longhorn on EPHEMERAL, so fresh or +# rebuilt storage workers need the documented compatibility exception. Keep it +# worker-scoped: control planes retain Talos's secure mount defaults. +apiVersion: v1alpha1 +kind: VolumeConfig +name: EPHEMERAL +mount: + secure: false diff --git a/talos/workers/label-nodes.yaml b/talos/workers/label-nodes.yaml index 9f09532bd..00d41a676 100644 --- a/talos/workers/label-nodes.yaml +++ b/talos/workers/label-nodes.yaml @@ -1,14 +1,6 @@ -# Kubelet node-labels for Talos workers. -# -# All node-labels must be combined in this single file: Talos strategic -# merge overwrites map keys, so if multiple worker patches set -# kubelet.extraArgs.node-labels the last one wins. -# -# Labels set here are applied at kubelet registration time, so DaemonSets -# (e.g. Longhorn) can match nodes immediately on first boot. Labels set -# via machine.nodeLabels are applied later by NodeApplyController and -# would race with registration-time scheduling. -machine: - kubelet: - extraArgs: - node-labels: "node.longhorn.io/create-default-disk=true" +# Talos worker labels. KubeNodeConfig is the Talos 1.14 representation used to +# render kubelet registration, so Longhorn can match workers on first boot. +apiVersion: v1alpha1 +kind: KubeNodeConfig +labels: + node.longhorn.io/create-default-disk: "true" diff --git a/talos/workers/mount-longhorn-data.yaml b/talos/workers/mount-longhorn-data.yaml index 2c6b34aab..f488d4478 100644 --- a/talos/workers/mount-longhorn-data.yaml +++ b/talos/workers/mount-longhorn-data.yaml @@ -7,10 +7,8 @@ # /dev/sdc, etc. Partitioning /dev/sdb with Talos would destroy those # volumes and break any PVC backed by hcloud-csi. # -# NOTE: node-labels for Longhorn (node.longhorn.io/create-default-disk=true) -# are set in label-nodes.yaml. Talos strategic merge overwrites map keys, -# so all kubelet.extraArgs.node-labels values must live in a single patch -# file — see label-nodes.yaml. +# The Longhorn worker label (node.longhorn.io/create-default-disk=true) is set +# through Talos 1.14's KubeNodeConfig in label-nodes.yaml. machine: kubelet: extraMounts: