Skip to content

Commit 08a57f2

Browse files
committed
Add a --with-containers flag to policy and fragment gen
1 parent d51bb46 commit 08a57f2

File tree

6 files changed

+188
-7
lines changed

6 files changed

+188
-7
lines changed

src/confcom/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,5 @@ azext_confcom/bin/*
3636
**/.coverage
3737

3838
**/htmlcov
39+
40+
!lib/

src/confcom/azext_confcom/_params.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,14 @@ def load_arguments(self, _):
198198
required=False,
199199
help="Exclude default fragments in the generated policy",
200200
)
201+
c.argument(
202+
"container_definitions",
203+
options_list=['--with-containers'],
204+
action='append',
205+
required=False,
206+
default=[],
207+
help='Container definitions to include in the policy'
208+
)
201209

202210
with self.argument_context("confcom acifragmentgen") as c:
203211
c.argument(
@@ -345,6 +353,14 @@ def load_arguments(self, _):
345353
help="Path to JSON file to write fragment import information. This is used with --generate-import. If not specified, the import statement will print to the console",
346354
validator=validate_fragment_json,
347355
)
356+
c.argument(
357+
"container_definitions",
358+
options_list=['--with-containers'],
359+
action='append',
360+
required=False,
361+
default=[],
362+
help='Container definitions to include in the policy'
363+
)
348364

349365
with self.argument_context("confcom katapolicygen") as c:
350366
c.argument(

src/confcom/azext_confcom/_validators.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ def validate_aci_source(namespace):
3838
namespace.input_path,
3939
namespace.arm_template,
4040
namespace.image_name,
41-
namespace.virtual_node_yaml_path
41+
namespace.virtual_node_yaml_path,
42+
namespace.container_definitions != [],
4243
])) != 1:
4344
raise CLIError("Can only generate CCE policy from one source at a time")
4445

@@ -71,7 +72,11 @@ def validate_fragment_key_and_chain(namespace):
7172

7273

7374
def validate_fragment_source(namespace):
74-
if not namespace.generate_import and sum(map(bool, [namespace.image_name, namespace.input_path])) != 1:
75+
if not namespace.generate_import and sum(map(bool, [
76+
namespace.image_name,
77+
namespace.input_path,
78+
namespace.container_definitions != [],
79+
])) != 1:
7580
raise CLIError("Must provide either an image name or an input file to generate a fragment")
7681

7782

src/confcom/azext_confcom/custom.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
from azext_confcom._validators import resolve_stdio
1212
from azext_confcom.config import (
1313
DEFAULT_REGO_FRAGMENTS, POLICY_FIELD_CONTAINERS_ELEMENTS_REGO_FRAGMENTS,
14-
REGO_IMPORT_FILE_STRUCTURE)
14+
REGO_IMPORT_FILE_STRUCTURE, ACI_FIELD_VERSION, ACI_FIELD_CONTAINERS)
1515
from azext_confcom.cose_proxy import CoseSignToolProxy
1616
from azext_confcom.errors import eprint
1717
from azext_confcom.fragment_util import get_all_fragment_contents
1818
from azext_confcom.init_checks import run_initial_docker_checks
1919
from azext_confcom.kata_proxy import KataPolicyGenProxy
20-
from azext_confcom.security_policy import OutputType
20+
from azext_confcom.security_policy import AciPolicy, OutputType
2121
from azext_confcom.template_util import (
2222
get_image_name, inject_policy_into_template, inject_policy_into_yaml,
2323
pretty_print_func, print_existing_policy_from_arm_template,
@@ -37,6 +37,7 @@ def acipolicygen_confcom(
3737
virtual_node_yaml_path: str,
3838
infrastructure_svn: str,
3939
tar_mapping_location: str,
40+
container_definitions: list,
4041
approve_wildcards: str = False,
4142
outraw: bool = False,
4243
outraw_pretty_print: bool = False,
@@ -147,6 +148,16 @@ def acipolicygen_confcom(
147148
exclude_default_fragments=exclude_default_fragments,
148149
infrastructure_svn=infrastructure_svn,
149150
)
151+
elif container_definitions:
152+
container_group_policies = AciPolicy(
153+
{
154+
ACI_FIELD_VERSION: "1.0",
155+
ACI_FIELD_CONTAINERS: [],
156+
},
157+
debug_mode=debug_mode,
158+
disable_stdio=disable_stdio,
159+
container_definitions=container_definitions,
160+
)
150161

151162
exit_code = 0
152163

@@ -227,6 +238,7 @@ def acifragmentgen_confcom(
227238
key: str,
228239
chain: str,
229240
minimum_svn: str,
241+
container_definitions: list,
230242
image_target: str = "",
231243
algo: str = "ES384",
232244
fragment_path: str = None,
@@ -299,13 +311,24 @@ def acifragmentgen_confcom(
299311
policy = security_policy.load_policy_from_image_name(
300312
image_name, debug_mode=debug_mode, disable_stdio=(not stdio_enabled)
301313
)
302-
else:
314+
elif input_path:
303315
# this is using --input
304316
if not tar_mapping:
305317
tar_mapping = os_util.load_tar_mapping_from_config_file(input_path)
306318
policy = security_policy.load_policy_from_json_file(
307319
input_path, debug_mode=debug_mode, disable_stdio=(not stdio_enabled)
308320
)
321+
elif container_definitions:
322+
policy = AciPolicy(
323+
{
324+
ACI_FIELD_VERSION: "1.0",
325+
ACI_FIELD_CONTAINERS: [],
326+
},
327+
debug_mode=debug_mode,
328+
disable_stdio=disable_stdio,
329+
container_definitions=container_definitions,
330+
)
331+
309332
# get all of the fragments that are being used in the policy
310333
# and associate them with each container group
311334
fragment_policy_list = []
@@ -321,7 +344,7 @@ def acifragmentgen_confcom(
321344

322345
# make sure we have images to generate a fragment
323346
policy_images = policy.get_images()
324-
if not policy_images:
347+
if not policy_images and not container_definitions:
325348
eprint("No images found in the policy or all images are covered by fragments")
326349

327350
if not feed:
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from dataclasses import dataclass, field, is_dataclass
7+
import inspect
8+
import sys
9+
from typing import Literal, Optional
10+
11+
12+
def get_default_capabilities():
13+
return [
14+
"CAP_AUDIT_WRITE",
15+
"CAP_CHOWN",
16+
"CAP_DAC_OVERRIDE",
17+
"CAP_FOWNER",
18+
"CAP_FSETID",
19+
"CAP_KILL",
20+
"CAP_MKNOD",
21+
"CAP_NET_BIND_SERVICE",
22+
"CAP_NET_RAW",
23+
"CAP_SETFCAP",
24+
"CAP_SETGID",
25+
"CAP_SETPCAP",
26+
"CAP_SETUID",
27+
"CAP_SYS_CHROOT"
28+
]
29+
30+
@dataclass
31+
class ContainerCapabilities:
32+
ambient: list[str] = field(default_factory=list)
33+
bounding: list[str] = field(default_factory=get_default_capabilities)
34+
effective: list[str] = field(default_factory=get_default_capabilities)
35+
inheritable: list[str] = field(default_factory=list)
36+
permitted: list[str] = field(default_factory=get_default_capabilities)
37+
38+
39+
@dataclass
40+
class ContainerRule:
41+
pattern: str
42+
strategy: str
43+
required: Optional[bool] = False
44+
45+
46+
@dataclass
47+
class ContainerExecProcesses:
48+
command: list[str]
49+
signals: Optional[list[str]] = None
50+
allow_stdio_access: bool = True
51+
52+
53+
@dataclass
54+
class ContainerMount:
55+
destination: str
56+
source: str
57+
type: str
58+
options: list[str] = field(default_factory=list)
59+
60+
61+
@dataclass
62+
class ContainerUser:
63+
group_idnames: list[ContainerRule] = field(default_factory=lambda: [ContainerRule(pattern="", strategy="any")])
64+
umask: str = "0022"
65+
user_idname: ContainerRule = field(default_factory=lambda: ContainerRule(pattern="", strategy="any"))
66+
67+
68+
@dataclass
69+
class FragmentReference:
70+
feed: str
71+
issuer: str
72+
minimum_svn: str
73+
includes: list[Literal["containers", "fragments", "namespace", "external_processes"]]
74+
path: Optional[str] = None
75+
76+
77+
@dataclass
78+
class Container:
79+
allow_elevated: bool = False
80+
allow_stdio_access: bool = True
81+
capabilities: ContainerCapabilities = field(default_factory=ContainerCapabilities)
82+
command: Optional[list[str]] = None
83+
env_rules: list[ContainerRule] = field(default_factory=list)
84+
exec_processes: list[ContainerExecProcesses] = field(default_factory=list)
85+
id: Optional[str] = None
86+
layers: list[str] = field(default_factory=list)
87+
mounts: list[ContainerMount] = field(default_factory=list)
88+
name: Optional[str] = None,
89+
no_new_privileges: bool = False
90+
seccomp_profile_sha256: str = ""
91+
signals: list[str] = field(default_factory=list)
92+
user: ContainerUser = field(default_factory=ContainerUser)
93+
working_dir: str = "/"
94+
95+
96+
@dataclass
97+
class Policy:
98+
package: str = "policy"
99+
api_version: str = "0.10.0"
100+
framework_version: str = "0.2.3"
101+
fragments: list[FragmentReference] = field(default_factory=list)
102+
containers: list[Container] = field(default_factory=list)
103+
allow_properties_access: bool = True
104+
allow_dump_stacks: bool = False
105+
allow_runtime_logging: bool = False
106+
allow_environment_variable_dropping: bool = True
107+
allow_unencrypted_scratch: bool = False
108+
allow_capability_dropping: bool = True
109+
110+
111+
@dataclass
112+
class Fragment:
113+
package: str = "fragment"
114+
svn: str = "0"
115+
framework_version: str = "0.2.3"
116+
fragments: list[FragmentReference] = field(default_factory=list)
117+
containers: list[Container] = field(default_factory=list)
118+
119+
120+
POLICY_CLASSES = [
121+
cls
122+
for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass)
123+
if is_dataclass(cls) and cls.__module__ == sys.modules[__name__].__name__
124+
]

src/confcom/azext_confcom/security_policy.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
# --------------------------------------------------------------------------------------------
55

66
import copy
7+
from dataclasses import asdict
78
import json
9+
import tempfile
810
import warnings
911
from enum import Enum, auto
10-
from typing import Any, Dict, List, Tuple, Union
12+
from typing import Any, Dict, List, Optional, Tuple, Union
1113

1214
import deepdiff
1315
from azext_confcom import config, os_util
16+
from azext_confcom.lib.policy import Container
1417
from azext_confcom.container import ContainerImage, UserContainerImage
1518
from azext_confcom.errors import eprint
1619
from azext_confcom.fragment_util import sanitize_fragment_fields
@@ -65,6 +68,7 @@ def __init__(
6568
disable_stdio: bool = False,
6669
is_vn2: bool = False,
6770
fragment_contents: Any = None,
71+
container_definitions: Optional[list] = None,
6872
) -> None:
6973
self._rootfs_proxy = None
7074
self._policy_str = None
@@ -74,6 +78,7 @@ def __init__(
7478
self._existing_fragments = existing_rego_fragments
7579
self._api_version = config.API_VERSION
7680
self._fragment_contents = fragment_contents
81+
self._container_definitions = container_definitions or []
7782

7883
if debug_mode:
7984
self._allow_properties_access = config.DEBUG_MODE_SETTINGS.get(
@@ -399,6 +404,12 @@ def _policy_serialization(self, pretty_print=False, include_sidecars: bool = Tru
399404
for container in policy:
400405
container[config.POLICY_FIELD_CONTAINERS_ELEMENTS_ALLOW_STDIO_ACCESS] = False
401406

407+
if self._container_definitions:
408+
policy += [
409+
asdict(Container(**json.loads(c)))
410+
for c in self._container_definitions
411+
]
412+
402413
if pretty_print:
403414
return pretty_print_func(policy)
404415
return print_func(policy)

0 commit comments

Comments
 (0)