diff --git a/linter_exclusions.yml b/linter_exclusions.yml index ce4aaa82bca..0c79cb63abf 100644 --- a/linter_exclusions.yml +++ b/linter_exclusions.yml @@ -3504,3 +3504,15 @@ neon postgres organization: neon postgres project: rule_exclusions: - require_wait_command_if_no_wait + +confcom containers from_radius: + parameters: + template: + rule_exclusions: + - no_positional_parameters + +confcom radius policy insert: + parameters: + policy_file: + rule_exclusions: + - no_positional_parameters diff --git a/src/confcom/azext_confcom/_help.py b/src/confcom/azext_confcom/_help.py index 15368cc61db..2a92b21c344 100644 --- a/src/confcom/azext_confcom/_help.py +++ b/src/confcom/azext_confcom/_help.py @@ -278,3 +278,83 @@ - name: Input a Kubernetes YAML file with a custom containerd socket path text: az confcom katapolicygen --yaml "./pod.json" --containerd-pull --containerd-socket-path "/my/custom/containerd.sock" """ + + +helps[ + "confcom containers" +] = """ + type: group + short-summary: Commands which generate Security Policy Container Definitions. +""" + + +helps[ + "confcom containers from_radius" +] = """ + type: command + short-summary: Create a Security Policy Container Definition based on a Radius app template. + + parameters: + - name: --parameters -p + type: string + short-summary: 'Input parameters file to optionally accompany a Bicep Template' + + - name: --idx + type: int + short-summary: 'The index of the container resource in the template to generate the policy for. Default is 0' + + - name: --platform + type: str + short-summary: 'The name of the platform the container definition will run on' + + + examples: + - name: Input a Bicep Template and generate container definitions + text: az confcom containers from_radius app.bicep + - name: Input a Bicep Template with a bicepparam file and generate container definitions + text: az confcom containers from_radius app.bicep --parameters app.bicepparam + - name: Input a Bicep Template with inline parameter and generate container definitions + text: az confcom containers from_radius app.bicep --parameters image=my.azurecr.io/myimage:tag + - name: Input a Bicep Template and generate container definitions for the second container resource + text: az confcom containers from_radius app.bicep --idx 1 +""" + + +helps[ + "confcom radius" +] = """ + type: group + short-summary: Commands related to Radius. +""" + + +helps[ + "confcom radius policy" +] = """ + type: group + short-summary: Commands related to Radius policies. +""" + + +helps[ + "confcom radius policy insert" +] = """ + type: command + short-summary: Inserts a Security Policy into a Radius app template. + + parameters: + - name: --template-file -f + type: string + short-summary: 'Input parameters file to optionally accompany a Bicep Template' + + - name: --idx + type: int + short-summary: 'The index of the container resource in the template to generate the policy for. Default is 0' + + + examples: + - name: Insert a Security Policy into a Radius app template + text: az confcom radius policy insert policy.rego -f app.bicep + - name: Insert a Security Policy into a Radius app template for the second container resource + text: az confcom radius policy insert policy.rego -f app.bicep --idx 1 +""" diff --git a/src/confcom/azext_confcom/_params.py b/src/confcom/azext_confcom/_params.py index ccbea8d0091..1bea4840664 100644 --- a/src/confcom/azext_confcom/_params.py +++ b/src/confcom/azext_confcom/_params.py @@ -4,8 +4,11 @@ # -------------------------------------------------------------------------------------------- # pylint: disable=line-too-long +import argparse import json +import sys from knack.arguments import CLIArgumentType +from argcomplete.completers import FilesCompleter from azext_confcom._validators import ( validate_params_file, validate_diff, @@ -434,3 +437,59 @@ def load_arguments(self, _): help="Path to containerd socket if not using the default", validator=validate_katapolicygen_input, ) + + with self.argument_context("confcom containers from_radius") as c: + c.positional( + "template", + type=str, + help="Template to create container definitions from", + ) + c.argument( + "parameters", + options_list=['--parameters', '-p'], + action='append', + nargs='+', + completer=FilesCompleter(), + required=False, + default=[], + help='The parameters for the radius template' + ) + c.argument( + "container_index", + options_list=['--idx'], + required=False, + default=0, + type=int, + help='The index of the container definition in the template to use' + ) + c.argument( + "platform", + options_list=["--platform"], + required=False, + default="aci", + type=str, + help="Platform to create container definition for", + ) + + with self.argument_context("confcom radius policy insert") as c: + c.positional( + "policy_file", + nargs='?', + type=argparse.FileType('rb'), + default=sys.stdin.buffer, + help="Policy to add to the template", + ) + c.argument( + "template_path", + options_list=("--template-file", '-f'), + required=False, + help="Path to the template file" + ) + c.argument( + "container_index", + options_list=['--idx'], + required=False, + default=0, + type=int, + help='The index of the container definition in the template to use' + ) diff --git a/src/confcom/azext_confcom/command/containers_from_radius.py b/src/confcom/azext_confcom/command/containers_from_radius.py new file mode 100644 index 00000000000..313abbee161 --- /dev/null +++ b/src/confcom/azext_confcom/command/containers_from_radius.py @@ -0,0 +1,105 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +import os +import tempfile + +from azext_confcom.lib.images import get_image_config, get_image_layers +from azext_confcom.lib.deployments import parse_deployment_template +from azext_confcom.lib.platform import ACI_MOUNTS +import re + + +def containers_from_radius( + az_cli_command, + template: str, + parameters: list, + container_index: int, + platform: str, +) -> None: + + # Remove the radius extension inclusion to avoid parsing errors + # For the purpose of extracting the container info we don't care about it + with tempfile.NamedTemporaryFile('w+', delete=True, suffix=".bicep") as temp_template_file: + with open(template, 'r') as f: + temp_template_file.write(f.read().replace("extension radius", "")) + temp_template_file.flush() + + # Handle parameters file if it's a path + if len(parameters) > 0 and isinstance(parameters[0][0], str) and os.path.isfile(parameters[0][0]): + parameters_path = parameters[0][0] + with open(parameters_path, 'r') as params_file: + params_content = params_file.read() + + # Replace any references to the original template file with the temporary one + params_content = re.sub( + r"using\s+'.*\.bicep'", + f"using '{os.path.basename(temp_template_file.name)}'", + params_content + ) + + with tempfile.NamedTemporaryFile('w+', delete=False, suffix=".bicepparam") as temp_params_file: + temp_params_file.write(params_content) + temp_params_file.flush() + parameters = [[temp_params_file.name]] + + template = parse_deployment_template( + az_cli_command, + temp_template_file.name, + parameters, + ) + + supported_resources = [r for r in template.get("resources", []) if r.get("type") in { + "Applications.Core/containers", + }] + + resource = supported_resources[container_index].get("properties", {}) + container = resource.get("container", {}) + image = container.get("image") + + mounts = { + "aci": ACI_MOUNTS, + }.get(platform, None) + + mounts += [ + { + "destination": mount_info["mountPath"], + "options": ["rbind", "rshared", "ro"], + "source": mount_info["source"], + "type": "bind" + } + for mount_info in container.get("volumes", {}).values() + ] + + image_config = get_image_config(image) + + env_rules = image_config.pop("env_rules", []) + env_rules += [ + { + "pattern": f'{k}={v["value"]}', + "strategy": "string", + "required": False, + } + for k, v in container.get("env", {}).items() + ] + env_rules += [ + { + "name": f"CONNECTIONS_{k.upper()}_.+", + "value": ".+", + "strategy": "re2", + "required": True, + } + for k in resource.get("connections", {}).keys() + ] + + return json.dumps({ + "id": image, + "name": image, + "layers": get_image_layers(image), + **({"mounts": mounts} if mounts else {}), + "env_rules": env_rules, + **image_config, + }) diff --git a/src/confcom/azext_confcom/command/radius_policy_insert.py b/src/confcom/azext_confcom/command/radius_policy_insert.py new file mode 100644 index 00000000000..d4efed30201 --- /dev/null +++ b/src/confcom/azext_confcom/command/radius_policy_insert.py @@ -0,0 +1,63 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import tempfile +from typing import BinaryIO +from azext_confcom.lib.serialization import policy_deserialize, policy_serialize +from azext_confcom.lib.policy import Policy, Container +import re +import base64 + + +def radius_policy_insert( + policy_file: BinaryIO, + template_path: str, + container_index: int, +) -> str: + + policy = None + if policy_file.name == "": + with tempfile.NamedTemporaryFile(delete=True) as temp_policy_file: + temp_policy_file.write(policy_file.read()) + temp_policy_file.flush() + policy = policy_deserialize(temp_policy_file.name) + else: + policy = policy_deserialize(policy_file.name) + + serialized_policy = policy_serialize(policy) + + # Read the template file + with open(template_path, 'r') as template_file: + template_content = template_file.read() + + # Base64 encode the serialized policy + encoded_policy = base64.b64encode(serialized_policy.encode()).decode() + + # Replace the nth ccePolicy value with the encoded policy, preserving original quote types + def replace_cce_policy(match): + full_match = match.group(0) + # Extract the key part (before the colon) + key_part = re.match(r'(["\']?)ccePolicy\1\s*:', full_match).group(0) + # Extract the quote type used for the value + value_quote_match = re.search(r':\s*(["\'])', full_match) + value_quote = value_quote_match.group(1) if value_quote_match else '"' + return f'{key_part} {value_quote}{encoded_policy}{value_quote}' + + # Find all matches and replace only the nth instance + pattern = r'(["\']?)ccePolicy\1\s*:\s*["\'][^"\']*["\']' + matches = list(re.finditer(pattern, template_content)) + + if len(matches) >= container_index: + # Replace only the nth match (convert to 0-based index) + target_match = matches[container_index] + start, end = target_match.span() + replacement = replace_cce_policy(target_match) + updated_content = template_content[:start] + replacement + template_content[end:] + else: + updated_content = template_content + + # Write the updated content back to the template file + with open(template_path, 'w') as template_file: + template_file.write(updated_content) diff --git a/src/confcom/azext_confcom/commands.py b/src/confcom/azext_confcom/commands.py index 1d2bb45f724..3d70a513b4d 100644 --- a/src/confcom/azext_confcom/commands.py +++ b/src/confcom/azext_confcom/commands.py @@ -13,3 +13,9 @@ def load_command_table(self, _): with self.command_group("confcom"): pass + + with self.command_group("confcom containers") as g: + g.custom_command("from_radius", "containers_from_radius") + + with self.command_group("confcom radius policy") as g: + g.custom_command("insert", "radius_policy_insert") diff --git a/src/confcom/azext_confcom/custom.py b/src/confcom/azext_confcom/custom.py index 2f90c796bbd..c51cedcaa2c 100644 --- a/src/confcom/azext_confcom/custom.py +++ b/src/confcom/azext_confcom/custom.py @@ -5,7 +5,7 @@ import os import sys -from typing import Optional +from typing import BinaryIO, Optional from azext_confcom import oras_proxy, os_util, security_policy from azext_confcom._validators import resolve_stdio @@ -18,6 +18,8 @@ from azext_confcom.init_checks import run_initial_docker_checks from azext_confcom.kata_proxy import KataPolicyGenProxy from azext_confcom.security_policy import AciPolicy, OutputType +from azext_confcom.command.radius_policy_insert import radius_policy_insert as _radius_policy_insert +from azext_confcom.command.containers_from_radius import containers_from_radius as _containers_from_radius from azext_confcom.template_util import ( get_image_name, inject_policy_into_template, inject_policy_into_yaml, pretty_print_func, print_existing_policy_from_arm_template, @@ -512,3 +514,31 @@ def get_fragment_output_type(outraw): if outraw: output_type = security_policy.OutputType.RAW return output_type + + +def containers_from_radius( + cmd, + template: str, + parameters: dict, + container_index: int, + platform: str, +) -> None: + print(_containers_from_radius( + az_cli_command=cmd, + template=template, + parameters=parameters, + container_index=container_index, + platform=platform, + )) + + +def radius_policy_insert( + policy_file: BinaryIO, + template_path: str, + container_index: int, +) -> None: + _radius_policy_insert( + policy_file=policy_file, + template_path=template_path, + container_index=container_index, + ) diff --git a/src/confcom/azext_confcom/lib/binaries.py b/src/confcom/azext_confcom/lib/binaries.py new file mode 100644 index 00000000000..3df0e5a231e --- /dev/null +++ b/src/confcom/azext_confcom/lib/binaries.py @@ -0,0 +1,13 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os + + +def get_binaries_dir(): + binaries_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "bin") + if not os.path.exists(binaries_dir): + os.makedirs(binaries_dir) + return binaries_dir diff --git a/src/confcom/azext_confcom/lib/deployments.py b/src/confcom/azext_confcom/lib/deployments.py new file mode 100644 index 00000000000..289ddcde9cc --- /dev/null +++ b/src/confcom/azext_confcom/lib/deployments.py @@ -0,0 +1,93 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +import re + +from azure.cli.command_modules.resource.custom import ( + _prepare_deployment_properties_unmodified, +) +from azure.cli.core.profiles import ResourceType + + +class _ResourceDeploymentCommandAdapter: + """Ensure required resource type defaults are present when reusing resource module helpers.""" + + def __init__(self, cmd): + self._cmd = cmd + self.cli_ctx = cmd.cli_ctx + + def get_models(self, *attr_args, **kwargs): + kwargs.setdefault('resource_type', ResourceType.MGMT_RESOURCE_DEPLOYMENTS) + return self._cmd.get_models(*attr_args, **kwargs) + + def __getattr__(self, name): + return getattr(self._cmd, name) + + +def get_parameters( + arm_template: dict, + arm_template_parameters: dict, +) -> dict: + + return { + parameter_key: ( + arm_template_parameters.get(parameter_key, {}).get("value") + or arm_template.get("parameters", {}).get(parameter_key, {}).get("value") + or arm_template.get("parameters", {}).get(parameter_key, {}).get("defaultValue") + ) + for parameter_key in arm_template.get("parameters", {}).keys() + } + + +def eval_parameters( + arm_template: dict, + arm_template_parameters: dict, +) -> dict: + + parameters = get_parameters(arm_template, arm_template_parameters) + return json.loads(re.compile(r"\[parameters\(\s*'([^']+)'\s*\)\]").sub( + lambda match: json.dumps(parameters.get(match.group(1)) or match.group(0))[1:-1], + json.dumps(arm_template), + )) + + +def eval_variables( + arm_template: dict, + _arm_template_parameters: dict, +) -> dict: + + variables = arm_template.get("variables", {}) + return json.loads(re.compile(r"\[variables\(\s*'([^']+)'\s*\)\]").sub( + lambda match: json.dumps(variables.get(match.group(1), match.group(0)))[1:-1], + json.dumps(arm_template), + )) + + +EVAL_FUNCS = [ + eval_parameters, + eval_variables, +] + + +def parse_deployment_template( + az_cli_command, + template: str, + parameters: dict, +) -> dict: + properties = _prepare_deployment_properties_unmodified( + cmd=_ResourceDeploymentCommandAdapter(az_cli_command), + deployment_scope='resourceGroup', + template_file=template, + parameters=parameters, + no_prompt=True, + ) + template = json.loads(properties.template) + parameters = properties.parameters or {} + + for eval_func in EVAL_FUNCS: + template = eval_func(template, parameters) + + return template diff --git a/src/confcom/azext_confcom/lib/images.py b/src/confcom/azext_confcom/lib/images.py new file mode 100644 index 00000000000..9f3924c53f0 --- /dev/null +++ b/src/confcom/azext_confcom/lib/images.py @@ -0,0 +1,64 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import functools +import os +import subprocess +import docker + + +@functools.lru_cache() +def get_image(image_ref: str) -> docker.models.images.Image: + + client = docker.from_env() + + try: + image = client.images.get(image_ref) + except docker.errors.ImageNotFound: + client.images.pull(image_ref) + + image = client.images.get(image_ref) + return image + + +def get_image_layers(image: str) -> list[str]: + + binary_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "bin", "dmverity-vhd") + + get_image(image) + result = subprocess.run( + [binary_path, "-d", "roothash", "-i", image], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + text=True, + ) + + return [line.split("hash: ")[-1] for line in result.stdout.splitlines()] + + +def get_image_config(image: str) -> dict: + + image_config = get_image(image).attrs.get("Config") + + config = {} + + if image_config.get("Cmd") or image_config.get("Entrypoint"): + config["command"] = ( + image_config.get("Entrypoint") or [] + + image_config.get("Cmd") or [] + ) + + if image_config.get("Env"): + config["env_rules"] = [{ + "pattern": p, + "strategy": "string", + "required": False, + } for p in image_config.get("Env")] + + if image_config.get("WorkingDir"): + config["working_dir"] = image_config.get("WorkingDir") + + return config diff --git a/src/confcom/azext_confcom/lib/opa.py b/src/confcom/azext_confcom/lib/opa.py new file mode 100644 index 00000000000..7c73adf61a1 --- /dev/null +++ b/src/confcom/azext_confcom/lib/opa.py @@ -0,0 +1,52 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import hashlib +import json +import os +from pathlib import Path +import platform +import subprocess +from typing import Iterable + +import requests + +from azext_confcom.lib.binaries import get_binaries_dir + +_opa_pathh = os.path.abspath(os.path.join(get_binaries_dir(), "opa")) +_expected_sha256 = "fe8e191d44fec33db2a3d0ca788b9f83f866d980c5371063620c3c6822792877" + + +def opa_get(): + + opa_fetch_resp = requests.get( + f"https://openpolicyagent.org/downloads/latest/opa_{platform.system().lower()}_amd64") + opa_fetch_resp.raise_for_status() + + assert hashlib.sha256(opa_fetch_resp.content).hexdigest() == _expected_sha256 + + with open(_opa_pathh, "wb") as f: + f.write(opa_fetch_resp.content) + + os.chmod(_opa_pathh, 0o755) + return _opa_pathh + + +def opa_run(args: Iterable[str]) -> subprocess.CompletedProcess: + return subprocess.run( + [_opa_pathh, *args], + check=True, + stdout=subprocess.PIPE, + text=True, + ) + + +def opa_eval(data_path: Path, query: str): + return json.loads(opa_run([ + "eval", + "--format", "json", + "--data", str(data_path), + query, + ]).stdout.strip()) diff --git a/src/confcom/azext_confcom/lib/platform.py b/src/confcom/azext_confcom/lib/platform.py new file mode 100644 index 00000000000..395d39a1309 --- /dev/null +++ b/src/confcom/azext_confcom/lib/platform.py @@ -0,0 +1,17 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +ACI_MOUNTS = [ + { + "destination": "/etc/resolv.conf", + "options": [ + "rbind", + "rshared", + "rw" + ], + "source": "sandbox:///tmp/atlas/resolvconf/.+", + "type": "bind" + } +] diff --git a/src/confcom/azext_confcom/lib/policy.py b/src/confcom/azext_confcom/lib/policy.py index 354d5d687db..f226f216357 100644 --- a/src/confcom/azext_confcom/lib/policy.py +++ b/src/confcom/azext_confcom/lib/policy.py @@ -36,12 +36,23 @@ class ContainerCapabilities: @dataclass -class ContainerRule: +class ContainerRulePattern: pattern: str strategy: str required: Optional[bool] = False +@dataclass +class ContainerRuleNameValue: + name: str + value: str + strategy: str + required: Optional[bool] = False + + +ContainerRule = ContainerRulePattern | ContainerRuleNameValue + + @dataclass class ContainerExecProcesses: command: list[str] @@ -59,9 +70,11 @@ class ContainerMount: @dataclass class ContainerUser: - group_idnames: list[ContainerRule] = field(default_factory=lambda: [ContainerRule(pattern="", strategy="any")]) + group_idnames: list[ContainerRule] = \ + field(default_factory=lambda: [ContainerRulePattern(pattern="", strategy="any")]) umask: str = "0022" - user_idname: ContainerRule = field(default_factory=lambda: ContainerRule(pattern="", strategy="any")) + user_idname: ContainerRule = \ + field(default_factory=lambda: ContainerRulePattern(pattern="", strategy="any")) @dataclass diff --git a/src/confcom/azext_confcom/lib/serialization.py b/src/confcom/azext_confcom/lib/serialization.py new file mode 100644 index 00000000000..0a30ae51c0b --- /dev/null +++ b/src/confcom/azext_confcom/lib/serialization.py @@ -0,0 +1,114 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from dataclasses import asdict +import json +from pathlib import Path +from textwrap import dedent +from typing import Union + +from azext_confcom.lib.opa import opa_eval +from azext_confcom.lib.policy import Container, FragmentReference, Fragment, Policy +import re + + +def rego_dict_factory(fields: list) -> dict: + rego_dict = {} + for key, value in fields: + if isinstance(value, str): + rego_dict[key] = f'"{value}"' + elif isinstance(value, bool): + rego_dict[key] = str(value).lower() + elif key == "fragments" or key == "containers": + rego_dict[key] = json.dumps(value, indent=2) + else: + rego_dict[key] = value + + return rego_dict + + +# This is a single entrypoint for serializing both Policy and Fragment objects +def policy_serialize(policy: Union[Policy, Fragment]): + + if isinstance(policy, Fragment): + return fragment_serialize(policy) + + policy_dict = asdict(policy) + fragments_json = json.dumps(policy_dict.pop("fragments"), indent=2) + containers_json = json.dumps(policy_dict.pop("containers"), indent=2) + + return dedent(f""" +package {policy_dict.pop('package')} + +api_version := "{policy_dict.pop('api_version')}" +framework_version := "{policy_dict.pop('framework_version')}" + +fragments := {fragments_json} + +containers := {containers_json} + +{chr(10).join(f"{key} := {str(value).lower()}" for key, value in policy_dict.items() if key.startswith("allow"))} + +mount_device := data.framework.mount_device +unmount_device := data.framework.unmount_device +mount_overlay := data.framework.mount_overlay +unmount_overlay := data.framework.unmount_overlay +create_container := data.framework.create_container +exec_in_container := data.framework.exec_in_container +exec_external := data.framework.exec_external +shutdown_container := data.framework.shutdown_container +signal_container_process := data.framework.signal_container_process +plan9_mount := data.framework.plan9_mount +plan9_unmount := data.framework.plan9_unmount +get_properties := data.framework.get_properties +dump_stacks := data.framework.dump_stacks +runtime_logging := data.framework.runtime_logging +load_fragment := data.framework.load_fragment +scratch_mount := data.framework.scratch_mount +scratch_unmount := data.framework.scratch_unmount + +reason := {{"errors": data.framework.errors}} +""") + + +def fragment_serialize(fragment: Fragment): + + fragment_dict = asdict(fragment) + fragments_json = json.dumps(fragment_dict.pop("fragments"), indent=2) + containers_json = json.dumps(fragment_dict.pop("containers"), indent=2) + + return dedent(f""" +package {fragment_dict.pop('package')} + +svn := "{fragment_dict.pop('svn')}" +framework_version := "{fragment_dict.pop('framework_version')}" + +fragments := {fragments_json} + +containers := {containers_json} +""") + + +def policy_deserialize(file_path: str): + + with open(file_path, 'r') as f: + content = f.read() + + package_match = re.search(r'package\s+(\S+)', content) + package_name = package_match.group(1) + + PolicyType = Policy if package_name == "policy" else Fragment + + raw_json = opa_eval(Path(file_path), f"data.{package_name}")["result"][0]["expressions"][0]["value"] + + raw_fragments = raw_json.pop("fragments", []) + raw_containers = raw_json.pop("containers", []) + + return PolicyType( + package=package_name, + fragments=[FragmentReference(**fragment) for fragment in raw_fragments], + containers=[Container(**container) for container in raw_containers], + **raw_json + ) diff --git a/src/confcom/setup.py b/src/confcom/setup.py index 359b1f80654..0f2d578bc1b 100644 --- a/src/confcom/setup.py +++ b/src/confcom/setup.py @@ -8,6 +8,7 @@ from codecs import open from setuptools import setup, find_packages +from azext_confcom.lib.opa import opa_get from azext_confcom.rootfs_proxy import SecurityPolicyProxy from azext_confcom.kata_proxy import KataPolicyGenProxy from azext_confcom.cose_proxy import CoseSignToolProxy @@ -47,6 +48,7 @@ SecurityPolicyProxy.download_binaries() KataPolicyGenProxy.download_binaries() CoseSignToolProxy.download_binaries() +opa_get() with open("README.md", "r", encoding="utf-8") as f: README = f.read()