diff --git a/src/confcom/HISTORY.rst b/src/confcom/HISTORY.rst index 3146344bbd7..72dee2e0ce3 100644 --- a/src/confcom/HISTORY.rst +++ b/src/confcom/HISTORY.rst @@ -2,6 +2,11 @@ Release History =============== +0.2.11 +* bug fix for clean room scenario where non-existent docker client connection attempted to be closed +* adding ability for ARM Template workflows to use regex for environment variables +* fixing linux permissions for dmverity-vhd tool + 0.2.10 * dmverity-vhd tool fixes * changing startup checks to errors rather than warnings diff --git a/src/confcom/README.md b/src/confcom/README.md index f1eda302014..18fde73c884 100644 --- a/src/confcom/README.md +++ b/src/confcom/README.md @@ -4,19 +4,11 @@ - [Repository](#repository) - [Prerequisites](#prerequisites) - [Installation Instructions (End User)](#installation-instructions-end-user) - - [Generating a confidential execution enforcement (cce) policy](#generating-a-confidential-execution-enforcement-cce-policy) - - [Setup and Instructions for Developers](#setup-and-instructions-for-developers) - - [Setup Development Environment](#setup-development-environment) - - [Build Extension Binary(Wheel) and Run Extension Tests](#build-extension-binarywheel-and-run-extension-tests) - - [Miscellaneous](#miscellaneous) - - [Azure Container Registration authentication](#azure-container-registration-authentication) - - [Authentication with service principals](#authentication-with-service-principals) - - [Authenticate with Azure managed identity](#authenticate-with-azure-managed-identity) - [Trademarks](#trademarks) ## Repository -- +- ## Prerequisites @@ -37,23 +29,6 @@ - Windows: [Docker Desktop](https://www.docker.com/products/docker-desktop) and [WSL2](https://docs.microsoft.com/en-us/windows/wsl/install) -## Docker Standalone Instructions (End User) - -### TODO: change this image when it goes to a public registry - -1. Download the docker container: `fishersnpregistry.azurecr.io/confcom-cli:clean-room` -2. Run: - - ```bash - docker run -v "$(pwd):/temp" -v /var/run/docker.sock:/var/run/docker.sock fishersnpregistry.azurecr.io/confcom-cli:clean-room az confcom acipolicygen -a temp/template.json - ``` - -Notes: - -- The first `-v` flag can be changed to go wherever in the local machine that has the input files for generating policies. For example, the ARM Template that is going to be used. -- The second `-v` is for mounting the Docker socket into the container, so Docker must be running on the host machine in order to generate policies from images that are contained within the Docker daemon. This includes images that need to be pulled from a remote registry. -- The path to the input file in the `az confcom acipolicygen` snippet must line up with where the local folder is getting mounted in the first `-v` flag. For example, above we are mounting to `/temp` in the container so the CLI command will be `az confcom acipolicygen -a /temp/template.json` because `template.json` is in the current local directory. - ## Installation Instructions (End User) 1. Install Azure CLI through following ways: @@ -67,9 +42,11 @@ Notes: 2. Option 2:(Linux Only) [Install through Linux Package Tools](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt). -## Generating a confidential execution enforcement (cce) policy +2. Install the `confcom` extension: -Please see [ACIConfidentialSecurityPolicySpec](https://microsoft-my.sharepoint.com/:w:/p/sewong/EV7PkPR5kWJMnmqm9TtWt0QBhmpYg1HqKwknw07DleugKQ?e=zLQZOl) + ```bash + az extension add -n confcom + ``` ## Trademarks diff --git a/src/confcom/azext_confcom/README.md b/src/confcom/azext_confcom/README.md index 2b201bc9264..c0399d99173 100644 --- a/src/confcom/azext_confcom/README.md +++ b/src/confcom/azext_confcom/README.md @@ -1,81 +1,77 @@ # Microsoft Azure CLI 'confcom' Extension Examples and Security Policy Rules Documentation -- [Microsoft Azure CLI 'confcom' Extension Examples and Security Policy Rules Documentation](#microsoft-azure-cli-confcom-Extension-examples-and-security-policy-rules-documentation) +- [Microsoft Azure CLI 'confcom' Extension Examples and Security Policy Rules Documentation](#microsoft-azure-cli-confcom-extension-examples-and-security-policy-rules-documentation) - [Microsoft Azure CLI 'confcom' Extension Examples](#microsoft-azure-cli-confcom-extension-examples) - [Security Policy Rules Documentation](#security-policy-rules-documentation) - - [mount_device](#mount_device) - - [unmount_device](#unmount_device) - - [mount_overlay](#mount_overlay) - - [unmount_overlay](#unmount_overlay) - - [create_container](#create_container) - - [exec_in_container](#exec_in_container) - - [exec_external](#exec_external) - - [shutdown_container](#shutdown_container) - - [signal_container_process](#signal_container_process) - - [plan9_mount](#plan9_mount) - - [plan9_unmount](#plan9_unmount) - - [scratch_mount](#scratch_mount) - - [scratch_unmount](#scratch_unmount) - - [load_fragment](#load_fragment) - - [fragments](#fragments) - - [reason](#reason) - - [A Sample Policy that Uses Framework](#a-sample-policy-that-uses-framework) - - [allow_properties_access](#a-sample-policy-that-uses-framework) - - [allow_dump_stack](#a-sample-policy-that-uses-framework) - - [allow_runtime_logging](#a-sample-policy-that-uses-framework) - - [allow_environment_variable_dropping](#allow_environment_variable_dropping) - - [allow_unencrypted_scratch](#allow_unencrypted_scratch) - - - - -## Microsoft Azure CLI 'confcom' Extension Examples - -Run `az confcom acipolicygen --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. - -**Note:** The Azure Confidential Computing CLI extension is in public preview and is subject to change. Some arguments may be added or removed and the way `confcom acipolicygen` command is called to achieve specific functionality may change as well. This documentation will be updated as changes to the tooling are published. + - [mount_device](#mount_device) + - [unmount_device](#unmount_device) + - [mount_overlay](#mount_overlay) + - [unmount_overlay](#unmount_overlay) + - [create_container](#create_container) + - [exec_in_container](#exec_in_container) + - [exec_external](#exec_external) + - [shutdown_container](#shutdown_container) + - [signal_container_process](#signal_container_process) + - [plan9_mount](#plan9_mount) + - [plan9_unmount](#plan9_unmount) + - [scratch_mount](#scratch_mount) + - [scratch_unmount](#scratch_unmount) + - [load_fragment](#load_fragment) + - [fragments](#fragments) + - [reason](#reason) + - [A Sample Policy that Uses Framework](#a-sample-policy-that-uses-framework) + - [allow_properties_access](#a-sample-policy-that-uses-framework) + - [allow_dump_stack](#a-sample-policy-that-uses-framework) + - [allow_runtime_logging](#a-sample-policy-that-uses-framework) + - [allow_environment_variable_dropping](#allow_environment_variable_dropping) + - [allow_unencrypted_scratch](#allow_unencrypted_scratch) + +## Microsoft Azure CLI 'confcom' Extension Examples + +Run `az confcom acipolicygen --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. + +**Note:** The Azure Confidential Computing CLI extension is in public preview and is subject to change. Some arguments may be added or removed and the way `confcom acipolicygen` command is called to achieve specific functionality may change as well. This documentation will be updated as changes to the tooling are published. **Prerequisites:** -Install the Azure CLI and Confidential Computing extension. +Install the Azure CLI and Confidential Computing extension. See the most recently released version of `confcom` extension. - az extension list-available -o table | grep confcom + az extension list-available -o table | grep confcom To add the most recent confcom extension, run: az extension add --name confcom -Use the `--version` argument to specify a version to add. - +Use the `--version` argument to specify a version to add. The `acipolicygen` command generates confidential computing security policies using an image, an input JSON file, or an ARM template. You can control the format of the generated policies using arguments. Note: It is recommended to use images with specific tags instead of the `latest` tag, as the `latest` tag can change at any time and images with different configurations may also have the latest tag. **Examples:** -Example 1: The following command creates a CCE policy and outputs it to the command line:
+Example 1: The following command creates a CCE policy and outputs it to the command line:
az confcom acipolicygen -a .\template.json --print-policy -This command combines the information of images from the ARM template with other information such as mount, environment variables and commands from the ARM template to create a CCE policy. +This command combines the information of images from the ARM template with other information such as mount, environment variables and commands from the ARM template to create a CCE policy. The `--print-policy` argument is included to display the policy on the command line rather than injecting it into the input ARM template. Example 2: This command injects a CCE policy into [ARM-template](arm.template.md) based on input from [parameters-file](template.parameters.md) so that there is no need to change the ARM template to pass variables into the CCE policy:
az confcom acipolicygen -a .\arm-template.json -p .\template.parameters.json -This is mainly for decoupling purposes so that an ARM template can remain the same and evolving variables can go into a different file. +This is mainly for decoupling purposes so that an ARM template can remain the same and evolving variables can go into a different file. -Example 3: This command takes the input of an ARM template to create a human-readable CCE policy in pretty print JSON format and output the result to the console. +Example 3: This command takes the input of an ARM template to create a human-readable CCE policy in pretty print JSON format and output the result to the console. NOTE: Generating JSON policy is for use by the customer only, and is not used by ACI In most cases. The default REGO format security policy is required.
az confcom acipolicygen -a ".\arm_template" --outraw-pretty-print --json -The default output of `acipolicygen` command is base64 encoded REGO format. -This example uses the `--json` argument to generate output in JSON format, use `--outraw-pretty-print` to indicate decoding policy in clear text and in pretty print format and print result to console. +The default output of `acipolicygen` command is base64 encoded REGO format. +This example uses the `--json` argument to generate output in JSON format, use `--outraw-pretty-print` to indicate decoding policy in clear text and in pretty print format and print result to console. Example 4: The following command takes the input of an ARM template to create a human-readable CCE policy in clear text and print to console:
- + az confcom acipolicygen -a ".\arm-template.json" --outraw Use `--outraw` argument to output policy in clear text compact REGO format. @@ -88,347 +84,447 @@ Example 6: Validate the policy present in the ARM template under "ccepolicy" and az confcom acipolicygen -a ".\arm-template.json" --diff -Example 7: Decode the existing CCE policy in ARM template and print to console in clear text. +Example 7: Decode the existing CCE policy in ARM template and print to console in clear text. az confcom acipolicygen -a ".\arm-template.json" --print-existing-policy -Example 8: Generate a CCE policy using `--disable-stdio` argument. -`--disable-stdio` argument disables container standard I/O access by setting `allow_stdio_access` to false. +Example 8: Generate a CCE policy using `--disable-stdio` argument. +`--disable-stdio` argument disables container standard I/O access by setting `allow_stdio_access` to false. az confcom acipolicygen -a ".\arm-template.json" --disable-stdio -Example 9: Inject a CCE policy into ARM template. +Example 9: Inject a CCE policy into ARM template. This command adds the `--debug-mode` argument to enable executing /bin/sh and /bin/bash in the container group:
az confcom acipolicygen -a .\sample-arm-input.json --debug-mode In the above example, The `--debug-mode` modifies the following to allow users to shell into the container via portal or the command line: -1. Adds the following to container rule so that users can access bash process. +1. Adds the following to container rule so that users can access bash process. -``` - "exec_processes": [ - { - "command": [ - "/bin/sh" - ], - "signals": [] - }, - { - "command": [ - "/bin/bash" - ], - "signals": [] - } + ```json + "exec_processes": [ + { + "command": [ + "/bin/sh" + ], + "signals": [] + }, + { + "command": [ + "/bin/bash" + ], + "signals": [] + } ] -``` -2. Changes the values of these three rules to true on the policy. -This is also for the purpose of allowing users to access logging, container properties and dump stack, all of which are part of loggings as well. -See [A Sample Policy that Uses Framework](#a-sample-policy-that-uses-framework) for details for the following rules: + ``` + +2. Changes the values of these three rules to true on the policy. +This is also for the purpose of allowing users to access logging, container properties and dump stack, all of which are part of loggings as well. +See [A Sample Policy that Uses Framework](#a-sample-policy-that-uses-framework) for details for the following rules: - allow_properties_access - allow_dump_stacks - allow_runtime_logging - - + Example 10: The confidential computing extension CLI is designed in such a way that generating policy does not necessarily have to depend on network calls as long as users have the layers of the images they want to generate policies for saved in a tar file locally. See the following example:
docker save ImageTag -o file.tar Disconnect from network and delete the local image from the docker daemon. -Use the following command to generate CCE policy for the image. +Use the following command to generate CCE policy for the image. az confcom acipolicygen -a .\sample-template-input.json --tar .\file.tar -Some users have unique scenarios such as cleanroom requirement. -In this case, users can still generate security policies witout relying on network calls. -Users just need to make a tar file by using the `docker save` command above, include the `--tar` argument when making the `acipolicygen` command and make sure the input JSON file contains the same image tag. +Some users have unique scenarios such as cleanroom requirement. +In this case, users can still generate security policies witout relying on network calls. +Users just need to make a tar file by using the `docker save` command above, include the `--tar` argument when making the `acipolicygen` command and make sure the input JSON file contains the same image tag. + +When generating security policy without using `--tar` argument, the confcom extension CLI tool attemps to fetch the image remotely if it is not locally available. +However, the CLI tool does not attempt to fetch remotely if `--tar` argument is used. + +Example 11: The process used in example 10 can also be used to save multiple images into the same tar file. See the following example:
+ + docker save ImageTag1 ImageTag2 ImageTag3 -o file.tar + +Disconnect from network and delete the local image from the docker daemon. +Use the following command to generate CCE policy for the image. + + az confcom acipolicygen -a .\sample-template-input.json --tar .\file.tar + +Example 12: If it is necessary to put images in their own tarballs, an external file can be used that maps images to their respective tarball paths. See the following example:
+ + docker save image:tag1 -o file1.tar + docker save image:tag2 -o file2.tar + docker save image:tag3 -o file3.tar + +Create the following file (as an example named "tar_mappings.json")on the local filesystem: + + ```json + { + "image:tag1": "./file1.tar", + "image:tag2": "./file2.tar", + "image:tag3": "./file3.tar", + } + ``` + +Disconnect from network and delete the local image from the docker daemon. +Use the following command to generate CCE policy for the image. + + az confcom acipolicygen -a .\sample-template-input.json --tar .\tar_mappings.json + +Example 13: Some use cases necessitate the use of regular expressions to allow for environment variables where either their values are secret, or unknown at policy-generation time. For these cases, the workflow below can be used:
+ +Create parameters in the ARM Template for each environment variable that has an unknown or secret value such as: + + ```json + { + "parameters": { + "placeholderValue": { + "type": "string", + "metadata": { + "description": "This value will not be placed in the template.json" + } + } + }, + } + ``` -When generating security policy without using `--tar` argument, the confcom extension CLI tool attemps to fetch the image remotely if it is not locally available. -However, the CLI tool does not attempt to fetch remotely if `--tar` argument is used. +Note that this parameter declaration does not have a "defaultValue". Once these parameters are defined, they may be used later in the ARM Template such as: + + ```json + { + "environmentVariables": [ + { + "name": "PATH", + "value": "/customized/path/value" + }, + { + "name": "MY_SECRET", + "value": "[parameters('placeholderValue')]" + } + ] + } + ``` + +The policy can then be generated with: + + az confcom acipolicygen -a template.json + +Because the ARM Template does not have a value defined for the "placeholderValue", the regular expression ".*" is used in the Rego policy. This allows for any value to be used. If the value is contained in a parameters file, that can be used when deploying such as: + + az deployment group create --template-file "template.json" --parameters "parameters.json" ## Security Policy Rules Documentation -Below is an example rego policy: +Below is an example rego policy: -``` -package policy + ```rego + package policy -import future.keywords.every -import future.keywords.in + import future.keywords.every + import future.keywords.in -api_svn := "0.10.0" -framework_svn := "0.1.0" + api_svn := "0.10.0" + framework_svn := "0.1.0" -fragments := [...] + fragments := [...] -containers := [...] + containers := [...] -allow_properties_access := false -allow_dump_stacks := false -allow_runtime_logging := false -allow_environment_variable_dropping := true -allow_unencrypted_scratch := false + allow_properties_access := false + allow_dump_stacks := false + allow_runtime_logging := false + allow_environment_variable_dropping := true + allow_unencrypted_scratch := false -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 + 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} -``` + reason := {"errors": data.framework.errors} + ``` -Every valid policy contain rules with the following names in the policy namespace. -Each rule must return a Rego object with a member named allowed, which indicates whether the action is allowed by policy. -We document each rule as follow: +Every valid policy contain rules with the following names in the policy namespace. +Each rule must return a Rego object with a member named allowed, which indicates whether the action is allowed by policy. +We document each rule as follow: ## mount_device + Receives an input object with the following members: -``` -{ - "name": "mount_device", - "target": "", - "deviceHash": "" -} -``` + + ```json + { + "name": "mount_device", + "target": "", + "deviceHash": "" + } + ``` ## unmount_device + Receives an input object with the following members: -``` -{ - "name": "unmount_device", - "unmountTarget": "" -} -``` + + ```json + { + "name": "unmount_device", + "unmountTarget": "" + } + ``` ## mount_overlay -Describe the layers to mount: -``` -{ - "name": "mount_overlay", - "containerID": "", - "layerPaths": [ - "", - "", - "", - /*...*/ - ], - "target": "" -} -``` + +Describe the layers to mount: + + ```json + { + "name": "mount_overlay", + "containerID": "", + "layerPaths": [ + "", + "", + "", + /*...*/ + ], + "target": "" + } + ``` ## unmount_overlay + Receives an input object with the following members: -``` -{ - "name": "unmount_overlay", - "unmountTarget": "" -} -``` - -## create_container -Indicates whether the UVM is allowed to create a specific container with the exact parameters provided to the method. -Provided in the following input object, the framework rule checks the exact parameters such as (command, environment variables, mounts etc.) -``` -{ - "name": "create_container", - "containerID": "", - "argList": [ - "", - "", - "", - /*...*/ - ], - "envList": [ - "=", - /*...*/ - ], - "workingDir": "", - "sandboxDir": "", - "hugePagesDir": "", - "mounts": [ - { - "destination": "", - "options": [ - "", - "", - /*...*/ - ], - "source": "", - "type": ""}, - ], - privileged: "" -} -``` + + ```json + { + "name": "unmount_overlay", + "unmountTarget": "" + } + ``` + +## create_container + +Indicates whether the UVM is allowed to create a specific container with the exact parameters provided to the method. +Provided in the following input object, the framework rule checks the exact parameters such as (command, environment variables, mounts etc.) + + ```json + { + "name": "create_container", + "containerID": "", + "argList": [ + "", + "", + "", + /*...*/ + ], + "envList": [ + "=", + /*...*/ + ], + "workingDir": "", + "sandboxDir": "", + "hugePagesDir": "", + "mounts": [ + { + "destination": "", + "options": [ + "", + "", + /*...*/ + ], + "source": "", + "type": ""}, + ], + "privileged": "" + } + ``` ## exec_in_container -Determines if a process should be executed in a container. -Receives an input object with the following elements: -``` -{ - "containerID": "", - "argList": [ - "", - "", - "", - /*...*/ - ], - "envList": [ - "=", - /*...*/ - ], - "workingDir": "" -} -``` + +Determines if a process should be executed in a container. +Receives an input object with the following elements: + + ```json + { + "containerID": "", + "argList": [ + "", + "", + "", + /*...*/ + ], + "envList": [ + "=", + /*...*/ + ], + "workingDir": "" + } + ``` ## exec_external -Determines if a process should be executed in the UVM. + +Determines if a process should be executed in the UVM. Receives an input object with the following elements: -``` -{ - "name": "exec_external", - "argList": [ - "", - "", - "", - /*...*/ - ], - "envList": [ - "=", - /*...*/ - ], - "workingDir": "" -} -``` + + ```json + { + "name": "exec_external", + "argList": [ + "", + "", + "", + /*...*/ + ], + "envList": [ + "=", + /*...*/ + ], + "workingDir": "" + } + ``` ## shutdown_container + Receives an input object with the following elements: -``` -{ - "name": "shutdown_container", - "containerID": "" -} -``` + + ```json + { + "name": "shutdown_container", + "containerID": "" + } + ``` ## signal_container_process -Describe the signal sent to the container. + +Describe the signal sent to the container. Receives an input object with the following elements: -``` -{ - "name": "signal_container_process", - "containerID": "", - "signal": "", - "isInitProcess": "", - "argList": [ - "", - "", - "", - /*...*/ - ] -} -``` + + ```json + { + "name": "signal_container_process", + "containerID": "", + "signal": "", + "isInitProcess": "", + "argList": [ + "", + "", + "", + /*...*/ + ] + } + ``` ## plan9_mount -Controls what directories on the host are allowed to be mounted into the UVM so that they can later be used as mounts within containers. -Azure confidential computing evaluated the mount channel from host machine to guest machine and eventually to containers. -A serious attack consists of overwriting attested directories on the UVM and then subsequently gets loaded into containers. -This rule contains a target that designates destination mount so that the mentioned attack does not happen. + +Controls what directories on the host are allowed to be mounted into the UVM so that they can later be used as mounts within containers. +Azure confidential computing evaluated the mount channel from host machine to guest machine and eventually to containers. +A serious attack consists of overwriting attested directories on the UVM and then subsequently gets loaded into containers. +This rule contains a target that designates destination mount so that the mentioned attack does not happen. It receives an input with the following elements: -``` -{ - "name": "plan9_mount", - "target": "" -} -``` + + ```json + { + "name": "plan9_mount", + "target": "" + } + ``` ## plan9_unmount + Receives an input with the following elements: -``` -{ - "name": "plan9_unmount", - "unmountTarget": "" -} -``` + + ```json + { + "name": "plan9_unmount", + "unmountTarget": "" + } + ``` ## scratch_mount -Scratch is writable storage from the UVM to the container. + +Scratch is writable storage from the UVM to the container. It receives an input with the following elements: -``` -{ - "name": "scratch_mount", - "target": "", - "encrypted": "true|false" -} -``` + + ```json + { + "name": "scratch_mount", + "target": "", + "encrypted": "true|false" + } + ``` ## scratch_unmount + Receives an input with the following elements: -``` -{ - "name": "scratch_unmount", - "unmountTarget": "", -} -``` -## load_fragment -This rule is used to determine whether a fragment can be loaded. -See [fragments](#fragments) for detailed explanation. - -## fragments - -What is a fragment? - -Confidential Container provides the core primitives for allowing customers to build container based application solutions that leave Microsoft and Microsoft operators outside of TCB(Trusted Computing Base). -In order to achieve this, our environment has to implement enforcement policies that not only dictates which containers are allowed to run, but also the explicit versions of each container that are allowed to run. -The implication of this is that in the case of Confidential ACI, if the customer is allowing ACI provided sidecars into their TCB, the customer environment won't be able to be start if ACI updates any of their sidecars for regular maintenance. -Given that some customers will want to allow ACI sidecars into their trusted environment, we need to provide a way for customers to indicate a level of trust in ACI so that sidecars that ACI has indicated are theirs and that the customer has agreed to accept can be run. -In order to achieve this, We will allow additional constraints to be provided to a container environment. -And we call these additionally defined constraints "fragments". -Fragments can serve a number of use-cases. -For now, we will focus on the ACI sidecar use case. -See the following example and how it defines a fragment. -The following fragment states that my confidential computing environment should trust containers published by the DID `did:web:accdemo.github.io` on the feed named `accdemo/utilities`. - -``` -fragments := [ + ```json { - "feed": "accdemo/utilities", - "iss": "did:web:accdemo.github.io", - "includes": [<"containers"|"fragments"|"external_processes"|"namespace">] + "name": "scratch_unmount", + "unmountTarget": "", + } + ``` + +## load_fragment + +This rule is used to determine whether a policy fragment can be loaded. +See [policy fragments](#policy_fragments) for detailed explanation. + +## policy fragments + +What is a policy fragment? + +Confidential Container provides the core primitives for allowing customers to build container based application solutions that leave Microsoft and Microsoft operators outside of TCB(Trusted Computing Base). +In order to achieve this, our environment has to implement enforcement policies that not only dictates which containers are allowed to run, but also the explicit versions of each container that are allowed to run. +The implication of this is that in the case of Confidential ACI, if the customer is allowing ACI provided sidecars into their TCB, the customer environment won't be able to be start if ACI updates any of their sidecars for regular maintenance. +Given that some customers will want to allow ACI sidecars into their trusted environment, we need to provide a way for customers to indicate a level of trust in ACI so that sidecars that ACI has indicated are theirs and that the customer has agreed to accept can be run. +In order to achieve this, We will allow additional constraints to be provided to a container environment. +And we call these additionally defined constraints "policy fragments". +Policy fragments can serve a number of use-cases. +For now, we will focus on the ACI sidecar use case. +See the following example and how it defines a policy fragment. +The following policy fragment states that my confidential computing environment should trust containers published by the DID `did:web:accdemo.github.io` on the feed named `accdemo/utilities`. + + ```rego + fragments := [ + { + "feed": "accdemo/utilities", + "iss": "did:web:accdemo.github.io", + "includes": [<"containers"|"fragments"|"external_processes"|"namespace">] + } + ] + + default load_fragment := [] + load_fragment := includes { + some fragment in fragments + input.iss == fragment.iss + input.feed == fragment.feed + includes := fragment.includes } -] - -default load_fragment := [] -load_fragment := includes { - some fragment in fragments - input.iss == fragment.iss - input.feed == fragment.feed - includes := fragment.includes -} -``` - -Every time a fragment is presented to the enclosing system (e.g. GCS), the enclosing system is provided with a COSE_Sign1 signed envelope. -The header in the envelope contains the feed and the issuer and these information are included in the `input` context. -The logic of load_fragment rule selects a fragment from the list of fragments which matches the issuer and feed. -The enclosing system loads the fragment and queries it for the `includes` e.g. `container` and inserts them into the data context for later use. + ``` + +Every time a policy fragment is presented to the enclosing system (e.g. GCS), the enclosing system is provided with a COSE_Sign1 signed envelope. +The header in the envelope contains the feed and the issuer and these information are included in the `input` context. +The logic of load_fragment rule selects a policy fragment from the list of policy fragments which matches the issuer and feed. +The enclosing system loads the policy fragment and queries it for the `includes` e.g. `container` and inserts them into the data context for later use. ## reason -A policy can optionally define this rule, which will be called when a policy issues a denial. + +A policy can optionally define this rule, which will be called when a policy issues a denial. This is used to populate an informative error message. ## A Sample Policy that Uses Framework @@ -436,45 +532,39 @@ This is used to populate an informative error message. A more detailed explanation is provided for the following rules that seem to appear more than once on the CCE policy: `allow_properties_access`, `get_properties`, `allow_dump_stack`, `dump_stack`, `allow_runtime_logging` and `runtime_logging` -Rego framework supports policy authors by both describing the form that user policies should take, and consequently the form that Microsoft-provided Rego modules will follow. -It also provides some pre-built policy components that can make policy authoring easier. -Microsoft provides a [Rego Framework](https://github.com/microsoft/hcsshim/blob/main/pkg/securitypolicy/framework.rego) to make writing policies easier. -It contains a collection of helper functions, which in turn provide default implementations of the required rules. -These functions operate on Rego data with expected formats. -We include a [sample policy](sample_policy.md) which uses this framework. +Rego framework supports policy authors by both describing the form that user policies should take, and consequently the form that Microsoft-provided Rego modules will follow. +It also provides some pre-built policy components that can make policy authoring easier. +Microsoft provides a [Rego Framework](https://github.com/microsoft/hcsshim/blob/main/pkg/securitypolicy/framework.rego) to make writing policies easier. +It contains a collection of helper functions, which in turn provide default implementations of the required rules. +These functions operate on Rego data with expected formats. +We include a [sample policy](sample_policy.md) which uses this framework. -The difference between `allow_properties_access` vs `get_properties`: +The difference between `allow_properties_access` vs `get_properties`: -There is an API that defines a rule called `get_properties`. -A custom user policy can implement this however it wants. -However, a policy that uses the framework indicates their desired behavior to the framework with a flag called `allow_get_properties`. If you look at the framework implementation for get_properties you will see that it returns data.policy.allow_get_properties. -The same logic applies to both dump_stack and runtime_logging. +There is an API that defines a rule called `get_properties`. +A custom user policy can implement this however it wants. +However, a policy that uses the framework indicates their desired behavior to the framework with a flag called `allow_get_properties`. If you look at the framework implementation for get_properties you will see that it returns data.policy.allow_get_properties. +The same logic applies to both dump_stack and runtime_logging. `allow_properties_access` VS `get_properties` -When set to true, this indicates that `get_properties` should be allowed. -It indicates whether the host can fetch properties from a container. +When set to true, this indicates that `get_properties` should be allowed. +It indicates whether the host can fetch properties from a container. `allow_dump_stack` VS `dump_stack` When set to true, this indicates that `dump_stacks` should be allowed. `allow_runtime_logging` VS `runtime_logging` -When `allow_runtime_logging` is set to true, this indicates that `runtime_logging` should be allowed. -Runtime logging is logging done by the UVM, i.e. outside of containers and their processes. +When `allow_runtime_logging` is set to true, this indicates that `runtime_logging` should be allowed. +Runtime logging is logging done by the UVM, i.e. outside of containers and their processes. ## allow_environment_variable_dropping -The allow_environment_variable_dropping flag allows the framework, if set, to try to drop environment variables if there are no matching containers/processes. -This is an important aspect of serviceability, as it allows customer policies to be robust to the addition of extraneous environment variables which are unused by their containers. -Note throughout this that the existing logic of required environment variables holds. -The logic of dropping env vars is a bit complex but in general the framework looks at the intersection of the set of provided variables and the environment variable rules of each container/process and finds the largest set, which happens to be the one that requires dropping the least number of env vars. -It then tests to see if that set satisfies any containers. - -## allow_unencrypted_scratch -This rule determines whether unencrypted writable storage from the UVM to the container is allowed. - - - - - +The allow_environment_variable_dropping flag allows the framework, if set, to try to drop environment variables if there are no matching containers/processes. +This is an important aspect of serviceability, as it allows customer policies to be robust to the addition of extraneous environment variables which are unused by their containers. +Note throughout this that the existing logic of required environment variables holds. +The logic of dropping env vars is a bit complex but in general the framework looks at the intersection of the set of provided variables and the environment variable rules of each container/process and finds the largest set, which happens to be the one that requires dropping the least number of env vars. +It then tests to see if that set satisfies any containers. +## allow_unencrypted_scratch +This rule determines whether unencrypted writable storage from the UVM to the container is allowed. diff --git a/src/confcom/azext_confcom/_help.py b/src/confcom/azext_confcom/_help.py index 5bf3035af01..b89072a42a4 100644 --- a/src/confcom/azext_confcom/_help.py +++ b/src/confcom/azext_confcom/_help.py @@ -82,10 +82,12 @@ short-summary: 'When enabled, the generated security policy is printed to the command line instead of injected into the input ARM Template' examples: - - name: Input a policy.json file to create a base64 encoded Confidential Container Security Policy - text: az confcom acipolicygen --input "./policy.json" - - name: Input a policy.json file to create a human-readable Confidential Container Security Policy - text: az confcom acipolicygen --input "./policy.json" --outraw-pretty-print - - name: Input a policy.json file to save a Confidential Container Security Policy to a file - text: az confcom acipolicygen --input "./policy.json" -s "./output-file.txt" + - name: Input an ARM Template file to inject a base64 encoded Confidential Container Security Policy into the ARM Template + text: az confcom acipolicygen --template-file "./template.json" + - name: Input an ARM Template file to create a human-readable Confidential Container Security Policy + text: az confcom acipolicygen --template-file "./template.json" --outraw-pretty-print + - name: Input an ARM Template file to save a Confidential Container Security Policy to a file + text: az confcom acipolicygen --template-file "./template.json" -s "./output-file.txt" + - name: Input an ARM Template file and use a tar file as the image source instead of the Docker daemon + text: az confcom acipolicygen --template-file "./template.json" --tar "./image.tar" """ diff --git a/src/confcom/azext_confcom/container.py b/src/confcom/azext_confcom/container.py index 8819ff65d6d..96e1e687f7b 100644 --- a/src/confcom/azext_confcom/container.py +++ b/src/confcom/azext_confcom/container.py @@ -308,7 +308,7 @@ def __init__( allow_elevated: bool, id_val: str, extraEnvironmentRules: Dict, - allowStdioAccess: bool = False, + allowStdioAccess: bool = True, execProcesses: List = None, signals: List = None, ) -> None: diff --git a/src/confcom/azext_confcom/data/internal_config.json b/src/confcom/azext_confcom/data/internal_config.json index 51ea730f898..401ea690d6d 100644 --- a/src/confcom/azext_confcom/data/internal_config.json +++ b/src/confcom/azext_confcom/data/internal_config.json @@ -1,5 +1,5 @@ { - "version": "0.2.10", + "version": "0.2.11", "hcsshim_config": { "maxVersion": "1.0.0", "minVersion": "0.0.1" diff --git a/src/confcom/azext_confcom/rootfs_proxy.py b/src/confcom/azext_confcom/rootfs_proxy.py index 5033368d41b..5e5ef0d26f1 100644 --- a/src/confcom/azext_confcom/rootfs_proxy.py +++ b/src/confcom/azext_confcom/rootfs_proxy.py @@ -6,6 +6,7 @@ import subprocess from typing import List import os +import stat from pathlib import Path import platform from azext_confcom.errors import eprint @@ -40,6 +41,10 @@ def __init__(self): if not os.path.exists(self.policy_bin): raise RuntimeError("The extension binary file cannot be located.") + if not os.access(self.policy_bin, os.X_OK): + # add executable permissions for the current user if they don't exist + st = os.stat(self.policy_bin) + os.chmod(self.policy_bin, st.st_mode | stat.S_IXUSR) def get_policy_image_layers( self, image: str, tag: str, tar_location: str = "" diff --git a/src/confcom/azext_confcom/security_policy.py b/src/confcom/azext_confcom/security_policy.py index 98c2687410d..a5a55c0cccf 100644 --- a/src/confcom/azext_confcom/security_policy.py +++ b/src/confcom/azext_confcom/security_policy.py @@ -139,8 +139,6 @@ def _get_rootfs_proxy(self) -> SecurityPolicyProxy: def _close_docker_client(self) -> None: if self._docker_client: self._get_docker_client().close() - else: - docker.from_env().close() def close(self) -> None: self._close_docker_client() @@ -375,7 +373,7 @@ def _policy_serialization(self, use_json, pretty_print=False) -> str: if not is_sidecars: # add in the default containers that have their hashes pre-computed - policy += config.DEFAULT_CONTAINERS + policy += copy.deepcopy(config.DEFAULT_CONTAINERS) if self._disable_stdio: for container in policy: container[config.POLICY_FIELD_CONTAINERS_ALLOW_STDIO_ACCESS] = False @@ -706,6 +704,7 @@ def load_policy_from_image_name( config.POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS: config.DEFAULT_REGO_FRAGMENTS, }, debug_mode=debug_mode, + disable_stdio=disable_stdio, ) diff --git a/src/confcom/azext_confcom/template_util.py b/src/confcom/azext_confcom/template_util.py index 66ea51218b8..4e9fd8ab68a 100644 --- a/src/confcom/azext_confcom/template_util.py +++ b/src/confcom/azext_confcom/template_util.py @@ -138,21 +138,33 @@ def process_env_vars_from_template(image_properties: dict) -> List[Dict[str, str ) if template_env_vars: - env_vars = [ - { - config.ACI_FIELD_CONTAINERS_ENVS_NAME: case_insensitive_dict_get( - x, "name" - ), - config.ACI_FIELD_CONTAINERS_ENVS_VALUE: case_insensitive_dict_get( - x, "value" - ) or - case_insensitive_dict_get( - x, "secureValue" - ), - config.ACI_FIELD_CONTAINERS_ENVS_STRATEGY: "string", - } - for x in template_env_vars - ] + for env_var in template_env_vars: + name = case_insensitive_dict_get(env_var, "name") + value = case_insensitive_dict_get(env_var, "value") or case_insensitive_dict_get(env_var, "secureValue") + + if not name: + eprint( + f"Environment variable with value: {value} is missing a name" + ) + + if value: + if config.ACI_FIELD_TEMPLATE_PARAMETERS in value: + response = input(f'Create a wildcard policy for the environment variable {name} (y/n): ') + if response.lower() == 'y': + env_vars.append({ + config.ACI_FIELD_CONTAINERS_ENVS_NAME: name, + config.ACI_FIELD_CONTAINERS_ENVS_VALUE: ".*", + config.ACI_FIELD_CONTAINERS_ENVS_STRATEGY: "re2", + }) + else: + env_vars.append({ + config.ACI_FIELD_CONTAINERS_ENVS_NAME: name, + config.ACI_FIELD_CONTAINERS_ENVS_VALUE: value, + config.ACI_FIELD_CONTAINERS_ENVS_STRATEGY: "string", + }) + else: + eprint(f'Environment variable {name} does not have a value. Please check the template file.') + return env_vars @@ -329,7 +341,7 @@ def change_key_names(dictionary) -> Dict: return dictionary -def find_value_in_params_and_vars(params: dict, vars_dict: dict, search: str) -> str: +def find_value_in_params_and_vars(params: dict, vars_dict: dict, search: str, ignore_undefined_parameters=False) -> str: """Utility function: either returns the input search value, or replaces it with the defined value in either params or vars of the ARM template""" # this pattern might need to be updated for more naming options in the future @@ -352,7 +364,7 @@ def find_value_in_params_and_vars(params: dict, vars_dict: dict, search: str) -> if not param_value: eprint( - f"""Field ["{param_name}"] not found in ["{config.ACI_FIELD_TEMPLATE_PARAMETERS}"] + f"""Field "{param_name}" not found in ["{config.ACI_FIELD_TEMPLATE_PARAMETERS}"] or ["{config.ACI_FIELD_TEMPLATE_VARIABLES}"]""" ) # fallback to default value @@ -362,16 +374,16 @@ def find_value_in_params_and_vars(params: dict, vars_dict: dict, search: str) -> else: match = case_insensitive_dict_get(vars_dict, param_name) - if not match: + if not match and not ignore_undefined_parameters: eprint( - f"""Field ["{param_name}"] not found in ["{config.ACI_FIELD_TEMPLATE_PARAMETERS}"] + f"""Field "{param_name}"'s value not found in ["{config.ACI_FIELD_TEMPLATE_PARAMETERS}"] or ["{config.ACI_FIELD_TEMPLATE_VARIABLES}"]""" ) - return match + return match or search -def parse_template(params: dict, vars_dict: dict, template) -> Any: +def parse_template(params: dict, vars_dict: dict, template, ignore_undefined_parameters=False) -> Any: """Utility function: replace all instances of variable and parameter references in an ARM template current limitations: - object values for parameters and variables @@ -382,12 +394,17 @@ def parse_template(params: dict, vars_dict: dict, template) -> Any: if isinstance(template, dict): for key, value in template.items(): if isinstance(value, str): - template[key] = find_value_in_params_and_vars(params, vars_dict, value) + # we want to ignore undefined parameters for only env var values, not names + template[key] = find_value_in_params_and_vars(params, vars_dict, value, + ignore_undefined_parameters=ignore_undefined_parameters + and key.lower() in ("value", "securevalue")) elif isinstance(value, dict): parse_template(params, vars_dict, value) elif isinstance(value, list): for i, _ in enumerate(value): - template[key][i] = parse_template(params, vars_dict, value[i]) + template[key][i] = parse_template(params, vars_dict, value[i], + ignore_undefined_parameters=key + == config.ACI_FIELD_CONTAINERS_ENVS) return template diff --git a/src/confcom/azext_confcom/tests/latest/README.md b/src/confcom/azext_confcom/tests/latest/README.md index f82de30f465..dfc916e0574 100644 --- a/src/confcom/azext_confcom/tests/latest/README.md +++ b/src/confcom/azext_confcom/tests/latest/README.md @@ -37,6 +37,9 @@ test_update_infrastructure_svn | python:3.6.14-slim-buster | Change the minimum test_multiple_policies | python:3.6.14-slim-buster & rust:1.52.1 | 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 | python:3.6.14-slim-buster & rust:1.52.1 | See if having an initContainer is picked up and added to the list of valid containers test_arm_template_without_stdio_access | rust:1.52.1 | See if disabling container stdio access gets passed down to individual containers +test_arm_template_policy_regex | python:3.6.14-slim-buster | Make sure the regex generated from the ARM Template workflow matches that of the policy.json workflow +test_wildcard_env_var | python:3.6.14-slim-buster | 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 ## policy.json [test file](test_confcom_scenario.py) 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 5a10b004d3e..da0526623e2 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_arm.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_arm.py @@ -9,6 +9,7 @@ import json import deepdiff import docker +from unittest.mock import patch from azext_confcom.security_policy import ( OutputType, @@ -1943,7 +1944,7 @@ def test_update_infrastructure_svn(self): # @unittest.skip("not in use") -@pytest.mark.run(order=8) +@pytest.mark.run(order=10) class MultiplePolicyTemplate(unittest.TestCase): custom_json = """ { @@ -2123,7 +2124,7 @@ def test_multiple_policies(self): # @unittest.skip("not in use") -@pytest.mark.run(order=10) +@pytest.mark.run(order=11) class PolicyGeneratingArmInitContainer(unittest.TestCase): custom_arm_json_default_value = """ @@ -2312,7 +2313,7 @@ def test_arm_template_with_init_container(self): # @unittest.skip("not in use") -@pytest.mark.run(order=11) +@pytest.mark.run(order=12) class PolicyGeneratingDisableStdioAccess(unittest.TestCase): custom_arm_json_default_value = """ @@ -2460,7 +2461,7 @@ def test_arm_template_without_stdio_access(self): # @unittest.skip("not in use") -@pytest.mark.run(order=11) +@pytest.mark.run(order=13) class PrintExistingPolicy(unittest.TestCase): def test_printing_existing_policy(self): @@ -2759,3 +2760,665 @@ def test_printing_existing_policy(self): # delete test file os.remove("test_template.json") os.remove("test_template2.json") + +# @unittest.skip("not in use") +@pytest.mark.run(order=14) +class PolicyGeneratingArmWildcardEnvs(unittest.TestCase): + custom_json = """ + { + "version": "1.0", + "containers": [ + { + "containerImage": "python:3.6.14-slim-buster", + "environmentVariables": [ + { + "name":"PATH", + "value":"/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "strategy":"string" + }, + { + "name":"LANG", + "value":"C.UTF-8", + "strategy":"string" + }, + { + "name":"GPG_KEY", + "value":"0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D", + "strategy":"string" + }, + { + "name":"PYTHON_VERSION", + "value":"3.6.14", + "strategy":"string" + }, + { + "name":"PYTHON_PIP_VERSION", + "value":"21.2.4", + "strategy":"string" + }, + { + "name":"PYTHON_GET_PIP_URL", + "value":"https://github.com/pypa/get-pip/raw/c20b0cfd643cd4a19246ccf204e2997af70f6b21/public/get-pip.py", + "strategy":"string" + }, + { + "name":"PYTHON_GET_PIP_SHA256", + "value":"fa6f3fb93cce234cd4e8dd2beb54a51ab9c247653b52855a48dd44e6b21ff28b", + "strategy":"string" + }, + { + "name":"TEST_WILDCARD_ENV", + "value":".*", + "strategy":"re2" + } + ], + "command": ["python3"], + "workingDir": "", + "mounts": [] + } + ] + } + """ + + custom_arm_json_error = """ + { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "variables": { + "image": "python:3.6.14-slim-buster" + }, + + + "parameters": { + "containergroupname": { + "type": "string", + "metadata": { + "description": "Name for the container group" + }, + "defaultValue":"simple-container-group" + }, + + "containername": { + "type": "string", + "metadata": { + "description": "Name for the container" + }, + "defaultValue":"simple-container" + }, + "port": { + "type": "string", + "metadata": { + "description": "Port to open on the container and the public IP address." + }, + "defaultValue": "8080" + }, + "cpuCores": { + "type": "string", + "metadata": { + "description": "The number of CPU cores to allocate to the container." + }, + "defaultValue": "1.0" + }, + "memoryInGb": { + "type": "string", + "metadata": { + "description": "The amount of memory to allocate to the container in gigabytes." + }, + "defaultValue": "1.5" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + } + }, + "resources": [ + { + "name": "[parameters('containergroupname')]", + "type": "Microsoft.ContainerInstance/containerGroups", + "apiVersion": "2022-04-01-preview", + "location": "[parameters('location')]", + "properties": { + "containers": [ + { + "name": "[parameters('containername')]", + + "properties": { + "image": "[variables('image')]", + "command": [ + "python3" + ], + "ports": [ + { + "port": "[parameters('port')]" + } + ], + "resources": { + "requests": { + "cpu": "[parameters('cpuCores')]", + "memoryInGb": "[parameters('memoryInGb')]" + } + }, + "environmentVariables": [ + { + "name": "PATH", + "value": "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + }, + { + "name": "TEST_WILDCARD_ENV" + } + ] + } + } + ], + "osType": "Linux", + "restartPolicy": "OnFailure", + "confidentialComputeProperties": { + "IsolationType": "SevSnp" + }, + "ipAddress": { + "type": "Public", + "ports": [ + { + "protocol": "Tcp", + "port": "[parameters( 'port' )]" + } + ] + } + } + } + ], + "outputs": { + "containerIPv4Address": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ContainerInstance/containerGroups/', parameters('containergroupname'))).ipAddress.ip]" + } + } + } + """ + custom_arm_json_error2 = """ + { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "variables": { + "image": "python:3.6.14-slim-buster" + }, + + + "parameters": { + "containergroupname": { + "type": "string", + "metadata": { + "description": "Name for the container group" + }, + "defaultValue":"simple-container-group" + }, + + "containername": { + "type": "string", + "metadata": { + "description": "Name for the container" + }, + "defaultValue":"simple-container" + }, + "port": { + "type": "string", + "metadata": { + "description": "Port to open on the container and the public IP address." + }, + "defaultValue": "8080" + }, + "cpuCores": { + "type": "string", + "metadata": { + "description": "The number of CPU cores to allocate to the container." + }, + "defaultValue": "1.0" + }, + "memoryInGb": { + "type": "string", + "metadata": { + "description": "The amount of memory to allocate to the container in gigabytes." + }, + "defaultValue": "1.5" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + } + }, + "resources": [ + { + "name": "[parameters('containergroupname')]", + "type": "Microsoft.ContainerInstance/containerGroups", + "apiVersion": "2022-04-01-preview", + "location": "[parameters('location')]", + "properties": { + "containers": [ + { + "name": "[parameters('containername')]", + + "properties": { + "image": "[variables('image')]", + "command": [ + "python3" + ], + "ports": [ + { + "port": "[parameters('port')]" + } + ], + "resources": { + "requests": { + "cpu": "[parameters('cpuCores')]", + "memoryInGb": "[parameters('memoryInGb')]" + } + }, + "environmentVariables": [ + { + "name": "[parameters('fake_parameter')]", + "value": "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + } + + ] + } + } + ], + "osType": "Linux", + "restartPolicy": "OnFailure", + "confidentialComputeProperties": { + "IsolationType": "SevSnp" + }, + "ipAddress": { + "type": "Public", + "ports": [ + { + "protocol": "Tcp", + "port": "[parameters( 'port' )]" + } + ] + } + } + } + ], + "outputs": { + "containerIPv4Address": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ContainerInstance/containerGroups/', parameters('containergroupname'))).ipAddress.ip]" + } + } + } + """ + custom_arm_json = """ + { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "variables": { + "image": "python:3.6.14-slim-buster" + }, + + + "parameters": { + "containergroupname": { + "type": "string", + "metadata": { + "description": "Name for the container group" + }, + "defaultValue":"simple-container-group" + }, + "wildcardParamName": { + "defaultValue": "TEST_WILDCARD_ENV", + "type": "string", + "metadata": { + "description": "Name for the container group" + } + + }, + "wildcardParamValue": { + "type": "string", + "metadata": { + "description": "Name for the container group" + } + + }, + + "containername": { + "type": "string", + "metadata": { + "description": "Name for the container" + }, + "defaultValue":"simple-container" + }, + "port": { + "type": "string", + "metadata": { + "description": "Port to open on the container and the public IP address." + }, + "defaultValue": "8080" + }, + "cpuCores": { + "type": "string", + "metadata": { + "description": "The number of CPU cores to allocate to the container." + }, + "defaultValue": "1.0" + }, + "memoryInGb": { + "type": "string", + "metadata": { + "description": "The amount of memory to allocate to the container in gigabytes." + }, + "defaultValue": "1.5" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + } + }, + "resources": [ + { + "name": "[parameters('containergroupname')]", + "type": "Microsoft.ContainerInstance/containerGroups", + "apiVersion": "2022-04-01-preview", + "location": "[parameters('location')]", + "properties": { + "containers": [ + { + "name": "[parameters('containername')]", + + "properties": { + "image": "[variables('image')]", + "command": [ + "python3" + ], + "ports": [ + { + "port": "[parameters('port')]" + } + ], + "resources": { + "requests": { + "cpu": "[parameters('cpuCores')]", + "memoryInGb": "[parameters('memoryInGb')]" + } + }, + "environmentVariables": [ + { + "name": "PATH", + "value": "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + }, + { + "name": "[parameters('wildcardParamName')]", + "value": "[parameters('wildcardParamValue')]" + } + ] + } + } + ], + "osType": "Linux", + "restartPolicy": "OnFailure", + "confidentialComputeProperties": { + "IsolationType": "SevSnp" + }, + "ipAddress": { + "type": "Public", + "ports": [ + { + "protocol": "Tcp", + "port": "[parameters( 'port' )]" + } + ] + } + } + } + ], + "outputs": { + "containerIPv4Address": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ContainerInstance/containerGroups/', parameters('containergroupname'))).ipAddress.ip]" + } + } + } + """ + custom_arm_json2 = """ + { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "variables": { + "image": "python:3.6.14-slim-buster" + }, + + + "parameters": { + "containergroupname": { + "type": "string", + "metadata": { + "description": "Name for the container group" + }, + "defaultValue":"simple-container-group" + }, + "wildcardParamName": { + "defaultValue": "TEST_WILDCARD_ENV", + "type": "string", + "metadata": { + "description": "Name for the container group" + } + + }, + "wildcardParamValue": { + "type": "string", + "metadata": { + "description": "Name for the container group" + } + + }, + "wildcardParamValue2": { + "type": "string", + "metadata": { + "description": "Name for the container group2" + } + + }, + + "containername": { + "type": "string", + "metadata": { + "description": "Name for the container" + }, + "defaultValue":"simple-container" + }, + "port": { + "type": "string", + "metadata": { + "description": "Port to open on the container and the public IP address." + }, + "defaultValue": "8080" + }, + "cpuCores": { + "type": "string", + "metadata": { + "description": "The number of CPU cores to allocate to the container." + }, + "defaultValue": "1.0" + }, + "memoryInGb": { + "type": "string", + "metadata": { + "description": "The amount of memory to allocate to the container in gigabytes." + }, + "defaultValue": "1.5" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + } + }, + "resources": [ + { + "name": "[parameters('containergroupname')]", + "type": "Microsoft.ContainerInstance/containerGroups", + "apiVersion": "2022-04-01-preview", + "location": "[parameters('location')]", + "properties": { + "containers": [ + { + "name": "[parameters('containername')]", + + "properties": { + "image": "[variables('image')]", + "command": [ + "python3" + ], + "ports": [ + { + "port": "[parameters('port')]" + } + ], + "resources": { + "requests": { + "cpu": "[parameters('cpuCores')]", + "memoryInGb": "[parameters('memoryInGb')]" + } + }, + "environmentVariables": [ + { + "name": "PATH", + "value": "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + }, + { + "name": "[parameters('wildcardParamName')]", + "value": "[parameters('wildcardParamValue')]" + }, + { + "name": "WILDCARD2", + "value": "[parameters('wildcardParamValue2')]" + } + ] + } + } + ], + "osType": "Linux", + "restartPolicy": "OnFailure", + "confidentialComputeProperties": { + "IsolationType": "SevSnp" + }, + "ipAddress": { + "type": "Public", + "ports": [ + { + "protocol": "Tcp", + "port": "[parameters( 'port' )]" + } + ] + } + } + } + ], + "outputs": { + "containerIPv4Address": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ContainerInstance/containerGroups/', parameters('containergroupname'))).ipAddress.ip]" + } + } + } + """ + aci_policy = None + aci_policy2 = None + + @classmethod + def setUpClass(cls): + with load_policy_from_str(cls.custom_json) as aci_policy: + aci_policy.populate_policy_content_for_all_images() + cls.aci_policy = aci_policy + + with patch('builtins.input', return_value='y'): + cls.aci_arm_policy = load_policy_from_arm_template_str(cls.custom_arm_json, "")[ + 0 + ] + cls.aci_arm_policy.populate_policy_content_for_all_images() + + with patch('builtins.input', side_effect=['y', 'n']): + cls.aci_arm_policy2 = load_policy_from_arm_template_str(cls.custom_arm_json2, "")[ + 0 + ] + cls.aci_arm_policy2.populate_policy_content_for_all_images() + + def test_arm_template_policy_regex(self): + # deep diff the output policies from the regular policy.json and the ARM template + normalized_aci_policy = json.loads( + self.aci_policy.get_serialized_output(output_type=OutputType.RAW, rego_boilerplate=False) + ) + + normalized_aci_arm_policy = json.loads( + self.aci_arm_policy.get_serialized_output( + output_type=OutputType.RAW,rego_boilerplate=False + ) + ) + + normalized_aci_policy[0].pop(config.POLICY_FIELD_CONTAINERS_ID) + + normalized_aci_arm_policy[0].pop(config.POLICY_FIELD_CONTAINERS_ID) + + self.assertEqual( + deepdiff.DeepDiff( + normalized_aci_policy, normalized_aci_arm_policy, ignore_order=True + ), + {}, + ) + + def test_wildcard_env_var(self): + normalized_aci_arm_policy = json.loads( + self.aci_arm_policy.get_serialized_output( + output_type=OutputType.RAW, rego_boilerplate=False + ) + ) + + self.assertEqual( + normalized_aci_arm_policy[0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS + ][1][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_STRATEGY], + "re2" + ) + + self.assertEqual( + normalized_aci_arm_policy[0][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS + ][1][config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_RULE], + "TEST_WILDCARD_ENV=.*" + ) + + normalized_aci_arm_policy2 = json.loads( + self.aci_arm_policy2.get_serialized_output( + output_type=OutputType.RAW, rego_boilerplate=False + ) + ) + + self.assertFalse( + 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=.*" + ) + + 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, "") + self.assertEqual(wrapped_exit.exception.code, 1) + + with self.assertRaises(SystemExit) as wrapped_exit: + load_policy_from_arm_template_str(self.custom_arm_json_error2, "") + self.assertEqual(wrapped_exit.exception.code, 1) + + 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 700bc156827..b1ae7646402 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_image.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_image.py @@ -29,10 +29,11 @@ def setUpClass(cls): cls.aci_policy = aci_policy def test_image_policy(self): - expected_policy = "cGFja2FnZSBwb2xpY3kKCmltcG9ydCBmdXR1cmUua2V5d29yZHMuZXZlcnkKaW1wb3J0IGZ1dHVyZS5rZXl3b3Jkcy5pbgoKYXBpX3N2biA6PSAiMC4xMC4wIgpmcmFtZXdvcmtfc3ZuIDo9ICIwLjEuMCIKCmZyYWdtZW50cyA6PSBbCiAgewogICAgImZlZWQiOiAibWNyLm1pY3Jvc29mdC5jb20vYWNpL2FjaS1jYy1pbmZyYS1mcmFnbWVudCIsCiAgICAiaW5jbHVkZXMiOiBbCiAgICAgICJjb250YWluZXJzIgogICAgXSwKICAgICJpc3N1ZXIiOiAiZGlkOng1MDk6MDpzaGEyNTY6SV9faXVMMjVvWEVWRmRUUF9hQkx4X2VUMVJQSGJDUV9FQ0JRZllacHQ5czo6ZWt1OjEuMy42LjEuNC4xLjMxMS43Ni41OS4xLjMiLAogICAgIm1pbmltdW1fc3ZuIjogIjEuMC4wIgogIH0KXQoKY29udGFpbmVycyA6PSBbeyJhbGxvd19lbGV2YXRlZCI6dHJ1ZSwiYWxsb3dfc3RkaW9fYWNjZXNzIjp0cnVlLCJjb21tYW5kIjpbInB5dGhvbjMiXSwiZW52X3J1bGVzIjpbeyJwYXR0ZXJuIjoiUEFUSD0vdXNyL2xvY2FsL2JpbjovdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5Ijoic3RyaW5nIn0seyJwYXR0ZXJuIjoiTEFORz1DLlVURi04IiwicmVxdWlyZWQiOmZhbHNlLCJzdHJhdGVneSI6InN0cmluZyJ9LHsicGF0dGVybiI6IkdQR19LRVk9MEQ5NkRGNEQ0MTEwRTVDNDNGQkZCMTdGMkQzNDdFQTZBQTY1NDIxRCIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJzdHJpbmcifSx7InBhdHRlcm4iOiJQWVRIT05fVkVSU0lPTj0zLjYuMTQiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5Ijoic3RyaW5nIn0seyJwYXR0ZXJuIjoiUFlUSE9OX1BJUF9WRVJTSU9OPTIxLjIuNCIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJzdHJpbmcifSx7InBhdHRlcm4iOiJQWVRIT05fR0VUX1BJUF9VUkw9aHR0cHM6Ly9naXRodWIuY29tL3B5cGEvZ2V0LXBpcC9yYXcvYzIwYjBjZmQ2NDNjZDRhMTkyNDZjY2YyMDRlMjk5N2FmNzBmNmIyMS9wdWJsaWMvZ2V0LXBpcC5weSIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJzdHJpbmcifSx7InBhdHRlcm4iOiJQWVRIT05fR0VUX1BJUF9TSEEyNTY9ZmE2ZjNmYjkzY2NlMjM0Y2Q0ZThkZDJiZWI1NGE1MWFiOWMyNDc2NTNiNTI4NTVhNDhkZDQ0ZTZiMjFmZjI4YiIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJzdHJpbmcifSx7InBhdHRlcm4iOiJURVJNPXh0ZXJtIiwicmVxdWlyZWQiOmZhbHNlLCJzdHJhdGVneSI6InN0cmluZyJ9LHsicGF0dGVybiI6IigoP2kpRkFCUklDKV8uKz0uKyIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJyZTIifSx7InBhdHRlcm4iOiJIT1NUTkFNRT0uKyIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJyZTIifSx7InBhdHRlcm4iOiJUKEUpP01QPS4rIiwicmVxdWlyZWQiOmZhbHNlLCJzdHJhdGVneSI6InJlMiJ9LHsicGF0dGVybiI6IkZhYnJpY1BhY2thZ2VGaWxlTmFtZT0uKyIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJyZTIifSx7InBhdHRlcm4iOiJIb3N0ZWRTZXJ2aWNlTmFtZT0uKyIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJyZTIifSx7InBhdHRlcm4iOiJJREVOVElUWV9BUElfVkVSU0lPTj0uKyIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJyZTIifSx7InBhdHRlcm4iOiJJREVOVElUWV9IRUFERVI9LisiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5IjoicmUyIn0seyJwYXR0ZXJuIjoiSURFTlRJVFlfU0VSVkVSX1RIVU1CUFJJTlQ9LisiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5IjoicmUyIn0seyJwYXR0ZXJuIjoiYXp1cmVjb250YWluZXJpbnN0YW5jZV9yZXN0YXJ0ZWRfYnk9LisiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5IjoicmUyIn1dLCJleGVjX3Byb2Nlc3NlcyI6W10sImlkIjoicHl0aG9uOjMuNi4xNC1zbGltLWJ1c3RlciIsImxheWVycyI6WyIyNTRjYzg1M2RhNjA4MTkwNWM5MTA5YzhiOWQ5OWM5ZmIwOTg3YmExZDg4ZjcyOTA4ODkwM2NmZmI4MGY1NWYxIiwiYTU2OGYxOTAwYmVkNjBhMDY0MWI3NmI5OTFhZDQzMTQ0NmQ5YzNhMzQ0ZDdiMjYxZjEwZGU4ZDhlNzM3NjNhYyIsImM3MGM1MzBlODQyZjY2MjE1YjBiZDk1NTg3NzE1N2JhMjRjMzc5OTMwMzU2N2MzZjU2NzNjNDU2NjNlYTRkMTUiLCIzZTg2YzNjY2YxNjQyYmY1ODRkZTMzYjQ5YzcyNDhmODdlZWNkMGY2ZDhjMDgzNTNkYWEzNmNjN2FkMGE3YjZhIiwiMWU0Njg0ZDhjN2NhYTc0YzY1MjQxNzJiNGQ1YTE1OWExMDg4NzYxM2VkNzBmMThkMGE1NWQwNWIyYWY2MWFjZCJdLCJtb3VudHMiOlt7ImRlc3RpbmF0aW9uIjoiL2V0Yy9yZXNvbHYuY29uZiIsIm9wdGlvbnMiOlsicmJpbmQiLCJyc2hhcmVkIiwicnciXSwic291cmNlIjoic2FuZGJveDovLy90bXAvYXRsYXMvcmVzb2x2Y29uZi8uKyIsInR5cGUiOiJiaW5kIn1dLCJzaWduYWxzIjpbXSwid29ya2luZ19kaXIiOiIvIn0seyJhbGxvd19lbGV2YXRlZCI6ZmFsc2UsImFsbG93X3N0ZGlvX2FjY2VzcyI6ZmFsc2UsImNvbW1hbmQiOlsiL3BhdXNlIl0sImVudl9ydWxlcyI6W3sicGF0dGVybiI6IlBBVEg9L3Vzci9sb2NhbC9zYmluOi91c3IvbG9jYWwvYmluOi91c3Ivc2JpbjovdXNyL2Jpbjovc2JpbjovYmluIiwicmVxdWlyZWQiOnRydWUsInN0cmF0ZWd5Ijoic3RyaW5nIn0seyJwYXR0ZXJuIjoiVEVSTT14dGVybSIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJzdHJpbmcifV0sImV4ZWNfcHJvY2Vzc2VzIjpbXSwibGF5ZXJzIjpbIjE2YjUxNDA1N2EwNmFkNjY1ZjkyYzAyODYzYWNhMDc0ZmQ1OTc2Yzc1NWQyNmJmZjE2MzY1Mjk5MTY5ZTg0MTUiXSwibW91bnRzIjpbXSwic2lnbmFscyI6W10sIndvcmtpbmdfZGlyIjoiLyJ9XQoKYWxsb3dfcHJvcGVydGllc19hY2Nlc3MgOj0gZmFsc2UKYWxsb3dfZHVtcF9zdGFja3MgOj0gZmFsc2UKYWxsb3dfcnVudGltZV9sb2dnaW5nIDo9IGZhbHNlCmFsbG93X2Vudmlyb25tZW50X3ZhcmlhYmxlX2Ryb3BwaW5nIDo9IHRydWUKYWxsb3dfdW5lbmNyeXB0ZWRfc2NyYXRjaCA6PSBmYWxzZQoKCgptb3VudF9kZXZpY2UgOj0gZGF0YS5mcmFtZXdvcmsubW91bnRfZGV2aWNlCnVubW91bnRfZGV2aWNlIDo9IGRhdGEuZnJhbWV3b3JrLnVubW91bnRfZGV2aWNlCm1vdW50X292ZXJsYXkgOj0gZGF0YS5mcmFtZXdvcmsubW91bnRfb3ZlcmxheQp1bm1vdW50X292ZXJsYXkgOj0gZGF0YS5mcmFtZXdvcmsudW5tb3VudF9vdmVybGF5CmNyZWF0ZV9jb250YWluZXIgOj0gZGF0YS5mcmFtZXdvcmsuY3JlYXRlX2NvbnRhaW5lcgpleGVjX2luX2NvbnRhaW5lciA6PSBkYXRhLmZyYW1ld29yay5leGVjX2luX2NvbnRhaW5lcgpleGVjX2V4dGVybmFsIDo9IGRhdGEuZnJhbWV3b3JrLmV4ZWNfZXh0ZXJuYWwKc2h1dGRvd25fY29udGFpbmVyIDo9IGRhdGEuZnJhbWV3b3JrLnNodXRkb3duX2NvbnRhaW5lcgpzaWduYWxfY29udGFpbmVyX3Byb2Nlc3MgOj0gZGF0YS5mcmFtZXdvcmsuc2lnbmFsX2NvbnRhaW5lcl9wcm9jZXNzCnBsYW45X21vdW50IDo9IGRhdGEuZnJhbWV3b3JrLnBsYW45X21vdW50CnBsYW45X3VubW91bnQgOj0gZGF0YS5mcmFtZXdvcmsucGxhbjlfdW5tb3VudApnZXRfcHJvcGVydGllcyA6PSBkYXRhLmZyYW1ld29yay5nZXRfcHJvcGVydGllcwpkdW1wX3N0YWNrcyA6PSBkYXRhLmZyYW1ld29yay5kdW1wX3N0YWNrcwpydW50aW1lX2xvZ2dpbmcgOj0gZGF0YS5mcmFtZXdvcmsucnVudGltZV9sb2dnaW5nCmxvYWRfZnJhZ21lbnQgOj0gZGF0YS5mcmFtZXdvcmsubG9hZF9mcmFnbWVudApzY3JhdGNoX21vdW50IDo9IGRhdGEuZnJhbWV3b3JrLnNjcmF0Y2hfbW91bnQKc2NyYXRjaF91bm1vdW50IDo9IGRhdGEuZnJhbWV3b3JrLnNjcmF0Y2hfdW5tb3VudAoKcmVhc29uIDo9IHsiZXJyb3JzIjogZGF0YS5mcmFtZXdvcmsuZXJyb3JzfQ==" + expected_policy = "cGFja2FnZSBwb2xpY3kKCmltcG9ydCBmdXR1cmUua2V5d29yZHMuZXZlcnkKaW1wb3J0IGZ1dHVyZS5rZXl3b3Jkcy5pbgoKYXBpX3N2biA6PSAiMC4xMC4wIgpmcmFtZXdvcmtfc3ZuIDo9ICIwLjEuMCIKCmZyYWdtZW50cyA6PSBbCiAgewogICAgImZlZWQiOiAibWNyLm1pY3Jvc29mdC5jb20vYWNpL2FjaS1jYy1pbmZyYS1mcmFnbWVudCIsCiAgICAiaW5jbHVkZXMiOiBbCiAgICAgICJjb250YWluZXJzIgogICAgXSwKICAgICJpc3N1ZXIiOiAiZGlkOng1MDk6MDpzaGEyNTY6SV9faXVMMjVvWEVWRmRUUF9hQkx4X2VUMVJQSGJDUV9FQ0JRZllacHQ5czo6ZWt1OjEuMy42LjEuNC4xLjMxMS43Ni41OS4xLjMiLAogICAgIm1pbmltdW1fc3ZuIjogIjEuMC4wIgogIH0KXQoKY29udGFpbmVycyA6PSBbeyJhbGxvd19lbGV2YXRlZCI6dHJ1ZSwiYWxsb3dfc3RkaW9fYWNjZXNzIjp0cnVlLCJjb21tYW5kIjpbInB5dGhvbjMiXSwiZW52X3J1bGVzIjpbeyJwYXR0ZXJuIjoiUEFUSD0vdXNyL2xvY2FsL2JpbjovdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5Ijoic3RyaW5nIn0seyJwYXR0ZXJuIjoiTEFORz1DLlVURi04IiwicmVxdWlyZWQiOmZhbHNlLCJzdHJhdGVneSI6InN0cmluZyJ9LHsicGF0dGVybiI6IkdQR19LRVk9MEQ5NkRGNEQ0MTEwRTVDNDNGQkZCMTdGMkQzNDdFQTZBQTY1NDIxRCIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJzdHJpbmcifSx7InBhdHRlcm4iOiJQWVRIT05fVkVSU0lPTj0zLjYuMTQiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5Ijoic3RyaW5nIn0seyJwYXR0ZXJuIjoiUFlUSE9OX1BJUF9WRVJTSU9OPTIxLjIuNCIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJzdHJpbmcifSx7InBhdHRlcm4iOiJQWVRIT05fR0VUX1BJUF9VUkw9aHR0cHM6Ly9naXRodWIuY29tL3B5cGEvZ2V0LXBpcC9yYXcvYzIwYjBjZmQ2NDNjZDRhMTkyNDZjY2YyMDRlMjk5N2FmNzBmNmIyMS9wdWJsaWMvZ2V0LXBpcC5weSIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJzdHJpbmcifSx7InBhdHRlcm4iOiJQWVRIT05fR0VUX1BJUF9TSEEyNTY9ZmE2ZjNmYjkzY2NlMjM0Y2Q0ZThkZDJiZWI1NGE1MWFiOWMyNDc2NTNiNTI4NTVhNDhkZDQ0ZTZiMjFmZjI4YiIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJzdHJpbmcifSx7InBhdHRlcm4iOiJURVJNPXh0ZXJtIiwicmVxdWlyZWQiOmZhbHNlLCJzdHJhdGVneSI6InN0cmluZyJ9LHsicGF0dGVybiI6IigoP2kpRkFCUklDKV8uKz0uKyIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJyZTIifSx7InBhdHRlcm4iOiJIT1NUTkFNRT0uKyIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJyZTIifSx7InBhdHRlcm4iOiJUKEUpP01QPS4rIiwicmVxdWlyZWQiOmZhbHNlLCJzdHJhdGVneSI6InJlMiJ9LHsicGF0dGVybiI6IkZhYnJpY1BhY2thZ2VGaWxlTmFtZT0uKyIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJyZTIifSx7InBhdHRlcm4iOiJIb3N0ZWRTZXJ2aWNlTmFtZT0uKyIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJyZTIifSx7InBhdHRlcm4iOiJJREVOVElUWV9BUElfVkVSU0lPTj0uKyIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJyZTIifSx7InBhdHRlcm4iOiJJREVOVElUWV9IRUFERVI9LisiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5IjoicmUyIn0seyJwYXR0ZXJuIjoiSURFTlRJVFlfU0VSVkVSX1RIVU1CUFJJTlQ9LisiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5IjoicmUyIn0seyJwYXR0ZXJuIjoiYXp1cmVjb250YWluZXJpbnN0YW5jZV9yZXN0YXJ0ZWRfYnk9LisiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5IjoicmUyIn1dLCJleGVjX3Byb2Nlc3NlcyI6W10sImlkIjoicHl0aG9uOjMuNi4xNC1zbGltLWJ1c3RlciIsImxheWVycyI6WyIyNTRjYzg1M2RhNjA4MTkwNWM5MTA5YzhiOWQ5OWM5ZmIwOTg3YmExZDg4ZjcyOTA4ODkwM2NmZmI4MGY1NWYxIiwiYTU2OGYxOTAwYmVkNjBhMDY0MWI3NmI5OTFhZDQzMTQ0NmQ5YzNhMzQ0ZDdiMjYxZjEwZGU4ZDhlNzM3NjNhYyIsImM3MGM1MzBlODQyZjY2MjE1YjBiZDk1NTg3NzE1N2JhMjRjMzc5OTMwMzU2N2MzZjU2NzNjNDU2NjNlYTRkMTUiLCIzZTg2YzNjY2YxNjQyYmY1ODRkZTMzYjQ5YzcyNDhmODdlZWNkMGY2ZDhjMDgzNTNkYWEzNmNjN2FkMGE3YjZhIiwiMWU0Njg0ZDhjN2NhYTc0YzY1MjQxNzJiNGQ1YTE1OWExMDg4NzYxM2VkNzBmMThkMGE1NWQwNWIyYWY2MWFjZCJdLCJtb3VudHMiOlt7ImRlc3RpbmF0aW9uIjoiL2V0Yy9yZXNvbHYuY29uZiIsIm9wdGlvbnMiOlsicmJpbmQiLCJyc2hhcmVkIiwicnciXSwic291cmNlIjoic2FuZGJveDovLy90bXAvYXRsYXMvcmVzb2x2Y29uZi8uKyIsInR5cGUiOiJiaW5kIn1dLCJzaWduYWxzIjpbXSwid29ya2luZ19kaXIiOiIvIn0seyJhbGxvd19lbGV2YXRlZCI6ZmFsc2UsImFsbG93X3N0ZGlvX2FjY2VzcyI6dHJ1ZSwiY29tbWFuZCI6WyIvcGF1c2UiXSwiZW52X3J1bGVzIjpbeyJwYXR0ZXJuIjoiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJyZXF1aXJlZCI6dHJ1ZSwic3RyYXRlZ3kiOiJzdHJpbmcifSx7InBhdHRlcm4iOiJURVJNPXh0ZXJtIiwicmVxdWlyZWQiOmZhbHNlLCJzdHJhdGVneSI6InN0cmluZyJ9XSwiZXhlY19wcm9jZXNzZXMiOltdLCJsYXllcnMiOlsiMTZiNTE0MDU3YTA2YWQ2NjVmOTJjMDI4NjNhY2EwNzRmZDU5NzZjNzU1ZDI2YmZmMTYzNjUyOTkxNjllODQxNSJdLCJtb3VudHMiOltdLCJzaWduYWxzIjpbXSwid29ya2luZ19kaXIiOiIvIn1dCgphbGxvd19wcm9wZXJ0aWVzX2FjY2VzcyA6PSBmYWxzZQphbGxvd19kdW1wX3N0YWNrcyA6PSBmYWxzZQphbGxvd19ydW50aW1lX2xvZ2dpbmcgOj0gZmFsc2UKYWxsb3dfZW52aXJvbm1lbnRfdmFyaWFibGVfZHJvcHBpbmcgOj0gdHJ1ZQphbGxvd191bmVuY3J5cHRlZF9zY3JhdGNoIDo9IGZhbHNlCgoKCm1vdW50X2RldmljZSA6PSBkYXRhLmZyYW1ld29yay5tb3VudF9kZXZpY2UKdW5tb3VudF9kZXZpY2UgOj0gZGF0YS5mcmFtZXdvcmsudW5tb3VudF9kZXZpY2UKbW91bnRfb3ZlcmxheSA6PSBkYXRhLmZyYW1ld29yay5tb3VudF9vdmVybGF5CnVubW91bnRfb3ZlcmxheSA6PSBkYXRhLmZyYW1ld29yay51bm1vdW50X292ZXJsYXkKY3JlYXRlX2NvbnRhaW5lciA6PSBkYXRhLmZyYW1ld29yay5jcmVhdGVfY29udGFpbmVyCmV4ZWNfaW5fY29udGFpbmVyIDo9IGRhdGEuZnJhbWV3b3JrLmV4ZWNfaW5fY29udGFpbmVyCmV4ZWNfZXh0ZXJuYWwgOj0gZGF0YS5mcmFtZXdvcmsuZXhlY19leHRlcm5hbApzaHV0ZG93bl9jb250YWluZXIgOj0gZGF0YS5mcmFtZXdvcmsuc2h1dGRvd25fY29udGFpbmVyCnNpZ25hbF9jb250YWluZXJfcHJvY2VzcyA6PSBkYXRhLmZyYW1ld29yay5zaWduYWxfY29udGFpbmVyX3Byb2Nlc3MKcGxhbjlfbW91bnQgOj0gZGF0YS5mcmFtZXdvcmsucGxhbjlfbW91bnQKcGxhbjlfdW5tb3VudCA6PSBkYXRhLmZyYW1ld29yay5wbGFuOV91bm1vdW50CmdldF9wcm9wZXJ0aWVzIDo9IGRhdGEuZnJhbWV3b3JrLmdldF9wcm9wZXJ0aWVzCmR1bXBfc3RhY2tzIDo9IGRhdGEuZnJhbWV3b3JrLmR1bXBfc3RhY2tzCnJ1bnRpbWVfbG9nZ2luZyA6PSBkYXRhLmZyYW1ld29yay5ydW50aW1lX2xvZ2dpbmcKbG9hZF9mcmFnbWVudCA6PSBkYXRhLmZyYW1ld29yay5sb2FkX2ZyYWdtZW50CnNjcmF0Y2hfbW91bnQgOj0gZGF0YS5mcmFtZXdvcmsuc2NyYXRjaF9tb3VudApzY3JhdGNoX3VubW91bnQgOj0gZGF0YS5mcmFtZXdvcmsuc2NyYXRjaF91bm1vdW50CgpyZWFzb24gOj0geyJlcnJvcnMiOiBkYXRhLmZyYW1ld29yay5lcnJvcnN9" # deep diff the output policies from the regular policy.json and the ARM template aci_policy_str = self.aci_policy.get_serialized_output() + self.assertEqual(aci_policy_str, expected_policy) 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 28b2462922c..d2fd2c292f7 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_scenario.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_scenario.py @@ -153,7 +153,7 @@ def test_user_container_mount_injected_dns(self): # @unittest.skip("not in use") @pytest.mark.run(order=2) -class PolicyGenerating2(unittest.TestCase): +class PolicyGenerating(unittest.TestCase): custom_json = """ { "version": "1.0", diff --git a/src/confcom/samples/README.md b/src/confcom/samples/README.md deleted file mode 100644 index a6048a1e386..00000000000 --- a/src/confcom/samples/README.md +++ /dev/null @@ -1,148 +0,0 @@ -# Microsoft Azure CLI 'confcom' Extension Samples - -## Example Input Configuration - -```json -{ - "version": "1.0", - "containers": [ - { - "containerImage": "rust:1.52.1", - "environmentVariables": [ - { - "name": "PATH", - "value": "/customized/path/value", - "strategy": "string" - }, - { - "name": "TEST_REGEXP_ENV", - "value": "test_regexp_env_[[:alpha:]]*", - "strategy": "re2" - } - ], - "command": ["rustc", "--help"], - "workingDir": ["/"], - "mounts": [ - { - "mountType": "azureFile", - "mountPath": "path/to/something/in/container", - "readonly": false - }, - { - "mountType": "secret", - "mountPath": "path/to/something/in/container", - "readonly": false - }, - { - "mountType": "emptyDir", - "mountPath": "path/to/something/in/container", - "readonly": false - } - ], - "wait_mount_points": [ - "path/to/something/in/container/blob0", - "path/to/something/in/container/blob1" - ], - "allow_elevated": true - } - ] -} -``` - -## version - -This specified the version of the input configuration file format. - -## containers - -This is a list of containers that will be deployed as part of a confidential container group. - -For each container the following items can be configured: - -### _containerImage_ - -The uri of the container image and container tag. - -### _environmentVariables_ - -The allowed environment variables for the container. There are 2 ways to specify the environment variables: - -1. Exact 'string' matching. With the _string_ strategy the value in the environment variable must exactly match the configured value. - -```json -{ - "name": "", - "value": "", - "strategy": "string" -}, -``` - -2. Regular expression "re2" matching. For more information see the [re2 guide.](https://github.com/google/re2/wiki/Syntax)
- -```json -{ - "name": "", - "value": "", - "strategy": "re2" -} -``` - -With re2 matching any value that matches the re2 expression will be allowed. - -```json - "=" -``` - -### _command_ - -The command item configures the allowed start-up command to launch inside the container. It is a list of strings that make up the final command to run. - -### **[Optional]** _workingDir_ - -The working directory item configures where the working directory where the command is executed. This is an optional field. If one is not specified the value is defaulted to the first value found in the following list: - -- The _working_dir_ field in the container image. -- Defaults to "/" - -### **[Optional]** _mounts_ - -Specifies the mounts that are allowed inside the container. - -```json - “mounts”: [ - { - "mountType": "azureFile | secret | emptyDir", - "mountPath": "path/to/something/in/container", - "readonly": "true | false" # Optional - } - ] -``` - -- _mountType_ - Specifies the type of the Azure mount. There are 3 types of Azure mounts that are supported. These mount should match with the mounts that are configured when deploying the container group. - 1. **azureFile** - This mount type corresponds to an [Azure file volume](https://docs.microsoft.com/en-us/azure/container-instances/container-instances-volume-azure-files) mount. - 2. **secret** - This mount type corresponds to an [Azure secret volume](https://docs.microsoft.com/en-us/azure/container-instances/container-instances-volume-secret). - 3. **emptyDir** - This mount type corresponds to an [Azure emptyDir volume](https://docs.microsoft.com/en-us/azure/container-instances/container-instances-volume-emptydir) - -- _mountPath_ - Specifies the container path for the corresponding mount. -- _readonly_ - Specifies whether the volume is read-only or writable. Defaults to false. - -### _wait_mount_points_ - -This configuration item list of container paths for different mounts that should exists before the command execution starts. If a mount does not exist, the container will not start to run. - -```json - "wait_mount_points": [ - "path/to/something/in/container/blob0", - "path/to/something/in/container/blob1" - ] -``` - -### **[Optional]** _allow_elevated_ - -By default, “/sys” and “/sys/fs/cgroup” mounts are added as “ro”, by setting "allow_elevated" to true, those mounts are added as “rw”. This is an optional field. If one is not specified the value is defaulted to false. - ---- - -## sample-template-output.json - -This file shows the changes that are made to an input ARM Template when the `--inject-policy` argument is supplied. diff --git a/src/confcom/setup.py b/src/confcom/setup.py index d8a6ff6cae8..2982235c626 100644 --- a/src/confcom/setup.py +++ b/src/confcom/setup.py @@ -11,7 +11,7 @@ import stat import requests import os -# TODO: do we need this? + try: from azure_bdist_wheel import cmdclass except ImportError: @@ -21,7 +21,7 @@ # TODO: Confirm this is the right version number you want and it matches your # HISTORY.rst entry. -VERSION = "0.2.10" +VERSION = "0.2.11" # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers @@ -58,9 +58,6 @@ r = requests.get("https://github.com/microsoft/hcsshim/releases/download/v0.10.0-rc.6/dmverity-vhd") with open(bin_path, "wb") as f: f.write(r.content) - # add executable permissions for the current user - st = os.stat(bin_path) - os.chmod(bin_path, st.st_mode | stat.S_IEXEC) with open("README.md", "r", encoding="utf-8") as f: README = f.read() @@ -77,8 +74,7 @@ long_description=README + "\n\n" + HISTORY, license="MIT", classifiers=CLASSIFIERS, - # TODO: should we be using the find_packages functionality or not? Most of the extensions do - packages=["azext_confcom"], + packages=find_packages(), install_requires=DEPENDENCIES, package_data={ "azext_confcom": [