Skip to content

Commit

Permalink
feat(namespace): add --namespaces argument and solve bugs (prowler-…
Browse files Browse the repository at this point in the history
  • Loading branch information
MrCloudSec authored Feb 28, 2024
1 parent 3e6b76d commit b0f2f34
Show file tree
Hide file tree
Showing 23 changed files with 227 additions and 91 deletions.
8 changes: 5 additions & 3 deletions prowler/providers/common/audit_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,13 @@ def print_kubernetes_credentials(self, audit_info: Kubernetes_Audit_Info):
# 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)

Expand Down Expand Up @@ -370,7 +369,10 @@ def set_kubernetes_audit_info(self, arguments) -> Kubernetes_Audit_Info:
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,
Expand Down
4 changes: 1 addition & 3 deletions prowler/providers/common/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,7 @@ def __init__(self, arguments, audit_info, mutelist_file, bulk_checks_metadata):
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

Expand Down
72 changes: 64 additions & 8 deletions prowler/providers/kubernetes/kubernetes_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,47 @@


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
)
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, input_context):
"""
Set up credentials for Kubernetes provider.
def __set_credentials__(self, kubeconfig_file, context):
: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",
Expand All @@ -46,11 +64,43 @@ def __set_credentials__(self, kubeconfig_file, context):
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:
Expand All @@ -70,6 +120,11 @@ def search_and_save_roles(
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", "")
Expand All @@ -89,6 +144,7 @@ def get_context_user_roles(self):
context_user,
"Role",
)
logger.info("Context user roles retrieved successfully.")
return roles
except Exception as error:
logger.critical(
Expand Down
89 changes: 77 additions & 12 deletions prowler/providers/kubernetes/kubernetes_provider_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,61 @@


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.")
sys.exit(1)
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",
Expand All @@ -58,6 +84,18 @@ def setup_session(self, kubeconfig_file, context):
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:
Expand All @@ -77,19 +115,23 @@ def search_and_save_roles(
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,
context_user,
"ClusterRole",
)

# Search in RoleBindings for all namespaces
roles = self.search_and_save_roles(
roles,
rbac_api.list_role_binding_for_all_namespaces().items,
Expand All @@ -103,8 +145,30 @@ def get_context_user_roles(self):
)
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"
Expand All @@ -117,7 +181,9 @@ def get_pod_current_namespace(self):
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:
Expand All @@ -128,13 +194,12 @@ def print_credentials(self):
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)
6 changes: 6 additions & 0 deletions prowler/providers/kubernetes/lib/arguments/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"Resource Management",
"Performance Optimization"
],
"ServiceName": "kube-controller-manager",
"ServiceName": "controller-manager",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "HostPorts",
"ResourceIdTemplate": "",
"Severity": "high",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Windows HostProcess Containers",
"ResourceIdTemplate": "",
"Severity": "high",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Privilege Escalation Control",
"ResourceIdTemplate": "",
"Severity": "high",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Capability Management",
"ResourceIdTemplate": "",
"Severity": "high",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"Security",
"Configuration"
],
"ServiceName": "Core",
"ServiceName": "core",
"SubServiceName": "Capability Assignment",
"ResourceIdTemplate": "",
"Severity": "high",
Expand Down
Loading

0 comments on commit b0f2f34

Please sign in to comment.