Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[confcom] fragment bug fixes and katapolicygen tooling update #8393

Merged
merged 11 commits into from
Jan 7, 2025
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
16 changes: 16 additions & 0 deletions src/confcom/azext_confcom/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
validate_fragment_path,
validate_fragment_json,
validate_fragment_json_policy,
validate_image_target,
validate_upload_fragment,
)


Expand Down Expand Up @@ -230,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 @@ -258,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 @@ -301,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 @@ -66,10 +66,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 @@ -78,6 +87,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 @@ -608,6 +608,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
Loading