feat(namespace): add --namespaces argument and solve bugs (#3431)

This commit is contained in:
Sergio Garcia
2024-02-28 19:33:29 +01:00
committed by GitHub
parent 3e6b76df76
commit b0f2f34d3b
23 changed files with 227 additions and 91 deletions

View File

@@ -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,

View File

@@ -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

View File

@@ -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(

View File

@@ -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:
context = config.list_kube_config_contexts()[1]
else:
# Otherwise try to load in-cluster config
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)

View File

@@ -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.",
)

View File

@@ -6,7 +6,7 @@
"Resource Management",
"Performance Optimization"
],
"ServiceName": "kube-controller-manager",
"ServiceName": "controller-manager",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "HostPorts",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Windows HostProcess Containers",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Privilege Escalation Control",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Capability Management",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Capability Assignment",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Host IPC Namespace",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Host Network Namespace",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Host PID Namespace",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Capability Control",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Privileged Containers",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Root User Control",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Best Practice",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Secrets Management",
"ResourceIdTemplate": "",
"Severity": "medium",

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Seccomp Profile",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -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,55 +26,56 @@ class Core(KubernetesService):
def __get_pods__(self):
try:
pods = self.client.list_pod_for_all_namespaces()
for pod in pods.items:
pod_containers = {}
containers = pod.spec.containers if pod.spec.containers else []
init_containers = (
pod.spec.init_containers if pod.spec.init_containers else []
)
ephemeral_containers = (
pod.spec.ephemeral_containers
if pod.spec.ephemeral_containers
else []
)
for container in containers + init_containers + ephemeral_containers:
pod_containers[container.name] = Container(
name=container.name,
image=container.image,
command=container.command if container.command else None,
ports=[
{"containerPort": port.container_port}
for port in container.ports
]
if container.ports
else None,
env=[
{"name": env.name, "value": env.value}
for env in container.env
]
if container.env
else None,
security_context=container.security_context,
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 []
init_containers = (
pod.spec.init_containers if pod.spec.init_containers else []
)
ephemeral_containers = (
pod.spec.ephemeral_containers
if pod.spec.ephemeral_containers
else []
)
for container in containers + init_containers + ephemeral_containers:
pod_containers[container.name] = Container(
name=container.name,
image=container.image,
command=container.command if container.command else None,
ports=[
{"containerPort": port.container_port}
for port in container.ports
]
if container.ports
else None,
env=[
{"name": env.name, "value": env.value}
for env in container.env
]
if container.env
else None,
security_context=container.security_context,
)
self.pods[pod.metadata.uid] = Pod(
name=pod.metadata.name,
uid=pod.metadata.uid,
namespace=pod.metadata.namespace,
labels=pod.metadata.labels,
annotations=pod.metadata.annotations,
node_name=pod.spec.node_name,
service_account=pod.spec.service_account_name,
status_phase=pod.status.phase,
pod_ip=pod.status.pod_ip,
host_ip=pod.status.host_ip,
host_pid=pod.spec.host_pid,
host_ipc=pod.spec.host_ipc,
host_network=pod.spec.host_network,
security_context=pod.spec.security_context,
containers=pod_containers,
)
self.pods[pod.metadata.uid] = Pod(
name=pod.metadata.name,
uid=pod.metadata.uid,
namespace=pod.metadata.namespace,
labels=pod.metadata.labels,
annotations=pod.metadata.annotations,
node_name=pod.spec.node_name,
service_account=pod.spec.service_account_name,
status_phase=pod.status.phase,
pod_ip=pod.status.pod_ip,
host_ip=pod.status.host_ip,
host_pid=pod.spec.host_pid,
host_ipc=pod.spec.host_ipc,
host_network=pod.spec.host_network,
security_context=pod.spec.security_context,
containers=pod_containers,
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"

View File

@@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Kubernetes API",
"ServiceName": "RBAC",
"SubServiceName": "Secrets Management",
"ResourceIdTemplate": "",
"Severity": "high",

View File

@@ -6,7 +6,7 @@
"Cluster Performance",
"Cluster Security"
],
"ServiceName": "kube-scheduler",
"ServiceName": "scheduler",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",

View File

@@ -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",