Skip to content

Commit 53239ec

Browse files
[confcom] Adding fragment support for VN2 (Azure#8700)
* bugfix to have it so users can specify args without command in vn2 yaml * adding more cases for specifying entrypoint and command * adding replacement for special env var * adding mount for workload identities and checking normalized yaml * update docs so multiple containers in a single tar file is not recommended or valid * fixing broken test * adding args to let vn2 use fragments * updating version number and history * updating test for different env var value in public container * updating another env var
1 parent d213309 commit 53239ec

16 files changed

+455
-45
lines changed

src/confcom/HISTORY.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
33
Release History
44
===============
5+
6+
1.2.3
7+
++++++
8+
* adding fragment support for VN2
9+
* bugfix for vn2 workload identities
10+
* no longer encouraged to have multiple images in the same tar file
11+
512
1.2.2
613
++++++
714
* support for pure OCI v1 schema 2 formatted images

src/confcom/azext_confcom/README.md

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -181,22 +181,7 @@ Users just need to make a tar file by using the `docker save` command above, inc
181181
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.
182182
However, the CLI tool does not attempt to fetch remotely if `--tar` argument is used.
183183

184-
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:
185-
186-
```bash
187-
docker save ImageTag1 ImageTag2 ImageTag3 -o file.tar
188-
```
189-
190-
Disconnect from network and delete the local image from the docker daemon.
191-
Use the following command to generate CCE policy for the image.
192-
193-
```bash
194-
az confcom acipolicygen -a .\sample-template-input.json --tar .\file.tar
195-
```
196-
197-
Note that multiple images saved to the tar file is only available using the docker-archive format for tar files. OCI does not support multi-image tar files at this time.
198-
199-
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:
184+
Example 11: 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:
200185

201186
```bash
202187
docker save image:tag1 -o file1.tar
@@ -221,7 +206,7 @@ Use the following command to generate CCE policy for the image.
221206
az confcom acipolicygen -a .\sample-template-input.json --tar .\tar_mappings.json
222207
```
223208

224-
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:
209+
Example 12: 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:
225210

226211
Create parameters in the ARM Template for each environment variable that has an unknown or secret value such as:
227212

@@ -292,6 +277,29 @@ Use the following command to generate and print a security policy for an AKS pod
292277
az confcom acipolicygen --virtual-node-yaml ./pod.yaml --print-policy
293278
```
294279

280+
To generate a security policy using a policy config file for Virtual Node, the `scenario` field must be equal to `"vn2"`. This looks like:
281+
282+
```json
283+
{
284+
"version": "1.0",
285+
"scenario": "vn2",
286+
"containers": [
287+
{
288+
"name": "my-image",
289+
"properties": {
290+
"image": "mcr.microsoft.com/acc/samples/aci/helloworld:2.8"
291+
}
292+
}
293+
]
294+
}
295+
```
296+
297+
This `scenario` field adds the necessary environment variables and mount values to containers in the config file.
298+
299+
### Workload Identity
300+
301+
To use workload identities with VN2, the associated label [described here](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview?tabs=dotnet#pod-labels) must be present. Having this will add the requisite environment variables and mounts to each container's policy.
302+
295303
> [!NOTE]
296304
> The `acipolicygen` command is specific to generating policies for ACI-based containers. For generating security policies for the [Confidential Containers on AKS](https://learn.microsoft.com/en-us/azure/aks/confidential-containers-overview) feature, use the `katapolicygen` command.
297305

src/confcom/azext_confcom/_params.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
validate_fragment_json_policy,
2525
validate_image_target,
2626
validate_upload_fragment,
27+
validate_infrastructure_svn,
2728
)
2829

2930

@@ -88,6 +89,7 @@ def load_arguments(self, _):
8889
options_list=("--infrastructure-svn",),
8990
required=False,
9091
help="Minimum Allowed Software Version Number for Infrastructure Fragment",
92+
validator=validate_infrastructure_svn,
9193
)
9294
c.argument(
9395
"debug_mode",

src/confcom/azext_confcom/_validators.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ def validate_print_format(namespace):
2424
raise CLIError("Can only print in one format at a time")
2525

2626

27+
def validate_infrastructure_svn(namespace):
28+
if namespace.infrastructure_svn and namespace.exclude_default_fragments:
29+
raise CLIError("Cannot set infrastructure SVN without using default fragments")
30+
31+
2732
def validate_aci_source(namespace):
2833
if sum(map(bool, [
2934
namespace.input_path,

src/confcom/azext_confcom/config.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
ACI_FIELD_RESOURCES = "resources"
1212
ACI_FIELD_RESOURCES_NAME = "name"
1313
ACI_FIELD_CONTAINERS = "containers"
14+
ACI_FIELD_SCENARIO = "scenario"
1415
ACI_FIELD_CONTAINERS_NAME = "name"
1516
ACI_FIELD_CONTAINERS_CONTAINERIMAGE = "containerImage"
1617
ACI_FIELD_CONTAINERS_ENVS = "environmentVariables"
@@ -63,6 +64,7 @@
6364
ACI_FIELD_TEMPLATE_RESOURCE_LABEL = "Microsoft.ContainerInstance/containerGroups"
6465
ACI_FIELD_TEMPLATE_RESOURCE_PROFILE_LABEL = "Microsoft.ContainerInstance/containerGroupProfiles"
6566
ACI_FIELD_TEMPLATE_COMMAND = "command"
67+
ACI_FIELD_TEMPLATE_ENTRYPOINT = "entrypoint"
6668
ACI_FIELD_TEMPLATE_ENVS = "environmentVariables"
6769
ACI_FIELD_TEMPLATE_VOLUME_MOUNTS = "volumeMounts"
6870
ACI_FIELD_TEMPLATE_MOUNTS_TYPE = "mountType"
@@ -84,8 +86,12 @@
8486
ACI_FIELD_YAML_LIVENESS_PROBE = "livenessProbe"
8587
ACI_FIELD_YAML_READINESS_PROBE = "readinessProbe"
8688
ACI_FIELD_YAML_STARTUP_PROBE = "startupProbe"
89+
ACI_FIELD_TEMPLATE_SPECIAL_ENV_VAR_REGEX_NAME = "THIM_ENDPOINT"
90+
ACI_FIELD_TEMPLATE_SPECIAL_ENV_VAR_REGEX_VALUE = "^===CONFIDENTIAL.THIM.ENDPOINT===$"
91+
8792
VIRTUAL_NODE_YAML_METADATA = "metadata"
8893
VIRTUAL_NODE_YAML_COMMAND = "command"
94+
VIRTUAL_NODE_YAML_ARGS = "args"
8995
VIRTUAL_NODE_YAML_NAME = "name"
9096
VIRTUAL_NODE_YAML_ANNOTATIONS = "annotations"
9197
VIRTUAL_NODE_YAML_LABELS = "labels"
@@ -158,6 +164,11 @@
158164
REGO_CONTAINER_START = "containers := "
159165
REGO_FRAGMENT_START = "fragments := "
160166

167+
# scenario options
168+
VN2 = "vn2"
169+
ACI = "aci"
170+
KATA = "kata"
171+
161172

162173
CONFIG_FILE = "./data/internal_config.json"
163174

@@ -191,6 +202,7 @@
191202
DEFAULT_MOUNTS_USER_VIRTUAL_NODE = _config["mount"]["default_mounts_user_virtual_node"]
192203
DEFAULT_MOUNTS_VIRTUAL_NODE = _config["mount"]["default_mounts_virtual_node"]
193204
DEFAULT_MOUNTS_PRIVILEGED_VIRTUAL_NODE = _config["mount"]["default_mounts_virtual_node_privileged"]
205+
DEFAULT_MOUNTS_WORKLOAD_IDENTITY_VIRTUAL_NODE = _config["mount"]["default_mounts_workload_identity_virtual_node"]
194206
# default mounts policy options for all containers
195207
DEFAULT_MOUNT_POLICY = _config["mount"]["default_policy"]
196208
# default rego policy to be added to all user containers

src/confcom/azext_confcom/container.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,19 @@ def extract_working_dir(container_json: Any) -> str:
133133
return workingDir
134134

135135

136+
def extract_entrypoint(container_json: Any) -> List[str]:
137+
# parse entrypoint. can either be a list of strings or None in the case of non-VN2 policy generation
138+
entrypoint = case_insensitive_dict_get(
139+
container_json, config.ACI_FIELD_TEMPLATE_ENTRYPOINT
140+
)
141+
if not isinstance(entrypoint, list) and entrypoint is not None:
142+
eprint(
143+
f'Field ["{config.ACI_FIELD_CONTAINERS}"]'
144+
+ f'["{config.ACI_FIELD_TEMPLATE_ENTRYPOINT}"] must be list of Strings.'
145+
)
146+
return entrypoint
147+
148+
136149
def extract_command(container_json: Any) -> List[str]:
137150
# parse command
138151
command = case_insensitive_dict_get(
@@ -512,7 +525,7 @@ def extract_get_signals(container_json: Any) -> List:
512525

513526

514527
class ContainerImage:
515-
# pylint: disable=too-many-instance-attributes
528+
# pylint: disable=too-many-instance-attributes, too-many-public-methods
516529

517530
@classmethod
518531
def from_json(
@@ -523,6 +536,7 @@ def from_json(
523536
id_val = extract_id(container_json)
524537
container_name = extract_container_name(container_json)
525538
environment_rules = extract_env_rules(container_json=container_json)
539+
entrypoint = extract_entrypoint(container_json)
526540
command = extract_command(container_json)
527541
working_dir = extract_working_dir(container_json)
528542
mounts = extract_mounts(container_json)
@@ -543,6 +557,7 @@ def from_json(
543557
containerImage=container_image,
544558
containerName=container_name,
545559
environmentRules=environment_rules,
560+
entrypoint=entrypoint,
546561
command=command,
547562
workingDir=working_dir,
548563
mounts=mounts,
@@ -568,14 +583,15 @@ def __init__(
568583
allow_elevated: bool,
569584
id_val: str,
570585
extraEnvironmentRules: Dict,
586+
entrypoint: List[str] = None,
571587
capabilities: Dict = copy.deepcopy(_CAPABILITIES),
572588
user: Dict = copy.deepcopy(_DEFAULT_USER),
573589
seccomp_profile_sha256: str = "",
574590
allowStdioAccess: bool = True,
575591
allowPrivilegeEscalation: bool = True,
576592
execProcesses: List = None,
577593
signals: List = None,
578-
containerName: str = ""
594+
containerName: str = "",
579595
) -> None:
580596
self.containerImage = containerImage
581597
self.containerName = containerName
@@ -584,6 +600,7 @@ def __init__(
584600
else:
585601
self.base, self.tag = containerImage, "latest"
586602
self._environmentRules = environmentRules
603+
self._entrypoint = entrypoint
587604
self._command = command
588605
self._workingDir = workingDir
589606
self._layers = []
@@ -621,6 +638,11 @@ def set_signals(self, signals: List) -> None:
621638
def set_working_dir(self, workingDir: str) -> None:
622639
self._workingDir = workingDir
623640

641+
# note that entrypoint is only used for VN2 containers because of kubernetes discrepancy in naming
642+
# entrypoint -> command, args -> command
643+
def get_entrypoint(self) -> List[str]:
644+
return self._entrypoint
645+
624646
def get_command(self) -> List[str]:
625647
return self._command
626648

@@ -645,6 +667,9 @@ def set_user(self, user: Dict) -> None:
645667
def get_mounts(self) -> List:
646668
return self._mounts
647669

670+
def set_mounts(self, mounts) -> None:
671+
self._mounts = mounts
672+
648673
def get_seccomp_profile_sha256(self) -> str:
649674
return self._seccomp_profile_sha256
650675

@@ -774,10 +799,11 @@ def from_json(
774799
image.get_mounts().extend(_DEFAULT_MOUNTS_VN2)
775800

776801
# Start with the customer environment rules
777-
env_rules = _INJECTED_CUSTOMER_ENV_RULES
802+
env_rules = copy.deepcopy(_INJECTED_CUSTOMER_ENV_RULES)
778803
# If is_vn2, add the VN2 environment rules
779804
if is_vn2:
780805
env_rules += _INJECTED_SERVICE_VN2_ENV_RULES
806+
image.set_mounts(image.get_mounts() + copy.deepcopy(config.DEFAULT_MOUNTS_VIRTUAL_NODE))
781807

782808
image.set_extra_environment_rules(env_rules)
783809
return image

src/confcom/azext_confcom/custom.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,10 @@ def acipolicygen_confcom(
144144
debug_mode=debug_mode,
145145
disable_stdio=disable_stdio,
146146
approve_wildcards=approve_wildcards,
147-
diff_mode=diff
147+
diff_mode=diff,
148+
rego_imports=fragments_list,
149+
exclude_default_fragments=exclude_default_fragments,
150+
infrastructure_svn=infrastructure_svn,
148151
)
149152

150153
exit_code = 0

src/confcom/azext_confcom/data/internal_config.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "1.2.2",
2+
"version": "1.2.3",
33
"hcsshim_config": {
44
"maxVersion": "1.0.0",
55
"minVersion": "0.0.1"
@@ -282,6 +282,14 @@
282282
"mountType": "emptyDir",
283283
"mountPath": "/etc/hostname"
284284
}
285+
],
286+
"default_mounts_workload_identity_virtual_node": [
287+
{
288+
"name": "azure-tokens",
289+
"mountType": "emptyDir",
290+
"mountPath": "/var/run/secrets/azure/tokens",
291+
"readonly": true
292+
}
285293
]
286294
},
287295
"sidecar_base_names": [

0 commit comments

Comments
 (0)