Skip to content
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

Add acl_mode, strict_scope, and hashing functionality #3

Merged
merged 7 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[flake8]
select = F,E722
ignore = F403,F405,F541
per-file-ignores =
*/__init__.py:F401,F403
5 changes: 4 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ jobs:
test:
needs: lint
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.x"
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install poetry
Expand Down
47 changes: 26 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

[![Python Version](https://img.shields.io/badge/python-3.9+-blue)](https://www.python.org) [![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](https://github.com/blacklanternsecurity/radixtarget/blob/master/LICENSE) [![Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![tests](https://github.com/blacklanternsecurity/radixtarget/actions/workflows/tests.yml/badge.svg)](https://github.com/blacklanternsecurity/radixtarget/actions/workflows/tests.yml) [![Codecov](https://codecov.io/gh/blacklanternsecurity/radixtarget/graph/badge.svg?token=7IPWMYMTGZ)](https://codecov.io/gh/blacklanternsecurity/radixtarget)

RadixTarget is a performant radix implementation designed for quick lookups of IP addresses/networks and DNS hostnames. Written in pure python and capable of roughly 100,000 lookups per second regardless of the size of the database.
RadixTarget is a performant radix implementation designed for quick lookups of IP addresses/networks and DNS hostnames.

Used by:
- [BBOT (Bighuge BLS OSINT Tool)](https://github.com/blacklanternsecurity/bbot)
- [cloudcheck](https://github.com/blacklanternsecurity/cloudcheck)
RadixTarget is:
- Written in pure python
- Capable of ~100,000 lookups per second regardless of database size
- 100% test coverage
- Used by:
- [BBOT](https://github.com/blacklanternsecurity/bbot)
- [cloudcheck](https://github.com/blacklanternsecurity/cloudcheck)
Written in pure python and capable of roughly 100,000 lookups per second regardless of database size, it's perfect for production .

### Installation ([PyPi](https://pypi.org/project/radixtarget/))

Expand All @@ -22,26 +27,26 @@ from radixtarget import RadixTarget
rt = RadixTarget()

# IPv4
rt.insert("192.168.1.0/24")
rt.search("192.168.1.10") # IPv4Network("192.168.1.0/24")
rt.search("192.168.2.10") # None
rt.add("192.168.1.0/24")
rt.get("192.168.1.10") # IPv4Network("192.168.1.0/24")
rt.get("192.168.2.10") # None

# ipv6
rt.insert("dead::/64")
rt.search("dead::beef") # IPv6Network("dead::/64")
rt.search("dead:cafe::beef") # None
# IPv6
rt.add("dead::/64")
rt.get("dead::beef") # IPv6Network("dead::/64")
rt.get("dead:cafe::beef") # None

# DNS
rt.insert("net")
rt.insert("www.example.com")
rt.insert("test.www.example.com")
rt.search("net") # "net"
rt.search("evilcorp.net") # "net"
rt.search("www.example.com") # "www.example.com"
rt.search("asdf.test.www.example.com") # "test.www.example.com"
rt.search("example.com") # None
rt.add("net")
rt.add("www.example.com")
rt.add("test.www.example.com")
rt.get("net") # "net"
rt.get("evilcorp.net") # "net"
rt.get("www.example.com") # "www.example.com"
rt.get("asdf.test.www.example.com") # "test.www.example.com"
rt.get("example.com") # None

# Custom data nodes
rt.insert("evilcorp.co.uk", "custom_data")
rt.search("www.evilcorp.co.uk") # "custom_data"
rt.add("evilcorp.co.uk", "custom_data")
rt.get("www.evilcorp.co.uk") # "custom_data"
```
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "radixtarget"
version = "1.1.0"
version = "2.0.0"
description = "Check whether an IP address belongs to a cloud provider"
authors = ["TheTechromancer"]
license = "GPL-3.0"
Expand Down
2 changes: 1 addition & 1 deletion radixtarget/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .radixtarget import RadixTarget
from .radixtarget import Target
62 changes: 62 additions & 0 deletions radixtarget/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import ipaddress


def host_size_key(host):
"""
Used for sorting by host size, so that parent dns names / ip subnets always come first

Notes:
- we have to use str(host) to break the tie between two hosts of the same length, e.g. evilcorp.com and evilcorp.net
"""
host = make_ip(host)
if is_ip(host):
try:
# bigger IP subnets should come first
return (-host.num_addresses, str(host))
except AttributeError:
# IP addresses default to 1
return (1, str(host))
# smaller domains should come first
return (len(host), str(host))


def is_ip(host):
"""Check if the given host is an instance of an IP address.

Args:
host (Any): The host to check.

Returns:
bool: True if the host is an instance of an IP address, False otherwise.
"""
return ipaddress._IPAddressBase in host.__class__.__mro__


def make_ip(host):
"""Convert a host to an IP network or return it as a lowercase string.

This function checks if the provided host is a string or an IP address.
If it is not a string and not an IP address, a ValueError is raised.
If the host is a valid IP address or network, it is converted to an
ipaddress.IPv4Network or ipaddress.IPv6Network object. If the host
cannot be converted, it is returned as a lowercase string.

Args:
host (str or ipaddress): The host to convert.

Raises:
ValueError: If the host is not of str or ipaddress type.

Returns:
ipaddress.IPv4Network or ipaddress.IPv6Network or str: The converted
IP network or the lowercase string representation of the host.
"""
if not isinstance(host, str):
if not is_ip(host):
raise ValueError(
f'Host "{host}" must be of str or ipaddress type, not "{type(host)}"'
)
try:
return ipaddress.ip_network(host, strict=False)
except Exception:
return host.lower()
Loading
Loading