From 903d348d6a325d042ea6ebbd8ce59734bdf649a9 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Thu, 18 Sep 2025 13:16:25 +0000 Subject: [PATCH 01/14] Initial dataclasses commit --- src/confcom/.gitignore | 2 + .../azext_confcom/lib/aci_policy_spec.py | 76 ++++++ .../lib/arm_to_aci_policy_spec.py | 217 ++++++++++++++++++ .../lib/image_refs_to_aci_policy_spec.py | 34 +++ src/confcom/azext_confcom/template_util.py | 6 + 5 files changed, 335 insertions(+) create mode 100644 src/confcom/azext_confcom/lib/aci_policy_spec.py create mode 100644 src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py create mode 100644 src/confcom/azext_confcom/lib/image_refs_to_aci_policy_spec.py diff --git a/src/confcom/.gitignore b/src/confcom/.gitignore index 562e4134172..a80f53ac125 100644 --- a/src/confcom/.gitignore +++ b/src/confcom/.gitignore @@ -36,3 +36,5 @@ azext_confcom/bin/* **/.coverage **/htmlcov + +!lib/ \ No newline at end of file diff --git a/src/confcom/azext_confcom/lib/aci_policy_spec.py b/src/confcom/azext_confcom/lib/aci_policy_spec.py new file mode 100644 index 00000000000..20c4b6c4072 --- /dev/null +++ b/src/confcom/azext_confcom/lib/aci_policy_spec.py @@ -0,0 +1,76 @@ +from dataclasses import dataclass +from typing import Optional +from typing_extensions import Literal + + +@dataclass +class AciContainerPropertyEnvVariable: + name: str + value: str + strategy: str + required: bool = False + + +@dataclass +class AciContainerPropertyExecProcesses: + command: list[str] + signals: Optional[list[str]] = None + allow_stdio_access: bool = True + + +@dataclass +class AciContainerPropertyVolumeMounts: + mountPath: str + name: Optional[str] = None + readonly: bool = False + mountType: Optional[Literal["azureFile", "secret", "configMap", "emptyDir"]] = None + + +@dataclass +class AciContainerPropertySecurityContextCapabilities: + add: list[str] + drop: list[str] + + +@dataclass +class AciContainerPropertySecurityContext: + priviledged: bool + runAsUser: int + runAsGroup: int + runAsNonRoot: bool + readOnlyRootFilesystem: bool + capabilities: AciContainerPropertySecurityContextCapabilities + + +@dataclass +class AciContainerProperties(): + image: str + allowStdioAccess: bool = True + environmentVariables: Optional[list[AciContainerPropertyEnvVariable]] = None + execProcesses: Optional[list[AciContainerPropertyExecProcesses]] = None + volumeMounts: Optional[list[AciContainerPropertyVolumeMounts]] = None + securityContext: Optional[AciContainerPropertySecurityContext] = None + command: Optional[list[str]] = None + + +# ------------------------------------------------------------------------------ + + +@dataclass +class AciFragmentSpec: + feed: str + issuer: str + minimum_svn: str + includes: list[Literal["containers", "fragments"]] + + +@dataclass +class AciContainerSpec: + name: str + properties: AciContainerProperties + + +@dataclass +class AciPolicySpec: + fragments: Optional[list[AciFragmentSpec]] + containers: Optional[list[AciContainerSpec]] \ No newline at end of file diff --git a/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py b/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py new file mode 100644 index 00000000000..0e6855adaa9 --- /dev/null +++ b/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py @@ -0,0 +1,217 @@ +from typing import Iterator, Optional +import json +import re +from azext_confcom import config +from azext_confcom.template_util import ( + get_probe_exec_processes, + is_sidecar, + process_configmap, + process_env_vars_from_template, + process_mounts +) +from azext_confcom.lib.aci_policy_spec import ( + AciContainerPropertyEnvVariable, + AciContainerPropertyExecProcesses, + AciContainerPropertySecurityContext, + AciContainerPropertySecurityContextCapabilities, + AciContainerPropertyVolumeMounts, + AciContainerSpec, + AciContainerProperties, + AciFragmentSpec, + AciPolicySpec, +) + +def eval_variables( + arm_template: dict, + arm_template_parameters: dict, +) -> dict: + + def parse_arm_parameters( + arm_template_parameters: dict + ) -> Iterator[tuple[str, str]]: + for param_name, param in arm_template_parameters.get("parameters", {}).items(): + if "value" in param: + yield param_name, param["value"] + elif "defaultValue" in param: + yield param_name, param["defaultValue"] + + json_str = json.dumps(arm_template) + variables = { + **arm_template.get("variables", {}), + **dict(parse_arm_parameters(arm_template_parameters)), + } + + pattern = re.compile(r"\[variables\('([^']+)'\)\]") + + def _replace(match): + var_name = match.group(1) + if var_name not in variables: + return match.group(0) + val = variables[var_name] + if isinstance(val, str): + return json.dumps(val)[1:-1] + return json.dumps(val) + + replaced = pattern.sub(_replace, json_str) + return json.loads(replaced) + + +EVAL_FUNCS = [ + eval_variables, +] + + +def arm_container_env_to_aci_policy_spec_env( + container_properties: dict, + approve_wildcards: bool, +) -> Iterator[AciContainerPropertyEnvVariable]: + + for env_var in [ + *process_env_vars_from_template({}, {}, container_properties, approve_wildcards), + *config.OPENGCS_ENV_RULES, + *config.FABRIC_ENV_RULES, + *config.MANAGED_IDENTITY_ENV_RULES, + *config.ENABLE_RESTART_ENV_RULE, + ]: + yield AciContainerPropertyEnvVariable(**env_var) + + +def arm_container_volumes_to_aci_policy_spec_volumes( + container_properties: dict, + container_group_volumes: list[dict], +) -> Iterator[AciContainerPropertyVolumeMounts]: + + for vol_mount in [ + *process_mounts(container_properties, container_group_volumes), + *process_configmap(container_properties), + *( + config.DEFAULT_MOUNTS_USER + if not is_sidecar(container_properties["image"]) else [] + ) + ]: + yield AciContainerPropertyVolumeMounts( + **{k: v for k, v in vol_mount.items() if v is not None}) + + +def arm_container_exec_procs_to_aci_policy_spec_exec_procs( + container_properties: dict, + debug_mode: bool, +) -> Iterator[AciContainerPropertyVolumeMounts]: + + for exec_process in [ + *container_properties.get("execProcesses", []), + *get_probe_exec_processes(container_properties), + *(config.DEBUG_MODE_SETTINGS.get("execProcesses", []) if debug_mode else []), + ]: + yield AciContainerPropertyExecProcesses(**exec_process) + + +def arm_container_props_to_aci_policy_spec_props( + container_group: dict, + container_properties: dict, + debug_mode: bool, + allow_stdio_access: bool, + approve_wildcards: bool, +) -> AciContainerProperties: + + return AciContainerProperties( + image=container_properties["image"], + command=container_properties.get("command", []), + allowStdioAccess=allow_stdio_access, + environmentVariables=list(arm_container_env_to_aci_policy_spec_env( + container_properties=container_properties, + approve_wildcards=approve_wildcards, + )), + volumeMounts=list(arm_container_volumes_to_aci_policy_spec_volumes( + container_properties=container_properties, + container_group_volumes=container_group["properties"].get("volumes", [])), + ), + execProcesses=list(arm_container_exec_procs_to_aci_policy_spec_exec_procs( + container_properties=container_properties, + debug_mode=debug_mode, + )), + securityContext=AciContainerPropertySecurityContext( + capabilities=AciContainerPropertySecurityContextCapabilities( + add=container_properties["securityContext"]["capabilities"].get("add", []), + drop=container_properties["securityContext"]["capabilities"].get("drop", []), + ) if "capabilities" in container_properties["securityContext"] else None, + **container_properties["securityContext"] + ) if "securityContext" in container_properties else None, + ) + + +def arm_container_to_aci_policy_spec_container( + container_group: dict, + container: dict, + debug_mode: bool, + allow_stdio_access: bool, + approve_wildcards: bool, +) -> AciContainerSpec: + + return AciContainerSpec( + name=container["name"], + properties=arm_container_props_to_aci_policy_spec_props( + container_group=container_group, + container_properties=container["properties"], + debug_mode=debug_mode, + allow_stdio_access=allow_stdio_access, + approve_wildcards=approve_wildcards, + ), + ) + + +def arm_container_group_to_aci_policy_spec_fragments( + container_group: dict, +) -> Iterator[AciFragmentSpec]: + + for fragment in container_group.get("properties", {}).get("standaloneFragments", []): + yield AciFragmentSpec(**fragment) + + +def arm_container_group_to_aci_policy_spec( + container_group: dict, + fragments: list[AciFragmentSpec], + debug_mode: bool, + allow_stdio_access: bool, + approve_wildcards: bool, +) -> AciPolicySpec: + + return AciPolicySpec( + fragments=[ + *fragments, + *arm_container_group_to_aci_policy_spec_fragments(container_group), + ], + containers=[ + arm_container_to_aci_policy_spec_container( + container_group=container_group, + container=c, + debug_mode=debug_mode, + allow_stdio_access=allow_stdio_access, + approve_wildcards=approve_wildcards, + ) + for c in container_group.get("properties", {}).get("containers", []) + ] + ) + + +def arm_to_aci_policy_spec( + arm_template: dict, + arm_template_parameters: dict, + fragments: list[AciFragmentSpec], + debug_mode: bool = False, + allow_stdio_access: bool = True, + approve_wildcards: bool = False, +) -> Iterator[AciPolicySpec]: + + for eval_func in EVAL_FUNCS: + arm_template = eval_func(arm_template, arm_template_parameters) + + for resource in arm_template.get("resources", []): + parser = { + "Microsoft.ContainerInstance/containerGroups": arm_container_group_to_aci_policy_spec, + "Microsoft.ContainerInstance/containerGroupProfiles": arm_container_group_to_aci_policy_spec, + }.get(resource["type"], (lambda r, f, d, io, w: None)) + + spec = parser(resource, fragments, debug_mode, allow_stdio_access, approve_wildcards) + if spec is not None: + yield spec diff --git a/src/confcom/azext_confcom/lib/image_refs_to_aci_policy_spec.py b/src/confcom/azext_confcom/lib/image_refs_to_aci_policy_spec.py new file mode 100644 index 00000000000..f634d7dc926 --- /dev/null +++ b/src/confcom/azext_confcom/lib/image_refs_to_aci_policy_spec.py @@ -0,0 +1,34 @@ +from azext_confcom.lib.aci_policy_spec import ( + AciContainerSpec, + AciContainerProperties, + AciFragmentSpec, + AciPolicySpec, +) + + +def image_ref_to_aci_container_spec( + image_ref: str, +) -> AciContainerSpec: + + return AciContainerSpec( + name=image_ref, + properties=AciContainerProperties( + image=image_ref, + ) + ) + + +def image_refs_to_aci_policy_spec( + image_refs: list[str], + fragments: list[AciFragmentSpec], +) -> AciPolicySpec: + + return AciPolicySpec( + fragments=[ + *fragments, + ], + containers=[ + image_ref_to_aci_container_spec(image_ref) + for image_ref in image_refs + ] + ) \ No newline at end of file diff --git a/src/confcom/azext_confcom/template_util.py b/src/confcom/azext_confcom/template_util.py index c968695d5f7..202136c1855 100644 --- a/src/confcom/azext_confcom/template_util.py +++ b/src/confcom/azext_confcom/template_util.py @@ -788,6 +788,12 @@ def extract_probe(exec_processes: List[dict], image_properties: dict, probe: str config.ACI_FIELD_CONTAINERS_SIGNAL_CONTAINER_PROCESSES: [], }) +def get_probe_exec_processes(image_properties: dict) -> List[dict]: + exec_processes: List[dict] = [] + extract_probe(exec_processes, image_properties, config.ACI_FIELD_CONTAINERS_READINESS_PROBE) + extract_probe(exec_processes, image_properties, config.ACI_FIELD_CONTAINERS_LIVENESS_PROBE) + return exec_processes + def extract_lifecycle_hook(exec_processes: List[dict], image_properties: dict, hook: str): lifecycle = case_insensitive_dict_get( From f01ec0041ccd4a53009b9f7c846c40c485826833 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Fri, 19 Sep 2025 14:51:12 +0000 Subject: [PATCH 02/14] Get all tests passing --- .../azext_confcom/lib/aci_policy_spec.py | 14 +- .../lib/arm_to_aci_policy_spec.py | 88 +++++--- src/confcom/azext_confcom/security_policy.py | 207 +++--------------- src/confcom/azext_confcom/template_util.py | 2 +- 4 files changed, 97 insertions(+), 214 deletions(-) diff --git a/src/confcom/azext_confcom/lib/aci_policy_spec.py b/src/confcom/azext_confcom/lib/aci_policy_spec.py index 20c4b6c4072..67a9a5ad371 100644 --- a/src/confcom/azext_confcom/lib/aci_policy_spec.py +++ b/src/confcom/azext_confcom/lib/aci_policy_spec.py @@ -34,12 +34,14 @@ class AciContainerPropertySecurityContextCapabilities: @dataclass class AciContainerPropertySecurityContext: - priviledged: bool - runAsUser: int - runAsGroup: int - runAsNonRoot: bool - readOnlyRootFilesystem: bool - capabilities: AciContainerPropertySecurityContextCapabilities + privileged: Optional[bool] = None + allowPrivilegeEscalation: Optional[bool] = None + runAsUser: Optional[int] = None + runAsGroup: Optional[int] = None + runAsNonRoot: Optional[bool] = None + readOnlyRootFilesystem: Optional[bool] = None + capabilities: Optional[AciContainerPropertySecurityContextCapabilities] = None + seccompProfile: Optional[str] = None @dataclass diff --git a/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py b/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py index 0e6855adaa9..9dc6378f372 100644 --- a/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py +++ b/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py @@ -21,53 +21,58 @@ AciPolicySpec, ) -def eval_variables( +def get_parameters( arm_template: dict, arm_template_parameters: dict, ) -> dict: - def parse_arm_parameters( - arm_template_parameters: dict - ) -> Iterator[tuple[str, str]]: - for param_name, param in arm_template_parameters.get("parameters", {}).items(): - if "value" in param: - yield param_name, param["value"] - elif "defaultValue" in param: - yield param_name, param["defaultValue"] - - json_str = json.dumps(arm_template) - variables = { - **arm_template.get("variables", {}), - **dict(parse_arm_parameters(arm_template_parameters)), + return { + parameter_key: ( + arm_template_parameters.get("parameters", {}).get(parameter_key, {}).get("value") + or arm_template.get("parameters", {}).get(parameter_key, {}).get("defaultValue") + ) + for parameter_key in arm_template.get("parameters", {}).keys() } - pattern = re.compile(r"\[variables\('([^']+)'\)\]") - def _replace(match): - var_name = match.group(1) - if var_name not in variables: - return match.group(0) - val = variables[var_name] - if isinstance(val, str): - return json.dumps(val)[1:-1] - return json.dumps(val) +def eval_parameters( + arm_template: dict, + arm_template_parameters: dict, +) -> dict: - replaced = pattern.sub(_replace, json_str) - return json.loads(replaced) + parameters = get_parameters(arm_template, arm_template_parameters) + return json.loads(re.compile(r"\[parameters\(\s*'([^']+)'\s*\)\]").sub( + lambda match: json.dumps(parameters.get(match.group(1)) or match.group(0))[1:-1], + json.dumps(arm_template), + )) + + +def eval_variables( + arm_template: dict, + arm_template_parameters: dict, +) -> dict: + + variables = arm_template.get("variables", {}) + return json.loads(re.compile(r"\[variables\(\s*'([^']+)'\s*\)\]").sub( + lambda match: json.dumps(variables.get(match.group(1), match.group(0)))[1:-1], + json.dumps(arm_template), + )) EVAL_FUNCS = [ - eval_variables, + eval_parameters, + eval_variables, ] def arm_container_env_to_aci_policy_spec_env( container_properties: dict, + parameters: dict, approve_wildcards: bool, ) -> Iterator[AciContainerPropertyEnvVariable]: for env_var in [ - *process_env_vars_from_template({}, {}, container_properties, approve_wildcards), + *process_env_vars_from_template(parameters, {}, container_properties, approve_wildcards), *config.OPENGCS_ENV_RULES, *config.FABRIC_ENV_RULES, *config.MANAGED_IDENTITY_ENV_RULES, @@ -85,7 +90,7 @@ def arm_container_volumes_to_aci_policy_spec_volumes( *process_mounts(container_properties, container_group_volumes), *process_configmap(container_properties), *( - config.DEFAULT_MOUNTS_USER + config.DEFAULT_MOUNTS_USER if not is_sidecar(container_properties["image"]) else [] ) ]: @@ -109,17 +114,21 @@ def arm_container_exec_procs_to_aci_policy_spec_exec_procs( def arm_container_props_to_aci_policy_spec_props( container_group: dict, container_properties: dict, + parameters: dict, debug_mode: bool, allow_stdio_access: bool, approve_wildcards: bool, ) -> AciContainerProperties: + capabilities = container_properties.get("securityContext", {}).pop("capabilities", None) + return AciContainerProperties( image=container_properties["image"], command=container_properties.get("command", []), allowStdioAccess=allow_stdio_access, environmentVariables=list(arm_container_env_to_aci_policy_spec_env( container_properties=container_properties, + parameters=parameters, approve_wildcards=approve_wildcards, )), volumeMounts=list(arm_container_volumes_to_aci_policy_spec_volumes( @@ -132,9 +141,9 @@ def arm_container_props_to_aci_policy_spec_props( )), securityContext=AciContainerPropertySecurityContext( capabilities=AciContainerPropertySecurityContextCapabilities( - add=container_properties["securityContext"]["capabilities"].get("add", []), - drop=container_properties["securityContext"]["capabilities"].get("drop", []), - ) if "capabilities" in container_properties["securityContext"] else None, + add=capabilities.get("add", []), + drop=capabilities.get("drop", []), + ) if capabilities else None, **container_properties["securityContext"] ) if "securityContext" in container_properties else None, ) @@ -143,6 +152,7 @@ def arm_container_props_to_aci_policy_spec_props( def arm_container_to_aci_policy_spec_container( container_group: dict, container: dict, + parameters: dict, debug_mode: bool, allow_stdio_access: bool, approve_wildcards: bool, @@ -153,6 +163,7 @@ def arm_container_to_aci_policy_spec_container( properties=arm_container_props_to_aci_policy_spec_props( container_group=container_group, container_properties=container["properties"], + parameters=parameters, debug_mode=debug_mode, allow_stdio_access=allow_stdio_access, approve_wildcards=approve_wildcards, @@ -170,26 +181,31 @@ def arm_container_group_to_aci_policy_spec_fragments( def arm_container_group_to_aci_policy_spec( container_group: dict, + parameters: dict, fragments: list[AciFragmentSpec], debug_mode: bool, allow_stdio_access: bool, approve_wildcards: bool, ) -> AciPolicySpec: + containers = container_group.get("properties", {})["containers"] + assert containers + return AciPolicySpec( fragments=[ - *fragments, + *(fragments if not container_group.get("tags", {}).get("Annotate-zero-sidecar") else []), *arm_container_group_to_aci_policy_spec_fragments(container_group), ], containers=[ arm_container_to_aci_policy_spec_container( container_group=container_group, container=c, + parameters=parameters, debug_mode=debug_mode, allow_stdio_access=allow_stdio_access, approve_wildcards=approve_wildcards, ) - for c in container_group.get("properties", {}).get("containers", []) + for c in containers + container_group.get("properties", {}).get("initContainers", []) ] ) @@ -206,12 +222,14 @@ def arm_to_aci_policy_spec( for eval_func in EVAL_FUNCS: arm_template = eval_func(arm_template, arm_template_parameters) + parameters = arm_template.get("parameters", {}) + for resource in arm_template.get("resources", []): parser = { "Microsoft.ContainerInstance/containerGroups": arm_container_group_to_aci_policy_spec, "Microsoft.ContainerInstance/containerGroupProfiles": arm_container_group_to_aci_policy_spec, - }.get(resource["type"], (lambda r, f, d, io, w: None)) + }.get(resource["type"], (lambda r, p, f, d, io, w: None)) - spec = parser(resource, fragments, debug_mode, allow_stdio_access, approve_wildcards) + spec = parser(resource, parameters, fragments, debug_mode, allow_stdio_access, approve_wildcards) if spec is not None: yield spec diff --git a/src/confcom/azext_confcom/security_policy.py b/src/confcom/azext_confcom/security_policy.py index 8ab29f52032..c14b90d3f12 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -8,7 +8,10 @@ import warnings from enum import Enum, auto from typing import Any, Dict, List, Tuple, Union +from dataclasses import asdict +from azext_confcom.lib.aci_policy_spec import AciFragmentSpec +from azext_confcom.lib.arm_to_aci_policy_spec import arm_to_aci_policy_spec import deepdiff from azext_confcom import config, os_util from azext_confcom.container import ContainerImage, UserContainerImage @@ -630,186 +633,46 @@ def load_policy_from_arm_template_str( fragment_contents: Any = None, exclude_default_fragments: bool = False, ) -> List[AciPolicy]: - """Function that converts ARM template string to an ACI Policy""" - input_arm_json = os_util.load_json_from_str(template_data) - input_parameter_json = {} - if parameter_data: - input_parameter_json = os_util.load_json_from_str(parameter_data) + aci_policies = [] - # find the image names and extract them from the template - arm_resources = case_insensitive_dict_get( - input_arm_json, config.ACI_FIELD_RESOURCES - ) - - if not arm_resources: - eprint(f"Field [{config.ACI_FIELD_RESOURCES}] is empty or cannot be found") - - aci_list = [ - item - for item in arm_resources - if item["type"] in config.ACI_FIELD_SUPPORTED_RESOURCES + fragments = [ + AciFragmentSpec( + feed=fragment["feed"], + issuer=fragment["issuer"], + includes=fragment["includes"], + minimum_svn=infrastructure_svn or fragment["minimum_svn"], + ) + for fragment in config.DEFAULT_REGO_FRAGMENTS ] - if not aci_list: + try: + for policy_spec in arm_to_aci_policy_spec( + arm_template=json.loads(template_data), + arm_template_parameters=json.loads(parameter_data) if parameter_data else {}, + fragments=fragments if not exclude_default_fragments else [], + debug_mode=debug_mode, + allow_stdio_access=not disable_stdio, + approve_wildcards=approve_wildcards, + ): + aci_policies.append(load_policy_from_json( + json.dumps(asdict(policy_spec)), + debug_mode, + disable_stdio, + infrastructure_svn, + # This statement covers both if fragments are excluded with + # the flag, and the per container group annotation + not all(fragment in policy_spec.fragments for fragment in fragments), + )) + except Exception as e: + eprint(f"Error processing ARM template: {e}") + + if len(aci_policies) == 0: eprint( f'Field ["type"] must contain one of {config.ACI_FIELD_SUPPORTED_RESOURCES}' ) - # extract variables and parameters in case we need to do substitutions - # while searching for image names - all_params = ( - case_insensitive_dict_get(input_arm_json, config.ACI_FIELD_TEMPLATE_PARAMETERS) - or {} - ) - - get_values_for_params(input_parameter_json, all_params) - - AciPolicy.all_params = all_params - AciPolicy.all_vars = case_insensitive_dict_get(input_arm_json, config.ACI_FIELD_TEMPLATE_VARIABLES) or {} - - container_groups = [] - - for resource in aci_list: - # initialize the list of containers we need to generate policies for - containers = [] - existing_containers = None - fragments = None - exclude_default_fragments = False - - tags = case_insensitive_dict_get(resource, config.ACI_FIELD_TEMPLATE_TAGS) - if tags: - exclude_default_fragments = case_insensitive_dict_get(tags, config.ACI_FIELD_TEMPLATE_ZERO_SIDECAR) - if isinstance(exclude_default_fragments, str): - exclude_default_fragments = exclude_default_fragments.lower() == "true" - - container_group_properties = case_insensitive_dict_get( - resource, config.ACI_FIELD_TEMPLATE_PROPERTIES - ) - container_list = case_insensitive_dict_get( - container_group_properties, config.ACI_FIELD_TEMPLATE_CONTAINERS - ) - - if not container_list: - eprint( - f'Field ["{config.POLICY_FIELD_CONTAINERS}"] must be a list of {config.POLICY_FIELD_CONTAINERS}' - ) - - init_container_list = case_insensitive_dict_get( - container_group_properties, config.ACI_FIELD_TEMPLATE_INIT_CONTAINERS - ) - # add init containers to the list of other containers since they aren't treated differently - # in the security policy - if init_container_list: - container_list.extend(init_container_list) - - # these are standalone fragments coming from the ARM template itself - standalone_fragments = extract_standalone_fragments(container_group_properties) - if standalone_fragments: - standalone_fragment_imports = create_list_of_standalone_imports(standalone_fragments) - unique_imports = set(rego_imports) - for fragment in standalone_fragment_imports: - if fragment not in unique_imports: - rego_imports.append(fragment) - unique_imports.add(fragment) - - try: - existing_containers, fragments = extract_confidential_properties( - container_group_properties - ) - except ValueError as e: - if diff_mode: - # In diff mode, we raise an error if the base64 policy is malformed - eprint(f"Unable to decode existing policy. Please check the base64 encoding.\n{e}") - else: - # In non-diff mode, we ignore the error and proceed without the policy - existing_containers, fragments = ([], []) - - rego_fragments = copy.deepcopy(config.DEFAULT_REGO_FRAGMENTS) if not exclude_default_fragments else [] - if infrastructure_svn: - # assumes the first DEFAULT_REGO_FRAGMENT is always the - # infrastructure fragment - rego_fragments[0][ - config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS_MINIMUM_SVN - ] = infrastructure_svn - if rego_imports: - # error check the rego imports for invalid data types - processed_imports = process_fragment_imports(rego_imports) - rego_fragments.extend(processed_imports) - - volumes = ( - case_insensitive_dict_get( - container_group_properties, config.ACI_FIELD_TEMPLATE_VOLUMES - ) - or [] - ) - if volumes and not isinstance(volumes, list): - # parameter definition is in parameter file but not arm template - eprint(f'Parameter ["{config.ACI_FIELD_TEMPLATE_VOLUMES}"] must be a list') - - for container in container_list: - image_properties = case_insensitive_dict_get( - container, config.ACI_FIELD_TEMPLATE_PROPERTIES - ) - image_name = case_insensitive_dict_get( - image_properties, config.ACI_FIELD_TEMPLATE_IMAGE - ) - - # this is guaranteed unique for a valid ARM template - container_name = case_insensitive_dict_get( - container, config.ACI_FIELD_CONTAINERS_NAME - ) - - if not image_name: - eprint( - f'Field ["{config.ACI_FIELD_TEMPLATE_IMAGE}"] is empty or cannot be found' - ) - - exec_processes = [] - extract_probe(exec_processes, image_properties, config.ACI_FIELD_CONTAINERS_READINESS_PROBE) - extract_probe(exec_processes, image_properties, config.ACI_FIELD_CONTAINERS_LIVENESS_PROBE) - - containers.append( - { - config.ACI_FIELD_CONTAINERS_ID: image_name, - config.ACI_FIELD_CONTAINERS_NAME: container_name, - config.ACI_FIELD_CONTAINERS_CONTAINERIMAGE: image_name, - config.ACI_FIELD_CONTAINERS_ENVS: process_env_vars_from_template( - AciPolicy.all_params, AciPolicy.all_vars, image_properties, approve_wildcards), - config.ACI_FIELD_CONTAINERS_COMMAND: case_insensitive_dict_get( - image_properties, config.ACI_FIELD_TEMPLATE_COMMAND - ) - or [], - config.ACI_FIELD_CONTAINERS_MOUNTS: process_mounts(image_properties, volumes) - + process_configmap(image_properties), - config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES: exec_processes - + config.DEBUG_MODE_SETTINGS.get(config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES) - if debug_mode - else exec_processes, - config.ACI_FIELD_CONTAINERS_SIGNAL_CONTAINER_PROCESSES: [], - config.ACI_FIELD_CONTAINERS_ALLOW_STDIO_ACCESS: not disable_stdio, - config.ACI_FIELD_CONTAINERS_SECURITY_CONTEXT: case_insensitive_dict_get( - image_properties, config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT - ), - } - ) - - container_groups.append( - AciPolicy( - { - config.ACI_FIELD_VERSION: "1.0", - config.ACI_FIELD_CONTAINERS: containers, - config.ACI_FIELD_TEMPLATE_CCE_POLICY: existing_containers, - }, - disable_stdio=disable_stdio, - rego_fragments=rego_fragments, - # fallback to default fragments if the policy is not present - existing_rego_fragments=fragments, - debug_mode=debug_mode, - fragment_contents=fragment_contents, - ) - ) - return container_groups + return aci_policies def load_policy_from_arm_template_file( diff --git a/src/confcom/azext_confcom/template_util.py b/src/confcom/azext_confcom/template_util.py index 202136c1855..7d645519a1b 100644 --- a/src/confcom/azext_confcom/template_util.py +++ b/src/confcom/azext_confcom/template_util.py @@ -520,7 +520,7 @@ def process_env_vars_from_config(container) -> List[Dict[str, str]]: config.ACI_FIELD_CONTAINERS_ENVS_NAME: name, config.ACI_FIELD_CONTAINERS_ENVS_VALUE: value, config.ACI_FIELD_CONTAINERS_ENVS_STRATEGY: - "re2" if case_insensitive_dict_get(env_var, "regex") else "string", + env_var.get("strategy", "re2" if (case_insensitive_dict_get(env_var, "regex")) else "string"), }) return env_vars From b4264cbb33976b5bf424229b13ae55f894f62966 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Sat, 20 Sep 2025 15:44:19 +0000 Subject: [PATCH 03/14] Parse parameter values from arm template --- src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py b/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py index 9dc6378f372..7463e3fc5f0 100644 --- a/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py +++ b/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py @@ -29,6 +29,7 @@ def get_parameters( return { parameter_key: ( arm_template_parameters.get("parameters", {}).get(parameter_key, {}).get("value") + or arm_template.get("parameters", {}).get(parameter_key, {}).get("value") or arm_template.get("parameters", {}).get(parameter_key, {}).get("defaultValue") ) for parameter_key in arm_template.get("parameters", {}).keys() From 2194653ba0f7fdcf5133788b42a9481cc0fcdaa4 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Sat, 20 Sep 2025 15:44:52 +0000 Subject: [PATCH 04/14] Avoid adding default fragments twice when loading arm template --- src/confcom/azext_confcom/security_policy.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/confcom/azext_confcom/security_policy.py b/src/confcom/azext_confcom/security_policy.py index c14b90d3f12..9a1d29ed40b 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -660,9 +660,8 @@ def load_policy_from_arm_template_str( debug_mode, disable_stdio, infrastructure_svn, - # This statement covers both if fragments are excluded with - # the flag, and the per container group annotation - not all(fragment in policy_spec.fragments for fragment in fragments), + # Fragments are already parsed + True, )) except Exception as e: eprint(f"Error processing ARM template: {e}") From 87e59ff79c00fd0357e155287fa60cf615b416b1 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Sat, 20 Sep 2025 15:45:01 +0000 Subject: [PATCH 05/14] Avoid adding mounts multiple times --- src/confcom/azext_confcom/container.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/confcom/azext_confcom/container.py b/src/confcom/azext_confcom/container.py index fcdc063f33b..0b252a6b3b9 100644 --- a/src/confcom/azext_confcom/container.py +++ b/src/confcom/azext_confcom/container.py @@ -791,12 +791,17 @@ def from_json( ) -> "UserContainerImage": image = super().from_json(container_json) image.__class__ = UserContainerImage + mount_paths = {m["mountPath"] for m in image.get_mounts()} # inject default mounts for user container - if (image.base not in config.BASELINE_SIDECAR_CONTAINERS) and (not is_vn2): - image.get_mounts().extend(_DEFAULT_MOUNTS) + if image.base not in config.BASELINE_SIDECAR_CONTAINERS and not is_vn2: + for mount in _DEFAULT_MOUNTS: + if mount["mountPath"] not in mount_paths: + image.get_mounts().append(mount) if (image.base not in config.BASELINE_SIDECAR_CONTAINERS) and (is_vn2): - image.get_mounts().extend(_DEFAULT_MOUNTS_VN2) + for mount in _DEFAULT_MOUNTS_VN2: + if mount["mountPath"] not in mount_paths: + image.get_mounts().append(mount) # Start with the customer environment rules env_rules = copy.deepcopy(_INJECTED_CUSTOMER_ENV_RULES) From f2921d267122a030ee38140e7f1cfb0eca8b648f Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Sat, 20 Sep 2025 16:05:09 +0000 Subject: [PATCH 06/14] Avoid adding debug exec processes multiple times --- src/confcom/azext_confcom/security_policy.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/confcom/azext_confcom/security_policy.py b/src/confcom/azext_confcom/security_policy.py index 9a1d29ed40b..a55ebba7a7b 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -871,6 +871,11 @@ def load_policy_from_json( envs += process_env_vars_from_config(container_properties) + if debug_mode: + for exec_process in config.DEBUG_MODE_SETTINGS.get(config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES, []): + if exec_process not in exec_processes: + exec_processes.append(exec_process) + output_containers.append( { config.ACI_FIELD_CONTAINERS_ID: image_name, @@ -882,10 +887,7 @@ def load_policy_from_json( container_properties, config.ACI_FIELD_TEMPLATE_COMMAND ) or [], config.ACI_FIELD_CONTAINERS_MOUNTS: mounts, - config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES: exec_processes - + config.DEBUG_MODE_SETTINGS.get(config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES) - if debug_mode - else exec_processes, + config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES: exec_processes, config.ACI_FIELD_CONTAINERS_SIGNAL_CONTAINER_PROCESSES: [], config.ACI_FIELD_CONTAINERS_ALLOW_STDIO_ACCESS: not disable_stdio, config.ACI_FIELD_CONTAINERS_SECURITY_CONTEXT: case_insensitive_dict_get( From 83b4081ee9419c5b7da46973b33b205fec82bea3 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Tue, 23 Sep 2025 15:51:03 +0000 Subject: [PATCH 07/14] Remove diff_mode arg for loading arm template --- src/confcom/azext_confcom/custom.py | 1 - src/confcom/azext_confcom/security_policy.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/confcom/azext_confcom/custom.py b/src/confcom/azext_confcom/custom.py index 4405fefcc14..5c7d2fe7008 100644 --- a/src/confcom/azext_confcom/custom.py +++ b/src/confcom/azext_confcom/custom.py @@ -123,7 +123,6 @@ def acipolicygen_confcom( debug_mode=debug_mode, disable_stdio=disable_stdio, approve_wildcards=approve_wildcards, - diff_mode=diff, rego_imports=fragments_list, exclude_default_fragments=exclude_default_fragments, ) diff --git a/src/confcom/azext_confcom/security_policy.py b/src/confcom/azext_confcom/security_policy.py index a55ebba7a7b..82bf3ddb3d5 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -628,7 +628,6 @@ def load_policy_from_arm_template_str( debug_mode: bool = False, disable_stdio: bool = False, approve_wildcards: bool = False, - diff_mode: bool = False, rego_imports: Any = None, fragment_contents: Any = None, exclude_default_fragments: bool = False, @@ -681,7 +680,6 @@ def load_policy_from_arm_template_file( debug_mode: bool = False, disable_stdio: bool = False, approve_wildcards: bool = False, - diff_mode: bool = False, rego_imports: list = None, fragment_contents: list = None, exclude_default_fragments: bool = False, @@ -699,7 +697,6 @@ def load_policy_from_arm_template_file( disable_stdio=disable_stdio, approve_wildcards=approve_wildcards, rego_imports=rego_imports, - diff_mode=diff_mode, fragment_contents=fragment_contents, exclude_default_fragments=exclude_default_fragments, ) From e0724ef1f34d284f2fcda56d99bd484d818821fa Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Tue, 23 Sep 2025 16:03:17 +0000 Subject: [PATCH 08/14] Add path as a field of AciFragmentSpec --- src/confcom/azext_confcom/lib/aci_policy_spec.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/confcom/azext_confcom/lib/aci_policy_spec.py b/src/confcom/azext_confcom/lib/aci_policy_spec.py index 67a9a5ad371..a9b61ce69f1 100644 --- a/src/confcom/azext_confcom/lib/aci_policy_spec.py +++ b/src/confcom/azext_confcom/lib/aci_policy_spec.py @@ -64,6 +64,7 @@ class AciFragmentSpec: issuer: str minimum_svn: str includes: list[Literal["containers", "fragments"]] + path: Optional[str] = None @dataclass From 83844a2e06d7171935ecbcc7b275bfd77ad7845e Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Tue, 23 Sep 2025 16:32:22 +0000 Subject: [PATCH 09/14] Respect fragments args --- src/confcom/azext_confcom/custom.py | 2 +- src/confcom/azext_confcom/oras_proxy.py | 2 +- src/confcom/azext_confcom/security_policy.py | 35 +++++++++++--------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/confcom/azext_confcom/custom.py b/src/confcom/azext_confcom/custom.py index 5c7d2fe7008..40cf2ad39b4 100644 --- a/src/confcom/azext_confcom/custom.py +++ b/src/confcom/azext_confcom/custom.py @@ -123,7 +123,7 @@ def acipolicygen_confcom( debug_mode=debug_mode, disable_stdio=disable_stdio, approve_wildcards=approve_wildcards, - rego_imports=fragments_list, + included_fragments=fragments_list, exclude_default_fragments=exclude_default_fragments, ) elif image_name: diff --git a/src/confcom/azext_confcom/oras_proxy.py b/src/confcom/azext_confcom/oras_proxy.py index a53f69127c0..36c9c091dca 100644 --- a/src/confcom/azext_confcom/oras_proxy.py +++ b/src/confcom/azext_confcom/oras_proxy.py @@ -191,7 +191,7 @@ def pull_all_standalone_fragments(fragment_imports): proxy = CoseSignToolProxy() for fragment in fragment_imports: - if fragment in DEFAULT_REGO_FRAGMENTS: + if any(fragment["feed"] == default_fragment["feed"] for default_fragment in DEFAULT_REGO_FRAGMENTS): continue path = fragment.get("path") feed = fragment.get("feed") diff --git a/src/confcom/azext_confcom/security_policy.py b/src/confcom/azext_confcom/security_policy.py index 82bf3ddb3d5..3997943f90d 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -628,28 +628,33 @@ def load_policy_from_arm_template_str( debug_mode: bool = False, disable_stdio: bool = False, approve_wildcards: bool = False, - rego_imports: Any = None, - fragment_contents: Any = None, + included_fragments: list[dict[str, Any]] = None, exclude_default_fragments: bool = False, ) -> List[AciPolicy]: aci_policies = [] - fragments = [ - AciFragmentSpec( - feed=fragment["feed"], - issuer=fragment["issuer"], - includes=fragment["includes"], - minimum_svn=infrastructure_svn or fragment["minimum_svn"], - ) - for fragment in config.DEFAULT_REGO_FRAGMENTS - ] + fragments = sorted([ + AciFragmentSpec( + feed=fragment["feed"], + issuer=fragment["issuer"], + includes=fragment["includes"], + minimum_svn=infrastructure_svn or fragment["minimum_svn"], + path=fragment.get("path"), + ) + for fragment in [ + *included_fragments, + *(config.DEFAULT_REGO_FRAGMENTS if not exclude_default_fragments else []), + ] + ], + key=lambda fragment: fragment.feed, + ) try: for policy_spec in arm_to_aci_policy_spec( arm_template=json.loads(template_data), arm_template_parameters=json.loads(parameter_data) if parameter_data else {}, - fragments=fragments if not exclude_default_fragments else [], + fragments=fragments, debug_mode=debug_mode, allow_stdio_access=not disable_stdio, approve_wildcards=approve_wildcards, @@ -680,8 +685,7 @@ def load_policy_from_arm_template_file( debug_mode: bool = False, disable_stdio: bool = False, approve_wildcards: bool = False, - rego_imports: list = None, - fragment_contents: list = None, + included_fragments: list[dict[str, Any]] = None, exclude_default_fragments: bool = False, ) -> List[AciPolicy]: """Utility function: generate policy object from given arm template and parameter file paths""" @@ -696,8 +700,7 @@ def load_policy_from_arm_template_file( debug_mode=debug_mode, disable_stdio=disable_stdio, approve_wildcards=approve_wildcards, - rego_imports=rego_imports, - fragment_contents=fragment_contents, + included_fragments=included_fragments, exclude_default_fragments=exclude_default_fragments, ) From c2fc0dcebdd7640e163f7558c4f498370bc65d96 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Wed, 24 Sep 2025 19:02:10 +0000 Subject: [PATCH 10/14] Fix case with no fragments --- src/confcom/azext_confcom/security_policy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/confcom/azext_confcom/security_policy.py b/src/confcom/azext_confcom/security_policy.py index 3997943f90d..2cbd6793d2e 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -643,7 +643,7 @@ def load_policy_from_arm_template_str( path=fragment.get("path"), ) for fragment in [ - *included_fragments, + *(included_fragments or []), *(config.DEFAULT_REGO_FRAGMENTS if not exclude_default_fragments else []), ] ], From bd271bddeb3dd09fddda774e37cebe8754ca3330 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Wed, 24 Sep 2025 19:28:54 +0000 Subject: [PATCH 11/14] Satisfy azdev style --- src/confcom/azext_confcom/lib/aci_policy_spec.py | 2 +- .../azext_confcom/lib/arm_to_aci_policy_spec.py | 4 +++- .../lib/image_refs_to_aci_policy_spec.py | 2 +- src/confcom/azext_confcom/security_policy.py | 14 +++++--------- src/confcom/azext_confcom/template_util.py | 1 + 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/confcom/azext_confcom/lib/aci_policy_spec.py b/src/confcom/azext_confcom/lib/aci_policy_spec.py index a9b61ce69f1..3a814fe25ec 100644 --- a/src/confcom/azext_confcom/lib/aci_policy_spec.py +++ b/src/confcom/azext_confcom/lib/aci_policy_spec.py @@ -76,4 +76,4 @@ class AciContainerSpec: @dataclass class AciPolicySpec: fragments: Optional[list[AciFragmentSpec]] - containers: Optional[list[AciContainerSpec]] \ No newline at end of file + containers: Optional[list[AciContainerSpec]] diff --git a/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py b/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py index 7463e3fc5f0..9efbe745d21 100644 --- a/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py +++ b/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py @@ -21,6 +21,7 @@ AciPolicySpec, ) + def get_parameters( arm_template: dict, arm_template_parameters: dict, @@ -96,7 +97,8 @@ def arm_container_volumes_to_aci_policy_spec_volumes( ) ]: yield AciContainerPropertyVolumeMounts( - **{k: v for k, v in vol_mount.items() if v is not None}) + **{k: v for k, v in vol_mount.items() if v is not None} + ) def arm_container_exec_procs_to_aci_policy_spec_exec_procs( diff --git a/src/confcom/azext_confcom/lib/image_refs_to_aci_policy_spec.py b/src/confcom/azext_confcom/lib/image_refs_to_aci_policy_spec.py index f634d7dc926..3efd026291f 100644 --- a/src/confcom/azext_confcom/lib/image_refs_to_aci_policy_spec.py +++ b/src/confcom/azext_confcom/lib/image_refs_to_aci_policy_spec.py @@ -31,4 +31,4 @@ def image_refs_to_aci_policy_spec( image_ref_to_aci_container_spec(image_ref) for image_ref in image_refs ] - ) \ No newline at end of file + ) diff --git a/src/confcom/azext_confcom/security_policy.py b/src/confcom/azext_confcom/security_policy.py index 2cbd6793d2e..206c20f0659 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -6,18 +6,17 @@ import copy import json import warnings +import deepdiff from enum import Enum, auto from typing import Any, Dict, List, Tuple, Union from dataclasses import asdict from azext_confcom.lib.aci_policy_spec import AciFragmentSpec from azext_confcom.lib.arm_to_aci_policy_spec import arm_to_aci_policy_spec -import deepdiff -from azext_confcom import config, os_util +from azext_confcom import (config, os_util) from azext_confcom.container import ContainerImage, UserContainerImage from azext_confcom.errors import eprint from azext_confcom.fragment_util import sanitize_fragment_fields -from azext_confcom.oras_proxy import create_list_of_standalone_imports from azext_confcom.rootfs_proxy import SecurityPolicyProxy from azext_confcom.template_util import (case_insensitive_dict_get, compare_env_vars, @@ -25,22 +24,17 @@ convert_to_pod_spec, decompose_confidential_properties, detect_old_format, - extract_confidential_properties, extract_lifecycle_hook, extract_probe, - extract_standalone_fragments, filter_non_pod_resources, get_container_diff, get_diff_size, get_image_info, get_tar_location_from_mapping, - get_values_for_params, get_volume_claim_templates, is_sidecar, pretty_print_func, print_func, process_configmap, process_env_vars_from_config, - process_env_vars_from_template, process_env_vars_from_yaml, process_fragment_imports, - process_mounts, process_mounts_from_config, readable_diff) from knack.log import get_logger @@ -634,7 +628,8 @@ def load_policy_from_arm_template_str( aci_policies = [] - fragments = sorted([ + fragments = sorted( + [ AciFragmentSpec( feed=fragment["feed"], issuer=fragment["issuer"], @@ -667,6 +662,7 @@ def load_policy_from_arm_template_str( # Fragments are already parsed True, )) + # Catch broad exception since we don't want to assume what errors might occur pylint: disable=W0718 except Exception as e: eprint(f"Error processing ARM template: {e}") diff --git a/src/confcom/azext_confcom/template_util.py b/src/confcom/azext_confcom/template_util.py index 7d645519a1b..3674c498405 100644 --- a/src/confcom/azext_confcom/template_util.py +++ b/src/confcom/azext_confcom/template_util.py @@ -788,6 +788,7 @@ def extract_probe(exec_processes: List[dict], image_properties: dict, probe: str config.ACI_FIELD_CONTAINERS_SIGNAL_CONTAINER_PROCESSES: [], }) + def get_probe_exec_processes(image_properties: dict) -> List[dict]: exec_processes: List[dict] = [] extract_probe(exec_processes, image_properties, config.ACI_FIELD_CONTAINERS_READINESS_PROBE) From 41babb3296e0236db2c33381503ecb76651c3e7e Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Mon, 29 Sep 2025 15:58:23 +0000 Subject: [PATCH 12/14] Preserve arbitrary orders to break dependency on fixes --- .../lib/arm_to_aci_policy_spec.py | 4 --- src/confcom/azext_confcom/security_policy.py | 29 +++++++++---------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py b/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py index 9efbe745d21..7e256438f47 100644 --- a/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py +++ b/src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py @@ -75,10 +75,6 @@ def arm_container_env_to_aci_policy_spec_env( for env_var in [ *process_env_vars_from_template(parameters, {}, container_properties, approve_wildcards), - *config.OPENGCS_ENV_RULES, - *config.FABRIC_ENV_RULES, - *config.MANAGED_IDENTITY_ENV_RULES, - *config.ENABLE_RESTART_ENV_RULE, ]: yield AciContainerPropertyEnvVariable(**env_var) diff --git a/src/confcom/azext_confcom/security_policy.py b/src/confcom/azext_confcom/security_policy.py index 206c20f0659..5b655a605b6 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -628,22 +628,19 @@ def load_policy_from_arm_template_str( aci_policies = [] - fragments = sorted( - [ - AciFragmentSpec( - feed=fragment["feed"], - issuer=fragment["issuer"], - includes=fragment["includes"], - minimum_svn=infrastructure_svn or fragment["minimum_svn"], - path=fragment.get("path"), - ) - for fragment in [ - *(included_fragments or []), - *(config.DEFAULT_REGO_FRAGMENTS if not exclude_default_fragments else []), - ] - ], - key=lambda fragment: fragment.feed, - ) + fragments = [ + AciFragmentSpec( + feed=fragment["feed"], + issuer=fragment["issuer"], + includes=fragment["includes"], + minimum_svn=infrastructure_svn or fragment["minimum_svn"], + path=fragment.get("path"), + ) + for fragment in [ + *(config.DEFAULT_REGO_FRAGMENTS if not exclude_default_fragments else []), + *(included_fragments or []), + ] + ] try: for policy_spec in arm_to_aci_policy_spec( From f6fb9658a53fe949cb605c50998dd47f0e1a4b41 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Wed, 1 Oct 2025 08:40:38 +0000 Subject: [PATCH 13/14] Restore the functionality of --diff --- src/confcom/azext_confcom/custom.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/confcom/azext_confcom/custom.py b/src/confcom/azext_confcom/custom.py index 40cf2ad39b4..6a035ad50c5 100644 --- a/src/confcom/azext_confcom/custom.py +++ b/src/confcom/azext_confcom/custom.py @@ -3,6 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- +import json import os import sys @@ -17,7 +18,7 @@ from azext_confcom.kata_proxy import KataPolicyGenProxy from azext_confcom.security_policy import OutputType from azext_confcom.template_util import ( - get_image_name, inject_policy_into_template, inject_policy_into_yaml, + extract_confidential_properties, get_image_name, inject_policy_into_template, inject_policy_into_yaml, pretty_print_func, print_existing_policy_from_arm_template, print_existing_policy_from_yaml, print_func, str_to_sha256) from knack.log import get_logger @@ -167,7 +168,22 @@ def acipolicygen_confcom( for policy in container_group_policies: policy.set_fragment_contents(fragment_policy_list) - for count, policy in enumerate(container_group_policies): + for idx, policy in enumerate(container_group_policies): + + # We will deprecate diff mode in favour of a separate tool, so we want + # supporting code to be all in one place even if it makes it more nasty + if diff: + if arm_template: + with open(arm_template, 'r') as f: + policy._existing_cce_policy = extract_confidential_properties( + [r for r in json.load(f)["resources"] if r["type"] in { + "Microsoft.ContainerInstance/containerGroups", + "Microsoft.ContainerInstance/containerGroupProfiles", + }][idx].get("properties", {}))[0] + + elif virtual_node_yaml_path: + ... # diff mode is handled in the load function + # this is where parameters and variables are populated policy.populate_policy_content_for_all_images( individual_image=bool(image_name), tar_mapping=tar_mapping, faster_hashing=faster_hashing @@ -177,7 +193,7 @@ def acipolicygen_confcom( exit_code = validate_sidecar_in_policy(policy, output_type == security_policy.OutputType.PRETTY_PRINT) elif virtual_node_yaml_path and not (print_policy_to_terminal or outraw or outraw_pretty_print or diff): result = inject_policy_into_yaml( - virtual_node_yaml_path, policy.get_serialized_output(omit_id=omit_id), count + virtual_node_yaml_path, policy.get_serialized_output(omit_id=omit_id), idx ) if result: print(str_to_sha256(policy.get_serialized_output(OutputType.RAW, omit_id=omit_id))) @@ -186,7 +202,7 @@ def acipolicygen_confcom( exit_code = get_diff_outputs(policy, output_type == security_policy.OutputType.PRETTY_PRINT) elif arm_template and not (print_policy_to_terminal or outraw or outraw_pretty_print): result = inject_policy_into_template(arm_template, arm_template_parameters, - policy.get_serialized_output(omit_id=omit_id), count) + policy.get_serialized_output(omit_id=omit_id), idx) if result: # this is always going to be the unencoded policy print(str_to_sha256(policy.get_serialized_output(OutputType.RAW, omit_id=omit_id))) From 7d62b05bd04d2a6cd41d0c3f3c38a62f395aafc7 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Wed, 1 Oct 2025 09:57:11 +0000 Subject: [PATCH 14/14] Satisfy azdev style --- src/confcom/azext_confcom/custom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/confcom/azext_confcom/custom.py b/src/confcom/azext_confcom/custom.py index 6a035ad50c5..7dabb5f3a89 100644 --- a/src/confcom/azext_confcom/custom.py +++ b/src/confcom/azext_confcom/custom.py @@ -175,6 +175,7 @@ def acipolicygen_confcom( if diff: if arm_template: with open(arm_template, 'r') as f: + # pylint: disable=protected-access policy._existing_cce_policy = extract_confidential_properties( [r for r in json.load(f)["resources"] if r["type"] in { "Microsoft.ContainerInstance/containerGroups", @@ -182,7 +183,7 @@ def acipolicygen_confcom( }][idx].get("properties", {}))[0] elif virtual_node_yaml_path: - ... # diff mode is handled in the load function + ... # diff mode is handled in the load function # this is where parameters and variables are populated policy.populate_policy_content_for_all_images(