Skip to content

Commit

Permalink
[confcom] fragment bug fixes and katapolicygen tooling update (Azure#…
Browse files Browse the repository at this point in the history
…8393)

* updating docs and bugfix for fragments

* decoupling fragment feed from image push target

* adding --omit-id to acifragmentgen

* updating version number

* adding bugfix and test for persistent volume template

* consolidating to version 1.1.2 from 1.1.3

* style fixes

* fixing tests

* commenting out some tests for now until we figure out how to run the bash script

* updating rules

* updating version number
  • Loading branch information
SethHollandsworth authored and ksayid committed Jan 8, 2025
1 parent 2a0f624 commit 6d2f923
Show file tree
Hide file tree
Showing 25 changed files with 1,052 additions and 243 deletions.
11 changes: 11 additions & 0 deletions src/confcom/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
Release History
===============

1.2.0
++++++
* fixing metadata for uploaded fragments
* fixing support for non-image feed names and attaching fragments to an image
* bug fixes for image-attached fragments
* adding ability to generate a fragment import from an image name using the remote attached fragments
* updating stdout import statement to look more like the file output
* adding `--omit-id` to the `acifragmentgen` command
* updating genpolicy to version 3.2.0.azl3.genpolicy2

1.1.1
++++++
* updating dmverity-vhd version with bugfix for empty image layers
Expand Down
31 changes: 21 additions & 10 deletions src/confcom/azext_confcom/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ For information on what a policy fragment is, see [policy fragments](#policy-fra
Example 1: The following command creates a security fragment and prints it to stdout as well as saving it to a file `contoso.rego`:

```bash
az confcom acifragmentgen --config ./fragment_config.json --svn 1 --namespace contoso
az confcom acifragmentgen --input ./fragment_config.json --svn 1 --namespace contoso
```

The config file is a JSON file that contains the following information:
Expand Down Expand Up @@ -708,7 +708,7 @@ The `--svn` argument is used to specify the security version number of the fragm
Example 2: This command creates a signed security fragment and attaches it to a container image in an ORAS-compliant registry:

```bash
az confcom acifragmentgen --chain ./samples/certs/intermediateCA/certs/www.contoso.com.chain.cert.pem --key ./samples/certs/intermediateCA/private/ec_p384_private.pem --svn 1 --namespace contoso --config ./samples/config.json --upload-fragment
az confcom acifragmentgen --chain ./samples/certs/intermediateCA/certs/www.contoso.com.chain.cert.pem --key ./samples/certs/intermediateCA/private/ec_p384_private.pem --svn 1 --namespace contoso --input ./samples/config.json --upload-fragment
```

Example 3: This command creates a file to be used by `acipolicygen` that says which fragments should be included in the policy. Note that the policy must be [COSE](https://www.iana.org/assignments/cose/cose.xhtml) signed:
Expand All @@ -721,19 +721,30 @@ This outputs a file `fragments.json` that contains the following information:

```json
{
"path": "./contoso.rego.cose",
"feed": "contoso.azurecr.io/example",
"includes": [
"containers",
"fragments"
],
"issuer": "did:x509:0:sha256:mLzv0uyBNQvC6hi4y9qy8hr6NSZuYFv6gfCwAEWBNqc::subject:CN:Contoso",
"minimum_svn": "1"
"fragments": [
{
"feed": "contoso.azurecr.io/example",
"includes": [
"containers",
"fragments"
],
"issuer": "did:x509:0:sha256:mLzv0uyBNQvC6hi4y9qy8hr6NSZuYFv6gfCwAEWBNqc::subject:CN:Contoso",
"minimum_svn": "1"
}
]
}
```

This file is then used by `acipolicygen` to generate a policy that includes custom fragments.

Example 4: The command creates a signed policy fragment and attaches it to a specified image in an ORAS-compliant registry:

```bash
az confcom acifragmentgen --chain ./samples/certs/intermediateCA/certs/www.contoso.com.chain.cert.pem --key ./samples/certs/intermediateCA/private/ec_p384_private.pem --svn 1 --namespace contoso --input ./samples/<my-config>.json --upload-fragment --image-target contoso.azurecr.io/<my-image>:latest --feed contoso.azurecr.io/<my-feed>
```

This could be useful in scenarios where an image-attached fragment is required but the fragment's feed is different from the image's location.

## Microsoft Azure CLI 'confcom katapolicygen' Extension Examples

Run `az confcom katapolicygen --help` to see a list of supported arguments along with explanations. The following commands demonstrate the usage of different arguments to generate confidential computing security policies.
Expand Down
16 changes: 13 additions & 3 deletions src/confcom/azext_confcom/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
- name: --omit-id
type: boolean
short-summary: 'When enabled, the generated policy will not contain the ID field. This will keep the policy from being tied to a specific image name and tag'
short-summary: 'When enabled, the generated policy will not contain the ID field. This will keep the policy from being tied to a specific image name and tag. This is helpful if the image being used will be present in multiple registries and used interchangeably'
- name: --include-fragments -f
type: boolean
Expand Down Expand Up @@ -167,6 +167,10 @@
type: string
short-summary: 'Path to an existing policy fragment file to be used with --generate-import. This option allows you to create import statements for the specified fragment without needing to pull it from an OCI registry'
- name: --omit-id
type: boolean
short-summary: 'When enabled, the generated policy will not contain the ID field. This will keep the policy from being tied to a specific image name and tag. This is helpful if the image being used will be present in multiple registries and used interchangeably'
- name: --generate-import -g
type: boolean
short-summary: 'Generate an import statement for a policy fragment'
Expand Down Expand Up @@ -201,9 +205,15 @@
- name: Input a config file to generate a fragment with a custom namespace and debug mode enabled
text: az confcom acifragmentgen --input "./config.json" --namespace "my-namespace" --debug-mode
- name: Generate an import statement for a signed local fragment
text: az confcom acifragmentgen --fragment-path "./fragment.json" --generate-import --minimum-svn 1
text: az confcom acifragmentgen --fragment-path "./fragment.rego.cose" --generate-import --minimum-svn 1
- name: Generate a fragment and COSE sign it with a key and chain
text: az confcom acifragmentgen --image mcr.microsoft.com/azuredocs/aci-helloworld --key "./key.pem" --chain "./chain.pem" --svn 1 --namespace contoso --no-print
text: az confcom acifragmentgen --input "./config.json" --key "./key.pem" --chain "./chain.pem" --svn 1 --namespace contoso --no-print
- name: Generate a fragment import from an image name
text: az confcom acifragmentgen --image <my-image> --generate-import --minimum-svn 1
- name: Attach a fragment to a specified image
text: az confcom acifragmentgen --input "./config.json" --key "./key.pem" --chain "./chain.pem" --svn 1 --namespace contoso --upload-fragment --image-target <my-image>
"""

helps[
Expand Down
17 changes: 16 additions & 1 deletion src/confcom/azext_confcom/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
validate_fragment_path,
validate_fragment_json,
validate_fragment_json_policy,
validate_aci_convert_source,
validate_image_target,
validate_upload_fragment,
)


Expand Down Expand Up @@ -231,6 +232,13 @@ def load_arguments(self, _):
required=False,
help="Feed for the generated policy fragment",
)
c.argument(
"image_target",
options_list=("--image-target"),
required=False,
help="Image target where the generated policy fragment is attached",
validator=validate_image_target,
)
c.argument(
"key",
options_list=("--key", "-k"),
Expand Down Expand Up @@ -259,6 +267,12 @@ def load_arguments(self, _):
help="Path to a policy fragment to be used with --generate-import to make import statements without having access to the fragment's OCI registry",
validator=validate_fragment_path,
)
c.argument(
"omit_id",
options_list=("--omit-id"),
required=False,
help="Omit the id field in the policy. This is helpful if the image being used will be present in multiple registries and used interchangeably.",
)
c.argument(
"generate_import",
options_list=("--generate-import", "-g"),
Expand Down Expand Up @@ -302,6 +316,7 @@ def load_arguments(self, _):
options_list=("--upload-fragment", "-u"),
required=False,
help="Upload a policy fragment to a container registry",
validator=validate_upload_fragment,
)
c.argument(
"no_print",
Expand Down
16 changes: 15 additions & 1 deletion src/confcom/azext_confcom/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,19 @@ def validate_fragment_source(namespace):
raise CLIError("Must provide either an image name or an input file to generate a fragment")


def validate_image_target(namespace):
if namespace.image_target and not namespace.upload_fragment:
raise CLIError("Must specify --upload-fragment to use --image-target")


def validate_upload_fragment(namespace):
if namespace.upload_fragment and not (namespace.key or namespace.chain):
raise CLIError("Must sign the fragment with --key and --chain to upload it")


def validate_fragment_generate_import(namespace):
if namespace.generate_import and sum(map(bool, [
namespace.fragment_path,
namespace.input_path,
namespace.image_name
])) != 1:
raise CLIError(
Expand All @@ -92,6 +101,11 @@ def validate_fragment_generate_import(namespace):
"an image name to generate an import statement"
)
)
if namespace.generate_import and namespace.output_filename:
raise CLIError(
"Cannot specify an output file (--output-filename) when generating an import statement." +
"Use --fragments-json (-j) to write to a file."
)


def validate_fragment_namespace_and_svn(namespace):
Expand Down
9 changes: 8 additions & 1 deletion src/confcom/azext_confcom/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
ACI_FIELD_YAML_READINESS_PROBE = "readinessProbe"
ACI_FIELD_YAML_STARTUP_PROBE = "startupProbe"
VIRTUAL_NODE_YAML_METADATA = "metadata"
VIRTUAL_NODE_YAML_COMMAND = "command"
VIRTUAL_NODE_YAML_NAME = "name"
VIRTUAL_NODE_YAML_ANNOTATIONS = "annotations"
VIRTUAL_NODE_YAML_LABELS = "labels"
Expand All @@ -103,6 +104,7 @@
VIRTUAL_NODE_YAML_RESOURCES_HUGEPAGES = "hugepages"
VIRTUAL_NODE_YAML_RESOURCES_EPHEMERAL_STORAGE = "ephemeral-storage"
VIRTUAL_NODE_YAML_SPECIAL_ENV_VAR_REGEX = "^===VIRTUALNODE2.CC.THIM.(.+)===$"
VIRTUAL_NODE_YAML_READ_ONLY_MANY = "ReadOnlyMany"

# output json values
POLICY_FIELD_CONTAINERS = "containers"
Expand Down Expand Up @@ -198,13 +200,18 @@
# reserved fragment names for existing pieces of Rego
RESERVED_FRAGMENT_NAMES = _config["reserved_fragment_namespaces"]
# fragment artifact type
ARTIFACT_TYPE = "application/x-ms-policy-frag"
ARTIFACT_TYPE = "application/x-ms-ccepolicy-frag"
# customer rego file for data to be injected
REGO_FILE = "./data/customer_rego_policy.txt"
REGO_FRAGMENT_FILE = "./data/customer_rego_fragment.txt"
script_directory = os.path.dirname(os.path.realpath(__file__))
REGO_FILE_PATH = f"{script_directory}/{REGO_FILE}"
REGO_FRAGMENT_FILE_PATH = f"{script_directory}/{REGO_FRAGMENT_FILE}"
REGO_IMPORT_FILE_STRUCTURE = """
{
"fragments": []
}
"""
CUSTOMER_REGO_POLICY = load_str_from_file(REGO_FILE_PATH)
CUSTOMER_REGO_FRAGMENT = load_str_from_file(REGO_FRAGMENT_FILE_PATH)
# sidecar rego file
Expand Down
3 changes: 3 additions & 0 deletions src/confcom/azext_confcom/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,9 @@ def get_id(self) -> str:
def get_name(self) -> str:
return self.containerName

def get_container_image(self) -> str:
return self.containerImage

def get_working_dir(self) -> str:
return self._workingDir

Expand Down
18 changes: 18 additions & 0 deletions src/confcom/azext_confcom/cose_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ def cose_sign(
payload_path,
"-key",
key_path,
"-salt",
"zero",
"-content-type",
"application/unknown+rego",
"-out",
out_path,
]
Expand Down Expand Up @@ -183,3 +187,17 @@ def extract_payload_from_path(self, fragment_path: str) -> str:

stdout = item.stdout.decode("utf-8")
return stdout.split("payload:")[1]

def extract_feed_from_path(self, fragment_path: str) -> str:
policy_bin_str = str(self.policy_bin)
if not os.path.exists(fragment_path):
eprint(f"The fragment file at {fragment_path} does not exist")

arg_list_chain = [policy_bin_str, "check", "--in", fragment_path, "--verbose"]

item = call_cose_sign_tool(arg_list_chain, "Error getting information from signed fragment file")

stdout = item.stdout.decode("utf-8")

# we want the text between the name and the next newline
return stdout.split("feed: ")[1].split("\n")[0]
Loading

0 comments on commit 6d2f923

Please sign in to comment.