Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions src/containerapp/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Release History
===============
upcoming
++++++
* 'az containerapp auth update': Support authenticating blob storage token store using managed identity with `--blob-container-uri` and `--blob-container-identity`.

1.1.0b4
++++++
Expand Down
4 changes: 2 additions & 2 deletions src/containerapp/azext_containerapp/_arc_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def create_folder(folder_name, time_stamp):
# For handling storage or OS exception that may occur during the execution
except OSError as e:
if "[Errno 28]" in str(e):
shutil.rmtree(filepath_with_timestamp, ignore_errors=False, onexc=None)
shutil.rmtree(filepath_with_timestamp, ignore_errors=False)
error = "No space left on device"
else:
error = f"Error while trying to create diagnostic logs folder. Exception: {str(e)}"
Expand Down Expand Up @@ -110,7 +110,7 @@ def create_sub_folder(parent_path, subfolder_name):
# For handling storage or OS exception that may occur during the execution
except OSError as e:
if "[Errno 28]" in str(e):
shutil.rmtree(filepath, ignore_errors=False, onexc=None)
shutil.rmtree(filepath, ignore_errors=False)
error = "No space left on device"
else:
error = f"Error while trying to create diagnostic logs folder. Exception: {str(e)}"
Expand Down
6 changes: 6 additions & 0 deletions src/containerapp/azext_containerapp/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,12 @@
- name: Configure the app to listen to the forward headers X-FORWARDED-HOST and X-FORWARDED-PROTO.
text: |
az containerapp auth update -g myResourceGroup --name my-containerapp --proxy-convention Standard
- name: Configure the blob storage token store using default system assigned managed identity to authenticate.
text: |
az containerapp auth update -g myResourceGroup --name my-containerapp --token-store true --blob-container-uri https://storageAccount1.blob.core.windows.net/container1
- name: Configure the blob storage token store using user assigned managed identity to authenticate.
text: |
az containerapp auth update -g myResourceGroup --name my-containerapp --token-store true --blob-container-uri https://storageAccount1.blob.core.windows.net/container1 --blob-container-identity managedIdentityResourceId
"""

helps['containerapp env workload-profile set'] = """
Expand Down
5 changes: 5 additions & 0 deletions src/containerapp/azext_containerapp/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ def load_arguments(self, _):
c.argument('user_assigned', nargs='+', help="Space-separated user identities to be assigned.")
c.argument('system_assigned', help="Boolean indicating whether to assign system-assigned identity.", action='store_true')

with self.argument_context('containerapp auth') as c:
c.argument('blob_container_uri', help='The URI of the blob storage containing the tokens. Should not be used along with sas_url_secret and sas_url_secret_name.', is_preview=True)
c.argument('blob_container_identity', options_list=['--blob-container-identity', '--bci'],
help='Default Empty to use system-assigned identity, or using Resource ID of a managed identity to authenticate with Azure blob storage.', is_preview=True)

with self.argument_context('containerapp env workload-profile set') as c:
c.argument('workload_profile_type', help="The type of workload profile to add or update. Run `az containerapp env workload-profile list-supported -l <region>` to check the options for your region.")
c.argument('min_nodes', help="The minimum node count for the workload profile")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,100 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
# pylint: disable=line-too-long, useless-parent-delegation

from azure.cli.core.azclierror import ArgumentUsageError
from azure.cli.core.commands.client_factory import get_subscription_id
from azure.cli.command_modules.containerapp.containerapp_auth_decorator import ContainerAppAuthDecorator
from azure.cli.command_modules.containerapp._utils import safe_set, set_field_in_auth_settings, update_http_settings_in_auth_settings, _ensure_identity_resource_id
from azure.cli.command_modules.containerapp._constants import BLOB_STORAGE_TOKEN_STORE_SECRET_SETTING_NAME


# decorator for preview auth show/update
class ContainerAppPreviewAuthDecorator(ContainerAppAuthDecorator):

def parent_construct_payload(self):
self.existing_auth = {}
try:
self.existing_auth = self.client.get(cmd=self.cmd, resource_group_name=self.get_argument_resource_group_name(), container_app_name=self.get_argument_name(), auth_config_name="current")["properties"]
except: # pylint: disable=bare-except
self.existing_auth["platform"] = {}
self.existing_auth["platform"]["enabled"] = True
self.existing_auth["globalValidation"] = {}
self.existing_auth["login"] = {}

self.existing_auth = set_field_in_auth_settings(self.existing_auth, self.get_argument_set_string())

if self.get_argument_enabled() is not None:
if "platform" not in self.existing_auth:
self.existing_auth["platform"] = {}
self.existing_auth["platform"]["enabled"] = self.get_argument_enabled()

if self.get_argument_runtime_version() is not None:
if "platform" not in self.existing_auth:
self.existing_auth["platform"] = {}
self.existing_auth["platform"]["runtimeVersion"] = self.get_argument_runtime_version()

if self.get_argument_config_file_path() is not None:
if "platform" not in self.existing_auth:
self.existing_auth["platform"] = {}
self.existing_auth["platform"]["configFilePath"] = self.get_argument_config_file_path()

if self.get_argument_unauthenticated_client_action() is not None:
if "globalValidation" not in self.existing_auth:
self.existing_auth["globalValidation"] = {}
self.existing_auth["globalValidation"]["unauthenticatedClientAction"] = self.get_argument_unauthenticated_client_action()

if self.get_argument_redirect_provider() is not None:
if "globalValidation" not in self.existing_auth:
self.existing_auth["globalValidation"] = {}
self.existing_auth["globalValidation"]["redirectToProvider"] = self.get_argument_redirect_provider()

if self.get_argument_excluded_paths() is not None:
if "globalValidation" not in self.existing_auth:
self.existing_auth["globalValidation"] = {}
self.existing_auth["globalValidation"]["excludedPaths"] = self.get_argument_excluded_paths().split(",")

self.existing_auth = update_http_settings_in_auth_settings(self.existing_auth, self.get_argument_require_https(),
self.get_argument_proxy_convention(), self.get_argument_proxy_custom_host_header(),
self.get_argument_proxy_custom_proto_header())

def construct_payload(self):
super().construct_payload()
self.parent_construct_payload()
self.set_up_token_store()

def set_up_token_store(self):
if self.get_argument_token_store() is None:
return

if self.get_argument_token_store() is False:
safe_set(self.existing_auth, "login", "tokenStore", "enabled", value=False)
return

safe_set(self.existing_auth, "login", "tokenStore", "enabled", value=True)
safe_set(self.existing_auth, "login", "tokenStore", "azureBlobStorage", value={})

param_provided = sum(1 for param in [self.get_argument_sas_url_secret(), self.get_argument_sas_url_secret_name(), self.get_argument_blob_container_uri()] if param is not None)
if param_provided != 1:
raise ArgumentUsageError(
'Usage Error: only blob storage token store is supported. --sas-url-secret, --sas-url-secret-name and --blob-container-uri should provide exactly one when token store is enabled')

if self.get_argument_blob_container_uri() is not None:
safe_set(self.existing_auth, "login", "tokenStore", "azureBlobStorage", "blobContainerUri", value=self.get_argument_blob_container_uri())

identity = self.get_argument_blob_container_identity()
if identity is not None:
identity = identity.lower()
subscription_id = get_subscription_id(self.cmd.cli_ctx)
identity = _ensure_identity_resource_id(subscription_id, self.get_argument_resource_group_name(), identity)
safe_set(self.existing_auth, "login", "tokenStore", "azureBlobStorage", "managedIdentityResourceId", value=identity)
return

sas_url_setting_name = BLOB_STORAGE_TOKEN_STORE_SECRET_SETTING_NAME
if self.get_argument_sas_url_secret_name() is not None:
sas_url_setting_name = self.get_argument_sas_url_secret_name()
safe_set(self.existing_auth, "login", "tokenStore", "azureBlobStorage", "sasUrlSettingName", value=sas_url_setting_name)

def get_argument_blob_container_uri(self):
return self.get_param("blob_container_uri")

def get_argument_blob_container_identity(self):
return self.get_param("blob_container_identity")
1 change: 1 addition & 0 deletions src/containerapp/azext_containerapp/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1401,6 +1401,7 @@ def update_auth_config(cmd, resource_group_name, name, set_string=None, enabled=
proxy_convention=None, proxy_custom_host_header=None,
proxy_custom_proto_header=None, excluded_paths=None,
token_store=None, sas_url_secret=None, sas_url_secret_name=None,
blob_container_uri=None, blob_container_identity=None,
yes=False):
raw_parameters = locals()
containerapp_auth_decorator = ContainerAppPreviewAuthDecorator(
Expand Down
Loading
Loading