Skip to content

Commit

Permalink
Merge pull request #7876 from Mab879/load_controls_from_folder
Browse files Browse the repository at this point in the history
Add the ability to load controls from folder
  • Loading branch information
jan-cerny committed Nov 16, 2021
2 parents 0fa42ad + e684458 commit 1bab60a
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 16 deletions.
50 changes: 50 additions & 0 deletions docs/manual/developer/03_creating_content.md
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,56 @@ Each of the values under the `rules` key maps onto a rule identifier in the
project. In the future, we could automatically assign references to rules via
this control file.

To help control length of control files content authors can create a directory with same name as the control file (without `.yml`) and add YAML files to that folder.
Then in the folder the author can crate `.yml` files for the controls.
See the example below.

```
$ cat controls/abcd.yml
id: abcd
title: ABCD Benchmark for securing Linux systems
version: 1.2.3
source: https://www.abcd.com/linux.pdf
```

```
$ cat controls/abcd/R1.yml
controls:
- id: R1
title: User session timeout
description: |-
Remote user sessions must be closed after a certain
period of inactivity.
status: automated
rules:
- sshd_set_idle_timeout
- accounts_tmout
- var_accounts_tmout=10_min
{{% if product == "rhel9" %}}
- cockpit_session_timeout
{{% endif %}}
```

```
$ cat controls/abcd/R2.yml
controls:
- id: R2
title: Minimization of configuration
description: |-
The features configured at the level of launched services
should be limited to the strict minimum.
status: supported
note: |-
This is individual depending on the system workload
therefore needs to be audited manually.
related_rules:
- systemd_target_multi_user
```


### Defining levels

Some real world policies, e.g., ANSSI, have a concept of levels.
Expand Down
13 changes: 13 additions & 0 deletions ssg/controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def __init__(self, filepath, env_yaml=None):
self.id = None
self.env_yaml = env_yaml
self.filepath = filepath
self.controls_dir = os.path.splitext(filepath)[0]
self.controls = []
self.controls_by_id = dict()
self.levels = []
Expand Down Expand Up @@ -170,6 +171,18 @@ def load(self):
self.levels_by_id[level.id] = level

controls_tree = ssg.utils.required_key(yaml_contents, "controls")
if os.path.exists(self.controls_dir) and os.path.isdir(self.controls_dir):
files = os.listdir(self.controls_dir)
for file in files:
if file.endswith('.yml'):
full_path = os.path.join(self.controls_dir, file)
yaml_contents = ssg.yaml.open_and_expand(full_path, self.env_yaml)
for control in yaml_contents['controls']:
controls_tree.append(control)
elif file.startswith('.'):
continue
else:
raise RuntimeError("Found non yaml file in %s" % self.controls_dir)
for c in self._parse_controls_tree(controls_tree):
self.controls.append(c)
self.controls_by_id[c.id] = c
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/ssg-module/data/controls_dir/jklm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
policy: JKLM Benchmark for securing Linux systems
title: JKLM Benchmark for securing Linux systems
id: jklm
version: 1.2.3
source: https://www.example.com/jklm/linux.pdf
controls:
- id: R2
title: Minimization of configuration
description: >-
The features configured at the level of launched services
should be limited to the strict minimum.
automated: no
note: >-
This is individual depending on the system workload
therefore needs to be audited manually.
related_rules:
- systemd_target_multi_use
- id: R4
title: Configure authentication
description: >-
Ensure authentication methods are functional to prevent
unauthorized access to the system.
controls:
- id: R4.a
title: Disable administrator accounts
automated: yes
rules:
- accounts_passwords_pam_faillock_deny_root
- id: R4.b
title: Enforce password quality standards
automated: yes
rules:
- accounts_password_pam_minlen
- accounts_password_pam_ocredit
- var_password_pam_ocredit=1
14 changes: 14 additions & 0 deletions tests/unit/ssg-module/data/controls_dir/jklm/r1.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
controls:
- id: R1
title: User session timeout
description: >-
Remote user sessions must be closed after a certain
period of inactivity.
automated: yes
rules:
- sshd_set_idle_timeout
- accounts_tmout
- var_accounts_tmout=10_min
- configure_crypto_policy
notes: >-
Certain period of inactivity is vague.
7 changes: 7 additions & 0 deletions tests/unit/ssg-module/data/controls_dir/jklm/r3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
controls:
- id: R3
title: Enabling SELinux targeted Policy
description: >-
It is recommended to enable SELinux in enforcing mode
and to use the targeted policy.
automated: yes
30 changes: 14 additions & 16 deletions tests/unit/ssg-module/test_controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,42 +13,34 @@
profiles_dir = os.path.join(data_dir, "profiles_dir")


def test_controls_load():
def _load_test(profile):
product_yaml = os.path.join(ssg_root, "products", "rhel8", "product.yml")
build_config_yaml = os.path.join(ssg_root, "build", "build_config.yml")
env_yaml = open_environment(build_config_yaml, product_yaml)
controls_manager = ssg.controls.ControlsManager(controls_dir, env_yaml)
controls_manager.load()

c_r1 = controls_manager.get_control("abcd", "R1")
c_r1 = controls_manager.get_control(profile, "R1")
assert c_r1.title == "User session timeout"
assert c_r1.description == "Remote user sessions must be closed after " \
"a certain period of inactivity."
"a certain period of inactivity."
assert c_r1.automated == "yes"

c_r1_rules = c_r1.selected
assert "sshd_set_idle_timeout" in c_r1_rules
assert "accounts_tmout" in c_r1_rules
assert "var_accounts_tmout=10_min" not in c_r1_rules
assert "var_accounts_tmout" in c_r1.variables
assert c_r1.variables["var_accounts_tmout"] == "10_min"

# abcd is a level-less policy
assert c_r1.levels == ["default"]

assert "vague" in c_r1.notes

c_r2 = controls_manager.get_control("abcd", "R2")
c_r2 = controls_manager.get_control(profile, "R2")
assert c_r2.automated == "no"
assert c_r2.note == "This is individual depending on the system " \
"workload therefore needs to be audited manually."
"workload therefore needs to be audited manually."
assert len(c_r2.selected) == 0

assert not c_r2.notes

c_r4 = controls_manager.get_control("abcd", "R4")
c_r4 = controls_manager.get_control(profile, "R4")
assert len(c_r4.selected) == 3

c_r4_rules = c_r4.selected
assert "accounts_passwords_pam_faillock_deny_root" in c_r4_rules
assert "accounts_password_pam_minlen" in c_r4_rules
Expand All @@ -57,6 +49,10 @@ def test_controls_load():
assert c_r4.variables["var_password_pam_ocredit"] == "1"


def test_controls_load():
_load_test("abcd")


def test_controls_levels():
product_yaml = os.path.join(ssg_root, "products", "rhel8", "product.yml")
build_config_yaml = os.path.join(ssg_root, "build", "build_config.yml")
Expand Down Expand Up @@ -133,8 +129,6 @@ def test_controls_levels():
assert s2_low[0].variables["var_password_pam_minlen"] == "1"




def test_controls_load_product():
product_yaml = os.path.join(ssg_root, "products", "rhel8", "product.yml")
build_config_yaml = os.path.join(ssg_root, "build", "build_config.yml")
Expand Down Expand Up @@ -284,3 +278,7 @@ def profile_resolution_all(cls, profile_all):
# by profile selections, not by using controls, so it should be in
# the resolved profile as well.
assert "security_patches_up_to_date" in selected


def test_load_control_from_folder():
_load_test("jklm")

0 comments on commit 1bab60a

Please sign in to comment.