104 lines
5.6 KiB
Python
104 lines
5.6 KiB
Python
|
|
"""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'
|