Skip to content

Commit

Permalink
Merge pull request #14 from salesforce/fix/GH-9-include-policy-assign…
Browse files Browse the repository at this point in the history
…ment

Fixes #10 - command line option changes
  • Loading branch information
kmcquade authored Mar 12, 2021
2 parents d2a846a + c8c9f01 commit aa1f06e
Show file tree
Hide file tree
Showing 21 changed files with 582 additions and 239 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@ count-loc:
echo "Website: https://github.com/XAMPPRocky/tokei#installation'"
tokei ./* --exclude --exclude '**/*.html' --exclude '**/*.json'

LOCAL_MODULE_SOURCE := "../../azure_guardrails/shared/terraform/policy-initiative-with-builtins"
MODULE_SOURCE := "../../azure_guardrails/shared/terraform/policy-initiative-with-builtins"

.PHONY: terraform-demo
terraform-demo: install
azure-guardrails --help
azure-guardrails generate-terraform --module-source ${LOCAL_MODULE_SOURCE} --service all --quiet > examples/terraform-demo/main.tf
azure-guardrails generate-terraform --module-source ${MODULE_SOURCE} --service all --subscription-name example > examples/terraform-demo/main.tf

.PHONY: update-policy-table
update-policy-table: install
Expand Down
240 changes: 125 additions & 115 deletions azure_guardrails/command/generate_terraform.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
"""
Generate Terraform for the Azure Policies
"""
import os
import logging
import json
from pathlib import Path
import yaml
import click
from colorama import Fore, Back
from click_option_group import optgroup, RequiredMutuallyExclusiveOptionGroup
from azure_guardrails import set_log_level, set_stream_logger
from azure_guardrails.terraform.terraform import get_terraform_template, TerraformTemplate
from azure_guardrails.shared import utils, validate
Expand All @@ -22,119 +18,126 @@


@click.command(name="generate-terraform", short_help="")
@click.option(
@optgroup.group("Azure Policy selection", help="")
@optgroup.option(
"--service",
"-s",
type=click.Choice(supported_services_argument_values),
type=str,
# type=click.Choice(supported_services_argument_values),
required=True,
default="all",
help="Services supported by Azure Policy definitions. Set to 'all' for all policies",
callback=validate.click_validate_supported_azure_service,
)
@click.option(
"--target-name",
"-n",
"target_name",
type=str,
required=True,
help="The target name. Must be the name of a subscription or management group.",
envvar="TARGET_NAME",
default="example"
)
@click.option(
"--target-type",
"-t",
"target_type",
required=True,
type=click.Choice(["subscription", "mg"]),
help="The target type - a subscription or management group.",
envvar="TARGET_TYPE",
default="subscription"
)
@click.option(
"--policy-set-name",
"-s",
"policy_set_name",
@optgroup.option(
"--exclude-services",
"exclude_services",
type=str,
required=True,
help="The name to use for the resulting Azure Policy Set/Initiative.",
envvar="POLICY_SET_NAME",
default="example"
help="Exclude specific services (comma-separated) without using a config file.",
callback=validate.click_validate_comma_separated_excluded_services
)
@click.option(
@optgroup.option(
"--enforce",
"-e",
"enforcement_mode",
is_flag=True,
default=False,
help="Enforce Azure Policies instead of just auditing them.",
help="Deny bad actions instead of auditing them.",
)
@click.option(
"--module-source",
"-m",
"terraform_module_source",
type=str,
required=True,
help="The source to use for the Terraform remote module. You can set this to your own fork or private Git Repo if you don't want to rely on this source code.",
envvar="TERRAFORM_MODULE_SOURCE",
default=utils.DEFAULT_TERRAFORM_MODULE_SOURCE
)
@click.option(
@optgroup.group("Configuration", help="")
@optgroup.option(
"--config-file",
"-c",
"config_file",
type=click.Path(exists=False),
required=False,
help="The config file",
)
@click.option(
"--generate-summary",
"-g",
@optgroup.group(
"Parameter Options",
cls=RequiredMutuallyExclusiveOptionGroup,
help="",
)
@optgroup.option(
"--no-params",
is_flag=True,
default=False,
help="Generate a Markdown table summary showing your policies and which standards they apply to.",
help="Only generate policies that do NOT require parameters",
)
@click.option(
"--with-parameters",
"-p",
@optgroup.option(
"--params-optional",
is_flag=True,
default=False,
help="Include Policies with Parameters",
help="Only generate policies where parameters are OPTIONAL",
)
@click.option(
"--empty-defaults",
"-d",
"include_empty_defaults",
@optgroup.option(
"--params-required",
is_flag=True,
default=False,
help="Include parameters with empty defaults",
help="Only generate policies where parameters are REQUIRED",
)
@click.option(
"--exclude-services",
"exclude_services",
# @optgroup.option(
# "--parameter-options",
# "-o",
# type=click.Choice(["defaults", "empty"], case_sensitive=True),
# multiple=True,
# required=False,
# default=None,
# help="Include Policies with Parameters that have default values (defaults) and/or Policies that have empty defaults that you must fill in (empty).",
# # callback=validate.click_validate_supported_azure_service, # TODO: Write this validation
# )
# Mutually exclusive option groups
# https://github.com/click-contrib/click-option-group
# https://stackoverflow.com/questions/37310718/mutually-exclusive-option-groups-in-python-click
@optgroup.group(
"Policy Scope Targets",
cls=RequiredMutuallyExclusiveOptionGroup,
help="",
)
@optgroup.option(
"--subscription",
type=str,
help="Exclude specific services (comma-separated) without using a config file.",
callback=validate.click_validate_comma_separated_excluded_services
help="The name of a subscription. Supply either this or --management-group",
)
@click.option(
"--quiet",
"-q",
@optgroup.option(
"--management-group",
type=str,
help="The name of a management group. Supply either this or --subscription",
)
@optgroup.group(
"Other options",
help="",
)
@optgroup.option(
"--no-summary",
"-n",
is_flag=True,
default=False,
help="Do not generate markdown or CSV summary files associated with the Terraform output",
)
@click.option(
"-v",
"--verbose",
"verbosity",
count=True,
)
def generate_terraform(service: str, with_parameters: bool, target_name: str, target_type: str,
policy_set_name: str, terraform_module_source: str, config_file: str, enforcement_mode: bool,
generate_summary: bool, include_empty_defaults: bool, exclude_services: list, quiet: bool):
def generate_terraform(
service: str,
exclude_services: list,
config_file: str,
no_params: bool,
params_optional: bool,
params_required: bool,
subscription: str,
management_group: str,
enforcement_mode: bool,
no_summary: bool,
verbosity: int
):
"""
Get Azure Policies
"""

# For the Generate Terraform example, we will deviate from the -vvv behavior
# and just force the user to use the quiet flag if they want to output via stdout
if quiet:
log_level = getattr(logging, "WARNING")
set_stream_logger(level=log_level)
else:
log_level = getattr(logging, "INFO")
set_stream_logger(level=log_level)
set_log_level(verbosity)

if not config_file:
logger.info(
Expand All @@ -143,40 +146,47 @@ def generate_terraform(service: str, with_parameters: bool, target_name: str, ta
else:
config = get_config_from_file(config_file=config_file, exclude_services=exclude_services)

subscription_name = ""
management_group = ""
if target_type == "subscription":
subscription_name = target_name
if subscription:
management_group = ""
else:
management_group = target_name
subscription = ""

# if generate_summary:
# if service == "all":
# services = Services(config=config)
# policy_names = services.get_display_names(with_parameters=with_parameters)
# else:
# services = Service(service_name=service, config=config)
# policy_names = services.get_display_names(with_parameters=with_parameters)
# compliance_coverage = ComplianceCoverage(display_names=policy_names)
# markdown_table = compliance_coverage.markdown_table()
# print(markdown_table)
# else:
with_parameters = False
include_empty_defaults = False

if generate_summary:
if service == "all":
services = Services(config=config)
policy_names = services.get_display_names(with_parameters=with_parameters)
else:
services = Service(service_name=service, config=config)
policy_names = services.get_display_names(with_parameters=with_parameters)
compliance_coverage = ComplianceCoverage(display_names=policy_names)
markdown_table = compliance_coverage.markdown_table()
print(markdown_table)
if no_params:
include_empty_defaults = False
with_parameters = False
elif params_required:
include_empty_defaults = True
elif params_optional:
with_parameters = True

if service == "all":
services = Services(config=config)
else:
services = Services(service_names=[service], config=config)
if with_parameters:
display_names = services.get_display_names_by_service_with_parameters(include_empty_defaults=include_empty_defaults)
terraform_template = TerraformTemplate(parameters=display_names,
subscription_name=subscription,
management_group=management_group,
enforcement_mode=enforcement_mode)
result = terraform_template.rendered()
else:
if service == "all":
services = Services(config=config)
else:
services = Services(service_names=[service], config=config)
if with_parameters:
display_names = services.get_display_names_by_service_with_parameters(include_empty_defaults=include_empty_defaults)
terraform_template = TerraformTemplate(name=policy_set_name, parameters=display_names,
subscription_name=subscription_name,
management_group=management_group,
enforcement_mode=enforcement_mode,
module_source=terraform_module_source)
result = terraform_template.rendered()
else:
display_names = services.get_display_names_sorted_by_service(with_parameters=with_parameters)
result = get_terraform_template(name=policy_set_name, policy_names=display_names,
subscription_name=subscription_name,
management_group=management_group, enforcement_mode=enforcement_mode,
module_source=terraform_module_source)
print(result)
display_names = services.get_display_names_sorted_by_service(with_parameters=with_parameters)
result = get_terraform_template(policy_names=display_names,
subscription_name=subscription,
management_group=management_group, enforcement_mode=enforcement_mode)
print(result)
19 changes: 14 additions & 5 deletions azure_guardrails/guardrails/policy_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ class PolicyDefinition:
https://docs.microsoft.com/en-us/azure/governance/policy/concepts/definition-structure
"""
def __init__(self, policy_content: dict):
def __init__(self, policy_content: dict, service_name: str):
self.content = policy_content
self.service_name = service_name
self.id = policy_content.get("id")
self.name = policy_content.get("name")
self.category = policy_content.get("properties").get("metadata").get("category", None)
Expand Down Expand Up @@ -80,14 +81,14 @@ def allowed_effects(self) -> list:
# This just means that there is no effect in there.
logger.debug(error)
# Sometimes that is because deployIfNotExists or Modify is in the rule somewhere. Let's search it as a string
if 'deployIfNotExists' in str(self.properties.policy_rule) and 'modify' in str(self.properties.policy_rule):
if 'deployifnotexists' in str(self.properties.policy_rule).lower() and 'modify' in str(self.properties.policy_rule):
logger.debug(f"Found BOTH deployIfNotExists and modify in the policy content for the policy: {self.display_name}")
allowed_effects.append("deployIfNotExists")
allowed_effects.append("modify")
elif 'deployIfNotExists' in str(self.properties.policy_rule):
elif 'deployifnotexists' in str(self.properties.policy_rule).lower():
logger.debug(f"Found deployIfNotExists in the policy content for the policy: {self.display_name}")
allowed_effects.append("deployIfNotExists")
elif 'modify' in str(self.properties.policy_rule):
elif 'modify' in str(self.properties.policy_rule).lower():
logger.debug(f"Found Modify in the policy content for the policy: {self.display_name}")
allowed_effects.append("modify")
else:
Expand All @@ -102,7 +103,15 @@ def allowed_effects(self) -> list:
@property
def modifies_resources(self) -> bool:
# Effects: https://docs.microsoft.com/en-us/azure/governance/policy/concepts/effects
if "append" in self.allowed_effects or "modify" in self.allowed_effects:
if (
"append" in self.allowed_effects
or "modify" in self.allowed_effects
or "Modify" in self.allowed_effects
or "deployifnotexists" in self.allowed_effects
or "DeployIfNotExists" in self.allowed_effects
or "deployIfNotExists" in self.allowed_effects
):
logger.debug(f"{self.service_name} - modifies_resources: The policy definition {self.display_name} has the allowed_effects: {self.allowed_effects}")
return True
else:
return False
Expand Down
Loading

0 comments on commit aa1f06e

Please sign in to comment.