diff --git a/linter_exclusions.yml b/linter_exclusions.yml index 5105cb7f34b..ce4aaa82bca 100644 --- a/linter_exclusions.yml +++ b/linter_exclusions.yml @@ -3504,15 +3504,3 @@ neon postgres organization: neon postgres project: rule_exclusions: - require_wait_command_if_no_wait - -confcom fragment push: - parameters: - signed_fragment: - rule_exclusions: - - no_positional_parameters - -confcom fragment attach: - parameters: - signed_fragment: - rule_exclusions: - - no_positional_parameters diff --git a/src/confcom/HISTORY.rst b/src/confcom/HISTORY.rst index 70a2ef341f6..8ffb3997568 100644 --- a/src/confcom/HISTORY.rst +++ b/src/confcom/HISTORY.rst @@ -3,12 +3,6 @@ Release History =============== -1.5.0 -++++++ -* restored the behaviour of --upload-fragment in acifragmentgen to attach to first image in input -* added confcom fragment push command to allow explicit uploading of standalone fragments -* added confcom fragment attach command to allow explicit uploading of image attached fragments - 1.4.5 ++++++ * Drop the dependency on OPA diff --git a/src/confcom/azext_confcom/_help.py b/src/confcom/azext_confcom/_help.py index 9817bef723e..15368cc61db 100644 --- a/src/confcom/azext_confcom/_help.py +++ b/src/confcom/azext_confcom/_help.py @@ -278,46 +278,3 @@ - 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 fragment" -] = """ - type: group - short-summary: Commands to handle Confidential Container Policy Fragments. -""" - -helps[ - "confcom fragment push" -] = """ - type: command - short-summary: Push a Confidential Container Policy Fragment to an ORAS registry - - parameters: - - name: --manifest-tag - type: string - short-summary: 'The reference to push the signed fragment to' - - examples: - - name: Push a signed fragment to a registry - text: az confcom fragment push ./fragment.reg.cose --manifest-tag myregistry.azurecr.io/fragment:latest - - name: Push the output of acifragmentgen to a registry - text: az confcom acifragmentgen --chain my.cert.pem --key my_key.pem --svn "1" --namespace contoso --feed "test-feed" --input ./fragment_spec.json | az confcom fragment push --manifest-tag myregistry.azurecr.io/fragment:latest -""" - -helps[ - "confcom fragment attach" -] = """ - type: command - short-summary: Attach a Confidential Container Policy Fragment to an image in an ORAS registry. - - parameters: - - name: --manifest-tag - type: string - short-summary: 'The reference to attach the signed fragment to' - - examples: - - name: Attach a signed fragment to a registry - text: az confcom fragment attach ./fragment.reg.cose --manifest-tag myregistry.azurecr.io/image:latest - - name: Attach the output of acifragmentgen to a registry - text: az confcom acifragmentgen --chain my.cert.pem --key my_key.pem --svn "1" --namespace contoso --feed "test-feed" --input ./fragment_spec.json | az confcom fragment attach --manifest-tag myregistry.azurecr.io/image:latest -""" diff --git a/src/confcom/azext_confcom/_params.py b/src/confcom/azext_confcom/_params.py index d75ce70abc1..ccbea8d0091 100644 --- a/src/confcom/azext_confcom/_params.py +++ b/src/confcom/azext_confcom/_params.py @@ -5,8 +5,6 @@ # pylint: disable=line-too-long import json -import argparse -import sys from knack.arguments import CLIArgumentType from azext_confcom._validators import ( validate_params_file, @@ -46,32 +44,6 @@ def load_arguments(self, _): c.argument("tags", tags_type) c.argument("confcom_name", confcom_name_type, options_list=["--name", "-n"]) - with self.argument_context("confcom fragment attach") as c: - c.positional( - "signed_fragment", - nargs='?', - type=argparse.FileType('rb'), - default=sys.stdin.buffer, - help="Signed fragment to attach", - ) - c.argument( - "manifest_tag", - help="Manifest tag for the fragment", - ) - - with self.argument_context("confcom fragment push") as c: - c.positional( - "signed_fragment", - nargs='?', - type=argparse.FileType('rb'), - default=sys.stdin.buffer, - help="Signed fragment to push", - ) - c.argument( - "manifest_tag", - help="Manifest tag for the fragment", - ) - with self.argument_context("confcom acipolicygen") as c: c.argument( "input_path", @@ -390,13 +362,6 @@ def load_arguments(self, _): type=json.loads, help='Container definitions to include in the policy' ) - c.argument( - "out_signed_fragment", - action="store_true", - default=False, - required=False, - help="Emit only the signed fragment bytes", - ) with self.argument_context("confcom katapolicygen") as c: c.argument( diff --git a/src/confcom/azext_confcom/command/fragment_attach.py b/src/confcom/azext_confcom/command/fragment_attach.py deleted file mode 100644 index 39f29ae48da..00000000000 --- a/src/confcom/azext_confcom/command/fragment_attach.py +++ /dev/null @@ -1,46 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import os -import subprocess -import tempfile -from typing import BinaryIO - - -def oras_attach( - signed_fragment: BinaryIO, - manifest_tag: str, -) -> None: - subprocess.run( - [ - "oras", - "attach", - "--artifact-type", "application/x-ms-ccepolicy-frag", - manifest_tag, - os.path.relpath(signed_fragment.name, start=os.getcwd()), - ], - check=True, - timeout=120, - ) - - -def fragment_attach( - signed_fragment: BinaryIO, - manifest_tag: str, -) -> None: - - if signed_fragment.name == "": - with tempfile.NamedTemporaryFile(delete=True) as temp_signed_fragment: - temp_signed_fragment.write(signed_fragment.read()) - temp_signed_fragment.flush() - oras_attach( - signed_fragment=temp_signed_fragment, - manifest_tag=manifest_tag, - ) - else: - oras_attach( - signed_fragment=signed_fragment, - manifest_tag=manifest_tag, - ) diff --git a/src/confcom/azext_confcom/command/fragment_push.py b/src/confcom/azext_confcom/command/fragment_push.py deleted file mode 100644 index 89912c87637..00000000000 --- a/src/confcom/azext_confcom/command/fragment_push.py +++ /dev/null @@ -1,46 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import os -import subprocess -import tempfile -from typing import BinaryIO - - -def oras_push( - signed_fragment: BinaryIO, - manifest_tag: str, -) -> None: - subprocess.run( - [ - "oras", - "push", - "--artifact-type", "application/x-ms-ccepolicy-frag", - manifest_tag, - os.path.relpath(signed_fragment.name, start=os.getcwd()), - ], - check=True, - timeout=120, - ) - - -def fragment_push( - signed_fragment: BinaryIO, - manifest_tag: str, -) -> None: - - if signed_fragment.name == "": - with tempfile.NamedTemporaryFile(delete=True) as temp_signed_fragment: - temp_signed_fragment.write(signed_fragment.read()) - temp_signed_fragment.flush() - oras_push( - signed_fragment=temp_signed_fragment, - manifest_tag=manifest_tag, - ) - else: - oras_push( - signed_fragment=signed_fragment, - manifest_tag=manifest_tag, - ) diff --git a/src/confcom/azext_confcom/commands.py b/src/confcom/azext_confcom/commands.py index 7e1e93eabca..1d2bb45f724 100644 --- a/src/confcom/azext_confcom/commands.py +++ b/src/confcom/azext_confcom/commands.py @@ -11,9 +11,5 @@ def load_command_table(self, _): g.custom_command("acifragmentgen", "acifragmentgen_confcom") g.custom_command("katapolicygen", "katapolicygen_confcom") - with self.command_group("confcom fragment") as g: - g.custom_command("attach", "fragment_attach", is_preview=True) - g.custom_command("push", "fragment_push", is_preview=True) - with self.command_group("confcom"): pass diff --git a/src/confcom/azext_confcom/custom.py b/src/confcom/azext_confcom/custom.py index 1b243a31370..2f90c796bbd 100644 --- a/src/confcom/azext_confcom/custom.py +++ b/src/confcom/azext_confcom/custom.py @@ -5,8 +5,7 @@ import os import sys -import tempfile -from typing import Optional, BinaryIO +from typing import Optional from azext_confcom import oras_proxy, os_util, security_policy from azext_confcom._validators import resolve_stdio @@ -23,8 +22,6 @@ get_image_name, inject_policy_into_template, inject_policy_into_yaml, pretty_print_func, print_existing_policy_from_arm_template, print_existing_policy_from_yaml, print_func, str_to_sha256) -from azext_confcom.command.fragment_attach import fragment_attach as _fragment_attach -from azext_confcom.command.fragment_push import fragment_push as _fragment_push from knack.log import get_logger from pkg_resources import parse_version @@ -258,7 +255,6 @@ def acifragmentgen_confcom( upload_fragment: bool = False, no_print: bool = False, fragments_json: str = "", - out_signed_fragment: bool = False, ): if container_definitions is None: container_definitions = [] @@ -365,16 +361,12 @@ def acifragmentgen_confcom( fragment_text = policy.generate_fragment(namespace, svn, output_type, omit_id=omit_id) - if output_type != security_policy.OutputType.DEFAULT and not no_print and not out_signed_fragment: + if output_type != security_policy.OutputType.DEFAULT and not no_print: print(fragment_text) # take ".rego" off the end of the filename if it's there, it'll get added back later output_filename = output_filename.replace(".rego", "") filename = f"{output_filename or namespace}.rego" - - if out_signed_fragment: - filename = os.path.join(tempfile.gettempdir(), filename) - os_util.write_str_to_file(filename, fragment_text) if key: @@ -382,23 +374,11 @@ def acifragmentgen_confcom( iss = cose_proxy.create_issuer(chain) out_path = filename + ".cose" - if out_signed_fragment: - out_path = os.path.join(tempfile.gettempdir(), os.path.basename(out_path)) - cose_proxy.cose_sign(filename, key, chain, feed, iss, algo, out_path) - - # Preserve default behaviour established since version 1.1.0 of attaching - # the fragment to the first image specified in input - # (or --image-target if specified) - if upload_fragment: - oras_proxy.attach_fragment_to_image( - image_name=image_target or policy_images[0].containerImage, - filename=out_path, - ) - - if out_signed_fragment: - with open(out_path, "rb") as f: - sys.stdout.buffer.write(f.read()) + if upload_fragment and image_target: + oras_proxy.attach_fragment_to_image(image_target, out_path) + elif upload_fragment: + oras_proxy.push_fragment_to_registry(feed, out_path) def katapolicygen_confcom( @@ -532,23 +512,3 @@ def get_fragment_output_type(outraw): if outraw: output_type = security_policy.OutputType.RAW return output_type - - -def fragment_attach( - signed_fragment: BinaryIO, - manifest_tag: str, -) -> None: - _fragment_attach( - signed_fragment=signed_fragment, - manifest_tag=manifest_tag - ) - - -def fragment_push( - signed_fragment: BinaryIO, - manifest_tag: str, -) -> None: - _fragment_push( - signed_fragment=signed_fragment, - manifest_tag=manifest_tag - ) diff --git a/src/confcom/azext_confcom/data/genpolicy-settings.json b/src/confcom/azext_confcom/data/genpolicy-settings.json deleted file mode 100644 index 73d9c1125bb..00000000000 --- a/src/confcom/azext_confcom/data/genpolicy-settings.json +++ /dev/null @@ -1,338 +0,0 @@ -{ - "pause_container": { - "Root": { - "Path": "$(cpath)/$(bundle-id)", - "Readonly": true - }, - "Mounts": [ - { - "destination": "/dev/shm", - "type_": "bind", - "source": "/run/kata-containers/sandbox/shm", - "options": [ - "rbind" - ] - }, - { - "destination": "/etc/resolv.conf", - "type_": "bind", - "options": [ - "rbind", - "ro", - "nosuid", - "nodev", - "noexec" - ] - } - ], - "Annotations": { - "io.kubernetes.cri.container-type": "sandbox", - "io.kubernetes.cri.sandbox-id": "^[a-z0-9]{64}$", - "io.kubernetes.cri.sandbox-log-directory": "^/var/log/pods/$(sandbox-namespace)_$(sandbox-name)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "io.katacontainers.pkg.oci.container_type": "pod_sandbox", - "io.kubernetes.cri.sandbox-namespace": "default", - "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/$(bundle-id)" - }, - "Process": { - "Args": [ - "/pause" - ] - }, - "Linux": { - "MaskedPaths": [ - "/proc/acpi", - "/proc/asound", - "/proc/kcore", - "/proc/keys", - "/proc/latency_stats", - "/proc/timer_list", - "/proc/timer_stats", - "/proc/sched_debug", - "/sys/firmware", - "/proc/scsi" - ], - "ReadonlyPaths": [ - "/proc/bus", - "/proc/fs", - "/proc/irq", - "/proc/sys", - "/proc/sysrq-trigger" - ] - } - }, - "other_container": { - "Root": { - "Path": "$(cpath)/$(bundle-id)" - }, - "Mounts": [ - { - "destination": "/etc/hosts", - "type_": "bind", - "options": [ - "rbind", - "rprivate", - "rw" - ] - }, - { - "destination": "/dev/termination-log", - "type_": "bind", - "options": [ - "rbind", - "rprivate", - "rw" - ] - }, - { - "destination": "/etc/hostname", - "type_": "bind", - "options": [ - "rbind", - "rprivate" - ] - }, - { - "destination": "/etc/resolv.conf", - "type_": "bind", - "options": [ - "rbind", - "rprivate" - ] - }, - { - "destination": "/dev/shm", - "type_": "bind", - "source": "/run/kata-containers/sandbox/shm", - "options": [ - "rbind" - ] - }, - { - "destination": "/var/run/secrets/kubernetes.io/serviceaccount", - "type_": "bind", - "options": [ - "rbind", - "rprivate", - "ro" - ] - }, - { - "destination": "/var/run/secrets/azure/tokens", - "source": "$(sfprefix)tokens$", - "type_": "bind", - "options": [ - "rbind", - "rprivate", - "ro" - ] - } - ], - "Annotations": { - "io.katacontainers.pkg.oci.bundle_path": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/$(bundle-id)", - "io.kubernetes.cri.sandbox-id": "^[a-z0-9]{64}$", - "io.katacontainers.pkg.oci.container_type": "pod_container", - "io.kubernetes.cri.container-type": "container" - } - }, - "volumes": { - "emptyDir": { - "mount_type": "local", - "mount_source": "^$(cpath)/$(sandbox-id)/local/", - "mount_point": "^$(cpath)/$(sandbox-id)/local/", - "driver": "local", - "source": "local", - "fstype": "local", - "options": [ - "mode=0777" - ] - }, - "emptyDir_memory": { - "mount_type": "bind", - "mount_source": "^/run/kata-containers/sandbox/ephemeral/", - "mount_point": "^/run/kata-containers/sandbox/ephemeral/", - "driver": "ephemeral", - "source": "tmpfs", - "fstype": "tmpfs", - "options": [] - }, - "configMap": { - "mount_type": "bind", - "mount_source": "$(sfprefix)", - "mount_point": "^$(cpath)/watchable/$(bundle-id)-[a-z0-9]{16}-", - "driver": "watchable-bind", - "fstype": "bind", - "options": [ - "rbind", - "rprivate", - "ro" - ] - }, - "confidential_configMap": { - "mount_type": "bind", - "mount_source": "$(sfprefix)", - "mount_point": "$(sfprefix)", - "driver": "local", - "fstype": "bind", - "options": [ - "rbind", - "rprivate", - "ro" - ] - } - }, - "mount_destinations": [ - "/sys/fs/cgroup", - "/etc/hosts", - "/dev/termination-log", - "/etc/hostname", - "/etc/resolv.conf", - "/dev/shm", - "/var/run/secrets/kubernetes.io/serviceaccount", - "/var/run/secrets/azure/tokens" - ], - "sandbox": { - "storages": [ - { - "driver": "ephemeral", - "driver_options": [], - "source": "shm", - "fstype": "tmpfs", - "options": [ - "noexec", - "nosuid", - "nodev", - "mode=1777", - "size=67108864" - ], - "mount_point": "/run/kata-containers/sandbox/shm", - "fs_group": null - } - ] - }, - "common": { - "cpath": "/run/kata-containers/shared/containers", - "sfprefix": "^$(cpath)/$(bundle-id)-[a-z0-9]{16}-", - "spath": "/run/kata-containers/sandbox/storage", - "ip_p": "[0-9]{1,5}", - "ipv4_a": "((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}", - "svc_name": "[A-Z0-9_\\.\\-]+", - "dns_label": "[a-zA-Z0-9_\\.\\-]+", - "s_source1": "^..2[0-9]{3}_[0-1][0-9]_[0-3][0-9]_[0-2][0-9]_[0-5][0-9]_[0-5][0-9]\\.[0-9]{1,10}$", - "s_source2": "^..data/", - "default_caps": [ - "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" - ], - "privileged_caps": [ - "CAP_CHOWN", - "CAP_DAC_OVERRIDE", - "CAP_DAC_READ_SEARCH", - "CAP_FOWNER", - "CAP_FSETID", - "CAP_KILL", - "CAP_SETGID", - "CAP_SETUID", - "CAP_SETPCAP", - "CAP_LINUX_IMMUTABLE", - "CAP_NET_BIND_SERVICE", - "CAP_NET_BROADCAST", - "CAP_NET_ADMIN", - "CAP_NET_RAW", - "CAP_IPC_LOCK", - "CAP_IPC_OWNER", - "CAP_SYS_MODULE", - "CAP_SYS_RAWIO", - "CAP_SYS_CHROOT", - "CAP_SYS_PTRACE", - "CAP_SYS_PACCT", - "CAP_SYS_ADMIN", - "CAP_SYS_BOOT", - "CAP_SYS_NICE", - "CAP_SYS_RESOURCE", - "CAP_SYS_TIME", - "CAP_SYS_TTY_CONFIG", - "CAP_MKNOD", - "CAP_LEASE", - "CAP_AUDIT_WRITE", - "CAP_AUDIT_CONTROL", - "CAP_SETFCAP", - "CAP_MAC_OVERRIDE", - "CAP_MAC_ADMIN", - "CAP_SYSLOG", - "CAP_WAKE_ALARM", - "CAP_BLOCK_SUSPEND", - "CAP_AUDIT_READ", - "CAP_PERFMON", - "CAP_BPF", - "CAP_CHECKPOINT_RESTORE" - ], - "virtio_blk_storage_classes": [ - "cc-local-csi", - "cc-managed-csi", - "cc-managed-premium-csi" - ], - "smb_storage_classes": [ - { - "name": "azurefile-csi-kata-cc", - "mount_options": [ - "dir_mode=0777", - "file_mode=0777", - "mfsymlinks", - "cache=strict", - "nosharesock", - "actimeo=30", - "nobrl" - ] - } - ] - }, - "kata_config": { - "confidential_guest": true - }, - "cluster_config": { - "default_namespace": "default" - }, - "request_defaults": { - "CreateContainerRequest": { - "allow_env_regex": [ - "^HOSTNAME=$(dns_label)$", - "^$(svc_name)_PORT_$(ip_p)_TCP=tcp://$(ipv4_a):$(ip_p)$", - "^$(svc_name)_PORT_$(ip_p)_TCP_PROTO=tcp$", - "^$(svc_name)_PORT_$(ip_p)_TCP_PORT=$(ip_p)$", - "^$(svc_name)_PORT_$(ip_p)_TCP_ADDR=$(ipv4_a)$", - "^$(svc_name)_SERVICE_HOST=$(ipv4_a)$", - "^$(svc_name)_SERVICE_PORT=$(ip_p)$", - "^$(svc_name)_SERVICE_PORT_$(dns_label)=$(ip_p)$", - "^$(svc_name)_PORT=tcp://$(ipv4_a):$(ip_p)$", - "^AZURE_CLIENT_ID=[A-Fa-f0-9-]*$", - "^AZURE_TENANT_ID=[A-Fa-f0-9-]*$", - "^AZURE_FEDERATED_TOKEN_FILE=/var/run/secrets/azure/tokens/azure-identity-token$", - "^AZURE_AUTHORITY_HOST=https://login\\.microsoftonline\\.com/$", - "^TERM=xterm$" - ] - }, - "CopyFileRequest": [ - "$(sfprefix)" - ], - "ExecProcessRequest": { - "commands": [], - "regex": [] - }, - "CloseStdinRequest": false, - "ReadStreamRequest": true, - "UpdateEphemeralMountsRequest": false, - "WriteStreamRequest": false - } -} diff --git a/src/confcom/azext_confcom/data/rules.rego b/src/confcom/azext_confcom/data/rules.rego index 4e4c3b3e03d..a5208cf9d3b 100644 --- a/src/confcom/azext_confcom/data/rules.rego +++ b/src/confcom/azext_confcom/data/rules.rego @@ -54,7 +54,6 @@ default AllowRequestsFailingPolicy := false # Constants S_NAME_KEY = "io.kubernetes.cri.sandbox-name" S_NAMESPACE_KEY = "io.kubernetes.cri.sandbox-namespace" -BUNDLE_ID = "[a-z0-9]{64}" CreateContainerRequest { # Check if the input request should be rejected even before checking the @@ -469,9 +468,6 @@ allow_by_bundle_or_sandbox_id(p_oci, i_oci, p_storages, i_storages) { bundle_path := i_oci.Annotations["io.katacontainers.pkg.oci.bundle_path"] bundle_id := replace(bundle_path, "/run/containerd/io.containerd.runtime.v2.task/k8s.io/", "") - bundle_id_format := concat("", ["^", BUNDLE_ID, "$"]) - regex.match(bundle_id_format, bundle_id) - key := "io.kubernetes.cri.sandbox-id" p_regex := p_oci.Annotations[key] @@ -1230,7 +1226,7 @@ CopyFileRequest { some regex1 in policy_data.request_defaults.CopyFileRequest regex2 := replace(regex1, "$(sfprefix)", policy_data.common.sfprefix) regex3 := replace(regex2, "$(cpath)", policy_data.common.cpath) - regex4 := replace(regex3, "$(bundle-id)", BUNDLE_ID) + regex4 := replace(regex3, "$(bundle-id)", "[a-z0-9]{64}") print("CopyFileRequest: regex4 =", regex4) regex.match(regex4, input.path) diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_acifragmentgen.py b/src/confcom/azext_confcom/tests/latest/test_confcom_acifragmentgen.py deleted file mode 100644 index e46b8e6385a..00000000000 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_acifragmentgen.py +++ /dev/null @@ -1,252 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# 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 json -import os -import subprocess -import tempfile -import pytest - -from azext_confcom.custom import acifragmentgen_confcom, fragment_push, fragment_attach - -TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), "..")) -SAMPLES_DIR = os.path.abspath(os.path.join(TEST_DIR, "..", "..", "..", "samples")) - - -@pytest.fixture() -def docker_image(): - - registry_id = subprocess.run( - ["docker", "run", "-d", "-p", "0:5000", "registry:2"], - stdout=subprocess.PIPE, - text=True, - ).stdout - - registry_port = subprocess.run( - ["docker", "port", registry_id], - stdout=subprocess.PIPE, - text=True, - ).stdout.split(":")[-1].strip() - - test_container_ref = f"localhost:{registry_port}/hello-world:latest" - subprocess.run(["docker", "pull", "hello-world"]) - subprocess.run(["docker", "tag", "hello-world", test_container_ref]) - subprocess.run(["docker", "push", test_container_ref]) - - with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8", delete=True) as temp_file: - json.dump({ - "version": "1.0.0", - "containers": [ - { - "name": "hello-world", - "properties": { - "image": test_container_ref, - }, - } - ] - }, temp_file) - temp_file.flush() - - yield test_container_ref, temp_file.name - - subprocess.run(["docker", "stop", registry_id]) - - -@pytest.fixture(scope="session") -def cert_chain(): - with tempfile.TemporaryDirectory() as temp_dir: - subprocess.run( - [ - os.path.join(SAMPLES_DIR, "certs", "create_certchain.sh"), - temp_dir - ], - check=True, - ) - yield temp_dir - - -def test_acifragmentgen_fragment_gen(docker_image): - - image_ref, spec_file_path = docker_image - - with tempfile.TemporaryDirectory() as temp_dir: # Prevent test writing files to repo - acifragmentgen_confcom( - image_name=None, - tar_mapping_location=None, - key=None, - chain=None, - minimum_svn=None, - input_path=spec_file_path, - svn="1", - namespace="contoso", - feed="test-feed", - outraw=True, - output_filename=os.path.join(temp_dir, "fragment.rego"), - out_signed_fragment=False, - ) - - # TODO: Implement a proper validation for the fragment, this is hard - # because each test run will have a unique image to have unique local - # registries on different ports - - -def test_acifragmentgen_fragment_sign(docker_image, cert_chain): - - image_ref, spec_file_path = docker_image - - with tempfile.TemporaryDirectory() as temp_dir: # Prevent test writing files to repo - acifragmentgen_confcom( - image_name=None, - tar_mapping_location=None, - key=os.path.join(cert_chain, "intermediateCA", "private", "ec_p384_private.pem"), - chain=os.path.join(cert_chain, "intermediateCA", "certs", "www.contoso.com.chain.cert.pem"), - minimum_svn=None, - input_path=spec_file_path, - svn="1", - namespace="contoso", - feed="test-feed", - outraw=True, - output_filename=os.path.join(temp_dir, "fragment.rego"), - out_signed_fragment=False, - ) - - # TODO: Implement a proper validation for the cose document - - -def test_acifragmentgen_fragment_upload_fragment(docker_image, cert_chain): - - image_ref, spec_file_path = docker_image - - with tempfile.TemporaryDirectory() as temp_dir: # Prevent test writing files to repo - acifragmentgen_confcom( - image_name=None, - tar_mapping_location=None, - key=os.path.join(cert_chain, "intermediateCA", "private", "ec_p384_private.pem"), - chain=os.path.join(cert_chain, "intermediateCA", "certs", "www.contoso.com.chain.cert.pem"), - minimum_svn=None, - input_path=spec_file_path, - svn="1", - namespace="contoso", - feed="test-feed", - outraw=True, - upload_fragment=True, - output_filename=os.path.relpath(os.path.join(temp_dir, "fragment.rego"), os.getcwd()), # Must be relative for oras - out_signed_fragment=False, - ) - - # Confirm the fragment exists and is attached in the registry - oras_result = json.loads(subprocess.run( - ["oras", "discover", image_ref, "--format", "json"], - stdout=subprocess.PIPE, - check=True, - ).stdout) - - if "referrers" in oras_result: - fragment_ref = oras_result["referrers"][0]["reference"] - elif oras_result.get("manifests")[0].get("artifactType") == "application/x-ms-ccepolicy-frag": - fragment_ref = oras_result["manifests"][0]["reference"] - else: - raise AssertionError(f"{oras_result=}") - - fragment_path = json.loads(subprocess.run( - ["oras", "pull", fragment_ref, "--format", "json", "-o", tempfile.gettempdir()], - check=True, - stdout=subprocess.PIPE, - ).stdout)["files"][0]["path"] - - - with open(fragment_path, "rb") as actual_fragment_file: - with open(os.path.join(temp_dir, "fragment.rego.cose"), "rb") as expected_fragment_file: - assert actual_fragment_file.read() == expected_fragment_file.read() - - -def test_acifragmentgen_fragment_push(docker_image, cert_chain, capsysbinary): - - image_ref, spec_file_path = docker_image - fragment_ref = image_ref.replace("hello-world", "fragment") - - acifragmentgen_confcom( - image_name=None, - tar_mapping_location=None, - key=os.path.join(cert_chain, "intermediateCA", "private", "ec_p384_private.pem"), - chain=os.path.join(cert_chain, "intermediateCA", "certs", "www.contoso.com.chain.cert.pem"), - minimum_svn=None, - input_path=spec_file_path, - svn="1", - namespace="contoso", - feed="test-feed", - out_signed_fragment=True, - ) - - signed_fragment = capsysbinary.readouterr()[0] - signed_fragment_io = io.BytesIO(signed_fragment) - signed_fragment_io.name = "" - - fragment_push( - signed_fragment=signed_fragment_io, - manifest_tag=fragment_ref, - ) - - # Confirm the fragment exists in the registry - fragment_path = json.loads(subprocess.run( - ["oras", "pull", fragment_ref, "--format", "json", "-o", tempfile.gettempdir()], - check=True, - stdout=subprocess.PIPE, - ).stdout)["files"][0]["path"] - - with open(fragment_path, "rb") as f: - assert f.read() == signed_fragment - - -def test_acifragmentgen_fragment_attach(docker_image, cert_chain, capsysbinary): - - image_ref, spec_file_path = docker_image - - acifragmentgen_confcom( - image_name=None, - tar_mapping_location=None, - key=os.path.join(cert_chain, "intermediateCA", "private", "ec_p384_private.pem"), - chain=os.path.join(cert_chain, "intermediateCA", "certs", "www.contoso.com.chain.cert.pem"), - minimum_svn=None, - input_path=spec_file_path, - svn="1", - namespace="contoso", - feed="test-feed", - out_signed_fragment=True, - ) - - signed_fragment = capsysbinary.readouterr()[0] - signed_fragment_io = io.BytesIO(signed_fragment) - signed_fragment_io.name = "" - - fragment_attach( - signed_fragment=signed_fragment_io, - manifest_tag=image_ref, - ) - - # Confirm the fragment exists and is attached in the registry - oras_result = json.loads(subprocess.run( - ["oras", "discover", image_ref, "--format", "json"], - stdout=subprocess.PIPE, - check=True, - ).stdout) - - if "referrers" in oras_result: - fragment_ref = oras_result["referrers"][0]["reference"] - elif oras_result["manifests"][0].get("artifactType") == "application/x-ms-ccepolicy-frag": - fragment_ref = oras_result["manifests"][0]["reference"] - else: - raise AssertionError(f"{oras_result=}") - - fragment_path = json.loads(subprocess.run( - ["oras", "pull", fragment_ref, "--format", "json", "-o", tempfile.gettempdir()], - check=True, - stdout=subprocess.PIPE, - ).stdout)["files"][0]["path"] - - with open(fragment_path, "rb") as f: - assert f.read() == signed_fragment 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 9b764b58241..cd5f0be05f6 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_arm.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_arm.py @@ -3,14 +3,10 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -import fcntl import os -import tempfile import unittest import json import deepdiff -import docker -import requests from unittest.mock import patch from azext_confcom.security_policy import ( @@ -28,7 +24,6 @@ ) TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), "..")) -PRUNE_LOCK_PATH = f"{tempfile.gettempdir()}/confcom_docker_prune.lock" class PolicyGeneratingArm(unittest.TestCase): @@ -5012,18 +5007,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - # Coordinate cleanup across xdist workers to avoid prune conflicts. - with open(PRUNE_LOCK_PATH, "w") as lock_file: - fcntl.flock(lock_file, fcntl.LOCK_EX) - try: - cls.client.containers.prune() - except (docker.errors.APIError, requests.exceptions.ReadTimeout) as exc: - # Ignore conflicts (another prune in flight) or slow daemon timeouts. - status = getattr(getattr(exc, "response", None), "status_code", None) - if status not in (409, None) or not isinstance(exc, requests.exceptions.ReadTimeout): - raise - finally: - fcntl.flock(lock_file, fcntl.LOCK_UN) + cls.client.containers.prune() cls.client.close() def test_arm_template_security_context_no_run_as_group(self): 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 66102f151da..2725ede31c0 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_fragment.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_fragment.py @@ -5,7 +5,6 @@ import json import os -from pathlib import Path import subprocess import tempfile import time @@ -494,7 +493,7 @@ def test_tar_file_fragment(self): try: with tempfile.TemporaryDirectory() as folder: filename = os.path.join(folder, "oci.tar") - filename2 = os.path.join(folder, "oci2.tar") + filename2 = os.path.join(self.path, "oci2.tar") tar_mapping_file = {"mcr.microsoft.com/aks/e2e/library-busybox:master.220314.1-linux-amd64": filename2} create_tar_file(filename) @@ -763,16 +762,14 @@ class FragmentPolicySigning(unittest.TestCase): """ @classmethod def setUpClass(cls): - cls.key_dir_parent = Path(tempfile.gettempdir(), "certchain") - cls.key_dir_parent.mkdir(parents=True, exist_ok=True) + cls.key_dir_parent = os.path.join(SAMPLES_DIR, 'certs') cls.key = os.path.join(cls.key_dir_parent, 'intermediateCA', 'private', 'ec_p384_private.pem') cls.chain = os.path.join(cls.key_dir_parent, 'intermediateCA', 'certs', 'www.contoso.com.chain.cert.pem') if not os.path.exists(cls.key) or not os.path.exists(cls.chain): - script_path = os.path.join(SAMPLES_DIR, "certs", 'create_certchain.sh') + script_path = os.path.join(cls.key_dir_parent, 'create_certchain.sh') arg_list = [ script_path, - cls.key_dir_parent.as_posix(), ] os.chmod(script_path, 0o755) @@ -780,7 +777,8 @@ def setUpClass(cls): item = subprocess.run( arg_list, check=False, - shell=False, + shell=True, + cwd=cls.key_dir_parent, env=os.environ.copy(), ) 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 1da2de3e90a..ab2733745f5 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_tar.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_tar.py @@ -175,7 +175,7 @@ def test_oci_tar_file(self): try: with tempfile.TemporaryDirectory() as folder: filename = os.path.join(folder, "oci.tar") - filename2 = os.path.join(folder, "oci2.tar") + filename2 = os.path.join(self.path, "oci2.tar") tar_mapping_file = {"mcr.microsoft.com/aks/e2e/library-busybox:master.220314.1-linux-amd64": filename2} create_tar_file(filename) 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 2c6a3ad8766..c6e8ad4a23a 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 @@ -4,8 +4,6 @@ # -------------------------------------------------------------------------------------------- import os -from pathlib import Path -import tempfile import unittest import json import subprocess @@ -24,7 +22,6 @@ ) TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), "..")) -SAMPLES_DIR = os.path.abspath(os.path.join(TEST_DIR, "..", "..", "..", "samples")) class PolicyGeneratingVirtualNode(unittest.TestCase): @@ -341,19 +338,17 @@ class PolicyGeneratingVirtualNode(unittest.TestCase): ports: - containerPort: 80 name: web - """ +""" @classmethod def setUpClass(cls): - cls.key_dir_parent = Path(tempfile.gettempdir(), "certchain") - cls.key_dir_parent.mkdir(parents=True, exist_ok=True) + cls.key_dir_parent = os.path.join(TEST_DIR, '..', '..', '..', 'samples', 'certs') cls.key = os.path.join(cls.key_dir_parent, 'intermediateCA', 'private', 'ec_p384_private.pem') cls.chain = os.path.join(cls.key_dir_parent, 'intermediateCA', 'certs', 'www.contoso.com.chain.cert.pem') if not os.path.exists(cls.key) or not os.path.exists(cls.chain): - script_path = os.path.join(SAMPLES_DIR, "certs", 'create_certchain.sh') + script_path = os.path.join(cls.key_dir_parent, 'create_certchain.sh') arg_list = [ script_path, - cls.key_dir_parent.as_posix(), ] os.chmod(script_path, 0o755) @@ -361,7 +356,8 @@ def setUpClass(cls): item = subprocess.run( arg_list, check=False, - shell=False, + shell=True, + cwd=cls.key_dir_parent, env=os.environ.copy(), ) @@ -538,4 +534,4 @@ def test_custom_args(self): containers = json.loads(extract_containers_from_text(virtual_node_policy.get_serialized_output(OutputType.PRETTY_PRINT), container_start)) command = containers[0].get("command") - self.assertEqual(command[-2:], ["test", "values"]) + self.assertEqual(command[-2:], ["test", "values"]) \ No newline at end of file diff --git a/src/confcom/samples/certs/create_certchain.sh b/src/confcom/samples/certs/create_certchain.sh index 48575efef3f..5e94f4c6f4e 100755 --- a/src/confcom/samples/certs/create_certchain.sh +++ b/src/confcom/samples/certs/create_certchain.sh @@ -3,91 +3,87 @@ OriginalPath=`pwd` RootPath=`realpath $(dirname $0)` -OutPath=${1:-$RootPath} - -mkdir -p $OutPath - -cd $OutPath +cd $RootPath # create dirs for root CA -mkdir -p $OutPath/rootCA/{certs,crl,newcerts,private,csr} -mkdir -p $OutPath/intermediateCA/{certs,crl,newcerts,private,csr} +mkdir -p $RootPath/rootCA/{certs,crl,newcerts,private,csr} +mkdir -p $RootPath/intermediateCA/{certs,crl,newcerts,private,csr} # create index files -echo 1000 > $OutPath/rootCA/serial -echo 1000 > $OutPath/intermediateCA/serial +echo 1000 > $RootPath/rootCA/serial +echo 1000 > $RootPath/intermediateCA/serial # create crlnumbers -echo 0100 > $OutPath/rootCA/crlnumber -echo 0100 > $OutPath/intermediateCA/crlnumber +echo 0100 > $RootPath/rootCA/crlnumber +echo 0100 > $RootPath/intermediateCA/crlnumber # create index files -touch $OutPath/rootCA/index.txt -touch $OutPath/intermediateCA/index.txt +touch $RootPath/rootCA/index.txt +touch $RootPath/intermediateCA/index.txt # NOTE: needed for testing -echo "unique_subject = no" >> $OutPath/rootCA/index.txt.attr -echo "unique_subject = no" >> $OutPath/intermediateCA/index.txt.attr +echo "unique_subject = no" >> $RootPath/rootCA/index.txt.attr +echo "unique_subject = no" >> $RootPath/intermediateCA/index.txt.attr # generate root key -openssl genrsa -out $OutPath/rootCA/private/ca.key.pem 4096 -chmod 400 $OutPath/rootCA/private/ca.key.pem +openssl genrsa -out $RootPath/rootCA/private/ca.key.pem 4096 +chmod 400 $RootPath/rootCA/private/ca.key.pem # view the key -# openssl rsa -noout -text -in $OutPath/rootCA/private/ca.key.pem +# openssl rsa -noout -text -in $RootPath/rootCA/private/ca.key.pem # generate root cert -openssl req -config $RootPath/openssl_root.cnf -key $OutPath/rootCA/private/ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out $OutPath/rootCA/certs/ca.cert.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Microsoft/OU=ACCCT/CN=Root CA" +openssl req -config openssl_root.cnf -key $RootPath/rootCA/private/ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out $RootPath/rootCA/certs/ca.cert.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Microsoft/OU=ACCCT/CN=Root CA" # change permissions on root key so it's not globally readable -chmod 644 $OutPath/rootCA/certs/ca.cert.pem +chmod 644 $RootPath/rootCA/certs/ca.cert.pem # verify root cert -openssl x509 -noout -text -in $OutPath/rootCA/certs/ca.cert.pem +openssl x509 -noout -text -in $RootPath/rootCA/certs/ca.cert.pem # generate intermediate key -openssl genrsa -out $OutPath/intermediateCA/private/intermediate.key.pem 4096 -chmod 600 $OutPath/intermediateCA/private/intermediate.key.pem +openssl genrsa -out $RootPath/intermediateCA/private/intermediate.key.pem 4096 +chmod 600 $RootPath/intermediateCA/private/intermediate.key.pem # make CSR for intermediate -openssl req -config $RootPath/openssl_intermediate.cnf -key $OutPath/intermediateCA/private/intermediate.key.pem -new -sha256 -out $OutPath/intermediateCA/certs/intermediate.csr.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Microsoft/OU=ACCCT/CN=Intermediate CA" +openssl req -config openssl_intermediate.cnf -key $RootPath/intermediateCA/private/intermediate.key.pem -new -sha256 -out $RootPath/intermediateCA/certs/intermediate.csr.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Microsoft/OU=ACCCT/CN=Intermediate CA" # sign intermediate cert with root -openssl ca -config $RootPath/openssl_root.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in $OutPath/intermediateCA/certs/intermediate.csr.pem -out $OutPath/intermediateCA/certs/intermediate.cert.pem -batch +openssl ca -config openssl_root.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in $RootPath/intermediateCA/certs/intermediate.csr.pem -out $RootPath/intermediateCA/certs/intermediate.cert.pem -batch # make it readable by everyone -chmod 644 $OutPath/intermediateCA/certs/intermediate.cert.pem +chmod 644 $RootPath/intermediateCA/certs/intermediate.cert.pem # print the cert -# openssl x509 -noout -text -in $OutPath/intermediateCA/certs/intermediate.cert.pem +# openssl x509 -noout -text -in $RootPath/intermediateCA/certs/intermediate.cert.pem # verify intermediate cert -openssl verify -CAfile $OutPath/rootCA/certs/ca.cert.pem $OutPath/intermediateCA/certs/intermediate.cert.pem +openssl verify -CAfile $RootPath/rootCA/certs/ca.cert.pem $RootPath/intermediateCA/certs/intermediate.cert.pem # create chain file -cat $OutPath/intermediateCA/certs/intermediate.cert.pem $OutPath/rootCA/certs/ca.cert.pem > $OutPath/intermediateCA/certs/ca-chain.cert.pem +cat $RootPath/intermediateCA/certs/intermediate.cert.pem $RootPath/rootCA/certs/ca.cert.pem > $RootPath/intermediateCA/certs/ca-chain.cert.pem # verify chain -openssl verify -CAfile $OutPath/intermediateCA/certs/ca-chain.cert.pem $OutPath/intermediateCA/certs/intermediate.cert.pem +openssl verify -CAfile $RootPath/intermediateCA/certs/ca-chain.cert.pem $RootPath/intermediateCA/certs/intermediate.cert.pem # create server key -openssl ecparam -out $OutPath/intermediateCA/private/www.contoso.com.key.pem -name secp384r1 -genkey -openssl pkcs8 -topk8 -nocrypt -in $OutPath/intermediateCA/private/www.contoso.com.key.pem -out $OutPath/intermediateCA/private/ec_p384_private.pem +openssl ecparam -out $RootPath/intermediateCA/private/www.contoso.com.key.pem -name secp384r1 -genkey +openssl pkcs8 -topk8 -nocrypt -in $RootPath/intermediateCA/private/www.contoso.com.key.pem -out $RootPath/intermediateCA/private/ec_p384_private.pem -chmod 600 $OutPath/intermediateCA/private/www.contoso.com.key.pem +chmod 600 $RootPath/intermediateCA/private/www.contoso.com.key.pem # create csr for server -openssl req -config $RootPath/openssl_intermediate.cnf -key $OutPath/intermediateCA/private/www.contoso.com.key.pem -new -sha384 -out $OutPath/intermediateCA/csr/www.contoso.com.csr.pem -batch +openssl req -config openssl_intermediate.cnf -key $RootPath/intermediateCA/private/www.contoso.com.key.pem -new -sha384 -out $RootPath/intermediateCA/csr/www.contoso.com.csr.pem -batch # sign server cert with intermediate key -openssl ca -config $RootPath/openssl_intermediate.cnf -extensions server_cert -days 375 -notext -md sha384 -in $OutPath/intermediateCA/csr/www.contoso.com.csr.pem -out $OutPath/intermediateCA/certs/www.contoso.com.cert.pem -batch +openssl ca -config openssl_intermediate.cnf -extensions server_cert -days 375 -notext -md sha384 -in $RootPath/intermediateCA/csr/www.contoso.com.csr.pem -out $RootPath/intermediateCA/certs/www.contoso.com.cert.pem -batch # print the cert -# openssl x509 -noout -text -in $OutPath/intermediateCA/certs/www.contoso.com.cert.pem +# openssl x509 -noout -text -in $RootPath/intermediateCA/certs/www.contoso.com.cert.pem # make a public key -# openssl x509 -pubkey -noout -in $OutPath/intermediateCA/certs/www.contoso.com.cert.pem -out $OutPath/intermediateCA/certs/pubkey.pem +# openssl x509 -pubkey -noout -in $RootPath/intermediateCA/certs/www.contoso.com.cert.pem -out $RootPath/intermediateCA/certs/pubkey.pem # create chain file -cat $OutPath/intermediateCA/certs/www.contoso.com.cert.pem $OutPath/intermediateCA/certs/intermediate.cert.pem $OutPath/rootCA/certs/ca.cert.pem > $OutPath/intermediateCA/certs/www.contoso.com.chain.cert.pem +cat $RootPath/intermediateCA/certs/www.contoso.com.cert.pem $RootPath/intermediateCA/certs/intermediate.cert.pem $RootPath/rootCA/certs/ca.cert.pem > $RootPath/intermediateCA/certs/www.contoso.com.chain.cert.pem cd $OriginalPath \ No newline at end of file diff --git a/src/confcom/setup.py b/src/confcom/setup.py index fe40522e879..7b8c1157a0d 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.5.0" +VERSION = "1.4.5" # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers