diff --git a/Makefile b/Makefile index c37eb7b..14a804a 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/azure_guardrails/command/generate_terraform.py b/azure_guardrails/command/generate_terraform.py index 8a6d5f1..1ef9f4c 100644 --- a/azure_guardrails/command/generate_terraform.py +++ b/azure_guardrails/command/generate_terraform.py @@ -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 @@ -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( @@ -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) diff --git a/azure_guardrails/guardrails/policy_definition.py b/azure_guardrails/guardrails/policy_definition.py index 7f6b387..d86ecdb 100644 --- a/azure_guardrails/guardrails/policy_definition.py +++ b/azure_guardrails/guardrails/policy_definition.py @@ -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) @@ -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: @@ -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 diff --git a/azure_guardrails/guardrails/services.py b/azure_guardrails/guardrails/services.py index a32128d..a47309a 100644 --- a/azure_guardrails/guardrails/services.py +++ b/azure_guardrails/guardrails/services.py @@ -25,7 +25,7 @@ def _policy_definitions(self) -> List[PolicyDefinition]: policy_definitions = [] for file in self.policy_files: policy_content = utils.get_policy_json(service_name=self.service_name, filename=file) - policy_definition = PolicyDefinition(policy_content) + policy_definition = PolicyDefinition(policy_content=policy_content, service_name=self.service_name) policy_definitions.append(policy_definition) return policy_definitions @@ -55,6 +55,7 @@ def get_display_names(self, with_parameters: bool = False, with_modify_capabilit # Case: Return all policies if all_policies: display_names.append(policy_definition.display_name) + logger.info(f"Adding Policy. display_name: {policy_definition.display_name} effect: {policy_definition.allowed_effects}") # Case: Return only policies that do not have parameters or modify capabilities if not with_parameters and not with_modify_capabilities: if ( @@ -62,6 +63,8 @@ def get_display_names(self, with_parameters: bool = False, with_modify_capabilit and not policy_definition.modifies_resources ): display_names.append(policy_definition.display_name) + logger.warning( + f"Adding Policy. display_name: {policy_definition.display_name} effect: {policy_definition.allowed_effects}") # Case: return policies with parameters only, as long as they do not include modify capabilities elif with_parameters and not with_modify_capabilities: if ( @@ -69,6 +72,8 @@ def get_display_names(self, with_parameters: bool = False, with_modify_capabilit and not policy_definition.modifies_resources ): display_names.append(policy_definition.display_name) + logger.info( + f"Adding Policy. display_name: {policy_definition.display_name} effect: {policy_definition.allowed_effects}") # Case: return policies with parameters and modify capabilities elif with_parameters and with_modify_capabilities: if ( @@ -76,6 +81,8 @@ def get_display_names(self, with_parameters: bool = False, with_modify_capabilit and policy_definition.includes_parameters ): display_names.append(policy_definition.display_name) + logger.info( + f"Adding Policy. display_name: {policy_definition.display_name} effect: {policy_definition.allowed_effects}") display_names.sort() return display_names diff --git a/azure_guardrails/shared/terraform/policy-initiative-with-builtins/README.md b/azure_guardrails/shared/terraform/policy-initiative-with-builtins/README.md index fbd52a8..2673907 100644 --- a/azure_guardrails/shared/terraform/policy-initiative-with-builtins/README.md +++ b/azure_guardrails/shared/terraform/policy-initiative-with-builtins/README.md @@ -26,7 +26,7 @@ cd examples/simple ```bash -az account set --subscription "redscar-dev" +az account set --subscription "example" ``` @@ -44,7 +44,7 @@ terraform apply -auto-apply locals { name = "example" } module "acr_policies" { - source = "git@github.com:kmcquade/azure-guardrails.git//azure_guardrails/shared/terraform/policy-initiative-with-builtins" + source = "git@github.com:salesforce/azure-guardrails.git//azure_guardrails/shared/terraform/policy-initiative-with-builtins" description = local.name display_name = local.name subscription_name = "example" diff --git a/azure_guardrails/shared/terraform/policy-initiative-with-builtins/outputs.tf b/azure_guardrails/shared/terraform/policy-initiative-with-builtins/outputs.tf index f6cb0a5..7e78345 100644 --- a/azure_guardrails/shared/terraform/policy-initiative-with-builtins/outputs.tf +++ b/azure_guardrails/shared/terraform/policy-initiative-with-builtins/outputs.tf @@ -12,3 +12,8 @@ output "policy_set_definition_id" { value = azurerm_policy_set_definition.guardrails.id description = "The ID of the Policy Set Definition." } + +output "count_of_policies_applied" { + description = "The number of Policies applied as part of the Policy Initiative" + value = length(var.policy_names) +} \ No newline at end of file diff --git a/azure_guardrails/shared/terraform/policy-initiative-with-builtins/terraform.tf b/azure_guardrails/shared/terraform/policy-initiative-with-builtins/terraform.tf index 1247628..374719a 100644 --- a/azure_guardrails/shared/terraform/policy-initiative-with-builtins/terraform.tf +++ b/azure_guardrails/shared/terraform/policy-initiative-with-builtins/terraform.tf @@ -2,6 +2,6 @@ terraform { required_version = ">= 0.12.0" } -provider "azurerm" { - features {} -} +//provider "azurerm" { +// features {} +//} diff --git a/azure_guardrails/shared/utils.py b/azure_guardrails/shared/utils.py index 4412512..cd047c6 100644 --- a/azure_guardrails/shared/utils.py +++ b/azure_guardrails/shared/utils.py @@ -3,7 +3,6 @@ import json import csv from pathlib import Path -from colorama import Fore END = "\033[0m" AZURE_POLICY_SERVICE_DIRECTORY = os.path.abspath( @@ -17,6 +16,8 @@ DEFAULT_TERRAFORM_MODULE_SOURCE = "git@github.com:salesforce/azure-guardrails.git//azure_guardrails/shared/terraform/policy-initiative-with-builtins" +PREFIX = "GrdRlz" + def get_service_names(): services = os.listdir(AZURE_POLICY_SERVICE_DIRECTORY) diff --git a/azure_guardrails/terraform/no-parameters/policy-set-with-builtins-v2.tf b/azure_guardrails/terraform/no-parameters/policy-set-with-builtins-v2.tf new file mode 100644 index 0000000..f79343b --- /dev/null +++ b/azure_guardrails/terraform/no-parameters/policy-set-with-builtins-v2.tf @@ -0,0 +1,97 @@ +locals { + name_{{ t.name }} = "{{ t.name }}" + subscription_name_{{ t.name }} = "{{ t.subscription_name }}" + management_group_{{ t.name }} = "{{ t.management_group }}" + enforcement_mode_{{ t.name }} = {{ t.enforcement_mode }} + policy_names_{{ t.name }} = [{% for service_name, service_policy_names in t.policy_names.items() %} + # ----------------------------------------------------------------------------------------------------------------- + # {{ service_name }} + # -----------------------------------------------------------------------------------------------------------------{% for policy_name in service_policy_names %} + "{{ policy_name }}",{% endfor %} + {% endfor %} + ] +} + +# --------------------------------------------------------------------------------------------------------------------- +# Azure Policy name lookups: +# Because the policies are built-in, we can just look up their IDs by their names. +# --------------------------------------------------------------------------------------------------------------------- +data "azurerm_policy_definition" "{{ t.name }}" { + count = length(local.policy_names_{{ t.name }}) + display_name = element(local.policy_names_{{ t.name }}, count.index) +} + +locals { + {{ t.name }}_policy_definitions = flatten([tolist([ + for definition in data.azurerm_policy_definition.{{ t.name }}.*.id : + map("policyDefinitionId", definition) + ]) + ]) +} + +# --------------------------------------------------------------------------------------------------------------------- +# Conditional data lookups: If the user supplies management group, look up the ID of the management group +# --------------------------------------------------------------------------------------------------------------------- +data "azurerm_management_group" "{{ t.name }}" { + count = local.management_group_{{ t.name }} != "" ? 1 : 0 + display_name = local.management_group_{{ t.name }} +} + +### If the user supplies subscription, look up the ID of the subscription +data "azurerm_subscriptions" "{{ t.name }}" { + count = local.subscription_name_{{ t.name }} != "" ? 1 : 0 + display_name_contains = local.subscription_name_{{ t.name }} +} + +locals { + {{ t.name }}_scope = local.management_group_{{ t.name }} != "" ? data.azurerm_management_group.{{ t.name }}[0].id : element(data.azurerm_subscriptions.{{ t.name }}[0].subscriptions.*.id, 0) +} + +# --------------------------------------------------------------------------------------------------------------------- +# Policy Initiative +# --------------------------------------------------------------------------------------------------------------------- +resource "azurerm_policy_set_definition" "{{ t.name }}" { + name = local.name_{{ t.name }} + policy_type = "Custom" + display_name = local.name_{{ t.name }} + description = local.name_{{ t.name }} + management_group_name = local.management_group_{{ t.name }} == "" ? null : local.management_group_{{ t.name }} + policy_definitions = tostring(jsonencode(local.{{ t.name }}_policy_definitions)) + metadata = tostring(jsonencode({ + category = local.name_{{ t.name }} + })) +} + +# --------------------------------------------------------------------------------------------------------------------- +# Azure Policy Assignments +# Apply the Policy Initiative to the specified scope +# --------------------------------------------------------------------------------------------------------------------- +resource "azurerm_policy_assignment" "{{ t.name }}" { + name = local.name_{{ t.name }} + policy_definition_id = azurerm_policy_set_definition.{{ t.name }}.id + scope = local.{{ t.name }}_scope + enforcement_mode = local.enforcement_mode_{{ t.name }} +} + +# --------------------------------------------------------------------------------------------------------------------- +# Outputs +# --------------------------------------------------------------------------------------------------------------------- +output "policy_assignment_ids" { + value = azurerm_policy_assignment.{{ t.name }}.*.id + description = "The IDs of the Policy Assignments." +} + +output "scope" { + value = local.{{ t.name }}_scope + description = "The target scope - either the management group or subscription, depending on which parameters were supplied" +} + +output "policy_set_definition_id" { + value = azurerm_policy_set_definition.{{ t.name }}.id + description = "The ID of the Policy Set Definition." +} + +output "count_of_policies_applied" { + description = "The number of Policies applied as part of the Policy Initiative" + value = length(local.policy_names_{{ t.name }}) +} \ No newline at end of file diff --git a/azure_guardrails/terraform/policy-set-with-builtins.tf b/azure_guardrails/terraform/no-parameters/policy-set-with-builtins.tf similarity index 52% rename from azure_guardrails/terraform/policy-set-with-builtins.tf rename to azure_guardrails/terraform/no-parameters/policy-set-with-builtins.tf index 62eda9c..8c61269 100644 --- a/azure_guardrails/terraform/policy-set-with-builtins.tf +++ b/azure_guardrails/terraform/no-parameters/policy-set-with-builtins.tf @@ -3,7 +3,7 @@ variable "subscription_name" { default = "{{ t.subscription_name }}" } variable "management_group" { default = "{{ t.management_group }}" } variable "enforcement_mode" { default = {{ t.enforcement_mode }} } -module "{{ t.name }}" { +module "{{ t.name | replace('-', '_')}}" { source = "{{ t.module_source }}" description = var.name display_name = var.name @@ -20,3 +20,26 @@ module "{{ t.name }}" { {% endfor %} ] } + +# --------------------------------------------------------------------------------------------------------------------- +# Outputs +# --------------------------------------------------------------------------------------------------------------------- +output "policy_set_definition_ids" { + description = "The ID of the Policy Set Definition." + value = module.{{ t.name | replace('-', '_')}}.policy_set_definition_id +} + +output "policy_assignment_ids" { + description = "The IDs of the Policy Assignments." + value = module.{{ t.name | replace('-', '_')}}.policy_set_definition_id +} + +output "scope" { + description = "The target scope - either the management group or subscription, depending on which parameters were supplied" + value = module.{{ t.name | replace('-', '_')}}.scope +} + +output "count_of_policies_applied" { + description = "The number of Policies applied as part of the Policy Initiative" + value = module.{{ t.name | replace('-', '_')}}.count_of_policies_applied +} \ No newline at end of file diff --git a/azure_guardrails/terraform/parameters/policy-set-with-parameters.tf b/azure_guardrails/terraform/parameters/policy-set-with-parameters.tf index 3397709..766e2aa 100644 --- a/azure_guardrails/terraform/parameters/policy-set-with-parameters.tf +++ b/azure_guardrails/terraform/parameters/policy-set-with-parameters.tf @@ -19,17 +19,43 @@ locals { "{{ key }}",{% endfor %}{% endfor %} ] policy_definition_map = zipmap( - data.azurerm_policy_definition.definition_lookups.*.display_name, - data.azurerm_policy_definition.definition_lookups.*.id + data.azurerm_policy_definition.{{ t.name | replace('-', '_')}}_definition_lookups.*.display_name, + data.azurerm_policy_definition.{{ t.name | replace('-', '_')}}_definition_lookups.*.id ) } -data "azurerm_policy_definition" "definition_lookups" { +# --------------------------------------------------------------------------------------------------------------------- +# Conditional data lookups: If the user supplies management group, look up the ID of the management group +# --------------------------------------------------------------------------------------------------------------------- +data "azurerm_management_group" "{{ t.name | replace('-', '_')}}" { + count = var.management_group != "" ? 1 : 0 + name = var.management_group +} + +### If the user supplies subscription, look up the ID of the subscription +data "azurerm_subscriptions" "{{ t.name | replace('-', '_')}}" { + count = var.subscription_name != "" ? 1 : 0 + display_name_contains = var.subscription_name +} + +locals { + scope = var.management_group != "" ? data.azurerm_management_group.{{ t.name | replace('-', '_')}}[0].id : element(data.azurerm_subscriptions.{{ t.name | replace('-', '_')}}[0].subscriptions.*.id, 0) +} + +# --------------------------------------------------------------------------------------------------------------------- +# Azure Policy Definition Lookups +# --------------------------------------------------------------------------------------------------------------------- + +data "azurerm_policy_definition" "{{ t.name | replace('-', '_')}}_definition_lookups" { count = length(local.policy_names) display_name = local.policy_names[count.index] } -resource "azurerm_policy_set_definition" "guardrails" { +# --------------------------------------------------------------------------------------------------------------------- +# Azure Policy Initiative Definition +# --------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_policy_set_definition" "{{ t.name | replace('-', '_')}}_guardrails" { name = var.name policy_type = "Custom" display_name = var.name @@ -54,6 +80,32 @@ resource "azurerm_policy_set_definition" "guardrails" { PARAMETERS } -output "id" { - value = azurerm_policy_set_definition.guardrails.id -} \ No newline at end of file +# --------------------------------------------------------------------------------------------------------------------- +# Azure Policy Assignments +# Apply the Policy Initiative to the specified scope +# --------------------------------------------------------------------------------------------------------------------- +//resource "azurerm_policy_assignment" "{{ t.name | replace('-', '_')}}_guardrails" { +// name = var.name +// policy_definition_id = azurerm_policy_set_definition.{{ t.name | replace('-', '_')}}_guardrails.id +// scope = local.scope +// enforcement_mode = var.enforcement_mode +//} + + +# --------------------------------------------------------------------------------------------------------------------- +# Outputs +# --------------------------------------------------------------------------------------------------------------------- +//output "policy_assignment_ids" { +// value = azurerm_policy_assignment.{{ t.name | replace('-', '_')}}_guardrails.*.id +// description = "The IDs of the Policy Assignments." +//} +// +//output "scope" { +// value = local.scope +// description = "The target scope - either the management group or subscription, depending on which parameters were supplied" +//} +// +//output "policy_set_definition_id" { +// value = azurerm_policy_set_definition.{{ t.name | replace('-', '_')}}_guardrails.id +// description = "The ID of the Policy Set Definition." +//} diff --git a/azure_guardrails/terraform/terraform.py b/azure_guardrails/terraform/terraform.py index d50741d..4c51d33 100644 --- a/azure_guardrails/terraform/terraform.py +++ b/azure_guardrails/terraform/terraform.py @@ -3,46 +3,53 @@ import logging from jinja2 import Template, Environment, FileSystemLoader from azure_guardrails.shared import utils -from azure_guardrails import set_log_level, set_stream_logger logger = logging.getLogger(__name__) -set_stream_logger("jinja2", logging.DEBUG) - -def get_terraform_template(name: str, policy_names: dict, subscription_name: str = "", - management_group: str = "", enforcement_mode: bool = False, - module_source: str = utils.DEFAULT_TERRAFORM_MODULE_SOURCE) -> str: +def get_terraform_template(policy_names: dict, subscription_name: str = "", + management_group: str = "", enforcement_mode: bool = False) -> str: if subscription_name == "" and management_group == "": raise Exception("Please supply a value for the subscription name or the management group") if enforcement_mode: enforcement_string = "true" else: enforcement_string = "false" + # TODO: Shorten the subscription name if it is over X characters + if subscription_name: + name = f"{subscription_name}-noparams" + # TODO: Shorten the management group name if it is over X characters + else: + name = f"{management_group}-noparams" + name = name.replace("-", "_") + name = name.lower() template_contents = dict( name=name, policy_names=policy_names, subscription_name=subscription_name, management_group=management_group, enforcement_mode=enforcement_string, - module_source=module_source ) - template_path = os.path.join(os.path.dirname(__file__)) + template_path = os.path.join(os.path.dirname(__file__), "no-parameters") env = Environment(loader=FileSystemLoader(template_path)) # nosec - template = env.get_template("policy-set-with-builtins.tf") + template = env.get_template("policy-set-with-builtins-v2.tf") return template.render(t=template_contents) class TerraformTemplate: """Terraform Template with Parameters""" - def __init__(self, name: str, + def __init__(self, parameters: dict, subscription_name: str = "", - management_group: str = "", enforcement_mode: bool = False, - module_source: str = utils.DEFAULT_TERRAFORM_MODULE_SOURCE): - self.name = name + management_group: str = "", enforcement_mode: bool = False): + # TODO: Shorten the subscription name if it is over X characters + if subscription_name: + self.name = f"{subscription_name}-params" + # TODO: Shorten the management group name if it is over X characters + else: + self.name = f"{management_group}-params" + # self.name = name self.service_parameters = self._parameters(parameters) - self.module_source = module_source if subscription_name == "" and management_group == "": raise Exception("Please supply a value for the subscription name or the management group") @@ -102,7 +109,6 @@ def rendered(self) -> str: subscription_name=self.subscription_name, management_group=self.management_group, enforcement_mode=self.enforcement_string, - module_source=self.module_source ) template_path = os.path.join(os.path.dirname(__file__), "parameters") env = Environment(loader=FileSystemLoader(template_path)) # nosec diff --git a/examples/terraform-demo-with-parameters/main.tf b/examples/terraform-demo-with-parameters/main.tf index f9e3db1..888a57e 100644 --- a/examples/terraform-demo-with-parameters/main.tf +++ b/examples/terraform-demo-with-parameters/main.tf @@ -1,4 +1,4 @@ -variable "name" { default = "example" } +variable "name" { default = "example-params" } variable "subscription_name" { default = "example" } variable "management_group" { default = "" } variable "enforcement_mode" { default = false } @@ -97,17 +97,43 @@ locals { "Resource logs in Azure Stream Analytics should be enabled", ] policy_definition_map = zipmap( - data.azurerm_policy_definition.definition_lookups.*.display_name, - data.azurerm_policy_definition.definition_lookups.*.id + data.azurerm_policy_definition.example_params_definition_lookups.*.display_name, + data.azurerm_policy_definition.example_params_definition_lookups.*.id ) } -data "azurerm_policy_definition" "definition_lookups" { +# --------------------------------------------------------------------------------------------------------------------- +# Conditional data lookups: If the user supplies management group, look up the ID of the management group +# --------------------------------------------------------------------------------------------------------------------- +data "azurerm_management_group" "example_params" { + count = var.management_group != "" ? 1 : 0 + name = var.management_group +} + +### If the user supplies subscription, look up the ID of the subscription +data "azurerm_subscriptions" "example_params" { + count = var.subscription_name != "" ? 1 : 0 + display_name_contains = var.subscription_name +} + +locals { + scope = var.management_group != "" ? data.azurerm_management_group.example_params[0].id : element(data.azurerm_subscriptions.example_params[0].subscriptions.*.id, 0) +} + +# --------------------------------------------------------------------------------------------------------------------- +# Azure Policy Definition Lookups +# --------------------------------------------------------------------------------------------------------------------- + +data "azurerm_policy_definition" "example_params_definition_lookups" { count = length(local.policy_names) display_name = local.policy_names[count.index] } -resource "azurerm_policy_set_definition" "guardrails" { +# --------------------------------------------------------------------------------------------------------------------- +# Azure Policy Initiative Definition +# --------------------------------------------------------------------------------------------------------------------- + +resource "azurerm_policy_set_definition" "example_params_guardrails" { name = var.name policy_type = "Custom" display_name = var.name @@ -547,7 +573,33 @@ resource "azurerm_policy_set_definition" "guardrails" { PARAMETERS } -output "id" { - value = azurerm_policy_set_definition.guardrails.id -} +# --------------------------------------------------------------------------------------------------------------------- +# Azure Policy Assignments +# Apply the Policy Initiative to the specified scope +# --------------------------------------------------------------------------------------------------------------------- +//resource "azurerm_policy_assignment" "example_params_guardrails" { +// name = var.name +// policy_definition_id = azurerm_policy_set_definition.example_params_guardrails.id +// scope = local.scope +// enforcement_mode = var.enforcement_mode +//} + + +# --------------------------------------------------------------------------------------------------------------------- +# Outputs +# --------------------------------------------------------------------------------------------------------------------- +//output "policy_assignment_ids" { +// value = azurerm_policy_assignment.example_params_guardrails.*.id +// description = "The IDs of the Policy Assignments." +//} +// +//output "scope" { +// value = local.scope +// description = "The target scope - either the management group or subscription, depending on which parameters were supplied" +//} +// +//output "policy_set_definition_id" { +// value = azurerm_policy_set_definition.example_params_guardrails.id +// description = "The ID of the Policy Set Definition." +//} diff --git a/examples/terraform-demo/main.tf b/examples/terraform-demo/main.tf index 9b4fa4b..a2f2ef0 100644 --- a/examples/terraform-demo/main.tf +++ b/examples/terraform-demo/main.tf @@ -1,36 +1,27 @@ -variable "name" { default = "example" } -variable "subscription_name" { default = "example" } -variable "management_group" { default = "" } -variable "enforcement_mode" { default = false } - -module "example" { - source = "../../azure_guardrails/shared/terraform/policy-initiative-with-builtins" - description = var.name - display_name = var.name - subscription_name = var.subscription_name - management_group = var.management_group - enforcement_mode = var.enforcement_mode - policy_set_definition_category = var.name - policy_set_name = var.name - policy_names = [ +locals { + name_example_noparams = "example_noparams" + subscription_name_example_noparams = "redscar-dev" + management_group_example_noparams = "" + enforcement_mode_example_noparams = false + policy_names_example_noparams = [ # ----------------------------------------------------------------------------------------------------------------- # API for FHIR # ----------------------------------------------------------------------------------------------------------------- "Azure API for FHIR should use a customer-managed key to encrypt data at rest", "Azure API for FHIR should use private link", "CORS should not allow every domain to access your API for FHIR", - + # ----------------------------------------------------------------------------------------------------------------- # App Configuration # ----------------------------------------------------------------------------------------------------------------- "App Configuration should use a customer-managed key", "App Configuration should use private link", - + # ----------------------------------------------------------------------------------------------------------------- # App Platform # ----------------------------------------------------------------------------------------------------------------- "[Preview]: Audit Azure Spring Cloud instances where distributed tracing is not enabled", - + # ----------------------------------------------------------------------------------------------------------------- # App Service # ----------------------------------------------------------------------------------------------------------------- @@ -62,18 +53,18 @@ module "example" { "Remote debugging should be turned off for Function Apps", "Remote debugging should be turned off for Web Applications", "Web Application should only be accessible over HTTPS", - + # ----------------------------------------------------------------------------------------------------------------- # Attestation # ----------------------------------------------------------------------------------------------------------------- "Azure Attestation providers should use private endpoints", - + # ----------------------------------------------------------------------------------------------------------------- # Automation # ----------------------------------------------------------------------------------------------------------------- "Automation account variables should be encrypted", "Azure Automation accounts should use customer-managed keys to encrypt data at rest", - + # ----------------------------------------------------------------------------------------------------------------- # Azure Data Explorer # ----------------------------------------------------------------------------------------------------------------- @@ -81,35 +72,35 @@ module "example" { "Disk encryption should be enabled on Azure Data Explorer", "Double encryption should be enabled on Azure Data Explorer", "Virtual network injection should be enabled for Azure Data Explorer", - + # ----------------------------------------------------------------------------------------------------------------- # Azure Stack Edge # ----------------------------------------------------------------------------------------------------------------- "Azure Stack Edge devices should use double-encryption", - + # ----------------------------------------------------------------------------------------------------------------- # Backup # ----------------------------------------------------------------------------------------------------------------- "Azure Backup should be enabled for Virtual Machines", - + # ----------------------------------------------------------------------------------------------------------------- # Batch # ----------------------------------------------------------------------------------------------------------------- "Azure Batch account should use customer-managed keys to encrypt data", "Public network access should be disabled for Batch accounts", - + # ----------------------------------------------------------------------------------------------------------------- # Bot Service # ----------------------------------------------------------------------------------------------------------------- "Bot Service endpoint should be a valid HTTPS URI", "Bot Service should be encrypted with a customer-managed key", - + # ----------------------------------------------------------------------------------------------------------------- # Cache # ----------------------------------------------------------------------------------------------------------------- "Azure Cache for Redis should reside within a virtual network", "Only secure connections to your Azure Cache for Redis should be enabled", - + # ----------------------------------------------------------------------------------------------------------------- # Cognitive Services # ----------------------------------------------------------------------------------------------------------------- @@ -119,7 +110,7 @@ module "example" { "Cognitive Services accounts should use customer owned storage", "Cognitive Services accounts should use customer owned storage or enable data encryption.", "Public network access should be disabled for Cognitive Services accounts", - + # ----------------------------------------------------------------------------------------------------------------- # Compute # ----------------------------------------------------------------------------------------------------------------- @@ -130,21 +121,21 @@ module "example" { "Require automatic OS image patching on Virtual Machine Scale Sets", "Unattached disks should be encrypted", "Virtual machines should be migrated to new Azure Resource Manager resources", - + # ----------------------------------------------------------------------------------------------------------------- # Container Registry # ----------------------------------------------------------------------------------------------------------------- "Container registries should be encrypted with a customer-managed key", "Container registries should not allow unrestricted network access", "Container registries should use private link", - + # ----------------------------------------------------------------------------------------------------------------- # Cosmos DB # ----------------------------------------------------------------------------------------------------------------- "Azure Cosmos DB accounts should have firewall rules", "Azure Cosmos DB accounts should use customer-managed keys to encrypt data at rest", "Azure Cosmos DB key based metadata write access should be disabled", - + # ----------------------------------------------------------------------------------------------------------------- # Data Factory # ----------------------------------------------------------------------------------------------------------------- @@ -153,12 +144,12 @@ module "example" { "[Preview]: Azure Data Factory linked services should use Key Vault for storing secrets", "[Preview]: Azure Data Factory linked services should use system-assigned managed identity authentication when it is supported", "[Preview]: Azure Data Factory should use a Git repository for source control", - + # ----------------------------------------------------------------------------------------------------------------- # Data Lake # ----------------------------------------------------------------------------------------------------------------- "Require encryption on Data Lake Store accounts", - + # ----------------------------------------------------------------------------------------------------------------- # Event Grid # ----------------------------------------------------------------------------------------------------------------- @@ -166,28 +157,28 @@ module "example" { "Azure Event Grid domains should use private link", "Azure Event Grid topics should disable public network access", "Azure Event Grid topics should use private link", - + # ----------------------------------------------------------------------------------------------------------------- # Event Hub # ----------------------------------------------------------------------------------------------------------------- "All authorization rules except RootManageSharedAccessKey should be removed from Event Hub namespace", "Authorization rules on the Event Hub instance should be defined", "Event Hub namespaces should use a customer-managed key for encryption", - + # ----------------------------------------------------------------------------------------------------------------- # General # ----------------------------------------------------------------------------------------------------------------- "Audit resource location matches resource group location", "Audit usage of custom RBAC rules", "Custom subscription owner roles should not exist", - + # ----------------------------------------------------------------------------------------------------------------- # HDInsight # ----------------------------------------------------------------------------------------------------------------- "Azure HDInsight clusters should use customer-managed keys to encrypt data at rest", "Azure HDInsight clusters should use encryption at host to encrypt data at rest", "Azure HDInsight clusters should use encryption in transit to encrypt communication between Azure HDInsight cluster nodes", - + # ----------------------------------------------------------------------------------------------------------------- # Key Vault # ----------------------------------------------------------------------------------------------------------------- @@ -200,30 +191,30 @@ module "example" { "[Preview]: Keys should be backed by a hardware security module (HSM)", "[Preview]: Private endpoint should be configured for Key Vault", "[Preview]: Secrets should have content type set", - + # ----------------------------------------------------------------------------------------------------------------- # Kubernetes # ----------------------------------------------------------------------------------------------------------------- "Azure Policy Add-on for Kubernetes service (AKS) should be installed and enabled on your clusters", "Both operating systems and data disks in Azure Kubernetes Service clusters should be encrypted by customer-managed keys", "Temp disks and cache for agent node pools in Azure Kubernetes Service clusters should be encrypted at host", - + # ----------------------------------------------------------------------------------------------------------------- # Lighthouse # ----------------------------------------------------------------------------------------------------------------- "Audit delegation of scopes to a managing tenant", - + # ----------------------------------------------------------------------------------------------------------------- # Machine Learning # ----------------------------------------------------------------------------------------------------------------- "Azure Machine Learning workspaces should be encrypted with a customer-managed key", "Azure Machine Learning workspaces should use private link", - + # ----------------------------------------------------------------------------------------------------------------- # Managed Application # ----------------------------------------------------------------------------------------------------------------- "Application definition for Managed Application should use customer provided storage account", - + # ----------------------------------------------------------------------------------------------------------------- # Monitoring # ----------------------------------------------------------------------------------------------------------------- @@ -240,15 +231,11 @@ module "example" { "The Log Analytics agent should be installed on Virtual Machine Scale Sets", "The Log Analytics agent should be installed on virtual machines", "Workbooks should be saved to storage accounts that you control", - "[Preview]: Deploy - Configure Linux Azure Monitor agent to enable Azure Monitor assignments on Linux virtual machines", - "[Preview]: Deploy - Configure Windows Azure Monitor agent to enable Azure Monitor assignments on Windows virtual machines", - "[Preview]: Deploy Dependency agent to Windows Azure Arc machines", - "[Preview]: Deploy Dependency agent to hybrid Linux Azure Arc machines", "[Preview]: Log Analytics agent should be installed on your Linux Azure Arc machines", "[Preview]: Log Analytics agent should be installed on your Windows Azure Arc machines", "[Preview]: Network traffic data collection agent should be installed on Linux virtual machines", "[Preview]: Network traffic data collection agent should be installed on Windows virtual machines", - + # ----------------------------------------------------------------------------------------------------------------- # Network # ----------------------------------------------------------------------------------------------------------------- @@ -270,12 +257,12 @@ module "example" { "Web Application Firewall (WAF) should be enabled for Azure Front Door Service service", "[Preview]: All Internet traffic should be routed via your deployed Azure Firewall", "[Preview]: Container Registry should use a virtual network service endpoint", - + # ----------------------------------------------------------------------------------------------------------------- # Portal # ----------------------------------------------------------------------------------------------------------------- "Shared dashboards should not have markdown tiles with inline content", - + # ----------------------------------------------------------------------------------------------------------------- # SQL # ----------------------------------------------------------------------------------------------------------------- @@ -322,7 +309,7 @@ module "example" { "Vulnerability Assessment settings for SQL server should contain an email address to receive scan reports", "Vulnerability assessment should be enabled on SQL Managed Instance", "Vulnerability assessment should be enabled on your SQL servers", - + # ----------------------------------------------------------------------------------------------------------------- # Security Center # ----------------------------------------------------------------------------------------------------------------- @@ -348,8 +335,6 @@ module "example" { "Disk encryption should be applied on virtual machines", "Email notification for high severity alerts should be enabled", "Email notification to subscription owner for high severity alerts should be enabled", - "Enable Azure Security Center on your subscription", - "Enable Security Center's auto provisioning of the Log Analytics agent on your subscriptions with default workspace.", "Endpoint protection solution should be installed on virtual machine scale sets", "External accounts with owner permissions should be removed from your subscription", "External accounts with read permissions should be removed from your subscription", @@ -384,27 +369,25 @@ module "example" { "Vulnerabilities in security configuration on your virtual machine scale sets should be remediated", "Vulnerabilities on your SQL databases should be remediated", "Vulnerabilities on your SQL servers on machine should be remediated", - "[Preview]: Deploy - Configure Linux machines to automatically install the Azure Security agent", - "[Preview]: Deploy - Configure Windows machines to automatically install the Azure Security agent", "[Preview]: Sensitive data in your SQL databases should be classified", - + # ----------------------------------------------------------------------------------------------------------------- # Service Bus # ----------------------------------------------------------------------------------------------------------------- "All authorization rules except RootManageSharedAccessKey should be removed from Service Bus namespace", "Service Bus Premium namespaces should use a customer-managed key for encryption", - + # ----------------------------------------------------------------------------------------------------------------- # Service Fabric # ----------------------------------------------------------------------------------------------------------------- "Service Fabric clusters should have the ClusterProtectionLevel property set to EncryptAndSign", "Service Fabric clusters should only use Azure Active Directory for client authentication", - + # ----------------------------------------------------------------------------------------------------------------- # SignalR # ----------------------------------------------------------------------------------------------------------------- "Azure SignalR Service should use private link", - + # ----------------------------------------------------------------------------------------------------------------- # Storage # ----------------------------------------------------------------------------------------------------------------- @@ -418,12 +401,12 @@ module "example" { "Storage accounts should restrict network access using virtual network rules", "Storage accounts should use customer-managed key for encryption", "[Preview]: Storage account public access should be disallowed", - + # ----------------------------------------------------------------------------------------------------------------- # Stream Analytics # ----------------------------------------------------------------------------------------------------------------- "Azure Stream Analytics jobs should use customer-managed keys to encrypt data", - + # ----------------------------------------------------------------------------------------------------------------- # Synapse # ----------------------------------------------------------------------------------------------------------------- @@ -433,11 +416,96 @@ module "example" { "Managed workspace virtual network on Azure Synapse workspaces should be enabled", "Private endpoint connections on Azure Synapse workspaces should be enabled", "Vulnerability assessment should be enabled on your Synapse workspaces", - + # ----------------------------------------------------------------------------------------------------------------- # VM Image Builder # ----------------------------------------------------------------------------------------------------------------- "VM Image Builder templates should use private link", - + ] } + +# --------------------------------------------------------------------------------------------------------------------- +# Azure Policy name lookups: +# Because the policies are built-in, we can just look up their IDs by their names. +# --------------------------------------------------------------------------------------------------------------------- +data "azurerm_policy_definition" "example_noparams" { + count = length(local.policy_names_example_noparams) + display_name = element(local.policy_names_example_noparams, count.index) +} + +locals { + example_noparams_policy_definitions = flatten([tolist([ + for definition in data.azurerm_policy_definition.example_noparams.*.id : + map("policyDefinitionId", definition) + ]) + ]) +} + +# --------------------------------------------------------------------------------------------------------------------- +# Conditional data lookups: If the user supplies management group, look up the ID of the management group +# --------------------------------------------------------------------------------------------------------------------- +data "azurerm_management_group" "example_noparams" { + count = local.management_group_example_noparams != "" ? 1 : 0 + display_name = local.management_group_example_noparams +} + +### If the user supplies subscription, look up the ID of the subscription +data "azurerm_subscriptions" "example_noparams" { + count = local.subscription_name_example_noparams != "" ? 1 : 0 + display_name_contains = local.subscription_name_example_noparams +} + +locals { + example_noparams_scope = local.management_group_example_noparams != "" ? data.azurerm_management_group.example_noparams[0].id : element(data.azurerm_subscriptions.example_noparams[0].subscriptions.*.id, 0) +} + +# --------------------------------------------------------------------------------------------------------------------- +# Policy Initiative +# --------------------------------------------------------------------------------------------------------------------- +resource "azurerm_policy_set_definition" "example_noparams" { + name = local.name_example_noparams + policy_type = "Custom" + display_name = local.name_example_noparams + description = local.name_example_noparams + management_group_name = local.management_group_example_noparams == "" ? null : local.management_group_example_noparams + policy_definitions = tostring(jsonencode(local.example_noparams_policy_definitions)) + metadata = tostring(jsonencode({ + category = local.name_example_noparams + })) +} + +# --------------------------------------------------------------------------------------------------------------------- +# Azure Policy Assignments +# Apply the Policy Initiative to the specified scope +# --------------------------------------------------------------------------------------------------------------------- +resource "azurerm_policy_assignment" "example_noparams" { + name = local.name_example_noparams + policy_definition_id = azurerm_policy_set_definition.example_noparams.id + scope = local.example_noparams_scope + enforcement_mode = local.enforcement_mode_example_noparams +} + +# --------------------------------------------------------------------------------------------------------------------- +# Outputs +# --------------------------------------------------------------------------------------------------------------------- +output "policy_assignment_ids" { + value = azurerm_policy_assignment.example_noparams.*.id + description = "The IDs of the Policy Assignments." +} + +output "scope" { + value = local.example_noparams_scope + description = "The target scope - either the management group or subscription, depending on which parameters were supplied" +} + +output "policy_set_definition_id" { + value = azurerm_policy_set_definition.example_noparams.id + description = "The ID of the Policy Set Definition." +} + +output "count_of_policies_applied" { + description = "The number of Policies applied as part of the Policy Initiative" + value = length(local.policy_names_example_noparams) +} + diff --git a/examples/terraform-demo/terraform.tf b/examples/terraform-demo/terraform.tf new file mode 100644 index 0000000..1247628 --- /dev/null +++ b/examples/terraform-demo/terraform.tf @@ -0,0 +1,7 @@ +terraform { + required_version = ">= 0.12.0" +} + +provider "azurerm" { + features {} +} diff --git a/requirements.txt b/requirements.txt index 2dcf792..91a307f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ click==7.1.2 +click_option_group==0.5.2 PyYAML==5.4.1 +# Required for printing things jinja2==2.11.3 -colorama==0.4.4 tabulate==0.8.9 +ruamel.yaml # Scrapers beautifulsoup4==4.9.3 requests==2.25.1 -ruamel.yaml \ No newline at end of file diff --git a/setup.py b/setup.py index 9b99cb0..e772e0b 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,16 @@ VERSION_RE = re.compile(r"""__version__ = ['"]([0-9.]+)['"]""") TESTS_REQUIRE = ["coverage", "nose", "pytest"] DESCRIPTION = "" +REQUIRED_PACKAGES = [ + "click", + "click_option_group", + "pyyaml", + "jinja2", + "tabulate", + "ruamel.yaml", + "beautifulsoup4", + "requests", +] def get_version(): @@ -32,13 +42,7 @@ def get_description(): url="https://github.com/salesforce/azure-guardrails", packages=setuptools.find_packages(exclude=["test*"]), tests_require=TESTS_REQUIRE, - install_requires=[ - "click", - "pyyaml", - "jinja2", - "colorama", - "tabulate" - ], + install_requires=REQUIRED_PACKAGES, classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", diff --git a/test/command/test_generate_terraform.py b/test/command/test_generate_terraform.py index 8aeac62..7af6238 100644 --- a/test/command/test_generate_terraform.py +++ b/test/command/test_generate_terraform.py @@ -21,18 +21,19 @@ def test_generate_terraform_command_with_click(self): """command.generate_terraform: should return exit code 0""" result = self.runner.invoke(generate_terraform, ["--help"]) self.assertTrue(result.exit_code == 0) - result = self.runner.invoke(generate_terraform, ["--service", "all", "--quiet"]) + result = self.runner.invoke(generate_terraform, ["--service", "all", "--subscription", "example", "--no-params"]) + print(result.output) self.assertTrue(result.exit_code == 0) def test_generate_terraform_command_with_config(self): """command.generate_terraform: with config file""" - result = self.runner.invoke(generate_terraform, ["--service", "all", "--config-file", default_config_file]) + result = self.runner.invoke(generate_terraform, ["--service", "all", "--subscription", "example", "--no-params", "--config-file", default_config_file]) self.assertTrue(result.exit_code == 0) # print(result.output) def test_generate_terraform_with_explicit_matches(self): """command.generate_terraform: with config file that matches keywords""" - result = self.runner.invoke(generate_terraform, ["--service", "all", "--config-file", config_with_keyword_matches]) + result = self.runner.invoke(generate_terraform, ["--service", "all", "--subscription", "example", "--no-params", "--config-file", config_with_keyword_matches]) # print(result.output) # We know for sure that no policies that match "customer-managed key" will also # contain "private link" in it (which is what the config file above looks for) diff --git a/test/logic/test_policy_definition.py b/test/logic/test_policy_definition.py index 5a82eb6..36a9fdc 100644 --- a/test/logic/test_policy_definition.py +++ b/test/logic/test_policy_definition.py @@ -9,7 +9,7 @@ def setUp(self): self.policy_json = utils.get_policy_json(service_name="Automation", filename="Automation_AuditUnencryptedVars_Audit.json") def test_policy_loading(self): - policy = PolicyDefinition(policy_content=self.policy_json) + policy = PolicyDefinition(policy_content=self.policy_json, service_name="Automation") self.assertTrue(policy.name == "3657f5a0-770e-44a3-b44e-9431ba1e9735") self.assertTrue(policy.display_name == "Automation account variables should be encrypted") self.assertTrue(policy.category == "Automation") diff --git a/test/logic/test_terraform.py b/test/logic/test_terraform.py index 31fe476..0eea679 100644 --- a/test/logic/test_terraform.py +++ b/test/logic/test_terraform.py @@ -13,7 +13,7 @@ def test_terraform_single_service(self): subscription_name = "example-subscription" management_group = "" enforcement_mode = False - result = get_terraform_template(name=policy_set_name, policy_names=policy_names, + result = get_terraform_template(policy_names=policy_names, subscription_name=subscription_name, management_group=management_group, enforcement_mode=enforcement_mode) # print(result) @@ -25,7 +25,7 @@ def test_terraform_all_services(self): management_group = "" enforcement_mode = False display_names = services.get_display_names_sorted_by_service(with_parameters=False) - result = get_terraform_template(name=policy_set_name, policy_names=display_names, + result = get_terraform_template(policy_names=display_names, subscription_name=subscription_name, management_group=management_group, enforcement_mode=enforcement_mode) # print(result) @@ -88,7 +88,7 @@ def setUp(self): } } } - self.terraform_template = TerraformTemplate(name="example", parameters=self.example_parameters, subscription_name="example") + self.terraform_template = TerraformTemplate(parameters=self.example_parameters, subscription_name="example") def test_get_policy_parameters(self): results = self.terraform_template.get_policy_parameters("Kubernetes", "Kubernetes cluster containers should only use allowed capabilities") diff --git a/update_data.py b/update_data.py index f9b8cc9..0d08526 100644 --- a/update_data.py +++ b/update_data.py @@ -6,7 +6,7 @@ import pandas as pd from azure_guardrails.scrapers.azure_docs import get_azure_html from azure_guardrails.scrapers.standard import scrape_standard -from azure_guardrails.shared.compliance_data import ComplianceResultsTransformer +from azure_guardrails.scrapers.compliance_data import ComplianceResultsTransformer logger = logging.getLogger(__name__) # pylint: disable=invalid-name