From 1f934570d840318fead6f5ffba966007783b642f Mon Sep 17 00:00:00 2001 From: Rebecca Mahany-Horton Date: Wed, 24 Jan 2024 10:44:54 -0500 Subject: [PATCH] uwsgi maybe --- tests/.gitignore | 1 + tests/k2server.py | 144 +++++++++++++------------------------- tests/kolide-launcher.nix | 34 +++++---- 3 files changed, 70 insertions(+), 109 deletions(-) create mode 100644 tests/.gitignore diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/tests/k2server.py b/tests/k2server.py index e50a22b..5525a66 100755 --- a/tests/k2server.py +++ b/tests/k2server.py @@ -1,98 +1,54 @@ #!/usr/bin/env python3 -import json -import sys - -from http.server import BaseHTTPRequestHandler, HTTPServer -from urllib.parse import urlparse +from flask import Flask, jsonify, request, Response +application = Flask(__name__) agent_flags_hash = "8c3503bd-c9f7-4503-ba8e-a51215e3e565" -class K2MockServer(BaseHTTPRequestHandler): - - def do_POST(self): - req_path = str(self.path) - req_path_parsed = urlparse(req_path) - - # Device server: JSONRPC endpoint - if req_path_parsed.path == "/": - # Check `method` param: RequestEnrollment and RequestConfig require specific responses - req_body = json.loads(self.rfile.read(int(self.headers.get('Content-Length')))) - - self.send_response(200) - self.send_header('Content-type', 'application/json') - self.end_headers() - - if req_body["method"] == "RequestEnrollment": - self.wfile.write(json.dumps({ - "node_key": "abd", - "node_invalid": False - }).encode()) - elif req_body["method"] == "RequestConfig": - self.wfile.write(json.dumps({ - "config": "{}", - "node_invalid": False - }).encode()) - else: - self.wfile.write(json.dumps({ - "node_invalid": False - }).encode()) - - return - - # Control server: get subsystems and hashes - elif req_path_parsed.path == "/api/agent/config": - self.send_response(200) - self.send_header('Content-type', 'application/json') - self.end_headers() - self.wfile.write(json.dumps({ - "token": "4223b8d9-3d29-4ec9-ba18-957650cbeff0", - "config": { - "agent_flags": agent_flags_hash - } - }).encode()) - return - - # Not a supported endpoint - else: - self.send_response(404) - self.end_headers() - return - - def do_GET(self): - req_path = str(self.path) - req_path_parsed = urlparse(req_path) - - # Control server: get challenge - if req_path_parsed.path == "/api/agent/config": - self.send_response(200) - self.send_header('Content-type', 'application/octet-stream') - self.end_headers() - self.wfile.write(b'1676065364') - return - - # Control server: get agent flags - elif req_path_parsed.path == "/api/agent/object/" + agent_flags_hash: - self.send_response(200) - self.send_header('Content-type', 'application/json') - self.end_headers() - self.wfile.write(json.dumps({"desktop_enabled": "1"}).encode()) - return - - # Version endpoint - elif req_path_parsed.path == "/version": - self.send_response(200) - self.send_header('Content-type', 'text/html; charset=utf-8') - self.end_headers() - self.wfile.write(b'1') - return - - # Not a supported endpoint - else: - self.send_response(404) - self.end_headers() - return - - -if __name__ == '__main__': - s = HTTPServer(('0.0.0.0', 8080), K2MockServer) - s.serve_forever() +# Device Server: JSONRPC endpoint +@application.route("/", methods=['POST']) +def jsonrpc(): + # Check `method` param: RequestEnrollment and RequestConfig require specific responses + req_body = request.get_json() + if req_body["method"] == "RequestEnrollment": + return jsonify({ + "node_key": "abd", + "node_invalid": False + }) + elif req_body["method"] == "RequestConfig": + return jsonify({ + "config": "{}", + "node_invalid": False + }) + else: + return jsonify({ + "node_invalid": False + }) + +# Control server: get challenge +@application.route("/api/agent/config", methods=['GET']) +def challenge(): + return Response(response='1676065364', status=200, mimetype='application/octet-stream') + +# Control server: get subsystems and hashes +@application.route("/api/agent/config", methods=['POST']) +def subsystems(): + return jsonify({ + "token": "4223b8d9-3d29-4ec9-ba18-957650cbeff0", + "config": { + "agent_flags": agent_flags_hash + } + }) + +# Control server: get subsystem +@application.route("/api/agent/object/", methods=['GET']) +def get_subsystem(): + if hash != agent_flags_hash: + return '', 404 + return jsonify({"desktop_enabled": "1"}) + +# Version endpoint +@application.route("/version", methods=['GET']) +def version(): + r = Response(response='1', status=200, mimetype='text/html') + r.headers['Content-Type'] = 'text/html; charset=utf-8' + return r diff --git a/tests/kolide-launcher.nix b/tests/kolide-launcher.nix index 3ee63a9..77af906 100644 --- a/tests/kolide-launcher.nix +++ b/tests/kolide-launcher.nix @@ -54,20 +54,23 @@ pkgs.nixosTest { networking.interfaces.eth1.ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ]; networking.extraHosts = "127.0.0.1 app.kolide.test"; - services.nginx = { + services.uwsgi = { enable = true; - virtualHosts."app.kolide.test" = {}; - }; - - systemd.services.mock-k2-server = { - description = "Mock K2 server (device and control)"; - serviceConfig.Type = "simple"; - serviceConfig.ExecStart = "${pkgs.python3}/bin/python ${./k2server.py}"; - serviceConfig.Restart = "on-failure"; - serviceConfig.RestartSec = 1; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - }; + plugins = [ "python3" ]; + capabilities = [ "CAP_NET_BIND_SERVICE" ]; + instance.type = "emperor"; + + instance.vassals.k2server = { + type = "normal"; + immediate-uid = "k2server"; + immediate-gid = "k2server"; + module = "wsgi:application"; + http = ":80"; + cap = "net_bind_service"; + pythonPackages = self: [ self.flask ]; + chdir = pkgs.writeTextDir "wsgi.py" builtins.readFile ./k2server.py; + }; + } }; }; @@ -86,13 +89,14 @@ pkgs.nixosTest { # Wait for mock k2 server to be online k2server.wait_for_unit("network-online.target") - k2server.wait_for_unit("mock-k2-server.service") - k2server.wait_for_unit("nginx.service") + k2server.wait_for_unit("uwsgi.service") k2server.wait_for_open_port(80) k2server.succeed("curl --fail http://app.kolide.test:8080/version") # Ensure machine can reach mock k2 server machine.wait_for_unit("network-online.target") + machine.succeed("curl http://k2server/") + machine.succeed("curl http://k2server/version") machine.succeed("curl http://app.kolide.test/") machine.succeed("nc -v -z 192.168.1.2 80") machine.wait_until_succeeds("curl --fail http://app.kolide.test/version", timeout=60)