mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
feat(kubelet): add 6 checks of Kubelet configuration files on the worker nodes (#3335)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import grp
|
||||
import json
|
||||
import os
|
||||
import pwd
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
@@ -8,6 +10,7 @@ from io import TextIOWrapper
|
||||
from ipaddress import ip_address
|
||||
from os.path import exists
|
||||
from time import mktime
|
||||
from typing import Optional
|
||||
|
||||
from detect_secrets import SecretsCollection
|
||||
from detect_secrets.settings import default_settings
|
||||
@@ -102,3 +105,60 @@ def outputs_unix_timestamp(is_unix_timestamp: bool, timestamp: datetime):
|
||||
else:
|
||||
timestamp = timestamp.isoformat()
|
||||
return timestamp
|
||||
|
||||
|
||||
def get_file_permissions(file_path: str) -> Optional[str]:
|
||||
"""
|
||||
Retrieves the permissions of a file.
|
||||
|
||||
Args:
|
||||
file_path (str): The path to the file.
|
||||
|
||||
Returns:
|
||||
Optional[str]: The file permissions in octal format, or None if an error occurs.
|
||||
"""
|
||||
try:
|
||||
# Get file status
|
||||
file_stat = os.stat(file_path)
|
||||
|
||||
# Extract permission bits using bitwise AND and formatting as octal
|
||||
permissions = oct(file_stat.st_mode & 0o777)
|
||||
return permissions
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"{file_path}: {e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def is_owned_by_root(file_path: str) -> bool:
|
||||
"""
|
||||
Checks if a file is owned by the root user and group.
|
||||
|
||||
Args:
|
||||
file_path (str): The path to the file.
|
||||
|
||||
Returns:
|
||||
bool: True if owned by root, False otherwise or None if file does not exist.
|
||||
"""
|
||||
try:
|
||||
# Get the file's status
|
||||
file_stat = os.stat(file_path)
|
||||
|
||||
# Get the user and group names from their IDs
|
||||
user_name = pwd.getpwuid(file_stat.st_uid).pw_name
|
||||
group_name = grp.getgrgid(file_stat.st_gid).gr_name
|
||||
|
||||
# Check if both user and group are 'root'
|
||||
return user_name == "root" and group_name == "root"
|
||||
|
||||
except FileNotFoundError as e:
|
||||
logger.error(
|
||||
f"{file_path}: {e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}"
|
||||
)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"{file_path}: {e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}"
|
||||
)
|
||||
return False
|
||||
|
||||
+1
-3
@@ -11,9 +11,7 @@ class core_minimize_admission_windows_hostprocess_containers(Check):
|
||||
report.resource_name = pod.name
|
||||
report.resource_id = pod.uid
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"Pod {pod.name} does not have the ability to run a Windows HostProcess."
|
||||
)
|
||||
report.status_extended = f"Pod {pod.name} does not have the ability to run a Windows HostProcess."
|
||||
|
||||
for container in pod.containers.values():
|
||||
if (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import socket
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
@@ -19,6 +20,9 @@ class Core(KubernetesService):
|
||||
self.__get_pods__()
|
||||
self.config_maps = {}
|
||||
self.__list_config_maps__()
|
||||
self.nodes = {}
|
||||
self.__list_nodes__()
|
||||
self.__in_worker_node__()
|
||||
|
||||
def __get_pods__(self):
|
||||
try:
|
||||
@@ -93,6 +97,41 @@ class Core(KubernetesService):
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def __list_nodes__(self):
|
||||
try:
|
||||
response = self.client.list_node()
|
||||
for node in response.items:
|
||||
node_model = Node(
|
||||
name=node.metadata.name,
|
||||
uid=node.metadata.uid,
|
||||
namespace=node.metadata.namespace
|
||||
if node.metadata.namespace
|
||||
else "cluster-wide",
|
||||
labels=node.metadata.labels,
|
||||
annotations=node.metadata.annotations,
|
||||
unschedulable=node.spec.unschedulable,
|
||||
node_info=node.status.node_info.to_dict()
|
||||
if node.status.node_info
|
||||
else None,
|
||||
)
|
||||
self.nodes[node.metadata.uid] = node_model
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def __in_worker_node__(self):
|
||||
try:
|
||||
hostname = socket.gethostname()
|
||||
for node in self.nodes.values():
|
||||
if hostname == node.name:
|
||||
node.inside = True
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Container:
|
||||
@@ -131,3 +170,14 @@ class ConfigMap(BaseModel):
|
||||
labels: Optional[dict]
|
||||
kubelet_args: list = []
|
||||
annotations: Optional[dict]
|
||||
|
||||
|
||||
class Node(BaseModel):
|
||||
name: str
|
||||
uid: str
|
||||
namespace: str
|
||||
labels: Optional[dict]
|
||||
annotations: Optional[dict]
|
||||
unschedulable: Optional[bool]
|
||||
node_info: Optional[dict]
|
||||
inside: bool = False
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"Provider": "kubernetes",
|
||||
"CheckID": "kubelet_conf_file_ownership",
|
||||
"CheckTitle": "Ensure kubelet.conf file ownership is set to root:root",
|
||||
"CheckType": [
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "kubelet",
|
||||
"SubServiceName": "Config File Ownership",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "KubernetesWorkerNode",
|
||||
"Description": "Ensure that the kubelet.conf file, which is the kubeconfig file for the node, has its file ownership set to root:root. This check verifies the proper ownership settings to maintain the security and integrity of the node's configuration.",
|
||||
"Risk": "Incorrect file ownership settings on kubelet.conf can lead to unauthorized access and potential security vulnerabilities.",
|
||||
"RelatedUrl": "https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/kubelet-integration/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "chown root:root /etc/kubernetes/kubelet.conf",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure kubelet.conf file ownership is correctly set to protect the node's configuration.",
|
||||
"Url": "https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"Node Security",
|
||||
"Compliance"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Regular checks of kubelet.conf file ownership are essential for maintaining node security."
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Kubernetes
|
||||
from prowler.lib.utils.utils import is_owned_by_root
|
||||
from prowler.providers.kubernetes.services.core.core_client import core_client
|
||||
|
||||
|
||||
class kubelet_conf_file_ownership(Check):
|
||||
def execute(self) -> Check_Report_Kubernetes:
|
||||
findings = []
|
||||
for node in core_client.nodes.values():
|
||||
report = Check_Report_Kubernetes(self.metadata())
|
||||
report.namespace = node.namespace
|
||||
report.resource_name = node.name
|
||||
report.resource_id = node.uid
|
||||
# It can only be checked if Prowler is being executed inside a worker node or if the file is the default one
|
||||
if node.inside:
|
||||
if is_owned_by_root("/etc/kubernetes/kubelet.conf") is None:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"kubelet.conf file not found in Node {node.name}, please verify kubelet.conf file ownership manually."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"kubelet.conf file ownership is set to root:root in Node {node.name}."
|
||||
if not is_owned_by_root("/etc/kubernetes/kubelet.conf"):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"kubelet.conf file ownership is not set to root:root in Node {node.name}."
|
||||
else:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Prowler is not being executed inside Node {node.name}, please verify kubelet.conf file ownership manually."
|
||||
findings.append(report)
|
||||
return findings
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"Provider": "kubernetes",
|
||||
"CheckID": "kubelet_conf_file_permissions",
|
||||
"CheckTitle": "Ensure kubelet.conf file permissions are set to 600 or more restrictive",
|
||||
"CheckType": [
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "kubelet",
|
||||
"SubServiceName": "Config File Permissions",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "KubernetesWorkerNode",
|
||||
"Description": "Ensure that the kubelet.conf file, which is the kubeconfig file for the node, has permissions set to 600 or more restrictive. This ensures the integrity and security of the node's configuration.",
|
||||
"Risk": "Improper permissions on kubelet.conf can expose sensitive configuration data, potentially leading to cluster security compromises.",
|
||||
"RelatedUrl": "https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/kubelet-integration/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "chmod 600 /etc/kubernetes/kubelet.conf",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure kubelet.conf file permissions are correctly set to protect the node's configuration.",
|
||||
"Url": "https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"Node Security",
|
||||
"Compliance"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Regular checks of kubelet.conf file permissions are essential for maintaining node security."
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Kubernetes
|
||||
from prowler.lib.utils.utils import get_file_permissions
|
||||
from prowler.providers.kubernetes.services.core.core_client import core_client
|
||||
|
||||
|
||||
class kubelet_conf_file_permissions(Check):
|
||||
def execute(self) -> Check_Report_Kubernetes:
|
||||
findings = []
|
||||
for node in core_client.nodes.values():
|
||||
report = Check_Report_Kubernetes(self.metadata())
|
||||
report.namespace = node.namespace
|
||||
report.resource_name = node.name
|
||||
report.resource_id = node.uid
|
||||
# It can only be checked if Prowler is being executed inside a worker node or if the file is the default one
|
||||
if node.inside:
|
||||
if not get_file_permissions("/etc/kubernetes/kubelet.conf"):
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Kubelet.conf file not found in Node {node.name}, please verify kubelet.conf file permissions manually."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"kubelet.conf file permissions are set to 600 or more restrictive in Node {node.name}."
|
||||
if get_file_permissions("/etc/kubernetes/kubelet.conf") > 0o600:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"kubelet.conf file permissions are not set to 600 or more restrictive in Node {node.name}."
|
||||
else:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Prowler is not being executed inside Node {node.name}, please verify kubelet.conf file permissions manually."
|
||||
findings.append(report)
|
||||
return findings
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"Provider": "kubernetes",
|
||||
"CheckID": "kubelet_config_yaml_ownership",
|
||||
"CheckTitle": "Validate kubelet config.yaml File Ownership",
|
||||
"CheckType": [
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "kubelet",
|
||||
"SubServiceName": "Config File Ownership",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "KubernetesWorkerNode",
|
||||
"Description": "Ensure that if the kubelet refers to a configuration file with the --config argument, that file is owned by root:root. The kubelet config file contains various critical parameters for the kubelet service on worker nodes, and its ownership should be strictly controlled.",
|
||||
"Risk": "Improper file ownership on kubelet config.yaml can expose sensitive data or allow unauthorized modifications.",
|
||||
"RelatedUrl": "https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "chown root:root /var/lib/kubelet/config.yaml",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Secure the kubelet configuration by enforcing strict file ownership.",
|
||||
"Url": "https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"Node Security",
|
||||
"Compliance"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Regularly verify the file ownership of kubelet config files to ensure they are not altered unexpectedly."
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Kubernetes
|
||||
from prowler.lib.utils.utils import is_owned_by_root
|
||||
from prowler.providers.kubernetes.services.core.core_client import core_client
|
||||
|
||||
|
||||
class kubelet_config_yaml_ownership(Check):
|
||||
def execute(self) -> Check_Report_Kubernetes:
|
||||
findings = []
|
||||
for node in core_client.nodes.values():
|
||||
report = Check_Report_Kubernetes(self.metadata())
|
||||
report.namespace = node.namespace
|
||||
report.resource_name = node.name
|
||||
report.resource_id = node.uid
|
||||
# It can only be checked if Prowler is being executed inside a worker node or if the file is the default one
|
||||
if node.inside:
|
||||
if is_owned_by_root("/var/lib/kubelet/config.yaml") is None:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Kubelet config.yaml file not found in Node {node.name}, please verify kubelet config.yaml file ownership manually."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"kubelet config.yaml file ownership is set to root:root in Node {node.name}."
|
||||
if not is_owned_by_root("/var/lib/kubelet/config.yaml"):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"kubelet config.yaml file ownership is set to root:root in Node {node.name}."
|
||||
else:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Prowler is not being executed inside Node {node.name}, please verify kubelet config.yaml file permissions manually."
|
||||
findings.append(report)
|
||||
return findings
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"Provider": "kubernetes",
|
||||
"CheckID": "kubelet_config_yaml_permissions",
|
||||
"CheckTitle": "Validate kubelet config.yaml File Permissions",
|
||||
"CheckType": [
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "kubelet",
|
||||
"SubServiceName": "Config File Permissions",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "KubernetesWorkerNode",
|
||||
"Description": "Ensure that if the kubelet refers to a configuration file with the --config argument, that file has permissions of 600 or more restrictive. The kubelet config file contains various critical parameters for the kubelet service on worker nodes, and its permissions should be strictly controlled.",
|
||||
"Risk": "Improper file permissions on kubelet config.yaml can expose sensitive data or allow unauthorized modifications.",
|
||||
"RelatedUrl": "https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "chmod 600 /var/lib/kubelet/config.yaml",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Secure the kubelet configuration by enforcing strict file permissions.",
|
||||
"Url": "https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"Node Security",
|
||||
"Compliance"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Regularly verify the file permissions of kubelet config files to ensure they are not altered unexpectedly."
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Kubernetes
|
||||
from prowler.lib.utils.utils import get_file_permissions
|
||||
from prowler.providers.kubernetes.services.core.core_client import core_client
|
||||
|
||||
|
||||
class kubelet_config_yaml_permissions(Check):
|
||||
def execute(self) -> Check_Report_Kubernetes:
|
||||
findings = []
|
||||
for node in core_client.nodes.values():
|
||||
report = Check_Report_Kubernetes(self.metadata())
|
||||
report.namespace = node.namespace
|
||||
report.resource_name = node.name
|
||||
report.resource_id = node.uid
|
||||
# It can only be checked if Prowler is being executed inside a worker node or if the file is the default one
|
||||
if node.inside:
|
||||
if not get_file_permissions("/var/lib/kubelet/config.yaml"):
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Kubelet config.yaml file not found in Node {node.name}, please verify kubelet config.yaml file permissions manually."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"kubelet config.yaml file permissions are set to 600 or more restrictive in Node {node.name}."
|
||||
if get_file_permissions("/var/lib/kubelet/config.yaml") > 0o600:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"kubelet config.yaml file permissions are not set to 600 or more restrictive in Node {node.name}."
|
||||
else:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Prowler is not being executed inside Node {node.name}, please verify kubelet config.yaml file permissions manually."
|
||||
findings.append(report)
|
||||
return findings
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"Provider": "kubernetes",
|
||||
"CheckID": "kubelet_service_file_ownership_root",
|
||||
"CheckTitle": "Ensure that the kubelet service file ownership is set to root:root",
|
||||
"CheckType": [
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "kubelet",
|
||||
"SubServiceName": "Service File Ownership",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "KubernetesWorkerNode",
|
||||
"Description": "This check ensures that the kubelet service file on each Node is owned by root. Proper file ownership is critical for the security and integrity of the kubelet service configuration.",
|
||||
"Risk": "Incorrect ownership settings can lead to unauthorized modifications, potentially compromising the security and functionality of the kubelet service.",
|
||||
"RelatedUrl": "https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/kubelet-integration/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "chown root:root /etc/systemd/system/kubelet.service.d/kubeadm.conf",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Set the kubelet service file ownership to root:root to maintain its integrity.",
|
||||
"Url": "https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-config/"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"Node Security",
|
||||
"Compliance"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Regular checks for file ownership can prevent unauthorized changes."
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Kubernetes
|
||||
from prowler.lib.utils.utils import is_owned_by_root
|
||||
from prowler.providers.kubernetes.services.core.core_client import core_client
|
||||
|
||||
|
||||
class kubelet_service_file_ownership_root(Check):
|
||||
def execute(self) -> Check_Report_Kubernetes:
|
||||
findings = []
|
||||
for node in core_client.nodes.values():
|
||||
report = Check_Report_Kubernetes(self.metadata())
|
||||
report.namespace = node.namespace
|
||||
report.resource_name = node.name
|
||||
report.resource_id = node.uid
|
||||
# It can only be checked if Prowler is being executed inside a worker node or if the file is the default one
|
||||
if node.inside:
|
||||
if (
|
||||
is_owned_by_root(
|
||||
"/etc/systemd/system/kubelet.service.d/kubeadm.conf"
|
||||
)
|
||||
is None
|
||||
):
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Kubelet service file not found in Node {node.name}, please verify Kubelet service file ownership manually."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Kubelet service file ownership is set to root:root in Node {node.name}."
|
||||
if not is_owned_by_root(
|
||||
"/etc/systemd/system/kubelet.service.d/kubeadm.conf"
|
||||
):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Kubelet service file ownership is not set to root:root in Node {node.name}."
|
||||
else:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Prowler is not being executed inside Node {node.name}, please verify Kubelet service file ownership manually."
|
||||
findings.append(report)
|
||||
return findings
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"Provider": "kubernetes",
|
||||
"CheckID": "kubelet_service_file_permissions",
|
||||
"CheckTitle": "Ensure that the kubelet service file permissions are set to 600 or more restrictive",
|
||||
"CheckType": [
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"ServiceName": "kubelet",
|
||||
"SubServiceName": "Kubelet Service File Permission",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "KubernetesNode",
|
||||
"Description": "This check ensures that the kubelet service file on worker nodes has permissions set to 600 or more restrictive, limiting the file's write access to only system administrators. This measure is crucial to maintain the integrity and security of the kubelet service configuration.",
|
||||
"Risk": "Improper file permissions on the kubelet service file could lead to unauthorized modifications, compromising node security and stability.",
|
||||
"RelatedUrl": "https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-config/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "chmod 600 /etc/systemd/system/kubelet.service.d/kubeadm.conf",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure the kubelet service file is securely configured with restrictive permissions.",
|
||||
"Url": "https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#44-joining-your-nodes"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"Node Security",
|
||||
"Configuration Management"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "The file location may vary based on the Kubernetes installation and should be verified for each cluster."
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Kubernetes
|
||||
from prowler.lib.utils.utils import get_file_permissions
|
||||
from prowler.providers.kubernetes.services.core.core_client import core_client
|
||||
|
||||
|
||||
class kubelet_service_file_permissions(Check):
|
||||
def execute(self) -> Check_Report_Kubernetes:
|
||||
findings = []
|
||||
for node in core_client.nodes.values():
|
||||
report = Check_Report_Kubernetes(self.metadata())
|
||||
report.namespace = node.namespace
|
||||
report.resource_name = node.name
|
||||
report.resource_id = node.uid
|
||||
# It can only be checked if Prowler is being executed inside a worker node or if the file is the default one
|
||||
if node.inside:
|
||||
if not get_file_permissions(
|
||||
"/etc/systemd/system/kubelet.service.d/kubeadm.conf"
|
||||
):
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Kubelet service file not found in Node {node.name}, please verify Kubelet service file permissions manually."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Kubelet service file permissions are set to 600 or more restrictive in Node {node.name}."
|
||||
if (
|
||||
get_file_permissions(
|
||||
"/etc/systemd/system/kubelet.service.d/kubeadm.conf"
|
||||
)
|
||||
> 0o600
|
||||
):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Kubelet service file permissions are not set to 600 or more restrictive in Node {node.name}."
|
||||
else:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"Prowler is not being executed inside Node {node.name}, please verify Kubelet service file permissions manually."
|
||||
findings.append(report)
|
||||
return findings
|
||||
@@ -9,7 +9,9 @@ from mock import patch
|
||||
from prowler.lib.utils.utils import (
|
||||
detect_secrets_scan,
|
||||
file_exists,
|
||||
get_file_permissions,
|
||||
hash_sha512,
|
||||
is_owned_by_root,
|
||||
open_file,
|
||||
outputs_unix_timestamp,
|
||||
parse_json_file,
|
||||
@@ -135,3 +137,27 @@ class Test_outputs_unix_timestamp:
|
||||
def test_outputs_unix_timestamp_true(self):
|
||||
time = datetime.now()
|
||||
assert outputs_unix_timestamp(True, time) == mktime(time.timetuple())
|
||||
|
||||
|
||||
class TestFilePermissions:
|
||||
def test_get_file_permissions(self):
|
||||
# Create a temporary file with known permissions
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
||||
temp_file.close()
|
||||
os.chmod(temp_file.name, 0o644) # Set permissions to 644 (-rw-r--r--)
|
||||
permissions = get_file_permissions(temp_file.name)
|
||||
assert permissions == "0o644"
|
||||
os.unlink(temp_file.name)
|
||||
assert not get_file_permissions("not_existing_file")
|
||||
|
||||
def test_is_owned_by_root(self):
|
||||
# Create a temporary file with known permissions
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
||||
temp_file.close()
|
||||
os.chmod(temp_file.name, 0o644) # Set permissions to 644 (-rw-r--r--)
|
||||
# Check ownership for the temporary file
|
||||
is_root = is_owned_by_root(temp_file.name)
|
||||
assert not is_root
|
||||
os.unlink(temp_file.name)
|
||||
assert not is_owned_by_root("not_existing_file")
|
||||
assert is_owned_by_root("/etc/passwd")
|
||||
|
||||
Reference in New Issue
Block a user