--- - 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