Skip to content

Commit 0aac33e

Browse files
authored
Support blob storage token store authenticate with MSI (Azure#8640)
1 parent d508c9c commit 0aac33e

File tree

8 files changed

+3392
-7
lines changed

8 files changed

+3392
-7
lines changed

src/containerapp/HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Release History
44
===============
55
upcoming
66
++++++
7+
* 'az containerapp auth update': Support authenticating blob storage token store using managed identity with `--blob-container-uri` and `--blob-container-identity`.
78

89
1.1.0b4
910
++++++

src/containerapp/azext_containerapp/_arc_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def create_folder(folder_name, time_stamp):
8181
# For handling storage or OS exception that may occur during the execution
8282
except OSError as e:
8383
if "[Errno 28]" in str(e):
84-
shutil.rmtree(filepath_with_timestamp, ignore_errors=False, onexc=None)
84+
shutil.rmtree(filepath_with_timestamp, ignore_errors=False)
8585
error = "No space left on device"
8686
else:
8787
error = f"Error while trying to create diagnostic logs folder. Exception: {str(e)}"
@@ -110,7 +110,7 @@ def create_sub_folder(parent_path, subfolder_name):
110110
# For handling storage or OS exception that may occur during the execution
111111
except OSError as e:
112112
if "[Errno 28]" in str(e):
113-
shutil.rmtree(filepath, ignore_errors=False, onexc=None)
113+
shutil.rmtree(filepath, ignore_errors=False)
114114
error = "No space left on device"
115115
else:
116116
error = f"Error while trying to create diagnostic logs folder. Exception: {str(e)}"

src/containerapp/azext_containerapp/_help.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,12 @@
715715
- name: Configure the app to listen to the forward headers X-FORWARDED-HOST and X-FORWARDED-PROTO.
716716
text: |
717717
az containerapp auth update -g myResourceGroup --name my-containerapp --proxy-convention Standard
718+
- name: Configure the blob storage token store using default system assigned managed identity to authenticate.
719+
text: |
720+
az containerapp auth update -g myResourceGroup --name my-containerapp --token-store true --blob-container-uri https://storageAccount1.blob.core.windows.net/container1
721+
- name: Configure the blob storage token store using user assigned managed identity to authenticate.
722+
text: |
723+
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
718724
"""
719725

720726
helps['containerapp env workload-profile set'] = """

src/containerapp/azext_containerapp/_params.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ def load_arguments(self, _):
276276
c.argument('user_assigned', nargs='+', help="Space-separated user identities to be assigned.")
277277
c.argument('system_assigned', help="Boolean indicating whether to assign system-assigned identity.", action='store_true')
278278

279+
with self.argument_context('containerapp auth') as c:
280+
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)
281+
c.argument('blob_container_identity', options_list=['--blob-container-identity', '--bci'],
282+
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)
283+
279284
with self.argument_context('containerapp env workload-profile set') as c:
280285
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.")
281286
c.argument('min_nodes', help="The minimum node count for the workload profile")

src/containerapp/azext_containerapp/containerapp_auth_decorator.py

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,100 @@
33
# Licensed under the MIT License. See License.txt in the project root for license information.
44
# --------------------------------------------------------------------------------------------
55
# pylint: disable=line-too-long, useless-parent-delegation
6-
6+
from azure.cli.core.azclierror import ArgumentUsageError
7+
from azure.cli.core.commands.client_factory import get_subscription_id
78
from azure.cli.command_modules.containerapp.containerapp_auth_decorator import ContainerAppAuthDecorator
9+
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
10+
from azure.cli.command_modules.containerapp._constants import BLOB_STORAGE_TOKEN_STORE_SECRET_SETTING_NAME
811

912

1013
# decorator for preview auth show/update
1114
class ContainerAppPreviewAuthDecorator(ContainerAppAuthDecorator):
15+
16+
def parent_construct_payload(self):
17+
self.existing_auth = {}
18+
try:
19+
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"]
20+
except: # pylint: disable=bare-except
21+
self.existing_auth["platform"] = {}
22+
self.existing_auth["platform"]["enabled"] = True
23+
self.existing_auth["globalValidation"] = {}
24+
self.existing_auth["login"] = {}
25+
26+
self.existing_auth = set_field_in_auth_settings(self.existing_auth, self.get_argument_set_string())
27+
28+
if self.get_argument_enabled() is not None:
29+
if "platform" not in self.existing_auth:
30+
self.existing_auth["platform"] = {}
31+
self.existing_auth["platform"]["enabled"] = self.get_argument_enabled()
32+
33+
if self.get_argument_runtime_version() is not None:
34+
if "platform" not in self.existing_auth:
35+
self.existing_auth["platform"] = {}
36+
self.existing_auth["platform"]["runtimeVersion"] = self.get_argument_runtime_version()
37+
38+
if self.get_argument_config_file_path() is not None:
39+
if "platform" not in self.existing_auth:
40+
self.existing_auth["platform"] = {}
41+
self.existing_auth["platform"]["configFilePath"] = self.get_argument_config_file_path()
42+
43+
if self.get_argument_unauthenticated_client_action() is not None:
44+
if "globalValidation" not in self.existing_auth:
45+
self.existing_auth["globalValidation"] = {}
46+
self.existing_auth["globalValidation"]["unauthenticatedClientAction"] = self.get_argument_unauthenticated_client_action()
47+
48+
if self.get_argument_redirect_provider() is not None:
49+
if "globalValidation" not in self.existing_auth:
50+
self.existing_auth["globalValidation"] = {}
51+
self.existing_auth["globalValidation"]["redirectToProvider"] = self.get_argument_redirect_provider()
52+
53+
if self.get_argument_excluded_paths() is not None:
54+
if "globalValidation" not in self.existing_auth:
55+
self.existing_auth["globalValidation"] = {}
56+
self.existing_auth["globalValidation"]["excludedPaths"] = self.get_argument_excluded_paths().split(",")
57+
58+
self.existing_auth = update_http_settings_in_auth_settings(self.existing_auth, self.get_argument_require_https(),
59+
self.get_argument_proxy_convention(), self.get_argument_proxy_custom_host_header(),
60+
self.get_argument_proxy_custom_proto_header())
61+
1262
def construct_payload(self):
13-
super().construct_payload()
63+
self.parent_construct_payload()
64+
self.set_up_token_store()
65+
66+
def set_up_token_store(self):
67+
if self.get_argument_token_store() is None:
68+
return
69+
70+
if self.get_argument_token_store() is False:
71+
safe_set(self.existing_auth, "login", "tokenStore", "enabled", value=False)
72+
return
73+
74+
safe_set(self.existing_auth, "login", "tokenStore", "enabled", value=True)
75+
safe_set(self.existing_auth, "login", "tokenStore", "azureBlobStorage", value={})
76+
77+
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)
78+
if param_provided != 1:
79+
raise ArgumentUsageError(
80+
'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')
81+
82+
if self.get_argument_blob_container_uri() is not None:
83+
safe_set(self.existing_auth, "login", "tokenStore", "azureBlobStorage", "blobContainerUri", value=self.get_argument_blob_container_uri())
84+
85+
identity = self.get_argument_blob_container_identity()
86+
if identity is not None:
87+
identity = identity.lower()
88+
subscription_id = get_subscription_id(self.cmd.cli_ctx)
89+
identity = _ensure_identity_resource_id(subscription_id, self.get_argument_resource_group_name(), identity)
90+
safe_set(self.existing_auth, "login", "tokenStore", "azureBlobStorage", "managedIdentityResourceId", value=identity)
91+
return
92+
93+
sas_url_setting_name = BLOB_STORAGE_TOKEN_STORE_SECRET_SETTING_NAME
94+
if self.get_argument_sas_url_secret_name() is not None:
95+
sas_url_setting_name = self.get_argument_sas_url_secret_name()
96+
safe_set(self.existing_auth, "login", "tokenStore", "azureBlobStorage", "sasUrlSettingName", value=sas_url_setting_name)
97+
98+
def get_argument_blob_container_uri(self):
99+
return self.get_param("blob_container_uri")
100+
101+
def get_argument_blob_container_identity(self):
102+
return self.get_param("blob_container_identity")

src/containerapp/azext_containerapp/custom.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,7 @@ def update_auth_config(cmd, resource_group_name, name, set_string=None, enabled=
14011401
proxy_convention=None, proxy_custom_host_header=None,
14021402
proxy_custom_proto_header=None, excluded_paths=None,
14031403
token_store=None, sas_url_secret=None, sas_url_secret_name=None,
1404+
blob_container_uri=None, blob_container_identity=None,
14041405
yes=False):
14051406
raw_parameters = locals()
14061407
containerapp_auth_decorator = ContainerAppPreviewAuthDecorator(

0 commit comments

Comments
 (0)