Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat to enable multiple SCPs #735

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ def __init__(self):
pass

@staticmethod
def _find_all(policy):
def _find_all_paths(policy):
_files = list(
glob.iglob(
f"./adf-bootstrap/**/{policy}.json",
f"./adf-bootstrap/**/{policy}*.json",
recursive=True,
)
)
Expand All @@ -40,12 +40,16 @@ def _compare_ordered_policy(self, obj):
return obj

@staticmethod
def _trim_scp_file_name(policy):
return policy[1:][:-8] if policy[1:][:-8] == "/" else policy[2:][:-9]

@staticmethod
def _trim_tagging_policy_file_name(policy):
return policy[1:][:-19] if policy[1:][:-19] == "/" else policy[2:][:-20]
def _trim_policy_file_name(policy):
# Returns policy target and filename as tuple
policy_filename = policy.split('/')[-1]
# `policy` should be formatted "./{target/path/here}/{policy_filename.json}"
# Target is defined by {target/path/here}
policy_target = '/'.join(policy.split('/')[1:-1])
if policy_target == '':
# if {target/path/here} is empty, target is root
policy_target = '/'
return policy_target, policy_filename

@staticmethod
def _is_govcloud(region: str) -> bool:
Expand All @@ -57,6 +61,16 @@ def _is_govcloud(region: str) -> bool:
"""
return region.startswith("us-gov")

@staticmethod
def return_policy_name(policy_type, target_path, policy_filename):
_type = 'scp' if policy_type == "SERVICE_CONTROL_POLICY" else 'tagging-policy'
if policy_filename != f'{_type}.json':
#filter the policy name to remove the .json extension
policy_filename = policy_filename.split('.')[0]
return f'adf-{_type}-{target_path}-{policy_filename}'
# Added for backwards-compatibility with previous versions of ADF
return f'adf-{_type}-{target_path}'

@staticmethod
def set_scp_attachment(access_identifier, organization_mapping, path, organizations):
if access_identifier:
Expand Down Expand Up @@ -85,10 +99,12 @@ def set_scp_attachment(access_identifier, organization_mapping, path, organizati

@staticmethod
def clean_and_remove_policy_attachment(
organization_mapping, path, organizations, policy_type
organization_mapping, path, policy_filename, organizations, policy_type
):
policy_name = OrganizationPolicy.return_policy_name(policy_type, path, policy_filename)
policy_id = organizations.describe_policy_id_for_target(
organization_mapping[path],
policy_name,
policy_type,
)
if policy_type == "SERVICE_CONTROL_POLICY":
Expand All @@ -99,14 +115,11 @@ def clean_and_remove_policy_attachment(
)
except organizations.client.exceptions.DuplicatePolicyAttachmentException:
pass
organizations.detach_policy(policy_id, organization_mapping[path])
organizations.delete_policy(policy_id)
LOGGER.info(
"Policy (%s) %s will be deleted. Path is: %s",
policy_type,
organization_mapping[path],
path,
)
if policy_id:
organizations.detach_policy(policy_id, organization_mapping[path])
organizations.delete_policy(policy_id)
LOGGER.info('Policy (%s) %s will be deleted. Name is %s',
policy_type, organization_mapping[path], policy_name)

def apply(
self, organizations, parameter_store, config
Expand All @@ -130,73 +143,81 @@ def apply(
}
)

supported_policies = ["scp", "tagging-policy"]
supported_policies = ["SERVICE_CONTROL_POLICY", "TAG_POLICY"]

if self._is_govcloud(REGION_DEFAULT):
supported_policies = ["scp"]
supported_policies = ["SERVICE_CONTROL_POLICY"]

for policy in supported_policies:
_type = "SERVICE_CONTROL_POLICY" if policy == "scp" else "TAG_POLICY"
organizations.enable_organization_policies(_type)
_policies = OrganizationPolicy._find_all(policy)
for policy_type in supported_policies:
_type = 'scp' if policy_type == "SERVICE_CONTROL_POLICY" else 'tagging-policy'
organizations.enable_organization_policies(policy_type)
policy_paths = OrganizationPolicy._find_all_paths(_type)
LOGGER.info(
"Policy_paths are %s",
policy_paths
)
try:
current_stored_policy = ast.literal_eval(
parameter_store.fetch_parameter(policy.replace('-', '_'))
parameter_store.fetch_parameter(_type)
)
LOGGER.info(
"current_stored_policy for type %s is: %s",
_type,
current_stored_policy,
)

for stored_policy in current_stored_policy:
path = (
OrganizationPolicy._trim_scp_file_name(stored_policy)
if policy == "scp"
else OrganizationPolicy._trim_tagging_policy_file_name(
stored_policy
)
)
path, policy_filename = OrganizationPolicy._trim_policy_file_name(stored_policy)
OrganizationPolicy.set_scp_attachment(
config.get("scp"), organization_mapping, path, organizations
)
if stored_policy not in _policies:
if stored_policy not in policy_paths:
path, policy_filename = OrganizationPolicy._trim_policy_file_name(
stored_policy
)
OrganizationPolicy.clean_and_remove_policy_attachment(
organization_mapping,
path,
policy_filename,
organizations,
_type,
policy_type
)
except ParameterNotFoundError:
LOGGER.debug(
"Parameter %s was not found in Parameter Store, continuing.",
policy,
_type,
)

for _policy in _policies:
path = (
OrganizationPolicy._trim_scp_file_name(_policy)
if policy == "scp"
else OrganizationPolicy._trim_tagging_policy_file_name(
_policy,
)
for policy_path in policy_paths:
path, policy_filename = OrganizationPolicy._trim_policy_file_name(policy_path)
policy_name = OrganizationPolicy.return_policy_name(
policy_type,
path,
policy_filename
)
policy_id = organizations.describe_policy_id_for_target(
organization_mapping[path],
_type,
policy_name,
policy_type
)
proposed_policy = Organizations.get_policy_body(_policy)
proposed_policy = Organizations.get_policy_body(policy_path)
if policy_id:
current_policy = organizations.describe_policy(policy_id)
if self._compare_ordered_policy(
current_policy.get("Content")
) == self._compare_ordered_policy(proposed_policy):
LOGGER.info(
"Policy (%s) %s does not require updating. Path is: %s",
policy,
"Policy (%s) %s does not require updating. Name is: %s",
_type,
organization_mapping[path],
path,
policy_name,
)
continue
LOGGER.info(
"Policy (%s) will be updated for %s. Path is: %s",
policy,
"Policy (%s) will be updated for %s. Name is: %s",
_type,
organization_mapping[path],
path,
policy_name,
)
organizations.update_policy(
proposed_policy,
Expand All @@ -206,39 +227,36 @@ def apply(
try:
policy_id = organizations.create_policy(
proposed_policy,
path,
_type,
policy_name,
policy_type
)
LOGGER.info(
"Policy (%s) has been created for %s. Path is: %s",
policy,
"Policy (%s) has been created for %s. Name is: %s",
_type,
organization_mapping[path],
path,
policy_name
)
organizations.attach_policy(
policy_id,
organization_mapping[path],
)
except organizations.client.exceptions.DuplicatePolicyAttachmentException:
LOGGER.info(
"Policy (%s) for %s exists and is attached already.",
policy,
"Policy (%s) for %s with name %s exists and is attached already.",
_type,
organization_mapping[path],
policy_name
)
except organizations.client.exceptions.DuplicatePolicyException:
LOGGER.info(
"Policy (%s) for %s exists ensuring attached.",
policy,
"Policy (%s) for %s with name %s exists ensuring attached.",
_type,
organization_mapping[path],
policy_name
)
policy_id = organizations.list_policies(
f"adf-{policy}-{path}",
_type,
policy_name,
_type
)
organizations.attach_policy(policy_id, organization_mapping[path])
parameter_store.put_parameter(
# Make the key consistently use underscores instead of dashes.
# So tagging-policy gets changed into tagging_policy.
policy.replace('-', '_'),
str(_policies),
)
parameter_store.put_parameter(_type, str(policy_paths))
Original file line number Diff line number Diff line change
Expand Up @@ -177,16 +177,14 @@ def update_policy(self, content, policy_id):
def create_policy(
self,
content,
ou_path,
policy_name,
policy_type="SERVICE_CONTROL_POLICY",
):
policy_type_name = (
"scp" if policy_type == "SERVICE_CONTROL_POLICY" else "tagging-policy"
)

response = self.client.create_policy(
Content=content,
Description=f"ADF Managed {policy_type}",
Name=f"adf-{policy_type_name}-{ou_path}",
Name=policy_name,
Type=policy_type,
)
return response["Policy"]["PolicySummary"]["Id"]
Expand All @@ -207,19 +205,19 @@ def list_policies(self, name, policy_type="SERVICE_CONTROL_POLICY"):
def describe_policy_id_for_target(
self,
target_id,
policy_name,
policy_type="SERVICE_CONTROL_POLICY",
):
response = self.client.list_policies_for_target(
TargetId=target_id, Filter=policy_type
)
adf_managed_policies = [
policy
for policy in response["Policies"]
if f"ADF Managed {policy_type}" in policy["Description"]
]
if adf_managed_policies:
return adf_managed_policies[0]["Id"]
return []
try:
return [
p for p in response['Policies']
if f'ADF Managed {policy_type}' in p['Description'] and p['Name'] == policy_name
][0]['Id']
except IndexError:
return []

def describe_policy(self, policy_id):
response = self.client.describe_policy(
Expand Down