Skip to content

Commit

Permalink
Test improvements (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaMahany authored Jan 25, 2024
1 parent d248829 commit ebf4b90
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ indent_size = 2
[*.yml]
indent_style = space
indent_size = 2

# python files: 4 spaces for indents
[*.py]
indent_style = space
indent_size = 4
8 changes: 8 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ jobs:
path: ${{ steps.test-derivation.outputs.drvpath }}/test-*.png
retention-days: 1

- name: upload test flare
uses: actions/upload-artifact@v4
if: always()
with:
name: test-flare
path: ${{ steps.test-derivation.outputs.drvpath }}/kolide_agent_flare_report_*.zip
retention-days: 1

- name: show flake output attributes
run: nix flake show --impure

Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,8 @@ Then start the `kolide-launcher.service` service.
[NixOS tests](https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests)
live in the [./tests](./tests) directory and are included via flake checks.
They are currently intended to run in CI only.

#### Running the mock K2 server

To run the mock K2 server locally for testing purposes, you can run
`python3 -m flask --app k2server run` from the `tests` directory.
45 changes: 32 additions & 13 deletions modules/kolide-launcher/default.nix
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
flake: { config, lib, pkgs, ... }:

let
inherit (lib) types mkEnableOption mkOption mkIf;
inherit (lib) types mkEnableOption mkOption mkIf optional strings;
inherit (flake.packages.x86_64-linux) kolide-launcher;
cfg = config.services.kolide-launcher;
in
Expand Down Expand Up @@ -62,6 +62,22 @@ in
Initial autoupdater subprocess delay.
'';
};

insecureTransport = mkOption {
type = types.bool;
default = false;
description = ''
Do not use TLS for transport layer.
'';
};

insecureTLS = mkOption {
type = types.bool;
default = false;
description = ''
Do not verify TLS certs for outgoing connections.
'';
};
};

config = mkIf cfg.enable {
Expand All @@ -79,18 +95,21 @@ in
# module. So, until we have a better option, we give the kolide-launcher unit access to the symlinks
# in `/run/current-system/sw/bin` and other likely locations that will allow it to find software inside the Nix store.
Environment = "PATH=/run/wrappers/bin:/bin:/sbin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin";
ExecStart = ''
${flake.packages.x86_64-linux.kolide-launcher}/bin/launcher \
--hostname ${cfg.kolideHostname} \
--root_directory ${cfg.rootDirectory} \
--osqueryd_path ${flake.packages.x86_64-linux.kolide-launcher}/bin/osqueryd \
--enroll_secret_path ${cfg.enrollSecretDirectory}/secret \
--update_channel ${cfg.updateChannel} \
--transport jsonrpc \
--autoupdate \
--autoupdate_interval ${cfg.autoupdateInterval} \
--autoupdater_initial_delay ${cfg.autoupdaterInitialDelay}
'';
ExecStart = strings.concatStringsSep " " ([
"${flake.packages.x86_64-linux.kolide-launcher}/bin/launcher"
"--hostname ${cfg.kolideHostname}"
"--root_directory ${cfg.rootDirectory}"
"--osqueryd_path ${flake.packages.x86_64-linux.kolide-launcher}/bin/osqueryd"
"--enroll_secret_path ${cfg.enrollSecretDirectory}/secret"
"--update_channel ${cfg.updateChannel}"
"--transport jsonrpc"
"--autoupdate"
"--autoupdate_interval ${cfg.autoupdateInterval}"
"--autoupdater_initial_delay ${cfg.autoupdaterInitialDelay}"
]
++ optional cfg.insecureTransport "--insecure_transport"
++ optional cfg.insecureTLS "--insecure"
);
Restart = "on-failure";
RestartSec = 3;
};
Expand Down
1 change: 1 addition & 0 deletions tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__
87 changes: 87 additions & 0 deletions tests/k2server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
import json
from flask import Flask, jsonify, request, Response

application = Flask(__name__)
agent_flags_hash = "8c3503bd-c9f7-4503-ba8e-a51215e3e565"

# 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({
"result": {
"node_key": "abd",
"node_invalid": False
},
"error": None,
"id": 0
})
elif req_body["method"] == "RequestConfig":
return jsonify({
"result": {
"config": json.dumps({
"options": {
"audit_allow_config": False,
"audit_allow_fim_events": False,
"audit_allow_process_events": False,
"audit_allow_fork_process_events": False,
"audit_allow_selinux_events": False,
"audit_allow_sockets": False,
"audit_allow_user_events": False,
"disable_audit": False,
"disable_events": False,
"enable_file_events": False,
"events_max": 10000,
"enable_bpf_events": False,
"events_expiry": 3601,
"read_max": 52428800,
"logger_event_type": False,
"distributed_interval": 30,
"schedule_epoch": "1705518221"
}
}),
"node_invalid": False
},
"error": None,
"id": 0
})
else:
return jsonify({
"result": {
"node_invalid": False
},
"error": None,
"id": 0
})

# 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/<hash>", methods=['GET'])
def get_subsystem(hash):
if hash != agent_flags_hash:
return '', 404
return jsonify({"desktop_enabled_v1": "enabled"})

# 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
44 changes: 42 additions & 2 deletions tests/kolide-launcher.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,30 @@ pkgs.nixosTest {
hardware.pulseaudio.enable = true;

services.kolide-launcher.enable = true;
services.kolide-launcher.kolideHostname = "app.kolide.test:80";
services.kolide-launcher.insecureTransport = true;
services.kolide-launcher.insecureTLS = true;

system.stateVersion = "23.11";

# Set up mock k2 server locally
networking.extraHosts = "127.0.0.1 app.kolide.test";
services.uwsgi = {
enable = true;
plugins = [ "python3" ];
capabilities = [ "CAP_NET_BIND_SERVICE" ];
instance.type = "emperor";

instance.vassals.k2server = {
type = "normal";
module = "wsgi:application";
http = ":80";
http-timeout = 30;
cap = "net_bind_service";
pythonPackages = self: [ self.flask ];
chdir = pkgs.writeTextDir "wsgi.py" (builtins.readFile ./k2server.py);
};
};
};

enableOCR = true;
Expand All @@ -54,6 +77,11 @@ pkgs.nixosTest {
if "${ci}":
machine.start()
with subtest("mock K2 server starts up"):
machine.wait_for_unit("network-online.target")
machine.wait_for_unit("uwsgi.service")
machine.wait_until_succeeds("curl --fail http://app.kolide.test/version", timeout=60)
with subtest("log in to MATE"):
machine.wait_for_unit("display-manager.service", timeout=120)
machine.wait_for_file("${xauthority}")
Expand All @@ -74,9 +102,9 @@ pkgs.nixosTest {
with subtest("launcher service runs and is set up correctly"):
machine.systemctl("stop kolide-launcher.service")
machine.systemctl("start kolide-launcher.service")
machine.wait_for_unit("kolide-launcher.service", timeout=120)
machine.wait_for_unit("kolide-launcher.service", timeout=60)
machine.wait_for_file("/var/kolide-k2/k2device.kolide.com/debug.json")
machine.sleep(60)
machine.sleep(30)
machine.screenshot("test-screen2.png")
with subtest("osquery runs"):
Expand All @@ -88,6 +116,18 @@ pkgs.nixosTest {
machine.wait_for_file("/var/kolide-k2/k2device.kolide.com/menu.json")
machine.screenshot("test-screen4.png")
machine.wait_until_succeeds("pgrep -U ${uid} launcher", timeout=120)
machine.screenshot("test-screen5.png")
with subtest("launcher flare"):
_, launcher_find_stdout = machine.execute("ls /nix/store | grep kolide-launcher-")
machine.execute("/nix/store/" + launcher_find_stdout.strip() + "/bin/launcher flare --save local")
# copy_from_vm can't take a wildcard path, so find the exact path before copying
_, flare_ls_out = machine.execute("ls ./kolide_agent_flare_report_*.zip")
flare_path = "./" + flare_ls_out.strip()
machine.copy_from_vm(flare_path, "./")
machine.shutdown()
'';
}

0 comments on commit ebf4b90

Please sign in to comment.