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

Support CORS policy and mTLS for CLI #6420

Merged
merged 10 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
44 changes: 44 additions & 0 deletions src/containerapp/azext_containerapp/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,50 @@
az containerapp ingress sticky-sessions show -n MyContainerapp -g MyResourceGroup
"""

helps['containerapp ingress cors'] = """
type: group
short-summary: Commands to set CORS policy for a container app.
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved
"""

helps['containerapp ingress cors enable'] = """
type: command
short-summary: Commands to enable CORS policy for a container app.
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved
examples:
- name: Set allowed origins and allowed methods for a container app.
text: |
az containerapp ingress cors enable -n MyContainerapp -g MyResourceGroup --allowed-origins http://www.contoso.com https://www.contoso.com --allowed-methods GET POST
- name: Set allowed origins, allowed methods and allowed headers for a container app.
text: |
az containerapp ingress cors enable -n MyContainerapp -g MyResourceGroup --allowed-origins * --allowed-methods * --allowed-headers header1 header2
"""

helps['containerapp ingress cors disable'] = """
type: command
short-summary: Commands to disable CORS policy for a container app.
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved
examples:
- name: Disable CORS policy for a container app.
text: |
az containerapp ingress cors disable -n MyContainerapp -g MyResourceGroup
"""

helps['containerapp ingress cors update'] = """
type: command
short-summary: Commands to update CORS policy for a container app.
examples:
- name: Update allowed origins and allowed methods for a container app while keeping other cors settings.
text: |
az containerapp ingress cors update -n MyContainerapp -g MyResourceGroup --allowed-origins http://www.contoso.com https://www.contoso.com --allowed-methods GET POST
"""

helps['containerapp ingress cors show'] = """
type: command
short-summary: Commands to show CORS policy for a container app.
examples:
- name: Show CORS policy for a container app.
text: |
az containerapp ingress cors show -n MyContainerapp -g MyResourceGroup
"""

# Registry Commands
helps['containerapp registry'] = """
type: group
Expand Down
13 changes: 12 additions & 1 deletion src/containerapp/azext_containerapp/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from ._validators import (validate_memory, validate_cpu, validate_managed_env_name_or_id, validate_registry_server,
validate_registry_user, validate_registry_pass, validate_target_port, validate_ingress,
validate_storage_name_or_id)
validate_storage_name_or_id, validate_cors_max_age)
from ._constants import UNAUTHENTICATED_CLIENT_ACTION, FORWARD_PROXY_CONVENTION, MAXIMUM_CONTAINER_APP_NAME_LENGTH, LOG_TYPE_CONSOLE, LOG_TYPE_SYSTEM


Expand Down Expand Up @@ -178,6 +178,9 @@ def load_arguments(self, _):
c.argument('certificate_file', options_list=['--custom-domain-certificate-file', '--certificate-file'], help='The filepath of the certificate file (.pfx or .pem) for the environment\'s custom domain. To manage certificates for container apps, use `az containerapp env certificate`.')
c.argument('certificate_password', options_list=['--custom-domain-certificate-password', '--certificate-password'], help='The certificate file password for the environment\'s custom domain.')

with self.argument_context('containerapp env', arg_group='Peer Authentication') as c:
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved
c.argument('mtls_enabled', arg_type=get_three_state_flag(), options_list=['--enable-mtls'], help='Boolean indicating if mTLS peer authentication is enabled for the environment.')

with self.argument_context('containerapp service') as c:
c.argument('service_name', options_list=['--name', '-n'], help="The service name.")
c.argument('environment_name', options_list=['--environment'], help="The environment name.")
Expand Down Expand Up @@ -299,6 +302,14 @@ def load_arguments(self, _):
with self.argument_context('containerapp ingress sticky-sessions') as c:
c.argument('affinity', arg_type=get_enum_type(['sticky', 'none']), help='Whether the affinity for the container app is Sticky or None.')

with self.argument_context('containerapp ingress cors') as c:
c.argument('allowed_origins', nargs='*', options_list=['--allowed-origins', '-r'], help="A list of allowed origin(s) for the container app. Values are space-separated. If you want to clear it you can use --allowed-origins with no arguments.")
c.argument('allowed_methods', nargs='*', options_list=['--allowed-methods', '-m'], help="A list of allowed method(s) for the container app. Values are space-separated. If you want to clear it you can use --allowed-methods with no arguments.")
c.argument('allowed_headers', nargs='*', options_list=['--allowed-headers', '-a'], help="A list of allowed header(s) for the container app. Values are space-separated. If you want to clear it you can use --allowed-headers with no arguments.")
zhoxing-ms marked this conversation as resolved.
Show resolved Hide resolved
c.argument('expose_headers', nargs='*', options_list=['--expose-headers', '-e'], help="A list of expose header(s) for the container app. Values are space-separated. If you want to clear it you can use --expose-headers with no arguments.")
c.argument('allow_credentials', options_list=['--allow-credentials'], arg_type=get_three_state_flag(), help='Whether the credential is allowed for the container app.')
c.argument('max_age', nargs='?', const='', validator=validate_cors_max_age, help="The maximum age of the allowed origin in seconds. Only postive integer or empty string are allowed. Empty string resets max_age to null.")

with self.argument_context('containerapp secret') as c:
c.argument('secrets', nargs='+', options_list=['--secrets', '-s'], help="A list of secret(s) for the container app. Space-separated values in 'key=value' or 'key=keyvaultref:keyvaulturl,identityref:identity' format (where 'key' cannot be longer than 20 characters).")
c.argument('secret_name', help="The name of the secret to show.")
Expand Down
13 changes: 13 additions & 0 deletions src/containerapp/azext_containerapp/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,16 @@ def validate_ssh(cmd, namespace):
_validate_revision_exists(cmd, namespace)
_validate_replica_exists(cmd, namespace)
_validate_container_exists(cmd, namespace)


def validate_cors_max_age(cmd, namespace):
if namespace.max_age:
try:
if namespace.max_age == "":
return

max_age = int(namespace.max_age)
if max_age < 0:
raise ValidationError("max-age must be a positive integer.")
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved
except ValueError:
raise ValidationError("max-age must be an integer.")
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 6 additions & 0 deletions src/containerapp/azext_containerapp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ def load_command_table(self, _):
g.custom_command('remove', 'remove_ip_restriction')
g.custom_show_command('list', 'show_ip_restrictions')

with self.command_group('containerapp ingress cors') as g:
g.custom_command('enable', 'enable_cors_policy', exception_handler=ex_handler_factory())
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved
g.custom_command('disable', 'disable_cors_policy', exception_handler=ex_handler_factory())
g.custom_command('update', 'update_cors_policy', exception_handler=ex_handler_factory())
g.custom_show_command('show', 'show_cors_policy')

with self.command_group('containerapp registry') as g:
g.custom_command('set', 'set_registry', exception_handler=ex_handler_factory())
g.custom_show_command('show', 'show_registry')
Expand Down
121 changes: 121 additions & 0 deletions src/containerapp/azext_containerapp/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,7 @@ def create_managed_environment(cmd,
certificate_file=None,
certificate_password=None,
enable_workload_profiles=False,
mtls_enabled=None,
no_wait=False):
if zone_redundant:
if not infrastructure_subnet_resource_id:
Expand Down Expand Up @@ -1378,6 +1379,8 @@ def create_managed_environment(cmd,
raise ValidationError('Infrastructure subnet resource ID needs to be supplied for internal only environments.')
managed_env_def["properties"]["vnetConfiguration"]["internal"] = True

if mtls_enabled is not None:
safe_set(managed_env_def, "properties", "peerAuthentication", "mtls", "enabled", value=mtls_enabled)
try:
r = ManagedEnvironmentClient.create(
cmd=cmd, resource_group_name=resource_group_name, name=name, managed_environment_envelope=managed_env_def, no_wait=no_wait)
Expand Down Expand Up @@ -1412,6 +1415,7 @@ def update_managed_environment(cmd,
workload_profile_name=None,
min_nodes=None,
max_nodes=None,
mtls_enabled=None,
no_wait=False):
if logs_destination == "log-analytics" or logs_customer_id or logs_key:
if logs_destination != "log-analytics":
Expand Down Expand Up @@ -1482,6 +1486,9 @@ def update_managed_environment(cmd,

safe_set(env_def, "properties", "workloadProfiles", value=workload_profiles)

if mtls_enabled is not None:
safe_set(env_def, "properties", "peerAuthentication", "mtls", "enabled", value=mtls_enabled)

try:
r = ManagedEnvironmentClient.update(
cmd=cmd, resource_group_name=resource_group_name, name=name, managed_environment_envelope=env_def, no_wait=no_wait)
Expand Down Expand Up @@ -3509,6 +3516,120 @@ def show_ingress_sticky_session(cmd, name, resource_group_name):
raise ValidationError("Ingress must be enabled to enable sticky sessions. Try running `az containerapp ingress -h` for more info.") from e


def enable_cors_policy(cmd, name, resource_group_name, allowed_origins, allowed_methods=None, allowed_headers=None, expose_headers=None, allow_credentials=None, max_age=None, no_wait=False):
_validate_subscription_registered(cmd, CONTAINER_APPS_RP)

if not allowed_origins:
raise ValidationError("Allowed origins must be specified.")
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved

containerapp_def = None
try:
containerapp_def = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name)
except:
pass
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved

if not containerapp_def:
raise ResourceNotFoundError(f"The containerapp '{name}' does not exist in group '{resource_group_name}'")

containerapp_patch = {}
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "allowedOrigins", value=allowed_origins)
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "allowedMethods", value=allowed_methods)
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "allowedHeaders", value=allowed_headers)
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "exposeHeaders", value=expose_headers)
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "allowCredentials", value=allow_credentials)
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "maxAge", value=max_age)
try:
r = ContainerAppClient.update(
cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_patch, no_wait=no_wait)
return safe_get(r, "properties", "configuration", "ingress", "corsPolicy", default={})
except Exception as e:
handle_raw_exception(e)


def disable_cors_policy(cmd, name, resource_group_name, no_wait=False):
_validate_subscription_registered(cmd, CONTAINER_APPS_RP)

containerapp_def = None
try:
containerapp_def = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name)
except:
pass
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved

if not containerapp_def:
raise ResourceNotFoundError(f"The containerapp '{name}' does not exist in group '{resource_group_name}'")

containerapp_patch = {}
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", value=None)
try:
r = ContainerAppClient.update(
cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_patch, no_wait=no_wait)
return safe_get(r, "properties", "configuration", "ingress", default={})
except Exception as e:
handle_raw_exception(e)


def update_cors_policy(cmd, name, resource_group_name, allowed_origins=None, allowed_methods=None, allowed_headers=None, expose_headers=None, allow_credentials=None, max_age=None, no_wait=False):
_validate_subscription_registered(cmd, CONTAINER_APPS_RP)

containerapp_def = None
try:
containerapp_def = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name)
except:
pass

if not containerapp_def:
raise ResourceNotFoundError(f"The containerapp '{name}' does not exist in group '{resource_group_name}'")

if allowed_origins is not None and len(allowed_origins) == 0:
raise ValidationError("allowed-origins must be specified if provided.")
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved

reset_max_age = False
if max_age == "":
reset_max_age = True

containerapp_patch = {}
if allowed_origins is not None:
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "allowedOrigins", value=allowed_origins)
if allowed_methods is not None:
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "allowedMethods", value=allowed_methods)
if allowed_headers is not None:
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "allowedHeaders", value=allowed_headers)
if expose_headers is not None:
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "exposeHeaders", value=expose_headers)
if allow_credentials is not None:
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "allowCredentials", value=allow_credentials)
if max_age is not None:
if reset_max_age:
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "maxAge", value=None)
else:
safe_set(containerapp_patch, "properties", "configuration", "ingress", "corsPolicy", "maxAge", value=max_age)

try:
r = ContainerAppClient.update(
cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_patch, no_wait=no_wait)
return safe_get(r, "properties", "configuration", "ingress", "corsPolicy", default={})
except Exception as e:
handle_raw_exception(e)


def show_cors_policy(cmd, name, resource_group_name):
_validate_subscription_registered(cmd, CONTAINER_APPS_RP)

containerapp_def = None
try:
containerapp_def = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name)
except:
pass
zhenqxuMSFT marked this conversation as resolved.
Show resolved Hide resolved

if not containerapp_def:
raise ResourceNotFoundError(f"The containerapp '{name}' does not exist in group '{resource_group_name}'")

try:
return safe_get(containerapp_def, "properties", "configuration", "ingress", "corsPolicy", default={})
except Exception as e:
raise ValidationError("CORS must be enabled to enable CORS policy. Try running `az containerapp ingress cors enable -h` for more info.") from e


def show_registry(cmd, name, resource_group_name, server):
_validate_subscription_registered(cmd, CONTAINER_APPS_RP)

Expand Down
Loading