From 5fba468a849e4f489cd3fd4f32ccc8f4e88a8891 Mon Sep 17 00:00:00 2001 From: JoukoVirtanen Date: Tue, 3 Jun 2025 19:33:43 -0700 Subject: [PATCH 1/4] X-Smart-Branch-Parent: main From 223b3758d776fb19e14a39cacf7fd09af734ac9c Mon Sep 17 00:00:00 2001 From: JoukoVirtanen Date: Tue, 3 Jun 2025 19:46:53 -0700 Subject: [PATCH 2/4] Added demo directory --- .../external-entities/demo/Dockerfile | 5 + util-scripts/external-entities/demo/Makefile | 9 ++ .../demo/collector-config-disabled.yml | 10 ++ .../demo/collector-config-enabled.yml | 10 ++ .../demo/create-cidr-block.sh | 17 ++++ .../demo/create-deployment-with-ext-ip.sh | 9 ++ .../demo/create-secret-for-qa.sh | 9 ++ .../external-entities/demo/deployment.yml | 61 ++++++++++++ .../demo/enable-collector-external-ips.sh | 4 + ...nal-destination-source-stable-template.yml | 61 ++++++++++++ .../demo/external-destination-source.yml | 58 +++++++++++ .../demo/get-network-policy.sh | 15 +++ .../external-entities/demo/lock-baseline.sh | 20 ++++ .../demo/mark-as-anomalous.sh | 49 ++++++++++ .../demo/ping-server/Dockerfile | 9 ++ .../demo/ping-server/Makefile | 9 ++ .../demo/ping-server/ping-server.yml | 52 ++++++++++ .../demo/ping-server/ping_server.py | 55 +++++++++++ .../external-entities/demo/ping_in_loop.py | 69 ++++++++++++++ .../external-entities/demo/prepare-tap.sh | 95 +++++++++++++++++++ .../external-entities/demo/scenario.sh | 47 +++++++++ .../external-entities/demo/setup-env-var.sh | 4 + 22 files changed, 677 insertions(+) create mode 100644 util-scripts/external-entities/demo/Dockerfile create mode 100644 util-scripts/external-entities/demo/Makefile create mode 100644 util-scripts/external-entities/demo/collector-config-disabled.yml create mode 100644 util-scripts/external-entities/demo/collector-config-enabled.yml create mode 100755 util-scripts/external-entities/demo/create-cidr-block.sh create mode 100755 util-scripts/external-entities/demo/create-deployment-with-ext-ip.sh create mode 100755 util-scripts/external-entities/demo/create-secret-for-qa.sh create mode 100644 util-scripts/external-entities/demo/deployment.yml create mode 100755 util-scripts/external-entities/demo/enable-collector-external-ips.sh create mode 100644 util-scripts/external-entities/demo/external-destination-source-stable-template.yml create mode 100644 util-scripts/external-entities/demo/external-destination-source.yml create mode 100755 util-scripts/external-entities/demo/get-network-policy.sh create mode 100755 util-scripts/external-entities/demo/lock-baseline.sh create mode 100755 util-scripts/external-entities/demo/mark-as-anomalous.sh create mode 100644 util-scripts/external-entities/demo/ping-server/Dockerfile create mode 100644 util-scripts/external-entities/demo/ping-server/Makefile create mode 100644 util-scripts/external-entities/demo/ping-server/ping-server.yml create mode 100644 util-scripts/external-entities/demo/ping-server/ping_server.py create mode 100644 util-scripts/external-entities/demo/ping_in_loop.py create mode 100755 util-scripts/external-entities/demo/prepare-tap.sh create mode 100755 util-scripts/external-entities/demo/scenario.sh create mode 100644 util-scripts/external-entities/demo/setup-env-var.sh diff --git a/util-scripts/external-entities/demo/Dockerfile b/util-scripts/external-entities/demo/Dockerfile new file mode 100644 index 0000000..1a280e0 --- /dev/null +++ b/util-scripts/external-entities/demo/Dockerfile @@ -0,0 +1,5 @@ +FROM registry.fedoraproject.org/fedora:41 + +RUN dnf install -y which iproute bpftool procps iptables nc + +COPY prepare-tap.sh /scripts/ diff --git a/util-scripts/external-entities/demo/Makefile b/util-scripts/external-entities/demo/Makefile new file mode 100644 index 0000000..3b3d390 --- /dev/null +++ b/util-scripts/external-entities/demo/Makefile @@ -0,0 +1,9 @@ +ifeq ($(TAG),) +TAG=$(shell git describe --tags --abbrev=10 --dirty) +endif + +.PHONY: +build: + docker build -t external-connection . + docker tag external-connection quay.io/$(REPOSITORY)/external-connection:$(TAG) + docker push quay.io/$(REPOSITORY)/external-connection:$(TAG) diff --git a/util-scripts/external-entities/demo/collector-config-disabled.yml b/util-scripts/external-entities/demo/collector-config-disabled.yml new file mode 100644 index 0000000..7a7be4e --- /dev/null +++ b/util-scripts/external-entities/demo/collector-config-disabled.yml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: collector-config + namespace: stackrox +data: + runtime_config.yaml: | + networking: + externalIps: + enabled: DISABLED diff --git a/util-scripts/external-entities/demo/collector-config-enabled.yml b/util-scripts/external-entities/demo/collector-config-enabled.yml new file mode 100644 index 0000000..7a34d94 --- /dev/null +++ b/util-scripts/external-entities/demo/collector-config-enabled.yml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: collector-config + namespace: stackrox +data: + runtime_config.yaml: | + networking: + externalIps: + enabled: ENABLED diff --git a/util-scripts/external-entities/demo/create-cidr-block.sh b/util-scripts/external-entities/demo/create-cidr-block.sh new file mode 100755 index 0000000..314cd59 --- /dev/null +++ b/util-scripts/external-entities/demo/create-cidr-block.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -eou pipefail + +ROX_ENDPOINT=${1:-localhost:8000} +cidr_block=${2:-8.8.8.0/24} +cidr_name=${3:-"testCIDR"} + +clusters_json="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/clusters" -k --header "Authorization: Bearer $ROX_API_TOKEN")" + +cluster_id="$(echo "$clusters_json" | jq -r '.clusters[0].id')" + +cidr_json='{"entity": {"cidr": "'"$cidr_block"'", "name": "'"$cidr_name"'", "id": ""}}' + + +create_cidr_block_response_json="$(curl --location --silent --request POST --data "$cidr_json" "https://${ROX_ENDPOINT}/v1/networkgraph/cluster/$cluster_id/externalentities" -k --header "Authorization: Bearer $ROX_API_TOKEN")" + +echo "$create_cidr_block_response_json" | jq diff --git a/util-scripts/external-entities/demo/create-deployment-with-ext-ip.sh b/util-scripts/external-entities/demo/create-deployment-with-ext-ip.sh new file mode 100755 index 0000000..dfa8ace --- /dev/null +++ b/util-scripts/external-entities/demo/create-deployment-with-ext-ip.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -eou pipefail + +export TARGET_IP=$1 +export PORT=$2 +export NAME=$3 + +envsubst < external-destination-source-stable-template.yml > deployment.yml +kubectl apply -f deployment.yml diff --git a/util-scripts/external-entities/demo/create-secret-for-qa.sh b/util-scripts/external-entities/demo/create-secret-for-qa.sh new file mode 100755 index 0000000..8b464fb --- /dev/null +++ b/util-scripts/external-entities/demo/create-secret-for-qa.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -eou pipefail + +kubectl -n stackrox get secret stackrox -o yaml > stackrox-secret.yml +sed 's|stackrox|qa|' stackrox-secret.yml > qa-secret.yml +kubectl create -f qa-secret.yml + +rm stackrox-secret.yml +rm qa-secret.yml diff --git a/util-scripts/external-entities/demo/deployment.yml b/util-scripts/external-entities/demo/deployment.yml new file mode 100644 index 0000000..f39a38e --- /dev/null +++ b/util-scripts/external-entities/demo/deployment.yml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "1" + generation: 1 + labels: + app: external-destination-source-3 + name: external-destination-source-3 + name: external-destination-source-3 + namespace: qa +spec: + minReadySeconds: 15 + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: external-destination-source-3 + name: external-destination-source-3 + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: external-destination-source-3 + deployment: external-destination-source-3 + name: external-destination-source-3 + name: external-destination-source-3 + namespace: qa + spec: + imagePullSecrets: + - name: qa + containers: + - args: + - | + /scripts/prepare-tap.sh -a "2.2.2.2/32" -o + nc -lk "2.2.2.2" "53" & + sleep 2 + while sleep 30; do nc -zv "2.2.2.2" "53"; done + command: + - /bin/bash + - -c + image: quay.io/jvirtane/external-connection:4.8.x-619-g1fb6d87786-dirty-4 + imagePullPolicy: IfNotPresent + name: external-destination-source + resources: {} + securityContext: + capabilities: {} + privileged: true + readOnlyRootFilesystem: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 diff --git a/util-scripts/external-entities/demo/enable-collector-external-ips.sh b/util-scripts/external-entities/demo/enable-collector-external-ips.sh new file mode 100755 index 0000000..e8d9d9b --- /dev/null +++ b/util-scripts/external-entities/demo/enable-collector-external-ips.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -eou pipefail + +kubectl create -f collector-config-enabled.yml diff --git a/util-scripts/external-entities/demo/external-destination-source-stable-template.yml b/util-scripts/external-entities/demo/external-destination-source-stable-template.yml new file mode 100644 index 0000000..b5df529 --- /dev/null +++ b/util-scripts/external-entities/demo/external-destination-source-stable-template.yml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "1" + generation: 1 + labels: + app: external-destination-source-${NAME} + name: external-destination-source-${NAME} + name: external-destination-source-${NAME} + namespace: qa +spec: + minReadySeconds: 15 + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: external-destination-source-${NAME} + name: external-destination-source-${NAME} + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: external-destination-source-${NAME} + deployment: external-destination-source-${NAME} + name: external-destination-source-${NAME} + name: external-destination-source-${NAME} + namespace: qa + spec: + imagePullSecrets: + - name: qa + containers: + - args: + - | + /scripts/prepare-tap.sh -a "${TARGET_IP}/32" -o + nc -lk "${TARGET_IP}" "${PORT}" & + sleep 2 + while sleep 30; do nc -zv "${TARGET_IP}" "${PORT}"; done + command: + - /bin/bash + - -c + image: quay.io/${REPOSITORY}/external-connection:${TAG} + imagePullPolicy: IfNotPresent + name: external-destination-source + resources: {} + securityContext: + capabilities: {} + privileged: true + readOnlyRootFilesystem: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 diff --git a/util-scripts/external-entities/demo/external-destination-source.yml b/util-scripts/external-entities/demo/external-destination-source.yml new file mode 100644 index 0000000..690adb4 --- /dev/null +++ b/util-scripts/external-entities/demo/external-destination-source.yml @@ -0,0 +1,58 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "1" + generation: 1 + labels: + app: external-destination-source + name: external-destination-source + name: external-destination-source + namespace: qa +spec: + minReadySeconds: 15 + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: external-destination-source + name: external-destination-source + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: external-destination-source + deployment: external-destination-source + name: external-destination-source + name: external-destination-source + namespace: qa + spec: + imagePullSecrets: + - name: qa + containers: + - args: + - while sleep 30; do wget -S -T 2 http://www.google.com; done + command: + - /bin/sh + - -c + image: quay.io/rhacs-eng/qa-multi-arch:nginx-1-15-4-alpine + imagePullPolicy: IfNotPresent + name: external-destination-source + resources: {} + securityContext: + capabilities: {} + privileged: false + readOnlyRootFilesystem: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 diff --git a/util-scripts/external-entities/demo/get-network-policy.sh b/util-scripts/external-entities/demo/get-network-policy.sh new file mode 100755 index 0000000..40a8a41 --- /dev/null +++ b/util-scripts/external-entities/demo/get-network-policy.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -eou pipefail + +ROX_ENDPOINT=${1:-localhost:8000} + +clusters_json="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/clusters" -k --header "Authorization: Bearer $ROX_API_TOKEN")" + +cluster_id="$(echo "$clusters_json" | jq -r '.clusters[0].id')" + +query="Cluster%3Aremote%2BNamespace%3Aqa&includePorts=true" +network_policy_json="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/networkpolicies/generate/${cluster_id}?deleteExisting=NONE&query=$query" -k --header "Authorization: Bearer $ROX_API_TOKEN")" + +network_policy=$(echo "$network_policy_json" | jq -r '.modification.applyYaml') + +printf "%b\n" "$network_policy" diff --git a/util-scripts/external-entities/demo/lock-baseline.sh b/util-scripts/external-entities/demo/lock-baseline.sh new file mode 100755 index 0000000..08298a5 --- /dev/null +++ b/util-scripts/external-entities/demo/lock-baseline.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -eou pipefail + +ROX_ENDPOINT=${1:-localhost:8000} + +deploymentname=${2:-external-destination-source-1} + +json_deployments="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/deployments" -k -H "Authorization: Bearer $ROX_API_TOKEN")" + +json_deployments="$(echo "$json_deployments" | jq --arg deploymentname "$deploymentname" '{deployments: [.deployments[] | select(.name == $deploymentname)]}')" +deployment="$(echo "$json_deployments" | jq --arg deploymentname "$deploymentname" '{deployments: [.deployments[] | select(.name == $deploymentname)]}' | jq -r .deployments[0].id)" + +echo "json_deployments= $deployment" + +json_status="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/networkbaseline/${deployment}/status/external" -k -H "Authorization: Bearer $ROX_API_TOKEN")" + +echo "$json_status" | jq + + +json_status="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/networkbaseline/${deployment}/lock" -k -H "Authorization: Bearer $ROX_API_TOKEN")" diff --git a/util-scripts/external-entities/demo/mark-as-anomalous.sh b/util-scripts/external-entities/demo/mark-as-anomalous.sh new file mode 100755 index 0000000..29f58c3 --- /dev/null +++ b/util-scripts/external-entities/demo/mark-as-anomalous.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -eoux pipefail + +ROX_ENDPOINT=${1:-localhost:8000} + +deploymentname=${2:-external-destination-source-1} + +json_deployments="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/deployments" -k -H "Authorization: Bearer $ROX_API_TOKEN")" + +json_deployments="$(echo "$json_deployments" | jq --arg deploymentname "$deploymentname" '{deployments: [.deployments[] | select(.name == $deploymentname)]}')" +deployment="$(echo "$json_deployments" | jq --arg deploymentname "$deploymentname" '{deployments: [.deployments[] | select(.name == $deploymentname)]}' | jq -r .deployments[0].id)" + +echo "json_deployments= $deployment" + +json_status="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/networkbaseline/${deployment}/status/external" -k -H "Authorization: Bearer $ROX_API_TOKEN")" + +echo "$json_status" | jq + +echo +echo +echo +echo + +json_new_status="$(echo "$json_status" | jq -c '.anomalous |= map(.status = (if .status == "ANOMALOUS" then "BASELINE" else .status end))')" + + +echo "$json_new_status" | jq + +#cidr_json='{"entity": {"cidr": "'"$cidr_block"'", "name": "'"$cidr_name"'", "id": ""}}' + +#create_cidr_block_response_json="$(curl --location --silent --request POST --data "$cidr_json" "https://${ROX_ENDPOINT}/v1/networkgraph/cluster/$cluster_id/externalentities" -k --header "Authorization: Bearer $ROX_API_TOKEN")" + +echo +echo +echo +echo +echo "Setting new status" +curl --location --silent --request POST --data "$json_new_status" "https://${ROX_ENDPOINT}/v1/networkbaseline/${deployment}/peers" -k --header "Authorization: Bearer $ROX_API_TOKEN" + +echo +echo +echo +echo + +echo "Checking the new status" +json_updated_status="$(curl --location --silent --request GET "https://${ROX_ENDPOINT}/v1/networkbaseline/${deployment}/status/external" -k -H "Authorization: Bearer $ROX_API_TOKEN")" + + +echo "$json_updated_status" | jq diff --git a/util-scripts/external-entities/demo/ping-server/Dockerfile b/util-scripts/external-entities/demo/ping-server/Dockerfile new file mode 100644 index 0000000..c41aa11 --- /dev/null +++ b/util-scripts/external-entities/demo/ping-server/Dockerfile @@ -0,0 +1,9 @@ +FROM registry.fedoraproject.org/fedora:41 + +RUN dnf install -y python pip + +RUN pip install flask + +COPY ping_server.py /ping_server.py + +CMD python /ping_server.py diff --git a/util-scripts/external-entities/demo/ping-server/Makefile b/util-scripts/external-entities/demo/ping-server/Makefile new file mode 100644 index 0000000..7b3ed90 --- /dev/null +++ b/util-scripts/external-entities/demo/ping-server/Makefile @@ -0,0 +1,9 @@ +ifeq ($(TAG),) +TAG=$(shell git describe --tags --abbrev=10 --dirty) +endif + +.PHONY: +build: + docker build -t ping-server . + docker tag ping-server quay.io/$(REPOSITORY)/ping-server:$(TAG) + docker push quay.io/$(REPOSITORY)/ping-server:$(TAG) diff --git a/util-scripts/external-entities/demo/ping-server/ping-server.yml b/util-scripts/external-entities/demo/ping-server/ping-server.yml new file mode 100644 index 0000000..e9b0e8e --- /dev/null +++ b/util-scripts/external-entities/demo/ping-server/ping-server.yml @@ -0,0 +1,52 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "1" + generation: 1 + labels: + app: ping-server + name: ping-server + name: ping-server + namespace: qa +spec: + minReadySeconds: 15 + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: ping-server + name: ping-server + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: ping-server + deployment: ping-server + name: ping-server + name: ping-server + namespace: qa + spec: + imagePullSecrets: + - name: qa + containers: + - image: quay.io/jvirtane/ping-server:4.8.x-619-g1fb6d87786-dirty-4 + imagePullPolicy: IfNotPresent + name: external-destination-source + resources: {} + securityContext: + capabilities: {} + privileged: true + readOnlyRootFilesystem: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 diff --git a/util-scripts/external-entities/demo/ping-server/ping_server.py b/util-scripts/external-entities/demo/ping-server/ping_server.py new file mode 100644 index 0000000..0f12e40 --- /dev/null +++ b/util-scripts/external-entities/demo/ping-server/ping_server.py @@ -0,0 +1,55 @@ +import threading +import time +from flask import Flask, request +import socket + +app = Flask(__name__) + +# Set of active targets (tuple of IP and port) +targets = set() +targets_lock = threading.Lock() + +def ping_target(ip, port, timeout=1): + """Ping target via TCP socket""" + try: + with socket.create_connection((ip, int(port)), timeout=timeout): + print(f"[✓] Pinged {ip}:{port}") + except Exception as e: + print(f"[✗] Failed to ping {ip}:{port} - {e}") + +def pinger(): + """Background thread to continuously ping targets""" + while True: + with targets_lock: + current_targets = list(targets) + for ip, port in current_targets: + ping_target(ip, port) + time.sleep(2) # Interval between pings + +@app.route('/') +def handle_request(): + action = request.args.get('action') + ip = request.args.get('ip') + port = request.args.get('port') + + if not all([action, ip, port]): + return "Missing required parameters: action, ip, port", 400 + + target = (ip, int(port)) + + with targets_lock: + if action == 'open': + targets.add(target) + return f"Opened {ip}:{port}", 200 + elif action == 'close': + targets.discard(target) + return f"Closed {ip}:{port}", 200 + else: + return f"Invalid action '{action}'", 400 + +if __name__ == '__main__': + # Start the background ping loop + threading.Thread(target=pinger, daemon=True).start() + # Start the Flask server + app.run(host='127.0.0.1', port=8181) + diff --git a/util-scripts/external-entities/demo/ping_in_loop.py b/util-scripts/external-entities/demo/ping_in_loop.py new file mode 100644 index 0000000..5204b9c --- /dev/null +++ b/util-scripts/external-entities/demo/ping_in_loop.py @@ -0,0 +1,69 @@ +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import urlparse, parse_qs +import threading +import socket +import time + +# Shared set of targets +targets_lock = threading.Lock() +targets = set() +ping_interval = 2 # seconds between pings + +def ping_target(ip, port): + try: + with socket.create_connection((ip, port), timeout=2): + print(f"Success: {ip}:{port}") + except Exception as e: + print(f"Failed: {ip}:{port} - {e}") + +def ping_loop(): + while True: + with targets_lock: + current_targets = list(targets) + for ip, port in current_targets: + ping_target(ip, port) + time.sleep(ping_interval) + +class RequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + parsed_path = urlparse(self.path) + query = parse_qs(parsed_path.query) + + if parsed_path.path == "/action=open": + ip = query.get("ip", [None])[0] + port = query.get("port", [None])[0] + print("ip= ", ip, " port= ", port) + + if ip and port: + try: + port = int(port) + with targets_lock: + targets.clear() # Reset all current targets + targets.add((ip, port)) + self.send_response(200) + self.end_headers() + self.wfile.write(f"Pinging {ip}:{port}...".encode()) + except ValueError: + self.send_response(400) + self.end_headers() + self.wfile.write(b"Invalid port value.") + else: + self.send_response(400) + self.end_headers() + self.wfile.write(b"Missing ip or port parameter.") + else: + self.send_response(404) + self.end_headers() + + def log_message(self, format, *args): + return # Disable default logging + +def run_server(port=8181): + server = HTTPServer(('127.0.0.1', port), RequestHandler) + print(f"Server started on port {port}") + server.serve_forever() + +if __name__ == "__main__": + threading.Thread(target=ping_loop, daemon=True).start() + run_server() + diff --git a/util-scripts/external-entities/demo/prepare-tap.sh b/util-scripts/external-entities/demo/prepare-tap.sh new file mode 100755 index 0000000..e538725 --- /dev/null +++ b/util-scripts/external-entities/demo/prepare-tap.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -eou pipefail + +# This script helps to prepare an environment for developing berserker network +# workload. It has the following preparatory steps: +# * Create and start up a new tun device for berserker to use +# * Optionally prepare iptables for the device to be visible +# +# The last step is optional, because iptables configuration could be different +# between development environments. Meaning it's not guaranteed that this part of +# the script is suitable for every case. + +stop() { echo "$*" 1>&2 ; exit 1; } + +which ip &>/dev/null || stop "Don't have the ip tool" +which whoami &>/dev/null || stop "Don't have the whoami tool" +which sysctl &>/dev/null || stop "Don't have the sysctl tool" + +ADDRESS="10.0.0.1/16" +NAME="berserker0" +USER="$(whoami)" +CONFIGURE_IPTABLE="false" +CONFIGURE_FIREWALLD="false" +CONFIGURE_TUNTAP_IF_EXISTS="false" + +while getopts ":a:t:u:i:fo" opt; do + case $opt in + a) ADDRESS="${OPTARG}" + ;; + t) NAME="${OPTARG}" + ;; + u) USER="${OPTARG}" + ;; + i) CONFIGURE_IPTABLE="true" + ;; + f) CONFIGURE_FIREWALLD="true" + ;; + o) CONFIGURE_TUNTAP_IF_EXISTS="true" + ;; + \?) echo "Invalid option -$OPTARG" >&2 + exit 1 + ;; + esac +done + +echo "Verifying if device ${NAME} is already created..." +if ip tuntap | grep "${NAME}" &> /dev/null; +then + echo "The devince ${NAME} already exists!" + if [[ "${CONFIGURE_TUNTAP_IF_EXISTS}" != "true" ]] + then + exit 1; + fi + + ip link delete "${NAME}" +fi + +echo "Creating tun device ${NAME} for user ${USER}..." +ip tuntap add name "${NAME}" mode tun user "${USER}" +ip link set "${NAME}" up + +echo "Assigning address ${ADDRESS} to device ${NAME}..." +ip addr add "${ADDRESS}" dev "${NAME}" + +if [[ "${CONFIGURE_FIREWALLD}" == "true" ]]; +then + which firewall-cmd &>/dev/null || stop "Don't have the firewal-cmd tool" + + echo "Adding to the trusted zone..." + firewall-cmd --zone=trusted --add-interface="${NAME}" +fi + +if [[ "${CONFIGURE_IPTABLE}" == "true" ]]; +then + which iptables &>/dev/null || stop "Don't have the iptables tool" + + echo "Enabling ip forward..." + sysctl net.ipv4.ip_forward=1 + + echo "Preparing iptable..." + iptables -t nat -A POSTROUTING -s "${ADDRESS}" -j MASQUERADE + iptables -A FORWARD -i "${NAME}" -s "${ADDRESS}" -j ACCEPT + iptables -A FORWARD -o "${NAME}" -d "${ADDRESS}" -j ACCEPT + + RULE_NR=$(iptables -t filter -L INPUT --line-numbers |\ + grep "REJECT all" |\ + awk '{print $1}') + + # Excempt tun device from potentiall reject all rule + if [[ $RULE_NR == "" ]]; then + iptables -I INPUT -i "${NAME}" -s "${ADDRESS}" -j ACCEPT + else + iptables -I INPUT $((RULE_NR - 1)) -i "${NAME}" -s "${ADDRESS}" -j ACCEPT + fi +fi diff --git a/util-scripts/external-entities/demo/scenario.sh b/util-scripts/external-entities/demo/scenario.sh new file mode 100755 index 0000000..c6c2fcf --- /dev/null +++ b/util-scripts/external-entities/demo/scenario.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -eou pipefail + +# Running these commands one by one is recommended instead of running this script. +# Check the UI after each command. +# This assumes that ACS has been deployed with ROX_EXTERNAL_IPS and ROX_NETWORK_GRAPH_EXTERNAL_IPS + +# Cleanup from previous runs +kubectl delete ns qa || true +kubectl -n stackrox delete configmap collector-config || true + +kubectl create ns qa + +# Create the image pull secret +./create-secret-for-qa.sh + +# Enable collection of external IPs by collector +./enable-collector-external-ips.sh + +#sleep 60 + +# Create a deployment that reaches out to google in the qa namespace +kubectl create -f external-destination-source.yml + +# Create a deployment that reaches out to 8.8.8.8 +./create-deployment-with-ext-ip.sh 8.8.8.8 53 1 + +# Remove comments before merging +# Create a CIDR block that matches the above deployment +#./create-cidr-block.sh 8.8.8.0/24 testCIDR + + +# Create a deployment that reaches out to 1.1.1.1 +./create-deployment-with-ext-ip.sh 1.1.1.1 53 2 + +# Get a network policy based on the network graph +# and apply it +./get-network-policy.sh > net-pol.yml +kubectl create -f net-pol.yml + +# Create a deployment that reaches out to 2.2.2.2 +./create-deployment-with-ext-ip.sh 2.2.2.2 53 3 + +# Remove comments before merging +# Try with different ports +# Mark as anom +# Alert on violations diff --git a/util-scripts/external-entities/demo/setup-env-var.sh b/util-scripts/external-entities/demo/setup-env-var.sh new file mode 100644 index 0000000..839a397 --- /dev/null +++ b/util-scripts/external-entities/demo/setup-env-var.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +export ROX_EXTERNAL_IPS=true +export ROX_NETWORK_GRAPH_EXTERNAL_IPS=true From b99b447eb7458cbc304e95c243583c57966499d3 Mon Sep 17 00:00:00 2001 From: JoukoVirtanen Date: Tue, 3 Jun 2025 21:32:50 -0700 Subject: [PATCH 3/4] Removed unused files --- .../external-entities/demo/deployment.yml | 61 ---------------- .../external-entities/demo/ping_in_loop.py | 69 ------------------- 2 files changed, 130 deletions(-) delete mode 100644 util-scripts/external-entities/demo/deployment.yml delete mode 100644 util-scripts/external-entities/demo/ping_in_loop.py diff --git a/util-scripts/external-entities/demo/deployment.yml b/util-scripts/external-entities/demo/deployment.yml deleted file mode 100644 index f39a38e..0000000 --- a/util-scripts/external-entities/demo/deployment.yml +++ /dev/null @@ -1,61 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - annotations: - deployment.kubernetes.io/revision: "1" - generation: 1 - labels: - app: external-destination-source-3 - name: external-destination-source-3 - name: external-destination-source-3 - namespace: qa -spec: - minReadySeconds: 15 - progressDeadlineSeconds: 600 - replicas: 1 - revisionHistoryLimit: 10 - selector: - matchLabels: - app: external-destination-source-3 - name: external-destination-source-3 - strategy: - rollingUpdate: - maxSurge: 25% - maxUnavailable: 25% - type: RollingUpdate - template: - metadata: - labels: - app: external-destination-source-3 - deployment: external-destination-source-3 - name: external-destination-source-3 - name: external-destination-source-3 - namespace: qa - spec: - imagePullSecrets: - - name: qa - containers: - - args: - - | - /scripts/prepare-tap.sh -a "2.2.2.2/32" -o - nc -lk "2.2.2.2" "53" & - sleep 2 - while sleep 30; do nc -zv "2.2.2.2" "53"; done - command: - - /bin/bash - - -c - image: quay.io/jvirtane/external-connection:4.8.x-619-g1fb6d87786-dirty-4 - imagePullPolicy: IfNotPresent - name: external-destination-source - resources: {} - securityContext: - capabilities: {} - privileged: true - readOnlyRootFilesystem: false - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - dnsPolicy: ClusterFirst - restartPolicy: Always - schedulerName: default-scheduler - securityContext: {} - terminationGracePeriodSeconds: 30 diff --git a/util-scripts/external-entities/demo/ping_in_loop.py b/util-scripts/external-entities/demo/ping_in_loop.py deleted file mode 100644 index 5204b9c..0000000 --- a/util-scripts/external-entities/demo/ping_in_loop.py +++ /dev/null @@ -1,69 +0,0 @@ -from http.server import BaseHTTPRequestHandler, HTTPServer -from urllib.parse import urlparse, parse_qs -import threading -import socket -import time - -# Shared set of targets -targets_lock = threading.Lock() -targets = set() -ping_interval = 2 # seconds between pings - -def ping_target(ip, port): - try: - with socket.create_connection((ip, port), timeout=2): - print(f"Success: {ip}:{port}") - except Exception as e: - print(f"Failed: {ip}:{port} - {e}") - -def ping_loop(): - while True: - with targets_lock: - current_targets = list(targets) - for ip, port in current_targets: - ping_target(ip, port) - time.sleep(ping_interval) - -class RequestHandler(BaseHTTPRequestHandler): - def do_GET(self): - parsed_path = urlparse(self.path) - query = parse_qs(parsed_path.query) - - if parsed_path.path == "/action=open": - ip = query.get("ip", [None])[0] - port = query.get("port", [None])[0] - print("ip= ", ip, " port= ", port) - - if ip and port: - try: - port = int(port) - with targets_lock: - targets.clear() # Reset all current targets - targets.add((ip, port)) - self.send_response(200) - self.end_headers() - self.wfile.write(f"Pinging {ip}:{port}...".encode()) - except ValueError: - self.send_response(400) - self.end_headers() - self.wfile.write(b"Invalid port value.") - else: - self.send_response(400) - self.end_headers() - self.wfile.write(b"Missing ip or port parameter.") - else: - self.send_response(404) - self.end_headers() - - def log_message(self, format, *args): - return # Disable default logging - -def run_server(port=8181): - server = HTTPServer(('127.0.0.1', port), RequestHandler) - print(f"Server started on port {port}") - server.serve_forever() - -if __name__ == "__main__": - threading.Thread(target=ping_loop, daemon=True).start() - run_server() - From 17e373580cc13677d32d8a585859fd09928f4cf4 Mon Sep 17 00:00:00 2001 From: JoukoVirtanen Date: Wed, 4 Jun 2025 15:00:26 -0700 Subject: [PATCH 4/4] Renamed ping-server to dynamic-connector --- .../Dockerfile | 4 ++-- .../demo/dynamic-connector/Makefile | 9 ++++++++ .../demo/dynamic-connector/demo.sh | 16 ++++++++++++++ .../dynamic-connector.py} | 19 ++++++++-------- .../dynamic-connector.yml} | 22 +++++++++---------- .../demo/dynamic-connector/pre-demo.sh | 12 ++++++++++ .../demo/ping-server/Makefile | 9 -------- 7 files changed, 59 insertions(+), 32 deletions(-) rename util-scripts/external-entities/demo/{ping-server => dynamic-connector}/Dockerfile (54%) create mode 100644 util-scripts/external-entities/demo/dynamic-connector/Makefile create mode 100755 util-scripts/external-entities/demo/dynamic-connector/demo.sh rename util-scripts/external-entities/demo/{ping-server/ping_server.py => dynamic-connector/dynamic-connector.py} (72%) rename util-scripts/external-entities/demo/{ping-server/ping-server.yml => dynamic-connector/dynamic-connector.yml} (71%) create mode 100755 util-scripts/external-entities/demo/dynamic-connector/pre-demo.sh delete mode 100644 util-scripts/external-entities/demo/ping-server/Makefile diff --git a/util-scripts/external-entities/demo/ping-server/Dockerfile b/util-scripts/external-entities/demo/dynamic-connector/Dockerfile similarity index 54% rename from util-scripts/external-entities/demo/ping-server/Dockerfile rename to util-scripts/external-entities/demo/dynamic-connector/Dockerfile index c41aa11..4615627 100644 --- a/util-scripts/external-entities/demo/ping-server/Dockerfile +++ b/util-scripts/external-entities/demo/dynamic-connector/Dockerfile @@ -4,6 +4,6 @@ RUN dnf install -y python pip RUN pip install flask -COPY ping_server.py /ping_server.py +COPY dynamic-connector.py /dynamic-connector.py -CMD python /ping_server.py +CMD python /dynamic-connector.py diff --git a/util-scripts/external-entities/demo/dynamic-connector/Makefile b/util-scripts/external-entities/demo/dynamic-connector/Makefile new file mode 100644 index 0000000..ef6725c --- /dev/null +++ b/util-scripts/external-entities/demo/dynamic-connector/Makefile @@ -0,0 +1,9 @@ +ifeq ($(TAG),) +TAG=$(shell git describe --tags --abbrev=10 --dirty) +endif + +.PHONY: +build: + docker build -t dynamic-connector . + docker tag dynamic-connector quay.io/$(REPOSITORY)/dynamic-connector:$(TAG) + docker push quay.io/$(REPOSITORY)/dynamic-connector:$(TAG) diff --git a/util-scripts/external-entities/demo/dynamic-connector/demo.sh b/util-scripts/external-entities/demo/dynamic-connector/demo.sh new file mode 100755 index 0000000..48e4817 --- /dev/null +++ b/util-scripts/external-entities/demo/dynamic-connector/demo.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -eou pipefail + +curl "http://127.0.0.1:8181/?action=open&ip=8.8.8.8&port=53" + +# Check network graph + +# Lock baseline + +curl "http://127.0.0.1:8181/?action=open&ip=142.250.72.238&port=80" + +# Check network graph and violations + +curl "http://127.0.0.1:8181/?action=open&ip=1.1.1.1&port=53" + +# Check network graph diff --git a/util-scripts/external-entities/demo/ping-server/ping_server.py b/util-scripts/external-entities/demo/dynamic-connector/dynamic-connector.py similarity index 72% rename from util-scripts/external-entities/demo/ping-server/ping_server.py rename to util-scripts/external-entities/demo/dynamic-connector/dynamic-connector.py index 0f12e40..8abab41 100644 --- a/util-scripts/external-entities/demo/ping-server/ping_server.py +++ b/util-scripts/external-entities/demo/dynamic-connector/dynamic-connector.py @@ -9,22 +9,22 @@ targets = set() targets_lock = threading.Lock() -def ping_target(ip, port, timeout=1): - """Ping target via TCP socket""" +def connect_to_target(ip, port, timeout=1): + """Connect to target via TCP socket""" try: with socket.create_connection((ip, int(port)), timeout=timeout): - print(f"[✓] Pinged {ip}:{port}") + print(f"[✓] Created connection {ip}:{port}") except Exception as e: - print(f"[✗] Failed to ping {ip}:{port} - {e}") + print(f"[✗] Failed to connect {ip}:{port} - {e}") -def pinger(): - """Background thread to continuously ping targets""" +def connector(): + """Background thread to continuously connect to targets""" while True: with targets_lock: current_targets = list(targets) for ip, port in current_targets: - ping_target(ip, port) - time.sleep(2) # Interval between pings + connect_to_target(ip, port) + time.sleep(2) @app.route('/') def handle_request(): @@ -48,8 +48,7 @@ def handle_request(): return f"Invalid action '{action}'", 400 if __name__ == '__main__': - # Start the background ping loop - threading.Thread(target=pinger, daemon=True).start() + threading.Thread(target=connector, daemon=True).start() # Start the Flask server app.run(host='127.0.0.1', port=8181) diff --git a/util-scripts/external-entities/demo/ping-server/ping-server.yml b/util-scripts/external-entities/demo/dynamic-connector/dynamic-connector.yml similarity index 71% rename from util-scripts/external-entities/demo/ping-server/ping-server.yml rename to util-scripts/external-entities/demo/dynamic-connector/dynamic-connector.yml index e9b0e8e..662be36 100644 --- a/util-scripts/external-entities/demo/ping-server/ping-server.yml +++ b/util-scripts/external-entities/demo/dynamic-connector/dynamic-connector.yml @@ -5,9 +5,9 @@ metadata: deployment.kubernetes.io/revision: "1" generation: 1 labels: - app: ping-server - name: ping-server - name: ping-server + app: dynamic-connector + name: dynamic-connector + name: dynamic-connector namespace: qa spec: minReadySeconds: 15 @@ -16,8 +16,8 @@ spec: revisionHistoryLimit: 10 selector: matchLabels: - app: ping-server - name: ping-server + app: dynamic-connector + name: dynamic-connector strategy: rollingUpdate: maxSurge: 25% @@ -26,18 +26,18 @@ spec: template: metadata: labels: - app: ping-server - deployment: ping-server - name: ping-server - name: ping-server + app: dynamic-connector + deployment: dynamic-connector + name: dynamic-connector + name: dynamic-connector namespace: qa spec: imagePullSecrets: - name: qa containers: - - image: quay.io/jvirtane/ping-server:4.8.x-619-g1fb6d87786-dirty-4 + - image: quay.io/jvirtane/dynamic-connector:4.8.x-909-gd5216e5572 imagePullPolicy: IfNotPresent - name: external-destination-source + name: dynamic-connector resources: {} securityContext: capabilities: {} diff --git a/util-scripts/external-entities/demo/dynamic-connector/pre-demo.sh b/util-scripts/external-entities/demo/dynamic-connector/pre-demo.sh new file mode 100755 index 0000000..d8f21a7 --- /dev/null +++ b/util-scripts/external-entities/demo/dynamic-connector/pre-demo.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -eou pipefail + +kubectl delete ns qa || true + +kubectl create ns qa + +kubectl create -f dynamic-connector.yml + +sleep 15 + +kubectl -n qa port-forward deploy/dynamic-connector 8181 > /dev/null 2>&1 & diff --git a/util-scripts/external-entities/demo/ping-server/Makefile b/util-scripts/external-entities/demo/ping-server/Makefile deleted file mode 100644 index 7b3ed90..0000000 --- a/util-scripts/external-entities/demo/ping-server/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -ifeq ($(TAG),) -TAG=$(shell git describe --tags --abbrev=10 --dirty) -endif - -.PHONY: -build: - docker build -t ping-server . - docker tag ping-server quay.io/$(REPOSITORY)/ping-server:$(TAG) - docker push quay.io/$(REPOSITORY)/ping-server:$(TAG)