Skip to content
Closed
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
47 changes: 43 additions & 4 deletions .github/workflows/talos-boot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <<YAML
apiVersion: v1
kind: Pod
metadata: {name: gw-from-pod, namespace: default}
spec:
restartPolicy: Never
securityContext: {runAsNonRoot: true, runAsUser: 65532, seccompProfile: {type: RuntimeDefault}}
containers:
- name: c
image: mirror.gcr.io/curlimages/curl:8.17.0
securityContext: {allowPrivilegeEscalation: false, capabilities: {drop: [ALL]}}
command: [sh, -c]
args:
- |
for ip in $NODE '[240f:6d:842b:1::2]'; do
echo "\$ip \$(curl -sk -o /dev/null -w '%{http_code}' --max-time 10 --connect-to ac.doany.io:443:\$ip:443 https://ac.doany.io/ || true)"
done
YAML
kubectl apply -f /tmp/curl.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/gw-from-pod --timeout=3m || true
OUT=$(kubectl logs gw-from-pod)
echo "$OUT" | sed 's/^/ /'
kubectl delete pod gw-from-pod --wait=false
[ "$(echo "$OUT" | grep -c ' 200$')" = 2 ] || { echo "::error::Pod からノードの IP の 443 に届かない(上の 000 の行)"; exit 1; }

- name: ディスクとネットワーク
timeout-minutes: 5
Expand All @@ -315,7 +348,13 @@ jobs:
[ "$GOT" = "$WANT" ] || fail "bond0 の MAC が $GOT(talos.sops.yaml は $WANT)"
echo " bond0 hardwareAddr: $GOT"

# 受信の防火壁は 2026-10-31 から外してある(bootstrap/talos.md)。戻したら、ingress の chain の既定が drop かの検査も戻す(#646)
# 受信の防火壁(talos.sops.yaml の NetworkDefaultActionConfig)が既定で捨てる形で載っているか。
# 通す側は、ここまでの talosctl(50000)・kubectl(6443)・curl(80 / 443)が SLIRP の 10.0.0.1 から通ったことで見ている
talosctl get nftableschains -o 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。前提が成り立っていない"
Expand Down
64 changes: 64 additions & 0 deletions bootstrap/gateway-notrack.yaml
Original file line number Diff line number Diff line change
@@ -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}
9 changes: 8 additions & 1 deletion bootstrap/render.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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' \
Expand Down
155 changes: 155 additions & 0 deletions bootstrap/talos.sops.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
Loading