From c8d2a44ab0f4cf1b31b8c92e7ec0b31ab3e24408 Mon Sep 17 00:00:00 2001 From: Sergio Garcia Date: Mon, 12 May 2025 06:28:04 -0400 Subject: [PATCH] feat(kubernetes): allow setting cluster name in in-cluster mode (#7695) --- docs/tutorials/kubernetes/in-cluster.md | 16 +++++++ prowler/providers/common/provider.py | 1 + .../kubernetes/kubernetes_provider.py | 17 +++++-- .../kubernetes/lib/arguments/arguments.py | 6 +++ .../kubernetes/kubernetes_provider_test.py | 48 +++++++++++++++++++ 5 files changed, 84 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/kubernetes/in-cluster.md b/docs/tutorials/kubernetes/in-cluster.md index 9b16b21f09..667b1f6436 100644 --- a/docs/tutorials/kubernetes/in-cluster.md +++ b/docs/tutorials/kubernetes/in-cluster.md @@ -20,3 +20,19 @@ kubectl logs prowler-XXXXX --namespace prowler-ns ???+ note By default, `prowler` will scan all namespaces in your active Kubernetes context. Use the [`--namespace`](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/kubernetes/namespace/) flag to specify the namespace(s) to be scanned. + +???+ tip "Identifying the cluster in reports" + When running in in-cluster mode, the Kubernetes API does not expose the actual cluster name by default. + + To uniquely identify the cluster in logs and reports, you can: + + - Use the `--cluster-name` flag to manually set the cluster name: + ```bash + prowler -p kubernetes --cluster-name production-cluster + ``` + - Or set the `CLUSTER_NAME` environment variable: + ```yaml + env: + - name: CLUSTER_NAME + value: production-cluster + ``` diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index aaa94b21d1..118346d6b5 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -207,6 +207,7 @@ class Provider(ABC): kubeconfig_file=arguments.kubeconfig_file, context=arguments.context, namespace=arguments.namespace, + cluster_name=arguments.cluster_name, config_path=arguments.config_file, mutelist_path=arguments.mutelist_file, fixer_config=fixer_config, diff --git a/prowler/providers/kubernetes/kubernetes_provider.py b/prowler/providers/kubernetes/kubernetes_provider.py index 0f11a41fb0..160ef4e897 100644 --- a/prowler/providers/kubernetes/kubernetes_provider.py +++ b/prowler/providers/kubernetes/kubernetes_provider.py @@ -70,6 +70,7 @@ class KubernetesProvider(Provider): kubeconfig_file: str = None, context: str = None, namespace: list = None, + cluster_name: str = None, config_path: str = None, config_content: dict = {}, fixer_config: dict = {}, @@ -84,6 +85,7 @@ class KubernetesProvider(Provider): kubeconfig_file (str): Path to the kubeconfig file. kubeconfig_content (str or dict): Content of the kubeconfig file. context (str): Context name. + cluster_name (str): Cluster name. namespace (list): List of namespaces. config_content (dict): Audit configuration. config_path (str): Path to the configuration file. @@ -147,7 +149,9 @@ class KubernetesProvider(Provider): """ logger.info("Instantiating Kubernetes Provider ...") - self._session = self.setup_session(kubeconfig_file, kubeconfig_content, context) + self._session = self.setup_session( + kubeconfig_file, kubeconfig_content, context, cluster_name + ) if not namespace: logger.info("Retrieving all namespaces ...") self._namespaces = self.get_all_namespaces() @@ -227,6 +231,7 @@ class KubernetesProvider(Provider): kubeconfig_file: str = None, kubeconfig_content: Union[dict, str] = None, context: str = None, + cluster_name: str = None, ) -> KubernetesSession: """ Sets up the Kubernetes session. @@ -235,7 +240,7 @@ class KubernetesProvider(Provider): kubeconfig_file (str): Path to the kubeconfig file. kubeconfig_content (str or dict): Content of the kubeconfig file. context (str): Context name. - + cluster_name (str): Cluster name. Returns: Tuple: A tuple containing the API client and the context. @@ -270,11 +275,15 @@ class KubernetesProvider(Provider): # If the kubeconfig file is not found, try to use the in-cluster config logger.info("Using in-cluster config") config.load_incluster_config() + # Use CLI flag or env var to set cluster name + resolved_cluster_name = cluster_name or os.getenv( + "CLUSTER_NAME", "in-cluster" + ) context = { "name": "In-Cluster", "context": { - "cluster": "in-cluster", # Placeholder, as the real cluster name is not available - "user": "service-account-name", # Also a placeholder + "cluster": resolved_cluster_name, + "user": "service-account-name", }, } return KubernetesSession( diff --git a/prowler/providers/kubernetes/lib/arguments/arguments.py b/prowler/providers/kubernetes/lib/arguments/arguments.py index c6aeace363..a3eb4b55b1 100644 --- a/prowler/providers/kubernetes/lib/arguments/arguments.py +++ b/prowler/providers/kubernetes/lib/arguments/arguments.py @@ -27,3 +27,9 @@ def init_parser(self): metavar="NAMESPACES", help="The namespaces where to scan for the Kubernetes resources. By default, Prowler will scan all namespaces available.", ) + k8s_auth_subparser.add_argument( + "--cluster-name", + nargs="?", + metavar="CLUSTER_NAME", + help="Manually specify the cluster name in in-cluster mode, by default it will be 'in-cluster'", + ) diff --git a/tests/providers/kubernetes/kubernetes_provider_test.py b/tests/providers/kubernetes/kubernetes_provider_test.py index 681e9b9b39..411693117d 100644 --- a/tests/providers/kubernetes/kubernetes_provider_test.py +++ b/tests/providers/kubernetes/kubernetes_provider_test.py @@ -1,6 +1,8 @@ from argparse import Namespace from unittest.mock import patch +from kubernetes.config.config_exception import ConfigException + from kubernetes import client from prowler.config.config import ( default_config_file_path, @@ -305,3 +307,49 @@ class TestKubernetesProvider: assert connection.is_connected assert connection.error is None + + def test_kubernetes_provider_incluster_with_env_var(self, monkeypatch): + monkeypatch.setenv("CLUSTER_NAME", "env-cluster-name") + + with ( + patch( + "kubernetes.config.load_kube_config", + side_effect=ConfigException("No kubeconfig"), + ), + patch("kubernetes.config.load_incluster_config", return_value=None), + patch("prowler.providers.kubernetes.kubernetes_provider.client.ApiClient"), + patch( + "prowler.providers.kubernetes.kubernetes_provider.KubernetesProvider.get_all_namespaces", + return_value=["default"], + ), + ): + session = KubernetesProvider.setup_session( + kubeconfig_file=None, + kubeconfig_content=None, + context=None, + cluster_name=None, + ) + assert isinstance(session, KubernetesSession) + assert session.context["context"]["cluster"] == "env-cluster-name" + + def test_kubernetes_provider_incluster_with_cli_flag(self): + with ( + patch( + "kubernetes.config.load_kube_config", + side_effect=ConfigException("No kubeconfig"), + ), + patch("kubernetes.config.load_incluster_config", return_value=None), + patch("prowler.providers.kubernetes.kubernetes_provider.client.ApiClient"), + patch( + "prowler.providers.kubernetes.kubernetes_provider.KubernetesProvider.get_all_namespaces", + return_value=["default"], + ), + ): + session = KubernetesProvider.setup_session( + kubeconfig_file=None, + kubeconfig_content=None, + context=None, + cluster_name="cli-cluster-name", + ) + assert isinstance(session, KubernetesSession) + assert session.context["context"]["cluster"] == "cli-cluster-name"