mirror of
https://github.com/aquasecurity/kube-hunter.git
synced 2026-03-01 01:00:25 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6689005544 |
@@ -76,6 +76,12 @@ To specify interface scanning, you can use the `--interface` option (this will s
|
||||
To specify a specific CIDR to scan, use the `--cidr` option. Example:
|
||||
`kube-hunter --cidr 192.168.0.0/24`
|
||||
|
||||
4. **Kubernetes node auto-discovery**
|
||||
|
||||
Set `--k8s-auto-discover-nodes` flag to query Kubernetes for all nodes in the cluster, and then attempt to scan them all. By default, it will use [in-cluster config](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) to connect to the Kubernetes API. If you'd like to use an explicit kubeconfig file, set `--kubeconfig /location/of/kubeconfig/file`.
|
||||
|
||||
Also note, that this is always done when using `--pod` mode.
|
||||
|
||||
### Active Hunting
|
||||
|
||||
Active hunting is an option in which kube-hunter will exploit vulnerabilities it finds, to explore for further vulnerabilities.
|
||||
|
||||
@@ -25,6 +25,8 @@ config = Config(
|
||||
quick=args.quick,
|
||||
remote=args.remote,
|
||||
statistics=args.statistics,
|
||||
k8s_auto_discover_nodes=args.k8s_auto_discover_nodes,
|
||||
kubeconfig=args.kubeconfig,
|
||||
)
|
||||
setup_logger(args.log, args.log_file)
|
||||
set_config(config)
|
||||
@@ -88,7 +90,7 @@ hunt_started = False
|
||||
|
||||
def main():
|
||||
global hunt_started
|
||||
scan_options = [config.pod, config.cidr, config.remote, config.interface]
|
||||
scan_options = [config.pod, config.cidr, config.remote, config.interface, config.k8s_auto_discover_nodes]
|
||||
try:
|
||||
if args.list:
|
||||
list_hunters()
|
||||
|
||||
@@ -36,6 +36,8 @@ class Config:
|
||||
remote: Optional[str] = None
|
||||
reporter: Optional[Any] = None
|
||||
statistics: bool = False
|
||||
k8s_auto_discover_nodes: bool = False
|
||||
kubeconfig: Optional[str] = None
|
||||
|
||||
|
||||
_config: Optional[Config] = None
|
||||
|
||||
@@ -46,6 +46,26 @@ def parser_add_arguments(parser):
|
||||
help="One or more remote ip/dns to hunt",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--k8s-auto-discover-nodes",
|
||||
action="store_true",
|
||||
help="Enables automatic detection of all nodes in a Kubernetes cluster "
|
||||
"by quering the Kubernetes API server. "
|
||||
"It supports both in-cluster config (when running as a pod), "
|
||||
"and a specific kubectl config file (use --kubeconfig to set this). "
|
||||
"By default, when this flag is set, it will use in-cluster config. "
|
||||
"NOTE: this is automatically switched on in --pod mode."
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--kubeconfig",
|
||||
type=str,
|
||||
metavar="KUBECONFIG",
|
||||
default=None,
|
||||
help="Specify the kubeconfig file to use for Kubernetes nodes auto discovery "
|
||||
" (to be used in conjuction with the --k8s-auto-discover-nodes flag."
|
||||
)
|
||||
|
||||
parser.add_argument("--active", action="store_true", help="Enables active hunting")
|
||||
|
||||
parser.add_argument(
|
||||
|
||||
@@ -8,6 +8,7 @@ from netaddr import IPNetwork, IPAddress, AddrFormatError
|
||||
from netifaces import AF_INET, ifaddresses, interfaces, gateways
|
||||
|
||||
from kube_hunter.conf import get_config
|
||||
from kube_hunter.modules.discovery.kubernetes_client import list_all_k8s_cluster_nodes
|
||||
from kube_hunter.core.events import handler
|
||||
from kube_hunter.core.events.types import Event, NewHostEvent, Vulnerability
|
||||
from kube_hunter.core.types import Discovery, InformationDisclosure, AWS, Azure
|
||||
@@ -114,6 +115,9 @@ class FromPodHostDiscovery(Discovery):
|
||||
|
||||
def execute(self):
|
||||
config = get_config()
|
||||
# Attempt to read all hosts from the Kubernetes API
|
||||
for host in list_all_k8s_cluster_nodes(config.kubeconfig):
|
||||
self.publish_event(NewHostEvent(host=host))
|
||||
# Scan any hosts that the user specified
|
||||
if config.remote or config.cidr:
|
||||
self.publish_event(HostScanEvent())
|
||||
@@ -298,6 +302,9 @@ class HostDiscovery(Discovery):
|
||||
elif len(config.remote) > 0:
|
||||
for host in config.remote:
|
||||
self.publish_event(NewHostEvent(host=host))
|
||||
elif config.k8s_auto_discover_nodes:
|
||||
for host in list_all_k8s_cluster_nodes(config.kubeconfig):
|
||||
self.publish_event(NewHostEvent(host=host))
|
||||
|
||||
# for normal scanning
|
||||
def scan_interfaces(self):
|
||||
|
||||
27
kube_hunter/modules/discovery/kubernetes_client.py
Normal file
27
kube_hunter/modules/discovery/kubernetes_client.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import logging
|
||||
import kubernetes
|
||||
|
||||
|
||||
def list_all_k8s_cluster_nodes(kube_config=None, client=None):
|
||||
logger = logging.getLogger(__name__)
|
||||
try:
|
||||
if kube_config:
|
||||
logger.info("Attempting to use kubeconfig file: %s", kube_config)
|
||||
kubernetes.config.load_kube_config(config_file=kube_config)
|
||||
else:
|
||||
logger.info("Attempting to use in cluster Kubernetes config")
|
||||
kubernetes.config.load_incluster_config()
|
||||
except kubernetes.config.config_exception.ConfigException:
|
||||
logger.exception("Failed to initiate Kubernetes client")
|
||||
return
|
||||
|
||||
try:
|
||||
if client is None:
|
||||
client = kubernetes.client.CoreV1Api()
|
||||
ret = client.list_node(watch=False)
|
||||
logger.info("Listed %d nodes in the cluster" % len(ret.items))
|
||||
for item in ret.items:
|
||||
for addr in item.status.addresses:
|
||||
yield addr.address
|
||||
except:
|
||||
logger.exception("Failed to list nodes from Kubernetes")
|
||||
@@ -41,6 +41,7 @@ install_requires =
|
||||
packaging
|
||||
dataclasses
|
||||
pluggy
|
||||
kubernetes==12.0.1
|
||||
setup_requires =
|
||||
setuptools>=30.3.0
|
||||
setuptools_scm
|
||||
|
||||
31
tests/discovery/test_k8s.py
Normal file
31
tests/discovery/test_k8s.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from kube_hunter.conf import Config, set_config
|
||||
|
||||
set_config(Config())
|
||||
|
||||
from kube_hunter.modules.discovery.kubernetes_client import list_all_k8s_cluster_nodes
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
|
||||
def test_client_yields_ips():
|
||||
client = MagicMock()
|
||||
response = MagicMock()
|
||||
client.list_node.return_value = response
|
||||
response.items = [MagicMock(), MagicMock()]
|
||||
response.items[0].status.addresses = [MagicMock(), MagicMock()]
|
||||
response.items[0].status.addresses[0].address = "127.0.0.1"
|
||||
response.items[0].status.addresses[1].address = "127.0.0.2"
|
||||
response.items[1].status.addresses = [MagicMock()]
|
||||
response.items[1].status.addresses[0].address = "127.0.0.3"
|
||||
|
||||
with patch('kubernetes.config.load_incluster_config') as m:
|
||||
output = list(list_all_k8s_cluster_nodes(client=client))
|
||||
m.assert_called_once()
|
||||
|
||||
assert output == ["127.0.0.1", "127.0.0.2", "127.0.0.3"]
|
||||
|
||||
|
||||
def test_client_uses_kubeconfig():
|
||||
with patch('kubernetes.config.load_kube_config') as m:
|
||||
list(list_all_k8s_cluster_nodes(kube_config="/location", client=MagicMock()))
|
||||
m.assert_called_once_with(config_file="/location")
|
||||
Reference in New Issue
Block a user