Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/confcom/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Release History
===============

1.2.8
++++++
* bugfix for --exclude-default-fragments flag not working as intended

1.2.7
++++++
* bugfix making it so that oras discover function doesn't error when no fragments are found in the remote repository
Expand Down
25 changes: 19 additions & 6 deletions src/confcom/azext_confcom/security_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ def set_fragment_contents(self, fragment_contents: List[str]) -> None:
self._fragment_contents = fragment_contents

def get_fragments(self) -> List[str]:
return self._fragments or []
return sorted(
self._fragments,
key=lambda x: x.get(config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS_FEED)
) or []

def get_serialized_output(
self,
Expand Down Expand Up @@ -391,6 +394,12 @@ def _policy_serialization(self, pretty_print=False, include_sidecars: bool = Tru
is_sidecars = all(is_sidecar(image.containerImage) for image in regular_container_images)
for image in regular_container_images:
image_dict = image.get_policy_json(omit_id=omit_id)

# Ensure the env variables are sorted for consistent output
image_dict[config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS].sort(
key=lambda env: env["pattern"],
)

policy.append(image_dict)
if (not is_sidecars or len(regular_container_images) == 0) and include_sidecars:
# add in the default containers that have their hashes pre-computed
Expand Down Expand Up @@ -675,13 +684,14 @@ def load_policy_from_arm_template_str(
containers = []
existing_containers = None
fragments = None
exclude_default_fragments = False
exclude_default_fragments_this_group = exclude_default_fragments

tags = case_insensitive_dict_get(resource, config.ACI_FIELD_TEMPLATE_TAGS)
if tags:
exclude_default_fragments = case_insensitive_dict_get(tags, config.ACI_FIELD_TEMPLATE_ZERO_SIDECAR)
if isinstance(exclude_default_fragments, str):
exclude_default_fragments = exclude_default_fragments.lower() == "true"
exclude_default_fragments_this_group = \
case_insensitive_dict_get(tags, config.ACI_FIELD_TEMPLATE_ZERO_SIDECAR)
if isinstance(exclude_default_fragments_this_group, str):
exclude_default_fragments_this_group = exclude_default_fragments_this_group.lower() == "true"

container_group_properties = case_insensitive_dict_get(
resource, config.ACI_FIELD_TEMPLATE_PROPERTIES
Expand Down Expand Up @@ -725,7 +735,10 @@ def load_policy_from_arm_template_str(
# In non-diff mode, we ignore the error and proceed without the policy
existing_containers, fragments = ([], [])

rego_fragments = copy.deepcopy(config.DEFAULT_REGO_FRAGMENTS) if not exclude_default_fragments else []
rego_fragments = (
copy.deepcopy(config.DEFAULT_REGO_FRAGMENTS)
if not exclude_default_fragments_this_group else []
)
if infrastructure_svn:
# assumes the first DEFAULT_REGO_FRAGMENT is always the
# infrastructure fragment
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import contextlib
import io
import os
import pytest
from itertools import product

from azext_confcom.custom import acipolicygen_confcom


TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), ".."))
SAMPLES_ROOT = os.path.abspath(os.path.join(TEST_DIR, "..", "..", "..", "samples", "aci"))
FRAGMENTS_DIR = os.path.abspath(os.path.join(TEST_DIR, "..", "..", "..", "samples", "fragments"))


POLICYGEN_ARGS = {
"policy.rego": {},
"policy_debug.rego": {"debug_mode": True},
"policy_exclude_default_fragment.rego": {"exclude_default_fragments": True},
"policy_infrastructure_svn.rego": {"infrastructure_svn": "99"},
"policy_disable_stdio.rego": {"disable_stdio": True},
"policy_fragment.rego": {
"include_fragments": True,
"fragments_json": os.path.join(FRAGMENTS_DIR, "fragment.json"),
},
}


@pytest.mark.parametrize(
"sample_directory,generated_policy_path",
product(os.listdir(SAMPLES_ROOT), POLICYGEN_ARGS.keys())
)
def test_acipolicygen(sample_directory, generated_policy_path):

for failing_sample_directory, failing_generated_policy_path in [
("multi_container_groups", "policy_fragment.rego"), # TODO: https://github.com/Azure/azure-cli-extensions/issues/9229
]:
if (
failing_sample_directory in (None, sample_directory)
and failing_generated_policy_path in (None, generated_policy_path)
):
pytest.skip("Skipping test due to known issue")

arm_template_path = os.path.join(SAMPLES_ROOT, sample_directory, "arm_template.json")
parameters_path = os.path.join(SAMPLES_ROOT, sample_directory, "parameters.json")
if not os.path.isfile(parameters_path):
parameters_path = None
flags = POLICYGEN_ARGS[generated_policy_path]

with open(os.path.join(SAMPLES_ROOT, sample_directory, generated_policy_path), "r", encoding="utf-8") as f:
expected_policy = f.read()

buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
acipolicygen_confcom(
input_path=None,
arm_template=arm_template_path,
arm_template_parameters=parameters_path,
image_name=None,
virtual_node_yaml_path=None,
infrastructure_svn=flags.pop("infrastructure_svn", None),
tar_mapping_location=None,
outraw=True,
**flags,
)
actual_policy = buffer.getvalue()

assert actual_policy == expected_policy, f"Policy generation mismatch, actual output for {os.path.join(sample_directory, generated_policy_path)}:\n{actual_policy}"
39 changes: 22 additions & 17 deletions src/confcom/azext_confcom/tests/latest/test_confcom_arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1618,8 +1618,8 @@ def test_incorrect_policy_diff(self):
"no_new_privileges": [{"tested_value": True, "policy_value": False}]
},
"env_rules": [
"environment variable with rule 'TEST_REGEXP_ENV=test_regexp_en' does not match strings or regex in policy rules",
"environment variable with rule 'ENV_VALUE=input_value' does not match strings or regex in policy rules",
"environment variable with rule 'TEST_REGEXP_ENV=test_regexp_en' does not match strings or regex in policy rules",
],
}
}
Expand Down Expand Up @@ -3611,18 +3611,17 @@ def test_wildcard_env_var(self):
)
)

self.assertEqual(
normalized_aci_arm_policy[0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS][1][
config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_STRATEGY
],
"re2",
test_env_var = next(
env_var
for env_var in normalized_aci_arm_policy[0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS]
if env_var[config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_RULE] == "TEST_WILDCARD_ENV=.*"
)

self.assertIsNotNone(test_env_var)

self.assertEqual(
normalized_aci_arm_policy[0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS][1][
config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_RULE
],
"TEST_WILDCARD_ENV=.*",
test_env_var[config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_STRATEGY],
"re2",
)

normalized_aci_arm_policy2 = json.loads(
Expand All @@ -3635,13 +3634,14 @@ def test_wildcard_env_var(self):
any([item.get("name") == "WILDCARD2" for item in normalized_aci_arm_policy2[0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS]])
)

self.assertEqual(
normalized_aci_arm_policy2[0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS][1][
config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_RULE
],
"TEST_WILDCARD_ENV=.*",
test_env_var = next(
env_var
for env_var in normalized_aci_arm_policy2[0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS]
if env_var[config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_RULE] == "TEST_WILDCARD_ENV=.*"
)

self.assertIsNotNone(test_env_var)

def test_wildcard_env_var_invalid(self):
with self.assertRaises(SystemExit) as wrapped_exit:
load_policy_from_arm_template_str(self.custom_arm_json_error, "")
Expand Down Expand Up @@ -3800,10 +3800,15 @@ def test_arm_template_with_env_var(self):
)
)

env_var = regular_image_json[0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS][0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_RULE]
env_var = next(
env_var[config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_RULE]
for env_var in regular_image_json[0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS]
if env_var[config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_RULE] == "PORT=parameters('abc')"
)

self.assertIsNotNone(env_var)

# 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/azurelinux/distroless/base:3.0")

def test_arm_template_config_map_sidecar(self):
Expand Down
38 changes: 38 additions & 0 deletions src/confcom/samples/aci/command/arm_template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"name": "group1",
"type": "Microsoft.ContainerInstance/containerGroups",
"apiVersion": "2023-05-01",
"location": "westeurope",
"properties": {
"osType": "Linux",
"restartPolicy": "OnFailure",
"confidentialComputeProperties": {
"ccePolicy": ""
},
"containers": [
{
"name": "container1",
"properties": {
"image": "mcr.microsoft.com/azurelinux/distroless/base@sha256:1e77d97e1e39f22ed9c52f49b3508b4c1044cec23743df9098ac44e025f654f2",
"resources": {
"requests": {
"cpu": "1",
"memoryInGb": "2"
}
},
"command": [
"/bin/sh",
"-c",
"while true; do echo hello; sleep 10; done"
]
}
}
]
}
}
]
}
50 changes: 50 additions & 0 deletions src/confcom/samples/aci/command/policy.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package policy

import future.keywords.every
import future.keywords.in

api_version := "0.10.0"
framework_version := "0.2.3"

fragments := [
{
"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":["/bin/sh","-c","while true; do echo hello; sleep 10; done"],"env_rules":[{"pattern":"(?i)(FABRIC)_.+=.+","required":false,"strategy":"re2"},{"pattern":"FabricPackageFileName=.+","required":false,"strategy":"re2"},{"pattern":"HOSTNAME=.+","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":"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","required":false,"strategy":"string"},{"pattern":"T(E)?MP=.+","required":false,"strategy":"re2"},{"pattern":"TERM=xterm","required":false,"strategy":"string"},{"pattern":"azurecontainerinstance_restarted_by=.+","required":false,"strategy":"re2"}],"exec_processes":[],"id":"mcr.microsoft.com/azurelinux/distroless/base@sha256:1e77d97e1e39f22ed9c52f49b3508b4c1044cec23743df9098ac44e025f654f2","layers":["243e1b3ce08093f2f0d9cd6a9eafde8737f64fec105ed59c346d309fbe760b58"],"mounts":[{"destination":"/etc/resolv.conf","options":["rbind","rshared","rw"],"source":"sandbox:///tmp/atlas/resolvconf/.+","type":"bind"}],"name":"container1","no_new_privileges":false,"seccomp_profile_sha256":"","signals":[],"user":{"group_idnames":[{"pattern":"","strategy":"any"}],"umask":"0022","user_idname":{"pattern":"","strategy":"any"}},"working_dir":"/"},{"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":[],"name":"pause-container","no_new_privileges":false,"seccomp_profile_sha256":"","signals":[],"user":{"group_idnames":[{"pattern":"","strategy":"any"}],"umask":"0022","user_idname":{"pattern":"","strategy":"any"}},"working_dir":"/"}]

allow_properties_access := true
allow_dump_stacks := false
allow_runtime_logging := false
allow_environment_variable_dropping := true
allow_unencrypted_scratch := false
allow_capability_dropping := true

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}


Loading