126 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			126 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 5613937623f0037a54490b22c60f7eb1aa52cf4e Mon Sep 17 00:00:00 2001
 | |
| From: James Chapman <jachapma@redhat.com>
 | |
| Date: Wed, 25 Jun 2025 14:11:05 +0000
 | |
| Subject: [PATCH] =?UTF-8?q?Issue=206825=20-=20RootDN=20Access=20Control=20?=
 | |
|  =?UTF-8?q?Plugin=20with=20wildcards=20for=20IP=20addre=E2=80=A6=20(#6826)?=
 | |
| MIME-Version: 1.0
 | |
| Content-Type: text/plain; charset=UTF-8
 | |
| Content-Transfer-Encoding: 8bit
 | |
| 
 | |
| Bug description:
 | |
| RootDN Access Control Plugin with wildcards for IP addresses fails withi
 | |
| an error "Invalid IP address"
 | |
| 
 | |
| socket.inet_aton() validates IPv4 IP addresses and does not support wildcards.
 | |
| 
 | |
| Fix description:
 | |
| Add a regex pattern to match wildcard IP addresses, check each octet is
 | |
| between 0-255
 | |
| 
 | |
| Fixes: https://github.com/389ds/389-ds-base/issues/6825
 | |
| 
 | |
| Reviewed by: @droideck (Thank you)
 | |
| ---
 | |
|  .../lib389/cli_conf/plugins/rootdn_ac.py      | 16 +++-----
 | |
|  src/lib389/lib389/utils.py                    | 40 +++++++++++++++++++
 | |
|  2 files changed, 45 insertions(+), 11 deletions(-)
 | |
| 
 | |
| diff --git a/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py b/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py
 | |
| index 65486fff8..1456f5ebe 100644
 | |
| --- a/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py
 | |
| +++ b/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py
 | |
| @@ -8,7 +8,7 @@
 | |
|  
 | |
|  import socket
 | |
|  from lib389.plugins import RootDNAccessControlPlugin
 | |
| -from lib389.utils import is_valid_hostname
 | |
| +from lib389.utils import is_valid_hostname, is_valid_ip
 | |
|  from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit
 | |
|  from lib389.cli_base import CustomHelpFormatter
 | |
|  
 | |
| @@ -62,19 +62,13 @@ def validate_args(args):
 | |
|  
 | |
|      if args.allow_ip is not None:
 | |
|          for ip in args.allow_ip:
 | |
| -            if ip != "delete":
 | |
| -                try:
 | |
| -                    socket.inet_aton(ip)
 | |
| -                except socket.error:
 | |
| -                    raise ValueError(f"Invalid IP address ({ip}) for '--allow-ip'")
 | |
| +            if ip != "delete" and not is_valid_ip(ip):
 | |
| +                raise ValueError(f"Invalid IP address ({ip}) for '--allow-ip'")
 | |
|  
 | |
|      if args.deny_ip is not None and args.deny_ip != "delete":
 | |
|          for ip in args.deny_ip:
 | |
| -            if ip != "delete":
 | |
| -                try:
 | |
| -                    socket.inet_aton(ip)
 | |
| -                except socket.error:
 | |
| -                    raise ValueError(f"Invalid IP address ({ip}) for '--deny-ip'")
 | |
| +            if ip != "delete" and not is_valid_ip(ip):
 | |
| +                raise ValueError(f"Invalid IP address ({ip}) for '--deny-ip'")
 | |
|  
 | |
|      if args.allow_host is not None:
 | |
|          for hostname in args.allow_host:
 | |
| diff --git a/src/lib389/lib389/utils.py b/src/lib389/lib389/utils.py
 | |
| index afc282e94..3937fc1a8 100644
 | |
| --- a/src/lib389/lib389/utils.py
 | |
| +++ b/src/lib389/lib389/utils.py
 | |
| @@ -31,6 +31,7 @@ import logging
 | |
|  import shutil
 | |
|  import ldap
 | |
|  import socket
 | |
| +import ipaddress
 | |
|  import time
 | |
|  import stat
 | |
|  from datetime import (datetime, timedelta)
 | |
| @@ -1707,6 +1708,45 @@ def is_valid_hostname(hostname):
 | |
|      allowed = re.compile(r"(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
 | |
|      return all(allowed.match(x) for x in hostname.split("."))
 | |
|  
 | |
| +def is_valid_ip(ip):
 | |
| +    """ Validate an IPv4 or IPv6 address, including asterisks for wildcards. """
 | |
| +    if '*' in ip and '.' in ip:
 | |
| +        ipv4_pattern = r'^(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)$'
 | |
| +        if re.match(ipv4_pattern, ip):
 | |
| +            octets = ip.split('.')
 | |
| +            for octet in octets:
 | |
| +                if octet != '*':
 | |
| +                    try:
 | |
| +                        val = int(octet, 10)
 | |
| +                        if not (0 <= val <= 255):
 | |
| +                            return False
 | |
| +                    except ValueError:
 | |
| +                        return False
 | |
| +            return True
 | |
| +        else:
 | |
| +            return False
 | |
| +
 | |
| +    if '*' in ip and ':' in ip:
 | |
| +        ipv6_pattern = r'^([0-9a-fA-F]{1,4}|\*)(:([0-9a-fA-F]{1,4}|\*)){0,7}$'
 | |
| +        if re.match(ipv6_pattern, ip):
 | |
| +            octets = ip.split(':')
 | |
| +            for octet in octets:
 | |
| +                if octet != '*':
 | |
| +                    try:
 | |
| +                        val = int(octet, 16)
 | |
| +                        if not (0 <= val <= 0xFFFF):
 | |
| +                            return False
 | |
| +                    except ValueError:
 | |
| +                        return False
 | |
| +            return True
 | |
| +        else:
 | |
| +            return False
 | |
| +
 | |
| +    try:
 | |
| +        ipaddress.ip_address(ip)
 | |
| +        return True
 | |
| +    except ValueError:
 | |
| +        return False
 | |
|  
 | |
|  def parse_size(size):
 | |
|      """
 | |
| -- 
 | |
| 2.49.0
 | |
| 
 |