Repository navigation
Use the zen-internals IP matcher #757
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
base: main
Are you sure you want to change the base?
Changes from all commits
f728ab8
e0f8df3
99a4da7
fe2569c
60abdbe
e0c24c8
3ebdc29
c63cc65
970b24e
61561f3
8917ddc
07df6c6
030b2ca
dd2f5a2
df410ef
db40f22
aa206e7
2495d69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,64 +1,45 @@ | ||||||||||||||||||||||||||||||
| import ipaddress | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||
| import pytricia | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| PYTRICIA_AVAILABLE = True | ||||||||||||||||||||||||||||||
| except ImportError: | ||||||||||||||||||||||||||||||
| PYTRICIA_AVAILABLE = False | ||||||||||||||||||||||||||||||
| from aikido_zen.helpers.logging import logger | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| logger.warning( | ||||||||||||||||||||||||||||||
| "pytricia is not available. This happens on windows devices where pytricia is not supported yet." | ||||||||||||||||||||||||||||||
| "Using fallback, this may result in slower performance." | ||||||||||||||||||||||||||||||
| "You can try to install pytricia for better performance: pip install pytricia" | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def preparse(network: str) -> str: | ||||||||||||||||||||||||||||||
| # Remove the brackets around IPv6 addresses if they are there. | ||||||||||||||||||||||||||||||
| network = network.strip("[]") | ||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||
| ip = ipaddress.IPv6Address(network) | ||||||||||||||||||||||||||||||
| if ip.ipv4_mapped: | ||||||||||||||||||||||||||||||
| return str(ip.ipv4_mapped) | ||||||||||||||||||||||||||||||
| except ValueError: | ||||||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||||||
| return network | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if PYTRICIA_AVAILABLE: | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| class IPMatcher: | ||||||||||||||||||||||||||||||
| def __init__(self, networks=None): | ||||||||||||||||||||||||||||||
| self.trie = pytricia.PyTricia(128) | ||||||||||||||||||||||||||||||
| if networks is not None: | ||||||||||||||||||||||||||||||
| for s in networks: | ||||||||||||||||||||||||||||||
| self._add(s) | ||||||||||||||||||||||||||||||
| # We freeze in constructor ensuring that after initialization the IPMatcher is always frozen. | ||||||||||||||||||||||||||||||
| self.trie.freeze() | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def has(self, network): | ||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||
| return self.trie.get(preparse(network)) is not None | ||||||||||||||||||||||||||||||
| except ValueError: | ||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def _add(self, network): | ||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||
| self.trie[preparse(network)] = True | ||||||||||||||||||||||||||||||
| except ValueError: | ||||||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||||||
| except SystemError: | ||||||||||||||||||||||||||||||
| # SystemError's have been known to occur in the PyTricia library (see issue #34 e.g.), | ||||||||||||||||||||||||||||||
| # best to play it safe and catch these errors. | ||||||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||||||
| return self | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def is_empty(self): | ||||||||||||||||||||||||||||||
| return len(self.trie) == 0 | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||
| # Fallback to pure Python implementation - this happens on windows machines since pytricia is not | ||||||||||||||||||||||||||||||
| # fully supported there. | ||||||||||||||||||||||||||||||
| from aikido_zen.helpers.ip_matcher_fallback import IPMatcher # noqa: F401 | ||||||||||||||||||||||||||||||
| import weakref | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| from aikido_zen.helpers import zen_internals | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| class IPMatcher: | ||||||||||||||||||||||||||||||
| def __init__(self, networks=None): | ||||||||||||||||||||||||||||||
| # json.loads can return lone surrogates such as "\udc80", and .encode() raises on | ||||||||||||||||||||||||||||||
| # them, which would fail the whole list update. Valid networks are ASCII. | ||||||||||||||||||||||||||||||
| # The library splits on newlines, so a newline would turn one entry into two. | ||||||||||||||||||||||||||||||
| networks = list(networks or []) | ||||||||||||||||||||||||||||||
| self._networks = [ | ||||||||||||||||||||||||||||||
| network | ||||||||||||||||||||||||||||||
| for network in networks | ||||||||||||||||||||||||||||||
| if isinstance(network, str) and network.isascii() and "\n" not in network | ||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||
|
aikido-autofix[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||
| self._skipped_count = len(networks) - len(self._networks) | ||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Show fix
Suggested change
Details✨ AI Reasoning Reply on this comment to give feedback or ignore the issue.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not relevant, the original value isn't needed after this line, so we just reuse the name
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⛔ You don't have permission to ignore issues. |
||||||||||||||||||||||||||||||
| self._handle = zen_internals.ip_matcher_create( | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Networks data provided to Zen by Aikido are expected to be well-formed so the rejection of any networks is considered an exception. A tolerable rejection rate may be preferable, depending on the source of the networks data.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, added it. Invalid networks are now logged with the list they came from, and the valid ones still apply. The lists are also updated together now, so updates never mix. |
||||||||||||||||||||||||||||||
| "\n".join(self._networks).encode() | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
| if self._handle is not None: | ||||||||||||||||||||||||||||||
| weakref.finalize(self, zen_internals.ip_matcher_free, self._handle) | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The finalizer may receive None as its callback when the library lacks ip_matcher_free; registering it can cause a runtime exception during finalization. Details✨ AI Reasoning 🔧 How do I fix it? Reply on this comment to give feedback or ignore the issue.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. Without ip_matcher_free, ip_matcher_create returns None. |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def is_built(self): | ||||||||||||||||||||||||||||||
| return self._handle is not None | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def rejected_count(self): | ||||||||||||||||||||||||||||||
| if self._handle is None: | ||||||||||||||||||||||||||||||
| return self._skipped_count | ||||||||||||||||||||||||||||||
| return self._skipped_count + zen_internals.ip_matcher_rejected_count( | ||||||||||||||||||||||||||||||
| self._handle | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def has(self, network): | ||||||||||||||||||||||||||||||
| if self._handle is None: | ||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||
| if not isinstance(network, str) or not network.isascii(): | ||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||
| return zen_internals.ip_matcher_has(self._handle, network.encode()) | ||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High - Missing native matcher disables SSRF and IP-list enforcement When the bundled zen-internals library is missing or cannot be loaded, IPMatcher has no handle and its Show fixRetain a functional pure-Python matcher fallback for load failures, or fail closed and make the application refuse to enable these security controls unless all required native matcher symbols are available. Ensure every supported deployment target ships and loads the matching artifact before accepting IP-based security configuration. More info - Reply on this comment to give feedback or ignore the issue.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On platforms that have no zen-internals build (for example 32-bit Linux), Zen fails open and logs a warning, the same way SQL injection detection already behaves there on main.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 We were not able to ignore this issue because of the following reason:
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def is_empty(self): | ||||||||||||||||||||||||||||||
| return len(self._networks) == 0 | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def __reduce__(self): | ||||||||||||||||||||||||||||||
| return self.__class__, (self._networks,) | ||||||||||||||||||||||||||||||
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.
any way we can avoid this? e.g. use a function
create_ip_matcherthat returns None if no IPMatcher could be created?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 tried it. create_ip_matcher() returning None removes the if for route allowlists, but every other place that uses a matcher then needs a None check: the blocklist, bypass list and both SSRF lists. That's 5 checks instead of 2, and any new caller has to remember it. So I would keep it as it is.
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.
Do those call sites need to check whether the IP matcher was built?