mirror of
https://github.com/aquasecurity/kube-hunter.git
synced 2026-03-01 09:10:29 +00:00
Compare commits
10 Commits
fix-huntin
...
v0.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a476d9383f | ||
|
|
6a3c7a885a | ||
|
|
b6be309651 | ||
|
|
0d5b3d57d3 | ||
|
|
69057acf9b | ||
|
|
e63200139e | ||
|
|
ad4cfe1c11 | ||
|
|
24b5a709ad | ||
|
|
9cadc0ee41 | ||
|
|
3950a1c2f2 |
14
.github/workflows/greetings.yml
vendored
Normal file
14
.github/workflows/greetings.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Greetings
|
||||
|
||||
on: [pull_request, issues]
|
||||
|
||||
jobs:
|
||||
greeting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: "Hola! @${{ github.actor }} 🥳 , You've just created an Issue!🌟 Thanks for making the Project Better"
|
||||
pr-message: 'Submitted a PR already ?? @${{ github.actor }} . Sit tight until one of our amazing maintainers review it. Make sure you read the contributing guide'
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,6 +24,7 @@ var/
|
||||
*.egg
|
||||
*.spec
|
||||
.eggs
|
||||
pip-wheel-metadata
|
||||
|
||||
# Directory Cache Files
|
||||
.DS_Store
|
||||
|
||||
@@ -5,6 +5,7 @@ python:
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
install:
|
||||
- pip install -r requirements.txt
|
||||
- pip install -r requirements-dev.txt
|
||||
|
||||
@@ -34,6 +34,7 @@ Table of Contents
|
||||
* [Prerequisites](#prerequisites)
|
||||
* [Container](#container)
|
||||
* [Pod](#pod)
|
||||
* [Contribution](#contribution)
|
||||
|
||||
## Hunting
|
||||
|
||||
@@ -174,5 +175,8 @@ The example `job.yaml` file defines a Job that will run kube-hunter in a pod, us
|
||||
* Find the pod name with `kubectl describe job kube-hunter`
|
||||
* View the test results with `kubectl logs <pod name>`
|
||||
|
||||
## Contribution
|
||||
To read the contribution guidelines, <a href="https://github.com/aquasecurity/kube-hunter/blob/master/CONTRIBUTING.md"> Click here </a>
|
||||
|
||||
## License
|
||||
This repository is available under the [Apache License 2.0](https://github.com/aquasecurity/kube-hunter/blob/master/LICENSE).
|
||||
|
||||
@@ -12,7 +12,7 @@ Kubernetes API was accessed with Pod Service Account or without Authentication (
|
||||
|
||||
## Remediation
|
||||
|
||||
Secure acess to your Kubernetes API.
|
||||
Secure access to your Kubernetes API.
|
||||
|
||||
It is recommended to explicitly specify a Service Account for all of your workloads (`serviceAccountName` in `Pod.Spec`), and manage their permissions according to the least privilege principal.
|
||||
|
||||
@@ -21,4 +21,4 @@ Consider opting out automatic mounting of SA token using `automountServiceAccoun
|
||||
|
||||
## References
|
||||
|
||||
- [Configure Service Accounts for Pods](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)
|
||||
- [Configure Service Accounts for Pods](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 111 KiB |
BIN
kube-hunter.png
BIN
kube-hunter.png
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 19 KiB |
@@ -18,6 +18,7 @@ config = Config(
|
||||
cidr=args.cidr,
|
||||
include_patched_versions=args.include_patched_versions,
|
||||
interface=args.interface,
|
||||
log_file=args.log_file,
|
||||
mapping=args.mapping,
|
||||
network_timeout=args.network_timeout,
|
||||
pod=args.pod,
|
||||
@@ -25,7 +26,7 @@ config = Config(
|
||||
remote=args.remote,
|
||||
statistics=args.statistics,
|
||||
)
|
||||
setup_logger(args.log)
|
||||
setup_logger(args.log, args.log_file)
|
||||
set_config(config)
|
||||
|
||||
# Running all other registered plugins before execution
|
||||
|
||||
@@ -13,6 +13,7 @@ class Config:
|
||||
- interface: Interface scanning mode
|
||||
- list_hunters: Print a list of existing hunters
|
||||
- log_level: Log level
|
||||
- log_file: Log File path
|
||||
- mapping: Report only found components
|
||||
- network_timeout: Timeout for network operations
|
||||
- pod: From pod scanning mode
|
||||
@@ -27,6 +28,7 @@ class Config:
|
||||
dispatcher: Optional[Any] = None
|
||||
include_patched_versions: bool = False
|
||||
interface: bool = False
|
||||
log_file: Optional[str] = None
|
||||
mapping: bool = False
|
||||
network_timeout: float = 5.0
|
||||
pod: bool = False
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import logging
|
||||
|
||||
|
||||
DEFAULT_LEVEL = logging.INFO
|
||||
DEFAULT_LEVEL_NAME = logging.getLevelName(DEFAULT_LEVEL)
|
||||
LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s %(message)s"
|
||||
@@ -10,7 +9,7 @@ logging.getLogger("scapy.runtime").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("scapy.loading").setLevel(logging.CRITICAL)
|
||||
|
||||
|
||||
def setup_logger(level_name):
|
||||
def setup_logger(level_name, logfile):
|
||||
# Remove any existing handlers
|
||||
# Unnecessary in Python 3.8 since `logging.basicConfig` has `force` parameter
|
||||
for h in logging.getLogger().handlers[:]:
|
||||
@@ -22,6 +21,9 @@ def setup_logger(level_name):
|
||||
else:
|
||||
log_level = getattr(logging, level_name.upper(), None)
|
||||
log_level = log_level if isinstance(log_level, int) else None
|
||||
logging.basicConfig(level=log_level or DEFAULT_LEVEL, format=LOG_FORMAT)
|
||||
if logfile is None:
|
||||
logging.basicConfig(level=log_level or DEFAULT_LEVEL, format=LOG_FORMAT)
|
||||
else:
|
||||
logging.basicConfig(filename=logfile, level=log_level or DEFAULT_LEVEL, format=LOG_FORMAT)
|
||||
if not log_level:
|
||||
logging.warning(f"Unknown log level '{level_name}', using {DEFAULT_LEVEL_NAME}")
|
||||
|
||||
@@ -56,6 +56,13 @@ def parser_add_arguments(parser):
|
||||
help="Set log level, options are: debug, info, warn, none",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--log-file",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Path to a log file to output all logs to",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--report",
|
||||
type=str,
|
||||
|
||||
@@ -46,11 +46,16 @@ class AzureSpnHunter(Hunter):
|
||||
logger.debug("failed getting pod info")
|
||||
else:
|
||||
pods_data = r.json().get("items", [])
|
||||
suspicious_volume_names = []
|
||||
for pod_data in pods_data:
|
||||
for container in pod_data["spec"]["containers"]:
|
||||
for mount in container["volumeMounts"]:
|
||||
path = mount["mountPath"]
|
||||
for volume in pod_data["spec"].get("volumes", []):
|
||||
if volume.get("hostPath"):
|
||||
path = volume["hostPath"]["path"]
|
||||
if "/etc/kubernetes/azure.json".startswith(path):
|
||||
suspicious_volume_names.append(volume["name"])
|
||||
for container in pod_data["spec"]["containers"]:
|
||||
for mount in container.get("volumeMounts", []):
|
||||
if mount["name"] in suspicious_volume_names:
|
||||
return {
|
||||
"name": container["name"],
|
||||
"pod": pod_data["metadata"]["name"],
|
||||
|
||||
@@ -9,7 +9,7 @@ from kube_hunter.modules.report.collector import (
|
||||
vulnerabilities_lock,
|
||||
)
|
||||
|
||||
EVIDENCE_PREVIEW = 40
|
||||
EVIDENCE_PREVIEW = 100
|
||||
MAX_TABLE_WIDTH = 20
|
||||
KB_LINK = "https://github.com/aquasecurity/kube-hunter/tree/master/docs/_kb"
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ classifiers =
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Topic :: Security
|
||||
|
||||
[options]
|
||||
|
||||
@@ -11,12 +11,13 @@ def test_setup_logger_level():
|
||||
("NOTEXISTS", logging.INFO),
|
||||
("BASIC_FORMAT", logging.INFO),
|
||||
]
|
||||
logFile = None
|
||||
for level, expected in test_cases:
|
||||
setup_logger(level)
|
||||
setup_logger(level, logFile)
|
||||
actual = logging.getLogger().getEffectiveLevel()
|
||||
assert actual == expected, f"{level} level should be {expected} (got {actual})"
|
||||
|
||||
|
||||
def test_setup_logger_none():
|
||||
setup_logger("NONE")
|
||||
setup_logger("NONE", None)
|
||||
assert logging.getLogger().manager.disable == logging.CRITICAL
|
||||
|
||||
56
tests/hunting/test_aks.py
Normal file
56
tests/hunting/test_aks.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# flake8: noqa: E402
|
||||
import requests_mock
|
||||
|
||||
from kube_hunter.conf import Config, set_config
|
||||
|
||||
set_config(Config())
|
||||
|
||||
from kube_hunter.modules.hunting.kubelet import ExposedRunHandler
|
||||
from kube_hunter.modules.hunting.aks import AzureSpnHunter
|
||||
|
||||
|
||||
def test_AzureSpnHunter():
|
||||
e = ExposedRunHandler()
|
||||
e.host = "mockKubernetes"
|
||||
e.port = 443
|
||||
e.protocol = "https"
|
||||
|
||||
pod_template = '{{"items":[ {{"apiVersion":"v1","kind":"Pod","metadata":{{"name":"etc","namespace":"default"}},"spec":{{"containers":[{{"command":["sleep","99999"],"image":"ubuntu","name":"test","volumeMounts":[{{"mountPath":"/mp","name":"v"}}]}}],"volumes":[{{"hostPath":{{"path":"{}"}},"name":"v"}}]}}}} ]}}'
|
||||
|
||||
bad_paths = ["/", "/etc", "/etc/", "/etc/kubernetes", "/etc/kubernetes/azure.json"]
|
||||
good_paths = ["/yo", "/etc/yo", "/etc/kubernetes/yo.json"]
|
||||
|
||||
for p in bad_paths:
|
||||
with requests_mock.Mocker() as m:
|
||||
m.get("https://mockKubernetes:443/pods", text=pod_template.format(p))
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c
|
||||
|
||||
for p in good_paths:
|
||||
with requests_mock.Mocker() as m:
|
||||
m.get("https://mockKubernetes:443/pods", text=pod_template.format(p))
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c == None
|
||||
|
||||
with requests_mock.Mocker() as m:
|
||||
pod_no_volume_mounts = '{"items":[ {"apiVersion":"v1","kind":"Pod","metadata":{"name":"etc","namespace":"default"},"spec":{"containers":[{"command":["sleep","99999"],"image":"ubuntu","name":"test"}],"volumes":[{"hostPath":{"path":"/whatever"},"name":"v"}]}} ]}'
|
||||
m.get("https://mockKubernetes:443/pods", text=pod_no_volume_mounts)
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c == None
|
||||
|
||||
with requests_mock.Mocker() as m:
|
||||
pod_no_volumes = '{"items":[ {"apiVersion":"v1","kind":"Pod","metadata":{"name":"etc","namespace":"default"},"spec":{"containers":[{"command":["sleep","99999"],"image":"ubuntu","name":"test"}]}} ]}'
|
||||
m.get("https://mockKubernetes:443/pods", text=pod_no_volumes)
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c == None
|
||||
|
||||
with requests_mock.Mocker() as m:
|
||||
pod_other_volume = '{"items":[ {"apiVersion":"v1","kind":"Pod","metadata":{"name":"etc","namespace":"default"},"spec":{"containers":[{"command":["sleep","99999"],"image":"ubuntu","name":"test","volumeMounts":[{"mountPath":"/mp","name":"v"}]}],"volumes":[{"emptyDir":{},"name":"v"}]}} ]}'
|
||||
m.get("https://mockKubernetes:443/pods", text=pod_other_volume)
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c == None
|
||||
Reference in New Issue
Block a user