From eae1a980d1220bdd2fcf5987188b1c4d6b97896a Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Wed, 4 Jun 2025 17:55:45 +0200 Subject: [PATCH 1/4] Update python-app.yml Update to cover Python from 3.9 to 3.12 --- .github/workflows/python-app.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index ad47922..0f8eab6 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -1,28 +1,25 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python +# This workflow will install Python dependencies, run tests and lint with multiple versions of Python # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - name: Python application - on: push: branches: [ "master", "develop" ] pull_request: branches: [ "master", "develop" ] - permissions: contents: read - jobs: build: - runs-on: ubuntu-latest - + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 - - name: Set up Python 3.11 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: - python-version: "3.11" + python-version: ${{ matrix.python-version }} - name: Setup timezone uses: zcong1993/setup-timezone@master with: From e21a0af804a8e20014f9128a05833f92c7cee3ca Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Wed, 4 Jun 2025 18:34:26 +0200 Subject: [PATCH 2/4] avoid use of match case to keep compatibility with python 3.9 --- README.md | 2 +- flowapp/services/rule_service.py | 49 +++++++++++------------- flowapp/services/whitelist_service.py | 55 +++++++++++++-------------- 3 files changed, 49 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index db67631..04fb166 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ See how is ExaFS integrated into the network in the picture below. ![ExaFS schema](./docs/app_schema_en.png) -The central part of the ExaFS is a web application, written in Python3.6 with Flask framework. It provides a user interface for ExaBGP rule CRUD operations. The application also provides the REST API with CRUD operations for the configuration rules. The web app uses Shibboleth authorization; the REST API is using token-based authorization. +The central part of the ExaFS is a web application, written in Python with Flask framework. It provides a user interface for ExaBGP rule CRUD operations. The application also provides the REST API with CRUD operations for the configuration rules. The web app uses Shibboleth authorization; the REST API is using token-based authorization. The app creates the ExaBGP commands and forwards them to ExaBGP process. All rules are carefully validated, and only valid rules are stored in the database and sent to the ExaBGP connector. diff --git a/flowapp/services/rule_service.py b/flowapp/services/rule_service.py index 997bd19..fca7719 100644 --- a/flowapp/services/rule_service.py +++ b/flowapp/services/rule_service.py @@ -352,35 +352,30 @@ def evaluate_rtbh_against_whitelists_check_results( Process all results for cases where rule is whitelisted by several whitelists. """ for rule, whitelist_key, relation in results: - match relation: - case Relation.EQUAL: - model = whitelist_rtbh_rule(model, wl_cache[whitelist_key]) - msg = f"RTBH Rule {model.id} {model} is equal to active whitelist {whitelist_key}. Rule is whitelisted." + if relation == Relation.EQUAL: + model = whitelist_rtbh_rule(model, wl_cache[whitelist_key]) + msg = f"RTBH Rule {model.id} {model} is equal to active whitelist {whitelist_key}. Rule is whitelisted." + flashes.append(msg) + current_app.logger.info(msg) + elif relation == Relation.SUBNET: + parts = subtract_network(target=str(model), whitelist=whitelist_key) + wl_id = wl_cache[whitelist_key].id + msg = f"RTBH Rule {model.id} {model} is supernet of active whitelist {whitelist_key}. Rule is whitelisted, {len(parts)} subnet rules created." + flashes.append(msg) + current_app.logger.info(msg) + for network in parts: + new_rule = create_rtbh_from_whitelist_parts(model, wl_id, whitelist_key, network, author, user_id) + msg = f"Created RTBH rule {new_rule.id} {new_rule} for {network} parted by whitelist {whitelist_key}" flashes.append(msg) current_app.logger.info(msg) - case Relation.SUBNET: - parts = subtract_network(target=str(model), whitelist=whitelist_key) - wl_id = wl_cache[whitelist_key].id - msg = f"RTBH Rule {model.id} {model} is supernet of active whitelist {whitelist_key}. Rule is whitelisted, {len(parts)} subnet rules created." - flashes.append(msg) - current_app.logger.info(msg) - for network in parts: - new_rule = create_rtbh_from_whitelist_parts(model, wl_id, whitelist_key, network, author, user_id) - msg = ( - f"Created RTBH rule {new_rule.id} {new_rule} for {network} parted by whitelist {whitelist_key}" - ) - flashes.append(msg) - current_app.logger.info(msg) - model.rstate_id = 4 - add_rtbh_rule_to_cache(model, wl_id, RuleOrigin.USER) - db.session.commit() - case Relation.SUPERNET: - model = whitelist_rtbh_rule(model, wl_cache[whitelist_key]) - msg = ( - f"RTBH Rule {model.id} {model} is subnet of active whitelist {whitelist_key}. Rule is whitelisted." - ) - current_app.logger.info(msg) - flashes.append(msg) + model.rstate_id = 4 + add_rtbh_rule_to_cache(model, wl_id, RuleOrigin.USER) + db.session.commit() + elif relation == Relation.SUPERNET: + model = whitelist_rtbh_rule(model, wl_cache[whitelist_key]) + msg = f"RTBH Rule {model.id} {model} is subnet of active whitelist {whitelist_key}. Rule is whitelisted." + current_app.logger.info(msg) + flashes.append(msg) return model diff --git a/flowapp/services/whitelist_service.py b/flowapp/services/whitelist_service.py index a934cc8..7cbef38 100644 --- a/flowapp/services/whitelist_service.py +++ b/flowapp/services/whitelist_service.py @@ -84,37 +84,34 @@ def evaluate_whitelist_against_rtbh_check_results( for rule_key, whitelist_key, relation in results: current_app.logger.info(f"whitelist {whitelist_key} is {relation} to Rule {rule_key}") - match relation: - case Relation.EQUAL: - whitelist_rtbh_rule(rtbh_rule_cache[rule_key], whitelist_model) - withdraw_rtbh_route(rtbh_rule_cache[rule_key]) - msg = "Existing active rule {rule_key} is equal to whitelist {whitelist_key}. Rule is now whitelisted." + if relation == Relation.EQUAL: + whitelist_rtbh_rule(rtbh_rule_cache[rule_key], whitelist_model) + withdraw_rtbh_route(rtbh_rule_cache[rule_key]) + msg = "Existing active rule {rule_key} is equal to whitelist {whitelist_key}. Rule is now whitelisted." + flashes.append(msg) + current_app.logger.info(msg) + elif relation == Relation.SUBNET: + parts = subtract_network(target=rule_key, whitelist=whitelist_key) + wl_id = whitelist_model.id + msg = f"Rule {rule_key} is supernet of whitelist {whitelist_key}. Rule is whitelisted, {len(parts)} subnet rules will be created." + flashes.append(msg) + current_app.logger.info(msg) + for network in parts: + rule_model = rtbh_rule_cache[rule_key] + create_rtbh_from_whitelist_parts(rule_model, wl_id, whitelist_key, network) + msg = f"Created RTBH rule from {rule_model.id} {network} parted by whitelist {whitelist_key}." flashes.append(msg) current_app.logger.info(msg) - case Relation.SUBNET: - parts = subtract_network(target=rule_key, whitelist=whitelist_key) - wl_id = whitelist_model.id - msg = f"Rule {rule_key} is supernet of whitelist {whitelist_key}. Rule is whitelisted, {len(parts)} subnet rules will be created." - flashes.append(msg) - current_app.logger.info(msg) - for network in parts: - rule_model = rtbh_rule_cache[rule_key] - create_rtbh_from_whitelist_parts(rule_model, wl_id, whitelist_key, network) - msg = f"Created RTBH rule from {rule_model.id} {network} parted by whitelist {whitelist_key}." - flashes.append(msg) - current_app.logger.info(msg) - rule_model.rstate_id = 4 - add_rtbh_rule_to_cache(rule_model, wl_id, RuleOrigin.USER) - db.session.commit() - case Relation.SUPERNET: - - whitelist_rtbh_rule(rtbh_rule_cache[rule_key], whitelist_model) - withdraw_rtbh_route(rtbh_rule_cache[rule_key]) - msg = ( - f"Existing active rule {rule_key} is subnet of whitelist {whitelist_key}. Rule is now whitelisted." - ) - current_app.logger.info(msg) - flashes.append(msg) + rule_model.rstate_id = 4 + add_rtbh_rule_to_cache(rule_model, wl_id, RuleOrigin.USER) + db.session.commit() + elif relation == Relation.SUPERNET: + + whitelist_rtbh_rule(rtbh_rule_cache[rule_key], whitelist_model) + withdraw_rtbh_route(rtbh_rule_cache[rule_key]) + msg = f"Existing active rule {rule_key} is subnet of whitelist {whitelist_key}. Rule is now whitelisted." + current_app.logger.info(msg) + flashes.append(msg) return whitelist_model From ba305000aa6eb6f19c7cede43c7f909218951147 Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Wed, 4 Jun 2025 18:40:00 +0200 Subject: [PATCH 3/4] avoid use of match case to keep compatibility with python 3.9 --- flowapp/services/whitelist_service.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/flowapp/services/whitelist_service.py b/flowapp/services/whitelist_service.py index 7cbef38..bf281af 100644 --- a/flowapp/services/whitelist_service.py +++ b/flowapp/services/whitelist_service.py @@ -151,13 +151,12 @@ def delete_whitelist(whitelist_id: int) -> List[str]: for cached_rule in cached_rules: rule_model_type = RuleTypes(cached_rule.rtype) cache_entries_count = RuleWhitelistCache.count_by_rule(cached_rule.rid, rule_model_type) - match rule_model_type: - case RuleTypes.IPv4: - rule_model = db.session.get(Flowspec4, cached_rule.rid) - case RuleTypes.IPv6: - rule_model = db.session.get(Flowspec6, cached_rule.rid) - case RuleTypes.RTBH: - rule_model = db.session.get(RTBH, cached_rule.rid) + if rule_model_type == RuleTypes.IPv4: + rule_model = db.session.get(Flowspec4, cached_rule.rid) + elif rule_model_type == RuleTypes.IPv6: + rule_model = db.session.get(Flowspec6, cached_rule.rid) + elif rule_model_type == RuleTypes.RTBH: + rule_model = db.session.get(RTBH, cached_rule.rid) rorigin_type = RuleOrigin(cached_rule.rorigin) current_app.logger.debug(f"Rule {rule_model} has origin {rorigin_type}") if rorigin_type == RuleOrigin.WHITELIST: From 42e93105d9ca52a153156ade8216876de762d24f Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Wed, 4 Jun 2025 18:59:40 +0200 Subject: [PATCH 4/4] Union instead | to be back compatible in type hints --- flowapp/services/whitelist_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flowapp/services/whitelist_common.py b/flowapp/services/whitelist_common.py index 3807dca..b9b9e8e 100644 --- a/flowapp/services/whitelist_common.py +++ b/flowapp/services/whitelist_common.py @@ -1,7 +1,7 @@ from enum import Enum, auto from functools import lru_cache import ipaddress -from typing import List, Tuple +from typing import List, Tuple, Union from flowapp import db from flowapp.constants import RuleOrigin, RuleTypes from flowapp.models import RTBH, RuleWhitelistCache, Whitelist @@ -59,7 +59,7 @@ def _is_same_ip_version(addr1: str, addr2: str) -> bool: @lru_cache(maxsize=1024) -def get_network(address: str) -> ipaddress.IPv4Network | ipaddress.IPv6Network: +def get_network(address: str) -> Union[ipaddress.IPv4Network, ipaddress.IPv6Network]: """ Create and cache an IP network object.