55import collections
66import os
77import copy
8+ import sys
89from glob import glob
10+ from typing import List , Dict
911
1012import ssg .entities .common
1113import ssg .yaml
@@ -369,6 +371,7 @@ class Policy(ssg.entities.common.XCCDFEntity):
369371 product (list): A list of products associated with the policy.
370372 """
371373 def __init__ (self , filepath , env_yaml = None ):
374+ self .controls_dirs = []
372375 self .id = None
373376 self .env_yaml = env_yaml
374377 self .filepath = filepath
@@ -637,6 +640,7 @@ def load(self):
637640 controls_dir = yaml_contents .get ("controls_dir" )
638641 if controls_dir :
639642 self .controls_dir = os .path .join (os .path .dirname (self .filepath ), controls_dir )
643+ self .controls_dirs = [self .controls_dir ]
640644 self .id = ssg .utils .required_key (yaml_contents , "id" )
641645 self .policy = ssg .utils .required_key (yaml_contents , "policy" )
642646 self .title = ssg .utils .required_key (yaml_contents , "title" )
@@ -786,17 +790,17 @@ def add_references(self, rules):
786790 control .add_references (self .reference_type , rules )
787791
788792
789- class ControlsManager () :
793+ class ControlsManager :
790794 """
791795 Manages the loading, processing, and saving of control policies.
792796
793797 Attributes:
794- controls_dir ( str): The directory where control policy files are located.
798+ controls_dirs (List[ str] ): The directories where control policy files are located.
795799 env_yaml (str, optional): The environment YAML file.
796800 existing_rules (dict, optional): Existing rules to check against.
797801 policies (dict): A dictionary of loaded policies.
798802 """
799- def __init__ (self , controls_dir , env_yaml = None , existing_rules = None ):
803+ def __init__ (self , controls_dirs : List [ str ] , env_yaml = None , existing_rules = None ):
800804 """
801805 Initializes the Controls class.
802806
@@ -805,19 +809,25 @@ def __init__(self, controls_dir, env_yaml=None, existing_rules=None):
805809 env_yaml (str, optional): Path to the environment YAML file. Defaults to None.
806810 existing_rules (dict, optional): Dictionary of existing rules. Defaults to None.
807811 """
808- self .controls_dir = os .path .abspath (controls_dir )
812+ self .controls_dirs = [ os .path .abspath (controls_dir ) for controls_dir in controls_dirs ]
809813 self .env_yaml = env_yaml
810814 self .existing_rules = existing_rules
811815 self .policies = {}
812816
813817 def _load (self , format ):
814- if not os .path .exists (self .controls_dir ):
815- return
816- for filename in sorted (glob (os .path .join (self .controls_dir , "*." + format ))):
817- filepath = os .path .join (self .controls_dir , filename )
818- policy = Policy (filepath , self .env_yaml )
819- policy .load ()
820- self .policies [policy .id ] = policy
818+ for controls_dir in self .controls_dirs :
819+ if not os .path .isdir (controls_dir ):
820+ continue
821+ for filepath in sorted (glob (os .path .join (controls_dir , "*." + format ))):
822+ policy = Policy (filepath , self .env_yaml )
823+ policy .load ()
824+ if policy .id in self .policies :
825+ print (f"Policy { policy .id } was defined first at "
826+ f"{ self .policies [policy .id ].filepath } and now another policy "
827+ f"with the same ID is being loaded from { policy .filepath } ."
828+ f"Overriding with later." ,
829+ file = sys .stderr )
830+ self .policies [policy .id ] = policy
821831 self .check_all_rules_exist ()
822832 self .resolve_controls ()
823833
@@ -948,7 +958,7 @@ def get_control(self, policy_id, control_id):
948958 control = policy .get_control (control_id )
949959 return control
950960
951- def get_all_controls_dict (self , policy_id ) :
961+ def get_all_controls_dict (self , policy_id : str ) -> Dict [ str , Control ] :
952962 """
953963 Retrieve all controls for a given policy as a dictionary.
954964
@@ -959,7 +969,6 @@ def get_all_controls_dict(self, policy_id):
959969 Dict[str, list]: A dictionary where the keys are control IDs and the values are lists
960970 of controls.
961971 """
962- # type: (str) -> typing.Dict[str, list]
963972 policy = self ._get_policy (policy_id )
964973 return policy .controls_by_id
965974
0 commit comments