From f2717bc10593f01509733f511d2244625e8af97c Mon Sep 17 00:00:00 2001 From: Joshua Kurz Date: Thu, 23 Feb 2023 22:30:24 -0500 Subject: [PATCH 1/6] feat(scalability): adding checks for compression and skipped file --- .../cluster_wide/scalability/control_plane.py | 15 +++++++++++++++ .../cluster_wide/scalability/skipped.json | 12 ++++++++++++ tests/test_scalability_control_plane.py | 19 ++++++++++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 hardeneks/cluster_wide/scalability/skipped.json diff --git a/hardeneks/cluster_wide/scalability/control_plane.py b/hardeneks/cluster_wide/scalability/control_plane.py index 7cae80a..1eb6398 100644 --- a/hardeneks/cluster_wide/scalability/control_plane.py +++ b/hardeneks/cluster_wide/scalability/control_plane.py @@ -22,3 +22,18 @@ def check_EKS_version(resources: Resources): return False return True + + +def check_kubectl_compression(resources: Resources): + _, active_context = kubernetes.config.list_kube_config_contexts() + if active_context.get("context", {}).get("disable-compression") != True: + console.print( + Panel( + f"[red]Disable kubectl Compression should equal True", + subtitle="[link=https://aws.github.io/aws-eks-best-practices/scalability/docs/control-plane/#disable-kubectl-compression]Click to see the guide[/link]", + ) + ) + console.print() + return False + + return True diff --git a/hardeneks/cluster_wide/scalability/skipped.json b/hardeneks/cluster_wide/scalability/skipped.json new file mode 100644 index 0000000..114447d --- /dev/null +++ b/hardeneks/cluster_wide/scalability/skipped.json @@ -0,0 +1,12 @@ +[{ + "name": "Limit workload and node bursting", + "link": "https://aws.github.io/aws-eks-best-practices/scalability/docs/control-plane/#limit-workload-and-node-bursting" +}, +{ + "name": "Scale nodes and pods down safely", + "link": "https://aws.github.io/aws-eks-best-practices/scalability/docs/control-plane/#scale-nodes-and-pods-down-safely" +}, +{ + "name": "Use Client-Side Cache when running Kubectl", + "link": "https://aws.github.io/aws-eks-best-practices/scalability/docs/control-plane/#use-client-side-cache-when-running-kubectl" +}] \ No newline at end of file diff --git a/tests/test_scalability_control_plane.py b/tests/test_scalability_control_plane.py index 13ffaa0..0114c6a 100644 --- a/tests/test_scalability_control_plane.py +++ b/tests/test_scalability_control_plane.py @@ -1,7 +1,10 @@ from hardeneks.resources import Resources from unittest.mock import patch -from hardeneks.cluster_wide.scalability.control_plane import check_EKS_version +from hardeneks.cluster_wide.scalability.control_plane import ( + check_EKS_version, + check_kubectl_compression +) class Version: @@ -21,3 +24,17 @@ def test_check_EKS_version(mocked_client): assert check_EKS_version(namespaced_resources) mocked_client.return_value = Version("24") assert check_EKS_version(namespaced_resources) + +@patch("kubernetes.config.list_kube_config_contexts") +def test_check_kubectl_compression(mocked_client): + namespaced_resources = Resources( + "some_region", "some_context", "some_cluster", [] + ) + mocked_client.return_value = None, {'context': {'cluster': 'test', 'user': 'foo', 'disable-compression': True}, 'name': 'foobarcluster'} + assert check_kubectl_compression(namespaced_resources) + mocked_client.return_value = None, {'context': {'cluster': 'test', 'user': 'foo'}, 'name': 'foobarcluster'} + assert not check_kubectl_compression(namespaced_resources) + mocked_client.return_value = None, {'name': 'foobarcluster'} + assert not check_kubectl_compression(namespaced_resources) + mocked_client.return_value = None, {'context': {'cluster': 'test', 'user': 'foo', 'disable-compression': False}, 'name': 'foobarcluster'} + assert not check_kubectl_compression(namespaced_resources) \ No newline at end of file From 7a9e94aa41656629b70d15ef601a6a3cd4276ad5 Mon Sep 17 00:00:00 2001 From: Joshua Kurz Date: Wed, 1 Mar 2023 16:02:10 -0500 Subject: [PATCH 2/6] feat(scalability): adding generic get_kube_config and getting clusters to check --- hardeneks/__init__.py | 14 ++++---- .../cluster_wide/scalability/control_plane.py | 35 +++++++++++-------- hardeneks/helpers.py | 17 +++++++++ hardeneks/resources.py | 3 +- tests/test_scalability_control_plane.py | 19 ++++++---- 5 files changed, 57 insertions(+), 31 deletions(-) create mode 100644 hardeneks/helpers.py diff --git a/hardeneks/__init__.py b/hardeneks/__init__.py index 0913747..08a7b13 100644 --- a/hardeneks/__init__.py +++ b/hardeneks/__init__.py @@ -2,7 +2,7 @@ from pathlib import Path from pkg_resources import resource_filename import tempfile -import urllib3 + import yaml from botocore.exceptions import EndpointConnectionError @@ -16,6 +16,7 @@ Resources, ) from .harden import harden +from hardeneks import helpers app = typer.Typer() @@ -66,14 +67,10 @@ def _get_cluster_name(context, region): def _get_region(): return boto3.session.Session().region_name - -def _load_kube_config(): - urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - kube_config_orig = f"{Path.home()}/.kube/config" +def _add_tls_verify(): + kubeconfig = helpers.get_kube_config() tmp_config = tempfile.NamedTemporaryFile().name - with open(kube_config_orig, "r") as fd: - kubeconfig = yaml.safe_load(fd) for cluster in kubeconfig["clusters"]: cluster["cluster"]["insecure-skip-tls-verify"] = True with open(tmp_config, "w") as fd: @@ -133,8 +130,9 @@ def run_hardeneks( """ if insecure_skip_tls_verify: - _load_kube_config() + _add_tls_verify() else: + # should pass in config file kubernetes.config.load_kube_config(context=context) context = _get_current_context(context) diff --git a/hardeneks/cluster_wide/scalability/control_plane.py b/hardeneks/cluster_wide/scalability/control_plane.py index 1eb6398..3c76c07 100644 --- a/hardeneks/cluster_wide/scalability/control_plane.py +++ b/hardeneks/cluster_wide/scalability/control_plane.py @@ -1,9 +1,10 @@ import re -from rich.panel import Panel +import urllib3 import kubernetes - +from rich.panel import Panel +from hardeneks import helpers from hardeneks import console -from ...resources import Resources +from hardeneks import Resources def check_EKS_version(resources: Resources): @@ -23,17 +24,23 @@ def check_EKS_version(resources: Resources): return True - +# +# check_kubectl_compression +# checks all clusters in config for disable-compression flag set to true +# if any cluster does not have setting, it returns False def check_kubectl_compression(resources: Resources): - _, active_context = kubernetes.config.list_kube_config_contexts() - if active_context.get("context", {}).get("disable-compression") != True: - console.print( - Panel( - f"[red]Disable kubectl Compression should equal True", - subtitle="[link=https://aws.github.io/aws-eks-best-practices/scalability/docs/control-plane/#disable-kubectl-compression]Click to see the guide[/link]", + kubeconfig = helpers.get_kube_config() + isSetCorrectly = True + for cluster in kubeconfig.get("clusters", []): + clusterName = cluster.get("name", "NoName") + if cluster.get("cluster", {}).get("disable-compression", False) != True: + isSetCorrectly = False + console.print( + Panel( + f"[red]DisableCompression in Cluster {clusterName} should equal True", + subtitle="[link=https://aws.github.io/aws-eks-best-practices/scalability/docs/control-plane/#disable-kubectl-compression]Click to see the guide[/link]", + ) ) - ) - console.print() - return False + console.print() - return True + return isSetCorrectly diff --git a/hardeneks/helpers.py b/hardeneks/helpers.py new file mode 100644 index 0000000..5f39fd8 --- /dev/null +++ b/hardeneks/helpers.py @@ -0,0 +1,17 @@ +from pathlib import Path +import urllib3 +import yaml + +# +# get_kube_config +# returns kube config in json +# +# we need to update this function to take in a config string, so users can pass in kubeconfig as a param +def get_kube_config(): + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + # need to fix this, so user can pass in .kube/config as a param (joshkurz) + kube_config_orig = f"{Path.home()}/.kube/config" + + with open(kube_config_orig, "r") as fd: + kubeconfig = yaml.safe_load(fd) + return kubeconfig \ No newline at end of file diff --git a/hardeneks/resources.py b/hardeneks/resources.py index 3097aab..08a64f4 100644 --- a/hardeneks/resources.py +++ b/hardeneks/resources.py @@ -1,6 +1,5 @@ from kubernetes import client - class Resources: def __init__(self, region, context, cluster, namespaces): self.region = region @@ -68,4 +67,4 @@ def set_resources(self): client.AutoscalingV1Api() .list_namespaced_horizontal_pod_autoscaler(self.namespace) .items - ) + ) \ No newline at end of file diff --git a/tests/test_scalability_control_plane.py b/tests/test_scalability_control_plane.py index 0114c6a..b1716af 100644 --- a/tests/test_scalability_control_plane.py +++ b/tests/test_scalability_control_plane.py @@ -1,5 +1,6 @@ from hardeneks.resources import Resources from unittest.mock import patch +from hardeneks import helpers from hardeneks.cluster_wide.scalability.control_plane import ( check_EKS_version, @@ -25,16 +26,20 @@ def test_check_EKS_version(mocked_client): mocked_client.return_value = Version("24") assert check_EKS_version(namespaced_resources) -@patch("kubernetes.config.list_kube_config_contexts") -def test_check_kubectl_compression(mocked_client): +@patch(helpers.__name__ + ".get_kube_config") +def test_check_kubectl_compression(mocked_helpers): namespaced_resources = Resources( "some_region", "some_context", "some_cluster", [] ) - mocked_client.return_value = None, {'context': {'cluster': 'test', 'user': 'foo', 'disable-compression': True}, 'name': 'foobarcluster'} + mocked_helpers.return_value = {'clusters': [{'cluster': {'server': 'testtest', 'disable-compression': True}, 'name': 'foobarcluster'}]} assert check_kubectl_compression(namespaced_resources) - mocked_client.return_value = None, {'context': {'cluster': 'test', 'user': 'foo'}, 'name': 'foobarcluster'} + mocked_helpers.return_value = {'clusters': [{'cluster': {'server': 'testtest', 'disable-compression': True}, 'name': 'foobarcluster6'},{'cluster': {'server': 'testtest', 'disable-compression': True}, 'name': 'foobarcluster2'}]} + assert check_kubectl_compression(namespaced_resources) + mocked_helpers.return_value = {'clusters': [{'cluster': {'server': 'testtest', 'disable-compression': True}, 'name': 'foobarcluster3'}, {'cluster': {'server': 'testtest', 'disable-compression': False}, 'name': 'foobarcluster4'}]} + assert not check_kubectl_compression(namespaced_resources) + mocked_helpers.return_value = {'clusters': [{'cluster': {'test': 'user'}, 'name': 'foobarcluster7'}]} assert not check_kubectl_compression(namespaced_resources) - mocked_client.return_value = None, {'name': 'foobarcluster'} + mocked_helpers.return_value = {'clusters': [{}]} assert not check_kubectl_compression(namespaced_resources) - mocked_client.return_value = None, {'context': {'cluster': 'test', 'user': 'foo', 'disable-compression': False}, 'name': 'foobarcluster'} - assert not check_kubectl_compression(namespaced_resources) \ No newline at end of file + mocked_helpers.return_value = {} + assert check_kubectl_compression(namespaced_resources) \ No newline at end of file From 9bde5ca08ab296e765a73856f0a72efaf4764825 Mon Sep 17 00:00:00 2001 From: Joshua Kurz Date: Fri, 3 Mar 2023 14:37:00 -0500 Subject: [PATCH 3/6] fix(scalability): only checking current cluster --- .../cluster_wide/scalability/control_plane.py | 24 +++++++++++-------- hardeneks/config.yaml | 1 + tests/test_scalability_control_plane.py | 8 +++---- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/hardeneks/cluster_wide/scalability/control_plane.py b/hardeneks/cluster_wide/scalability/control_plane.py index 3c76c07..d8ad7e6 100644 --- a/hardeneks/cluster_wide/scalability/control_plane.py +++ b/hardeneks/cluster_wide/scalability/control_plane.py @@ -30,17 +30,21 @@ def check_EKS_version(resources: Resources): # if any cluster does not have setting, it returns False def check_kubectl_compression(resources: Resources): kubeconfig = helpers.get_kube_config() - isSetCorrectly = True + isSetCorrectly = False for cluster in kubeconfig.get("clusters", []): - clusterName = cluster.get("name", "NoName") - if cluster.get("cluster", {}).get("disable-compression", False) != True: - isSetCorrectly = False - console.print( - Panel( - f"[red]DisableCompression in Cluster {clusterName} should equal True", - subtitle="[link=https://aws.github.io/aws-eks-best-practices/scalability/docs/control-plane/#disable-kubectl-compression]Click to see the guide[/link]", + clusterName = cluster.get("name", None) + if (clusterName == resources.cluster): + if cluster.get("cluster", {}).get("disable-compression", False) != True: + console.print( + Panel( + f"[red]`disable-compression` in Cluster {clusterName} should equal True", + subtitle="[link=https://aws.github.io/aws-eks-best-practices/scalability/docs/control-plane/#disable-kubectl-compression]Click to see the guide[/link]", + ) ) - ) - console.print() + console.print() + else: + isSetCorrectly = True + break + return isSetCorrectly diff --git a/hardeneks/config.yaml b/hardeneks/config.yaml index a1459cc..9eb5452 100644 --- a/hardeneks/config.yaml +++ b/hardeneks/config.yaml @@ -54,6 +54,7 @@ rules: scalability: control_plane: - check_EKS_version + - check_kubectl_compression namespace_based: security: iam: diff --git a/tests/test_scalability_control_plane.py b/tests/test_scalability_control_plane.py index b1716af..9b11a2e 100644 --- a/tests/test_scalability_control_plane.py +++ b/tests/test_scalability_control_plane.py @@ -29,17 +29,17 @@ def test_check_EKS_version(mocked_client): @patch(helpers.__name__ + ".get_kube_config") def test_check_kubectl_compression(mocked_helpers): namespaced_resources = Resources( - "some_region", "some_context", "some_cluster", [] + "some_region", "some_context", "foobarcluster", [] ) mocked_helpers.return_value = {'clusters': [{'cluster': {'server': 'testtest', 'disable-compression': True}, 'name': 'foobarcluster'}]} assert check_kubectl_compression(namespaced_resources) - mocked_helpers.return_value = {'clusters': [{'cluster': {'server': 'testtest', 'disable-compression': True}, 'name': 'foobarcluster6'},{'cluster': {'server': 'testtest', 'disable-compression': True}, 'name': 'foobarcluster2'}]} + mocked_helpers.return_value = {'clusters': [{'cluster': {'server': 'testtest', 'disable-compression': True}, 'name': 'foobarcluster'}, {'cluster': {'server': 'testtest', 'disable-compression': False}, 'name': 'foobarcluster2'}]} assert check_kubectl_compression(namespaced_resources) - mocked_helpers.return_value = {'clusters': [{'cluster': {'server': 'testtest', 'disable-compression': True}, 'name': 'foobarcluster3'}, {'cluster': {'server': 'testtest', 'disable-compression': False}, 'name': 'foobarcluster4'}]} + mocked_helpers.return_value = {'clusters': [{'cluster': {'server': 'testtest', 'disable-compression': False}, 'name': 'foobarcluster'}, {'cluster': {'server': 'testtest', 'disable-compression': False}, 'name': 'foobarcluster4'}]} assert not check_kubectl_compression(namespaced_resources) mocked_helpers.return_value = {'clusters': [{'cluster': {'test': 'user'}, 'name': 'foobarcluster7'}]} assert not check_kubectl_compression(namespaced_resources) mocked_helpers.return_value = {'clusters': [{}]} assert not check_kubectl_compression(namespaced_resources) mocked_helpers.return_value = {} - assert check_kubectl_compression(namespaced_resources) \ No newline at end of file + assert not check_kubectl_compression(namespaced_resources) \ No newline at end of file From 275b25b322f678896bbe34be6767ed58fca07742 Mon Sep 17 00:00:00 2001 From: Joshua Kurz Date: Fri, 3 Mar 2023 14:42:13 -0500 Subject: [PATCH 4/6] fix(scalability): fixing up some things --- hardeneks/cluster_wide/scalability/control_plane.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hardeneks/cluster_wide/scalability/control_plane.py b/hardeneks/cluster_wide/scalability/control_plane.py index d8ad7e6..48bfdba 100644 --- a/hardeneks/cluster_wide/scalability/control_plane.py +++ b/hardeneks/cluster_wide/scalability/control_plane.py @@ -1,5 +1,4 @@ import re -import urllib3 import kubernetes from rich.panel import Panel from hardeneks import helpers From 09468c8af548bd15e8a910f9dd0494f8bf977592 Mon Sep 17 00:00:00 2001 From: Joshua Kurz Date: Fri, 3 Mar 2023 14:43:10 -0500 Subject: [PATCH 5/6] fix(scalability): fixing up some things 2 --- hardeneks/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hardeneks/__init__.py b/hardeneks/__init__.py index 08a7b13..275a63e 100644 --- a/hardeneks/__init__.py +++ b/hardeneks/__init__.py @@ -2,7 +2,6 @@ from pathlib import Path from pkg_resources import resource_filename import tempfile - import yaml from botocore.exceptions import EndpointConnectionError From bce8c040b4f9acb21f117cd9ed716c5f5e7236a9 Mon Sep 17 00:00:00 2001 From: Joshua Kurz Date: Fri, 3 Mar 2023 15:35:07 -0500 Subject: [PATCH 6/6] fix(scalability): checking clusterName in cluster.name --- hardeneks/cluster_wide/scalability/control_plane.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hardeneks/cluster_wide/scalability/control_plane.py b/hardeneks/cluster_wide/scalability/control_plane.py index 48bfdba..b972c65 100644 --- a/hardeneks/cluster_wide/scalability/control_plane.py +++ b/hardeneks/cluster_wide/scalability/control_plane.py @@ -31,8 +31,8 @@ def check_kubectl_compression(resources: Resources): kubeconfig = helpers.get_kube_config() isSetCorrectly = False for cluster in kubeconfig.get("clusters", []): - clusterName = cluster.get("name", None) - if (clusterName == resources.cluster): + clusterName = cluster.get("name", "") + if (resources.cluster in clusterName): if cluster.get("cluster", {}).get("disable-compression", False) != True: console.print( Panel(