Skip to content
Open
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
17 changes: 13 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,8 @@ jobs:
needs: [changes]
if: (needs.changes.outputs.images == 'true') || contains(fromJSON(needs.changes.outputs.ci-jobs || '[]'), 'build-images')
runs-on: ubuntu-latest
timeout-minutes: 15
# Include all image builds, cache exports, artifact upload and cleanup.
timeout-minutes: 30
permissions:
contents: read
steps:
Expand Down Expand Up @@ -1232,6 +1233,8 @@ jobs:
run: python3 charts/tests/check_gateway.py
- name: standalone panel isolation and persistence chart renders
run: python3 charts/tests/check_standalone.py
- name: strict network profile and sentinel policy selectors
run: python3 charts/tests/check_network_isolation.py
- name: sanity-check rendered manifests
run: |
grep -q 'kind: Deployment' /tmp/rendered.yaml
Expand Down Expand Up @@ -1793,9 +1796,13 @@ jobs:
# (operator does zero logins and is wait-dominated, so it runs
# wide; the api buckets stay within the login limiter's budget).
# The arm64 job below uses the same steps and per-bucket settings.
bucket: [operator, api-auth, api-roles, api-rbac, api-agent, api-mods, telemetry, optional-components]
bucket: [operator, api-auth, api-roles, api-rbac, api-agent, api-mods, telemetry, optional-components, network-isolation]
arch: [amd64]
include:
- bucket: network-isolation
parallel: 1
test_timeout: 15m
job_timeout: 60
- bucket: operator
parallel: 8
test_timeout: 20m
Expand Down Expand Up @@ -1856,6 +1863,8 @@ jobs:
arch: ${{ matrix.arch }}
- name: bring up cluster + helm install
if: env.RUN_REGEX != '' || env.TAIL_REGEX != ''
env:
GAMEPLANE_E2E_CNI: ${{ matrix.bucket == 'network-isolation' && 'cilium' || 'kindnet' }}
run: ./deploy/kind/e2e.sh up "$CLUSTER" e2e
- name: run go e2e (${{ matrix.bucket }})
if: env.RUN_REGEX != ''
Expand Down Expand Up @@ -2660,7 +2669,7 @@ jobs:
'go-e2e-unit': (n) => n === 'go (test/e2e)',
'api-postgres': (n) => n === 'api (postgres)',
'e2e-buckets': (n) => n === 'e2e bucket coverage',
'e2e-go': (n) => /^e2e (operator|api-auth|api-roles|api-rbac|api-agent|api-mods|telemetry|optional-components) \//.test(n) && n.includes('/ amd64 '),
'e2e-go': (n) => /^e2e (operator|api-auth|api-roles|api-rbac|api-agent|api-mods|telemetry|optional-components|network-isolation) \//.test(n) && n.includes('/ amd64 '),
'e2e-go-arm64': (n) => /^e2e (operator|api-auth|api-roles|api-rbac|api-agent|api-mods|telemetry|optional-components) \//.test(n) && n.includes('/ arm64 '),
'e2e-multicluster': (n) => n.startsWith('e2e multicluster /') && n.includes('/ amd64 '),
'e2e-multicluster-arm64': (n) => n.startsWith('e2e multicluster /') && n.includes('/ arm64 '),
Expand Down Expand Up @@ -2813,7 +2822,7 @@ jobs:
const bucketSet = new Set();
for (const j of apiJobs) {
const n = j.name || '';
const m = n.match(/^e2e (operator|api-auth|api-roles|api-rbac|api-agent|api-mods|telemetry|optional-components) \//);
const m = n.match(/^e2e (operator|api-auth|api-roles|api-rbac|api-agent|api-mods|telemetry|optional-components|network-isolation) \//);
if (m) bucketSet.add(m[1]);
if (/^e2e multicluster \//.test(n)) bucketSet.add('multicluster');
if (/^e2e upgrade \//.test(n)) bucketSet.add('upgrade');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: backups.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: backupschedules.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: clusters.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: gameservers.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: gametemplates.gameplane.local
spec:
Expand Down Expand Up @@ -2114,6 +2114,12 @@ spec:
minimum: 0
type: integer
type: object
sentinelIsolation:
description: |-
SentinelIsolation restricts a wake sentinel to its own server's advertised
ports. Templates opt in after module connectivity validation; absent or
false retains the legacy namespace-wide sentinel allowances.
type: boolean
storage:
description: Storage describes the default persistent storage layout.
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: modules.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: modulesources.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: networkcaptures.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: restores.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: backups.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: backupschedules.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: clusters.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: gameservers.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: gametemplates.gameplane.local
spec:
Expand Down Expand Up @@ -2114,6 +2114,12 @@ spec:
minimum: 0
type: integer
type: object
sentinelIsolation:
description: |-
SentinelIsolation restricts a wake sentinel to its own server's advertised
ports. Templates opt in after module connectivity validation; absent or
false retains the legacy namespace-wide sentinel allowances.
type: boolean
storage:
description: Storage describes the default persistent storage layout.
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: modules.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: modulesources.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: networkcaptures.gameplane.local
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
gameplane.local/crd-bundle-sha256: 181aeee1d52a9cb178b0f8a8a862dc5959e772facf11817d5e70e5196574cd4c
gameplane.local/crd-bundle-sha256: 0cf9b2a9fa0d548476df615efb31f6c362eb68abeb3e85ba34798d7a6c431b23
controller-gen.kubebuilder.io/version: v0.20.1
name: restores.gameplane.local
spec:
Expand Down
20 changes: 13 additions & 7 deletions charts/gameplane/templates/networkpolicies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,9 @@ spec:
- { protocol: TCP, port: 443 }
- { protocol: TCP, port: 6443 }
---
# Allow the sentinel to egress to game pods on advertised ports for the proxy path.
# This is the INGRESS half only, on the game pod's own selector.
# Legacy sentinel proxy allowance, ingress half. This grants ALL ports between
# non-isolated sentinels and games. NetworkPolicies are additive; per-server
# player ingress does not narrow it. Opted-in templates use operator policies.
# NetworkPolicy requires both sides to agree: the destination (this policy)
# admits the sentinel, but default-deny-egress's podSelector: {} still
# governs the sentinel as a SOURCE and only opens DNS. allow-sentinel-to-game-egress
Expand All @@ -349,14 +350,17 @@ spec:
podSelector:
matchLabels:
app.kubernetes.io/name: gameplane-game
matchExpressions:
- { key: gameplane.local/sentinel-isolation, operator: NotIn, values: ["true"] }
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: gameplane-waker
# All ports; the per-server ingress policy covers which ports are
# actually advertised for each server.
matchExpressions:
- { key: gameplane.local/sentinel-isolation, operator: NotIn, values: ["true"] }
# Intentionally unrestricted for templates that have not opted in.
---
# The Egress half of allow-sentinel-to-game (see its comment above): admits
# the sentinel, as a traffic SOURCE, to reach game pods. Selects the
Expand All @@ -374,15 +378,17 @@ spec:
podSelector:
matchLabels:
app.kubernetes.io/name: gameplane-waker
matchExpressions:
- { key: gameplane.local/sentinel-isolation, operator: NotIn, values: ["true"] }
policyTypes: [Egress]
egress:
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: gameplane-game
# All ports; allow-sentinel-to-game above (the matching ingress half)
# and the per-server ingress policy cover which ports are actually
# advertised for each server.
matchExpressions:
- { key: gameplane.local/sentinel-isolation, operator: NotIn, values: ["true"] }
# Intentionally unrestricted for templates that have not opted in.
{{- end }}

{{- end }}
28 changes: 28 additions & 0 deletions charts/gameplane/templates/validate.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
{{- $network := .Values.networkPolicies | default dict }}
{{- if $network.strict }}
{{- if not $network.enabled }}
{{- fail "networkPolicies.strict requires networkPolicies.enabled=true" }}
{{- end }}
{{- range $key := list "kubeletCIDRs" "apiServerCIDRs" "probePorts" }}
{{- $entries := index $network $key }}
{{- if or (not (kindIs "slice" $entries)) (eq (len $entries) 0) }}
{{- fail (printf "networkPolicies.strict requires a non-empty networkPolicies.%s list" $key) }}
{{- end }}
{{- end }}
{{- $octet := "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])" }}
{{- $ipv4 := printf "^%s(\\.%s){3}/([0-9]|[12][0-9]|3[0-2])$" $octet $octet }}
{{- $h := "[0-9A-Fa-f]{1,4}" }}
{{- $ipv6 := printf "^((%s:){7}%s|(%s:){1,7}:|(%s:){1,6}:%s|(%s:){1,5}(:%s){1,2}|(%s:){1,4}(:%s){1,3}|(%s:){1,3}(:%s){1,4}|(%s:){1,2}(:%s){1,5}|%s:((:%s){1,6})|:((:%s){1,7}|:))/([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8])$" $h $h $h $h $h $h $h $h $h $h $h $h $h $h $h $h }}
{{- range $key := list "kubeletCIDRs" "apiServerCIDRs" }}
{{- range (index $network $key) }}
{{- if or (not (kindIs "string" .)) (not (or (regexMatch $ipv4 (toString .)) (regexMatch $ipv6 (toString .)))) }}
{{- fail (printf "networkPolicies.%s entries must be explicit IPv4/IPv6 CIDRs" $key) }}
{{- end }}
{{- end }}
{{- end }}
{{- range $network.probePorts }}
{{- if or (not (has (.protocol | default "") (list "TCP" "UDP" "SCTP"))) (not (regexMatch "^[0-9]+$" (toString .port))) (lt (int .port) 1) (gt (int .port) 65535) }}
{{- fail "networkPolicies.probePorts requires protocol TCP/UDP/SCTP and an integer port from 1 to 65535" }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.api.standalone }}
{{- if ne (include "gameplane.apiEnabled" .) "true" }}
{{- fail "api.standalone requires api.enabled=true" }}
Expand Down
3 changes: 3 additions & 0 deletions charts/gameplane/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,9 @@ gamesNamespace: gameplane-games

networkPolicies:
enabled: true
# Fail closed unless actual kubelet source CIDRs/probe ports and apiserver
# endpoint CIDRs are supplied. Defaults retain the legacy broad allowances.
strict: false
# CIDRs that hold kubelet source IPs for liveness/readiness probes.
# If unset, the chart falls back to RFC1918 + link-local ranges,
# which cover most clusters without opening the public internet.
Expand Down
49 changes: 49 additions & 0 deletions charts/tests/check_network_isolation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""CI-only rendered effective policy tuple and strict-profile regressions."""
from check_standalone import render, one


def main():
values = {"networkPolicies": {"strict": True,
"kubeletCIDRs": ["192.0.2.10/32"],
"probePorts": [{"protocol": "TCP", "port": 8090}],
"apiServerCIDRs": ["198.51.100.20/32"]}}
objects = render(values)
probe = one(objects, "NetworkPolicy", "allow-kubelet-probes")["spec"]["ingress"]
assert probe == [{"from": [{"ipBlock": {"cidr": "192.0.2.10/32"}}],
"ports": [{"protocol": "TCP", "port": 8090}]}]
for name in ("allow-agent-to-apiserver", "allow-sentinel-to-apiserver"):
egress = one(objects, "NetworkPolicy", name)["spec"]["egress"]
assert egress == [{"to": [{"ipBlock": {"cidr": "198.51.100.20/32"}}],
"ports": [{"protocol": "TCP", "port": 443}, {"protocol": "TCP", "port": 6443}]}]
excluded = [{"key": "gameplane.local/sentinel-isolation", "operator": "NotIn", "values": ["true"]}]
for name, direction, peer in (("allow-sentinel-to-game", "ingress", "from"),
("allow-sentinel-to-game-egress", "egress", "to")):
policy = one(objects, "NetworkPolicy", name)["spec"]
assert policy["podSelector"]["matchExpressions"] == excluded
assert policy[direction][0][peer][0]["podSelector"]["matchExpressions"] == excluded
import copy
for key in ("kubeletCIDRs", "apiServerCIDRs", "probePorts"):
invalid = copy.deepcopy(values)
invalid["networkPolicies"][key] = []
render(invalid, failure="requires a non-empty networkPolicies." + key)
for cidr in ("", "10.0.0.1", "999.0.0.1/32", "10.0.0.1/33", "2001:::1/64", "2001:db8/64", "::/129"):
invalid = copy.deepcopy(values)
invalid["networkPolicies"]["apiServerCIDRs"] = [cidr]
render(invalid, failure="must be explicit IPv4/IPv6 CIDRs")
for cidr in ("::/0", "::1/128", "2001:db8::/64", "2001:db8:1:2:3:4:5:6/128"):
valid = copy.deepcopy(values)
valid["networkPolicies"]["apiServerCIDRs"] = [cidr]
render(valid)
for port in (0, 65536, 1.5, "all"):
invalid = copy.deepcopy(values)
invalid["networkPolicies"]["probePorts"][0]["port"] = port
render(invalid, failure="integer port")
render({"networkPolicies": {"strict": True, "enabled": False}}, failure="requires networkPolicies.enabled=true")
# Legacy/default installs still render the documented broad fallback.
fallback = one(render(), "NetworkPolicy", "allow-kubelet-probes")["spec"]["ingress"][0]
assert "ports" not in fallback and {p["ipBlock"]["cidr"] for p in fallback["from"]} == {
"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16"}


if __name__ == "__main__":
main()
Loading
Loading