diff --git a/src/confcom/HISTORY.rst b/src/confcom/HISTORY.rst index 8704b22645a..1c00993a147 100644 --- a/src/confcom/HISTORY.rst +++ b/src/confcom/HISTORY.rst @@ -3,6 +3,13 @@ Release History =============== +1.2.5 +++++++ +* consolidating functions for --input policygen +* bugfix for "scenario" field in json input +* updating tests and examples to use azurelinux +* "name" field is required when using --input + 1.2.4 ++++++ * rolling back genpolicy version for Azure Linux V2 support instead of V3 diff --git a/src/confcom/azext_confcom/README.md b/src/confcom/azext_confcom/README.md index 27f7e76f384..ba173e90365 100644 --- a/src/confcom/azext_confcom/README.md +++ b/src/confcom/azext_confcom/README.md @@ -252,6 +252,69 @@ Because the ARM Template does not have a value defined for the "placeholderValue az deployment group create --template-file "template.json" --parameters "parameters.json" ``` +Example 13: Another way to add additional flexibility to a security policy is by using a "pure json" approach to the config file. +This gives the flexibility of using regular expressions for environment variables and including fragments without the need for the `--fragments-json` flag. +It uses the same format as `acifragmentgen` such that if there needs to be different deployments with similar configs, very few changes are needed. +Note that a name for each container is required. + +```json +{ + "fragments": [ + { + "feed": "contoso.azurecr.io/example", + "includes": [ + "containers", + "fragments" + ], + "issuer": "did:x509:0:sha256:mLzv0uyBNQvC6hi4y9qy8hr6NSZuYFv6gfCwAEWBNqc::subject:CN:Contoso", + "minimum_svn": "1" + } + ], + "containers": [ + { + "name": "my-image", + "properties": { + "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.9", + "environmentVariables": [ + { + "name": "PATH", + "value": "/customized/path/value" + }, + { + "name": "TEST_REGEXP_ENV", + "value": "test_regexp_env(.*)", + "regex": true + } + ], + "execProcesses": [ + { + "command": [ + "ls" + ] + } + ], + "volumeMounts": [ + { + "name": "mymount", + "mountPath": "/mount/mymount", + "mountType": "emptyDir", + "readOnly": false + } + ], + "command": [ + "python3", + "main.py" + ] + } + } + ] +} +``` + +```bash +az confcom acipolicygen -i config.json +``` + ## dmverity Layer Hashing To ensure the container that is being deployed is the intended container, the `confcom` tooling uses [dmverity hashing](https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html). This is done by downloading the container locally with the Docker Daemon (or using a pre-downloaded tar file of the OCI image) and performing the dmverity hashing using the [dmverity-vhd tool](https://github.com/microsoft/hcsshim/tree/main/cmd/dmverity-vhd). These layer hashes are placed into the Rego security policy in the "layers" field of their respective container. Note that these dmverity layer hashes are different than the layer hashes reported by `docker image inspect`. @@ -287,18 +350,32 @@ To generate a security policy using a policy config file for Virtual Node, the ` { "name": "my-image", "properties": { - "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.8" + "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.9" } } ] } ``` -This `scenario` field adds the necessary environment variables and mount values to containers in the config file. +This `scenario` field adds the necessary environment variables and mount values to containers in the config file. Currently `vn2` and `aci` are the only supported values for `scenario`, but others may be added in the future as more products onboard to the `confcom` extension. `aci` is the default value. ### Workload Identity To use workload identities with VN2, the associated label [described here](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview?tabs=dotnet#pod-labels) must be present. Having this will add the requisite environment variables and mounts to each container's policy. +To generate a policy with workload identity capabilities for VN2 using the JSON format, the following `labels` key and nested values must be present: + +```json +{ + "version": "1.0", + "scenario": "vn2", + "labels": { + "azure.workload.identity/use": true + }, + "containers": [ + ... + ] +} +``` > [!NOTE] > The `acipolicygen` command is specific to generating policies for ACI-based containers. For generating security policies for the [Confidential Containers on AKS](https://learn.microsoft.com/en-us/azure/aks/confidential-containers-overview) feature, use the `katapolicygen` command. @@ -698,29 +775,29 @@ The config file is a JSON file that contains the following information: ```json { - "containers": [ - { - "name": "my-image", - "properties": { - "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.8", - "environmentVariables": [ - { - "name": "PATH", - "value": "/customized/path/value" - }, - { - "name": "TEST_REGEXP_ENV", - "value": "test_regexp_env(.*)", - "regex": true - } - ], - "command": [ - "python3", - "main.py" + "containers": [ + { + "name": "my-image", + "properties": { + "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.9", + "environmentVariables": [ + { + "name": "PATH", + "value": "/customized/path/value" + }, + { + "name": "TEST_REGEXP_ENV", + "value": "test_regexp_env(.*)", + "regex": true + } + ], + "command": [ + "python3", + "main.py" + ] + } + } ] - } - } - ] } ``` @@ -766,6 +843,29 @@ az confcom acifragmentgen --chain ./samples/certs/intermediateCA/certs/www.conto This could be useful in scenarios where an image-attached fragment is required but the fragment's feed is different from the image's location. +Example 5: This format can also be used to generate fragments used for VN2. Adding the `scenario` key with the value `vn2` tells confcom which default values need to be added. Save this file as `fragment_config.json`: + +```json +{ + "version": "1.0", + "scenario": "vn2", + "containers": [ + { + "name": "my-image", + "properties": { + "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.9" + } + } + ] +} +``` + +Using the same command, the default mounts and environment variables used by VN2 will be added to the policy fragment. + +```bash +az confcom acifragmentgen --input ./fragment_config.json --svn 1 --namespace contoso +``` + ## Microsoft Azure CLI 'confcom katapolicygen' Extension Examples Run `az confcom katapolicygen --help` to see a list of supported arguments along with explanations. The following commands demonstrate the usage of different arguments to generate confidential computing security policies. @@ -809,11 +909,11 @@ The `--print-policy` argument is included to display the policy on the command l Example 2: This command injects a security policy into the pod spec based on input from a config map so that there is no need to change the pod spec to pass variables into the security policy: ```bash -az confcom katapolicygen -y .\\pod.yaml -c .\\config-map.yaml +az confcom katapolicygen -y ./pod.yaml -c ./config-map.yaml ``` Example 3: This command caches the layer hashes and stores them locally on your computer to make future computations faster if the same images are used: ```bash -az confcom katapolicygen -y .\\pod.yaml -u +az confcom katapolicygen -y ./pod.yaml -u ``` diff --git a/src/confcom/azext_confcom/cose_proxy.py b/src/confcom/azext_confcom/cose_proxy.py index e026219398a..8278ccc6083 100644 --- a/src/confcom/azext_confcom/cose_proxy.py +++ b/src/confcom/azext_confcom/cose_proxy.py @@ -143,8 +143,7 @@ def create_issuer(self, cert_path: str) -> str: return item.stdout.decode("utf-8") # generate an import statement from a signed policy fragment - def generate_import_from_path(self, fragment_path: str, minimum_svn: int) -> str: - # TODO: make sure the fragment is signed correctly + def generate_import_from_path(self, fragment_path: str, minimum_svn: str) -> str: if not os.path.exists(fragment_path): eprint(f"The fragment file at {fragment_path} does not exist") diff --git a/src/confcom/azext_confcom/custom.py b/src/confcom/azext_confcom/custom.py index 8b969bfee67..cac4f53f977 100644 --- a/src/confcom/azext_confcom/custom.py +++ b/src/confcom/azext_confcom/custom.py @@ -115,7 +115,7 @@ def acipolicygen_confcom( ) # error checking for making sure an input is provided is above if input_path: - container_group_policies = security_policy.load_policy_from_file( + container_group_policies = security_policy.load_policy_from_json_file( input_path, debug_mode=debug_mode, infrastructure_svn=infrastructure_svn, @@ -228,7 +228,7 @@ def acifragmentgen_confcom( feed: str, key: str, chain: str, - minimum_svn: int, + minimum_svn: str, image_target: str = "", algo: str = "ES384", fragment_path: str = None, @@ -292,7 +292,7 @@ def acifragmentgen_confcom( # this is using --input if not tar_mapping: tar_mapping = os_util.load_tar_mapping_from_config_file(input_path) - policy = security_policy.load_policy_from_config_file( + policy = security_policy.load_policy_from_json_file( input_path, debug_mode=debug_mode, disable_stdio=disable_stdio ) # get all of the fragments that are being used in the policy diff --git a/src/confcom/azext_confcom/data/internal_config.json b/src/confcom/azext_confcom/data/internal_config.json index 051eda024d0..598e47d5f32 100644 --- a/src/confcom/azext_confcom/data/internal_config.json +++ b/src/confcom/azext_confcom/data/internal_config.json @@ -1,5 +1,5 @@ { - "version": "1.2.4", + "version": "1.2.5", "hcsshim_config": { "maxVersion": "1.0.0", "minVersion": "0.0.1" diff --git a/src/confcom/azext_confcom/oras_proxy.py b/src/confcom/azext_confcom/oras_proxy.py index 0622c1456b2..28e06e6f118 100644 --- a/src/confcom/azext_confcom/oras_proxy.py +++ b/src/confcom/azext_confcom/oras_proxy.py @@ -175,7 +175,7 @@ def attach_fragment_to_image(image_name: str, filename: str): print(f"Fragment attached to image '{image_name}' with Digest:{digest}") -def generate_imports_from_image_name(image_name: str, minimum_svn: int) -> List[dict]: +def generate_imports_from_image_name(image_name: str, minimum_svn: str) -> List[dict]: cose_proxy = CoseSignToolProxy() fragment_hashes = discover(image_name) import_list = [] diff --git a/src/confcom/azext_confcom/security_policy.py b/src/confcom/azext_confcom/security_policy.py index 6d6d49cdf4a..32328dc0da1 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -42,6 +42,8 @@ process_mounts_from_config, process_fragment_imports, get_container_diff, + convert_config_v0_to_v1, + detect_old_format, ) from azext_confcom.rootfs_proxy import SecurityPolicyProxy @@ -771,7 +773,7 @@ def load_policy_from_arm_template_str( 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("execProcesses") + + config.DEBUG_MODE_SETTINGS.get(config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES) if debug_mode else exec_processes, config.ACI_FIELD_CONTAINERS_SIGNAL_CONTAINER_PROCESSES: [], @@ -831,25 +833,6 @@ def load_policy_from_arm_template_file( ) -def load_policy_from_file( - path: str, - debug_mode: bool = False, - disable_stdio: bool = False, - infrastructure_svn: str = None, - exclude_default_fragments: bool = False, -) -> AciPolicy: - """Utility function: generate policy object from given json file path""" - policy_input_json = os_util.load_str_from_file(path) - - return load_policy_from_str( - policy_input_json, - debug_mode=debug_mode, - disable_stdio=disable_stdio, - infrastructure_svn=infrastructure_svn, - exclude_default_fragments=exclude_default_fragments, - ) - - def load_policy_from_image_name( image_names: Union[List[str], str], debug_mode: bool = False, disable_stdio: bool = False ) -> AciPolicy: @@ -885,16 +868,40 @@ def load_policy_from_image_name( ) -def load_policy_from_str( +def load_policy_from_json_file( data: str, debug_mode: bool = False, disable_stdio: bool = False, infrastructure_svn: str = None, exclude_default_fragments: bool = False, ) -> AciPolicy: - """Utility function: generate policy object from given json string""" + json_content = os_util.load_str_from_file(data) + return load_policy_from_json( + json_content, + debug_mode=debug_mode, + disable_stdio=disable_stdio, + infrastructure_svn=infrastructure_svn, + exclude_default_fragments=exclude_default_fragments + ) + + +def load_policy_from_json( + data: str, + debug_mode: bool = False, + disable_stdio: bool = False, + infrastructure_svn: str = None, + exclude_default_fragments: bool = False, +) -> AciPolicy: + output_containers = [] + # 1) Parse incoming string as JSON policy_input_json = os_util.load_json_from_str(data) - containers = case_insensitive_dict_get( + + is_old_format = detect_old_format(policy_input_json) + if is_old_format: + policy_input_json = convert_config_v0_to_v1(policy_input_json) + + # 2) Extract top-level fields + input_containers = case_insensitive_dict_get( policy_input_json, config.ACI_FIELD_CONTAINERS ) or [] @@ -909,116 +916,107 @@ def load_policy_from_str( policy_input_json, config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS ) or [] - if rego_fragments: - if not isinstance(rego_fragments, list): - eprint( - f'Field ["{config.ACI_FIELD_CONTAINERS}"]' - + f'["{config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS}"]' - + "can only be a list." - ) - - for fragment in rego_fragments: - feed = case_insensitive_dict_get( - fragment, config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS_FEED - ) - if not isinstance(feed, str): - eprint( - f'Field ["{config.ACI_FIELD_CONTAINERS}"]' - + f'["{config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS}"]' - + f'["{config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS}"]' - + "can only be a string value." - ) - - iss = case_insensitive_dict_get( - fragment, config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS_ISS - ) or case_insensitive_dict_get(fragment, config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS_ISSUER) - if not isinstance(iss, str): - eprint( - f'Field ["{config.ACI_FIELD_CONTAINERS}"]' - + f'["{config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS}"]' - + f'["{config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS_ISS}"]' - + "can only be a string value." - ) - - minimum_svn = case_insensitive_dict_get( - fragment, config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS_MINIMUM_SVN - ) or case_insensitive_dict_get(fragment, config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS_MINIMUM_SVN) - if not isinstance(minimum_svn, str): - eprint( - f'Field ["{config.ACI_FIELD_CONTAINERS}"]' - + f'["{config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS}"]' - + f'["{config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS_MINIMUM_SVN}"]' - + "can only be a string value." - ) + scenario = case_insensitive_dict_get( + policy_input_json, config.ACI_FIELD_SCENARIO + ) or "" - includes = case_insensitive_dict_get( - fragment, config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS_INCLUDES - ) - if not isinstance(includes, list): - eprint( - f'Field ["{config.ACI_FIELD_CONTAINERS}"]' - + f'["{config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS}"]' - + f'["{config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS_INCLUDES}"]' - + "can only be a list." - ) + # 3) Process rego_fragments + if rego_fragments: + process_fragment_imports(rego_fragments) - if not containers and not rego_fragments: + if not input_containers and not rego_fragments: eprint( f'Field ["{config.ACI_FIELD_CONTAINERS}"]' + f' and field ["{config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS}"] can not both be empty.' ) - for container in containers: - image_properties = case_insensitive_dict_get(container, config.ACI_FIELD_TEMPLATE_PROPERTIES) + for container in input_containers: + container_properties = case_insensitive_dict_get( + container, config.ACI_FIELD_TEMPLATE_PROPERTIES + ) + image_name = case_insensitive_dict_get( - container, config.ACI_FIELD_CONTAINERS_CONTAINERIMAGE - ) or case_insensitive_dict_get(image_properties, config.ACI_FIELD_TEMPLATE_IMAGE) + container_properties, config.ACI_FIELD_TEMPLATE_IMAGE + ) + + if not image_name: + eprint( + f'Field ["{config.ACI_FIELD_TEMPLATE_IMAGE}"] is empty or cannot be found' + ) container_name = case_insensitive_dict_get( container, config.ACI_FIELD_CONTAINERS_NAME ) or image_name - if not image_name: - eprint( - f'Field ["{config.ACI_FIELD_CONTAINERS_CONTAINERIMAGE}"] is empty or can not be found.' - ) - container[config.ACI_FIELD_CONTAINERS_ID] = image_name - container[config.ACI_FIELD_CONTAINERS_NAME] = container_name + if not container_name: + eprint(f'Field ["{config.ACI_FIELD_CONTAINERS_NAME}"] is empty or cannot be found') - # set the fields that are present in the container but not in the - # config - container[config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES] = container.get( - config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES, []) + ( - config.DEBUG_MODE_SETTINGS.get("execProcesses") if debug_mode else [] - ) - container[config.ACI_FIELD_CONTAINERS_SIGNAL_CONTAINER_PROCESSES] = [] + exec_processes = case_insensitive_dict_get( + container_properties, config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES + ) or [] - if image_properties: - 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) - container[config.ACI_FIELD_CONTAINERS_CONTAINERIMAGE] = image_name - container[config.ACI_FIELD_CONTAINERS_ENVS] = process_env_vars_from_config(image_properties) - container[config.ACI_FIELD_CONTAINERS_COMMAND] = case_insensitive_dict_get( - image_properties, config.ACI_FIELD_TEMPLATE_COMMAND - ) or [] - container[config.ACI_FIELD_CONTAINERS_MOUNTS] = ( - process_mounts_from_config(image_properties) + - process_configmap(image_properties) - ) - container[config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES] = ( - exec_processes + - config.DEBUG_MODE_SETTINGS.get("execProcesses") - if debug_mode else exec_processes - ) - container[config.ACI_FIELD_CONTAINERS_ALLOW_STDIO_ACCESS] = not disable_stdio - container[config.ACI_FIELD_CONTAINERS_SECURITY_CONTEXT] = case_insensitive_dict_get( - image_properties, config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT - ) + # add the signal section if it's not present + for exec_process in exec_processes: + if config.ACI_FIELD_CONTAINERS_SIGNAL_CONTAINER_PROCESSES not in exec_process: + exec_process[config.ACI_FIELD_CONTAINERS_SIGNAL_CONTAINER_PROCESSES] = [] + extract_probe(exec_processes, container_properties, config.ACI_FIELD_CONTAINERS_READINESS_PROBE) + extract_probe(exec_processes, container_properties, config.ACI_FIELD_CONTAINERS_LIVENESS_PROBE) + + container_security_context = case_insensitive_dict_get( + container_properties, config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT + ) or {} + + working_dir = case_insensitive_dict_get(container_properties, config.ACI_FIELD_CONTAINERS_WORKINGDIR) + + mounts = process_mounts_from_config(container_properties) + process_configmap(container_properties) + if ( + scenario.lower() == config.VN2 and + case_insensitive_dict_get(container_security_context, config.ACI_FIELD_CONTAINERS_PRIVILEGED) + ): + mounts += config.DEFAULT_MOUNTS_PRIVILEGED_VIRTUAL_NODE + + labels = case_insensitive_dict_get(policy_input_json, config.VIRTUAL_NODE_YAML_LABELS) or [] + envs = [] + # use workload identity + if ( + scenario.lower() == config.VN2 and + config.VIRTUAL_NODE_YAML_LABEL_WORKLOAD_IDENTITY in labels and + case_insensitive_dict_get(labels, config.VIRTUAL_NODE_YAML_LABEL_WORKLOAD_IDENTITY) + ): + envs += config.VIRTUAL_NODE_ENV_RULES_WORKLOAD_IDENTITY + mounts += config.DEFAULT_MOUNTS_WORKLOAD_IDENTITY_VIRTUAL_NODE + + envs += process_env_vars_from_config(container_properties) + + output_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_WORKINGDIR: working_dir, + config.ACI_FIELD_CONTAINERS_ENVS: envs, + config.ACI_FIELD_CONTAINERS_COMMAND: case_insensitive_dict_get( + 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_SIGNAL_CONTAINER_PROCESSES: [], + config.ACI_FIELD_CONTAINERS_ALLOW_STDIO_ACCESS: not disable_stdio, + config.ACI_FIELD_CONTAINERS_SECURITY_CONTEXT: case_insensitive_dict_get( + container_properties, config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT + ), + } + ) + + # Add default fragments if necessary if not exclude_default_fragments: rego_fragments.extend(copy.deepcopy(config.DEFAULT_REGO_FRAGMENTS)) + # changes the svn of the infrastructure fragment provided by ACI if infrastructure_svn: # assumes the first DEFAULT_REGO_FRAGMENT is always the # infrastructure fragment @@ -1027,9 +1025,14 @@ def load_policy_from_str( ] = infrastructure_svn return AciPolicy( - policy_input_json, + { + config.ACI_FIELD_VERSION: version, + config.ACI_FIELD_CONTAINERS: output_containers, + }, + disable_stdio=disable_stdio, rego_fragments=rego_fragments, debug_mode=debug_mode, + is_vn2=scenario.lower() == config.VN2, ) @@ -1268,7 +1271,7 @@ def load_policy_from_virtual_node_yaml_str( config.ACI_FIELD_CONTAINERS_COMMAND: args, config.ACI_FIELD_CONTAINERS_MOUNTS: mounts, config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES: exec_processes - + config.DEBUG_MODE_SETTINGS.get("execProcesses") + + config.DEBUG_MODE_SETTINGS.get(config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES) if debug_mode else exec_processes, config.ACI_FIELD_CONTAINERS_SIGNAL_CONTAINER_PROCESSES: [], @@ -1293,105 +1296,3 @@ def load_policy_from_virtual_node_yaml_str( ) ) return all_policies - - -def load_policy_from_config_file(config_file, debug_mode: bool = False, disable_stdio: bool = False): - config_content = os_util.load_str_from_file(config_file) - return load_policy_from_config_str(config_content, debug_mode, disable_stdio) - - -# Used for generating policy fragments -def load_policy_from_config_str(config_str, debug_mode: bool = False, disable_stdio: bool = False): - config_dict = os_util.load_json_from_str(config_str) - containers = [] - - rego_fragments = case_insensitive_dict_get( - config_dict, config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS - ) - - container_list = case_insensitive_dict_get( - config_dict, config.ACI_FIELD_CONTAINERS - ) - - scenario = case_insensitive_dict_get( - config_dict, config.ACI_FIELD_SCENARIO - ) or "" - - for container in container_list: - container_name = case_insensitive_dict_get( - container, config.ACI_FIELD_CONTAINERS_NAME - ) - if not container_name: - eprint(f'Field ["{config.ACI_FIELD_CONTAINERS_NAME}"] is empty or cannot be found') - - container_properties = case_insensitive_dict_get( - container, config.ACI_FIELD_TEMPLATE_PROPERTIES - ) - - image_name = case_insensitive_dict_get( - container_properties, config.ACI_FIELD_TEMPLATE_IMAGE - ) - - if not image_name: - eprint( - f'Field ["{config.ACI_FIELD_TEMPLATE_IMAGE}"] is empty or cannot be found' - ) - - exec_processes = case_insensitive_dict_get( - container_properties, config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES - ) or [] - - # add the signal section if it's not present - for exec_process in exec_processes: - if config.ACI_FIELD_CONTAINERS_SIGNAL_CONTAINER_PROCESSES not in exec_process: - exec_process[config.ACI_FIELD_CONTAINERS_SIGNAL_CONTAINER_PROCESSES] = [] - - extract_probe(exec_processes, container_properties, config.ACI_FIELD_CONTAINERS_READINESS_PROBE) - extract_probe(exec_processes, container_properties, config.ACI_FIELD_CONTAINERS_LIVENESS_PROBE) - - container_security_context = case_insensitive_dict_get( - container_properties, config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT - ) or {} - - mounts = process_mounts_from_config(container_properties) + process_configmap(container_properties) - if ( - scenario.lower() == config.VN2 and - case_insensitive_dict_get(container_security_context, config.ACI_FIELD_CONTAINERS_PRIVILEGED) is True - ): - mounts += config.DEFAULT_MOUNTS_PRIVILEGED_VIRTUAL_NODE - - 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_config( - container_properties - ), - config.ACI_FIELD_CONTAINERS_COMMAND: case_insensitive_dict_get( - 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("execProcesses") - 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( - container_properties, config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT - ), - } - ) - - return AciPolicy( - { - config.ACI_FIELD_VERSION: "1.0", - config.ACI_FIELD_CONTAINERS: containers, - }, - disable_stdio=disable_stdio, - rego_fragments=rego_fragments, - debug_mode=debug_mode, - is_vn2=scenario.lower() == config.VN2, - ) diff --git a/src/confcom/azext_confcom/template_util.py b/src/confcom/azext_confcom/template_util.py index a1089cf71f3..49614e7e4f8 100644 --- a/src/confcom/azext_confcom/template_util.py +++ b/src/confcom/azext_confcom/template_util.py @@ -505,7 +505,7 @@ def process_env_vars_from_config(container) -> List[Dict[str, str]]: name = case_insensitive_dict_get(env_var, "name") secure_value = case_insensitive_dict_get(env_var, "secureValue") is_secure = bool(secure_value) - value = case_insensitive_dict_get(env_var, "value") or secure_value + value = case_insensitive_dict_get(env_var, "value") or secure_value or "" if not name and not is_secure: eprint( @@ -515,8 +515,6 @@ def process_env_vars_from_config(container) -> List[Dict[str, str]]: eprint( "Environment variable with secure value is missing a name" ) - elif not value: - eprint(f'Environment variable {name} does not have a value. Please check the template file.') env_vars.append({ config.ACI_FIELD_CONTAINERS_ENVS_NAME: name, @@ -1494,3 +1492,198 @@ def pick(obj, *keys): # put temp_syscalls back into policy policy['syscalls'] = temp_syscalls return policy + + +def convert_config_v0_to_v1(old_data): + """ + Convert a JSON structure from the 'v0' format to the 'v1' format. + If the input is already in 'v1' format, return the original input. + + :param old_data: Dictionary in the old format. + :return: Dictionary in the new format. + + Expected old_data schema (simplified): + { + "version": "1.0", + "containers": [ + { + "name": "...", + "containerImage": "...", + "environmentVariables": [ + { + "name": "...", + "value": "...", + "strategy": "string" or "re2" (optional, default is string) + } + ], + "command": [...], + "workingDir": "...", + "mounts": [ + { + "mountType": "...", + "mountPath": "...", + "readonly": bool + } + ] + }, + ... + ] + } + + Returns a structure matching the 'new' format: + { + "version": "...", + "fragments": [], + "containers": [ + { + "name": "...", + "properties": { + "image": "...", + "workingDir": "...", + "execProcesses": [ + { + "command": [...] + } + ], + "volumeMounts": [ + { + "name": "...", + "mountPath": "...", + "mountType": "...", + "readOnly": bool + } + ], + "environmentVariables": [ + { + "name": "...", + "value": "...", + "regex": bool (only present if we decide so, default is false) + } + ] + } + }, + ... + ] + } + """ + if not detect_old_format(old_data): + logger.warning("JSON config is already in v1 format") + return old_data + + # Prepare the structure of the new JSON + new_data = { + config.ACI_FIELD_VERSION: old_data.get(config.ACI_FIELD_VERSION, "1.0"), # default if missing + config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS: [], # empty by default in your example + config.ACI_FIELD_CONTAINERS: [] + } + + old_containers = old_data.get(config.ACI_FIELD_CONTAINERS, []) + + for old_container in old_containers: + # Build the 'environmentVariables' section in the new format + new_envs = [] + for env_var in old_container.get(config.ACI_FIELD_CONTAINERS_ENVS) or []: + # Decide if we need 'regex' or not, based on 'strategy' or your custom logic + # Here we'll assume "strategy"=="re2" means 'regex' = True + # If strategy is missing or 'string', omit 'regex' or set it to False + env_entry = { + config.ACI_FIELD_CONTAINERS_ENVS_NAME: env_var.get(config.ACI_FIELD_CONTAINERS_ENVS_NAME), + config.ACI_FIELD_CONTAINERS_ENVS_VALUE: env_var.get(config.ACI_FIELD_CONTAINERS_ENVS_VALUE, "") + } + strategy = env_var.get(config.ACI_FIELD_CONTAINERS_ENVS_STRATEGY) + if strategy == "re2": + env_entry["regex"] = True + + new_envs.append(env_entry) + + # Build the 'execProcesses' from the old 'command' + exec_processes = [] + old_command_list = old_container.get(config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES, []) + if old_command_list: + exec_processes.append({config.ACI_FIELD_CONTAINERS_COMMAND: old_command_list}) + + command = old_container.get(config.ACI_FIELD_CONTAINERS_COMMAND) + + # Liveness probe => exec process + liveness_probe = old_container.get(config.ACI_FIELD_CONTAINERS_LIVENESS_PROBE, {}) + liveness_exec = liveness_probe.get(config.ACI_FIELD_CONTAINERS_PROBE_ACTION, {}) + liveness_command = liveness_exec.get(config.ACI_FIELD_CONTAINERS_COMMAND, []) + if liveness_command: + exec_processes.append({ + config.ACI_FIELD_CONTAINERS_COMMAND: liveness_command + }) + + # Readiness probe => exec process + readiness_probe = old_container.get(config.ACI_FIELD_CONTAINERS_READINESS_PROBE, {}) + readiness_exec = readiness_probe.get(config.ACI_FIELD_CONTAINERS_PROBE_ACTION, {}) + readiness_command = readiness_exec.get(config.ACI_FIELD_CONTAINERS_COMMAND, []) + if readiness_command: + exec_processes.append({ + config.ACI_FIELD_CONTAINERS_COMMAND: readiness_command + }) + + # Build the 'volumeMounts' section + volume_mounts = [] + for mount in old_container.get(config.ACI_FIELD_CONTAINERS_MOUNTS) or []: + # For 'name', we can take the mountType or generate something else: + # e.g. if mountType is "azureFile", name "azurefile" + mount_name = mount.get(config.ACI_FIELD_CONTAINERS_MOUNTS_TYPE, "defaultName").lower() + volume_mount = { + config.ACI_FIELD_CONTAINERS_ENVS_NAME: mount_name, + config.ACI_FIELD_TEMPLATE_MOUNTS_PATH: mount.get(config.ACI_FIELD_CONTAINERS_MOUNTS_PATH), + config.ACI_FIELD_TEMPLATE_MOUNTS_TYPE: mount.get(config.ACI_FIELD_CONTAINERS_MOUNTS_TYPE), + config.ACI_FIELD_TEMPLATE_MOUNTS_READONLY: mount.get(config.ACI_FIELD_CONTAINERS_MOUNTS_READONLY, True), + } + volume_mounts.append(volume_mount) + + # Create the container's "properties" object + container_properties = { + config.ACI_FIELD_TEMPLATE_IMAGE: old_container.get(config.ACI_FIELD_CONTAINERS_CONTAINERIMAGE), + config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES: exec_processes, + config.ACI_FIELD_TEMPLATE_VOLUME_MOUNTS: volume_mounts, + config.ACI_FIELD_CONTAINERS_ENVS: new_envs, + config.ACI_FIELD_CONTAINERS_COMMAND: command, + } + + if old_container.get(config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT) is not None: + container_properties[ + config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT + ] = old_container[config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT] + + if old_container.get(config.ACI_FIELD_CONTAINERS_ALLOW_ELEVATED) is not None: + if config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT not in container_properties: + container_properties[config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT] = {} + container_properties[ + config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT + ][config.ACI_FIELD_CONTAINERS_PRIVILEGED] = old_container.get(config.ACI_FIELD_CONTAINERS_ALLOW_ELEVATED) + + if old_container.get(config.ACI_FIELD_CONTAINERS_WORKINGDIR) is not None: + container_properties[ + config.ACI_FIELD_CONTAINERS_WORKINGDIR + ] = old_container.get(config.ACI_FIELD_CONTAINERS_WORKINGDIR) + + # Finally, assemble the new container dict + new_container = { + config.ACI_FIELD_CONTAINERS_NAME: old_container.get(config.ACI_FIELD_CONTAINERS_NAME), + config.ACI_FIELD_TEMPLATE_PROPERTIES: container_properties + } + + new_data[config.ACI_FIELD_CONTAINERS].append(new_container) + + return new_data + + +def detect_old_format(old_data): + # we want to encourage customers to transition to the new format. The best way to check for the old format is + # to see if the json is flattened. This is an appropriate check since the image name is required + # and they are located in different places in the two formats + old_containers = old_data.get(config.ACI_FIELD_CONTAINERS, []) + if len(old_containers) > 0 and old_containers[0].get(config.ACI_FIELD_CONTAINERS_CONTAINERIMAGE) is not None: + logger.warning( + "%s %s %s", + "(Deprecation Warning) The input format used is deprecated.", + "To view the current format, please look at the examples in: ", + "https://github.com/Azure/azure-cli-extensions/blob/main/src/confcom/azext_confcom/README.md" + ) + return True + return False diff --git a/src/confcom/azext_confcom/tests/latest/README.md b/src/confcom/azext_confcom/tests/latest/README.md index e15a5b2f2f9..469edd7f905 100644 --- a/src/confcom/azext_confcom/tests/latest/README.md +++ b/src/confcom/azext_confcom/tests/latest/README.md @@ -19,30 +19,30 @@ It uses the ARM template used to deploy a ACI Container Group while taking into Test Name | Image Used | Purpose ---|---|--- -test_arm_template_policy | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Generate an ARM Template policy and policy.json policy and see if their outputs match -test_default_infrastructure_svn | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | See the default value of the minimum SVN for the infrastructure fragment -test_default_pause_container | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | See if the default pause containers match the config +test_arm_template_policy | mcr.microsoft.com/azurelinux/base/python:3.12 | Generate an ARM Template policy and policy.json policy and see if their outputs match +test_default_infrastructure_svn | mcr.microsoft.com/azurelinux/base/python:3.12 | See the default value of the minimum SVN for the infrastructure fragment +test_default_pause_container | mcr.microsoft.com/azurelinux/base/python:3.12 | See if the default pause containers match the config test_arm_template_missing_image_name | N/A | Error condition if an image isn't specified test_arm_template_missing_resources | N/A | Error condition where no resources are specified to deploy test_arm_template_missing_aci | N/A | Error condition where ACI is not specified in resources test_arm_template_missing_containers | N/A | Error condition where there are no containers in the ACI resource -test_arm_template_missing_definition | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Error condition where image is specified in template.parameters.json but not in template.json +test_arm_template_missing_definition | mcr.microsoft.com/azurelinux/base/python:3.12 | Error condition where image is specified in template.parameters.json but not in template.json test_arm_template_with_parameter_file | mcr.microsoft.com/azure-functions/python:4-python3.8 | Condition where image in template.parameters.json overwrites image name in template.json test_arm_template_with_parameter_file_injected_env_vars | mcr.microsoft.com/azure-functions/python:4-python3.8 | See if env vars from the image are injected into the policy. Also make sure the `concat` function in ARM template won't break the CLI if it's not in a required spot like image name test_arm_template_with_parameter_file_arm_config | mcr.microsoft.com/azure-functions/python:4-python3.8 | Test valid case of using a parameter file with JSON output instead of Rego test_arm_template_with_parameter_file_clean_room | mcr.microsoft.com/azure-functions/node:4 | Test clean room case where image specified does not exist remotely but does locally -test_policy_diff | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | See if the diff functionality outputs `True` when diffs match completely -test_incorrect_policy_diff | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | Check output formatting and functionality of diff command -test_update_infrastructure_svn | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Change the minimum SVN for the insfrastructure fragment -test_multiple_policies | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot & mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | See if two unique policies are generated from a single ARM Template container multiple container groups. Also have an extra resource that is untouched. Also has a secureValue for an environment variable. -test_arm_template_with_init_container | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot & mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | See if having an initContainer is picked up and added to the list of valid containers -test_arm_template_without_stdio_access | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | See if disabling container stdio access gets passed down to individual containers -test_arm_template_omit_id | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Check that the id field is omitted from the policy -test_arm_template_allow_elevated_false | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | Disabling allow_elevated via securityContext -test_arm_template_policy_regex | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Make sure the regex generated from the ARM Template workflow matches that of the policy.json workflow -test_wildcard_env_var | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Check that an "allow all" regex is created when a value for env var is not provided via a parameter value +test_policy_diff | mcr.microsoft.com/azurelinux/distroless/base:3.0 | See if the diff functionality outputs `True` when diffs match completely +test_incorrect_policy_diff | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Check output formatting and functionality of diff command +test_update_infrastructure_svn | mcr.microsoft.com/azurelinux/base/python:3.12 | Change the minimum SVN for the insfrastructure fragment +test_multiple_policies | mcr.microsoft.com/azurelinux/base/python:3.12 & mcr.microsoft.com/azurelinux/distroless/base:3.0 | See if two unique policies are generated from a single ARM Template container multiple container groups. Also have an extra resource that is untouched. Also has a secureValue for an environment variable. +test_arm_template_with_init_container | mcr.microsoft.com/azurelinux/base/python:3.12 & mcr.microsoft.com/azurelinux/distroless/base:3.0 | See if having an initContainer is picked up and added to the list of valid containers +test_arm_template_without_stdio_access | mcr.microsoft.com/azurelinux/distroless/base:3.0 | See if disabling container stdio access gets passed down to individual containers +test_arm_template_omit_id | mcr.microsoft.com/azurelinux/base/python:3.12 | Check that the id field is omitted from the policy +test_arm_template_allow_elevated_false | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Disabling allow_elevated via securityContext +test_arm_template_policy_regex | mcr.microsoft.com/azurelinux/base/python:3.12 | Make sure the regex generated from the ARM Template workflow matches that of the policy.json workflow +test_wildcard_env_var | mcr.microsoft.com/azurelinux/base/python:3.12 | Check that an "allow all" regex is created when a value for env var is not provided via a parameter value test_wildcard_env_var_invalid | N/A | Make sure the process errors out if a value is not given for an env var or an undefined parameter is used for the name of an env var -test_arm_template_with_env_var | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | Make sure that a value that looks similar to but is not an ARM parameter is treated as a string +test_arm_template_with_env_var | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Make sure that a value that looks similar to but is not an ARM parameter is treated as a string test_arm_template_security_context_defaults | N/A | Make sure default values for securityContext are correct test_arm_template_security_context_allow_privilege_escalation | N/A | See if changing the allowPrivilegeEscalation flag is working test_arm_template_security_context_user | N/A | Set the user field manually to make sure it is reflected in the policy @@ -57,7 +57,7 @@ test_arm_template_security_context_user_group | N/A | See if user is set correct test_arm_template_security_context_uid_group | N/A | See if user is set correctly by getting the user field from the Docker image in the format uid:group test_arm_template_security_context_uid | N/A | See if user is set correctly by getting the user field from the Docker image in the format uid test_arm_template_security_context_user_dockerfile | N/A | See if user is set correctly by getting the user field from the Docker image in the format user -test_zero_sidecar | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Make sure the infrastructure fragment is taken out when the appropriate tag is present in an ARM template +test_zero_sidecar | mcr.microsoft.com/azurelinux/base/python:3.12 | Make sure the infrastructure fragment is taken out when the appropriate tag is present in an ARM template ## policy.json [test file](test_confcom_scenario.py) @@ -66,31 +66,31 @@ It is still used for generating sidecar CCE Policies. Test Name | Image Used | Purpose ---|---|--- -test_user_container_customized_mounts | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | See if mounts are translated correctly to the appropriate source and destination locations -test_user_container_mount_injected_dns | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | See if the resolvconf mount works properly +test_user_container_customized_mounts | mcr.microsoft.com/azurelinux/distroless/base:3.0 | See if mounts are translated correctly to the appropriate source and destination locations +test_user_container_mount_injected_dns | mcr.microsoft.com/azurelinux/base/python:3.12 | See if the resolvconf mount works properly test_injected_sidecar_container_msi | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201203.1 | Make sure User mounts and env vars aren't added to sidecar containers, using JSON output format -test_debug_flags | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Enable flags set via debug_mode +test_debug_flags | mcr.microsoft.com/azurelinux/base/python:3.12 | Enable flags set via debug_mode test_sidecar | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1 | See if sidecar validation would pass policy created by given policy.json test_sidecar_stdio_access_default | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1 | Check that sidecar containers have std I/O access by default test_incorrect_sidecar | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1 | See what output format for failing sidecar validation would be -test_customized_workingdir | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Using different working dir than specified in image metadata -test_allow_elevated | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Using allow_elevated in container -test_image_layers_python | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Make sure image layers are as expected -test_docker_pull | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | Test pulling an image from docker client -test_infrastructure_svn | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | make sure the correct infrastructure_svn is present in the policy -test_stdio_access_default | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Checking the default value for std I/O access -test_stdio_access_updated | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Checking the value for std I/O when it's set -test_omit_id | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Check that the id field is omitted from the policy -test_environment_variables_parsing | mcr.microsoft.com/azuredocs/aci-dataprocessing-cc:v1 | Make sure env vars are output in the right format +test_customized_workingdir | mcr.microsoft.com/azurelinux/base/python:3.12 | Using different working dir than specified in image metadata +test_allow_elevated | mcr.microsoft.com/azurelinux/base/python:3.12 | Using allow_elevated in container +test_image_layers_python | mcr.microsoft.com/azurelinux/base/python:3.12 | Make sure image layers are as expected +test_docker_pull | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Test pulling an image from docker client +test_infrastructure_svn | mcr.microsoft.com/azurelinux/distroless/base:3.0 | make sure the correct infrastructure_svn is present in the policy +test_stdio_access_default | mcr.microsoft.com/azurelinux/base/python:3.12 | Checking the default value for std I/O access +test_stdio_access_updated | mcr.microsoft.com/azurelinux/base/python:3.12 | Checking the value for std I/O when it's set +test_omit_id | mcr.microsoft.com/azurelinux/base/python:3.12 | Check that the id field is omitted from the policy +test_environment_variables_parsing | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Make sure env vars are output in the right format test_get_layers_from_not_exists_image | notexists:1.0.0 | Fail out grabbing layers if image doesn't exist -test_incorrect_allow_elevated_data_type | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | Making allow_elevated fail out if it's not a boolean -test_incorrect_workingdir_path | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | Fail if working dir isn't an absolute path string -test_incorrect_workingdir_data_type | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | Fail if working dir is an array -test_incorrect_command_data_type | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | Fail if command is not array of strings +test_incorrect_allow_elevated_data_type | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Making allow_elevated fail out if it's not a boolean +test_incorrect_workingdir_path | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Fail if working dir isn't an absolute path string +test_incorrect_workingdir_data_type | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Fail if working dir is an array +test_incorrect_command_data_type | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Fail if command is not array of strings test_json_missing_containers | N/A | Fail if containers are not specified test_json_missing_containerImage | N/A | Fail if container doesn't have an image specified -test_json_missing_environmentVariables | mcr.microsoft.com/azuredocs/aci-dataprocessing-cc:v1 | Fail if there are no env vars defined -test_json_missing_command | mcr.microsoft.com/azuredocs/aci-dataprocessing-cc:v1 | Fail if there is no command specified +test_json_missing_environmentVariables | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Fail if there are no env vars defined +test_json_missing_command | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Fail if there is no command specified ## Image [test file](test_confcom_image.py) @@ -99,7 +99,7 @@ It accepts a string of the image name and tag and outputs a CCE Policy using the Test Name | Image Used | Purpose ---|---|--- -test_image_policy | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Create a policy based on only an image name +test_image_policy | mcr.microsoft.com/azurelinux/base/python:3.12 | Create a policy based on only an image name test_sidecar_image_policy |mcr.microsoft.com/aci/atlas-mount-azure-file-volume:master_20201210.2| Create a policy based on a sidecar so no env vars are injected test_invalid_image_policy | mcr.microsoft.com/aci/fake-image:master_20201210.2 | Fail out if the image doesn't exist locally or remotely test_clean_room_policy | mcr.microsoft.com/aci/atlas-mount-azure-file-volume:master_20201210.2 | create a new tag of a sidecar locally and make sure it matches the original @@ -123,8 +123,8 @@ This is a way to generate a CCE policy without the use of the docker daemon. The Test Name | Image Used | Purpose ---|---|--- -test_arm_template_with_parameter_file_clean_room_tar | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | Create a policy from a tar file and compare it to a policy generated from an ARM template -test_arm_template_mixed_mode_tar | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot & mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | Create a policy with one image from a tar file and one image that must be downloaded or used locally from the daemon +test_arm_template_with_parameter_file_clean_room_tar | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Create a policy from a tar file and compare it to a policy generated from an ARM template +test_arm_template_mixed_mode_tar | mcr.microsoft.com/azurelinux/base/python:3.12 & mcr.microsoft.com/azurelinux/distroless/base:3.0 | Create a policy with one image from a tar file and one image that must be downloaded or used locally from the daemon test_arm_template_with_parameter_file_clean_room_tar_invalid | N/A | Fail out if searching for an image in a tar file that does not include it test_clean_room_fake_tar_invalid | N/A | Fail out if the path to the tar file doesn't exist @@ -146,9 +146,9 @@ This is how to generate security policies for Virtual Nodes on AKS Test Name | Image Used | Purpose ---|---|--- -test_compare_policy_sources | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Compare the output of a policy generated from a Virtual Node file and a policy generated from an input json -test_configmaps | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Check that the configmaps are being added to the policy in env var and mount form -test_secrets | mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot | Check that the secrets are being added to the policy in env var and mount form +test_compare_policy_sources | mcr.microsoft.com/azurelinux/base/python:3.12 | Compare the output of a policy generated from a Virtual Node file and a policy generated from an input json +test_configmaps | mcr.microsoft.com/azurelinux/base/python:3.12 | Check that the configmaps are being added to the policy in env var and mount form +test_secrets | mcr.microsoft.com/azurelinux/base/python:3.12 | Check that the secrets are being added to the policy in env var and mount form ## Fragment File [test file](test_confcom_fragment.py) @@ -156,15 +156,28 @@ This is how to generate a policy fragment to be included in a CCE Policy for Con Test Name | Image Used | Purpose ---|---|--- -test_fragment_user_container_customized_mounts | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | See if mounts are translated correctly to the appropriate source and destination locations -test_fragment_user_container_mount_injected_dns | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | See if the resolvconf mount works properly +test_fragment_user_container_customized_mounts | mcr.microsoft.com/azurelinux/distroless/base:3.0 | See if mounts are translated correctly to the appropriate source and destination locations +test_fragment_user_container_mount_injected_dns | mcr.microsoft.com/azurelinux/distroless/base:3.0 | See if the resolvconf mount works properly test_fragment_omit_id | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201203.1 | Check that the id field is omitted from the policy test_fragment_injected_sidecar_container_msi | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201203.1 | Make sure User mounts and env vars aren't added to sidecar containers, using JSON output format -test_debug_processes | mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 | Enable exec_processes via debug_mode +test_debug_processes | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Enable exec_processes via debug_mode test_fragment_sidecar | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1 | See if sidecar fragments can be created by a given policy.json test_fragment_sidecar_stdio_access_default | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1 | Check that sidecar containers have std I/O access by default test_fragment_incorrect_sidecar | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1 | See what output format for failing sidecar validation would be -test_signing | mcr.microsoft.com/acc/samples/aci/helloworld:2.8 | Sign a fragment with a key and chain file -test_generate_import | mcr.microsoft.com/acc/samples/aci/helloworld:2.8 | Generate an import statement for the signed fragment file -test_local_fragment_references | mcr.microsoft.com/acc/samples/aci/helloworld:2.8 | Make sure the fragment references are correct when the fragment is local +test_signing | mcr.microsoft.com/acc/samples/aci/helloworld:2.9 | Sign a fragment with a key and chain file +test_generate_import | mcr.microsoft.com/acc/samples/aci/helloworld:2.9 | Generate an import statement for the signed fragment file +test_local_fragment_references | mcr.microsoft.com/acc/samples/aci/helloworld:2.9 | Make sure the fragment references are correct when the fragment is local test_invalid_input | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1 | Fail out under various invalid input circumstances + +## Policy Conversion File [test file](test_confcom_policy_conversion.py) + +Test Name | Image Used | Purpose +---|---|--- +test_detect_old_format | N/A | Verify that old and new format configurations are correctly identified +test_top_level_fields_propagated | N/A | Check that top-level fields like version and fragments are preserved during conversion +test_container_count_preserved | N/A | Ensure the number of containers remains unchanged after conversion +test_env_strategy_to_regex_flag | N/A | Verify environment variable strategy is correctly translated to regex flags in v1 format +test_exec_processes_built_correctly | N/A | Check command and probe commands are correctly aggregated into execProcesses +test_volume_mount_basic_fields | N/A | Test volume mount properties are correctly translated to the new format +test_workingdir_and_allow_elevated_migrated | N/A | Verify workingDir and allow_elevated are correctly moved to security context +test_already_v1_returns_same_object | N/A | Confirm conversion is idempotent - v1 format input remains unchanged diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_arm.py b/src/confcom/azext_confcom/tests/latest/test_confcom_arm.py index 49e03b90e46..cd5f0be05f6 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_arm.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_arm.py @@ -11,7 +11,7 @@ from azext_confcom.security_policy import ( OutputType, - load_policy_from_str, + load_policy_from_json, load_policy_from_arm_template_str, ) import azext_confcom.config as config @@ -33,7 +33,7 @@ class PolicyGeneratingArm(unittest.TestCase): "containers": [ { "name": "simple-container", - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "containerImage": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [ { "name":"PATH", @@ -65,7 +65,7 @@ class PolicyGeneratingArm(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -204,7 +204,7 @@ class PolicyGeneratingArm(unittest.TestCase): @classmethod def setUpClass(cls): - with load_policy_from_str(cls.custom_json) as aci_policy: + with load_policy_from_json(cls.custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() cls.aci_policy = aci_policy @@ -697,7 +697,7 @@ def test_arm_template_missing_definition(self): "contentVersion": "1.0.0.0", "parameters": { "image": { - "value": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "value": "mcr.microsoft.com/azurelinux/base/python:3.12" }, "containername": { "value": "simple-container" @@ -758,7 +758,7 @@ def test_arm_template_with_parameter_file(self): "metadata": { "description": "Name for the image" }, - "defaultValue": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "defaultValue": "mcr.microsoft.com/azurelinux/base/python:3.12" }, "port": { @@ -898,7 +898,7 @@ def test_arm_template_with_parameter_file_injected_env_vars(self): "metadata": { "description": "Name for the container group" }, - "defaultValue":"mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "defaultValue":"mcr.microsoft.com/azurelinux/base/python:3.12" }, "imagebase": { "type": "string", @@ -1078,7 +1078,7 @@ def test_arm_template_with_parameter_file_arm_config(self): "metadata": { "description": "Name for the container group" }, - "defaultValue":"mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "defaultValue":"mcr.microsoft.com/azurelinux/base/python:3.12" }, "imagebase": { "type": "string", @@ -1423,7 +1423,7 @@ class PolicyDiff(unittest.TestCase): "contentVersion": "1.0.0.0", "variables": { "container1name": "aci-test", - "container1image": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0" + "container1image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, "resources": [ { @@ -1502,7 +1502,7 @@ class PolicyDiff(unittest.TestCase): "contentVersion": "1.0.0.0", "variables": { "container1name": "aci-test", - "container1image": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0" + "container1image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, "resources": [ { @@ -1633,7 +1633,7 @@ class PolicyGeneratingArmInfrastructureSvn(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -1801,9 +1801,9 @@ class MultiplePolicyTemplate(unittest.TestCase): "contentVersion": "1.0.0.0", "variables": { "container1name": "aci-test", - "container1image": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0", + "container1image": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "container2name": "aci-test2", - "container2image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "container2image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, "resources": [ { @@ -1827,6 +1827,11 @@ class MultiplePolicyTemplate(unittest.TestCase): "memoryInGb": 1.5 } }, + "command": [ + "/bin/sh", + "-c", + "ls" + ], "ports": [ { "port": 80 @@ -1887,6 +1892,11 @@ class MultiplePolicyTemplate(unittest.TestCase): "name": "[variables('container2name')]", "properties": { "image": "[variables('container2image')]", + "command": [ + "/bin/sh", + "-c", + "ls" + ], "resources": { "requests": { "cpu": 1, @@ -1944,6 +1954,11 @@ class MultiplePolicyTemplate(unittest.TestCase): "name": "aci-test-1", "properties": { "image": "[variables('container1image')]", + "command": [ + "/bin/sh", + "-c", + "ls" + ], "resources": { "requests": { "cpu": 1, @@ -1962,6 +1977,11 @@ class MultiplePolicyTemplate(unittest.TestCase): "name": "aci-test-2", "properties": { "image": "[variables('container1image')]", + "command": [ + "/bin/sh", + "-c", + "ls" + ], "resources": { "requests": { "cpu": 1, @@ -1980,6 +2000,11 @@ class MultiplePolicyTemplate(unittest.TestCase): "name": "aci-test-3", "properties": { "image": "[variables('container1image')]", + "command": [ + "/bin/sh", + "-c", + "ls" + ], "resources": { "requests": { "cpu": 1, @@ -2060,7 +2085,7 @@ def test_multiple_policies(self): is_valid, diff = self.aci_policy.validate_cce_policy() self.assertFalse(is_valid) # just check to make sure the containers in both policies are different - expected_diff = {"aci-test": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 not found in policy"} + expected_diff = {"aci-test": "mcr.microsoft.com/azurelinux/distroless/base:3.0 not found in policy"} self.assertEqual(diff, expected_diff) def test_multiple_diffs(self): @@ -2097,7 +2122,7 @@ class PolicyGeneratingArmInitContainer(unittest.TestCase): "metadata": { "description": "Name for the container group" }, - "defaultValue":"mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0" + "defaultValue":"mcr.microsoft.com/azurelinux/distroless/base:3.0" }, "containername": { "type": "string", @@ -2179,7 +2204,7 @@ class PolicyGeneratingArmInitContainer(unittest.TestCase): { "name": "init-container-python", "properties": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "image": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [ { "name":"PATH", @@ -2282,7 +2307,7 @@ class PolicyGeneratingDisableStdioAccess(unittest.TestCase): "metadata": { "description": "Name for the container group" }, - "defaultValue":"mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0" + "defaultValue":"mcr.microsoft.com/azurelinux/distroless/base:3.0" }, "containername": { "type": "string", @@ -2428,7 +2453,7 @@ class PolicyGeneratingOmitId(unittest.TestCase): "metadata": { "description": "Name for the container group" }, - "defaultValue":"mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "defaultValue":"mcr.microsoft.com/azurelinux/base/python:3.12" }, "containername": { "type": "string", @@ -2571,7 +2596,7 @@ class PolicyGeneratingAllowElevated(unittest.TestCase): "metadata": { "description": "Name for the container group" }, - "defaultValue":"mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0" + "defaultValue":"mcr.microsoft.com/azurelinux/distroless/base:3.0" }, "containername": { "type": "string", @@ -2706,7 +2731,7 @@ def test_printing_existing_policy(self): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -2846,7 +2871,7 @@ def test_printing_existing_policy(self): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, "parameters": { "containergroupname": { @@ -3002,7 +3027,7 @@ class PolicyGeneratingArmWildcardEnvs(unittest.TestCase): "containers": [ { "name": "simple-container", - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "containerImage": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [ { "name":"PATH", @@ -3028,7 +3053,7 @@ class PolicyGeneratingArmWildcardEnvs(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -3146,7 +3171,7 @@ class PolicyGeneratingArmWildcardEnvs(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -3262,7 +3287,7 @@ class PolicyGeneratingArmWildcardEnvs(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -3396,7 +3421,7 @@ class PolicyGeneratingArmWildcardEnvs(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -3541,7 +3566,7 @@ class PolicyGeneratingArmWildcardEnvs(unittest.TestCase): @classmethod def setUpClass(cls): - with load_policy_from_str(cls.custom_json) as aci_policy: + with load_policy_from_json(cls.custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() cls.aci_policy = aci_policy @@ -3651,7 +3676,7 @@ class PolicyGeneratingEdgeCases(unittest.TestCase): "metadata": { "description": "Name for the container group" }, - "defaultValue":"mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0" + "defaultValue":"mcr.microsoft.com/azurelinux/distroless/base:3.0" }, "containername": { "type": "string", @@ -3779,7 +3804,7 @@ def test_arm_template_with_env_var(self): # see if the remote image and the local one produce the same output self.assertEqual(env_var, "PORT=parameters('abc')") - self.assertEqual(regular_image_json[0][config.POLICY_FIELD_CONTAINERS_ID], "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0") + self.assertEqual(regular_image_json[0][config.POLICY_FIELD_CONTAINERS_ID], "mcr.microsoft.com/azurelinux/distroless/base:3.0") def test_arm_template_config_map_sidecar(self): regular_image_json = json.loads( @@ -3799,7 +3824,7 @@ class PolicyGeneratingSecurityContext(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -3940,7 +3965,7 @@ class PolicyGeneratingSecurityContext(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -4091,7 +4116,7 @@ class PolicyGeneratingSecurityContext(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -4238,7 +4263,7 @@ class PolicyGeneratingSecurityContext(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -4407,20 +4432,7 @@ def setUpClass(cls): cls.aci_arm_policy4.populate_policy_content_for_all_images() def test_arm_template_security_context_defaults(self): - expected_user_json = json.loads("""{ - "user_idname": - { - "pattern": "nonroot", - "strategy": "name" - }, - "group_idnames": [ - { - "pattern": "", - "strategy": "any" - } - ], - "umask": "0022" - }""") + expected_user_json = config.DEFAULT_USER regular_image_json = json.loads( self.aci_arm_policy.get_serialized_output( @@ -4526,7 +4538,7 @@ class PolicyGeneratingSecurityContextUserEdgeCases(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -4676,7 +4688,7 @@ class PolicyGeneratingSecurityContextUserEdgeCases(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -5046,7 +5058,7 @@ def test_arm_template_security_context_no_run_as_user(self): self.assertEqual(deepdiff.DeepDiff(regular_image_json[0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_USER], expected_user_json, ignore_order=True), {}) def test_arm_template_security_context_uid_gid(self): - dockerfile_contents = ["FROM mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0\n", "USER 456:123\n"] + dockerfile_contents = ["FROM mcr.microsoft.com/azurelinux/distroless/base:3.0\n", "USER 456:123\n"] try: with open(self.dockerfile_path, "w") as dockerfile: @@ -5085,7 +5097,7 @@ def test_arm_template_security_context_uid_gid(self): self.client.images.remove(image[0].attrs.get("Id")) def test_arm_template_security_context_user_gid(self): - dockerfile_contents = ["FROM mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0\n", "USER test_user:123\n"] + dockerfile_contents = ["FROM mcr.microsoft.com/azurelinux/distroless/base:3.0\n", "USER test_user:123\n"] try: with open(self.dockerfile_path2, "w") as dockerfile: @@ -5124,7 +5136,7 @@ def test_arm_template_security_context_user_gid(self): self.client.images.remove(image[0].attrs.get("Id")) def test_arm_template_security_context_user_group(self): - dockerfile_contents = ["FROM mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0\n", "USER test_user:test_group\n"] + dockerfile_contents = ["FROM mcr.microsoft.com/azurelinux/distroless/base:3.0\n", "USER test_user:test_group\n"] try: with open(self.dockerfile_path3, "w") as dockerfile: dockerfile.writelines(dockerfile_contents) @@ -5163,7 +5175,7 @@ def test_arm_template_security_context_user_group(self): def test_arm_template_security_context_uid_group(self): # valid values are "user", "uid", - dockerfile_contents = ["FROM mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0\n", "USER 456:test_group\n"] + dockerfile_contents = ["FROM mcr.microsoft.com/azurelinux/distroless/base:3.0\n", "USER 456:test_group\n"] try: with open(self.dockerfile_path4, "w") as dockerfile: dockerfile.writelines(dockerfile_contents) @@ -5202,7 +5214,7 @@ def test_arm_template_security_context_uid_group(self): self.client.images.remove(image[0].attrs.get("Id")) def test_arm_template_security_context_uid(self): - dockerfile_contents = ["FROM mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0\n", "USER 456\n"] + dockerfile_contents = ["FROM mcr.microsoft.com/azurelinux/distroless/base:3.0\n", "USER 456\n"] try: with open(self.dockerfile_path5, "w") as dockerfile: dockerfile.writelines(dockerfile_contents) @@ -5240,7 +5252,7 @@ def test_arm_template_security_context_uid(self): self.client.images.remove(image[0].attrs.get("Id")) def test_arm_template_security_context_user_dockerfile(self): - dockerfile_contents = ["FROM mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0\n", "USER test_user\n"] + dockerfile_contents = ["FROM mcr.microsoft.com/azurelinux/distroless/base:3.0\n", "USER test_user\n"] try: with open(self.dockerfile_path6, "w") as dockerfile: dockerfile.writelines(dockerfile_contents) @@ -5284,7 +5296,7 @@ class PolicyGeneratingSecurityContextSeccompProfileEdgeCases(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, @@ -5637,7 +5649,7 @@ class PolicyZeroSidecar(unittest.TestCase): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_fragment.py b/src/confcom/azext_confcom/tests/latest/test_confcom_fragment.py index 743ddb41d85..7a4a06ca873 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_fragment.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_fragment.py @@ -12,7 +12,7 @@ from azext_confcom.security_policy import ( UserContainerImage, OutputType, - load_policy_from_config_str + load_policy_from_json ) from azext_confcom.cose_proxy import CoseSignToolProxy @@ -46,7 +46,7 @@ class FragmentMountEnforcement(unittest.TestCase): { "name": "test-container", "properties": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0", + "image": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "environmentVariables": [ { "name": "PATH", @@ -81,7 +81,7 @@ class FragmentMountEnforcement(unittest.TestCase): { "name": "simple-container", "properties": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "image": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [ { "name": "PATH", @@ -118,7 +118,7 @@ class FragmentMountEnforcement(unittest.TestCase): @classmethod def setUpClass(cls): - with load_policy_from_config_str(cls.custom_json) as aci_policy: + with load_policy_from_json(cls.custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() cls.aci_policy = aci_policy @@ -127,7 +127,7 @@ def test_fragment_user_container_customized_mounts(self): ( img for img in self.aci_policy.get_images() - if isinstance(img, UserContainerImage) and img.base == "mcr.microsoft.com/cbl-mariner/distroless/minimal" + if isinstance(img, UserContainerImage) and img.base == "mcr.microsoft.com/azurelinux/distroless/base" ), None, ) @@ -169,7 +169,7 @@ def test_fragment_user_container_mount_injected_dns(self): ( img for img in self.aci_policy.get_images() - if isinstance(img, UserContainerImage) and img.base == "mcr.microsoft.com/cbl-mariner/distroless/minimal" + if isinstance(img, UserContainerImage) and img.base == "mcr.microsoft.com/azurelinux/distroless/base" ), None, ) @@ -350,7 +350,7 @@ class FragmentGenerating(unittest.TestCase): @classmethod def setUpClass(cls): - with load_policy_from_config_str(cls.custom_json) as aci_policy: + with load_policy_from_json(cls.custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() cls.aci_policy = aci_policy @@ -473,7 +473,7 @@ class FragmentPolicyGeneratingDebugMode(unittest.TestCase): { "name": "test-container", "properties": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0", + "image": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "environmentVariables": [ ], @@ -487,7 +487,7 @@ class FragmentPolicyGeneratingDebugMode(unittest.TestCase): @classmethod def setUpClass(cls): - with load_policy_from_config_str(cls.custom_json, debug_mode=True) as aci_policy: + with load_policy_from_json(cls.custom_json, debug_mode=True) as aci_policy: aci_policy.populate_policy_content_for_all_images() cls.aci_policy = aci_policy @@ -563,10 +563,10 @@ class FragmentSidecarValidation(unittest.TestCase): @classmethod def setUpClass(cls): - with load_policy_from_config_str(cls.custom_json) as aci_policy: + with load_policy_from_json(cls.custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() cls.aci_policy = aci_policy - with load_policy_from_config_str(cls.custom_json2) as aci_policy2: + with load_policy_from_json(cls.custom_json2) as aci_policy2: aci_policy2.populate_policy_content_for_all_images() cls.aci_policy2 = aci_policy2 @@ -620,7 +620,7 @@ class FragmentPolicySigning(unittest.TestCase): { "name": "my-image", "properties": { - "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.8", + "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.9", "execProcesses": [ { "command": [ @@ -687,7 +687,7 @@ class FragmentPolicySigning(unittest.TestCase): { "name": "my-image", "properties": { - "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.8", + "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.9", "execProcesses": [ { "command": [ @@ -745,10 +745,10 @@ def setUpClass(cls): if item.returncode != 0: raise Exception("Error creating certificate chain") - with load_policy_from_config_str(cls.custom_json) as aci_policy: + with load_policy_from_json(cls.custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() cls.aci_policy = aci_policy - with load_policy_from_config_str(cls.custom_json2) as aci_policy2: + with load_policy_from_json(cls.custom_json2) as aci_policy2: aci_policy2.populate_policy_content_for_all_images() cls.aci_policy2 = aci_policy2 @@ -780,7 +780,7 @@ def test_generate_import(self): algo = "ES384" out_path = filename + ".cose" - fragment_text = self.aci_policy.generate_fragment("payload4", 1, OutputType.RAW) + fragment_text = self.aci_policy.generate_fragment("payload4", "1", OutputType.RAW) try: write_str_to_file(filename, fragment_text) @@ -788,7 +788,7 @@ def test_generate_import(self): iss = cose_proxy.create_issuer(self.chain) cose_proxy.cose_sign(filename, self.key, self.chain, feed, iss, algo, out_path) - import_statement = cose_proxy.generate_import_from_path(out_path, 1) + import_statement = cose_proxy.generate_import_from_path(out_path, "1") self.assertTrue(import_statement) self.assertEqual( import_statement.get(config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS_ISSUER,""),iss @@ -797,7 +797,7 @@ def test_generate_import(self): import_statement.get(config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS_FEED,""),feed ) self.assertEqual( - import_statement.get(config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS_MINIMUM_SVN,""),1 + import_statement.get(config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS_MINIMUM_SVN,""), "1" ) self.assertEqual( import_statement.get(config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS_INCLUDES,[]),[config.POLICY_FIELD_CONTAINERS, config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS] @@ -819,7 +819,7 @@ def test_local_fragment_references(self): out_path = filename + ".cose" out_path2 = filename2 + ".cose" - fragment_text = self.aci_policy.generate_fragment("payload2", 1, OutputType.RAW) + fragment_text = self.aci_policy.generate_fragment("payload2", "1", OutputType.RAW) try: write_str_to_file(filename, fragment_text) @@ -831,7 +831,7 @@ def test_local_fragment_references(self): # this will insert the import statement from the first fragment into the second one acifragmentgen_confcom( - None, None, None, None, None, None, None, None, generate_import=True, minimum_svn=1, fragments_json=fragment_json, fragment_path=out_path + None, None, None, None, None, None, None, None, generate_import=True, minimum_svn="1", fragments_json=fragment_json, fragment_path=out_path ) # put the "path" field into the import statement temp_json = load_json_from_file(fragment_json) @@ -840,7 +840,7 @@ def test_local_fragment_references(self): write_str_to_file(fragment_json, json.dumps(temp_json)) acifragmentgen_confcom( - None, fragment_json, None, "payload3", 1, feed2, self.key, self.chain, None, output_filename=filename2 + None, fragment_json, None, "payload3", "1", feed2, self.key, self.chain, None, output_filename=filename2 ) # make sure all of our output files exist @@ -852,7 +852,7 @@ def test_local_fragment_references(self): # see if the import statement is in the rego file self.assertTrue("test_feed" in rego_str) # make sure the image covered by the first fragment isn't in the second fragment - self.assertFalse("mcr.microsoft.com/acc/samples/aci/helloworld:2.8" in rego_str) + self.assertFalse("mcr.microsoft.com/acc/samples/aci/helloworld:2.9" in rego_str) except Exception as e: raise e finally: @@ -862,6 +862,85 @@ def test_local_fragment_references(self): delete_silently(out_path2) delete_silently(fragment_json) + +class FragmentVirtualNode(unittest.TestCase): + custom_json = """ +{ + "version": "1.0", + "scenario": "vn2", + "labels": { + "azure.workload.identity/use": true + }, + "containers": [ + { + "name": "test-container", + "properties": { + "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.9", + "environmentVariables": [ + { + "name": "PATH", + "value": ".+", + "regex": true + } + ], + "command": [ + "/bin/sh", + "-c", + "while true; do echo 'Hello World'; done" + ], + "securityContext": { + "privileged": true + } + } + } + ] +} + """ + + aci_policy = None + + @classmethod + def setUpClass(cls): + with load_policy_from_json(cls.custom_json) as aci_policy: + aci_policy.populate_policy_content_for_all_images() + cls.aci_policy = aci_policy + + def test_fragment_vn2_env_vars(self): + image = self.aci_policy.get_images()[0] + env_names = [i.get('pattern') for i in image._get_environment_rules()] + env_rules = [f"{i.get('name')}={i.get('value')}" for i in config.VIRTUAL_NODE_ENV_RULES] + for env_rule in env_rules: + self.assertIn(env_rule, env_names) + + def test_fragment_vn2_workload_identity_env_vars(self): + image = self.aci_policy.get_images()[0] + env_names = [i.get('pattern') for i in image._get_environment_rules()] + env_rules = [f"{i.get('name')}={i.get('value')}" for i in config.VIRTUAL_NODE_ENV_RULES_WORKLOAD_IDENTITY] + for env_rule in env_rules: + self.assertIn(env_rule, env_names) + + def test_fragment_vn2_user_mounts(self): + image = self.aci_policy.get_images()[0] + mount_destinations = [i.get('destination') for i in image._get_mounts_json()] + default_mounts = [i.get('mountPath') for i in config.DEFAULT_MOUNTS_VIRTUAL_NODE + config.DEFAULT_MOUNTS_USER_VIRTUAL_NODE] + for default_mount in default_mounts: + self.assertIn(default_mount, mount_destinations) + + def test_fragment_vn2_privileged_mounts(self): + image = self.aci_policy.get_images()[0] + mount_destinations = [i.get('destination') for i in image._get_mounts_json()] + default_mounts = [i.get('mountPath') for i in config.DEFAULT_MOUNTS_PRIVILEGED_VIRTUAL_NODE] + for default_mount in default_mounts: + self.assertIn(default_mount, mount_destinations) + + def test_fragment_vn2_workload_identity_mounts(self): + image = self.aci_policy.get_images()[0] + mount_destinations = [i.get('destination') for i in image._get_mounts_json()] + default_mounts = [i.get('mountPath') for i in config.DEFAULT_MOUNTS_WORKLOAD_IDENTITY_VIRTUAL_NODE] + for default_mount in default_mounts: + self.assertIn(default_mount, mount_destinations) + + class InitialFragmentErrors(ScenarioTest): def test_invalid_input(self): with self.assertRaises(CLIError) as wrapped_exit: diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_image.py b/src/confcom/azext_confcom/tests/latest/test_confcom_image.py index bb323473a86..9b95e82cc6d 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_image.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_image.py @@ -11,7 +11,7 @@ from azext_confcom.security_policy import ( OutputType, load_policy_from_image_name, - load_policy_from_str, + load_policy_from_json, ) from azext_confcom.template_util import DockerClient import azext_confcom.config as config @@ -25,7 +25,8 @@ class PolicyGeneratingImage(unittest.TestCase): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "name": "mcr.microsoft.com/azurelinux/base/python:3.12", + "containerImage": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [ ], @@ -38,10 +39,10 @@ class PolicyGeneratingImage(unittest.TestCase): @classmethod def setUpClass(cls): - with load_policy_from_image_name("mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot") as aci_policy: + with load_policy_from_image_name("mcr.microsoft.com/azurelinux/base/python:3.12") as aci_policy: aci_policy.populate_policy_content_for_all_images(individual_image=True) cls.aci_policy = aci_policy - with load_policy_from_str(cls.custom_json) as custom_policy: + with load_policy_from_json(cls.custom_json) as custom_policy: custom_policy.populate_policy_content_for_all_images() cls.custom_policy = custom_policy @@ -56,6 +57,7 @@ class PolicyGeneratingImageSidecar(unittest.TestCase): "version": "1.0", "containers": [ { + "name": "mcr.microsoft.com/aci/atlas-mount-azure-file-volume:master_20201210.2", "containerImage": "mcr.microsoft.com/aci/atlas-mount-azure-file-volume:master_20201210.2", "environmentVariables": [ @@ -76,7 +78,7 @@ def setUpClass(cls): ) as aci_policy: aci_policy.populate_policy_content_for_all_images(individual_image=True) cls.aci_policy = aci_policy - with load_policy_from_str(cls.custom_json) as custom_policy: + with load_policy_from_json(cls.custom_json) as custom_policy: custom_policy.populate_policy_content_for_all_images(individual_image=True) cls.custom_policy = custom_policy diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_policy_conversion.py b/src/confcom/azext_confcom/tests/latest/test_confcom_policy_conversion.py new file mode 100644 index 00000000000..94a1b50cbee --- /dev/null +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_policy_conversion.py @@ -0,0 +1,123 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import copy +import json +import unittest +from typing import Any, Dict + +from azext_confcom.template_util import ( + convert_config_v0_to_v1, + detect_old_format, +) +import azext_confcom.config as cfg + +class ConvertConfigTests(unittest.TestCase): + """Tests for the conversion of old-format ACI configs to v1 format. + + This includes: + • Old-format detection + • Default propagation of top-level fields (`version`, `fragments`) + • Environment-variable strategy → `regex` flag mapping + • `command` + liveness / readiness probes → `execProcesses` aggregation + • Volume-mount translation and naming rules + • Migration of `workingDir` and `allow_elevated` into security context + • Idempotence when input is already v1 + """ + + @classmethod + def setUpClass(cls) -> None: + cls._old_json = """ + { + "version": "1.0", + "containers": [{ + "name": "demo", + "containerImage": "demo:latest", + "environmentVariables": [ + {"name": "FOO", "value": "bar", "strategy": "string"}, + {"name": "REGEXP_VAR", "value": "v.*", "strategy": "re2"} + ], + "command": ["python", "app.py"], + "livenessProbe": {"exec": {"command": ["echo", "alive"]}}, + "readinessProbe": {"exec": {"command": ["echo", "ready"]}}, + "workingDir": "/work", + "allow_elevated": true, + "mounts": [{ + "mountType": "azureFile", + "mountPath": "/mnt/af", + "readonly": true + }] + }] + } + """ + cls._old_cfg: Dict[str, Any] = json.loads(cls._old_json) + cls._new_cfg: Dict[str, Any] = convert_config_v0_to_v1(copy.deepcopy(cls._old_cfg)) + + def test_detect_old_format(self) -> None: + self.assertTrue(detect_old_format(self._old_cfg)) + self.assertFalse(detect_old_format(self._new_cfg)) + + def test_top_level_fields_propagated(self) -> None: + self.assertEqual(self._new_cfg[cfg.ACI_FIELD_VERSION], "1.0") + self.assertEqual(self._new_cfg[cfg.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS], []) + + def test_container_count_preserved(self) -> None: + self.assertEqual( + len(self._new_cfg[cfg.ACI_FIELD_CONTAINERS]), + len(self._old_cfg[cfg.ACI_FIELD_CONTAINERS]), + ) + + def test_env_strategy_to_regex_flag(self) -> None: + new_container = self._new_cfg[cfg.ACI_FIELD_CONTAINERS][0] + envs = new_container[cfg.ACI_FIELD_TEMPLATE_PROPERTIES][ + cfg.ACI_FIELD_CONTAINERS_ENVS + ] + + string_env = next(e for e in envs if e["name"] == "FOO") + self.assertNotIn("regex", string_env) + + regex_env = next(e for e in envs if e["name"] == "REGEXP_VAR") + self.assertTrue(regex_env.get("regex", False)) + + def test_exec_processes_built_correctly(self) -> None: + props = self._new_cfg[cfg.ACI_FIELD_CONTAINERS][0][cfg.ACI_FIELD_TEMPLATE_PROPERTIES] + + # Primary entrypoint stays in `command` + self.assertEqual(props[cfg.ACI_FIELD_CONTAINERS_COMMAND], ["python", "app.py"]) + + # Only probe commands land in execProcesses + proc_cmds = [ + p[cfg.ACI_FIELD_CONTAINERS_COMMAND] + for p in props[cfg.ACI_FIELD_CONTAINERS_EXEC_PROCESSES] + ] + self.assertCountEqual(proc_cmds, [["echo", "alive"], ["echo", "ready"]]) + self.assertEqual(len(proc_cmds), 2) + + def test_volume_mount_basic_fields(self) -> None: + props = self._new_cfg[cfg.ACI_FIELD_CONTAINERS][0][ + cfg.ACI_FIELD_TEMPLATE_PROPERTIES + ] + vm = props[cfg.ACI_FIELD_TEMPLATE_VOLUME_MOUNTS][0] + + self.assertEqual(vm[cfg.ACI_FIELD_CONTAINERS_ENVS_NAME], "azurefile") + self.assertEqual(vm[cfg.ACI_FIELD_TEMPLATE_MOUNTS_PATH], "/mnt/af") + self.assertEqual(vm[cfg.ACI_FIELD_TEMPLATE_MOUNTS_TYPE], "azureFile") + self.assertTrue(vm[cfg.ACI_FIELD_TEMPLATE_MOUNTS_READONLY]) + + def test_workingdir_and_allow_elevated_migrated(self) -> None: + props = self._new_cfg[cfg.ACI_FIELD_CONTAINERS][0][ + cfg.ACI_FIELD_TEMPLATE_PROPERTIES + ] + self.assertEqual(props[cfg.ACI_FIELD_CONTAINERS_WORKINGDIR], "/work") + self.assertTrue( + props[cfg.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT][ + cfg.ACI_FIELD_CONTAINERS_PRIVILEGED + ] + ) + + def test_already_v1_returns_same_object(self) -> None: + v1_in = {"version": "1.0", "fragments": [], "containers": []} + v1_out = convert_config_v0_to_v1(copy.deepcopy(v1_in)) + self.assertEqual(v1_out, v1_in) diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_scenario.py b/src/confcom/azext_confcom/tests/latest/test_confcom_scenario.py index e47fc94e7ea..a6ccb14db2c 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_scenario.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_scenario.py @@ -10,7 +10,7 @@ from azext_confcom.security_policy import ( UserContainerImage, OutputType, - load_policy_from_str, + load_policy_from_json, ) import azext_confcom.config as config @@ -25,7 +25,8 @@ class MountEnforcement(unittest.TestCase): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0", + "name": "mcr.microsoft.com/azurelinux/distroless/base:3.0", + "containerImage": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "environmentVariables": [ { "name": "PATH", @@ -48,7 +49,8 @@ class MountEnforcement(unittest.TestCase): ] }, { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "name": "mcr.microsoft.com/azurelinux/base/python:3.12", + "containerImage": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [], "command": ["echo", "hello"], "workingDir": "/customized/absolute/path", @@ -64,7 +66,7 @@ class MountEnforcement(unittest.TestCase): @classmethod def setUpClass(cls): - with load_policy_from_str(cls.custom_json) as aci_policy: + with load_policy_from_json(cls.custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() cls.aci_policy = aci_policy @@ -73,7 +75,7 @@ def test_user_container_customized_mounts(self): ( img for img in self.aci_policy.get_images() - if isinstance(img, UserContainerImage) and img.base == "mcr.microsoft.com/cbl-mariner/distroless/minimal" + if isinstance(img, UserContainerImage) and img.base == "mcr.microsoft.com/azurelinux/distroless/base" ), None, ) @@ -112,7 +114,7 @@ def test_user_container_mount_injected_dns(self): ( img for img in self.aci_policy.get_images() - if isinstance(img, UserContainerImage) and img.base == "mcr.microsoft.com/cbl-mariner/distroless/python" + if isinstance(img, UserContainerImage) and img.base == "mcr.microsoft.com/azurelinux/base/python" ), None, ) @@ -154,6 +156,7 @@ class PolicyGenerating(unittest.TestCase): "version": "1.0", "containers": [ { + "name": "mcr.microsoft.com/aci/msi-atlas-adapter:master_20201203.1", "containerImage": "mcr.microsoft.com/aci/msi-atlas-adapter:master_20201203.1", "environmentVariables": [ { @@ -262,7 +265,7 @@ class PolicyGenerating(unittest.TestCase): @classmethod def setUpClass(cls): - with load_policy_from_str(cls.custom_json) as aci_policy: + with load_policy_from_json(cls.custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() cls.aci_policy = aci_policy @@ -365,7 +368,8 @@ class PolicyGeneratingDebugMode(unittest.TestCase): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "name": "mcr.microsoft.com/azurelinux/base/python:3.12", + "containerImage": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [ ], @@ -379,7 +383,7 @@ class PolicyGeneratingDebugMode(unittest.TestCase): @classmethod def setUpClass(cls): - with load_policy_from_str(cls.custom_json, debug_mode=True) as aci_policy: + with load_policy_from_json(cls.custom_json, debug_mode=True) as aci_policy: aci_policy.populate_policy_content_for_all_images() cls.aci_policy = aci_policy @@ -412,6 +416,7 @@ class SidecarValidation(unittest.TestCase): "version": "1.0", "containers": [ { + "name": "mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1", "containerImage": "mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1", "environmentVariables": [ { @@ -436,6 +441,7 @@ class SidecarValidation(unittest.TestCase): "version": "1.0", "containers": [ { + "name": "mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1", "containerImage": "mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1", "environmentVariables": [ {"name": "PATH", @@ -459,10 +465,10 @@ class SidecarValidation(unittest.TestCase): @classmethod def setUpClass(cls): - with load_policy_from_str(cls.custom_json) as aci_policy: + with load_policy_from_json(cls.custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() cls.aci_policy = aci_policy - with load_policy_from_str(cls.custom_json2) as aci_policy2: + with load_policy_from_json(cls.custom_json2) as aci_policy2: aci_policy2.populate_policy_content_for_all_images() cls.aci_policy2 = aci_policy2 @@ -505,7 +511,8 @@ def test_customized_workingdir(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "name": "mcr.microsoft.com/azurelinux/base/python:3.12", + "containerImage": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [], "command": ["echo", "hello"], "workingDir": "/customized/absolute/path" @@ -513,7 +520,7 @@ def test_customized_workingdir(self): ] } """ - with load_policy_from_str(custom_json) as aci_policy: + with load_policy_from_json(custom_json) as aci_policy: # pull actual image to local for next step image = next( ( @@ -533,7 +540,8 @@ def test_allow_elevated(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "name": "mcr.microsoft.com/azurelinux/base/python:3.12", + "containerImage": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [], "command": ["echo", "hello"], "workingDir": "/customized/absolute/path", @@ -542,7 +550,7 @@ def test_allow_elevated(self): ] } """ - with load_policy_from_str(custom_json) as aci_policy: + with load_policy_from_json(custom_json) as aci_policy: # pull actual image to local for next step image = next( ( @@ -562,14 +570,15 @@ def test_image_layers_python(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "name": "mcr.microsoft.com/azurelinux/base/python:3.12", + "containerImage": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [], "command": ["echo", "hello"] } ] } """ - with load_policy_from_str(custom_json) as aci_policy: + with load_policy_from_json(custom_json) as aci_policy: # pull actual image to local for next step with DockerClient() as client: image_ref = aci_policy.get_images()[0] @@ -577,8 +586,9 @@ def test_image_layers_python(self): aci_policy.populate_policy_content_for_all_images() layers = aci_policy.get_images()[0]._layers expected_layers = [ - "81f4183d118e68e8bb3c8845da3b0fe3cc331a63184b534ea0307d4d01c52418", - "3a1edaedd7e7e0072846d74219577c05b25498c233196d38ae0fe6cde8c2c92a" + "1824eb49720f59202136bd38681f32e57239676c9a423fb1e025aa210aaa22aa", + "8d68e2b0c2c32fa7fab2a5543d94e0b70b5bea400ca7f89f4c925a02ae15f453", + "44e8e0480dc3c0d04564ba87b3ba851cfab008717abdf6be83baf47963599614" ] self.assertEqual(len(layers), len(expected_layers)) for i in range(len(expected_layers)): @@ -590,14 +600,15 @@ def test_docker_pull(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0", + "name": "mcr.microsoft.com/azurelinux/distroless/base:3.0", + "containerImage": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "environmentVariables": [], "command": ["echo", "hello"] } ] } """ - with load_policy_from_str(custom_json) as aci_policy: + with load_policy_from_json(custom_json) as aci_policy: with DockerClient() as client: image_ref = aci_policy.get_images()[0] image = client.images.pull(image_ref.base, tag=image_ref.tag) @@ -605,7 +616,7 @@ def test_docker_pull(self): self.assertEqual( image.tags[0], - "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0", + "mcr.microsoft.com/azurelinux/distroless/base:3.0", ) def test_infrastructure_svn(self): @@ -614,14 +625,15 @@ def test_infrastructure_svn(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0", + "name": "mcr.microsoft.com/azurelinux/distroless/base:3.0", + "containerImage": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "environmentVariables": [], "command": ["echo", "hello"] } ] } """ - with load_policy_from_str(custom_json) as aci_policy: + with load_policy_from_json(custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() output = aci_policy.get_serialized_output(OutputType.PRETTY_PRINT) @@ -633,7 +645,8 @@ def test_environment_variables_parsing(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/azuredocs/aci-dataprocessing-cc:v1", + "name": "mcr.microsoft.com/azurelinux/distroless/base:3.0", + "containerImage": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "environmentVariables": [ { "name": "env-name1", @@ -651,7 +664,7 @@ def test_environment_variables_parsing(self): ] } """ - containers = load_policy_from_str(custom_json).get_images() + containers = load_policy_from_json(custom_json).get_images() self.assertEqual(len(containers), 1) envs = containers[0]._environmentRules self.assertIsNotNone(envs) @@ -690,14 +703,15 @@ def test_stdio_access_default(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "name": "mcr.microsoft.com/azurelinux/base/python:3.12", + "containerImage": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [], "command": ["echo", "hello"] } ] } """ - with load_policy_from_str(custom_json) as aci_policy: + with load_policy_from_json(custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() self.assertTrue( json.loads( @@ -713,7 +727,8 @@ def test_stdio_access_updated(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "name": "mcr.microsoft.com/azurelinux/base/python:3.12", + "containerImage": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [], "command": ["echo", "hello"], "allowStdioAccess": false @@ -721,7 +736,7 @@ def test_stdio_access_updated(self): ] } """ - with load_policy_from_str(custom_json) as aci_policy: + with load_policy_from_json(custom_json, disable_stdio=True) as aci_policy: aci_policy.populate_policy_content_for_all_images() self.assertFalse( @@ -733,12 +748,13 @@ def test_stdio_access_updated(self): ) def test_omit_id(self): - image_name = "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + image_name = "mcr.microsoft.com/azurelinux/base/python:3.12" custom_json = f""" {{ "version": "1.0", "containers": [ {{ + "name": "{image_name}", "containerImage": "{image_name}", "environmentVariables": [], "command": ["echo", "hello"], @@ -747,7 +763,7 @@ def test_omit_id(self): ] }} """ - with load_policy_from_str(custom_json) as aci_policy: + with load_policy_from_json(custom_json) as aci_policy: aci_policy.populate_policy_content_for_all_images() self.assertIsNone( @@ -776,6 +792,7 @@ def test_get_layers_from_not_exists_image(self): "version": "1.0", "containers": [ { + "name": "fake-name", "containerImage": "notexists:1.0.0", "environmentVariables": [], "command": ["echo", "hello"] @@ -783,7 +800,7 @@ def test_get_layers_from_not_exists_image(self): ] } """ - with load_policy_from_str(custom_json) as aci_policy: + with load_policy_from_json(custom_json) as aci_policy: with self.assertRaises(SystemExit) as exc_info: aci_policy.populate_policy_content_for_all_images() self.assertEqual(exc_info.exception.code, 1) @@ -794,7 +811,8 @@ def test_incorrect_allow_elevated_data_type(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0", + "name": "mcr.microsoft.com/azurelinux/distroless/base:3.0", + "containerImage": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "environmentVariables": [], "command": "echo hello", "workingDir": "relative/string/path", @@ -805,7 +823,7 @@ def test_incorrect_allow_elevated_data_type(self): """ # allow_elevated can only be a boolean with self.assertRaises(SystemExit) as exc_info: - load_policy_from_str(custom_json) + load_policy_from_json(custom_json) self.assertEqual(exc_info.exception.code, 1) def test_incorrect_workingdir_path(self): @@ -814,7 +832,8 @@ def test_incorrect_workingdir_path(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0", + "name": "mcr.microsoft.com/azurelinux/distroless/base:3.0", + "containerImage": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "environmentVariables": [], "command": "echo hello", "workingDir": "relative/string/path" @@ -824,7 +843,7 @@ def test_incorrect_workingdir_path(self): """ # workingDir can only be absolute path string with self.assertRaises(SystemExit) as exc_info: - load_policy_from_str(custom_json) + load_policy_from_json(custom_json) self.assertEqual(exc_info.exception.code, 1) def test_incorrect_workingdir_data_type(self): @@ -833,7 +852,8 @@ def test_incorrect_workingdir_data_type(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0", + "name": "mcr.microsoft.com/azurelinux/distroless/base:3.0", + "containerImage": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "environmentVariables": [], "command": "echo hello", "workingDir": ["hello"] @@ -843,7 +863,7 @@ def test_incorrect_workingdir_data_type(self): """ # workingDir can only be single string with self.assertRaises(SystemExit) as exc_info: - load_policy_from_str(custom_json) + load_policy_from_json(custom_json) self.assertEqual(exc_info.exception.code, 1) def test_incorrect_command_data_type(self): @@ -852,7 +872,8 @@ def test_incorrect_command_data_type(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0", + "name": "mcr.microsoft.com/azurelinux/distroless/base:3.0", + "containerImage": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "environmentVariables": [], "command": "echo hello" } @@ -861,7 +882,7 @@ def test_incorrect_command_data_type(self): """ # command can only be list of strings with self.assertRaises(SystemExit) as exc_info: - load_policy_from_str(custom_json) + load_policy_from_json(custom_json) self.assertEqual(exc_info.exception.code, 1) def test_json_missing_containers(self): @@ -871,7 +892,7 @@ def test_json_missing_containers(self): } """ with self.assertRaises(SystemExit) as exc_info: - load_policy_from_str(custom_json) + load_policy_from_json(custom_json) self.assertEqual(exc_info.exception.code, 1) def test_json_missing_containerImage(self): @@ -880,6 +901,7 @@ def test_json_missing_containerImage(self): "version": "1.0", "containers": [ { + "name": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "environmentVariables": [ { "name": "port", @@ -893,7 +915,7 @@ def test_json_missing_containerImage(self): } """ with self.assertRaises(SystemExit) as exc_info: - load_policy_from_str(custom_json) + load_policy_from_json(custom_json) self.assertEqual(exc_info.exception.code, 1) def test_json_missing_environmentVariables(self): @@ -902,15 +924,24 @@ def test_json_missing_environmentVariables(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/azuredocs/aci-dataprocessing-cc:v1", + "name": "mcr.microsoft.com/azurelinux/distroless/base:3.0", + "containerImage": "mcr.microsoft.com/azurelinux/distroless/base:3.0", "command": ["python", "app.py"] } ] } """ - with self.assertRaises(SystemExit) as exc_info: - load_policy_from_str(custom_json) - self.assertEqual(exc_info.exception.code, 1) + with load_policy_from_json(custom_json) as aci_policy: + aci_policy.populate_policy_content_for_all_images() + + self.assertIsNotNone( + json.loads( + aci_policy.get_serialized_output( + output_type=OutputType.RAW, rego_boilerplate=False, omit_id=True + ) + )[0].get(config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS) + ) + def test_json_missing_command(self): custom_json = """ @@ -918,7 +949,8 @@ def test_json_missing_command(self): "version": "1.0", "containers": [ { - "containerImage": "mcr.microsoft.com/azuredocs/aci-dataprocessing-cc:v1", + "name": "mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1", + "containerImage": "mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1", "environmentVariables": [ { "name": "port", @@ -930,6 +962,15 @@ def test_json_missing_command(self): ] } """ - with self.assertRaises(SystemExit) as exc_info: - load_policy_from_str(custom_json) - self.assertEqual(exc_info.exception.code, 1) + with load_policy_from_json(custom_json) as aci_policy: + aci_policy.populate_policy_content_for_all_images() + + self.assertIsNotNone( + json.loads( + aci_policy.get_serialized_output( + output_type=OutputType.RAW, rego_boilerplate=False, omit_id=True + ) + )[0].get(config.POLICY_FIELD_CONTAINERS_ELEMENTS_COMMANDS) + ) + + diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_tar.py b/src/confcom/azext_confcom/tests/latest/test_confcom_tar.py index 5567a266226..0dc99e631de 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_tar.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_tar.py @@ -428,7 +428,7 @@ def test_arm_template_mixed_mode_tar(self): "metadata": { "description": "Name for the container group" }, - "defaultValue":"mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "defaultValue":"mcr.microsoft.com/azurelinux/base/python:3.12" }, "containername2": { "type": "string", @@ -572,7 +572,7 @@ def test_arm_template_mixed_mode_tar(self): filename = os.path.join(self.path, "./mariner2.tar") create_tar_file(filename) - image_mapping = {"mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot": filename} + image_mapping = {"mcr.microsoft.com/azurelinux/base/python:3.12": filename} # check to make sure many:1 mapping doesn't work with self.assertRaises(SystemExit) as exc_info: @@ -624,7 +624,7 @@ def test_arm_template_with_parameter_file_clean_room_tar_invalid(self): "metadata": { "description": "Name for the container group" }, - "defaultValue":"mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0" + "defaultValue":"mcr.microsoft.com/azurelinux/distroless/base:3.0" }, "containername": { "type": "string", @@ -766,7 +766,7 @@ def test_clean_room_fake_tar_invalid(self): "metadata": { "description": "Name for the container group" }, - "defaultValue":"mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0" + "defaultValue":"mcr.microsoft.com/azurelinux/distroless/base:3.0" }, "containername": { "type": "string", diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_template_util.py b/src/confcom/azext_confcom/tests/latest/test_confcom_template_util.py index edaab331ded..08a88bf87d0 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_template_util.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_template_util.py @@ -350,7 +350,7 @@ def test_inject_policy_into_template(self): "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "variables": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot" + "image": "mcr.microsoft.com/azurelinux/base/python:3.12" }, diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_virtual_node.py b/src/confcom/azext_confcom/tests/latest/test_confcom_virtual_node.py index a3d6d8fbec5..4cab7fe2b0e 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_virtual_node.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_virtual_node.py @@ -11,7 +11,7 @@ import azext_confcom.os_util as os_util from azext_confcom.template_util import extract_containers_from_text from azext_confcom.security_policy import ( - load_policy_from_str, + load_policy_from_json, load_policy_from_virtual_node_yaml_str, OutputType, decompose_confidential_properties @@ -33,7 +33,7 @@ class PolicyGeneratingVirtualNode(unittest.TestCase): "containers": [ { "name": "simple-container", - "containerImage": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "containerImage": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [ { "name":"PATH", @@ -68,7 +68,7 @@ class PolicyGeneratingVirtualNode(unittest.TestCase): { "name": "simple-container", "properties": { - "image": "mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot", + "image": "mcr.microsoft.com/azurelinux/base/python:3.12", "environmentVariables": [ { "name": "PATH", @@ -106,7 +106,7 @@ class PolicyGeneratingVirtualNode(unittest.TestCase): spec: containers: - name: simple-container - image: mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot + image: mcr.microsoft.com/azurelinux/base/python:3.12 command: ["python3"] env: - name: PATH @@ -138,7 +138,7 @@ class PolicyGeneratingVirtualNode(unittest.TestCase): spec: containers: - name: simple-container - image: mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot + image: mcr.microsoft.com/azurelinux/base/python:3.12 command: - python3 env: @@ -201,7 +201,7 @@ class PolicyGeneratingVirtualNode(unittest.TestCase): spec: containers: - name: simple-container - image: mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot + image: mcr.microsoft.com/azurelinux/base/python:3.12 command: - python3 env: @@ -267,7 +267,7 @@ class PolicyGeneratingVirtualNode(unittest.TestCase): spec: initContainers: - name: init-container - image: mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 + image: mcr.microsoft.com/azurelinux/distroless/base:3.0 command: - echo "hello world!" env: @@ -275,7 +275,7 @@ class PolicyGeneratingVirtualNode(unittest.TestCase): value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin containers: - name: simple-container - image: mcr.microsoft.com/cbl-mariner/distroless/python:3.9-nonroot + image: mcr.microsoft.com/azurelinux/base/python:3.12 command: - python3 """ @@ -298,7 +298,7 @@ class PolicyGeneratingVirtualNode(unittest.TestCase): spec: containers: - name: nginx - image: mcr.microsoft.com/cbl-mariner/distroless/minimal:2.0 + image: mcr.microsoft.com/azurelinux/distroless/base:3.0 ports: - containerPort: 80 name: web @@ -365,7 +365,7 @@ def setUpClass(cls): raise Exception("Error creating certificate chain") def test_compare_policy_sources(self): - custom_policy = load_policy_from_str(self.custom_json) + custom_policy = load_policy_from_json(self.custom_json) custom_policy.populate_policy_content_for_all_images() virtual_node_policy = load_policy_from_virtual_node_yaml_str(self.custom_yaml)[0] virtual_node_policy.populate_policy_content_for_all_images() @@ -383,17 +383,18 @@ def test_compare_policy_sources(self): def test_virtual_node_policy_fragments(self): + fragment_filename = "policy_file.json" + yaml_filename = "policy_file.yaml" + rego_filename = "example_file" + import_filename = "my_fragments.json" + signed_file_path = f"{rego_filename}.rego.cose" try: - fragment_filename = "policy_file.json" - yaml_filename = "policy_file.yaml" + os_util.write_str_to_file(fragment_filename, self.custom_json2) os_util.write_str_to_file(yaml_filename, self.custom_yaml) - rego_filename = "example_file" acifragmentgen_confcom(None, fragment_filename, None, rego_filename, "1", "test_feed_file", self.key, self.chain, None) # create import file - import_filename = "my_fragments.json" - signed_file_path = f"{rego_filename}.rego.cose" acifragmentgen_confcom(None, None, None, None, None, None, None, None, "1", fragment_path=signed_file_path, generate_import=True, fragments_json=import_filename) # add path into the fragment import import_data = os_util.load_json_from_file(import_filename) diff --git a/src/confcom/samples/config.json b/src/confcom/samples/config.json index 4d6709e713d..d54b5e31110 100644 --- a/src/confcom/samples/config.json +++ b/src/confcom/samples/config.json @@ -14,7 +14,7 @@ { "name": "my-image", "properties": { - "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.8", + "image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.9", "execProcesses": [ { "command": [ diff --git a/src/confcom/samples/contoso.rego b/src/confcom/samples/contoso.rego index bc10bbdebf0..d2b26c51c08 100644 --- a/src/confcom/samples/contoso.rego +++ b/src/confcom/samples/contoso.rego @@ -1,277 +1,213 @@ -package contoso - -svn := "1" -framework_version := "0.2.3" - -fragments := [] - -containers := [ - { - "allow_elevated": false, - "allow_stdio_access": true, - "capabilities": { - "ambient": [], - "bounding": [ - "CAP_AUDIT_WRITE", - "CAP_CHOWN", - "CAP_DAC_OVERRIDE", - "CAP_FOWNER", - "CAP_FSETID", - "CAP_KILL", - "CAP_MKNOD", - "CAP_NET_BIND_SERVICE", - "CAP_NET_RAW", - "CAP_SETFCAP", - "CAP_SETGID", - "CAP_SETPCAP", - "CAP_SETUID", - "CAP_SYS_CHROOT" - ], - "effective": [ - "CAP_AUDIT_WRITE", - "CAP_CHOWN", - "CAP_DAC_OVERRIDE", - "CAP_FOWNER", - "CAP_FSETID", - "CAP_KILL", - "CAP_MKNOD", - "CAP_NET_BIND_SERVICE", - "CAP_NET_RAW", - "CAP_SETFCAP", - "CAP_SETGID", - "CAP_SETPCAP", - "CAP_SETUID", - "CAP_SYS_CHROOT" - ], - "inheritable": [], - "permitted": [ - "CAP_AUDIT_WRITE", - "CAP_CHOWN", - "CAP_DAC_OVERRIDE", - "CAP_FOWNER", - "CAP_FSETID", - "CAP_KILL", - "CAP_MKNOD", - "CAP_NET_BIND_SERVICE", - "CAP_NET_RAW", - "CAP_SETFCAP", - "CAP_SETGID", - "CAP_SETPCAP", - "CAP_SETUID", - "CAP_SYS_CHROOT" - ] - }, - "command": [ - "python3", - "main.py" - ], - "env_rules": [ - { - "pattern": "TEST_REGEXP_ENV=test_regexp_env(.*)", - "required": false, - "strategy": "re2" - }, - { - "pattern": "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "required": false, - "strategy": "string" - }, - { - "pattern": "PYTHONUNBUFFERED=1", - "required": false, - "strategy": "string" - }, - { - "pattern": "TERM=xterm", - "required": false, - "strategy": "string" - }, - { - "pattern": "(?i)(FABRIC)_.+=.+", - "required": false, - "strategy": "re2" - }, - { - "pattern": "HOSTNAME=.+", - "required": false, - "strategy": "re2" - }, - { - "pattern": "T(E)?MP=.+", - "required": false, - "strategy": "re2" - }, - { - "pattern": "FabricPackageFileName=.+", - "required": false, - "strategy": "re2" - }, - { - "pattern": "HostedServiceName=.+", - "required": false, - "strategy": "re2" - }, - { - "pattern": "IDENTITY_API_VERSION=.+", - "required": false, - "strategy": "re2" - }, - { - "pattern": "IDENTITY_HEADER=.+", - "required": false, - "strategy": "re2" - }, - { - "pattern": "IDENTITY_SERVER_THUMBPRINT=.+", - "required": false, - "strategy": "re2" - }, - { - "pattern": "azurecontainerinstance_restarted_by=.+", - "required": false, - "strategy": "re2" - } - ], - "exec_processes": [], - "id": "mcr.microsoft.com/acc/samples/aci/helloworld:2.8", - "layers": [ - "0de62d1aaa53f09c1ba26871cc97bda0ed29ea2eba4eb95c42b800159f0c087c", - "1db0e60df71bbeda66196a3b518967cbc1b650cda08ada110744e0e07c965a5a", - "e5c725f6ef8eae5de23753c9af8ca5489153eecd12982a0db0fc13d93fc7e124", - "fdafe8a7071ca0af2ec45276bd7c4abe8aa3068b1fef08856251cf19638c52f2", - "398208096568e4d3b1f7e420038c23d2bd3ba0a6c6b21b0f0d8f61c04d796bd7" - ], - "mounts": [ - { - "destination": "/mount/azurefile", - "options": [ - "rbind", - "rshared", - "rw" - ], - "source": "sandbox:///tmp/atlas/azureFileVolume/.+", - "type": "bind" - }, - { - "destination": "/etc/resolv.conf", - "options": [ - "rbind", - "rshared", - "rw" - ], - "source": "sandbox:///tmp/atlas/resolvconf/.+", - "type": "bind" - } - ], - "no_new_privileges": false, - "seccomp_profile_sha256": "", - "signals": [], - "user": { - "group_idnames": [ - { - "pattern": "", - "strategy": "any" - } - ], - "umask": "0022", - "user_idname": { - "pattern": "", - "strategy": "any" - } - }, - "working_dir": "/app" - }, - { - "allow_elevated": false, - "allow_stdio_access": true, - "capabilities": { - "ambient": [], - "bounding": [ - "CAP_CHOWN", - "CAP_DAC_OVERRIDE", - "CAP_FSETID", - "CAP_FOWNER", - "CAP_MKNOD", - "CAP_NET_RAW", - "CAP_SETGID", - "CAP_SETUID", - "CAP_SETFCAP", - "CAP_SETPCAP", - "CAP_NET_BIND_SERVICE", - "CAP_SYS_CHROOT", - "CAP_KILL", - "CAP_AUDIT_WRITE" - ], - "effective": [ - "CAP_CHOWN", - "CAP_DAC_OVERRIDE", - "CAP_FSETID", - "CAP_FOWNER", - "CAP_MKNOD", - "CAP_NET_RAW", - "CAP_SETGID", - "CAP_SETUID", - "CAP_SETFCAP", - "CAP_SETPCAP", - "CAP_NET_BIND_SERVICE", - "CAP_SYS_CHROOT", - "CAP_KILL", - "CAP_AUDIT_WRITE" - ], - "inheritable": [], - "permitted": [ - "CAP_CHOWN", - "CAP_DAC_OVERRIDE", - "CAP_FSETID", - "CAP_FOWNER", - "CAP_MKNOD", - "CAP_NET_RAW", - "CAP_SETGID", - "CAP_SETUID", - "CAP_SETFCAP", - "CAP_SETPCAP", - "CAP_NET_BIND_SERVICE", - "CAP_SYS_CHROOT", - "CAP_KILL", - "CAP_AUDIT_WRITE" - ] - }, - "command": [ - "/pause" - ], - "env_rules": [ - { - "pattern": "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "required": true, - "strategy": "string" - }, - { - "pattern": "TERM=xterm", - "required": false, - "strategy": "string" - } - ], - "exec_processes": [], - "layers": [ - "16b514057a06ad665f92c02863aca074fd5976c755d26bff16365299169e8415" - ], - "mounts": [], - "no_new_privileges": false, - "seccomp_profile_sha256": "", - "signals": [], - "user": { - "group_idnames": [ - { - "pattern": "", - "strategy": "any" - } - ], - "umask": "0022", - "user_idname": { - "pattern": "", - "strategy": "any" - } - }, - "working_dir": "/" - } -] +package contoso + +svn := "1" +framework_version := "0.2.3" + +fragments := [ + { + "feed": "contoso.azurecr.io/infra", + "includes": [ + "containers" + ], + "issuer": "did:x509:0:sha256:I__iuL25oXEVFdTP_aBLx_eT1RPHbCQ_ECBQfYZpt9s::eku:1.3.6.1.4.1.311.76.59.1.3", + "minimum_svn": "1" + }, + { + "feed": "mcr.microsoft.com/aci/aci-cc-infra-fragment", + "includes": [ + "containers", + "fragments" + ], + "issuer": "did:x509:0:sha256:I__iuL25oXEVFdTP_aBLx_eT1RPHbCQ_ECBQfYZpt9s::eku:1.3.6.1.4.1.311.76.59.1.3", + "minimum_svn": "1" + } +] + +containers := [ + { + "allow_elevated": false, + "allow_stdio_access": true, + "capabilities": { + "ambient": [], + "bounding": [ + "CAP_AUDIT_WRITE", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SETFCAP", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYS_CHROOT" + ], + "effective": [ + "CAP_AUDIT_WRITE", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SETFCAP", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYS_CHROOT" + ], + "inheritable": [], + "permitted": [ + "CAP_AUDIT_WRITE", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SETFCAP", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYS_CHROOT" + ] + }, + "command": [ + "python3", + "main.py" + ], + "env_rules": [ + { + "pattern": "PATH=/customized/path/value", + "required": false, + "strategy": "string" + }, + { + "pattern": "TEST_REGEXP_ENV=test_regexp_env(.*)", + "required": false, + "strategy": "re2" + }, + { + "pattern": "PYTHONUNBUFFERED=1", + "required": false, + "strategy": "string" + }, + { + "pattern": "TERM=xterm", + "required": false, + "strategy": "string" + }, + { + "pattern": "(?i)(FABRIC)_.+=.+", + "required": false, + "strategy": "re2" + }, + { + "pattern": "HOSTNAME=.+", + "required": false, + "strategy": "re2" + }, + { + "pattern": "T(E)?MP=.+", + "required": false, + "strategy": "re2" + }, + { + "pattern": "FabricPackageFileName=.+", + "required": false, + "strategy": "re2" + }, + { + "pattern": "HostedServiceName=.+", + "required": false, + "strategy": "re2" + }, + { + "pattern": "IDENTITY_API_VERSION=.+", + "required": false, + "strategy": "re2" + }, + { + "pattern": "IDENTITY_HEADER=.+", + "required": false, + "strategy": "re2" + }, + { + "pattern": "IDENTITY_SERVER_THUMBPRINT=.+", + "required": false, + "strategy": "re2" + }, + { + "pattern": "azurecontainerinstance_restarted_by=.+", + "required": false, + "strategy": "re2" + } + ], + "exec_processes": [ + { + "command": [ + "echo", + "Hello World" + ], + "signals": [] + } + ], + "id": "mcr.microsoft.com/acc/samples/aci/helloworld:2.9", + "layers": [ + "4e74440c7b0e6e6c1cc9e6eb9b779e1ffde807122ed8a16bb0422a1d64fd5aa8", + "4cf856bcde8e1fa71f57d2218e21dd7c1a6a12c6d930d2bdb4bdb13a46fed9e4", + "41a52f45506177737caec5d57fe6160b6c8942dcac1bc7834fc0e94e62ff6b4d", + "b8ea8eae7795453b5e3dcfafe3f11fb2d68efb1062308e4d2411d44dd19fa97c", + "a0df1939f552483286c45204e7f583c9a6146963a79556fe22578d7b7e63e7a1", + "3ccbd6b119e951f3f2586339e9d10168b064a5852fd87cfae94af47a89f4d6c6", + "8348c9d4357db6a600aa4c5116ed9755a230d274096706a7d214c02105d0b256" + ], + "mounts": [ + { + "destination": "/mount/azurefile", + "options": [ + "rbind", + "rshared", + "ro" + ], + "source": "sandbox:///tmp/atlas/azureFileVolume/.+", + "type": "bind" + }, + { + "destination": "/etc/resolv.conf", + "options": [ + "rbind", + "rshared", + "rw" + ], + "source": "sandbox:///tmp/atlas/resolvconf/.+", + "type": "bind" + } + ], + "name": "my-image", + "no_new_privileges": false, + "seccomp_profile_sha256": "", + "signals": [], + "user": { + "group_idnames": [ + { + "pattern": "", + "strategy": "any" + } + ], + "umask": "0022", + "user_idname": { + "pattern": "", + "strategy": "any" + } + }, + "working_dir": "/app" + } +] diff --git a/src/confcom/setup.py b/src/confcom/setup.py index f3a80ffcd2e..b3c6f2b7f33 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.2.4" +VERSION = "1.2.5" # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers