Files
2026-10-26 13:18:46 -04:00

281 lines
14 KiB
YAML
Executable File

---
- name: Validate Luanti WASM role without a cluster
hosts: localhost
connection: local
gather_facts: false
vars:
# Non-routable documentation fixtures only; never pulled or deployed.
luanti_wasm_web_image: registry.invalid/fixture/luanti-web:test-1
luanti_wasm_proxy_image: "registry.invalid/fixture/lwproxy@sha256:{{ 'a' * 64 }}"
luanti_wasm_game_cidrs: [192.0.2.15/32, '2001:db8::15/128']
tasks:
- name: Load defaults and validate fixture settings
ansible.builtin.include_role:
name: ansible-role-luanti-wasm
tasks_from: validate.yml
public: true
- name: Render the actual role templates
ansible.builtin.include_role:
name: ansible-role-luanti-wasm
tasks_from: render.yml
- name: Check private stateless defaults and service isolation
ansible.builtin.assert:
that:
- not luanti_wasm_ingress_enabled
- _luanti_wasm_manifests.deployment.apiVersion == 'apps/v1'
- _luanti_wasm_manifests.deployment.spec.replicas == 1
- _luanti_wasm_manifests.deployment.spec.template.spec.containers | length == 2
- _luanti_wasm_manifests.service.spec.type == 'ClusterIP'
- _luanti_wasm_manifests.service.spec.ports | map(attribute='port') | list == [8080]
- _luanti_wasm_manifests.service.spec.ports[0].targetPort == 'http'
- _luanti_wasm_manifests.service.spec.selector == _luanti_wasm_manifests.deployment.spec.template.metadata.labels
- "'hostNetwork' not in _luanti_wasm_manifests.deployment.spec.template.spec"
- "'spec' not in _luanti_wasm_manifests.ingress"
- _luanti_wasm_manifests.deployment.spec.template.spec.volumes | length == 2
- _luanti_wasm_manifests.deployment.spec.template.spec.volumes | selectattr('persistentVolumeClaim', 'defined') | list | length == 0
- name: Check pod security, bounded memory temporary volume, and probes
ansible.builtin.assert:
that:
- not pod.automountServiceAccountToken
- pod.securityContext.runAsNonRoot
- pod.securityContext.seccompProfile.type == 'RuntimeDefault'
- pod.securityContext.fsGroup == 101
- pod.volumes[1].emptyDir == {'medium':'Memory', 'sizeLimit':'64Mi'}
- pod.containers[0].securityContext.runAsUser == 101
- pod.containers[1].securityContext.runAsUser == 65532
- pod.containers[0].readinessProbe.httpGet == {'path':'/', 'port':'http'}
- pod.containers[0].livenessProbe.httpGet.port == 'http'
- pod.containers[1].readinessProbe.tcpSocket.port == 'proxy'
- pod.containers[1].livenessProbe.tcpSocket.port == 'proxy'
- pod.containers[0].image == luanti_wasm_web_image
- pod.containers[1].image == luanti_wasm_proxy_image
- pod.containers[0].volumeMounts[0].mountPath == '/etc/nginx/nginx.conf'
- pod.containers[1].volumeMounts[0].mountPath == '/etc/lwproxy.toml'
- pod.containers[1].volumeMounts[0].subPath == 'lwproxy.toml'
vars:
pod: "{{ _luanti_wasm_manifests.deployment.spec.template.spec }}"
- name: Check both containers are hardened and resource bounded
ansible.builtin.assert:
that:
- item.securityContext.runAsNonRoot
- item.securityContext.readOnlyRootFilesystem
- not item.securityContext.allowPrivilegeEscalation
- item.securityContext.capabilities.drop == ['ALL']
- item.resources.requests == {'cpu':'100m', 'memory':'64Mi'}
- item.resources.limits.memory == '256Mi'
- item.volumeMounts[0].readOnly
- item.startupProbe.failureThreshold == 30
- item.ports | selectattr('hostPort', 'defined') | list | length == 0
loop: "{{ _luanti_wasm_manifests.deployment.spec.template.spec.containers }}"
- name: Check WASM headers, release cache contract, and localhost routing
ansible.builtin.assert:
that:
- "'add_header Cross-Origin-Opener-Policy same-origin always;' in nginx"
- "'add_header Cross-Origin-Embedder-Policy require-corp always;' in nginx"
- "'add_header X-Content-Type-Options nosniff always;' in nginx"
- "'~^/[0-9a-f]{12}/' in nginx"
- "'public, max-age=31536000, immutable' in nginx"
- "'default \"no-cache\";' in nginx"
- "'proxy_pass http://127.0.0.1:7777;' in nginx"
- "'proxy_set_header Upgrade $http_upgrade;' in nginx"
- "'proxy_read_timeout 3600s;' in nginx"
- "'proxy_buffering off;' in nginx"
- "'bind_address = \"0.0.0.0:7777\"' in proxy"
- "'allowed_hosts = [\"content.luanti.org\", \"servers.luanti.org\"]' in proxy"
- _luanti_wasm_manifests.configmap.metadata.name == 'luanti-wasm-config'
- _luanti_wasm_manifests.deployment.spec.template.spec.volumes[0].configMap.name == _luanti_wasm_manifests.configmap.metadata.name
vars:
nginx: "{{ _luanti_wasm_manifests.configmap.data['nginx.conf'] }}"
proxy: "{{ _luanti_wasm_manifests.configmap.data['lwproxy.toml'] }}"
- name: Check mandatory policy and absence of wildcard egress
ansible.builtin.assert:
that:
- policy.policyTypes == ['Ingress', 'Egress']
- policy.podSelector.matchLabels == _luanti_wasm_manifests.service.spec.selector
- policy.ingress == [{'ports':[{'port':8080, 'protocol':'TCP'}]}]
- policy.egress | length == 2
- policy.egress[0].to == [{'namespaceSelector':{'matchLabels':{'kubernetes.io/metadata.name':'kube-system'}}, 'podSelector':{'matchLabels':{'k8s-app':'kube-dns'}}}]
- policy.egress[0].ports == [{'port':53, 'protocol':'UDP'}, {'port':53, 'protocol':'TCP'}]
- policy.egress[1].to | map(attribute='ipBlock.cidr') | list == luanti_wasm_game_cidrs
- policy.egress[1].ports == [{'port':30000, 'protocol':'UDP'}]
vars:
policy: "{{ _luanti_wasm_manifests.networkpolicy.spec }}"
- name: Preserve initial checksum
ansible.builtin.set_fact:
_initial_checksum: "{{ _luanti_wasm_manifests.deployment.spec.template.metadata.annotations['checksum/config'] }}"
- name: Re-render identical settings
ansible.builtin.include_role:
name: ansible-role-luanti-wasm
tasks_from: render.yml
- name: Check deterministic SHA256 checksum
ansible.builtin.assert:
that:
- _initial_checksum is match('^[a-f0-9]{64}$')
- _initial_checksum == _luanti_wasm_manifests.deployment.spec.template.metadata.annotations['checksum/config']
- name: Configure optional egress and ingress restrictions
ansible.builtin.set_fact:
luanti_wasm_https_cidrs: [198.51.100.0/24, '2001:db8:1::/64']
luanti_wasm_ingress_peers:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
podSelector:
matchLabels:
app.kubernetes.io/component: controller
- ipBlock:
cidr: 203.0.113.0/24
except: [203.0.113.1/32]
luanti_wasm_http_allowed_hosts: [content.luanti.org]
luanti_wasm_websocket_timeout_seconds: 7200
luanti_wasm_replicas: 2
- name: Validate optional settings
ansible.builtin.include_role:
name: ansible-role-luanti-wasm
tasks_from: validate.yml
- name: Render optional settings
ansible.builtin.include_role:
name: ansible-role-luanti-wasm
tasks_from: render.yml
- name: Check optional rules never widen ports or open arbitrary egress
ansible.builtin.assert:
that:
- _luanti_wasm_manifests.deployment.spec.replicas == 2
- _luanti_wasm_manifests.networkpolicy.spec.ingress[0]['from'] == luanti_wasm_ingress_peers
- _luanti_wasm_manifests.networkpolicy.spec.ingress[0].ports == [{'port':8080, 'protocol':'TCP'}]
- _luanti_wasm_manifests.networkpolicy.spec.egress | length == 3
- _luanti_wasm_manifests.networkpolicy.spec.egress[2].ports == [{'port':443, 'protocol':'TCP'}]
- _luanti_wasm_manifests.networkpolicy.spec.egress[2].to | map(attribute='ipBlock.cidr') | list == luanti_wasm_https_cidrs
- _initial_checksum != _luanti_wasm_manifests.deployment.spec.template.metadata.annotations['checksum/config']
- "'proxy_read_timeout 7200s;' in _luanti_wasm_manifests.configmap.data['nginx.conf']"
- name: Accept latest tags for both runtime images
ansible.builtin.set_fact:
luanti_wasm_web_image: registry.invalid/fixture/luanti-web:latest
luanti_wasm_proxy_image: registry.invalid/fixture/lwproxy:latest
- name: Validate latest-tagged runtime images
ansible.builtin.include_role:
name: ansible-role-luanti-wasm
tasks_from: validate.yml
- name: Restore pinned test image references
ansible.builtin.set_fact:
luanti_wasm_web_image: registry.invalid/fixture/luanti-web:test-1
luanti_wasm_proxy_image: "registry.invalid/fixture/lwproxy@sha256:{{ 'a' * 64 }}"
- name: Supply fixture eom inputs
ansible.builtin.set_fact:
eom_luanti_wasm_web_image: registry.invalid/fixture/eom-web:release-1
eom_luanti_wasm_proxy_image: registry.invalid/fixture/eom-proxy:release-1
eom_luanti_wasm_game_cidrs: [192.0.2.15/32]
- name: Load real eom playbook variables
ansible.builtin.set_fact:
# Rendering an entire YAML file quotes its embedded list expressions;
# restore those two list types, as native playbook evaluation does.
"{{ item.key }}": "{{ (item.value | from_yaml) if item.key in ['luanti_wasm_game_cidrs', 'luanti_wasm_https_cidrs'] else item.value }}"
loop: "{{ (lookup('ansible.builtin.template', '../playbooks/deploy-eom.yml') | from_yaml)[0].vars | dict2items }}"
- name: Validate eom deployment settings without deploying
ansible.builtin.include_role:
name: ansible-role-luanti-wasm
tasks_from: validate.yml
- name: Render eom deployment settings
ansible.builtin.include_role:
name: ansible-role-luanti-wasm
tasks_from: render.yml
- name: Check TLS ingress and eom integration
ansible.builtin.assert:
that:
- _luanti_wasm_manifests.ingress.apiVersion == 'networking.k8s.io/v1'
- _luanti_wasm_manifests.ingress.spec.ingressClassName == 'nginx'
- _luanti_wasm_manifests.ingress.spec.rules[0].host == 'luanti.eom.dev'
- _luanti_wasm_manifests.ingress.spec.tls == [{'hosts':['luanti.eom.dev'], 'secretName':'luanti-wasm-tls'}]
- _luanti_wasm_manifests.ingress.spec.rules[0].http.paths[0].backend.service.port.number == 8080
- _luanti_wasm_manifests.ingress.metadata.annotations['cert-manager.io/cluster-issuer'] == 'ca-issuer'
- _luanti_wasm_manifests.ingress.metadata.annotations['nginx.ingress.kubernetes.io/proxy-read-timeout'] == '3600'
- _luanti_wasm_manifests.deployment.spec.template.spec.containers[0].image == eom_luanti_wasm_web_image
- name: Exercise external TLS and configurable annotations
ansible.builtin.set_fact:
luanti_wasm_cluster_issuer: ""
luanti_wasm_ingress_annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "7200"
nginx.ingress.kubernetes.io/proxy-send-timeout: "7200"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
- name: Render external TLS configuration
ansible.builtin.include_role:
name: ansible-role-luanti-wasm
tasks_from: render.yml
- name: Check cert-manager is optional but TLS is not
ansible.builtin.assert:
that:
- "'cert-manager.io/cluster-issuer' not in _luanti_wasm_manifests.ingress.metadata.annotations"
- _luanti_wasm_manifests.ingress.spec.tls | length == 1
- _luanti_wasm_manifests.ingress.metadata.annotations['nginx.ingress.kubernetes.io/proxy-read-timeout'] == '7200'
- name: Capture valid baseline for rejection tests
ansible.builtin.set_fact:
_luanti_baseline: "{{ vars | dict2items | selectattr('key', 'match', '^luanti_wasm_') | items2dict }}"
- name: Reject invalid settings at role validation, never at Kubernetes
ansible.builtin.include_tasks: invalid.yml
loop:
- {luanti_wasm_web_image: ""}
- {luanti_wasm_proxy_image: ""}
- {luanti_wasm_web_image: 'registry.invalid/team/web'}
- {luanti_wasm_proxy_image: 'registry.invalid/team/proxy@sha256:abc'}
- {luanti_wasm_web_image: 'https://registry.invalid/team/web:1'}
- {luanti_wasm_game_cidrs: []}
- {luanti_wasm_game_cidrs: ['0.0.0.0/0']}
- {luanti_wasm_https_cidrs: ['::/0']}
- {luanti_wasm_game_cidrs: ['192.0.2.999/32']}
- {luanti_wasm_game_cidrs: ['192.0.2.1/24']}
- {luanti_wasm_game_cidrs: ['2001:db8:::/64']}
- {luanti_wasm_game_cidrs: ['2001:db8::/129']}
- {luanti_wasm_game_cidrs: ['luanti.eom.dev/32']}
- {luanti_wasm_game_cidrs: '192.0.2.0/24'}
- {luanti_wasm_ingress_peers: [{}]}
- {luanti_wasm_ingress_peers: ['not-an-object']}
- {luanti_wasm_ingress_peers: [{namespaceSelector: 'not-an-object'}]}
- {luanti_wasm_ingress_peers: [{ports: [7777]}]}
- {luanti_wasm_ingress_peers: [{ipBlock: {cidr: '0.0.0.0/0'}}]}
- {luanti_wasm_ingress_peers: [{ipBlock: {cidr: '192.0.2.0/24', except: ['198.51.100.0/24']}}]}
- {luanti_wasm_tls_enabled: false}
- {luanti_wasm_tls_secret_name: ""}
- {luanti_wasm_hostname: 'https://luanti.eom.dev/'}
- {luanti_wasm_hostname: '*.eom.dev'}
- {luanti_wasm_name: 'Invalid_Name'}
- {luanti_wasm_name: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'}
- {luanti_wasm_namespace: 'bad.namespace'}
- {luanti_wasm_replicas: 0}
- {luanti_wasm_replicas: '2'}
- {luanti_wasm_replicas: true}
- {luanti_wasm_game_port: 65536}
- {luanti_wasm_websocket_timeout_seconds: '3600; bad'}
- {luanti_wasm_ingress_enabled: 'true'}
- {luanti_wasm_image_pull_policy: Sometimes}
- {luanti_wasm_ingress_annotations: {example.org/key: false}}
- {luanti_wasm_http_allowed_hosts: ['*.luanti.org']}
- {luanti_wasm_web_resources: {requests: {cpu: 100m}}}
- {luanti_wasm_tmp_size_limit: unbounded}
loop_control:
loop_var: invalid_settings