From 903d348d6a325d042ea6ebbd8ce59734bdf649a9 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Thu, 18 Sep 2025 13:16:25 +0000 Subject: [PATCH 01/25] 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/25] 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/25] 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/25] 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/25] 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/25] 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/25] 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/25] 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/25] 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/25] 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/25] 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/25] 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/25] 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/25] 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( From 0d73ba96f3bf8bb1abcc1f1ca5aabe955e4c4d28 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Wed, 1 Oct 2025 13:37:53 +0000 Subject: [PATCH 15/25] Only use infrastructure-svn on the infrastructure fragment --- src/confcom/azext_confcom/security_policy.py | 23 ++++++++------------ 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/confcom/azext_confcom/security_policy.py b/src/confcom/azext_confcom/security_policy.py index 5b655a605b6..97126213c72 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -628,25 +628,20 @@ def load_policy_from_arm_template_str( aci_policies = [] - 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 []), - ] - ] + if included_fragments is None: + included_fragments = [] + + if not exclude_default_fragments: + for idx, fragment in enumerate(config.DEFAULT_REGO_FRAGMENTS): + if infrastructure_svn: + fragment["minimum_svn"] = infrastructure_svn + included_fragments.insert(idx, fragment) 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, + fragments=[AciFragmentSpec(**fragment) for fragment in included_fragments], debug_mode=debug_mode, allow_stdio_access=not disable_stdio, approve_wildcards=approve_wildcards, From 9cfd2d6a2eaa58d492d497ba2d3331222edd4ebe Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Wed, 1 Oct 2025 15:03:34 +0000 Subject: [PATCH 16/25] Add mypy tests and fix the couple of issues it finds --- .../azext_confcom/lib/aci_policy_spec.py | 2 +- .../lib/arm_to_aci_policy_spec.py | 10 ++- .../tests/latest/test_confcom_types.py | 68 +++++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 src/confcom/azext_confcom/tests/latest/test_confcom_types.py diff --git a/src/confcom/azext_confcom/lib/aci_policy_spec.py b/src/confcom/azext_confcom/lib/aci_policy_spec.py index 3a814fe25ec..17242ac43de 100644 --- a/src/confcom/azext_confcom/lib/aci_policy_spec.py +++ b/src/confcom/azext_confcom/lib/aci_policy_spec.py @@ -8,7 +8,7 @@ class AciContainerPropertyEnvVariable: name: str value: str strategy: str - required: bool = False + required: Optional[bool] = False @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 7e256438f47..2aff1531be2 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 @@ -76,7 +76,13 @@ def arm_container_env_to_aci_policy_spec_env( for env_var in [ *process_env_vars_from_template(parameters, {}, container_properties, approve_wildcards), ]: - yield AciContainerPropertyEnvVariable(**env_var) + yield AciContainerPropertyEnvVariable( + # At time of writing, we only get env vars from process_env_vars_from_template + # which never specifies "required", however futures sources might so + # we need to handle both in a way the type system can understand + required=bool(env_var.pop("required")) if "required" in env_var else None, + **env_var + ) def arm_container_volumes_to_aci_policy_spec_volumes( @@ -100,7 +106,7 @@ def arm_container_volumes_to_aci_policy_spec_volumes( def arm_container_exec_procs_to_aci_policy_spec_exec_procs( container_properties: dict, debug_mode: bool, -) -> Iterator[AciContainerPropertyVolumeMounts]: +) -> Iterator[AciContainerPropertyExecProcesses]: for exec_process in [ *container_properties.get("execProcesses", []), diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_types.py b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py new file mode 100644 index 00000000000..c2b4f688a88 --- /dev/null +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py @@ -0,0 +1,68 @@ +from pathlib import Path + +import pytest + +mypy = pytest.importorskip("mypy") +from mypy import api as mypy_api # pylint: disable=wrong-import-position + +AZEXT_ROOT = Path(__file__).resolve().parents[2] +LIB_ROOT = AZEXT_ROOT / "lib" +MYPY_ARGS = [ + "--explicit-package-bases", +] + + +def _discover_modules() -> list[tuple[str, Path]]: + modules: list[tuple[str, Path]] = [] + for path in sorted(AZEXT_ROOT.rglob("*.py")): + if not path.is_file(): + continue + rel = path.relative_to(AZEXT_ROOT) + module_name = f"{rel.with_suffix('').as_posix().replace('/', '.')}" + modules.append((module_name, path)) + return modules + + +MODULE_PATHS = _discover_modules() + + +# These files already existed when this test was added and will be fixed incrementally +BAD_MODULES = { + "__init__", + "_params", + "_validators", + "_help", + "config", + "container", + "cose_proxy", + "custom", + "errors", + "fragment_util", + "init_checks", + "kata_proxy", + "oras_proxy", + "os_util", + "rootfs_proxy", + "security_policy", + "template_util", + "tests" +} + + +@pytest.mark.parametrize( + "module_name, target_path", + [pytest.param(name, path, id=name) for name, path in MODULE_PATHS], +) +def test_mypy(module_name: str, target_path: Path): + assert MODULE_PATHS, f"No Python files discovered under {LIB_ROOT}" + + if any(module_name.startswith(bad_module) for bad_module in BAD_MODULES): + pytest.skip(f"Skipping mypy test for {module_name} due to known issues") + + stdout, stderr, exit_status = mypy_api.run([*MYPY_ARGS, str(target_path)]) + assert exit_status == 0, ( + f"mypy reported issues for {module_name}\n" + f"command: mypy {' '.join(MYPY_ARGS + [str(target_path)])}\n" + f"stdout:\n{stdout}\n" + f"stderr:\n{stderr}" + ) From b08b01294c83a6cb8f009cf7acdb2a302cb3843c Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Wed, 1 Oct 2025 15:05:03 +0000 Subject: [PATCH 17/25] Fix error message --- 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 97126213c72..0ddcb1dfb7a 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -660,7 +660,7 @@ def load_policy_from_arm_template_str( if len(aci_policies) == 0: eprint( - f'Field ["type"] must contain one of {config.ACI_FIELD_SUPPORTED_RESOURCES}' + f'At least one resource must have ["type"] equalling one of {config.ACI_FIELD_SUPPORTED_RESOURCES}' ) return aci_policies From 3a0701a9d8ed19c26a962be9bb2d89273c7f0342 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Wed, 1 Oct 2025 15:22:19 +0000 Subject: [PATCH 18/25] Prevent the new test failing at the --discover step --- .../azext_confcom/tests/latest/test_confcom_types.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_types.py b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py index c2b4f688a88..248377aa9ec 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_types.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py @@ -2,8 +2,11 @@ import pytest -mypy = pytest.importorskip("mypy") -from mypy import api as mypy_api # pylint: disable=wrong-import-position +try: + mypy = pytest.importorskip("mypy") + from mypy import api as mypy_api # pylint: disable=wrong-import-position +except (ImportError, pytest.skip.Exception): + mypy_api = None AZEXT_ROOT = Path(__file__).resolve().parents[2] LIB_ROOT = AZEXT_ROOT / "lib" From 21989a33d318639a797cce7ffe1796be71f82c77 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Wed, 1 Oct 2025 15:40:53 +0000 Subject: [PATCH 19/25] Fix skipping behaviour --- .../azext_confcom/tests/latest/test_confcom_types.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_types.py b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py index 248377aa9ec..00f4acfb32e 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_types.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py @@ -3,11 +3,17 @@ import pytest try: - mypy = pytest.importorskip("mypy") - from mypy import api as mypy_api # pylint: disable=wrong-import-position -except (ImportError, pytest.skip.Exception): + from mypy import api as mypy_api +except ImportError: mypy_api = None + +@pytest.fixture(scope="module", autouse=True) +def check_mypy(): + if mypy_api is None: + pytest.skip("missing mypy import") + + AZEXT_ROOT = Path(__file__).resolve().parents[2] LIB_ROOT = AZEXT_ROOT / "lib" MYPY_ARGS = [ From 65c52865af0bc666bbbb5e68402f7ff39e199783 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Thu, 2 Oct 2025 06:29:05 +0000 Subject: [PATCH 20/25] Clean up test --- src/confcom/azext_confcom/tests/latest/test_confcom_types.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_types.py b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py index 00f4acfb32e..5dee0f44718 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_types.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py @@ -15,7 +15,6 @@ def check_mypy(): AZEXT_ROOT = Path(__file__).resolve().parents[2] -LIB_ROOT = AZEXT_ROOT / "lib" MYPY_ARGS = [ "--explicit-package-bases", ] @@ -63,7 +62,6 @@ def _discover_modules() -> list[tuple[str, Path]]: [pytest.param(name, path, id=name) for name, path in MODULE_PATHS], ) def test_mypy(module_name: str, target_path: Path): - assert MODULE_PATHS, f"No Python files discovered under {LIB_ROOT}" if any(module_name.startswith(bad_module) for bad_module in BAD_MODULES): pytest.skip(f"Skipping mypy test for {module_name} due to known issues") From 4e2eba1262988a8ffcc253dc48e15f4223b298da Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Thu, 2 Oct 2025 07:18:52 +0000 Subject: [PATCH 21/25] Add missing licenses --- src/confcom/azext_confcom/lib/aci_policy_spec.py | 5 +++++ src/confcom/azext_confcom/lib/arm_to_aci_policy_spec.py | 7 ++++++- .../azext_confcom/lib/image_refs_to_aci_policy_spec.py | 5 +++++ .../azext_confcom/tests/latest/test_confcom_types.py | 5 +++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/confcom/azext_confcom/lib/aci_policy_spec.py b/src/confcom/azext_confcom/lib/aci_policy_spec.py index 17242ac43de..52c39a1a979 100644 --- a/src/confcom/azext_confcom/lib/aci_policy_spec.py +++ b/src/confcom/azext_confcom/lib/aci_policy_spec.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from dataclasses import dataclass from typing import Optional from typing_extensions import Literal 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 2aff1531be2..c2711563f30 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 @@ -1,4 +1,9 @@ -from typing import Iterator, Optional +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from typing import Iterator import json import re from azext_confcom import config 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 3efd026291f..875d8a9ccc7 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 @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from azext_confcom.lib.aci_policy_spec import ( AciContainerSpec, AciContainerProperties, diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_types.py b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py index 5dee0f44718..1e1eb1527da 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_types.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from pathlib import Path import pytest From c940d6c89b14869168373cf9808e5cd17b339ff9 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Thu, 2 Oct 2025 08:33:04 +0000 Subject: [PATCH 22/25] Don't consider test files for mypy tests --- src/confcom/azext_confcom/tests/latest/test_confcom_types.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_types.py b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py index 1e1eb1527da..645c075c64b 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_types.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_types.py @@ -31,6 +31,8 @@ def _discover_modules() -> list[tuple[str, Path]]: if not path.is_file(): continue rel = path.relative_to(AZEXT_ROOT) + if rel.parts[0] == "tests": + continue module_name = f"{rel.with_suffix('').as_posix().replace('/', '.')}" modules.append((module_name, path)) return modules @@ -58,7 +60,6 @@ def _discover_modules() -> list[tuple[str, Path]]: "rootfs_proxy", "security_policy", "template_util", - "tests" } From 19a55ae91a87e428d19dbec4e2cf6f7a8da560ae Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Fri, 17 Oct 2025 16:29:48 +0000 Subject: [PATCH 23/25] Retrigger CI --- 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 f1e66afc3ce..c7cc65036d6 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -13,7 +13,7 @@ from azext_confcom.lib.aci_policy_spec import AciFragmentSpec from azext_confcom.lib.arm_to_aci_policy_spec import arm_to_aci_policy_spec -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 bb06acddee5691eaba50a935430c618a92935b95 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Tue, 28 Oct 2025 12:56:53 +0000 Subject: [PATCH 24/25] Bump version and history --- src/confcom/HISTORY.rst | 4 ++++ src/confcom/setup.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/confcom/HISTORY.rst b/src/confcom/HISTORY.rst index f45806e4666..89969d14df2 100644 --- a/src/confcom/HISTORY.rst +++ b/src/confcom/HISTORY.rst @@ -3,6 +3,10 @@ Release History =============== +1.3.1 +++++++ +* Define a clear specification for acipolicygen input and use this when parsing ARM templates + 1.3.0 ++++++ * Add a new --enable-stdio flag, with a warning if neither this or --disable-stdio is set diff --git a/src/confcom/setup.py b/src/confcom/setup.py index df6c58e0180..21014bd893c 100644 --- a/src/confcom/setup.py +++ b/src/confcom/setup.py @@ -19,7 +19,7 @@ logger.warn("Wheel is not available, disabling bdist_wheel hook") -VERSION = "1.3.0" +VERSION = "1.3.1" # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers From ab711ab2a53389a887ea3441ad31064e26767fa3 Mon Sep 17 00:00:00 2001 From: Dominic Ayre Date: Tue, 28 Oct 2025 13:02:09 +0000 Subject: [PATCH 25/25] Bump version after merge of main --- src/confcom/HISTORY.rst | 2 +- src/confcom/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/confcom/HISTORY.rst b/src/confcom/HISTORY.rst index 89969d14df2..b902f33bd64 100644 --- a/src/confcom/HISTORY.rst +++ b/src/confcom/HISTORY.rst @@ -3,7 +3,7 @@ Release History =============== -1.3.1 +1.3.2 ++++++ * Define a clear specification for acipolicygen input and use this when parsing ARM templates diff --git a/src/confcom/setup.py b/src/confcom/setup.py index 21014bd893c..f89ef9d2071 100644 --- a/src/confcom/setup.py +++ b/src/confcom/setup.py @@ -19,7 +19,7 @@ logger.warn("Wheel is not available, disabling bdist_wheel hook") -VERSION = "1.3.1" +VERSION = "1.3.2" # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers