diff --git a/.github/workflows/talos-boot.yml b/.github/workflows/talos-boot.yml index 19da7b3d..59cb2c1f 100644 --- a/.github/workflows/talos-boot.yml +++ b/.github/workflows/talos-boot.yml @@ -281,9 +281,7 @@ jobs: echo "::error::Gateway を通らない(http=$H https=$S。期待は 301 / 200)" exit 1 - # 受信の防火壁は 2026-10-31 から外してある(bootstrap/talos.md)。外から 10250 が通らないことは見ない。 - # 戻したら「外から kubelet」の検査(期待は 000)も戻す(#646) - - name: 外と LAN から届く(443 と 10250) + - name: 受信の防火壁(外から 443 は通り、10250 は通らない) timeout-minutes: 3 run: | set -euo pipefail @@ -297,6 +295,41 @@ jobs: SW=$(code -k --resolve ac.doany.io:28443:127.0.0.1 https://ac.doany.io:28443/) echo " 外から Gateway: $SW" [ "$SW" = 200 ] || fail "外から 443 が通らない($SW)" + KW=$(code -k --max-time 5 https://127.0.0.1:30250/healthz) + echo " 外から kubelet: $KW" + [ "$KW" = 000 ] || fail "外から kubelet(10250)に届いた($KW)。防火壁が効いていない" + + # **Pod からノード自身の IP の Gateway に届くか。** Pod は fj.doany.io の AAAA(ノードの公開 IPv6)で Forgejo に来る + # (deploy/forgejo/runner.yaml)。防火壁があると conntrack の invalid として捨てられ、TCP はつながるが応答が来なかった + # (2026-10-31。#652 で防火壁を外した。bootstrap/talos.md) + - name: Pod からノードの IP の Gateway に届く + timeout-minutes: 4 + run: | + set -euo pipefail + cat > /tmp/curl.yaml < /tmp/nft.yaml + P=$(yq -r 'select(.metadata.id == "ingress") | .spec.policy' /tmp/nft.yaml) + echo " nftables の ingress の既定: $P" + [ "$(echo "$P" | tr 'A-Z' 'a-z')" = drop ] || { cat /tmp/nft.yaml; fail "ingress の chain が既定で捨てる形(drop)でない"; } + # 以下は forwarding=1 のあとで見ないと意味が無い(talos.sops.yaml の accept_ra) F=$(talosctl read /proc/sys/net/ipv6/conf/bond0/forwarding) [ "$F" = 1 ] || fail "bond0 の forwarding が $F。前提が成り立っていない" diff --git a/bootstrap/gateway-notrack.yaml b/bootstrap/gateway-notrack.yaml new file mode 100644 index 00000000..05251c84 --- /dev/null +++ b/bootstrap/gateway-notrack.yaml @@ -0,0 +1,64 @@ +# Pod からノード自身の IP の 80・443 番(Gateway。hostNetwork の Cilium の Envoy)への行きのパケットを conntrack から外す。 +# render.sh が inline に載せる(受信の防火壁と同じ machine config の層。CI の talos-boot が「Pod からノードの IP の Gateway に届く」で見る)。 +# +# **無いと、受信の防火壁(talos.sops.yaml の NetworkDefaultActionConfig)が Pod からの Gateway を捨てる。** +# Envoy の待ち受けには Cilium の印(0xa00 / 0xb00)が付き、Cilium は raw の OUTPUT で cilium_host・lxc+ へ出る +# その印のパケット(= Envoy から Pod への帰り)を NOTRACK にする。conntrack には行きの SYN だけが残り、Pod の次の ACK と +# データが invalid になって、Talos の `ct state invalid drop` で捨てられる(TCP はつながるが応答が来ない)。 +# Pod は fj.doany.io の AAAA(ノードの公開 IPv6)で Forgejo に来る(../deploy/forgejo/runner.yaml)ので、Argo CD も runner も止まる。 +# 行きも NOTRACK にすれば往復とも追跡しない形で揃い、防火壁の private-tcp(10.0.0.0/8・fc00::/7)の accept に当たる。 +# 外からの 443 は物理 NIC から帰るので Cilium の NOTRACK に当たらず、ここも触らない。 +# ClusterIP の Service 経由は BPF で済むので netfilter を通らない(2026-10-31 の Z440 への移行で踏んだ。../bootstrap/talos.md) +# +# iptables は Cilium と同じもの(nft か legacy)を使う。Cilium の規則がある方を見て決める +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: gateway-notrack + namespace: kube-system +spec: + selector: + matchLabels: + app: gateway-notrack + template: + metadata: + labels: + app: gateway-notrack + spec: + hostNetwork: true + priorityClassName: system-node-critical + tolerations: + - operator: Exists + containers: + # iptables と ip6tables が入っていて、版を Cilium と揃えられるので Cilium の agent のイメージを使う + - name: notrack + image: quay.io/cilium/cilium:v1.20.4 + command: [sh, -euc] + args: + - | + pick() { + for v in nft legacy; do + if "iptables-$v-save" -t raw 2>/dev/null | grep -q CILIUM; then echo "$v"; return; fi + done + } + # Cilium が規則を入れるまで待つ + until V=$(pick) && [ -n "$V" ]; do echo "Cilium の raw の規則を待つ"; sleep 5; done + echo "iptables-$V を使う" + rule() { # $1=iptables か ip6tables $2=Pod の網 + c=$1-$V + spec="-s $2 -p tcp -m multiport --dports 80,443 -m addrtype --dst-type LOCAL -j CT --notrack" + # shellcheck disable=SC2086 + $c -t raw -C PREROUTING $spec 2>/dev/null || { $c -t raw -I PREROUTING $spec; echo "足した: $c $spec"; } + } + # Cilium が chain を作り直しても消えないよう、1 分ごとに確かめて足し直す + while :; do + rule iptables 10.42.0.0/16 + rule ip6tables fd42::/64 + sleep 60 + done + securityContext: + capabilities: + add: [NET_ADMIN, NET_RAW] + drop: [ALL] + resources: + requests: {cpu: 1m, memory: 16Mi} diff --git a/bootstrap/render.sh b/bootstrap/render.sh index bb3a327a..97f7a097 100755 --- a/bootstrap/render.sh +++ b/bootstrap/render.sh @@ -156,6 +156,12 @@ inline storage < bootstrap/storage.yaml > "$WORK/inline-storage.yaml" # CI と人の RBAC。締め出されても直せるよう ArgoCD に載せない(ArgoCD は bootstrap/ を argocd-repos.yaml しか見ない)。 inline apiserver-rbac < bootstrap/apiserver-rbac.yaml > "$WORK/inline-apiserver-rbac.yaml" +# 受信の防火壁が Pod からノードの IP の Gateway を捨てないようにする(gateway-notrack.yaml の冒頭)。防火壁と同じ層に置く +inline gateway-notrack < bootstrap/gateway-notrack.yaml > "$WORK/inline-gateway-notrack.yaml" + +# 受信の防火壁が Pod からノードの IP の Gateway を捨てないようにする(gateway-notrack.yaml の冒頭)。防火壁と同じ層に置く +inline gateway-notrack < bootstrap/gateway-notrack.yaml > "$WORK/inline-gateway-notrack.yaml" + chart argocd argocd # infisical は chart がパスワードを平文 env に焼くので ArgoCD に置けない(../docs/decisions.md)。 chart infisical infisical @@ -238,13 +244,14 @@ for n in "kube-apiserver:$K8S" "ghcr.io/siderolabs/kubelet:$K8S" \ 'name: cilium' cilium-operator KUBERNETES_SERVICE_PORT 'value: \"7445\"' 'cgroup-root: \"/sys/fs/cgroup\"' \ 'kind: GatewayClass' io.cilium/gateway-controller \ 'kind: RawVolumeConfig' 'kind: LVMVolumeGroupConfig' 'name: dm_thin_pool' \ + 'kind: NetworkDefaultActionConfig' 'name: public-tcp' 'name: private-tcp' \ 'name: lvm-localpv' lvm-localpv-node lvm-localpv-controller 'storageCapacity: false' 'doany.io/lvm-thinpool: ready' \ 'name: storage' 'name: local-path' 'provisioner: local.csi.openebs.io' 'volgroup: data' \ 'kind: VolumeSnapshotClass' 'name: lvm-thinpool' \ "external-snapshotter/$SNAP/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml" \ "external-snapshotter/$SNAP/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml" \ hubble-generate-certs \ - 'name: apiserver-rbac' 'name: cilium-drift' \ + 'name: apiserver-rbac' 'name: cilium-drift' 'name: gateway-notrack' \ "gateway-api/releases/download/$GWAPI/standard-install.yaml" \ 'name: argocd' argocd-server 'name: argocd-application-crd' manifests/crds/applicationset-crd.yaml \ 'kind: Namespace\nmetadata:\n name: argocd' \ diff --git a/bootstrap/talos.sops.yaml b/bootstrap/talos.sops.yaml index e28d1e88..41d4454e 100644 --- a/bootstrap/talos.sops.yaml +++ b/bootstrap/talos.sops.yaml @@ -537,6 +537,161 @@ sops: mac_only_encrypted: true version: 3.13.3 --- +# ホスト(Talos の API・etcd・kubelet・Cilium・Gateway の Envoy など)が受けるものの防火壁(Ingress Firewall)。 +# ルーターは DMZ と IPv6 ホスト公開で全部のポートをノードに流すので(../docs/router.md)、外に出すものはここで決める。 +# 既定は捨て、下の NetworkRuleConfig に当たるものだけ通す。lo・確立済みの戻り・ICMP / ICMPv6(5 個/秒まで)・ +# Pod と Service の網どうしは Talos が自分で通す。効くのはホスト宛て(input)だけで、hostPort は Cilium が Pod へ +# 転送するのでここを通らない(Pod の手前は Cilium のポリシー。53 番は ../deploy/adguardhome/network-policy.yaml)。 +# 間違えると Talos の API ごと締め出されるので、当てるときは apply-config --mode=try(talos.md「上げ方 / 当て直し方」)。 +apiVersion: v1alpha1 +kind: NetworkDefaultActionConfig +ingress: block +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIQnpITHlrS0lxZkZXb3dy + UmNMYUhFamtsR2Rpb3BTMXozYjQwQkoxOEhFClFOeDRQV2tTYTBlL09RUS9oMUZG + ZHZwNXVnYzA4dkIyME0rbnc0Zm44SEUKLS0tIDhTNmNMU1ZVeGEwbzltTDdpVVRP + M0FiWEJ2MHIzRkU2WHRxWlo3N0FsNlEKgYVZr+vTicTztpwMQ7oj/yLEk12f/JZX + uMTe29EqIoB7gE/SCG9z+qoLXd4NHrA1lfrLjTDSCYNEhGiDyMhIMQ== + -----END AGE ENCRYPTED FILE----- + recipient: age1sgnnkn67wpt4wgjsff4c36w3cmjkk557ng504e4aczx2lzls9y6ssy0x9r + encrypted_regex: ^password$ + lastmodified: "2026-10-30T16:40:35Z" + mac: ENC[AES256_GCM,data:HeBfzNkyaDPxwfMepe0/4U5g0tAUyMf9AZMYly5La46tj8Ap5K4z9jp3tcOR9tADq1nCBhgtIKDiLmrNqOOWLShV1jDVM35hpBDHMpYuJjAGish8pBZUb3TetvSa1fnzOE24JrCZxpaK69q5V9akQzKIlhM/qgRhU7jFqPhyDT8=,iv:8QW9fMoDnME0qsE/TMcpi3zKgl+Oy+9mN8jzRY7XnN4=,tag:gOxsxLzsRPBx8iQjY5RW/A==,type:str] + mac_only_encrypted: true + version: 3.13.3 +--- +# 外から要るもの(TCP)。ホストが受けるのは 80 / 443(Gateway の Envoy。hostNetwork)と 6443(ks.doany.io と +# cilium-drift.yml)。853(DoT)・3129(3proxy)・8443(LiveKit)は hostPort でここを通らないが、通る形に変わっても +# 落ちないよう書いておく。足すときは ../docs/router.md の表も。 +apiVersion: v1alpha1 +kind: NetworkRuleConfig +name: public-tcp +portSelector: + ports: + - 80 + - 443 + - 853 + - 3129 + - 6443 + - 8443 + protocol: tcp +ingress: + - subnet: 0.0.0.0/0 + - subnet: ::/0 +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIQnpITHlrS0lxZkZXb3dy + UmNMYUhFamtsR2Rpb3BTMXozYjQwQkoxOEhFClFOeDRQV2tTYTBlL09RUS9oMUZG + ZHZwNXVnYzA4dkIyME0rbnc0Zm44SEUKLS0tIDhTNmNMU1ZVeGEwbzltTDdpVVRP + M0FiWEJ2MHIzRkU2WHRxWlo3N0FsNlEKgYVZr+vTicTztpwMQ7oj/yLEk12f/JZX + uMTe29EqIoB7gE/SCG9z+qoLXd4NHrA1lfrLjTDSCYNEhGiDyMhIMQ== + -----END AGE ENCRYPTED FILE----- + recipient: age1sgnnkn67wpt4wgjsff4c36w3cmjkk557ng504e4aczx2lzls9y6ssy0x9r + encrypted_regex: ^password$ + lastmodified: "2026-10-30T16:40:35Z" + mac: ENC[AES256_GCM,data:HeBfzNkyaDPxwfMepe0/4U5g0tAUyMf9AZMYly5La46tj8Ap5K4z9jp3tcOR9tADq1nCBhgtIKDiLmrNqOOWLShV1jDVM35hpBDHMpYuJjAGish8pBZUb3TetvSa1fnzOE24JrCZxpaK69q5V9akQzKIlhM/qgRhU7jFqPhyDT8=,iv:8QW9fMoDnME0qsE/TMcpi3zKgl+Oy+9mN8jzRY7XnN4=,tag:gOxsxLzsRPBx8iQjY5RW/A==,type:str] + mac_only_encrypted: true + version: 3.13.3 +--- +# 外から要るもの(UDP)。LiveKit の 8443 と NetBird の routing peer の 51820(どちらも hostPort。上と同じく念のため)。 +# STUN は外のもの(Cloudflare)を使うので開けない。 +apiVersion: v1alpha1 +kind: NetworkRuleConfig +name: public-udp +portSelector: + ports: + - 8443 + - 51820 + protocol: udp +ingress: + - subnet: 0.0.0.0/0 + - subnet: ::/0 +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIQnpITHlrS0lxZkZXb3dy + UmNMYUhFamtsR2Rpb3BTMXozYjQwQkoxOEhFClFOeDRQV2tTYTBlL09RUS9oMUZG + ZHZwNXVnYzA4dkIyME0rbnc0Zm44SEUKLS0tIDhTNmNMU1ZVeGEwbzltTDdpVVRP + M0FiWEJ2MHIzRkU2WHRxWlo3N0FsNlEKgYVZr+vTicTztpwMQ7oj/yLEk12f/JZX + uMTe29EqIoB7gE/SCG9z+qoLXd4NHrA1lfrLjTDSCYNEhGiDyMhIMQ== + -----END AGE ENCRYPTED FILE----- + recipient: age1sgnnkn67wpt4wgjsff4c36w3cmjkk557ng504e4aczx2lzls9y6ssy0x9r + encrypted_regex: ^password$ + lastmodified: "2026-10-30T16:40:35Z" + mac: ENC[AES256_GCM,data:HeBfzNkyaDPxwfMepe0/4U5g0tAUyMf9AZMYly5La46tj8Ap5K4z9jp3tcOR9tADq1nCBhgtIKDiLmrNqOOWLShV1jDVM35hpBDHMpYuJjAGish8pBZUb3TetvSa1fnzOE24JrCZxpaK69q5V9akQzKIlhM/qgRhU7jFqPhyDT8=,iv:8QW9fMoDnME0qsE/TMcpi3zKgl+Oy+9mN8jzRY7XnN4=,tag:gOxsxLzsRPBx8iQjY5RW/A==,type:str] + mac_only_encrypted: true + version: 3.13.3 +--- +# プライベートのアドレスからは全部のポート(TCP。UDP は次)。家の LAN(10.0.0.0/24・10.10.0.0/24)・NetBird(100.64.0.0/10)・ +# Pod と Service(10.42 / 10.43・fd42 / fd43。10.0.0.0/8 と fc00::/7 に入る)。talosctl(作業用 PC から 10.10.0.4:50000)・ +# etcd・kubelet・Cilium(4240・4244・8472・9963・9964)・Pod からの Talos API(k8up)とノードの DNS(169.254.116.108:53)はここ。 +# 家の LAN の IPv6 の GUA(RA のプレフィックス。変わりうる)は入れていないので、LAN からは IPv4 で繋ぐ。 +# 172.16/12・192.168/16 は使っていないので入れない(使うものだけ。足すなら上の送信元を増やしたとき)。 +apiVersion: v1alpha1 +kind: NetworkRuleConfig +name: private-tcp +portSelector: + ports: + - 1-65535 + protocol: tcp +ingress: + - subnet: 10.0.0.0/8 + - subnet: 100.64.0.0/10 + - subnet: fc00::/7 + - subnet: fe80::/10 +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIQnpITHlrS0lxZkZXb3dy + UmNMYUhFamtsR2Rpb3BTMXozYjQwQkoxOEhFClFOeDRQV2tTYTBlL09RUS9oMUZG + ZHZwNXVnYzA4dkIyME0rbnc0Zm44SEUKLS0tIDhTNmNMU1ZVeGEwbzltTDdpVVRP + M0FiWEJ2MHIzRkU2WHRxWlo3N0FsNlEKgYVZr+vTicTztpwMQ7oj/yLEk12f/JZX + uMTe29EqIoB7gE/SCG9z+qoLXd4NHrA1lfrLjTDSCYNEhGiDyMhIMQ== + -----END AGE ENCRYPTED FILE----- + recipient: age1sgnnkn67wpt4wgjsff4c36w3cmjkk557ng504e4aczx2lzls9y6ssy0x9r + encrypted_regex: ^password$ + lastmodified: "2026-10-30T16:40:35Z" + mac: ENC[AES256_GCM,data:HeBfzNkyaDPxwfMepe0/4U5g0tAUyMf9AZMYly5La46tj8Ap5K4z9jp3tcOR9tADq1nCBhgtIKDiLmrNqOOWLShV1jDVM35hpBDHMpYuJjAGish8pBZUb3TetvSa1fnzOE24JrCZxpaK69q5V9akQzKIlhM/qgRhU7jFqPhyDT8=,iv:8QW9fMoDnME0qsE/TMcpi3zKgl+Oy+9mN8jzRY7XnN4=,tag:gOxsxLzsRPBx8iQjY5RW/A==,type:str] + mac_only_encrypted: true + version: 3.13.3 +--- +# 上と同じ送信元から UDP の全部(Cilium の VXLAN 8472、ノードの DNS など)。 +apiVersion: v1alpha1 +kind: NetworkRuleConfig +name: private-udp +portSelector: + ports: + - 1-65535 + protocol: udp +ingress: + - subnet: 10.0.0.0/8 + - subnet: 100.64.0.0/10 + - subnet: fc00::/7 + - subnet: fe80::/10 +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIQnpITHlrS0lxZkZXb3dy + UmNMYUhFamtsR2Rpb3BTMXozYjQwQkoxOEhFClFOeDRQV2tTYTBlL09RUS9oMUZG + ZHZwNXVnYzA4dkIyME0rbnc0Zm44SEUKLS0tIDhTNmNMU1ZVeGEwbzltTDdpVVRP + M0FiWEJ2MHIzRkU2WHRxWlo3N0FsNlEKgYVZr+vTicTztpwMQ7oj/yLEk12f/JZX + uMTe29EqIoB7gE/SCG9z+qoLXd4NHrA1lfrLjTDSCYNEhGiDyMhIMQ== + -----END AGE ENCRYPTED FILE----- + recipient: age1sgnnkn67wpt4wgjsff4c36w3cmjkk557ng504e4aczx2lzls9y6ssy0x9r + encrypted_regex: ^password$ + lastmodified: "2026-10-30T16:40:35Z" + mac: ENC[AES256_GCM,data:HeBfzNkyaDPxwfMepe0/4U5g0tAUyMf9AZMYly5La46tj8Ap5K4z9jp3tcOR9tADq1nCBhgtIKDiLmrNqOOWLShV1jDVM35hpBDHMpYuJjAGish8pBZUb3TetvSa1fnzOE24JrCZxpaK69q5V9akQzKIlhM/qgRhU7jFqPhyDT8=,iv:8QW9fMoDnME0qsE/TMcpi3zKgl+Oy+9mN8jzRY7XnN4=,tag:gOxsxLzsRPBx8iQjY5RW/A==,type:str] + mac_only_encrypted: true + version: 3.13.3 +--- # 外から入る CI(cilium-drift.yml の OIDC トークン)と人(Entra)の認証。権限の中身は apiserver-rbac.yaml。 apiVersion: v1alpha1 kind: KubeAuthenticationConfig diff --git a/renovate.json b/renovate.json index 96ec07f4..229bd052 100644 --- a/renovate.json +++ b/renovate.json @@ -76,6 +76,14 @@ } ], "packageRules": [ + { + "description": "bootstrap/gateway-notrack.yaml は Cilium の agent のイメージ(iptables 入り)を使う。chart と同じ版に揃えて一緒に上げる。", + "matchPackageNames": [ + "cilium", + "quay.io/cilium/cilium" + ], + "groupName": "cilium" + }, { "description": "bun.lock は pulumi install で作る(SDK が file:sdks/netbird で、Renovate 自身の bun install は落ちる)。bun.lock も package file なので matchFileNames に入れる。skipArtifactsUpdate は枝の全部の更新が指定しているときだけ効くので専用の枝にする。許可は deploy/renovate/renovate.yaml の RENOVATE_ALLOWED_COMMANDS。", "matchFileNames": [