Skip to content

Commit c011b67

Browse files
MahatiCDomAyremicromaomao
authored
[confcom] Windows support for the tooling (#9783)
* Support windows platform images * Pull specific versions of integrity-vhd instead of latest * Set version to be wcow * Make version compliant * Make the policy windows shaped * Support --debug-mode * Make lib a module * Only add platform flag on windows * Use updated integrity-vhd for fixed C-WCOW policy gen * Support windows images also in the new "containers from_image" command * [confcom]: acipolicygen: Fix missing platform field in generated policy config for vn2 * Update tooling to consume json * Bump framework version for windows * Update dll requirements And add a fix to framework for rw_mount * Bump confcom version 2.0.0 and pick v2.0 dmverity-vhd release * [confcom] Update policy api version for "new style" command too * [confcom] Make the to-be-released version 2.0.0b1 instead of an actual 2.0.0 Suggested-by: Ken Gordon <kegordo@microsoft.com> * Update azext_confcom/data/README * Update all sample policies to the new api version and add the rw_mount_device enforcement point This fixes unit tests * Remove misleading comment This is not, in fact, where parameters and variables are populated. That happens in the constructor for AciPolicy. * [confcom] Fix trying to fetch image with a name containing unresolved parameters/variables * [confcom] Fix lint errors * [confcom] Add --platform commandline option * [confcom] fix Virtual Node bug * [confcom] Derive image platform and add --platform validation * [confcom] update help text and README * [confcom] Do not use inspect, just use image pull * [confcom] Address copilot reviews * [confcom] regenerate golden policies --------- Co-authored-by: Dominic Ayre <dominicayre@microsoft.com> Co-authored-by: Dominic Ayre <domayre@outlook.com> Co-authored-by: Tingmao Wang <tingmaowang@microsoft.com>
1 parent a7886ca commit c011b67

196 files changed

Lines changed: 2398 additions & 1858 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/confcom/HISTORY.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
Release History
44
===============
55

6+
2.0.0b1
7+
+++++++
8+
* Add Windows container support with CIM-based layer hashing
9+
* Support for mounted_cim field in security policies for Windows containers
10+
611
1.8.0
712
+++++
813
* Ensure that fragments are attached to the correct manifest for a multiarch image.

src/confcom/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
```
3030

3131
- Windows: [Docker Desktop](https://www.docker.com/products/docker-desktop) and [WSL2](https://docs.microsoft.com/en-us/windows/wsl/install)
32+
- **CimWriter.dll** (Windows only, for Windows container support)
33+
- Required for generating security policies for Windows containers
34+
- Windows Server 2025 or newer is recommended for deterministic hash generation
3235

3336
## Installation Instructions (End User)
3437

@@ -57,6 +60,21 @@ The `confcom` extension does not currently support:
5760
- Variables and Parameters with non-primitive data types e.g. objects and arrays
5861
- Nested and Linked ARM Templates
5962

63+
## Platform Support (Linux and Windows Policies)
64+
65+
The `--platform` parameter controls whether policies are generated for Linux (`linux/amd64`, the default) or Windows (`windows/amd64`) containers.
66+
67+
**Docker Desktop must be running in the matching container mode** to produce correct layer hashes:
68+
69+
| Policy Target | Docker Container Mode | Where to Run |
70+
|---|---|---|
71+
| Linux (`--platform linux/amd64`) | Linux containers | WSL or PowerShell |
72+
| Windows (`--platform windows/amd64`) | Windows containers | PowerShell only |
73+
74+
- **Windows policies cannot be generated from WSL**, because Windows layer hashing (CIMfs) requires Windows APIs.
75+
- **Linux policies can be generated from either WSL or PowerShell**, as long as Docker Desktop is in Linux containers mode.
76+
- Running with the wrong Docker container mode may produce **incorrect layer hashes** that will cause the container to be rejected at runtime.
77+
6078
## Trademarks
6179

6280
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft

src/confcom/azext_confcom/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ package policy
399399
import future.keywords.every
400400
import future.keywords.in
401401
402-
api_version := "0.10.0"
402+
api_version := "0.11.0"
403403
framework_version := "0.1.0"
404404
405405
fragments := [...]
@@ -432,6 +432,7 @@ runtime_logging := data.framework.runtime_logging
432432
load_fragment := data.framework.load_fragment
433433
scratch_mount := data.framework.scratch_mount
434434
scratch_unmount := data.framework.scratch_unmount
435+
rw_mount_device := data.framework.rw_mount_device
435436
436437
reason := {"errors": data.framework.errors}
437438
```

src/confcom/azext_confcom/_help.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@
105105
type: boolean
106106
short-summary: 'When enabled, the default fragments are not included in the generated policy. This includes containers needed to mount azure files, mount secrets, mount git repos, and other common ACI features'
107107
108+
- name: --platform
109+
type: string
110+
short-summary: 'Target platform for policy generation (linux/amd64 or windows/amd64). Defaults to linux/amd64. Docker Desktop must be running in the matching container mode to produce correct layer hashes.'
111+
108112
examples:
109113
- name: Input an ARM Template file to inject a base64 encoded Confidential Container Security Policy into the ARM Template
110114
text: az confcom acipolicygen --template-file "./template.json"
@@ -116,6 +120,8 @@
116120
text: az confcom acipolicygen --template-file "./template.json" --tar "./image.tar"
117121
- name: Input an ARM Template file and use a fragments JSON file to generate a policy
118122
text: az confcom acipolicygen --template-file "./template.json" --fragments-json "./fragments.json" --include-fragments
123+
- name: Generate a Windows container policy (requires Docker Desktop in Windows containers mode)
124+
text: az confcom acipolicygen --template-file "./template.json" --platform windows/amd64 --outraw-pretty-print
119125
"""
120126

121127
helps[
@@ -340,7 +346,7 @@
340346
parameters:
341347
- name: --platform
342348
type: str
343-
short-summary: 'The name of the platform the container definition will run on'
349+
short-summary: 'The name of the platform the container definition will run on. Must be either "aci" or "vn2".'
344350
345351
346352
examples:

src/confcom/azext_confcom/_params.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,17 @@ def load_arguments(self, _):
114114
help="Image Name",
115115
validator=validate_aci_source
116116
)
117+
c.argument(
118+
"platform",
119+
options_list=("--platform",),
120+
required=False,
121+
default="linux/amd64",
122+
help="Target platform for policy generation. Defaults to linux/amd64. "
123+
"Note: Docker Desktop must be running in the matching container mode "
124+
"(Linux containers for linux/amd64, Windows containers for windows/amd64) "
125+
"to produce correct layer hashes.",
126+
choices=["linux/amd64", "windows/amd64"],
127+
)
117128
c.argument(
118129
"tar_mapping_location",
119130
options_list=("--tar",),
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"azext.minCliCoreVersion": "2.26.2"
3-
}
2+
"azext.minCliCoreVersion": "2.26.2",
3+
"azext.isPreview": true
4+
}

src/confcom/azext_confcom/command/containers_from_image.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
from azext_confcom.lib.containers import from_image as lib_containers_from_image
99

1010

11-
def containers_from_image(image: str, platform: str) -> None:
12-
print(json.dumps(lib_containers_from_image(image, platform)))
11+
def containers_from_image(image: str, aci_or_vn2: str) -> None:
12+
print(json.dumps(lib_containers_from_image(image, aci_or_vn2)))

src/confcom/azext_confcom/command/containers_from_vn2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def containers_from_vn2(
192192

193193
container_defs = []
194194
for template_container, template_doc in template_containers:
195-
image_container_def = container_from_image(template_container.get("image"), platform="vn2")
195+
image_container_def = container_from_image(template_container.get("image"), aci_or_vn2="vn2")
196196

197197
template_container_def = {
198198
"name": template_container.get("name"),

src/confcom/azext_confcom/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS_RULE = "pattern"
128128
POLICY_FIELD_CONTAINERS_ELEMENTS_REQUIRED = "required"
129129
POLICY_FIELD_CONTAINERS_ELEMENTS_LAYERS = "layers"
130+
POLICY_FIELD_CONTAINERS_ELEMENTS_MOUNTED_CIM = "mounted_cim"
130131
POLICY_FIELD_CONTAINERS_ELEMENTS_WORKINGDIR = "working_dir"
131132
POLICY_FIELD_CONTAINERS_ELEMENTS_MOUNTS = "mounts"
132133
POLICY_FIELD_CONTAINERS_ELEMENTS_MOUNTS_SOURCE = "source"
@@ -211,6 +212,7 @@
211212
DEFAULT_REGO_FRAGMENTS = _config["default_rego_fragments"]
212213
# things that need to be set for debug mode
213214
DEBUG_MODE_SETTINGS = _config["debugMode"]
215+
DEBUG_MODE_SETTINGS_WINDOWS = _config["debugModeWindows"]
214216
# reserved fragment names for existing pieces of Rego
215217
RESERVED_FRAGMENT_NAMES = _config["reserved_fragment_namespaces"]
216218
# fragment artifact type
@@ -227,6 +229,7 @@
227229
}
228230
"""
229231
CUSTOMER_REGO_POLICY = load_str_from_file(REGO_FILE_PATH)
232+
CUSTOMER_REGO_POLICY_WINDOWS = load_str_from_file(f"{script_directory}/data/customer_rego_policy_windows.txt")
230233
CUSTOMER_REGO_FRAGMENT = load_str_from_file(REGO_FRAGMENT_FILE_PATH)
231234
# sidecar rego file
232235
SIDECAR_REGO_FILE = "./data/sidecar_rego_policy.txt"

src/confcom/azext_confcom/container.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@ def from_json(
563563
mounts=mounts,
564564
allow_elevated=allow_elevated,
565565
extraEnvironmentRules=[],
566+
platform=container_json.get("platform", "linux/amd64"),
566567
execProcesses=exec_processes,
567568
signals=signals,
568569
user=user,
@@ -583,6 +584,7 @@ def __init__(
583584
allow_elevated: bool,
584585
id_val: str,
585586
extraEnvironmentRules: Dict,
587+
platform: str = "linux/amd64",
586588
entrypoint: List[str] = None,
587589
capabilities: Dict = copy.deepcopy(_CAPABILITIES),
588590
user: Dict = copy.deepcopy(_DEFAULT_USER),
@@ -604,6 +606,7 @@ def __init__(
604606
self._command = command
605607
self._workingDir = workingDir
606608
self._layers = []
609+
self._mounted_cim = []
607610
self._mounts = mounts
608611
self._allow_elevated = allow_elevated
609612
self._allow_stdio_access = allowStdioAccess
@@ -615,6 +618,7 @@ def __init__(
615618
self._exec_processes = execProcesses or []
616619
self._signals = signals or []
617620
self._extraEnvironmentRules = extraEnvironmentRules
621+
self._platform = platform
618622

619623
def get_policy_json(self, omit_id: bool = False) -> str:
620624
return self._populate_policy_json_elements(omit_id=omit_id)
@@ -658,6 +662,12 @@ def get_layers(self) -> List[str]:
658662
def set_layers(self, layers: List[str]) -> None:
659663
self._layers = layers
660664

665+
def get_mounted_cim(self) -> List[str]:
666+
return self._mounted_cim
667+
668+
def set_mounted_cim(self, mounted_cim: List[str]) -> None:
669+
self._mounted_cim = mounted_cim
670+
661671
def get_user(self) -> Dict:
662672
return self._user
663673

@@ -764,16 +774,27 @@ def _populate_policy_json_elements(self, omit_id: bool = False) -> Dict[str, Any
764774
config.POLICY_FIELD_CONTAINERS_ELEMENTS_ENVS: self._get_environment_rules(),
765775
config.POLICY_FIELD_CONTAINERS_ELEMENTS_WORKINGDIR: self._workingDir,
766776
config.POLICY_FIELD_CONTAINERS_ELEMENTS_MOUNTS: self._get_mounts_json(),
767-
config.POLICY_FIELD_CONTAINERS_ELEMENTS_ALLOW_ELEVATED: self._allow_elevated,
768777
config.POLICY_FIELD_CONTAINERS_ELEMENTS_EXEC_PROCESSES: self._exec_processes,
769778
config.POLICY_FIELD_CONTAINERS_ELEMENTS_SIGNAL_CONTAINER_PROCESSES: self._signals,
770-
config.POLICY_FIELD_CONTAINERS_ELEMENTS_USER: self.get_user(),
771-
config.POLICY_FIELD_CONTAINERS_ELEMENTS_CAPABILITIES: self._capabilities,
772-
config.POLICY_FIELD_CONTAINERS_ELEMENTS_SECCOMP_PROFILE_SHA256: self._seccomp_profile_sha256,
773779
config.POLICY_FIELD_CONTAINERS_ELEMENTS_ALLOW_STDIO_ACCESS: self._allow_stdio_access,
774-
config.POLICY_FIELD_CONTAINERS_ELEMENTS_NO_NEW_PRIVILEGES: not self._allow_privilege_escalation
775780
}
776781

782+
if self._platform.startswith("linux"):
783+
elements.update({
784+
config.POLICY_FIELD_CONTAINERS_ELEMENTS_CAPABILITIES: self._capabilities,
785+
config.POLICY_FIELD_CONTAINERS_ELEMENTS_SECCOMP_PROFILE_SHA256: self._seccomp_profile_sha256,
786+
config.POLICY_FIELD_CONTAINERS_ELEMENTS_USER: self.get_user(),
787+
config.POLICY_FIELD_CONTAINERS_ELEMENTS_ALLOW_ELEVATED: self._allow_elevated,
788+
config.POLICY_FIELD_CONTAINERS_ELEMENTS_NO_NEW_PRIVILEGES: not self._allow_privilege_escalation,
789+
})
790+
elif self._platform.startswith("windows"):
791+
elements.update({
792+
config.POLICY_FIELD_CONTAINERS_ELEMENTS_USER: self.get_user()["user_idname"]["pattern"],
793+
})
794+
# Add mounted_cim for Windows if present
795+
if self._mounted_cim:
796+
elements[config.POLICY_FIELD_CONTAINERS_ELEMENTS_MOUNTED_CIM] = self._mounted_cim
797+
777798
if not omit_id:
778799
elements[config.POLICY_FIELD_CONTAINERS_ID] = self._identifier
779800
# if we are omitting the id, we should remove the id value from the policy if it's in the name field
@@ -793,13 +814,17 @@ def from_json(
793814
image.__class__ = UserContainerImage
794815
# inject default mounts for user container
795816
if (image.base not in config.BASELINE_SIDECAR_CONTAINERS) and (not is_vn2):
796-
image.get_mounts().extend(_DEFAULT_MOUNTS)
817+
if container_json.get("platform", "linux/amd64").startswith("linux"):
818+
image.get_mounts().extend(_DEFAULT_MOUNTS)
797819

798820
if (image.base not in config.BASELINE_SIDECAR_CONTAINERS) and (is_vn2):
799821
image.get_mounts().extend(_DEFAULT_MOUNTS_VN2)
800822

801823
# Start with the customer environment rules
802-
env_rules = copy.deepcopy(_INJECTED_CUSTOMER_ENV_RULES)
824+
env_rules = (
825+
copy.deepcopy(_INJECTED_CUSTOMER_ENV_RULES)
826+
if container_json.get("platform", "linux/amd64").startswith("linux") else []
827+
)
803828
# If is_vn2, add the VN2 environment rules
804829
if is_vn2:
805830
env_rules += _INJECTED_SERVICE_VN2_ENV_RULES

0 commit comments

Comments
 (0)