1# -------------------------------------------------------------------------------------------- 2# Copyright (c) Microsoft Corporation. All rights reserved. 3# Licensed under the MIT License. See License.txt in the project root for license information. 4# -------------------------------------------------------------------------------------------- 5 6from typing import (Optional, List) 7from azure.mgmt.cdn.models import (AFDEndpoint, HealthProbeRequestType, EnabledState, Route, LinkToDefaultDomain, 8 ResourceReference, AFDEndpointProtocols, HttpsRedirect, ForwardingProtocol, 9 QueryStringCachingBehavior, HealthProbeParameters, MatchProcessingBehavior, 10 AFDOrigin, AFDOriginGroup, SharedPrivateLinkResourceProperties, CompressionSettings, 11 LoadBalancingSettingsParameters, SecurityPolicyWebApplicationFirewallParameters, 12 SecurityPolicyWebApplicationFirewallAssociation, CustomerCertificateParameters, 13 AFDDomain, AFDDomainHttpsParameters, AfdCertificateType, AfdMinimumTlsVersion, 14 AFDEndpointUpdateParameters, SkuName, AfdPurgeParameters, Secret, 15 SecurityPolicy, ProfileUpdateParameters) 16 17from azure.mgmt.cdn.operations import (AFDOriginGroupsOperations, AFDOriginsOperations, AFDProfilesOperations, 18 SecretsOperations, AFDEndpointsOperations, RoutesOperations, RuleSetsOperations, 19 RulesOperations, SecurityPoliciesOperations, AFDCustomDomainsOperations, 20 ProfilesOperations) 21 22from azure.cli.core.commands.client_factory import get_subscription_id 23from azure.cli.core.util import (sdk_no_wait) 24from azure.cli.core.azclierror import (InvalidArgumentValueError) 25from azure.core.exceptions import (ResourceNotFoundError) 26 27from knack.log import get_logger 28from msrest.polling import LROPoller, NoPolling 29 30from .custom import _update_mapper 31 32logger = get_logger(__name__) 33 34 35def default_content_types(): 36 return ["application/eot", 37 "application/font", 38 "application/font-sfnt", 39 "application/javascript", 40 "application/json", 41 "application/opentype", 42 "application/otf", 43 "application/pkcs7-mime", 44 "application/truetype", 45 "application/ttf", 46 "application/vnd.ms-fontobject", 47 "application/xhtml+xml", 48 "application/xml", 49 "application/xml+rss", 50 "application/x-font-opentype", 51 "application/x-font-truetype", 52 "application/x-font-ttf", 53 "application/x-httpd-cgi", 54 "application/x-javascript", 55 "application/x-mpegurl", 56 "application/x-opentype", 57 "application/x-otf", 58 "application/x-perl", 59 "application/x-ttf", 60 "font/eot", 61 "font/ttf", 62 "font/otf", 63 "font/opentype", 64 "image/svg+xml", 65 "text/css", 66 "text/csv", 67 "text/html", 68 "text/javascript", 69 "text/js", 70 "text/plain", 71 "text/richtext", 72 "text/tab-separated-values", 73 "text/xml", 74 "text/x-script", 75 "text/x-component", 76 "text/x-java-source"] 77 78 79def create_afd_profile(client: ProfilesOperations, resource_group_name, profile_name, 80 sku: SkuName, 81 tags=None): 82 from azure.mgmt.cdn.models import (Profile, Sku) 83 84 # Force location to global 85 profile = Profile(location="global", sku=Sku(name=sku), tags=tags) 86 return client.begin_create(resource_group_name, profile_name, profile) 87 88 89def delete_afd_profile(client: ProfilesOperations, resource_group_name, profile_name): 90 profile = None 91 try: 92 profile = client.get(resource_group_name, profile_name) 93 except ResourceNotFoundError: 94 pass 95 96 if profile is None or profile.sku.name not in (SkuName.premium_azure_front_door, 97 SkuName.standard_azure_front_door): 98 def get_long_running_output(_): 99 return None 100 101 logger.warning("Unexpected SKU type, only Standard_AzureFrontDoor and Premium_AzureFrontDoor are supported.") 102 return LROPoller(client, None, get_long_running_output, NoPolling()) 103 104 return client.begin_delete(resource_group_name, profile_name) 105 106 107def update_afd_profile(client: ProfilesOperations, resource_group_name, profile_name, tags): 108 profile = client.get(resource_group_name, profile_name) 109 if profile.sku.name not in (SkuName.premium_azure_front_door, SkuName.standard_azure_front_door): 110 logger.warning('Unexpected SKU type, only Standard_AzureFrontDoor and Premium_AzureFrontDoor are supported') 111 raise ResourceNotFoundError("Operation returned an invalid status code 'Not Found'") 112 113 return client.begin_update(resource_group_name, profile_name, ProfileUpdateParameters(tags=tags)) 114 115 116def list_afd_profiles(client: ProfilesOperations, resource_group_name=None): 117 profile_list = client.list_by_resource_group(resource_group_name=resource_group_name) \ 118 if resource_group_name else client.list() 119 120 profile_list = [profile for profile in profile_list if profile.sku.name in 121 (SkuName.premium_azure_front_door, SkuName.standard_azure_front_door)] 122 123 return list(profile_list) 124 125 126def get_afd_profile(client: ProfilesOperations, resource_group_name, profile_name): 127 profile = client.get(resource_group_name, profile_name) 128 if profile.sku.name not in (SkuName.premium_azure_front_door, SkuName.standard_azure_front_door): 129 # Workaround to make the behavior consist with true "Not Found" 130 logger.warning('Unexpected SKU type, only Standard_AzureFrontDoor and Premium_AzureFrontDoor are supported') 131 raise ResourceNotFoundError("Operation returned an invalid status code 'Not Found'") 132 133 return profile 134 135 136def list_resource_usage(client: AFDProfilesOperations, resource_group_name, profile_name): 137 return client.list_resource_usage(resource_group_name, profile_name) 138 139 140def create_afd_endpoint(client: AFDEndpointsOperations, resource_group_name, profile_name, endpoint_name, 141 origin_response_timeout_seconds, 142 enabled_state, tags=None, no_wait=None): 143 144 # Force location to global 145 endpoint = AFDEndpoint(location="global", 146 origin_response_timeout_seconds=origin_response_timeout_seconds, 147 enabled_state=enabled_state, 148 tags=tags) 149 150 return sdk_no_wait(no_wait, client.begin_create, resource_group_name, profile_name, endpoint_name, endpoint) 151 152 153def purge_afd_endpoint_content(client: AFDEndpointsOperations, resource_group_name, profile_name, endpoint_name, 154 content_paths, domains=None, no_wait=None): 155 endpoint = AfdPurgeParameters(content_paths=content_paths, 156 domains=domains) 157 158 return sdk_no_wait(no_wait, client.begin_purge_content, resource_group_name, profile_name, endpoint_name, endpoint) 159 160 161def update_afd_endpoint(client: AFDEndpointsOperations, resource_group_name, profile_name, endpoint_name, 162 origin_response_timeout_seconds=None, enabled_state=None, tags=None): 163 update_properties = AFDEndpointUpdateParameters( 164 origin_response_timeout_seconds=origin_response_timeout_seconds, 165 enabled_state=enabled_state, 166 tags=tags 167 ) 168 169 return client.begin_update(resource_group_name, profile_name, endpoint_name, update_properties) 170 171 172def create_afd_origin_group(client: AFDOriginGroupsOperations, 173 resource_group_name: str, 174 profile_name: str, 175 origin_group_name: str, 176 load_balancing_sample_size: int, 177 load_balancing_successful_samples_required: int, 178 load_balancing_additional_latency_in_milliseconds: int, 179 probe_request_type: HealthProbeRequestType, 180 probe_protocol: str, 181 probe_path: str, 182 probe_interval_in_seconds: int = 240): 183 184 # Add response error detection support once RP support it. 185 health_probe_parameters = HealthProbeParameters(probe_path=probe_path, 186 probe_request_type=probe_request_type, 187 probe_protocol=probe_protocol, 188 probe_interval_in_seconds=probe_interval_in_seconds) 189 190 load_balancing_settings_parameters = LoadBalancingSettingsParameters( 191 sample_size=load_balancing_sample_size, 192 successful_samples_required=load_balancing_successful_samples_required, 193 additional_latency_in_milliseconds=load_balancing_additional_latency_in_milliseconds) 194 195 afd_origin_group = AFDOriginGroup(load_balancing_settings=load_balancing_settings_parameters, 196 health_probe_settings=health_probe_parameters) 197 198 return client.begin_create(resource_group_name, 199 profile_name, 200 origin_group_name, 201 afd_origin_group).result() 202 203 204def update_afd_origin_group(client: AFDOriginGroupsOperations, 205 resource_group_name: str, 206 profile_name: str, 207 origin_group_name: str, 208 load_balancing_sample_size: int = None, 209 load_balancing_successful_samples_required: int = None, 210 load_balancing_additional_latency_in_milliseconds: int = None, 211 probe_request_type: HealthProbeRequestType = None, 212 probe_protocol: str = None, 213 probe_path: str = None, 214 probe_interval_in_seconds: int = None): 215 216 # Move these to the parameters list once support is added in RP. 217 existing = client.get(resource_group_name, profile_name, origin_group_name) 218 219 health_probe_parameters = HealthProbeParameters(probe_path=probe_path, 220 probe_request_type=probe_request_type, 221 probe_protocol=probe_protocol, 222 probe_interval_in_seconds=probe_interval_in_seconds) 223 224 _update_mapper(existing.health_probe_settings, 225 health_probe_parameters, 226 ["probe_path", "probe_request_type", "probe_protocol", "probe_interval_in_seconds"]) 227 228 load_balancing_settings_parameters = LoadBalancingSettingsParameters( 229 sample_size=load_balancing_sample_size, 230 successful_samples_required=load_balancing_successful_samples_required, 231 additional_latency_in_milliseconds=load_balancing_additional_latency_in_milliseconds) 232 _update_mapper(existing.load_balancing_settings, 233 load_balancing_settings_parameters, 234 ["sample_size", "successful_samples_required", "additional_latency_in_milliseconds"]) 235 236 afd_origin_group = AFDOriginGroup(load_balancing_settings=load_balancing_settings_parameters, 237 health_probe_settings=health_probe_parameters) 238 239 return client.begin_create(resource_group_name, 240 profile_name, 241 origin_group_name, 242 afd_origin_group).result() 243 244 245def create_afd_origin(client: AFDOriginsOperations, 246 resource_group_name: str, 247 profile_name: str, 248 origin_group_name: str, 249 origin_name: str, 250 host_name: str, 251 enabled_state: EnabledState, 252 enable_private_link: bool = None, 253 private_link_resource: str = None, 254 private_link_location: str = None, 255 private_link_sub_resource_type: str = None, 256 private_link_request_message: str = None, 257 http_port: int = 80, 258 https_port: int = 443, 259 origin_host_header: Optional[str] = None, 260 priority: int = 1, 261 weight: int = 1000): 262 263 shared_private_link_resource = None 264 if enable_private_link: 265 shared_private_link_resource = SharedPrivateLinkResourceProperties( 266 private_link=ResourceReference(id=private_link_resource), 267 private_link_location=private_link_location, 268 group_id=private_link_sub_resource_type, 269 request_message=private_link_request_message) 270 271 return client.begin_create(resource_group_name, 272 profile_name, 273 origin_group_name, 274 origin_name, 275 AFDOrigin( 276 host_name=host_name, 277 http_port=http_port, 278 https_port=https_port, 279 origin_host_header=origin_host_header, 280 priority=priority, 281 weight=weight, 282 shared_private_link_resource=shared_private_link_resource, 283 enabled_state=enabled_state)) 284 285 286def update_afd_origin(client: AFDOriginsOperations, 287 resource_group_name: str, 288 profile_name: str, 289 origin_group_name: str, 290 origin_name: str, 291 host_name: str = None, 292 enabled_state: EnabledState = None, 293 http_port: int = None, 294 https_port: int = None, 295 origin_host_header: Optional[str] = None, 296 priority: int = None, 297 weight: int = None, 298 enable_private_link: bool = None, 299 private_link_resource: str = None, 300 private_link_location: str = None, 301 private_link_sub_resource_type: str = None, 302 private_link_request_message: str = None): 303 304 existing = client.get(resource_group_name, profile_name, origin_group_name, origin_name) 305 origin = AFDOrigin( 306 host_name=host_name, 307 http_port=http_port, 308 https_port=https_port, 309 origin_host_header=origin_host_header, 310 priority=priority, 311 weight=weight, 312 enabled_state=enabled_state) 313 314 _update_mapper( 315 existing, 316 origin, 317 ["host_name", "http_port", "https_port", "origin_host_header", "priority", "weight", "enabled_state"]) 318 319 if enable_private_link is not None and not enable_private_link: 320 origin.shared_private_link_resource = None 321 elif (private_link_resource is not None or 322 private_link_location is not None or 323 private_link_sub_resource_type is not None or 324 private_link_request_message is not None): 325 shared_private_link_resource = SharedPrivateLinkResourceProperties( 326 private_link=ResourceReference(id=private_link_resource) if private_link_resource is not None else None, 327 private_link_location=private_link_location, 328 group_id=private_link_sub_resource_type, 329 request_message=private_link_request_message) 330 331 if existing.shared_private_link_resource is not None: 332 existing_shared_private_link_resource = SharedPrivateLinkResourceProperties( 333 private_link=ResourceReference(id=existing.shared_private_link_resource["privateLink"]['id']), 334 private_link_location=existing.shared_private_link_resource["privateLinkLocation"], 335 group_id=existing.shared_private_link_resource["groupId"], 336 request_message=existing.shared_private_link_resource["requestMessage"]) 337 338 _update_mapper( 339 existing_shared_private_link_resource, 340 shared_private_link_resource, 341 ["private_link", "private_link_location", "group_id", "request_message"]) 342 343 origin.shared_private_link_resource = shared_private_link_resource 344 else: 345 origin.shared_private_link_resource = existing.shared_private_link_resource 346 347 # client.update does not allow unset field 348 return client.begin_create(resource_group_name, 349 profile_name, 350 origin_group_name, 351 origin_name, 352 origin) 353 354 355def create_afd_route(cmd, 356 client: RoutesOperations, 357 resource_group_name: str, 358 profile_name: str, 359 endpoint_name: str, 360 route_name: str, 361 https_redirect: HttpsRedirect, 362 supported_protocols: List[AFDEndpointProtocols], 363 origin_group: str, 364 forwarding_protocol: ForwardingProtocol, 365 link_to_default_domain: LinkToDefaultDomain = None, 366 is_compression_enabled: bool = False, 367 content_types_to_compress: List[str] = None, 368 query_string_caching_behavior: QueryStringCachingBehavior = None, 369 custom_domains: List[str] = None, 370 origin_path: Optional[str] = None, 371 patterns_to_match: List[str] = None, 372 rule_sets: List[str] = None): 373 374 subscription_id = get_subscription_id(cmd.cli_ctx) 375 formatted_custom_domains = [] 376 if custom_domains is not None: 377 for custom_domain in custom_domains: 378 if '/customdomains/' not in custom_domain.lower(): 379 custom_domain = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \ 380 f'/providers/Microsoft.Cdn/profiles/{profile_name}/customDomains/{custom_domain}' 381 382 # If the origin is not an ID, assume it's a name and format it as an ID. 383 formatted_custom_domains.append(ResourceReference(id=custom_domain)) 384 385 if '/origingroups/' not in origin_group.lower(): 386 origin_group = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \ 387 f'/providers/Microsoft.Cdn/profiles/{profile_name}/originGroups/{origin_group}' 388 389 compression_settings = CompressionSettings( 390 content_types_to_compress=content_types_to_compress, 391 is_compression_enabled=is_compression_enabled 392 ) 393 394 if is_compression_enabled and content_types_to_compress is None: 395 compression_settings.content_types_to_compress = default_content_types() 396 397 if not compression_settings.is_compression_enabled: 398 compression_settings.content_types_to_compress = [] 399 400 formatted_rule_sets = [] 401 if rule_sets is not None: 402 for rule_set in rule_sets: 403 if '/rulesets/' not in rule_set.lower(): 404 rule_set = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \ 405 f'/providers/Microsoft.Cdn/profiles/{profile_name}/ruleSets/{rule_set}' 406 407 formatted_rule_sets.append(ResourceReference(id=rule_set)) 408 409 return client.begin_create(resource_group_name, 410 profile_name, 411 endpoint_name, 412 route_name, 413 Route( 414 custom_domains=formatted_custom_domains, 415 origin_path=origin_path, 416 patterns_to_match=patterns_to_match if patterns_to_match is not None else ['/*'], 417 supported_protocols=supported_protocols, 418 https_redirect=https_redirect, 419 origin_group=ResourceReference(id=origin_group), 420 forwarding_protocol=forwarding_protocol, 421 rule_sets=formatted_rule_sets, 422 query_string_caching_behavior=query_string_caching_behavior, 423 compression_settings=compression_settings, 424 link_to_default_domain=LinkToDefaultDomain.enabled if link_to_default_domain else 425 LinkToDefaultDomain.disabled)) 426 427 428def update_afd_route(cmd, 429 client: RoutesOperations, 430 resource_group_name: str, 431 profile_name: str, 432 endpoint_name: str, 433 route_name: str, 434 https_redirect: HttpsRedirect = None, 435 supported_protocols: List[AFDEndpointProtocols] = None, 436 origin_group: str = None, 437 forwarding_protocol: ForwardingProtocol = None, 438 link_to_default_domain: LinkToDefaultDomain = None, 439 is_compression_enabled: bool = None, 440 content_types_to_compress: List[str] = None, 441 query_string_caching_behavior: QueryStringCachingBehavior = None, 442 custom_domains: List[str] = None, 443 origin_path: Optional[str] = None, 444 patterns_to_match: List[str] = None, 445 rule_sets: List[str] = None): 446 447 existing = client.get(resource_group_name, profile_name, endpoint_name, route_name) 448 route = Route( 449 origin_path=origin_path, 450 origin_group=existing.origin_group, 451 patterns_to_match=patterns_to_match, 452 supported_protocols=supported_protocols, 453 https_redirect=https_redirect, 454 forwarding_protocol=forwarding_protocol, 455 query_string_caching_behavior=query_string_caching_behavior, 456 link_to_default_domain=link_to_default_domain) 457 458 subscription_id = get_subscription_id(cmd.cli_ctx) 459 if custom_domains is not None: 460 formatted_custom_domains = [] 461 for custom_domain in custom_domains: 462 if '/customdomains/' not in custom_domain.lower(): 463 custom_domain = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \ 464 f'/providers/Microsoft.Cdn/profiles/{profile_name}/customDomains/{custom_domain}' 465 466 # If the origin is not an ID, assume it's a name and format it as an ID. 467 formatted_custom_domains.append(ResourceReference(id=custom_domain)) 468 469 route.custom_domains = formatted_custom_domains 470 471 if origin_group is not None: 472 if '/origingroups/' not in origin_group.lower(): 473 origin_group = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \ 474 f'/providers/Microsoft.Cdn/profiles/{profile_name}/originGroups/{origin_group}' 475 476 route.origin_group = origin_group 477 478 if rule_sets is not None: 479 formatted_rule_sets = [] 480 for rule_set in rule_sets: 481 if '/rulesets/' not in rule_set.lower(): 482 rule_set = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \ 483 f'/providers/Microsoft.Cdn/profiles/{profile_name}/ruleSets/{rule_set}' 484 485 # If the origin is not an ID, assume it's a name and format it as an ID. 486 formatted_rule_sets.append(ResourceReference(id=rule_set)) 487 488 route.rule_sets = formatted_rule_sets 489 490 _update_mapper(existing, route, 491 ["custom_domains", "origin_path", "patterns_to_match", 492 "supported_protocols", "https_redirect", "origin_group", 493 "forwarding_protocol", "rule_sets", "query_string_caching_behavior", 494 "link_to_default_domain", "compression_settings"]) 495 496 if is_compression_enabled: 497 if existing.compression_settings is None: 498 route.compression_settings = CompressionSettings( 499 content_types_to_compress=content_types_to_compress if content_types_to_compress is not None else 500 default_content_types(), 501 is_compression_enabled=is_compression_enabled 502 ) 503 else: 504 route.compression_settings = CompressionSettings( 505 content_types_to_compress=content_types_to_compress if content_types_to_compress is not None else 506 existing.compression_settings.content_types_to_compress, 507 is_compression_enabled=is_compression_enabled 508 ) 509 elif is_compression_enabled is None: 510 if content_types_to_compress is not None and existing.compression_settings is not None: 511 route.compression_settings = CompressionSettings( 512 content_types_to_compress=content_types_to_compress, 513 is_compression_enabled=existing.compression_settings["isCompressionEnabled"] 514 ) 515 else: 516 route.compression_settings = CompressionSettings( 517 content_types_to_compress=[], 518 is_compression_enabled=False 519 ) 520 521 return client.begin_create(resource_group_name, 522 profile_name, 523 endpoint_name, 524 route_name, 525 route) 526 527 528def create_afd_rule_set(client: RuleSetsOperations, 529 resource_group_name: str, 530 profile_name: str, 531 rule_set_name: str): 532 533 return client.begin_create(resource_group_name, profile_name, rule_set_name) 534 535 536# pylint: disable=too-many-locals 537def create_afd_rule(client: RulesOperations, resource_group_name, profile_name, rule_set_name, 538 order, rule_name, action_name, match_variable=None, operator=None, 539 match_values=None, selector=None, negate_condition=None, transform=None, 540 cache_behavior=None, cache_duration=None, header_action=None, 541 header_name=None, header_value=None, query_string_behavior=None, query_parameters=None, 542 redirect_type=None, redirect_protocol=None, custom_hostname=None, custom_path=None, 543 custom_querystring=None, custom_fragment=None, source_pattern=None, 544 destination=None, preserve_unmatched_path=None, 545 match_processing_behavior: MatchProcessingBehavior = None): 546 from azure.mgmt.cdn.models import Rule 547 from .custom import create_condition 548 from .custom import create_action 549 550 conditions = [] 551 condition = create_condition(match_variable, operator, match_values, selector, negate_condition, transform) 552 if condition is not None: 553 conditions.append(condition) 554 555 actions = [] 556 action = create_action(action_name, cache_behavior, cache_duration, header_action, header_name, 557 header_value, query_string_behavior, query_parameters, redirect_type, 558 redirect_protocol, custom_hostname, custom_path, custom_querystring, 559 custom_fragment, source_pattern, destination, preserve_unmatched_path) 560 if action is not None: 561 actions.append(action) 562 563 rule = Rule( 564 name=rule_name, 565 order=order, 566 conditions=conditions, 567 actions=actions, 568 match_processing_behavior=match_processing_behavior 569 ) 570 571 return client.begin_create(resource_group_name, 572 profile_name, 573 rule_set_name, 574 rule_name, 575 rule=rule) 576 577 578def add_afd_rule_condition(client: RulesOperations, resource_group_name, profile_name, rule_set_name, 579 rule_name, match_variable, operator, match_values=None, selector=None, 580 negate_condition=None, transform=None): 581 from .custom import create_condition 582 583 existing_rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name) 584 condition = create_condition(match_variable, operator, match_values, selector, negate_condition, transform) 585 existing_rule.conditions.append(condition) 586 587 return client.begin_create(resource_group_name, 588 profile_name, 589 rule_set_name, 590 rule_name, 591 rule=existing_rule) 592 593 594def add_afd_rule_action(client: RulesOperations, resource_group_name, profile_name, rule_set_name, 595 rule_name, action_name, cache_behavior=None, cache_duration=None, 596 header_action=None, header_name=None, header_value=None, query_string_behavior=None, 597 query_parameters=None, redirect_type=None, redirect_protocol=None, custom_hostname=None, 598 custom_path=None, custom_querystring=None, custom_fragment=None, source_pattern=None, 599 destination=None, preserve_unmatched_path=None): 600 from .custom import create_action 601 602 existing_rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name) 603 action = create_action(action_name, cache_behavior, cache_duration, header_action, header_name, 604 header_value, query_string_behavior, query_parameters, redirect_type, 605 redirect_protocol, custom_hostname, custom_path, custom_querystring, 606 custom_fragment, source_pattern, destination, preserve_unmatched_path) 607 608 existing_rule.actions.append(action) 609 return client.begin_create(resource_group_name, 610 profile_name, 611 rule_set_name, 612 rule_name, 613 rule=existing_rule) 614 615 616def remove_afd_rule_condition(client: RulesOperations, resource_group_name, profile_name, 617 rule_set_name, rule_name, index): 618 existing_rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name) 619 if len(existing_rule.conditions) > 1 and index < len(existing_rule.conditions): 620 existing_rule.conditions.pop(index) 621 else: 622 logger.warning("Invalid condition index found. This command will be skipped. Please check the rule.") 623 624 return client.begin_create(resource_group_name, 625 profile_name, 626 rule_set_name, 627 rule_name, 628 rule=existing_rule) 629 630 631def remove_afd_rule_action(client: RulesOperations, resource_group_name, profile_name, rule_set_name, rule_name, index): 632 existing_rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name) 633 if len(existing_rule.actions) > 1 and index < len(existing_rule.actions): 634 existing_rule.actions.pop(index) 635 else: 636 logger.warning("Invalid condition index found. This command will be skipped. Please check the rule.") 637 638 return client.begin_create(resource_group_name, 639 profile_name, 640 rule_set_name, 641 rule_name, 642 rule=existing_rule) 643 644 645def list_afd_rule_condition(client: RulesOperations, resource_group_name, 646 profile_name, rule_set_name, 647 rule_name): 648 rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name) 649 return rule.conditions 650 651 652def list_afd_rule_action(client: RulesOperations, resource_group_name, 653 profile_name, rule_set_name, 654 rule_name): 655 rule = client.get(resource_group_name, profile_name, rule_set_name, rule_name) 656 return rule.actions 657 658 659def create_afd_security_policy(client: SecurityPoliciesOperations, 660 resource_group_name, 661 profile_name, 662 security_policy_name, 663 domains: List[str], 664 waf_policy: str): 665 666 if any(("/afdendpoints/" not in domain.lower() and 667 "/customdomains/" not in domain.lower()) for domain in domains): 668 raise InvalidArgumentValueError('Domain should either be endpoint ID or custom domain ID.') 669 670 if "/frontdoorwebapplicationfirewallpolicies/" not in waf_policy.lower(): 671 raise InvalidArgumentValueError('waf_policy should be valid Front Door WAF policy ID.') 672 673 # Add patterns and multiple domains support in the feature 674 parameters = SecurityPolicyWebApplicationFirewallParameters( 675 waf_policy=ResourceReference(id=waf_policy), 676 associations=[SecurityPolicyWebApplicationFirewallAssociation( 677 domains=[ResourceReference(id=domain) for domain in domains], 678 patterns_to_match=["/*"])]) 679 680 return client.begin_create(resource_group_name, 681 profile_name, 682 security_policy_name, 683 SecurityPolicy(parameters=parameters)) 684 685 686def update_afd_security_policy(client: SecurityPoliciesOperations, 687 resource_group_name, 688 profile_name, 689 security_policy_name, 690 domains: List[str] = None, 691 waf_policy: str = None): 692 693 if domains is not None and any(("/afdendpoints/" not in domain.lower() and 694 "/customdomains/" not in domain.lower()) for domain in domains): 695 raise InvalidArgumentValueError('Domain should be either endpoint ID or custom domain ID.') 696 697 if waf_policy is not None and "/frontdoorwebapplicationfirewallpolicies/" not in waf_policy: 698 raise InvalidArgumentValueError('waf_policy should be Front Door WAF policy ID.') 699 700 existing = client.get(resource_group_name, profile_name, security_policy_name) 701 702 # Add patterns and multiple domains support in the future 703 parameters = SecurityPolicyWebApplicationFirewallParameters( 704 waf_policy=ResourceReference(id=waf_policy) if waf_policy is not None else existing.parameters.waf_policy, 705 associations=[SecurityPolicyWebApplicationFirewallAssociation( 706 domains=[ResourceReference(id=domain) for domain in domains], 707 patterns_to_match=["/*"])] if domains is not None else existing.parameters.associations) 708 709 return client.begin_create(resource_group_name, 710 profile_name, 711 security_policy_name, 712 SecurityPolicy(parameters=parameters)) 713 714 715def create_afd_secret(client: SecretsOperations, 716 resource_group_name, 717 profile_name, 718 secret_name, 719 secret_source, 720 secret_version: str = None, 721 use_latest_version: bool = None): 722 723 if "/certificates/" not in secret_source.lower(): 724 raise InvalidArgumentValueError('secret_source should be valid Azure key vault certificate ID.') 725 726 if secret_version is None and not use_latest_version: 727 raise InvalidArgumentValueError('Either specify secret_version or enable use_latest_version.') 728 729 # Only support CustomerCertificate for the moment 730 parameters = None 731 if use_latest_version: 732 parameters = CustomerCertificateParameters( 733 secret_source=ResourceReference(id=secret_source), 734 secret_version=None, 735 use_latest_version=True 736 ) 737 else: 738 parameters = CustomerCertificateParameters( 739 secret_source=ResourceReference(id=f'{secret_source}/{secret_version}'), 740 secret_version=secret_version, 741 use_latest_version=False 742 ) 743 744 return client.begin_create(resource_group_name, 745 profile_name, 746 secret_name, 747 Secret(parameters=parameters)) 748 749 750def update_afd_secret(client: SecretsOperations, 751 resource_group_name, 752 profile_name, 753 secret_name, 754 secret_source: str = None, 755 secret_version: str = None, 756 use_latest_version: bool = None): 757 existing = client.get(resource_group_name, profile_name, secret_name) 758 759 secret_source_id = secret_source if secret_source is not None else existing.parameters.secret_source.id 760 if existing.parameters.secret_version is not None and existing.parameters.secret_version in secret_source_id: 761 version_start = secret_source_id.lower().rindex(f'/{existing.parameters.secret_version}') 762 secret_source_id = secret_source_id[0:version_start] 763 764 secret_version = (secret_version if secret_version is not None 765 else existing.parameters.secret_version) 766 767 use_latest_version = (use_latest_version if use_latest_version is not None 768 else existing.parameters.use_latest_version) 769 770 return create_afd_secret(client, 771 resource_group_name, 772 profile_name, 773 secret_name, 774 secret_source_id, 775 secret_version, 776 use_latest_version) 777 778 779def create_afd_custom_domain(cmd, 780 client: AFDCustomDomainsOperations, 781 resource_group_name: str, 782 profile_name: str, 783 custom_domain_name: str, 784 host_name: str, 785 certificate_type: AfdCertificateType, 786 minimum_tls_version: AfdMinimumTlsVersion, 787 azure_dns_zone: str = None, 788 secret: str = None, 789 no_wait: bool = None): 790 791 if azure_dns_zone is not None and "/dnszones/" not in azure_dns_zone.lower(): 792 raise InvalidArgumentValueError('azure_dns_zone should be valid Azure dns zone ID.') 793 794 subscription_id = get_subscription_id(cmd.cli_ctx) 795 if secret is not None and "/secrets/" not in secret.lower(): 796 secret = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \ 797 f'/providers/Microsoft.Cdn/profiles/{profile_name}/secrets/{secret}' 798 799 tls_settings = AFDDomainHttpsParameters(certificate_type=certificate_type, 800 minimum_tls_version=minimum_tls_version, 801 secret=ResourceReference(id=secret) if secret is not None else None) 802 803 afd_domain = AFDDomain(host_name=host_name, 804 tls_settings=tls_settings, 805 azure_dns_zone=ResourceReference(id=azure_dns_zone) if azure_dns_zone is not None else None) 806 807 return sdk_no_wait(no_wait, client.begin_create, resource_group_name, profile_name, custom_domain_name, afd_domain) 808 809 810def update_afd_custom_domain(cmd, 811 client: AFDCustomDomainsOperations, 812 resource_group_name: str, 813 profile_name: str, 814 custom_domain_name: str, 815 certificate_type: AfdCertificateType = None, 816 minimum_tls_version: AfdMinimumTlsVersion = None, 817 azure_dns_zone: str = None, 818 secret: str = None): 819 820 if azure_dns_zone is not None and "/dnszones/" not in azure_dns_zone.lower(): 821 raise InvalidArgumentValueError('azure_dns_zone should be valid Azure dns zone ID.') 822 823 subscription_id = get_subscription_id(cmd.cli_ctx) 824 if secret is not None and "/secrets/" not in secret.lower(): 825 secret = f'/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}' \ 826 f'/providers/Microsoft.Cdn/profiles/{profile_name}/secrets/{secret}' 827 828 existing = client.get(resource_group_name, profile_name, custom_domain_name) 829 830 tls_settings = AFDDomainHttpsParameters(certificate_type=certificate_type, 831 minimum_tls_version=minimum_tls_version, 832 secret=ResourceReference(id=secret) if secret is not None else None) 833 834 _update_mapper(existing.tls_settings, tls_settings, ["certificate_type", "minimum_tls_version", "secret"]) 835 836 if certificate_type == AfdCertificateType.managed_certificate: 837 tls_settings.secret = None 838 839 afd_domain = AFDDomain( 840 host_name=existing.host_name, 841 tls_settings=tls_settings, 842 azure_dns_zone=ResourceReference(id=azure_dns_zone) if azure_dns_zone is not None else existing.azure_dns_zone) 843 844 return client.begin_create(resource_group_name, 845 profile_name, 846 custom_domain_name, 847 afd_domain).result() 848 849# endregion 850