mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-11-04 12:18:17 +00:00 
			
		
		
		
	sys-dns-wildcards: always create apex wildcard (*.apex); use explicit_domains for CURRENT_PLAY_DOMAINS_ALL list; update README and unit tests. Ref: https://chatgpt.com/share/68c37a74-7468-800f-a612-765bbbd442de
This commit is contained in:
		@@ -1,19 +1,17 @@
 | 
				
			|||||||
# sys-dns-wildcards
 | 
					# sys-dns-wildcards
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Create Cloudflare DNS **wildcard** A/AAAA records (`*.parent`) only for **parent hosts** (hosts that have children).
 | 
					Create Cloudflare DNS **wildcard** A/AAAA records (`*.parent`) for **parent hosts** (hosts that have children) **and** always for the **apex** (SLD.TLD).
 | 
				
			||||||
The **apex** (SLD.TLD) is considered when computing parents, but **no base host** or `*.apex` record is created by this role.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Examples:
 | 
					Examples:
 | 
				
			||||||
- c.wiki.example.com  -> parent: wiki.example.com -> creates: `*.wiki.example.com`
 | 
					- c.wiki.example.com  -> parent: wiki.example.com -> creates: `*.wiki.example.com`
 | 
				
			||||||
- a.b.example.com     -> parent: b.example.com    -> creates: `*.b.example.com`
 | 
					- a.b.example.com     -> parent: b.example.com    -> creates: `*.b.example.com`
 | 
				
			||||||
- example.com (apex)  -> used to detect parents, but **no** `example.com` or `*.example.com` record is created
 | 
					- example.com (apex)  -> also creates: `*.example.com`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Inputs
 | 
					## Inputs
 | 
				
			||||||
- parent_dns_domains (list[str], optional): FQDNs to evaluate. If empty, the role flattens CURRENT_PLAY_DOMAINS_ALL.
 | 
					- parent_dns_domains (list[str], optional): FQDNs to evaluate. If empty, the role flattens CURRENT_PLAY_DOMAINS_ALL.
 | 
				
			||||||
- PRIMARY_DOMAIN (apex), defaults_networks.internet.ip4, optional defaults_networks.internet.ip6
 | 
					- PRIMARY_DOMAIN (apex), defaults_networks.internet.ip4, optional defaults_networks.internet.ip6
 | 
				
			||||||
- Flags:
 | 
					- Flags:
 | 
				
			||||||
  - parent_dns_enabled (bool, default: true)
 | 
					  - parent_dns_enabled (bool, default: true)
 | 
				
			||||||
  - parent_dns_ipv6_enabled (bool, default: true)
 | 
					 | 
				
			||||||
  - parent_dns_proxied (bool, default: false)
 | 
					  - parent_dns_proxied (bool, default: false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Usage
 | 
					## Usage
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1 @@
 | 
				
			|||||||
parent_dns_proxied: false
 | 
					parent_dns_proxied: false
 | 
				
			||||||
parent_dns_domains: []
 | 
					 | 
				
			||||||
parent_dns_ipv6_enabled: true
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					# roles/sys-dns-wildcards/filter_plugins/wildcard_dns.py
 | 
				
			||||||
from ansible.errors import AnsibleFilterError
 | 
					from ansible.errors import AnsibleFilterError
 | 
				
			||||||
import ipaddress
 | 
					import ipaddress
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,7 +16,9 @@ def _depth(domain: str, apex: str) -> int:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _parent_of_child(domain: str, apex: str) -> str | None:
 | 
					def _parent_of_child(domain: str, apex: str) -> str | None:
 | 
				
			||||||
    """For a child like a.b.example.com return b.example.com; else None (needs depth >= 2)."""
 | 
					    """
 | 
				
			||||||
 | 
					    For a child like a.b.example.com return b.example.com; else None (needs depth >= 2).
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    if not domain.endswith(apex):
 | 
					    if not domain.endswith(apex):
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
    parts = domain.split(".")
 | 
					    parts = domain.split(".")
 | 
				
			||||||
@@ -27,7 +30,7 @@ def _parent_of_child(domain: str, apex: str) -> str | None:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def _flatten_domains_any_structure(domains_like) -> list[str]:
 | 
					def _flatten_domains_any_structure(domains_like) -> list[str]:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Accepts CURRENT_PLAY_DOMAINS_ALL-like structures:
 | 
					    Accept CURRENT_PLAY_DOMAINS*_like structures:
 | 
				
			||||||
      - dict values: str | list/tuple/set[str] | dict (one level deeper)
 | 
					      - dict values: str | list/tuple/set[str] | dict (one level deeper)
 | 
				
			||||||
    Returns unique, sorted host list.
 | 
					    Returns unique, sorted host list.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@@ -100,17 +103,23 @@ def _build_wildcard_records(
 | 
				
			|||||||
        records.append({
 | 
					        records.append({
 | 
				
			||||||
            "zone": apex,
 | 
					            "zone": apex,
 | 
				
			||||||
            "type": rtype,
 | 
					            "type": rtype,
 | 
				
			||||||
            "name": name,
 | 
					            "name": name,     # For apex wildcard, name "*" means "*.apex" in Cloudflare
 | 
				
			||||||
            "content": content,
 | 
					            "content": content,
 | 
				
			||||||
            "proxied": bool(proxied),
 | 
					            "proxied": bool(proxied),
 | 
				
			||||||
            "ttl": 1,
 | 
					            "ttl": 1,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for p in sorted(set(parents)):
 | 
					    for p in sorted(set(parents)):
 | 
				
			||||||
        rel = p[:-len(apex)-1] if p != apex else ""  # relative part; apex shouldn't produce wildcard
 | 
					        # Create wildcard at apex as well (name="*")
 | 
				
			||||||
 | 
					        if p == apex:
 | 
				
			||||||
 | 
					            wc = "*"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # relative part (drop ".apex")
 | 
				
			||||||
 | 
					            rel = p[:-len(apex)-1]
 | 
				
			||||||
            if not rel:
 | 
					            if not rel:
 | 
				
			||||||
            # Do NOT create *.apex here (explicitly excluded by requirement)
 | 
					                # Safety guard; should not happen because p==apex handled above
 | 
				
			||||||
            continue
 | 
					                wc = "*"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
                wc = f"*.{rel}"
 | 
					                wc = f"*.{rel}"
 | 
				
			||||||
        _add(wc, "A", str(ip4))
 | 
					        _add(wc, "A", str(ip4))
 | 
				
			||||||
        if ipv6_enabled and ip6 and _is_global(str(ip6)):
 | 
					        if ipv6_enabled and ip6 and _is_global(str(ip6)):
 | 
				
			||||||
@@ -119,7 +128,7 @@ def _build_wildcard_records(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def wildcard_records(
 | 
					def wildcard_records(
 | 
				
			||||||
    current_play_domains_all: dict,
 | 
					    current_play_domains_all,  # dict expected when explicit_domains is None
 | 
				
			||||||
    apex: str,
 | 
					    apex: str,
 | 
				
			||||||
    ip4: str,
 | 
					    ip4: str,
 | 
				
			||||||
    ip6: str | None = None,
 | 
					    ip6: str | None = None,
 | 
				
			||||||
@@ -129,17 +138,25 @@ def wildcard_records(
 | 
				
			|||||||
    ipv6_enabled: bool = True,
 | 
					    ipv6_enabled: bool = True,
 | 
				
			||||||
) -> list[dict]:
 | 
					) -> list[dict]:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Build only wildcard records for parents:
 | 
					    Build wildcard records:
 | 
				
			||||||
      for each parent 'parent.apex' -> create '*.parent' A/AAAA.
 | 
					      - for each parent 'parent.apex' -> create '*.parent' A/AAAA
 | 
				
			||||||
      No base parent records and no '*.apex' are created.
 | 
					      - ALWAYS also create '*.apex' (apex wildcard), modeled as name="*"
 | 
				
			||||||
 | 
					    Sources:
 | 
				
			||||||
 | 
					      - If 'explicit_domains' is provided and non-empty, use it (expects list[str]).
 | 
				
			||||||
 | 
					      - Else flatten 'current_play_domains_all' (expects dict).
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    # Source domains
 | 
					    # Source domains
 | 
				
			||||||
    if explicit_domains and len(explicit_domains) > 0:
 | 
					    if explicit_domains and len(explicit_domains) > 0:
 | 
				
			||||||
 | 
					        if not isinstance(explicit_domains, list) or not all(isinstance(x, str) for x in explicit_domains):
 | 
				
			||||||
 | 
					            raise AnsibleFilterError("explicit_domains must be list[str]")
 | 
				
			||||||
        domains = sorted(set(explicit_domains))
 | 
					        domains = sorted(set(explicit_domains))
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        domains = _flatten_domains_any_structure(current_play_domains_all)
 | 
					        domains = _flatten_domains_any_structure(current_play_domains_all)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Determine parents and ALWAYS include apex for apex wildcard
 | 
				
			||||||
    parents = _parents_from(domains, apex, min_child_depth=min_child_depth)
 | 
					    parents = _parents_from(domains, apex, min_child_depth=min_child_depth)
 | 
				
			||||||
 | 
					    parents = list(set(parents) | {apex})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return _build_wildcard_records(
 | 
					    return _build_wildcard_records(
 | 
				
			||||||
        parents,
 | 
					        parents,
 | 
				
			||||||
        apex,
 | 
					        apex,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,12 @@
 | 
				
			|||||||
SYN_DNS_WILDCARD_RECORDS: >-
 | 
					SYN_DNS_WILDCARD_RECORDS: >-
 | 
				
			||||||
  {{ CURRENT_PLAY_DOMAINS_ALL
 | 
					  {{
 | 
				
			||||||
      | wildcard_records(
 | 
					    {} |
 | 
				
			||||||
 | 
					    wildcard_records(
 | 
				
			||||||
      PRIMARY_DOMAIN,
 | 
					      PRIMARY_DOMAIN,
 | 
				
			||||||
          defaults_networks.internet.ip4,
 | 
					      networks.internet.ip4,
 | 
				
			||||||
          (defaults_networks.internet.ip6 | default('')),
 | 
					      networks.internet.ip6,
 | 
				
			||||||
      parent_dns_proxied,
 | 
					      parent_dns_proxied,
 | 
				
			||||||
          (parent_dns_domains | default([])),
 | 
					      explicit_domains=CURRENT_PLAY_DOMAINS_ALL,
 | 
				
			||||||
          2,
 | 
					      min_child_depth=2,
 | 
				
			||||||
          parent_dns_ipv6_enabled
 | 
					      ipv6_enabled=true)
 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
  }}
 | 
					  }}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,7 +48,7 @@ class TestWildcardDNS(unittest.TestCase):
 | 
				
			|||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        self.wildcard_records = _get_filter()
 | 
					        self.wildcard_records = _get_filter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_only_wildcards_no_apex_or_base(self):
 | 
					    def test_only_wildcards_including_apex(self):
 | 
				
			||||||
        apex = "example.com"
 | 
					        apex = "example.com"
 | 
				
			||||||
        cpda = {
 | 
					        cpda = {
 | 
				
			||||||
            "svc-a": ["c.wiki.example.com", "a.b.example.com"],
 | 
					            "svc-a": ["c.wiki.example.com", "a.b.example.com"],
 | 
				
			||||||
@@ -69,19 +69,24 @@ class TestWildcardDNS(unittest.TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        got = _as_set(recs)
 | 
					        got = _as_set(recs)
 | 
				
			||||||
        expected = {
 | 
					        expected = {
 | 
				
			||||||
 | 
					            # apex wildcard always
 | 
				
			||||||
 | 
					            ("A", "*", "203.0.113.10", True),
 | 
				
			||||||
 | 
					            ("AAAA", "*", "2606:4700:4700::1111", True),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # derived parents
 | 
				
			||||||
            ("A", "*.wiki", "203.0.113.10", True),
 | 
					            ("A", "*.wiki", "203.0.113.10", True),
 | 
				
			||||||
            ("AAAA", "*.wiki", "2606:4700:4700::1111", True),
 | 
					            ("AAAA", "*.wiki", "2606:4700:4700::1111", True),
 | 
				
			||||||
            ("A", "*.b", "203.0.113.10", True),
 | 
					            ("A", "*.b", "203.0.113.10", True),
 | 
				
			||||||
            ("AAAA", "*.b", "2606:4700:4700::1111", True),
 | 
					            ("AAAA", "*.b", "2606:4700:4700::1111", True),
 | 
				
			||||||
            # now included because www.a.b.example.com promotes a.b.example.com as a parent
 | 
					            # www.a.b.example.com promotes a.b.example.com as a parent
 | 
				
			||||||
            ("A", "*.a.b", "203.0.113.10", True),
 | 
					            ("A", "*.a.b", "203.0.113.10", True),
 | 
				
			||||||
            ("AAAA", "*.a.b", "2606:4700:4700::1111", True),
 | 
					            ("AAAA", "*.a.b", "2606:4700:4700::1111", True),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        self.assertEqual(got, expected)
 | 
					        self.assertEqual(got, expected)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_min_child_depth_prevents_apex_wildcard(self):
 | 
					    def test_min_child_depth_yields_only_apex(self):
 | 
				
			||||||
        apex = "example.com"
 | 
					        apex = "example.com"
 | 
				
			||||||
        cpda = {"svc": ["x.example.com"]}  # depth = 1
 | 
					        cpda = {"svc": ["x.example.com"]}  # depth = 1, below threshold
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        recs = self.wildcard_records(
 | 
					        recs = self.wildcard_records(
 | 
				
			||||||
            current_play_domains_all=cpda,
 | 
					            current_play_domains_all=cpda,
 | 
				
			||||||
@@ -93,13 +98,18 @@ class TestWildcardDNS(unittest.TestCase):
 | 
				
			|||||||
            min_child_depth=2,  # requires >= 2 → no parent derived
 | 
					            min_child_depth=2,  # requires >= 2 → no parent derived
 | 
				
			||||||
            ipv6_enabled=True,
 | 
					            ipv6_enabled=True,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(recs, [])
 | 
					        got = _as_set(recs)
 | 
				
			||||||
 | 
					        expected = {
 | 
				
			||||||
 | 
					            ("A", "*", "198.51.100.42", False),
 | 
				
			||||||
 | 
					            ("AAAA", "*", "2606:4700:4700::1111", False),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        self.assertEqual(got, expected)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_ipv6_disabled_and_private_ipv6_filtered(self):
 | 
					    def test_ipv6_disabled_and_private_ipv6_filtered(self):
 | 
				
			||||||
        apex = "example.com"
 | 
					        apex = "example.com"
 | 
				
			||||||
        cpda = {"svc": ["a.b.example.com"]}
 | 
					        cpda = {"svc": ["a.b.example.com"]}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # IPv6 disabled → only A record
 | 
					        # IPv6 disabled → only A records (apex + parent)
 | 
				
			||||||
        recs1 = self.wildcard_records(
 | 
					        recs1 = self.wildcard_records(
 | 
				
			||||||
            current_play_domains_all=cpda,
 | 
					            current_play_domains_all=cpda,
 | 
				
			||||||
            apex=apex,
 | 
					            apex=apex,
 | 
				
			||||||
@@ -110,9 +120,15 @@ class TestWildcardDNS(unittest.TestCase):
 | 
				
			|||||||
            min_child_depth=2,
 | 
					            min_child_depth=2,
 | 
				
			||||||
            ipv6_enabled=False,
 | 
					            ipv6_enabled=False,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(_as_set(recs1), {("A", "*.b", "203.0.113.9", False)})
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            _as_set(recs1),
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ("A", "*", "203.0.113.9", False),
 | 
				
			||||||
 | 
					                ("A", "*.b", "203.0.113.9", False),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # IPv6 enabled but ULA (not global) → skip AAAA
 | 
					        # IPv6 enabled but ULA (not global) → skip AAAA (apex + parent)
 | 
				
			||||||
        recs2 = self.wildcard_records(
 | 
					        recs2 = self.wildcard_records(
 | 
				
			||||||
            current_play_domains_all=cpda,
 | 
					            current_play_domains_all=cpda,
 | 
				
			||||||
            apex=apex,
 | 
					            apex=apex,
 | 
				
			||||||
@@ -123,7 +139,13 @@ class TestWildcardDNS(unittest.TestCase):
 | 
				
			|||||||
            min_child_depth=2,
 | 
					            min_child_depth=2,
 | 
				
			||||||
            ipv6_enabled=True,
 | 
					            ipv6_enabled=True,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(_as_set(recs2), {("A", "*.b", "203.0.113.9", False)})
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            _as_set(recs2),
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ("A", "*", "203.0.113.9", False),
 | 
				
			||||||
 | 
					                ("A", "*.b", "203.0.113.9", False),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_proxied_flag_true_is_set(self):
 | 
					    def test_proxied_flag_true_is_set(self):
 | 
				
			||||||
        recs = self.wildcard_records(
 | 
					        recs = self.wildcard_records(
 | 
				
			||||||
@@ -137,7 +159,13 @@ class TestWildcardDNS(unittest.TestCase):
 | 
				
			|||||||
            ipv6_enabled=True,
 | 
					            ipv6_enabled=True,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertTrue(all(r.get("proxied") is True for r in recs))
 | 
					        self.assertTrue(all(r.get("proxied") is True for r in recs))
 | 
				
			||||||
        self.assertEqual(_as_set(recs), {("A", "*.b", "203.0.113.7", True)})
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            _as_set(recs),
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ("A", "*", "203.0.113.7", True),
 | 
				
			||||||
 | 
					                ("A", "*.b", "203.0.113.7", True),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_explicit_domains_override_source(self):
 | 
					    def test_explicit_domains_override_source(self):
 | 
				
			||||||
        cpda = {"svc": ["ignore.me.example.com", "a.b.example.com"]}
 | 
					        cpda = {"svc": ["ignore.me.example.com", "a.b.example.com"]}
 | 
				
			||||||
@@ -156,6 +184,11 @@ class TestWildcardDNS(unittest.TestCase):
 | 
				
			|||||||
        self.assertEqual(
 | 
					        self.assertEqual(
 | 
				
			||||||
            _as_set(recs),
 | 
					            _as_set(recs),
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                # apex wildcard always
 | 
				
			||||||
 | 
					                ("A", "*", "203.0.113.5", False),
 | 
				
			||||||
 | 
					                ("AAAA", "*", "2606:4700:4700::1111", False),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # derived from explicit domain
 | 
				
			||||||
                ("A", "*.wiki", "203.0.113.5", False),
 | 
					                ("A", "*.wiki", "203.0.113.5", False),
 | 
				
			||||||
                ("AAAA", "*.wiki", "2606:4700:4700::1111", False),
 | 
					                ("AAAA", "*.wiki", "2606:4700:4700::1111", False),
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
@@ -183,11 +216,16 @@ class TestWildcardDNS(unittest.TestCase):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
        got = _as_set(recs)
 | 
					        got = _as_set(recs)
 | 
				
			||||||
        expected = {
 | 
					        expected = {
 | 
				
			||||||
 | 
					            # apex wildcard always
 | 
				
			||||||
 | 
					            ("A", "*", "203.0.113.21", False),
 | 
				
			||||||
 | 
					            ("AAAA", "*", "2606:4700:4700::1111", False),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # derived parents
 | 
				
			||||||
            ("A", "*.wiki", "203.0.113.21", False),
 | 
					            ("A", "*.wiki", "203.0.113.21", False),
 | 
				
			||||||
            ("AAAA", "*.wiki", "2606:4700:4700::1111", False),
 | 
					            ("AAAA", "*.wiki", "2606:4700:4700::1111", False),
 | 
				
			||||||
            ("A", "*.b", "203.0.113.21", False),
 | 
					            ("A", "*.b", "203.0.113.21", False),
 | 
				
			||||||
            ("AAAA", "*.b", "2606:4700:4700::1111", False),
 | 
					            ("AAAA", "*.b", "2606:4700:4700::1111", False),
 | 
				
			||||||
            # now included because www.a.b.example.com promotes a.b.example.com as a parent
 | 
					            # www.a.b.example.com promotes a.b.example.com as a parent
 | 
				
			||||||
            ("A", "*.a.b", "203.0.113.21", False),
 | 
					            ("A", "*.a.b", "203.0.113.21", False),
 | 
				
			||||||
            ("AAAA", "*.a.b", "2606:4700:4700::1111", False),
 | 
					            ("AAAA", "*.a.b", "2606:4700:4700::1111", False),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user