-
Notifications
You must be signed in to change notification settings - Fork 14
Implement support for named ports in NetPols #134
Conversation
56d9d00
to
5bb572d
Compare
Codecov Report
@@ Coverage Diff @@
## master #134 +/- ##
==========================================
- Coverage 39.21% 38.20% -1.01%
==========================================
Files 11 11
Lines 1321 1369 +48
Branches 264 276 +12
==========================================
+ Hits 518 523 +5
- Misses 754 798 +44
+ Partials 49 48 -1
Continue to review full report at Codecov.
|
7ea674c
to
c8f992f
Compare
dc7e280
to
3e7b08d
Compare
3e7b08d
to
5462c0e
Compare
789b798
to
1e991c6
Compare
1e991c6
to
a2e94de
Compare
2f6f41c
to
e6ee650
Compare
016bfb6
to
2dc5d68
Compare
2dc5d68
to
32d7c91
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor comments and one potential issues with named ports containing a -
. Otherwise LGTM, thanks for contributing :)
@@ -11,7 +11,8 @@ minikube start \ | |||
--cni=calico \ | |||
--container-runtime=docker \ | |||
--host-only-cidr=172.17.17.1/24 \ | |||
--kubernetes-version="${KUBERNETES_VERSION}" | |||
--kubernetes-version="${KUBERNETES_VERSION}" \ | |||
--insecure-registry=localhost:5000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to have not been needed up until now, do you know why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kubelet was not able to pull the images from the internal minikube registry addon.
After adding this, it worked for me. But I did not further investigate logs besides the kubelet event, so I'm not sure whats the exact cause and why the pipeline does not have this issue.
@@ -264,6 +264,14 @@ def transform_results( | |||
LOGGER.debug("mapped_sender_pod: %s", mapped_sender_pod) | |||
LOGGER.debug("mapped_receiver_pod: %s", mapped_receiver_pod) | |||
LOGGER.debug("raw_results: %s", json.dumps(raw_results)) | |||
if mapped_receiver_pod not in raw_results[mapped_sender_pod]: | |||
LOGGER.error( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would prefer the results to contain an entry for these failures, where the state is ERR. This way other output formats would also show the error without checking logs and test results would not vanish "silently" (when only looking at the result).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, yes.
I thought about refactoring this whole error handling in another gh issue (basically differentiate between success, failure(case failed) and error (prerequisites etc failed). But you are right, should at least be consistent for now.
@@ -151,6 +151,9 @@ def run_tests_for_sender_pod(sender_pod, cases): | |||
# TODO check if network ns is None -> HostNetwork is set | |||
results = {} | |||
for target, ports in cases[from_host_string].items(): | |||
if target is None: | |||
LOGGER.error("Target is none. Skipping") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I think I would prefer the results to contain an entry for these failures, where the state is ERR. This way other output formats would also show the error without checking logs and test results would not vanish "silently" (when only looking at the result).
|
||
|
||
def resolve_port_name(namespace, pod_label_selector_string, portname): | ||
api = k8s.client.CoreV1Api() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer passing the pod list. In test_orchestrator all calculations so far have been done on one momentary state of the API, so that the state is consistent across the different steps of translating test cases to the test run.
p for p in service_ports if p.target_port == port_int | ||
p | ||
for p in service_ports | ||
# TODO Services could also contain named ports. We should cross-compare here (resvoledName == numerical) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be done at another point, as we are iterating over numbered_ports
here, wouldn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is related to the following issue (I guess we think about different things):
We are iterating over numbered_ports
, but it could be the case, that the NetworkPolicy itself contains a numerical port (e.g. 80), but the Service uses a name (e.g. http), which is defined within the container.
In this case we are fine, since there is a service for port 80, but the comparison (e.g. if 80 == http) would fail and let the case fail, hence we would need to resolve the service ports here first to handle each edge case correctly.
I decided to skip this and only leave a comment as this is a edge case. I guess someone will use named ports in the NetPol and Svc accordingly in general.
src/illuminatio/test_orchestrator.py
Outdated
|
||
# Strip of the "-" and add it again after name has been resolved | ||
prefix = "-" if "-" in named_port else "" | ||
named_port_without_prefix = named_port.replace("-", "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Port names can also contain a -
, as far as I know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks.
str(service_ports_for_port[0].port), | ||
) | ||
else: | ||
# TODO change to exception (explanation see above) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
which is the explanation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same as for the numerical ports. Unfortunately the comment is not within the "review window":
"# TODO this was a hotfix for recipe 11, where ports 53 were allowed but not for any target,
# resulting in test to 53 being written despite no service matching them existing.
# That error should be handled in test generation, an exception here would be fine"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will provide fixes during this week (hopefully ;) )
p for p in service_ports if p.target_port == port_int | ||
p | ||
for p in service_ports | ||
# TODO Services could also contain named ports. We should cross-compare here (resvoledName == numerical) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is related to the following issue (I guess we think about different things):
We are iterating over numbered_ports
, but it could be the case, that the NetworkPolicy itself contains a numerical port (e.g. 80), but the Service uses a name (e.g. http), which is defined within the container.
In this case we are fine, since there is a service for port 80, but the comparison (e.g. if 80 == http) would fail and let the case fail, hence we would need to resolve the service ports here first to handle each edge case correctly.
I decided to skip this and only leave a comment as this is a edge case. I guess someone will use named ports in the NetPol and Svc accordingly in general.
@@ -264,6 +264,14 @@ def transform_results( | |||
LOGGER.debug("mapped_sender_pod: %s", mapped_sender_pod) | |||
LOGGER.debug("mapped_receiver_pod: %s", mapped_receiver_pod) | |||
LOGGER.debug("raw_results: %s", json.dumps(raw_results)) | |||
if mapped_receiver_pod not in raw_results[mapped_sender_pod]: | |||
LOGGER.error( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, yes.
I thought about refactoring this whole error handling in another gh issue (basically differentiate between success, failure(case failed) and error (prerequisites etc failed). But you are right, should at least be consistent for now.
str(service_ports_for_port[0].port), | ||
) | ||
else: | ||
# TODO change to exception (explanation see above) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same as for the numerical ports. Unfortunately the comment is not within the "review window":
"# TODO this was a hotfix for recipe 11, where ports 53 were allowed but not for any target,
# resulting in test to 53 being written despite no service matching them existing.
# That error should be handled in test generation, an exception here would be fine"
@@ -11,7 +11,8 @@ minikube start \ | |||
--cni=calico \ | |||
--container-runtime=docker \ | |||
--host-only-cidr=172.17.17.1/24 \ | |||
--kubernetes-version="${KUBERNETES_VERSION}" | |||
--kubernetes-version="${KUBERNETES_VERSION}" \ | |||
--insecure-registry=localhost:5000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kubelet was not able to pull the images from the internal minikube registry addon.
After adding this, it worked for me. But I did not further investigate logs besides the kubelet event, so I'm not sure whats the exact cause and why the pipeline does not have this issue.
src/illuminatio/test_orchestrator.py
Outdated
|
||
# Strip of the "-" and add it again after name has been resolved | ||
prefix = "-" if "-" in named_port else "" | ||
named_port_without_prefix = named_port.replace("-", "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks.
This reverts commit b2892d8.
Fixes #131
Changes
Illuminatio will lookup the port name during test case orchestration and will use numerical port by then. Currently the first matched pod/container's port will be used, which should be sufficient for the most cases. Note that there are some cases, which are still not yet covered and could lead to false positives for example (see 2. point in TODOs).
While testing the implementation using https://github.com/cloudogu/k8s-security-demos I have discovered some more issues.
Most of them need further investigation, but I have created some sanity checks (e.g. if =! none) to handle the errors and get at least a result. I will try to split those different related issues into actual issues to have them addressed separately.
Implementation
The named ports are printed out as is for the generated test cases, but are resolved later one to numerical ports. This is basically the same approach as for the pod label selectors, which are translated to actual pods or dummy pods.
TODOs
-> I will create a followup issue therefore since this requires architectural redesign in some places.
-> Egress is currently not (fully) supported anway(?), see Support egress policies #65. Since namedports are rarely used in egress policies, I decided to skip this feature for now.