net-kingdom/tools/contract-tests/tests/test_ingress.py

104 lines
5.6 KiB
Python
Raw Normal View History

"""Row E9: every owner's ingress admits the executor pod (NK-WP-0058-T02), read from the owners' source.
Kubernetes semantics, for the subset these policies use: a pod that no Ingress policy selects is open; once any
policy selects it, traffic must match an ingress rule of one of them (peer selectors and port). matchExpressions and
ipBlock peers are not used by these policies; a policy that uses them fails the test instead of being guessed.
Not covered here: OpenBao (rapp-openbao is private) and the executor's egress (the rapp's own CI, row E9 there).
"""
from pathlib import Path
import pytest
import yaml
REPO = Path(__file__).resolve().parents[3]
SIBLINGS = REPO.parent
EXECUTOR = {'namespace': 'sso', 'labels': {'app.kubernetes.io/name': 'bounded-executor'}}
SSO = 'sso-mfa/k8s/network-policies/netpol-sso.yaml'
# (owner, policy files relative to the owner, namespace, target pod labels, port), from the executor's config
TARGETS = [
('approval-engine', ['deploy/networkpolicies.yaml'], 'approval-engine',
{'app.kubernetes.io/name': 'approval-engine'}, 8080),
('audit-core', ['deploy/networkpolicies.yaml'], 'audit-core', {'app.kubernetes.io/name': 'audit-core'}, 8080),
('informed-decision', ['deploy/lifecycle/*.yaml'], 'informed-decision',
{'app.kubernetes.io/name': 'informed-decision', 'app.kubernetes.io/component': 'review'}, 8081),
] + [('net-kingdom', [SSO], 'sso', {'app.kubernetes.io/name': name}, port)
for name, port in (('lldap', 17170), ('keycape', 8080), ('authelia', 9091))]
def selects(selector, labels):
if set(selector) - {'matchLabels'}:
raise AssertionError(f'unsupported selector {sorted(selector)}: extend the evaluator, do not guess')
return all(labels.get(k) == v for k, v in (selector.get('matchLabels') or {}).items())
def peer_admits(peer, policy_ns, source):
if set(peer) - {'namespaceSelector', 'podSelector'}:
raise AssertionError(f'unsupported peer {sorted(peer)}')
if 'namespaceSelector' in peer:
ns_ok = selects(peer['namespaceSelector'], {'kubernetes.io/metadata.name': source['namespace']})
else:
ns_ok = source['namespace'] == policy_ns
return ns_ok and selects(peer.get('podSelector') or {}, source['labels'])
def port_admits(ports, port):
return not ports or any(p.get('port') == port and p.get('protocol', 'TCP') == 'TCP' for p in ports)
def admitted(policies, namespace, labels, port, source=EXECUTOR):
applying = [p for p in policies if (p['metadata'].get('namespace') or namespace) == namespace
and 'Ingress' in p['spec'].get('policyTypes', ['Ingress'])
and selects(p['spec'].get('podSelector') or {}, labels)]
if not applying:
return 'open'
for policy in applying:
for rule in policy['spec'].get('ingress') or []:
peers = rule.get('from')
if (not peers or any(peer_admits(peer, namespace, source) for peer in peers)) \
and port_admits(rule.get('ports'), port):
return 'admitted:' + policy['metadata']['name']
return 'denied'
def policies_of(owner, patterns):
root = REPO if owner == 'net-kingdom' else SIBLINGS / owner
if not root.is_dir():
pytest.skip(f'owner checkout missing: {owner}')
out = []
for pattern in patterns:
for path in sorted(root.glob(pattern)):
out += [d for d in yaml.safe_load_all(path.read_text()) if d and d.get('kind') == 'NetworkPolicy']
return out
@pytest.mark.parametrize('owner, files, namespace, labels, port', TARGETS,
ids=[f"{t[0]}:{t[3]['app.kubernetes.io/name']}:{t[4]}" for t in TARGETS])
def test_owner_ingress_admits_the_executor(owner, files, namespace, labels, port):
result = admitted(policies_of(owner, files), namespace, labels, port)
assert result != 'denied', f'{owner} ingress does not admit sso/bounded-executor on {port}'
def policy(name, namespace, target, peers=None, ports=None):
rule = {**({'from': peers} if peers is not None else {}), **({'ports': ports} if ports else {})}
return {'kind': 'NetworkPolicy', 'metadata': {'name': name, 'namespace': namespace},
'spec': {'podSelector': {'matchLabels': target}, 'policyTypes': ['Ingress'], 'ingress': [rule]}}
def test_the_evaluator_on_synthetic_policies():
target, ns = {'app.kubernetes.io/name': 'svc'}, 'svc'
executor_peer = {'namespaceSelector': {'matchLabels': {'kubernetes.io/metadata.name': 'sso'}},
'podSelector': {'matchLabels': {'app.kubernetes.io/name': 'bounded-executor'}}}
other_peer = {'namespaceSelector': {'matchLabels': {'kubernetes.io/metadata.name': 'other'}}}
assert admitted([], ns, target, 8080) == 'open'
assert admitted([policy('a', ns, target, [executor_peer], [{'port': 8080}])], ns, target, 8080) == 'admitted:a'
assert admitted([policy('a', ns, target, [executor_peer], [{'port': 9090}])], ns, target, 8080) == 'denied'
assert admitted([policy('a', ns, target, [other_peer])], ns, target, 8080) == 'denied'
assert admitted([policy('a', ns, {'app.kubernetes.io/name': 'else'}, [other_peer])], ns, target, 8080) == 'open'
deny_all = {'kind': 'NetworkPolicy', 'metadata': {'name': 'deny', 'namespace': ns},
'spec': {'podSelector': {}, 'policyTypes': ['Ingress']}}
assert admitted([deny_all], ns, target, 8080) == 'denied'
same_ns_peer = {'podSelector': {'matchLabels': {'app.kubernetes.io/name': 'bounded-executor'}}}
assert admitted([deny_all, policy('b', 'sso', target, [same_ns_peer])], 'sso', target, 8080) == 'admitted:b'
assert admitted([deny_all, policy('b', ns, target, [same_ns_peer])], ns, target, 8080) == 'denied'