Files
kube-hunter/tests/hunting/test_apiserver_hunter.py
Yehuda Chikvashvili 0f1739262f Linting Standards (#330)
Fix linting issues with flake8 and black.
Add pre-commit congifuration, update documnetation for it.
Apply linting check in Travis CI.
2020-04-05 05:22:24 +03:00

276 lines
8.1 KiB
Python

import requests_mock
import time
from kube_hunter.modules.hunting.apiserver import (
AccessApiServer,
AccessApiServerWithToken,
ServerApiAccess,
AccessApiServerActive,
)
from kube_hunter.modules.hunting.apiserver import (
ListNamespaces,
ListPodsAndNamespaces,
ListRoles,
ListClusterRoles,
)
from kube_hunter.modules.hunting.apiserver import ApiServerPassiveHunterFinished
from kube_hunter.modules.hunting.apiserver import CreateANamespace, DeleteANamespace
from kube_hunter.modules.discovery.apiserver import ApiServer
from kube_hunter.core.types import UnauthenticatedAccess, InformationDisclosure
from kube_hunter.core.events import handler
counter = 0
def test_ApiServerToken():
global counter
counter = 0
e = ApiServer()
e.host = "1.2.3.4"
e.auth_token = "my-secret-token"
# Test that the pod's token is passed on through the event
h = AccessApiServerWithToken(e)
assert h.event.auth_token == "my-secret-token"
# This test doesn't generate any events
time.sleep(0.01)
assert counter == 0
def test_AccessApiServer():
global counter
counter = 0
e = ApiServer()
e.host = "mockKubernetes"
e.port = 443
e.protocol = "https"
with requests_mock.Mocker() as m:
m.get("https://mockKubernetes:443/api", text="{}")
m.get(
"https://mockKubernetes:443/api/v1/namespaces", text='{"items":[{"metadata":{"name":"hello"}}]}',
)
m.get(
"https://mockKubernetes:443/api/v1/pods",
text='{"items":[{"metadata":{"name":"podA", "namespace":"namespaceA"}}, \
{"metadata":{"name":"podB", "namespace":"namespaceB"}}]}',
)
m.get(
"https://mockkubernetes:443/apis/rbac.authorization.k8s.io/v1/roles", status_code=403,
)
m.get(
"https://mockkubernetes:443/apis/rbac.authorization.k8s.io/v1/clusterroles", text='{"items":[]}',
)
m.get(
"https://mockkubernetes:443/version",
text='{"major": "1","minor": "13+", "gitVersion": "v1.13.6-gke.13", \
"gitCommit": "fcbc1d20b6bca1936c0317743055ac75aef608ce", \
"gitTreeState": "clean", "buildDate": "2019-06-19T20:50:07Z", \
"goVersion": "go1.11.5b4", "compiler": "gc", \
"platform": "linux/amd64"}',
)
h = AccessApiServer(e)
h.execute()
# We should see events for Server API Access, Namespaces, Pods, and the passive hunter finished
time.sleep(0.01)
assert counter == 4
# Try with an auth token
counter = 0
with requests_mock.Mocker() as m:
# TODO check that these responses reflect what Kubernetes does
m.get("https://mocktoken:443/api", text="{}")
m.get(
"https://mocktoken:443/api/v1/namespaces", text='{"items":[{"metadata":{"name":"hello"}}]}',
)
m.get(
"https://mocktoken:443/api/v1/pods",
text='{"items":[{"metadata":{"name":"podA", "namespace":"namespaceA"}}, \
{"metadata":{"name":"podB", "namespace":"namespaceB"}}]}',
)
m.get(
"https://mocktoken:443/apis/rbac.authorization.k8s.io/v1/roles", status_code=403,
)
m.get(
"https://mocktoken:443/apis/rbac.authorization.k8s.io/v1/clusterroles",
text='{"items":[{"metadata":{"name":"my-role"}}]}',
)
e.auth_token = "so-secret"
e.host = "mocktoken"
h = AccessApiServerWithToken(e)
h.execute()
# We should see the same set of events but with the addition of Cluster Roles
time.sleep(0.01)
assert counter == 5
@handler.subscribe(ListNamespaces)
class test_ListNamespaces(object):
def __init__(self, event):
print("ListNamespaces")
assert event.evidence == ["hello"]
if event.host == "mocktoken":
assert event.auth_token == "so-secret"
else:
assert event.auth_token is None
global counter
counter += 1
@handler.subscribe(ListPodsAndNamespaces)
class test_ListPodsAndNamespaces(object):
def __init__(self, event):
print("ListPodsAndNamespaces")
assert len(event.evidence) == 2
for pod in event.evidence:
if pod["name"] == "podA":
assert pod["namespace"] == "namespaceA"
if pod["name"] == "podB":
assert pod["namespace"] == "namespaceB"
if event.host == "mocktoken":
assert event.auth_token == "so-secret"
assert "token" in event.name
assert "anon" not in event.name
else:
assert event.auth_token is None
assert "token" not in event.name
assert "anon" in event.name
global counter
counter += 1
# Should never see this because the API call in the test returns 403 status code
@handler.subscribe(ListRoles)
class test_ListRoles(object):
def __init__(self, event):
print("ListRoles")
assert 0
global counter
counter += 1
# Should only see this when we have a token because the API call returns an empty list of items
# in the test where we have no token
@handler.subscribe(ListClusterRoles)
class test_ListClusterRoles(object):
def __init__(self, event):
print("ListClusterRoles")
assert event.auth_token == "so-secret"
global counter
counter += 1
@handler.subscribe(ServerApiAccess)
class test_ServerApiAccess(object):
def __init__(self, event):
print("ServerApiAccess")
if event.category == UnauthenticatedAccess:
assert event.auth_token is None
else:
assert event.category == InformationDisclosure
assert event.auth_token == "so-secret"
global counter
counter += 1
@handler.subscribe(ApiServerPassiveHunterFinished)
class test_PassiveHunterFinished(object):
def __init__(self, event):
print("PassiveHunterFinished")
assert event.namespaces == ["hello"]
global counter
counter += 1
def test_AccessApiServerActive():
e = ApiServerPassiveHunterFinished(namespaces=["hello-namespace"])
e.host = "mockKubernetes"
e.port = 443
e.protocol = "https"
with requests_mock.Mocker() as m:
# TODO more tests here with real responses
m.post(
"https://mockKubernetes:443/api/v1/namespaces",
text="""
{
"kind": "Namespace",
"apiVersion": "v1",
"metadata": {
"name": "abcde",
"selfLink": "/api/v1/namespaces/abcde",
"uid": "4a7aa47c-39ba-11e9-ab46-08002781145e",
"resourceVersion": "694180",
"creationTimestamp": "2019-02-26T11:33:08Z"
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
}
""",
)
m.post("https://mockKubernetes:443/api/v1/clusterroles", text="{}")
m.post(
"https://mockkubernetes:443/apis/rbac.authorization.k8s.io/v1/clusterroles", text="{}",
)
m.post(
"https://mockkubernetes:443/api/v1/namespaces/hello-namespace/pods", text="{}",
)
m.post(
"https://mockkubernetes:443" "/apis/rbac.authorization.k8s.io/v1/namespaces/hello-namespace/roles",
text="{}",
)
m.delete(
"https://mockKubernetes:443/api/v1/namespaces/abcde",
text="""
{
"kind": "Namespace",
"apiVersion": "v1",
"metadata": {
"name": "abcde",
"selfLink": "/api/v1/namespaces/abcde",
"uid": "4a7aa47c-39ba-11e9-ab46-08002781145e",
"resourceVersion": "694780",
"creationTimestamp": "2019-02-26T11:33:08Z",
"deletionTimestamp": "2019-02-26T11:40:58Z"
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Terminating"
}
}
""",
)
h = AccessApiServerActive(e)
h.execute()
@handler.subscribe(CreateANamespace)
class test_CreateANamespace(object):
def __init__(self, event):
assert "abcde" in event.evidence
@handler.subscribe(DeleteANamespace)
class test_DeleteANamespace(object):
def __init__(self, event):
assert "2019-02-26" in event.evidence