From 0414da03c462e9b2d4cea02d2aec63b530f8784a Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Fri, 19 Jul 2024 14:06:36 +0200 Subject: [PATCH 01/26] Update aikido env variables --- README.md | 6 +++++- sample-apps/django-mysql-gunicorn/.env.example | 3 +++ sample-apps/django-mysql/.env.example | 3 +++ sample-apps/flask-mysql-uwsgi/.env.example | 1 + sample-apps/flask-mysql/.env.example | 1 + 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e371e638..1134ed9b5 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ -# firewall-python \ No newline at end of file +# firewall-python + +## Environment variables +- `AIKIDO_SECRET_KEY` : Secret to encrypt IPC comms +- `AIKIDO_DEBUG` : Boolean value to enable debug logs diff --git a/sample-apps/django-mysql-gunicorn/.env.example b/sample-apps/django-mysql-gunicorn/.env.example index 7815dc0e0..f842f1455 100644 --- a/sample-apps/django-mysql-gunicorn/.env.example +++ b/sample-apps/django-mysql-gunicorn/.env.example @@ -3,4 +3,7 @@ MYSQL_DATABASE="db" MYSQL_USER="user" MYSQL_PASSWORD="password" MYSQL_ROOT_PASSWORD="password" + +# Aikido keys AIKIDO_DEBUG=true +AIKIDO_SECRET_KEY="your_secret_key" diff --git a/sample-apps/django-mysql/.env.example b/sample-apps/django-mysql/.env.example index 7815dc0e0..f842f1455 100644 --- a/sample-apps/django-mysql/.env.example +++ b/sample-apps/django-mysql/.env.example @@ -3,4 +3,7 @@ MYSQL_DATABASE="db" MYSQL_USER="user" MYSQL_PASSWORD="password" MYSQL_ROOT_PASSWORD="password" + +# Aikido keys AIKIDO_DEBUG=true +AIKIDO_SECRET_KEY="your_secret_key" diff --git a/sample-apps/flask-mysql-uwsgi/.env.example b/sample-apps/flask-mysql-uwsgi/.env.example index 35d3d4bab..3fd0f3424 100644 --- a/sample-apps/flask-mysql-uwsgi/.env.example +++ b/sample-apps/flask-mysql-uwsgi/.env.example @@ -1 +1,2 @@ AIKIDO_DEBUG=true +AIKIDO_SECRET_KEY="your_secret_key" diff --git a/sample-apps/flask-mysql/.env.example b/sample-apps/flask-mysql/.env.example index 35d3d4bab..3fd0f3424 100644 --- a/sample-apps/flask-mysql/.env.example +++ b/sample-apps/flask-mysql/.env.example @@ -1 +1,2 @@ AIKIDO_DEBUG=true +AIKIDO_SECRET_KEY="your_secret_key" From c2627c1453f8b5829dfad2bb90d79bab5ce3cb69 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Fri, 19 Jul 2024 14:10:28 +0200 Subject: [PATCH 02/26] Update agent __init__.py : Create IPC --- aikido_firewall/__init__.py | 4 +- aikido_firewall/agent/__init__.py | 106 ++++++++++++++++++------------ 2 files changed, 66 insertions(+), 44 deletions(-) diff --git a/aikido_firewall/__init__.py b/aikido_firewall/__init__.py index 292c8aed9..0d6499762 100644 --- a/aikido_firewall/__init__.py +++ b/aikido_firewall/__init__.py @@ -9,7 +9,7 @@ from aikido_firewall.helpers.logging import logger # Import agent -from aikido_firewall.agent import start_agent +from aikido_firewall.agent import start_ipc # Load environment variables load_dotenv() @@ -25,4 +25,4 @@ def protect(): import aikido_firewall.sinks.pymysql logger.info("Aikido python firewall started") - start_agent() + start_ipc() diff --git a/aikido_firewall/agent/__init__.py b/aikido_firewall/agent/__init__.py index 418f0172b..7a907b5d4 100644 --- a/aikido_firewall/agent/__init__.py +++ b/aikido_firewall/agent/__init__.py @@ -3,72 +3,94 @@ """ import time -import queue -from threading import Thread +import os +from multiprocessing.connection import Listener, Client +from multiprocessing import Process from aikido_firewall.helpers.logging import logger -AGENT_SEC_INTERVAL = 60 +AGENT_SEC_INTERVAL = 5 +IPC_ADDRESS = ("localhost", 9898) # Specify the IP address and port -class AikidoThread: +class AikidoProc: """ Our agent thread """ - def __init__(self, q): + def __init__(self, address, key): logger.debug("Agent thread started") + listener = Listener(address, authkey=key) + self.queue = [] while True: - while not q.empty(): - self.process_data(q.get()) - time.sleep(AGENT_SEC_INTERVAL) - self.q = q - self.current_context = None - - def process_data(self, item): - """Will process the data added to the queue""" - action, data = item - logger.debug("Action %s, Data %s", action, data) - if action == "REPORT": - logger.debug("Report") - self.current_context = data - else: - logger.error("Action `%s` is not defined. (Aikido Agent)", action) + conn = listener.accept() + logger.debug("connection accepted from %s", listener.last_accepted) + while True: + data = conn.recv() + logger.error(data) # Temporary debugging + if data[0] == "SQL_INJECTION": + self.queue.append(data[1]) + elif data[0] == "CLOSE": + conn.close() + break + + def report_to_agent(self): + """ + Reports the found data to an Aikido server + """ + logger.debug("Reporting to aikido server") + # Implement # pylint: disable=invalid-name # This variable does change -agent = None +ipc = None -def get_agent(): +def get_ipc(): """Returns the globally stored agent""" - return agent + return ipc -def start_agent(): +def start_ipc(): """ Starts a thread to handle incoming/outgoing data """ # pylint: disable=global-statement # We need this to be global - global agent - - # This creates a queue for Inter-Process Communication - logger.debug("Creating IPC Queue") - q = queue.Queue() + global ipc - logger.debug("Starting a new agent thread") - agent_thread = Thread(target=AikidoThread, args=(q,)) - agent_thread.start() - agent = Agent(q) + if not "AIKIDO_SECRET_KEY" in os.environ: + raise EnvironmentError("AIKIDO_SECRET_KEY is not set.") + ipc = IPC(IPC_ADDRESS, os.environ["AIKIDO_SECRET_KEY"]) + ipc.start_aikido_listener() -class Agent: +class IPC: """Agent class""" - def __init__(self, q): - self.q = q - - def report(self, obj, action): - """ - Report something to the agent - """ - self.q.put((action, obj)) + def __init__(self, address, key): + self.address = address + self.key = str.encode(key) + self.agent_proc = None + + def start_aikido_listener(self): + """This will start the aikido thread which listens""" + self.agent_proc = Process( + target=AikidoProc, + args=( + self.address, + self.key, + ), + ) + logger.debug("Starting a new agent thread") + self.agent_proc.start() + + def send_data(self, action, obj): + """This creates a new client for comms to the thread""" + try: + conn = Client(self.address, authkey=self.key) + logger.debug("Created connection %s", conn) + conn.send((action, obj)) + conn.send(("CLOSE", {})) + conn.close() + logger.debug("Connection closed") + except Exception as e: + logger.info("Failed to send data to agent %s", e) From be15ca9f04faaf7d2ccb1f3e5edeff247b13ed76 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Fri, 19 Jul 2024 14:22:43 +0200 Subject: [PATCH 03/26] Create a reporting thread --- aikido_firewall/agent/__init__.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/aikido_firewall/agent/__init__.py b/aikido_firewall/agent/__init__.py index 7a907b5d4..3d9d3acad 100644 --- a/aikido_firewall/agent/__init__.py +++ b/aikido_firewall/agent/__init__.py @@ -6,6 +6,8 @@ import os from multiprocessing.connection import Listener, Client from multiprocessing import Process +from threading import Thread +from queue import Queue from aikido_firewall.helpers.logging import logger AGENT_SEC_INTERVAL = 5 @@ -20,19 +22,29 @@ class AikidoProc: def __init__(self, address, key): logger.debug("Agent thread started") listener = Listener(address, authkey=key) - self.queue = [] + self.queue = Queue() + # Start reporting thread : + Thread(target=self.reporting_thread).start() + while True: conn = listener.accept() logger.debug("connection accepted from %s", listener.last_accepted) while True: data = conn.recv() - logger.error(data) # Temporary debugging + logger.error(data) # Temporary debugging if data[0] == "SQL_INJECTION": - self.queue.append(data[1]) + self.queue.put(data[1]) elif data[0] == "CLOSE": conn.close() break + def reporting_thread(self): + """Reporting thread""" + logger.debug("Started reporting thread") + while True: + logger.debug(self.queue) + time.sleep(AGENT_SEC_INTERVAL) + def report_to_agent(self): """ Reports the found data to an Aikido server From b028c787f5e3fdcfba18e3f3bd3a701ac6b34881 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Mon, 22 Jul 2024 18:27:02 +0200 Subject: [PATCH 04/26] Actually read from queue and send to queue, also temp crit. logs --- aikido_firewall/agent/__init__.py | 8 ++++++-- aikido_firewall/sinks/pymysql.py | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/aikido_firewall/agent/__init__.py b/aikido_firewall/agent/__init__.py index 3d9d3acad..170d3aea2 100644 --- a/aikido_firewall/agent/__init__.py +++ b/aikido_firewall/agent/__init__.py @@ -42,15 +42,19 @@ def reporting_thread(self): """Reporting thread""" logger.debug("Started reporting thread") while True: - logger.debug(self.queue) + self.report_to_agent() time.sleep(AGENT_SEC_INTERVAL) def report_to_agent(self): """ Reports the found data to an Aikido server """ + items_to_report = [] + while not self.queue.empty(): + items_to_report.append(self.queue.get()) logger.debug("Reporting to aikido server") - # Implement + logger.critical("Items to report : %s", items_to_report) + # Currently not making API calls # pylint: disable=invalid-name # This variable does change diff --git a/aikido_firewall/sinks/pymysql.py b/aikido_firewall/sinks/pymysql.py index c474c82e5..fe115e314 100644 --- a/aikido_firewall/sinks/pymysql.py +++ b/aikido_firewall/sinks/pymysql.py @@ -12,6 +12,7 @@ check_context_for_sql_injection, ) from aikido_firewall.vulnerabilities.sql_injection.dialects import MySQL +from aikido_firewall.agent import get_ipc logger = logging.getLogger("aikido_firewall") From d4fe2e870f08963795b9f6aa637315234933522c Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Mon, 22 Jul 2024 18:27:02 +0200 Subject: [PATCH 05/26] Actually read from queue and send to queue, also temp crit. logs --- aikido_firewall/agent/__init__.py | 8 ++++++-- aikido_firewall/sinks/pymysql.py | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/aikido_firewall/agent/__init__.py b/aikido_firewall/agent/__init__.py index 3d9d3acad..170d3aea2 100644 --- a/aikido_firewall/agent/__init__.py +++ b/aikido_firewall/agent/__init__.py @@ -42,15 +42,19 @@ def reporting_thread(self): """Reporting thread""" logger.debug("Started reporting thread") while True: - logger.debug(self.queue) + self.report_to_agent() time.sleep(AGENT_SEC_INTERVAL) def report_to_agent(self): """ Reports the found data to an Aikido server """ + items_to_report = [] + while not self.queue.empty(): + items_to_report.append(self.queue.get()) logger.debug("Reporting to aikido server") - # Implement + logger.critical("Items to report : %s", items_to_report) + # Currently not making API calls # pylint: disable=invalid-name # This variable does change diff --git a/aikido_firewall/sinks/pymysql.py b/aikido_firewall/sinks/pymysql.py index c474c82e5..228c66b96 100644 --- a/aikido_firewall/sinks/pymysql.py +++ b/aikido_firewall/sinks/pymysql.py @@ -12,6 +12,7 @@ check_context_for_sql_injection, ) from aikido_firewall.vulnerabilities.sql_injection.dialects import MySQL +from aikido_firewall.agent import get_ipc logger = logging.getLogger("aikido_firewall") @@ -36,6 +37,7 @@ def aikido_new_query(_self, sql, unbuffered=False): logger.info("sql_injection results : %s", json.dumps(result)) if result: + get_ipc().send_data("SQL_INJECTION", result) raise Exception("SQL Injection [aikido_firewall]") return prev_query_function(_self, sql, unbuffered=False) From 51a7550b696b5e7b31db6737f6f312d38fe1a976 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 13:59:48 +0200 Subject: [PATCH 06/26] Add tests for agent init function --- aikido_firewall/agent/init_test.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 aikido_firewall/agent/init_test.py diff --git a/aikido_firewall/agent/init_test.py b/aikido_firewall/agent/init_test.py new file mode 100644 index 000000000..dce43a6c0 --- /dev/null +++ b/aikido_firewall/agent/init_test.py @@ -0,0 +1,12 @@ +import pytest +from aikido_firewall.agent import IPC + + +def test_ipc_init(): + address = ("localhost", 9898) + key = "secret_key" + ipc = IPC(address, key) + + assert ipc.address == address + assert ipc.key == b"secret_key" # Ensure key is encoded as bytes + assert ipc.agent_proc is None From 91e8f9cc2a2b96a567a00f8ea8a05b3a6577dad1 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 14:07:58 +0200 Subject: [PATCH 07/26] Add test cases for the start_ipc and get_ipc functions --- aikido_firewall/agent/init_test.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/aikido_firewall/agent/init_test.py b/aikido_firewall/agent/init_test.py index dce43a6c0..40249105c 100644 --- a/aikido_firewall/agent/init_test.py +++ b/aikido_firewall/agent/init_test.py @@ -1,5 +1,5 @@ import pytest -from aikido_firewall.agent import IPC +from aikido_firewall.agent import IPC, start_ipc, get_ipc, IPC_ADDRESS def test_ipc_init(): @@ -10,3 +10,16 @@ def test_ipc_init(): assert ipc.address == address assert ipc.key == b"secret_key" # Ensure key is encoded as bytes assert ipc.agent_proc is None + + +def test_start_ipc(mocker): + assert get_ipc() == None + mocker.patch("os.environ", {"AIKIDO_SECRET_KEY": "mock_key"}) + + start_ipc() + + assert get_ipc() != None + assert get_ipc().address == IPC_ADDRESS + assert get_ipc().key == b"mock_key" + + get_ipc().agent_proc.kill() From e307854da7fc7ffedf3cdee60ceaea950b019f97 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 14:13:13 +0200 Subject: [PATCH 08/26] Add tests to make sure AIKIDO_SECRET_KEY needs to be set --- aikido_firewall/agent/init_test.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/aikido_firewall/agent/init_test.py b/aikido_firewall/agent/init_test.py index 40249105c..a3d159e00 100644 --- a/aikido_firewall/agent/init_test.py +++ b/aikido_firewall/agent/init_test.py @@ -12,6 +12,15 @@ def test_ipc_init(): assert ipc.agent_proc is None +def test_start_ipc_missing_secret_key(mocker): + mocker.patch("os.environ", {}) + + with pytest.raises(EnvironmentError) as exc_info: + start_ipc() + + assert str(exc_info.value) == "AIKIDO_SECRET_KEY is not set." + + def test_start_ipc(mocker): assert get_ipc() == None mocker.patch("os.environ", {"AIKIDO_SECRET_KEY": "mock_key"}) From 4220ec6d2b08be457ed0f92cb599fcfff87b9e8b Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 14:16:53 +0200 Subject: [PATCH 09/26] Add some tests for protect() funtion --- aikido_firewall/init_test.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 aikido_firewall/init_test.py diff --git a/aikido_firewall/init_test.py b/aikido_firewall/init_test.py new file mode 100644 index 000000000..294e55d06 --- /dev/null +++ b/aikido_firewall/init_test.py @@ -0,0 +1,12 @@ +import pytest +from aikido_firewall import protect +from aikido_firewall.agent import get_ipc +def test_protect_with_django(monkeypatch, caplog): + monkeypatch.setitem(globals(), 'aikido_firewall.sources.django', 'dummy_django_module') + monkeypatch.setitem(globals(), 'aikido_firewall.sinks.pymysql', 'dummy_pymysql_module') + + protect(module="django") + + assert "Aikido python firewall started" in caplog.text + assert get_ipc() != None + get_ipc().agent_proc.kill() From 0abfd6603c00821a0d820dea72115f506ecd7e79 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 14:21:31 +0200 Subject: [PATCH 10/26] Test connection refused --- aikido_firewall/agent/init_test.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/aikido_firewall/agent/init_test.py b/aikido_firewall/agent/init_test.py index a3d159e00..bea471392 100644 --- a/aikido_firewall/agent/init_test.py +++ b/aikido_firewall/agent/init_test.py @@ -32,3 +32,16 @@ def test_start_ipc(mocker): assert get_ipc().key == b"mock_key" get_ipc().agent_proc.kill() + +def test_send_data_exception(monkeypatch, caplog): + def mock_client(address, authkey): + raise Exception("Connection Error") + + monkeypatch.setitem(globals(), 'Client', mock_client) + monkeypatch.setitem(globals(), 'logger', caplog) + + ipc = IPC(("localhost", 9898), "mock_key") + ipc.send_data("ACTION", "Test Object") + + assert "Failed to send data to agent" in caplog.text + # Add assertions for handling exceptions From 53849e595a00bb95a53bd510e45591948087440c Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 14:23:38 +0200 Subject: [PATCH 11/26] Linting --- aikido_firewall/agent/init_test.py | 5 +++-- aikido_firewall/init_test.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/aikido_firewall/agent/init_test.py b/aikido_firewall/agent/init_test.py index bea471392..1aa6a8ca1 100644 --- a/aikido_firewall/agent/init_test.py +++ b/aikido_firewall/agent/init_test.py @@ -33,12 +33,13 @@ def test_start_ipc(mocker): get_ipc().agent_proc.kill() + def test_send_data_exception(monkeypatch, caplog): def mock_client(address, authkey): raise Exception("Connection Error") - monkeypatch.setitem(globals(), 'Client', mock_client) - monkeypatch.setitem(globals(), 'logger', caplog) + monkeypatch.setitem(globals(), "Client", mock_client) + monkeypatch.setitem(globals(), "logger", caplog) ipc = IPC(("localhost", 9898), "mock_key") ipc.send_data("ACTION", "Test Object") diff --git a/aikido_firewall/init_test.py b/aikido_firewall/init_test.py index 294e55d06..a581667ef 100644 --- a/aikido_firewall/init_test.py +++ b/aikido_firewall/init_test.py @@ -1,9 +1,15 @@ import pytest from aikido_firewall import protect from aikido_firewall.agent import get_ipc + + def test_protect_with_django(monkeypatch, caplog): - monkeypatch.setitem(globals(), 'aikido_firewall.sources.django', 'dummy_django_module') - monkeypatch.setitem(globals(), 'aikido_firewall.sinks.pymysql', 'dummy_pymysql_module') + monkeypatch.setitem( + globals(), "aikido_firewall.sources.django", "dummy_django_module" + ) + monkeypatch.setitem( + globals(), "aikido_firewall.sinks.pymysql", "dummy_pymysql_module" + ) protect(module="django") From 5b4c42654aef5287df122159d2c55d4a2e1965d9 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 14:25:54 +0200 Subject: [PATCH 12/26] Use monkeypatch for environment vars instead of mocker --- aikido_firewall/agent/init_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aikido_firewall/agent/init_test.py b/aikido_firewall/agent/init_test.py index 1aa6a8ca1..0e2c354f8 100644 --- a/aikido_firewall/agent/init_test.py +++ b/aikido_firewall/agent/init_test.py @@ -21,9 +21,9 @@ def test_start_ipc_missing_secret_key(mocker): assert str(exc_info.value) == "AIKIDO_SECRET_KEY is not set." -def test_start_ipc(mocker): +def test_start_ipc(monkeypatch): assert get_ipc() == None - mocker.patch("os.environ", {"AIKIDO_SECRET_KEY": "mock_key"}) + monkeypatch.setenv("AIKIDO_SECRET_KEY", "mock_key") start_ipc() From 29890c5b26260c1c1ac1e425eb73c5172e6fc26e Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 14:30:57 +0200 Subject: [PATCH 13/26] Comment out bc not working in CI?CD --- aikido_firewall/agent/init_test.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/aikido_firewall/agent/init_test.py b/aikido_firewall/agent/init_test.py index 0e2c354f8..db9813f21 100644 --- a/aikido_firewall/agent/init_test.py +++ b/aikido_firewall/agent/init_test.py @@ -21,17 +21,18 @@ def test_start_ipc_missing_secret_key(mocker): assert str(exc_info.value) == "AIKIDO_SECRET_KEY is not set." -def test_start_ipc(monkeypatch): - assert get_ipc() == None - monkeypatch.setenv("AIKIDO_SECRET_KEY", "mock_key") - - start_ipc() - - assert get_ipc() != None - assert get_ipc().address == IPC_ADDRESS - assert get_ipc().key == b"mock_key" - - get_ipc().agent_proc.kill() +# Following function does not work +# def test_start_ipc(monkeypatch): +# assert get_ipc() == None +# monkeypatch.setenv("AIKIDO_SECRET_KEY", "mock_key") +# +# start_ipc() +# +# assert get_ipc() != None +# assert get_ipc().address == IPC_ADDRESS +# assert get_ipc().key == b"mock_key" +# +# get_ipc().agent_proc.kill() def test_send_data_exception(monkeypatch, caplog): From fe05991f96fe1296534dcf687edb6aef16a4d530 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 23 Jul 2024 14:40:08 +0200 Subject: [PATCH 14/26] Testing/Linting --- aikido_firewall/agent/init_test.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/aikido_firewall/agent/init_test.py b/aikido_firewall/agent/init_test.py index db9813f21..1cc0e65fc 100644 --- a/aikido_firewall/agent/init_test.py +++ b/aikido_firewall/agent/init_test.py @@ -18,21 +18,21 @@ def test_start_ipc_missing_secret_key(mocker): with pytest.raises(EnvironmentError) as exc_info: start_ipc() - assert str(exc_info.value) == "AIKIDO_SECRET_KEY is not set." + assert "AIKIDO_SECRET_KEY is not set." in str(exc_info.value) # Following function does not work -# def test_start_ipc(monkeypatch): -# assert get_ipc() == None -# monkeypatch.setenv("AIKIDO_SECRET_KEY", "mock_key") -# -# start_ipc() -# -# assert get_ipc() != None -# assert get_ipc().address == IPC_ADDRESS -# assert get_ipc().key == b"mock_key" -# -# get_ipc().agent_proc.kill() +def test_start_ipc(monkeypatch): + assert get_ipc() == None + monkeypatch.setenv("AIKIDO_SECRET_KEY", "mock_key") + + start_ipc() + + assert get_ipc() != None + assert get_ipc().address == IPC_ADDRESS + assert get_ipc().key == b"mock_key" + + get_ipc().agent_proc.kill() def test_send_data_exception(monkeypatch, caplog): From 364b1424b0b72168cb5e7a2f2623a1c1de582dbb Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Wed, 24 Jul 2024 13:36:51 +0200 Subject: [PATCH 15/26] get environment variables using the more standard way --- aikido_firewall/agent/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aikido_firewall/agent/__init__.py b/aikido_firewall/agent/__init__.py index 170d3aea2..b617845c4 100644 --- a/aikido_firewall/agent/__init__.py +++ b/aikido_firewall/agent/__init__.py @@ -72,10 +72,10 @@ def start_ipc(): """ # pylint: disable=global-statement # We need this to be global global ipc - - if not "AIKIDO_SECRET_KEY" in os.environ: + aikido_secret_key_env = os.getenv("AIKIDO_SECRET_KEY") + if not aikido_secret_key_env: raise EnvironmentError("AIKIDO_SECRET_KEY is not set.") - ipc = IPC(IPC_ADDRESS, os.environ["AIKIDO_SECRET_KEY"]) + ipc = IPC(IPC_ADDRESS, aikido_secret_key_env) ipc.start_aikido_listener() From 01571d612f41108a487d106daf5542e0641a5caf Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Wed, 24 Jul 2024 13:37:30 +0200 Subject: [PATCH 16/26] Update testing so that init test does not fail bc of env vars --- aikido_firewall/init_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aikido_firewall/init_test.py b/aikido_firewall/init_test.py index a581667ef..4c9d3572c 100644 --- a/aikido_firewall/init_test.py +++ b/aikido_firewall/init_test.py @@ -10,6 +10,7 @@ def test_protect_with_django(monkeypatch, caplog): monkeypatch.setitem( globals(), "aikido_firewall.sinks.pymysql", "dummy_pymysql_module" ) + monkeypatch.setenv("AIKIDO_SECRET_KEY", "mock_key") protect(module="django") From 6a91bca5a6f172aa1dac6c4359fa632805b19663 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Wed, 24 Jul 2024 14:38:37 +0200 Subject: [PATCH 17/26] Update how con is imported for testing + basic test case (no asserts) --- aikido_firewall/agent/__init__.py | 6 +++--- aikido_firewall/agent/init_test.py | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/aikido_firewall/agent/__init__.py b/aikido_firewall/agent/__init__.py index b617845c4..5d23d8050 100644 --- a/aikido_firewall/agent/__init__.py +++ b/aikido_firewall/agent/__init__.py @@ -4,7 +4,7 @@ import time import os -from multiprocessing.connection import Listener, Client +import multiprocessing.connection as con from multiprocessing import Process from threading import Thread from queue import Queue @@ -21,7 +21,7 @@ class AikidoProc: def __init__(self, address, key): logger.debug("Agent thread started") - listener = Listener(address, authkey=key) + listener = con.Listener(address, authkey=key) self.queue = Queue() # Start reporting thread : Thread(target=self.reporting_thread).start() @@ -102,7 +102,7 @@ def start_aikido_listener(self): def send_data(self, action, obj): """This creates a new client for comms to the thread""" try: - conn = Client(self.address, authkey=self.key) + conn = con.Client(self.address, authkey=self.key) logger.debug("Created connection %s", conn) conn.send((action, obj)) conn.send(("CLOSE", {})) diff --git a/aikido_firewall/agent/init_test.py b/aikido_firewall/agent/init_test.py index 1cc0e65fc..53f782534 100644 --- a/aikido_firewall/agent/init_test.py +++ b/aikido_firewall/agent/init_test.py @@ -47,3 +47,12 @@ def mock_client(address, authkey): assert "Failed to send data to agent" in caplog.text # Add assertions for handling exceptions + + +def test_send_data_successful(monkeypatch, caplog, mocker): + ipc = IPC(("localhost"), "mock_key") + mock_client = mocker.MagicMock() + monkeypatch.setattr("multiprocessing.connection.Client", mock_client) + + # Call the send_data function + ipc.send_data("ACTION", {"key": "value"}) From 81fae9c2915fae0930466f3df9f846247e81fd5b Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Thu, 25 Jul 2024 14:55:40 +0200 Subject: [PATCH 18/26] Change interval to 10 minutes --- aikido_firewall/agent/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aikido_firewall/agent/__init__.py b/aikido_firewall/agent/__init__.py index 5d23d8050..063d34af9 100644 --- a/aikido_firewall/agent/__init__.py +++ b/aikido_firewall/agent/__init__.py @@ -10,7 +10,7 @@ from queue import Queue from aikido_firewall.helpers.logging import logger -AGENT_SEC_INTERVAL = 5 +AGENT_SEC_INTERVAL = 600 # 10 minutes IPC_ADDRESS = ("localhost", 9898) # Specify the IP address and port From 4db88c659137444b361bdeb7d0129a1f6e91fdea Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Thu, 25 Jul 2024 15:01:20 +0200 Subject: [PATCH 19/26] Remove any mention of Agent and use Background process instead --- aikido_firewall/agent/__init__.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/aikido_firewall/agent/__init__.py b/aikido_firewall/agent/__init__.py index 063d34af9..70282c7ec 100644 --- a/aikido_firewall/agent/__init__.py +++ b/aikido_firewall/agent/__init__.py @@ -14,13 +14,13 @@ IPC_ADDRESS = ("localhost", 9898) # Specify the IP address and port -class AikidoProc: +class AikidoBackgroundProcess: """ Our agent thread """ def __init__(self, address, key): - logger.debug("Agent thread started") + logger.debug("Background process started") listener = con.Listener(address, authkey=key) self.queue = Queue() # Start reporting thread : @@ -80,17 +80,22 @@ def start_ipc(): class IPC: - """Agent class""" + """ + Facilitates Inter-Process communication + """ def __init__(self, address, key): self.address = address self.key = str.encode(key) - self.agent_proc = None + self.background_process = None def start_aikido_listener(self): - """This will start the aikido thread which listens""" - self.agent_proc = Process( - target=AikidoProc, + """ + This will start the aikido background process which listens + and makes calls to the API + """ + self.background_process = Process( + target=AikidoBackgroundProcess, args=( self.address, self.key, From 7853f5fada141dd86f738996c19c113a44838b71 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Thu, 25 Jul 2024 15:11:49 +0200 Subject: [PATCH 20/26] Renames --- aikido_firewall/__init__.py | 10 ++-- .../{agent => background_process}/__init__.py | 36 +++++++----- .../init_test.py | 29 ++++++---- aikido_firewall/init_test.py | 6 +- aikido_firewall/sinks/pymysql.py | 4 +- sample-apps/flask-mongo/app.py | 55 +++++++++++++++++++ sample-apps/flask-mysql-uwsgi/app.py | 2 +- sample-apps/flask-mysql/app.py | 2 +- sample-apps/flask-postgres/app.py | 46 ++++++++++++++++ 9 files changed, 153 insertions(+), 37 deletions(-) rename aikido_firewall/{agent => background_process}/__init__.py (74%) rename aikido_firewall/{agent => background_process}/init_test.py (66%) create mode 100644 sample-apps/flask-mongo/app.py create mode 100644 sample-apps/flask-postgres/app.py diff --git a/aikido_firewall/__init__.py b/aikido_firewall/__init__.py index 71d7c383e..18eafc779 100644 --- a/aikido_firewall/__init__.py +++ b/aikido_firewall/__init__.py @@ -8,15 +8,17 @@ # Import logger from aikido_firewall.helpers.logging import logger -# Import agent -from aikido_firewall.agent import start_ipc +# Import background process +from aikido_firewall.background_process import start_background_process # Load environment variables load_dotenv() def protect(module="any"): - """Start Aikido agent""" + """ + Protect user's application + """ # Import sources import aikido_firewall.sources.django @@ -27,4 +29,4 @@ def protect(module="any"): import aikido_firewall.sinks.pymysql logger.info("Aikido python firewall started") - start_ipc() + start_background_process() diff --git a/aikido_firewall/agent/__init__.py b/aikido_firewall/background_process/__init__.py similarity index 74% rename from aikido_firewall/agent/__init__.py rename to aikido_firewall/background_process/__init__.py index 70282c7ec..63fb14c08 100644 --- a/aikido_firewall/agent/__init__.py +++ b/aikido_firewall/background_process/__init__.py @@ -1,5 +1,6 @@ """ -Aikido agent, this will create a new thread and listen for stuff sent by our sources and sinks +Aikido background process, this will create a new process +and listen for data sent by our sources and sinks """ import time @@ -10,13 +11,15 @@ from queue import Queue from aikido_firewall.helpers.logging import logger -AGENT_SEC_INTERVAL = 600 # 10 minutes +REPORT_SEC_INTERVAL = 600 # 10 minutes IPC_ADDRESS = ("localhost", 9898) # Specify the IP address and port class AikidoBackgroundProcess: """ - Our agent thread + Aikido's background process consists of 2 threads : + - (main) Listening thread which listens on an IPC socket for incoming data + - (spawned) reporting thread which will collect the IPC data and send it to a Reporter """ def __init__(self, address, key): @@ -42,10 +45,10 @@ def reporting_thread(self): """Reporting thread""" logger.debug("Started reporting thread") while True: - self.report_to_agent() - time.sleep(AGENT_SEC_INTERVAL) + self.send_to_reporter() + time.sleep(REPORT_SEC_INTERVAL) - def report_to_agent(self): + def send_to_reporter(self): """ Reports the found data to an Aikido server """ @@ -61,14 +64,17 @@ def report_to_agent(self): ipc = None -def get_ipc(): - """Returns the globally stored agent""" +def get_comms(): + """ + Returns the globally stored IPC object, which you need + to communicate to our background process. + """ return ipc -def start_ipc(): +def start_background_process(): """ - Starts a thread to handle incoming/outgoing data + Starts a process to handle incoming/outgoing data """ # pylint: disable=global-statement # We need this to be global global ipc @@ -101,11 +107,13 @@ def start_aikido_listener(self): self.key, ), ) - logger.debug("Starting a new agent thread") - self.agent_proc.start() + logger.debug("Starting the background process") + self.background_process.start() def send_data(self, action, obj): - """This creates a new client for comms to the thread""" + """ + This creates a new client for comms to the background process + """ try: conn = con.Client(self.address, authkey=self.key) logger.debug("Created connection %s", conn) @@ -114,4 +122,4 @@ def send_data(self, action, obj): conn.close() logger.debug("Connection closed") except Exception as e: - logger.info("Failed to send data to agent %s", e) + logger.info("Failed to send data to bg process : %s", e) diff --git a/aikido_firewall/agent/init_test.py b/aikido_firewall/background_process/init_test.py similarity index 66% rename from aikido_firewall/agent/init_test.py rename to aikido_firewall/background_process/init_test.py index 53f782534..d1d3a733d 100644 --- a/aikido_firewall/agent/init_test.py +++ b/aikido_firewall/background_process/init_test.py @@ -1,5 +1,10 @@ import pytest -from aikido_firewall.agent import IPC, start_ipc, get_ipc, IPC_ADDRESS +from aikido_firewall.background_process import ( + IPC, + start_background_process, + get_comms, + IPC_ADDRESS, +) def test_ipc_init(): @@ -9,30 +14,30 @@ def test_ipc_init(): assert ipc.address == address assert ipc.key == b"secret_key" # Ensure key is encoded as bytes - assert ipc.agent_proc is None + assert ipc.background_process is None -def test_start_ipc_missing_secret_key(mocker): +def test_start_background_process_missing_secret_key(mocker): mocker.patch("os.environ", {}) with pytest.raises(EnvironmentError) as exc_info: - start_ipc() + start_background_process() assert "AIKIDO_SECRET_KEY is not set." in str(exc_info.value) # Following function does not work -def test_start_ipc(monkeypatch): - assert get_ipc() == None +def test_start_background_process(monkeypatch): + assert get_comms() == None monkeypatch.setenv("AIKIDO_SECRET_KEY", "mock_key") - start_ipc() + start_background_process() - assert get_ipc() != None - assert get_ipc().address == IPC_ADDRESS - assert get_ipc().key == b"mock_key" + assert get_comms() != None + assert get_comms().address == IPC_ADDRESS + assert get_comms().key == b"mock_key" - get_ipc().agent_proc.kill() + get_comms().background_process.kill() def test_send_data_exception(monkeypatch, caplog): @@ -45,7 +50,7 @@ def mock_client(address, authkey): ipc = IPC(("localhost", 9898), "mock_key") ipc.send_data("ACTION", "Test Object") - assert "Failed to send data to agent" in caplog.text + assert "Failed to send data to bg" in caplog.text # Add assertions for handling exceptions diff --git a/aikido_firewall/init_test.py b/aikido_firewall/init_test.py index 4c9d3572c..8792736e6 100644 --- a/aikido_firewall/init_test.py +++ b/aikido_firewall/init_test.py @@ -1,6 +1,6 @@ import pytest from aikido_firewall import protect -from aikido_firewall.agent import get_ipc +from aikido_firewall.background_process import get_comms def test_protect_with_django(monkeypatch, caplog): @@ -15,5 +15,5 @@ def test_protect_with_django(monkeypatch, caplog): protect(module="django") assert "Aikido python firewall started" in caplog.text - assert get_ipc() != None - get_ipc().agent_proc.kill() + assert get_comms() != None + get_comms().background_process.kill() diff --git a/aikido_firewall/sinks/pymysql.py b/aikido_firewall/sinks/pymysql.py index 228c66b96..7f81a1205 100644 --- a/aikido_firewall/sinks/pymysql.py +++ b/aikido_firewall/sinks/pymysql.py @@ -12,7 +12,7 @@ check_context_for_sql_injection, ) from aikido_firewall.vulnerabilities.sql_injection.dialects import MySQL -from aikido_firewall.agent import get_ipc +from aikido_firewall.background_process import get_comms logger = logging.getLogger("aikido_firewall") @@ -37,7 +37,7 @@ def aikido_new_query(_self, sql, unbuffered=False): logger.info("sql_injection results : %s", json.dumps(result)) if result: - get_ipc().send_data("SQL_INJECTION", result) + get_comms().send_data("SQL_INJECTION", result) raise Exception("SQL Injection [aikido_firewall]") return prev_query_function(_self, sql, unbuffered=False) diff --git a/sample-apps/flask-mongo/app.py b/sample-apps/flask-mongo/app.py new file mode 100644 index 000000000..121f7f117 --- /dev/null +++ b/sample-apps/flask-mongo/app.py @@ -0,0 +1,55 @@ +import aikido_firewall # Aikido package import +aikido_firewall.protect() + +import json +from flask import Flask, render_template, request +from flask_pymongo import PyMongo +from bson import ObjectId + +app = Flask(__name__) +if __name__ == '__main__': + app.run(threaded=True) # Run threaded so we can test how our bg process works +app.config["MONGO_URI"] = "mongodb://admin:password@db:27017/my_database?authSource=admin" +mongo = PyMongo(app) + +@app.route("/") +def homepage(): + dogs = mongo.db.dogs.find() + return render_template('index.html', title='Homepage', dogs=dogs) + + +@app.route('/dogpage/') +def get_dogpage(dog_id): + dog = mongo.db.dogs.find_one({"_id": ObjectId(dog_id)}) + return render_template('dogpage.html', title=f'Dog', dog=dog) + +@app.route("/create", methods=['GET']) +def show_create_dog_form(): + return render_template('create_dog.html') + +@app.route("/create", methods=['POST']) +def create_dog(): + new_dog = { + 'dog_name': request.form['dog_name'], + 'pswd': request.form['pswd'] + } + res = mongo.db.dogs.insert_one(new_dog) + return f'Dog with id {res.inserted_id} created successfully' + +@app.route("/auth", methods=['GET']) +def show_auth_form(): + return render_template('auth.html') + +@app.route("/auth", methods=['POST']) +def post_auth(): + data = request.get_json() + dog_info = { + 'dog_name': data.get('dog_name'), + 'pswd': data.get('pswd') + } + dog = mongo.db.dogs.find_one(dog_info) + if dog: + dog_name = dog["dog_name"] + return f'Dog with name {dog_name} authenticated successfully' + else: + return f'Auth failed' diff --git a/sample-apps/flask-mysql-uwsgi/app.py b/sample-apps/flask-mysql-uwsgi/app.py index b6d6ea069..b10c8541f 100644 --- a/sample-apps/flask-mysql-uwsgi/app.py +++ b/sample-apps/flask-mysql-uwsgi/app.py @@ -6,7 +6,7 @@ app = Flask(__name__) if __name__ == '__main__': - app.run() # Run threaded so we can test our agent's capabilities + app.run() mysql = MySQL() app.config['MYSQL_DATABASE_HOST'] = 'db' diff --git a/sample-apps/flask-mysql/app.py b/sample-apps/flask-mysql/app.py index 2322b0cef..a62f0416b 100644 --- a/sample-apps/flask-mysql/app.py +++ b/sample-apps/flask-mysql/app.py @@ -6,7 +6,7 @@ app = Flask(__name__) if __name__ == '__main__': - app.run(threaded=True) # Run threaded so we can test our agent's capabilities + app.run(threaded=True) # Run threaded so we can test how our bg process works mysql = MySQL() app.config['MYSQL_DATABASE_HOST'] = 'db' diff --git a/sample-apps/flask-postgres/app.py b/sample-apps/flask-postgres/app.py new file mode 100644 index 000000000..1fabbc6a8 --- /dev/null +++ b/sample-apps/flask-postgres/app.py @@ -0,0 +1,46 @@ +import aikido_firewall # Aikido package import +aikido_firewall.protect() + +from flask import Flask, render_template, request +import psycopg2 + +app = Flask(__name__) +if __name__ == '__main__': + app.run(threaded=True) # Run threaded so we can test how our bg process works + +def get_db_connection(): + return psycopg2.connect( + host="db", + database="db", + user="user", + password="password") + +@app.route("/") +def homepage(): + cursor = get_db_connection().cursor() + cursor.execute("SELECT * FROM dogs") + dogs = cursor.fetchall() + return render_template('index.html', title='Homepage', dogs=dogs) + + +@app.route('/dogpage/') +def get_dogpage(dog_id): + cursor = get_db_connection().cursor() + cursor.execute("SELECT * FROM dogs WHERE id = " + str(dog_id)) + dog = cursor.fetchmany(1)[0] + return render_template('dogpage.html', title=f'Dog', dog=dog, isAdmin=("Yes" if dog[2] else "No")) + +@app.route("/create", methods=['GET']) +def show_create_dog_form(): + return render_template('create_dog.html') + +@app.route("/create", methods=['POST']) +def create_dog(): + dog_name = request.form['dog_name'] + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute(f"INSERT INTO dogs (dog_name, isAdmin) VALUES ('%s', FALSE)" % (dog_name)) + conn.commit() + cursor.close() + conn.close() + return f'Dog {dog_name} created successfully' From 772b34b3648b636da4cede9dfc146b4c284cfa58 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Thu, 25 Jul 2024 15:15:49 +0200 Subject: [PATCH 21/26] Remove AIKIDO_SECRET_KEY and generate on the spot --- README.md | 1 - aikido_firewall/background_process/__init__.py | 11 ++++++----- aikido_firewall/background_process/init_test.py | 12 ------------ aikido_firewall/init_test.py | 1 - sample-apps/django-mysql-gunicorn/.env.example | 1 - sample-apps/django-mysql/.env.example | 1 - sample-apps/flask-mysql-uwsgi/.env.example | 1 - sample-apps/flask-mysql/.env.example | 1 - 8 files changed, 6 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 1134ed9b5..371ed00ab 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # firewall-python ## Environment variables -- `AIKIDO_SECRET_KEY` : Secret to encrypt IPC comms - `AIKIDO_DEBUG` : Boolean value to enable debug logs diff --git a/aikido_firewall/background_process/__init__.py b/aikido_firewall/background_process/__init__.py index 63fb14c08..3871176c5 100644 --- a/aikido_firewall/background_process/__init__.py +++ b/aikido_firewall/background_process/__init__.py @@ -5,6 +5,7 @@ import time import os +import secrets import multiprocessing.connection as con from multiprocessing import Process from threading import Thread @@ -78,10 +79,10 @@ def start_background_process(): """ # pylint: disable=global-statement # We need this to be global global ipc - aikido_secret_key_env = os.getenv("AIKIDO_SECRET_KEY") - if not aikido_secret_key_env: - raise EnvironmentError("AIKIDO_SECRET_KEY is not set.") - ipc = IPC(IPC_ADDRESS, aikido_secret_key_env) + # Generate a secret key : + generated_key_bytes = secrets.token_bytes(32) + + ipc = IPC(IPC_ADDRESS, generated_key_bytes) ipc.start_aikido_listener() @@ -92,7 +93,7 @@ class IPC: def __init__(self, address, key): self.address = address - self.key = str.encode(key) + self.key = key self.background_process = None def start_aikido_listener(self): diff --git a/aikido_firewall/background_process/init_test.py b/aikido_firewall/background_process/init_test.py index d1d3a733d..348942c5b 100644 --- a/aikido_firewall/background_process/init_test.py +++ b/aikido_firewall/background_process/init_test.py @@ -17,25 +17,13 @@ def test_ipc_init(): assert ipc.background_process is None -def test_start_background_process_missing_secret_key(mocker): - mocker.patch("os.environ", {}) - - with pytest.raises(EnvironmentError) as exc_info: - start_background_process() - - assert "AIKIDO_SECRET_KEY is not set." in str(exc_info.value) - - # Following function does not work def test_start_background_process(monkeypatch): assert get_comms() == None - monkeypatch.setenv("AIKIDO_SECRET_KEY", "mock_key") - start_background_process() assert get_comms() != None assert get_comms().address == IPC_ADDRESS - assert get_comms().key == b"mock_key" get_comms().background_process.kill() diff --git a/aikido_firewall/init_test.py b/aikido_firewall/init_test.py index 8792736e6..752532f5a 100644 --- a/aikido_firewall/init_test.py +++ b/aikido_firewall/init_test.py @@ -10,7 +10,6 @@ def test_protect_with_django(monkeypatch, caplog): monkeypatch.setitem( globals(), "aikido_firewall.sinks.pymysql", "dummy_pymysql_module" ) - monkeypatch.setenv("AIKIDO_SECRET_KEY", "mock_key") protect(module="django") diff --git a/sample-apps/django-mysql-gunicorn/.env.example b/sample-apps/django-mysql-gunicorn/.env.example index f842f1455..1a56af06a 100644 --- a/sample-apps/django-mysql-gunicorn/.env.example +++ b/sample-apps/django-mysql-gunicorn/.env.example @@ -6,4 +6,3 @@ MYSQL_ROOT_PASSWORD="password" # Aikido keys AIKIDO_DEBUG=true -AIKIDO_SECRET_KEY="your_secret_key" diff --git a/sample-apps/django-mysql/.env.example b/sample-apps/django-mysql/.env.example index f842f1455..1a56af06a 100644 --- a/sample-apps/django-mysql/.env.example +++ b/sample-apps/django-mysql/.env.example @@ -6,4 +6,3 @@ MYSQL_ROOT_PASSWORD="password" # Aikido keys AIKIDO_DEBUG=true -AIKIDO_SECRET_KEY="your_secret_key" diff --git a/sample-apps/flask-mysql-uwsgi/.env.example b/sample-apps/flask-mysql-uwsgi/.env.example index 3fd0f3424..35d3d4bab 100644 --- a/sample-apps/flask-mysql-uwsgi/.env.example +++ b/sample-apps/flask-mysql-uwsgi/.env.example @@ -1,2 +1 @@ AIKIDO_DEBUG=true -AIKIDO_SECRET_KEY="your_secret_key" diff --git a/sample-apps/flask-mysql/.env.example b/sample-apps/flask-mysql/.env.example index 3fd0f3424..35d3d4bab 100644 --- a/sample-apps/flask-mysql/.env.example +++ b/sample-apps/flask-mysql/.env.example @@ -1,2 +1 @@ AIKIDO_DEBUG=true -AIKIDO_SECRET_KEY="your_secret_key" From f7dfb3633e50a711182c991d543a4ad8740cb68b Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Thu, 25 Jul 2024 15:15:49 +0200 Subject: [PATCH 22/26] Remove AIKIDO_SECRET_KEY and generate on the spot --- README.md | 1 - aikido_firewall/background_process/__init__.py | 11 ++++++----- aikido_firewall/background_process/init_test.py | 13 ------------- aikido_firewall/init_test.py | 1 - sample-apps/django-mysql-gunicorn/.env.example | 1 - sample-apps/django-mysql/.env.example | 1 - sample-apps/flask-mysql-uwsgi/.env.example | 1 - sample-apps/flask-mysql/.env.example | 1 - 8 files changed, 6 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 1134ed9b5..371ed00ab 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # firewall-python ## Environment variables -- `AIKIDO_SECRET_KEY` : Secret to encrypt IPC comms - `AIKIDO_DEBUG` : Boolean value to enable debug logs diff --git a/aikido_firewall/background_process/__init__.py b/aikido_firewall/background_process/__init__.py index 63fb14c08..3871176c5 100644 --- a/aikido_firewall/background_process/__init__.py +++ b/aikido_firewall/background_process/__init__.py @@ -5,6 +5,7 @@ import time import os +import secrets import multiprocessing.connection as con from multiprocessing import Process from threading import Thread @@ -78,10 +79,10 @@ def start_background_process(): """ # pylint: disable=global-statement # We need this to be global global ipc - aikido_secret_key_env = os.getenv("AIKIDO_SECRET_KEY") - if not aikido_secret_key_env: - raise EnvironmentError("AIKIDO_SECRET_KEY is not set.") - ipc = IPC(IPC_ADDRESS, aikido_secret_key_env) + # Generate a secret key : + generated_key_bytes = secrets.token_bytes(32) + + ipc = IPC(IPC_ADDRESS, generated_key_bytes) ipc.start_aikido_listener() @@ -92,7 +93,7 @@ class IPC: def __init__(self, address, key): self.address = address - self.key = str.encode(key) + self.key = key self.background_process = None def start_aikido_listener(self): diff --git a/aikido_firewall/background_process/init_test.py b/aikido_firewall/background_process/init_test.py index d1d3a733d..7ede6dd5d 100644 --- a/aikido_firewall/background_process/init_test.py +++ b/aikido_firewall/background_process/init_test.py @@ -13,29 +13,16 @@ def test_ipc_init(): ipc = IPC(address, key) assert ipc.address == address - assert ipc.key == b"secret_key" # Ensure key is encoded as bytes assert ipc.background_process is None -def test_start_background_process_missing_secret_key(mocker): - mocker.patch("os.environ", {}) - - with pytest.raises(EnvironmentError) as exc_info: - start_background_process() - - assert "AIKIDO_SECRET_KEY is not set." in str(exc_info.value) - - # Following function does not work def test_start_background_process(monkeypatch): assert get_comms() == None - monkeypatch.setenv("AIKIDO_SECRET_KEY", "mock_key") - start_background_process() assert get_comms() != None assert get_comms().address == IPC_ADDRESS - assert get_comms().key == b"mock_key" get_comms().background_process.kill() diff --git a/aikido_firewall/init_test.py b/aikido_firewall/init_test.py index 8792736e6..752532f5a 100644 --- a/aikido_firewall/init_test.py +++ b/aikido_firewall/init_test.py @@ -10,7 +10,6 @@ def test_protect_with_django(monkeypatch, caplog): monkeypatch.setitem( globals(), "aikido_firewall.sinks.pymysql", "dummy_pymysql_module" ) - monkeypatch.setenv("AIKIDO_SECRET_KEY", "mock_key") protect(module="django") diff --git a/sample-apps/django-mysql-gunicorn/.env.example b/sample-apps/django-mysql-gunicorn/.env.example index f842f1455..1a56af06a 100644 --- a/sample-apps/django-mysql-gunicorn/.env.example +++ b/sample-apps/django-mysql-gunicorn/.env.example @@ -6,4 +6,3 @@ MYSQL_ROOT_PASSWORD="password" # Aikido keys AIKIDO_DEBUG=true -AIKIDO_SECRET_KEY="your_secret_key" diff --git a/sample-apps/django-mysql/.env.example b/sample-apps/django-mysql/.env.example index f842f1455..1a56af06a 100644 --- a/sample-apps/django-mysql/.env.example +++ b/sample-apps/django-mysql/.env.example @@ -6,4 +6,3 @@ MYSQL_ROOT_PASSWORD="password" # Aikido keys AIKIDO_DEBUG=true -AIKIDO_SECRET_KEY="your_secret_key" diff --git a/sample-apps/flask-mysql-uwsgi/.env.example b/sample-apps/flask-mysql-uwsgi/.env.example index 3fd0f3424..35d3d4bab 100644 --- a/sample-apps/flask-mysql-uwsgi/.env.example +++ b/sample-apps/flask-mysql-uwsgi/.env.example @@ -1,2 +1 @@ AIKIDO_DEBUG=true -AIKIDO_SECRET_KEY="your_secret_key" diff --git a/sample-apps/flask-mysql/.env.example b/sample-apps/flask-mysql/.env.example index 3fd0f3424..35d3d4bab 100644 --- a/sample-apps/flask-mysql/.env.example +++ b/sample-apps/flask-mysql/.env.example @@ -1,2 +1 @@ AIKIDO_DEBUG=true -AIKIDO_SECRET_KEY="your_secret_key" From d2c98e77afd39c553f380bcafcb7c57146463931 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Thu, 25 Jul 2024 15:18:52 +0200 Subject: [PATCH 23/26] Rename action to "ATTACK" for comms --- aikido_firewall/background_process/__init__.py | 2 +- aikido_firewall/sinks/pymysql.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aikido_firewall/background_process/__init__.py b/aikido_firewall/background_process/__init__.py index 3871176c5..1c0ee3647 100644 --- a/aikido_firewall/background_process/__init__.py +++ b/aikido_firewall/background_process/__init__.py @@ -36,7 +36,7 @@ def __init__(self, address, key): while True: data = conn.recv() logger.error(data) # Temporary debugging - if data[0] == "SQL_INJECTION": + if data[0] == "ATTACK": self.queue.put(data[1]) elif data[0] == "CLOSE": conn.close() diff --git a/aikido_firewall/sinks/pymysql.py b/aikido_firewall/sinks/pymysql.py index 7f81a1205..a48e86420 100644 --- a/aikido_firewall/sinks/pymysql.py +++ b/aikido_firewall/sinks/pymysql.py @@ -37,7 +37,7 @@ def aikido_new_query(_self, sql, unbuffered=False): logger.info("sql_injection results : %s", json.dumps(result)) if result: - get_comms().send_data("SQL_INJECTION", result) + get_comms().send_data("ATTACK", result) raise Exception("SQL Injection [aikido_firewall]") return prev_query_function(_self, sql, unbuffered=False) From bb4ce9f66fb6192dbe18a42d84d668e1cb0c4bf2 Mon Sep 17 00:00:00 2001 From: willem-delbare <20814660+willem-delbare@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:45:39 +0200 Subject: [PATCH 24/26] Update aikido_firewall/background_process/__init__.py --- aikido_firewall/background_process/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aikido_firewall/background_process/__init__.py b/aikido_firewall/background_process/__init__.py index 1c0ee3647..be992dcea 100644 --- a/aikido_firewall/background_process/__init__.py +++ b/aikido_firewall/background_process/__init__.py @@ -38,7 +38,7 @@ def __init__(self, address, key): logger.error(data) # Temporary debugging if data[0] == "ATTACK": self.queue.put(data[1]) - elif data[0] == "CLOSE": + elif data[0] == "CLOSE": // this is a kind of EOL for python IPC conn.close() break From 8b926194c24cf5b00a2fb6b9bcd8144857295283 Mon Sep 17 00:00:00 2001 From: willem-delbare <20814660+willem-delbare@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:45:52 +0200 Subject: [PATCH 25/26] Update aikido_firewall/background_process/__init__.py --- aikido_firewall/background_process/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aikido_firewall/background_process/__init__.py b/aikido_firewall/background_process/__init__.py index be992dcea..efa21d25a 100644 --- a/aikido_firewall/background_process/__init__.py +++ b/aikido_firewall/background_process/__init__.py @@ -38,7 +38,7 @@ def __init__(self, address, key): logger.error(data) # Temporary debugging if data[0] == "ATTACK": self.queue.put(data[1]) - elif data[0] == "CLOSE": // this is a kind of EOL for python IPC + elif data[0] == "CLOSE": # this is a kind of EOL for python IPC conn.close() break From 24061c7844b47d6709f437958bad3ff6ed09e433 Mon Sep 17 00:00:00 2001 From: Wout Feys Date: Tue, 30 Jul 2024 09:48:53 +0200 Subject: [PATCH 26/26] Linting --- aikido_firewall/background_process/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aikido_firewall/background_process/__init__.py b/aikido_firewall/background_process/__init__.py index efa21d25a..586f84fe9 100644 --- a/aikido_firewall/background_process/__init__.py +++ b/aikido_firewall/background_process/__init__.py @@ -38,7 +38,7 @@ def __init__(self, address, key): logger.error(data) # Temporary debugging if data[0] == "ATTACK": self.queue.put(data[1]) - elif data[0] == "CLOSE": # this is a kind of EOL for python IPC + elif data[0] == "CLOSE": # this is a kind of EOL for python IPC conn.close() break