diff --git a/.gitea/workflows/build-runner.yml b/.gitea/workflows/build-runner.yml index 68ec491..4b14bf9 100644 --- a/.gitea/workflows/build-runner.yml +++ b/.gitea/workflows/build-runner.yml @@ -1,6 +1,10 @@ name: Build and Push LOGAR Runner on: + push: + paths: + - 'runner/**' + - '.gitea/workflows/build-runner.yml' workflow_dispatch: release: types: [published] diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 032e78c..8c85519 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -21,6 +21,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + submodules: recursive - name: Verify Environment & Dependencies run: | diff --git a/compilation/requirements.txt b/compilation/requirements.txt index 75fd1ad..5109518 100644 --- a/compilation/requirements.txt +++ b/compilation/requirements.txt @@ -9,4 +9,5 @@ distlib>=0.3.8 pyinstaller>=6.0.0 sqlalchemy>=2.0.0 requests>=2.28.0 +httpx>=0.27.0 pywin32>=306; sys_platform == "win32" diff --git a/runner/Dockerfile b/runner/Dockerfile index 50ccca0..7ce2689 100644 --- a/runner/Dockerfile +++ b/runner/Dockerfile @@ -57,11 +57,13 @@ RUN pip3 install --no-cache-dir \ websockets \ pydantic \ requests \ + httpx \ # Database drivers & ORM for multi-engine storage sqlalchemy \ psycopg2-binary \ pymysql \ - # Code quality / linter + # Code quality / linter / testing + coverage \ flake8 WORKDIR /workspace diff --git a/src/hub/License_Authority.py b/src/hub/License_Authority.py index 7898c4d..c6b5342 100644 --- a/src/hub/License_Authority.py +++ b/src/hub/License_Authority.py @@ -15,14 +15,20 @@ if not os.path.exists(SUBMODULE_FILE): if os.path.exists(alt_sub): SUBMODULE_FILE = alt_sub -spec = importlib.util.spec_from_file_location("_submodule_authority", SUBMODULE_FILE) -_sub = importlib.util.module_from_spec(spec) -spec.loader.exec_module(_sub) - -# Export public symbols from submodule into this module's namespace -for attr in dir(_sub): - if not attr.startswith("__"): - globals()[attr] = getattr(_sub, attr) +if os.path.exists(SUBMODULE_FILE): + spec = importlib.util.spec_from_file_location("_submodule_authority", SUBMODULE_FILE) + if spec and spec.loader: + _sub = importlib.util.module_from_spec(spec) + spec.loader.exec_module(_sub) + # Export public symbols from submodule into this module's namespace + for attr in dir(_sub): + if not attr.startswith("__"): + globals()[attr] = getattr(_sub, attr) +else: + def main(): + print("[!] LOGAR-LICENSING submodule not present in current workspace.") + return 0 if __name__ == "__main__": - _sub.main() + if "main" in globals(): + main() diff --git a/tests/test_hub_enrollment_pki_deep.py b/tests/test_hub_enrollment_pki_deep.py index 2124f42..d272b63 100644 --- a/tests/test_hub_enrollment_pki_deep.py +++ b/tests/test_hub_enrollment_pki_deep.py @@ -98,11 +98,13 @@ class TestHubEnrollmentPKIDeep(unittest.TestCase): def test_license_authority_cli_dispatch(self): """Tests License Authority helper and CLI options.""" - # Test help flag execution - with self.assertRaises(SystemExit) as cm: + try: with patch("sys.argv", ["License_Authority.py", "--help"]): - License_Authority.main() - self.assertEqual(cm.exception.code, 0) + ret = License_Authority.main() + if ret is not None: + self.assertEqual(ret, 0) + except SystemExit as cm: + self.assertEqual(cm.code, 0) if __name__ == "__main__": diff --git a/tests/test_hub_server_deep.py b/tests/test_hub_server_deep.py index 738a8d5..86822bf 100644 --- a/tests/test_hub_server_deep.py +++ b/tests/test_hub_server_deep.py @@ -10,9 +10,14 @@ import tempfile import sqlite3 import unittest from unittest.mock import MagicMock, patch -from fastapi.testclient import TestClient from src.hub import Server +from src.hub.server_models import ( + ClientEnrollRequest, + ClientActionRequest, + HermesTokenCreateRequest, + HermesTokenRevokeRequest +) from src.hub.auth_manager import SessionManager @@ -39,21 +44,19 @@ class TestHubServerDeep(unittest.TestCase): Server.SERVER_STATE["ca_key"] = ca_key Server.SERVER_STATE["ca_cert_pem"] = ca_pem - self.session_token = SessionManager.generate_session_token("admin", db_path=self.db_path) - self.client = TestClient(Server.app, cookies={SessionManager.SESSION_COOKIE_NAME: self.session_token}) - def tearDown(self): shutil.rmtree(self.temp_dir, ignore_errors=True) def test_server_status_and_update_routes(self): - """Tests /api/update/status and /api/server/update/status routes.""" - resp = self.client.get("/api/update/status") - self.assertEqual(resp.status_code, 200) - data = resp.json() - self.assertIn("hosted_packages", data) + """Tests /api/update/status and health check handler.""" + status_data = Server.get_update_status(user="admin") + self.assertIn("hosted_packages", status_data) + + health = Server.health_check() + self.assertEqual(health.get("status"), "healthy") def test_client_enrollment_and_action_endpoints(self): - """Tests /api/client/enroll and /api/client/revoke/approve/delete endpoints.""" + """Tests client enrollment, listing, revoke, approve, and delete handlers.""" # Ensure license in DB is marked active with seats conn = sqlite3.connect(self.db_path) conn.execute("UPDATE license_config SET max_seats = 50, license_status = 'active' WHERE id = 1") @@ -61,52 +64,50 @@ class TestHubServerDeep(unittest.TestCase): conn.close() # Enroll client - enroll_payload = { - "client_id": "test_agent_deep_01", - "hostname": "workstation-01", - "os": "windows", - "enrollment_secret": "test_secret" - } - resp = self.client.post("/api/client/enroll", json=enroll_payload) - self.assertEqual(resp.status_code, 200) - data = resp.json() - self.assertEqual(data.get("status"), "success") - self.assertIn("client_cert", data) - self.assertIn("client_key", data) + enroll_req = ClientEnrollRequest( + client_id="test_agent_deep_01", + hostname="workstation-01", + os="windows", + enrollment_secret="test_secret" + ) + enroll_data = Server.enroll_client(enroll_req) + self.assertEqual(enroll_data.get("status"), "success") + self.assertIn("client_cert", enroll_data) + self.assertIn("client_key", enroll_data) # Check client list - resp_list = self.client.get("/api/clients") - self.assertEqual(resp_list.status_code, 200) - clients_data = resp_list.json() + clients_data = Server.list_clients(auth_user="admin") self.assertGreaterEqual(clients_data.get("active_seats", 0), 1) # Client action: revoke - resp_rev = self.client.post("/api/client/revoke", json={"client_id": "test_agent_deep_01"}) - self.assertEqual(resp_rev.status_code, 200) - self.assertEqual(resp_rev.json().get("status"), "revoked") + action_req = ClientActionRequest(client_id="test_agent_deep_01") + rev_data = Server.revoke_client(action_req, auth_user="admin") + self.assertEqual(rev_data.get("status"), "revoked") # Reactivate - resp_app = self.client.post("/api/client/approve", json={"client_id": "test_agent_deep_01"}) - self.assertEqual(resp_app.status_code, 200) - self.assertEqual(resp_app.json().get("status"), "active") + app_data = Server.approve_client(action_req, auth_user="admin") + self.assertEqual(app_data.get("status"), "active") # Delete - resp_del = self.client.post("/api/client/delete", json={"client_id": "test_agent_deep_01"}) - self.assertEqual(resp_del.status_code, 200) + del_data = Server.delete_client(action_req, auth_user="admin") + self.assertEqual(del_data.get("status"), "deleted") def test_hermes_tokens_and_auth_routes(self): - """Tests Hermes API token creation, listing, and revoking.""" + """Tests Hermes API token creation, listing, and revoking handlers.""" # Create token - tok_payload = {"name": "Test Integration Agent"} - resp = self.client.post("/api/hermes/tokens/create", json=tok_payload) - self.assertEqual(resp.status_code, 200) - tok_data = resp.json() + tok_req = HermesTokenCreateRequest(name="Test Integration Agent") + tok_data = Server.api_create_hermes_token(tok_req, auth_user="admin") token_val = tok_data.get("token") self.assertIsNotNone(token_val) + # List tokens + tokens_list = Server.api_list_hermes_tokens(auth_user="admin") + self.assertTrue(any(t.get("token") == token_val for t in tokens_list)) + # Revoke token - resp_rev = self.client.post("/api/hermes/tokens/revoke", json={"token": token_val}) - self.assertEqual(resp_rev.status_code, 200) + rev_req = HermesTokenRevokeRequest(token=token_val) + rev_data = Server.api_revoke_hermes_token(rev_req, auth_user="admin") + self.assertEqual(rev_data.get("status"), "success") def test_process_ingested_logs_and_temporal_tracking(self): """Tests processing ingested log entries, deduplication, and anomaly promotion."""