generated from canonical/platform-engineering-charm-template
-
Notifications
You must be signed in to change notification settings - Fork 9
Haproxy route policy rules matching #401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Thanhphan1147
wants to merge
84
commits into
main
Choose a base branch
from
haproxy-route-policy-rules-matching
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
84 commits
Select commit
Hold shift + click to select a range
7b7a688
Implement request API
Thanhphan1147 00a3ea7
update model validation before save and add unit tests
Thanhphan1147 77622dd
Merge branch 'main' into haprox-route-policy-requests-api
Thanhphan1147 bc11a1f
use environment variables for secret key
Thanhphan1147 6f1499d
Merge branch 'haprox-route-policy-requests-api' of github.com:canonic…
Thanhphan1147 6e98d08
ruff format
Thanhphan1147 f073454
add secret key for testing
Thanhphan1147 4842e4a
remove port attribute from test
Thanhphan1147 5b667c0
add requirements.txt for testing
Thanhphan1147 e719d33
reintroduce port field
Thanhphan1147 597eb66
Add change artifact
Thanhphan1147 6f51301
run lint with uv
Thanhphan1147 7f245ea
add unit testing
Thanhphan1147 6254485
remove custom test
Thanhphan1147 ad50aa3
update migration
Thanhphan1147 3640143
Wrap creation under `transaction.atomic`
Thanhphan1147 adbf18f
Potential fix for pull request finding
Thanhphan1147 daef5d3
remove unused code
Thanhphan1147 78e5dc0
minor fixes to settings
Thanhphan1147 cd93076
Potential fix for pull request finding
Thanhphan1147 f93e9cb
use django serializer
Thanhphan1147 4a92e21
update gitignore
Thanhphan1147 7b1c9ca
Merge branch 'haprox-route-policy-requests-api' of github.com:canonic…
Thanhphan1147 22e4aef
update view to use django rest
Thanhphan1147 266e20e
remove python-version
Thanhphan1147 d5db748
update gitignore
Thanhphan1147 34ca372
add missing license headers
Thanhphan1147 3ea5d0e
Add rules engine
Thanhphan1147 16833e4
update migration
Thanhphan1147 efe9beb
update view
Thanhphan1147 593518e
fix lint
Thanhphan1147 f23afe1
remove extra tests
Thanhphan1147 6955b92
add validation and update tests
Thanhphan1147 ff23fa0
update view
Thanhphan1147 314a687
remove to_dict
Thanhphan1147 a10a032
use serializer for get
Thanhphan1147 c4b297f
use serializer
Thanhphan1147 4f09fbc
remove unused tests
Thanhphan1147 ad1c42c
use filter for delete query
Thanhphan1147 fb9e143
update tests and move validation to serializer class
Thanhphan1147 b05c12a
Apply suggestion from @github-actions[bot]
Thanhphan1147 10a2708
Update haproxy-route-policy/policy/migrations/0001_initial.py
Thanhphan1147 571e469
Revert "Update haproxy-route-policy/policy/migrations/0001_initial.py"
Thanhphan1147 a22bedd
ignore migration files for license header
Thanhphan1147 7d3d20b
remove license header from generated files
Thanhphan1147 e41031c
Merge branch 'haprox-route-policy-requests-api' into haproxy-route-po…
Thanhphan1147 00c094e
add change artifact
Thanhphan1147 cd7c49a
add envlist to tox commands
Thanhphan1147 503a313
update envlist
Thanhphan1147 fc25b19
Merge branch 'haprox-route-policy-requests-api' into haproxy-route-po…
Thanhphan1147 a2d1ea2
convert pk to uuid for requests
Thanhphan1147 a9ceb87
Merge branch 'haprox-route-policy-requests-api' into haproxy-route-po…
Thanhphan1147 988ba70
Add guard against mal-formed uuid and parameter. Add logging configs,…
Thanhphan1147 9a246aa
add validators for port and paths
Thanhphan1147 35a286f
add tests for validators
Thanhphan1147 a42d4c3
add note for migration
Thanhphan1147 324114e
Merge remote-tracking branch 'origin/haprox-route-policy-requests-api…
Thanhphan1147 ee86d7d
ruff format
Thanhphan1147 9101175
remove unused imports
Thanhphan1147 69872ec
add static tests
Thanhphan1147 fee2543
Merge remote-tracking branch 'origin/main' into haproxy-route-policy-…
Thanhphan1147 b700245
guard rules API against pk
Thanhphan1147 931b5b1
Merge branch 'main' into haproxy-route-policy-rules-api
alithethird e3d7215
update view, middle wares and tests
Thanhphan1147 04f29ca
Merge branch 'haproxy-route-policy-rules-api' of github.com:canonical…
Thanhphan1147 b4717a4
Merge branch 'main' into haproxy-route-policy-rules-api
Thanhphan1147 6c1db27
refactor tests by parametrizing
Thanhphan1147 92fddb4
Merge branch 'haproxy-route-policy-rules-api' of github.com:canonical…
Thanhphan1147 ea6afe4
group tests by parameterizing
Thanhphan1147 7ec072c
refactor Rule model to rename attribute from "value" to "parameters"
Thanhphan1147 9a3d61a
update test name
Thanhphan1147 6b75a0a
update naming
Thanhphan1147 5e94907
Add coverage-report as part of unit test suite
Thanhphan1147 1910db5
update env list
Thanhphan1147 6a3b4b7
implement rule evaluation
Thanhphan1147 c564054
add change artifact
Thanhphan1147 92e6810
update imports
Thanhphan1147 ed16de9
update naming
Thanhphan1147 d3b5e6d
update rules matching logic
Thanhphan1147 ec6d312
update tests
Thanhphan1147 93ce0f4
save request using serializer with the correct instace
Thanhphan1147 7172da3
Merge remote-tracking branch 'origin/main' into haproxy-route-policy-…
Thanhphan1147 3e3a0a9
group tests
Thanhphan1147 60cc53f
update formatting
Thanhphan1147 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| version_schema: 2 | ||
|
|
||
| changes: | ||
| - title: Added rule matching engine and request evaluation on creation | ||
| author: tphan025 | ||
| type: minor | ||
| description: > | ||
| Added a rule matching engine that evaluates backend requests against rules | ||
| ordered by descending priority. Within the same priority group, deny rules | ||
| take precedence over allow rules. Integrated the engine into the bulk create | ||
| endpoint so that each new request is evaluated immediately and its status is | ||
| set to accepted, rejected, or pending accordingly. Included unit tests for the | ||
| matching logic and integration tests for rule evaluation during request creation. | ||
| urls: | ||
| pr: | ||
| - https://github.com/canonical/haproxy-operator/pull/401 | ||
| related_doc: | ||
| related_issue: | ||
| visibility: public | ||
| highlight: false |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| # Copyright 2026 Canonical Ltd. | ||
| # See LICENSE file for licensing details. | ||
|
|
||
| """Rule matching engine for evaluating backend requests against rules. | ||
|
|
||
| Rules are evaluated following these principles: | ||
| P1: Rules are grouped by priority and evaluated starting from the highest | ||
| priority group. | ||
| P2: Within the same priority group, "deny" rules take precedence over | ||
| "allow" rules. | ||
|
|
||
| If no rules match a request, its status remains "pending". | ||
| """ | ||
|
|
||
| import logging | ||
| from itertools import groupby | ||
| from policy.db_models import ( | ||
| BackendRequest, | ||
| Rule, | ||
| RULE_ACTION_ALLOW, | ||
| RULE_ACTION_DENY, | ||
| RULE_KIND_HOSTNAME_AND_PATH_MATCH, | ||
| REQUEST_STATUS_ACCEPTED, | ||
| REQUEST_STATUS_REJECTED, | ||
| REQUEST_STATUS_PENDING, | ||
| ) | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def _hostname_and_path_match(rule: Rule, request: BackendRequest) -> bool: | ||
| """Check if a hostname_and_path_match rule matches a backend request. | ||
|
|
||
| A rule matches if: | ||
| 1. Any of the rule's `hostnames` appear in the request's `hostname_acls` | ||
| if `hostnames` is not empty. | ||
| 2. Any of the rule's `paths` appear in the request's `paths` | ||
| if `paths` is not empty.. | ||
|
|
||
| Args: | ||
| rule: The rule to check. | ||
| request: The backend request to evaluate. | ||
|
|
||
| Returns: | ||
| True if the rule matches the request, False otherwise. | ||
| """ | ||
| rule_hostnames: list = rule.parameters.get("hostnames", []) | ||
| rule_paths: list = rule.parameters.get("paths", []) | ||
|
|
||
| # A rule with no hostnames can never match. | ||
| if not rule_hostnames: | ||
| return False | ||
|
|
||
| # At least one rule hostname must appear in the request's hostname_acls. | ||
| hostname_matched = bool(set(rule_hostnames).intersection(request.hostname_acls)) | ||
| if not hostname_matched: | ||
| return False | ||
|
|
||
| # Empty rule paths means "match all paths" (wildcard). | ||
| if not rule_paths: | ||
| return True | ||
|
|
||
| # At least one rule path must appear in the request's paths. | ||
| return bool(set(rule_paths).intersection(request.paths)) | ||
|
|
||
|
|
||
| def evaluate_request(request: BackendRequest) -> str: | ||
| """Evaluate a backend request against all rules and return the resulting status. | ||
|
|
||
| Rules are fetched from the database, ordered by descending priority. | ||
| They are grouped by priority level and evaluated from highest to lowest. | ||
|
|
||
| Within the same priority group: | ||
| - If any "deny" rule matches, the request is rejected. | ||
| - If any "allow" rule matches (and no deny matched), the request is accepted. | ||
| - If no rules match at this priority level, move to the next group. | ||
|
|
||
| If no rules match at any priority level, the request stays "pending". | ||
|
|
||
| Args: | ||
| request: The backend request to evaluate. | ||
|
|
||
| Returns: | ||
| The resulting status string: "accepted", "rejected", or "pending". | ||
| """ | ||
| rules = Rule.objects.all().order_by("-priority") | ||
|
|
||
| for _priority, group in groupby(rules, key=lambda rule: rule.priority): | ||
| allow_matched = False | ||
| deny_matched = False | ||
|
|
||
| for rule in group: | ||
| if not _matches(rule, request): | ||
| continue | ||
|
|
||
| if rule.action == RULE_ACTION_DENY: | ||
| deny_matched = True | ||
| elif rule.action == RULE_ACTION_ALLOW: | ||
| allow_matched = True | ||
|
|
||
| # P2: deny rules have priority over allow rules within the same priority level | ||
| if deny_matched: | ||
| return REQUEST_STATUS_REJECTED | ||
| if allow_matched: | ||
| return REQUEST_STATUS_ACCEPTED | ||
|
|
||
| return REQUEST_STATUS_PENDING | ||
|
|
||
|
|
||
| def _matches(rule: Rule, request: BackendRequest) -> bool: | ||
| """Dispatch matching logic based on the rule kind. | ||
|
|
||
| Args: | ||
| rule: The rule to evaluate. | ||
| request: The backend request to evaluate against. | ||
|
|
||
| Returns: | ||
| True if the rule matches the request. | ||
| """ | ||
| if rule.kind == RULE_KIND_HOSTNAME_AND_PATH_MATCH: | ||
| return _hostname_and_path_match(rule, request) | ||
| return False | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we discussed it in the spec, but I wonder if we should not store the information of which rule has been matched somewhere to ease troubleshooting?