Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions src/azure-cli/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
Release History
===============

Upcoming Release
++++++++++++++++

**App Service**

* `az webapp/functionapp config access-restriction add`: Allow adding multiple access-restriction rules with the same source IP, service tag, or subnet when the ``--http-header`` filters differ. The duplicate-rule check is now header-aware, matching the ARM API behavior. The IP comparison is also order-insensitive when multiple comma-separated CIDRs are provided in a single rule.

2.85.0
++++++

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,70 @@ def _validate_ip_address_existence(cmd, namespace):
scm_site = namespace.scm_site
configs = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'get_configuration', slot)
access_rules = configs.scm_ip_security_restrictions if scm_site else configs.ip_security_restrictions
ip_exists = [x.ip_address == namespace.ip_address for x in access_rules]
if True in ip_exists:
raise ArgumentUsageError('IP address: ' + namespace.ip_address + ' already exists. '
'Cannot add duplicate IP address values.')
new_headers = _normalize_http_headers(getattr(namespace, 'http_headers', None))
new_ip_set = _normalize_ip_address_list(namespace.ip_address)
for rule in access_rules or []:
if _normalize_ip_address_list(rule.ip_address) != new_ip_set:
continue
if _normalize_http_headers(rule.headers) == new_headers:
raise ArgumentUsageError(
"An access-restriction rule with IP address '{}' and the same HTTP header filter "
"already exists. Cannot add a duplicate rule. Use a different IP address or vary "
"the --http-header values to create an additional rule.".format(namespace.ip_address))
Comment thread
seligj95 marked this conversation as resolved.


def _normalize_ip_address_list(ip_address):
"""Return a frozenset of canonical CIDR strings parsed from a comma-separated input.

The CLI accepts up to 8 comma-separated CIDRs in a single rule's ``--ip-address``. ARM stores
them in the same comma-separated form on the rule's ``ip_address`` attribute. Two rules
represent the same source set regardless of the order in which the CIDRs appear, so duplicate
detection should compare unordered sets. Returns ``frozenset()`` for missing/empty input.
"""
if not ip_address:
return frozenset()
return frozenset(part.strip() for part in ip_address.split(',') if part.strip())


def _normalize_http_headers(headers):
"""Normalize an http-header collection for duplicate-rule comparison.

Accepts either the CLI input form (list of "name=value" strings, as supplied via --http-header)
or the SDK/ARM form (dict mapping header name to a list of values). Returns a dict whose keys
are lowercased header names and whose values are frozensets of value strings, so that order of
values within a single header (which ARM treats as a set) does not affect equality. None and
an empty collection are both normalized to an empty dict so that a rule with no headers
compares equal to itself regardless of representation.
"""
if not headers:
return {}
result = {}
if isinstance(headers, dict):
for header_name, values in headers.items():
if header_name is None:
continue
normalized_name = header_name.strip().lower()
if values is None:
continue
if isinstance(values, str):
values = [values]
value_set = frozenset(v for v in values if v)
if value_set:
result[normalized_name] = value_set
return result
# CLI input form: list of "name=value" strings.
for header_str in headers:
if header_str is None or '=' not in header_str:
continue
header_name, _, header_value = header_str.partition('=')
normalized_name = header_name.strip().lower()
normalized_value = header_value.strip()
if not normalized_name or not normalized_value:
continue
Comment thread
seligj95 marked this conversation as resolved.
existing = set(result.get(normalized_name, frozenset()))
existing.add(normalized_value)
result[normalized_name] = frozenset(existing)
return result


def validate_service_tag(cmd, namespace):
Expand Down Expand Up @@ -360,10 +420,16 @@ def _validate_service_tag_existence(cmd, namespace):
input_tag_value = namespace.service_tag.replace(' ', '')
configs = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'get_configuration', slot)
access_rules = configs.scm_ip_security_restrictions if scm_site else configs.ip_security_restrictions
for rule in access_rules:
if rule.ip_address and rule.ip_address.lower() == input_tag_value.lower():
raise ArgumentUsageError('Service Tag: ' + namespace.service_tag + ' already exists. '
'Cannot add duplicate Service Tag values.')
new_headers = _normalize_http_headers(getattr(namespace, 'http_headers', None))
for rule in access_rules or []:
if not rule.ip_address or rule.ip_address.lower() != input_tag_value.lower():
continue
if _normalize_http_headers(rule.headers) == new_headers:
raise ArgumentUsageError(
"A service-tag access-restriction rule with value '{}' and the same HTTP header "
"filter already exists. Cannot add a duplicate rule. Use a different service tag "
"or vary the --http-header values to create an additional rule.".format(
namespace.service_tag))


def validate_public_cloud(cmd):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from knack.log import get_logger

from ._appservice_utils import _generic_site_operation
from ._validators import _normalize_http_headers
from .custom import get_site_configs

logger = get_logger(__name__)
Expand Down Expand Up @@ -59,11 +60,18 @@ def add_webapp_access_restriction(
subnet_id = _validate_subnet(cmd.cli_ctx, subnet, vnet_name, vnet_rg)
if not ignore_missing_vnet_service_endpoint:
_ensure_subnet_service_endpoint(cmd.cli_ctx, subnet_id)
# check for duplicates
# check for duplicates (header-aware: same subnet + identical http-header filter)
for rule in list(access_rules):
if rule.vnet_subnet_resource_id and rule.vnet_subnet_resource_id.lower() == subnet_id.lower():
raise ArgumentUsageError('Service endpoint rule for: ' + subnet_id + ' already exists. '
'Cannot add duplicate service endpoint rules.')
if not rule.vnet_subnet_resource_id:
continue
if rule.vnet_subnet_resource_id.lower() != subnet_id.lower():
continue
if _normalize_http_headers(rule.headers) == _normalize_http_headers(http_headers):
raise ArgumentUsageError(
Comment thread
seligj95 marked this conversation as resolved.
"A service-endpoint access-restriction rule for subnet '{}' with the same "
"HTTP header filter already exists. Cannot add a duplicate rule. Use a "
"different subnet or vary the --http-header values to create an additional "
Comment thread
seligj95 marked this conversation as resolved.
Outdated
"rule.".format(subnet_id))
rule_instance = IpSecurityRestriction(
name=rule_name, vnet_subnet_resource_id=subnet_id,
priority=priority, action=action, tag='Default', description=description)
Expand Down
Loading
Loading