mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
feat(namespace): add --namespaces argument and solve bugs (#3431)
This commit is contained in:
@@ -63,14 +63,13 @@ GCP Account: {Fore.YELLOW}[{profile}]{Style.RESET_ALL} GCP Project IDs: {Fore.Y
|
||||
# Get the current context
|
||||
cluster_name = self.context.get("context").get("cluster")
|
||||
user_name = self.context.get("context").get("user")
|
||||
namespace = self.context.get("namespace", "default")
|
||||
roles = self.get_context_user_roles()
|
||||
roles_str = ", ".join(roles) if roles else "No associated Roles"
|
||||
|
||||
report = f"""
|
||||
This report is being generated using the Kubernetes configuration below:
|
||||
|
||||
Kubernetes Cluster: {Fore.YELLOW}[{cluster_name}]{Style.RESET_ALL} User: {Fore.YELLOW}[{user_name}]{Style.RESET_ALL} Namespace: {Fore.YELLOW}[{namespace}]{Style.RESET_ALL} Roles: {Fore.YELLOW}[{roles_str}]{Style.RESET_ALL}
|
||||
Kubernetes Cluster: {Fore.YELLOW}[{cluster_name}]{Style.RESET_ALL} User: {Fore.YELLOW}[{user_name}]{Style.RESET_ALL} Namespaces: {Fore.YELLOW}[{', '.join(self.namespaces)}]{Style.RESET_ALL} Roles: {Fore.YELLOW}[{roles_str}]{Style.RESET_ALL}
|
||||
"""
|
||||
print(report)
|
||||
|
||||
@@ -370,7 +369,10 @@ Azure Identity Type: {Fore.YELLOW}[{audit_info.identity.identity_type}]{Style.RE
|
||||
logger.info("Checking if any context is set ...")
|
||||
context = arguments.get("context")
|
||||
|
||||
kubernetes_provider = Kubernetes_Provider(kubeconfig_file, context)
|
||||
logger.info("Checking if any namespace is set ...")
|
||||
namespaces = arguments.get("namespaces")
|
||||
|
||||
kubernetes_provider = Kubernetes_Provider(kubeconfig_file, context, namespaces)
|
||||
|
||||
(
|
||||
kubernetes_audit_info.api_client,
|
||||
|
||||
@@ -121,9 +121,7 @@ class Kubernetes_Output_Options(Provider_Output_Options):
|
||||
not hasattr(arguments, "output_filename")
|
||||
or arguments.output_filename is None
|
||||
):
|
||||
self.output_filename = (
|
||||
f"prowler-output-{audit_info.context['name']}-{output_file_timestamp}"
|
||||
)
|
||||
self.output_filename = f"prowler-output-{audit_info.context['name'].replace(':', '_').replace('/', '_')}-{output_file_timestamp}"
|
||||
else:
|
||||
self.output_filename = arguments.output_filename
|
||||
|
||||
|
||||
@@ -7,11 +7,7 @@ from prowler.lib.logger import logger
|
||||
|
||||
|
||||
class Kubernetes_Provider:
|
||||
def __init__(
|
||||
self,
|
||||
kubeconfig_file: str,
|
||||
context: str,
|
||||
):
|
||||
def __init__(self, kubeconfig_file: str, context: str, namespaces: str):
|
||||
logger.info("Instantiating Kubernetes Provider ...")
|
||||
self.api_client, self.context = self.__set_credentials__(
|
||||
kubeconfig_file, context
|
||||
@@ -19,17 +15,39 @@ class Kubernetes_Provider:
|
||||
if not self.api_client:
|
||||
logger.critical("Failed to set up a Kubernetes session.")
|
||||
sys.exit(1)
|
||||
if not namespaces:
|
||||
logger.info("Retrieving all namespaces ...")
|
||||
self.namespaces = self.get_all_namespaces()
|
||||
else:
|
||||
self.namespaces = namespaces
|
||||
|
||||
def __set_credentials__(self, kubeconfig_file, context):
|
||||
def __set_credentials__(self, kubeconfig_file, input_context):
|
||||
"""
|
||||
Set up credentials for Kubernetes provider.
|
||||
|
||||
:param kubeconfig_file: Path to kubeconfig file.
|
||||
:param input_context: Context to be used for Kubernetes session.
|
||||
:return: Tuple containing ApiClient and context information.
|
||||
"""
|
||||
try:
|
||||
if kubeconfig_file:
|
||||
# Use kubeconfig file if provided
|
||||
logger.info(f"Loading kubeconfig from file: {kubeconfig_file} ...")
|
||||
config.load_kube_config(
|
||||
config_file=os.path.abspath(kubeconfig_file), context=context
|
||||
config_file=os.path.abspath(kubeconfig_file), context=input_context
|
||||
)
|
||||
context = config.list_kube_config_contexts()[0][0]
|
||||
# Set context if input in argument
|
||||
if input_context:
|
||||
contexts = config.list_kube_config_contexts()[0]
|
||||
for context_item in contexts:
|
||||
if context_item["name"] == input_context:
|
||||
context = context_item
|
||||
else:
|
||||
# Get active context
|
||||
context = config.list_kube_config_contexts()[1]
|
||||
else:
|
||||
# Otherwise try to load in-cluster config
|
||||
logger.info("Loading in-cluster config ...")
|
||||
config.load_incluster_config()
|
||||
context = {
|
||||
"name": "In-Cluster",
|
||||
@@ -46,11 +64,43 @@ class Kubernetes_Provider:
|
||||
sys.exit(1)
|
||||
|
||||
def get_credentials(self):
|
||||
"""
|
||||
Get Kubernetes API client and context.
|
||||
|
||||
:return: Tuple containing ApiClient and context information.
|
||||
"""
|
||||
return self.api_client, self.context
|
||||
|
||||
def get_all_namespaces(self):
|
||||
"""
|
||||
Retrieve a list of all namespaces from a Kubernetes cluster.
|
||||
|
||||
:return: List of namespaces.
|
||||
"""
|
||||
try:
|
||||
v1 = client.CoreV1Api()
|
||||
namespace_list = v1.list_namespace(timeout_seconds=2, _request_timeout=2)
|
||||
namespaces = [item.metadata.name for item in namespace_list.items]
|
||||
logger.info("All namespaces retrieved successfully.")
|
||||
return namespaces
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
sys.exit()
|
||||
|
||||
def search_and_save_roles(
|
||||
self, roles: list, role_bindings, context_user: str, role_binding_type: str
|
||||
):
|
||||
"""
|
||||
Search and save roles based on role bindings and context user.
|
||||
|
||||
:param roles: List of roles to save.
|
||||
:param role_bindings: List of role bindings to search.
|
||||
:param context_user: Context user to match.
|
||||
:param role_binding_type: Type of role binding.
|
||||
:return: Updated list of roles.
|
||||
"""
|
||||
try:
|
||||
for rb in role_bindings:
|
||||
if rb.subjects:
|
||||
@@ -70,6 +120,11 @@ class Kubernetes_Provider:
|
||||
sys.exit(1)
|
||||
|
||||
def get_context_user_roles(self):
|
||||
"""
|
||||
Get roles assigned to the context user.
|
||||
|
||||
:return: List of roles assigned to the context user.
|
||||
"""
|
||||
try:
|
||||
rbac_api = client.RbacAuthorizationV1Api()
|
||||
context_user = self.context.get("context", {}).get("user", "")
|
||||
@@ -89,6 +144,7 @@ class Kubernetes_Provider:
|
||||
context_user,
|
||||
"Role",
|
||||
)
|
||||
logger.info("Context user roles retrieved successfully.")
|
||||
return roles
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
|
||||
@@ -11,18 +11,28 @@ from prowler.providers.common.provider import Provider
|
||||
|
||||
|
||||
class KubernetesProvider(Provider):
|
||||
# TODO change class name from Provider to Provider
|
||||
api_client: Any
|
||||
context: dict
|
||||
namespaces: list
|
||||
audit_resources: Optional[Any]
|
||||
audit_metadata: Optional[Any]
|
||||
audit_config: Optional[dict]
|
||||
|
||||
def __init__(self, arguments: Namespace):
|
||||
"""
|
||||
Initializes the KubernetesProvider instance.
|
||||
Args:
|
||||
arguments (dict): A dictionary containing configuration arguments.
|
||||
"""
|
||||
logger.info("Instantiating Kubernetes Provider ...")
|
||||
self.api_client, self.context = self.setup_session(
|
||||
arguments.kubeconfig_file, arguments.context
|
||||
)
|
||||
if not arguments.namespaces:
|
||||
logger.info("Retrieving all namespaces ...")
|
||||
self.namespaces = self.get_all_namespaces()
|
||||
else:
|
||||
self.namespaces = arguments.namespaces
|
||||
|
||||
if not self.api_client:
|
||||
logger.critical("Failed to set up a Kubernetes session.")
|
||||
@@ -30,16 +40,32 @@ class KubernetesProvider(Provider):
|
||||
if not arguments.only_logs:
|
||||
self.print_credentials()
|
||||
|
||||
def setup_session(self, kubeconfig_file, context):
|
||||
def setup_session(self, kubeconfig_file, input_context):
|
||||
"""
|
||||
Sets up the Kubernetes session.
|
||||
|
||||
Args:
|
||||
kubeconfig_file (str): Path to the kubeconfig file.
|
||||
input_context (str): Context name.
|
||||
|
||||
Returns:
|
||||
Tuple: A tuple containing the API client and the context.
|
||||
"""
|
||||
try:
|
||||
if kubeconfig_file:
|
||||
# Use kubeconfig file if provided
|
||||
logger.info(f"Using kubeconfig file: {kubeconfig_file}")
|
||||
config.load_kube_config(
|
||||
config_file=os.path.abspath(kubeconfig_file), context=context
|
||||
config_file=os.path.abspath(kubeconfig_file), context=input_context
|
||||
)
|
||||
context = config.list_kube_config_contexts()[0][0]
|
||||
if input_context:
|
||||
contexts = config.list_kube_config_contexts()[0]
|
||||
for context_item in contexts:
|
||||
if context_item["name"] == input_context:
|
||||
context = context_item
|
||||
else:
|
||||
# Otherwise try to load in-cluster config
|
||||
context = config.list_kube_config_contexts()[1]
|
||||
else:
|
||||
logger.info("Using in-cluster config")
|
||||
config.load_incluster_config()
|
||||
context = {
|
||||
"name": "In-Cluster",
|
||||
@@ -58,6 +84,18 @@ class KubernetesProvider(Provider):
|
||||
def search_and_save_roles(
|
||||
self, roles: list, role_bindings, context_user: str, role_binding_type: str
|
||||
):
|
||||
"""
|
||||
Searches for and saves roles.
|
||||
|
||||
Args:
|
||||
roles (list): A list to save the roles.
|
||||
role_bindings: Role bindings.
|
||||
context_user (str): Context user.
|
||||
role_binding_type (str): Role binding type.
|
||||
|
||||
Returns:
|
||||
list: A list containing the roles.
|
||||
"""
|
||||
try:
|
||||
for rb in role_bindings:
|
||||
if rb.subjects:
|
||||
@@ -77,11 +115,16 @@ class KubernetesProvider(Provider):
|
||||
sys.exit(1)
|
||||
|
||||
def get_context_user_roles(self):
|
||||
"""
|
||||
Retrieves the context user roles.
|
||||
|
||||
Returns:
|
||||
list: A list containing the context user roles.
|
||||
"""
|
||||
try:
|
||||
rbac_api = client.RbacAuthorizationV1Api()
|
||||
context_user = self.context.get("context", {}).get("user", "")
|
||||
roles = []
|
||||
# Search in ClusterRoleBindings
|
||||
roles = self.search_and_save_roles(
|
||||
roles,
|
||||
rbac_api.list_cluster_role_binding().items,
|
||||
@@ -89,7 +132,6 @@ class KubernetesProvider(Provider):
|
||||
"ClusterRole",
|
||||
)
|
||||
|
||||
# Search in RoleBindings for all namespaces
|
||||
roles = self.search_and_save_roles(
|
||||
roles,
|
||||
rbac_api.list_role_binding_for_all_namespaces().items,
|
||||
@@ -103,8 +145,30 @@ class KubernetesProvider(Provider):
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
def get_all_namespaces(self):
|
||||
"""
|
||||
Retrieves all namespaces.
|
||||
Returns:
|
||||
list: A list containing all namespace names.
|
||||
"""
|
||||
try:
|
||||
v1 = client.CoreV1Api()
|
||||
namespace_list = v1.list_namespace(timeout_seconds=2, _request_timeout=2)
|
||||
namespaces = [item.metadata.name for item in namespace_list.items]
|
||||
logger.info("All namespaces retrieved successfully.")
|
||||
return namespaces
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
sys.exit()
|
||||
|
||||
def get_pod_current_namespace(self):
|
||||
"""Retrieve the current namespace from the pod's mounted service account info."""
|
||||
"""
|
||||
Retrieves the current namespace from the pod's mounted service account info.
|
||||
Returns:
|
||||
str: The current namespace.
|
||||
"""
|
||||
try:
|
||||
with open(
|
||||
"/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r"
|
||||
@@ -117,7 +181,9 @@ class KubernetesProvider(Provider):
|
||||
return "default"
|
||||
|
||||
def print_credentials(self):
|
||||
# Get the current context
|
||||
"""
|
||||
Prints the Kubernetes credentials.
|
||||
"""
|
||||
if self.context.get("name") == "In-Cluster":
|
||||
report = f"""
|
||||
This report is being generated using the Kubernetes configuration below:
|
||||
@@ -128,13 +194,12 @@ Kubernetes Pod: {Fore.YELLOW}[prowler]{Style.RESET_ALL} Namespace: {Fore.YELLOW
|
||||
else:
|
||||
cluster_name = self.context.get("context").get("cluster")
|
||||
user_name = self.context.get("context").get("user")
|
||||
namespace = self.context.get("namespace", "default")
|
||||
roles = self.get_context_user_roles()
|
||||
roles_str = ", ".join(roles) if roles else "No associated Roles"
|
||||
|
||||
report = f"""
|
||||
This report is being generated using the Kubernetes configuration below:
|
||||
|
||||
Kubernetes Cluster: {Fore.YELLOW}[{cluster_name}]{Style.RESET_ALL} User: {Fore.YELLOW}[{user_name}]{Style.RESET_ALL} Namespace: {Fore.YELLOW}[{namespace}]{Style.RESET_ALL} Roles: {Fore.YELLOW}[{roles_str}]{Style.RESET_ALL}
|
||||
Kubernetes Cluster: {Fore.YELLOW}[{cluster_name}]{Style.RESET_ALL} User: {Fore.YELLOW}[{user_name}]{Style.RESET_ALL} Namespaces: {Fore.YELLOW}[{', '.join(self.namespaces)}]{Style.RESET_ALL} Roles: {Fore.YELLOW}[{roles_str}]{Style.RESET_ALL}
|
||||
"""
|
||||
print(report)
|
||||
|
||||
@@ -19,3 +19,9 @@ def init_parser(self):
|
||||
metavar="CONTEXT_NAME",
|
||||
help="The name of the kubeconfig context to use. By default, current_context from config file will be used.",
|
||||
)
|
||||
k8s_auth_subparser.add_argument(
|
||||
"--namespaces",
|
||||
nargs="+",
|
||||
metavar="NAMESPACES",
|
||||
help="The namespaces where to scan for the Kubernetes resources. By default, Prowler will scan all namespaces available.",
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Resource Management",
|
||||
"Performance Optimization"
|
||||
],
|
||||
"ServiceName": "kube-controller-manager",
|
||||
"ServiceName": "controller-manager",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "HostPorts",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Windows HostProcess Containers",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Privilege Escalation Control",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Capability Management",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Capability Assignment",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Host IPC Namespace",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Host Network Namespace",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Host PID Namespace",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Capability Control",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Privileged Containers",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Root User Control",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Best Practice",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Secrets Management",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Core",
|
||||
"ServiceName": "core",
|
||||
"SubServiceName": "Seccomp Profile",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -15,7 +15,7 @@ class Core(KubernetesService):
|
||||
def __init__(self, audit_info):
|
||||
super().__init__(audit_info)
|
||||
self.client = client.CoreV1Api(self.api_client)
|
||||
|
||||
self.namespaces = audit_info.namespaces
|
||||
self.pods = {}
|
||||
self.__get_pods__()
|
||||
self.config_maps = {}
|
||||
@@ -26,7 +26,8 @@ class Core(KubernetesService):
|
||||
|
||||
def __get_pods__(self):
|
||||
try:
|
||||
pods = self.client.list_pod_for_all_namespaces()
|
||||
for namespace in self.namespaces:
|
||||
pods = self.client.list_namespaced_pod(namespace)
|
||||
for pod in pods.items:
|
||||
pod_containers = {}
|
||||
containers = pod.spec.containers if pod.spec.containers else []
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "Kubernetes API",
|
||||
"ServiceName": "RBAC",
|
||||
"SubServiceName": "Secrets Management",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"Cluster Performance",
|
||||
"Cluster Security"
|
||||
],
|
||||
"ServiceName": "kube-scheduler",
|
||||
"ServiceName": "scheduler",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
|
||||
@@ -1145,6 +1145,14 @@ class Test_Parser:
|
||||
assert parsed.provider == "kubernetes"
|
||||
assert parsed.context == context
|
||||
|
||||
def test_parser_kubernetes_auth_namespace(self):
|
||||
argument = "--namespaces"
|
||||
namespaces = ["default", "kube-system"]
|
||||
command = [prowler_command, "kubernetes", argument, namespaces]
|
||||
parsed = self.parser.parse(command)
|
||||
assert parsed.provider == "kubernetes"
|
||||
assert parsed.namespaces == namespaces
|
||||
|
||||
def test_validate_azure_region_valid_regions(self):
|
||||
expected_regions = [
|
||||
"AzureChinaCloud",
|
||||
|
||||
Reference in New Issue
Block a user