diff --git a/scanners/generic/tools/oobtkube.py b/scanners/generic/tools/oobtkube.py index 04ed668..f233d73 100644 --- a/scanners/generic/tools/oobtkube.py +++ b/scanners/generic/tools/oobtkube.py @@ -22,8 +22,12 @@ # ###################################### import argparse +import json import os +import queue +import re import socket +import subprocess import sys import threading import time @@ -60,53 +64,127 @@ def scan_with_k8s_config(cfg_file_path, ipaddr, port): print(f"Command run: {cmd}") os.system(cmd) - kube_cmd = f"kubectl apply -f {tmp_file}" + # if using 'apply' and a resource already exists, the command won't run as it returns as 'unchanged' + # therefore 'create' and 'replace' are used + kube_cmd = f"kubectl create -f {tmp_file} > /dev/null 2>&1; kubectl replace -f {tmp_file}" print(f"Command run: {kube_cmd}") os.system(kube_cmd) -def start_socket_listener(port, data_received, stop_event, duration): +def start_socket_listener(port, shared_queue, data_received, stop_event, duration): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.bind((SERVER_HOST, port)) + try: + server_socket.bind((SERVER_HOST, port)) + except OSError as e: + print( + f"{e}. Stopping the server. It might take a few seconds. Please try again later." + ) + stop_event.set() + server_socket.close() + return server_socket.settimeout(duration) server_socket.listen(1) print(f"Listening on port {port}") try: + client_socket = None client_socket, client_address = server_socket.accept() print(f"Accepted connection from {client_address}") while not stop_event.is_set(): - try: - data = client_socket.recv(1024) - if not data: - break + data = client_socket.recv(1024) + if not data: + break - print("Received data:", data.decode("utf-8")) + shared_queue.put(data.decode("utf-8")) - # Send a custom response back to the client - response = "HTTP/1.1 200 OK\r\n\r\nfrom oob_listener!\n" - client_socket.send(response.encode("utf-8")) + # Send a custom response back to the client + response = "HTTP/1.1 200 OK\r\n\r\nfrom oob_listener!\n" + client_socket.send(response.encode("utf-8")) - data_received.set() + data_received.set() - # Stop the listener after the first request - stop_event.set() - break + # Stop the listener after the first request + stop_event.set() + break - except socket.timeout: - pass + except TimeoutError: + print("Timeout reached. Stopping the server.") + pass except Exception as e: raise RuntimeError("An error occurred. See logs for details.") from e finally: - client_socket.close() - server_socket.close() - - + if client_socket: + client_socket.close() + if server_socket: + server_socket.close() + + +def convert_to_sarif_json(result_message, tool_name, artifact_url="", snippet=""): + sarif_output = { + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [ + { + "tool": {"driver": {"name": tool_name, "version": "1.0.0"}}, + "results": [ + { + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": {"uri": artifact_url}, + "region": { + "startLine": 1, + "properties": { + # pylint: disable=C0301 + "startLineFailure": "Resolved invalid start line: 0 - used fallback value instead." + }, + "snippet": {"text": snippet}, + }, + } + } + ], + "message": {"text": result_message}, + "ruleId": "RAPIDAST-OOBTKUBE-00001", + } + ], + } + ], + } + return json.dumps(sarif_output) + + +def get_kubernetes_api_url(): + try: + # Run kubectl cluster-info command and capture the output + output = subprocess.check_output(["kubectl", "cluster-info"]).decode("utf-8") + + # Find the line containing the Kubernetes master URL + lines = output.split("\n") + for line in lines: + if "is running at" in line: + # Use regular expression to extract the URL + url_match = re.search(r"(https?://[^\s;]+:[0-9]+)", line) + + if url_match: + # Extract and print the URL + api_url = url_match.group(1) + print("Kubernetes API server URL:", api_url) + return api_url + # Return None if URL is not found + return None + except subprocess.CalledProcessError as e: + # Handle error if kubectl command fails + print("Error:", e) + return None + + +# pylint: disable=R0915 def main(): # Parse command-line arguments parser = argparse.ArgumentParser( @@ -136,6 +214,13 @@ def main(): parser.add_argument( "-f", "--filename", type=str, required=True, help="Kubernetes config file path" ) + # add argument for '-o' to output the result to a file + parser.add_argument( + "-o", + "--output", + type=str, + help="Output result to a file in the SARIF format (default: stdout)", + ) args = parser.parse_args() @@ -150,11 +235,12 @@ def main(): # Create a few threading events data_received = threading.Event() stop_event = threading.Event() + shared_queue = queue.Queue() # Start socket listener in a separate thread socket_listener_thread = threading.Thread( target=start_socket_listener, - args=(args.port, data_received, stop_event, args.duration), + args=(args.port, shared_queue, data_received, stop_event, args.duration), ) socket_listener_thread.start() @@ -162,6 +248,10 @@ def main(): # You may need to adjust this delay based on your system time.sleep(5) + if stop_event.is_set(): + print("Socket listener failed to start. Exiting...") + sys.exit(1) + print("Listener thread started") # Record the start time for the main function @@ -182,9 +272,32 @@ def main(): socket_listener_thread.join() if data_received.is_set(): - print(f"RESULT: {MESSAGE_DETECTED}") + result_message = f"{MESSAGE_DETECTED}" + tool = "RapiDAST-oobtkube" + + kubernetes_api_url = get_kubernetes_api_url() + if kubernetes_api_url: + print("Kubernetes API URL:", kubernetes_api_url) + artifact_url = kubernetes_api_url + else: + print("Failed to retrieve Kubernetes API URL.") + artifact_url = "a target k8s operator" + + snippet = shared_queue.get() + print("Request received:", snippet) + + sarif_output = convert_to_sarif_json( + result_message, tool, artifact_url, snippet + ) + + if args.output: + with open(args.output, "w", encoding="utf-8") as f: + f.write(sarif_output) + else: + print(f"OOBTKUBE RESULT: {MESSAGE_DETECTED}") + print(sarif_output) else: - print(f"RESULT: {MESSAGE_NOT_DETECTED}") + print(f"OOBTKUBE RESULT: {MESSAGE_NOT_DETECTED}") sys.exit(0)