From 732ba5d183b81606c1817a437097fb3b3fd4c62a Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Thu, 28 Mar 2024 07:47:19 -0500 Subject: [PATCH 01/10] update --- data/change_history/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/change_history/README.md diff --git a/data/change_history/README.md b/data/change_history/README.md new file mode 100644 index 0000000000..e69de29bb2 From c1139d9890fa27db168f56b5496b4ae29d67c940 Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Thu, 28 Mar 2024 07:48:01 -0500 Subject: [PATCH 02/10] fix --- data/change_history/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 data/change_history/README.md diff --git a/data/change_history/README.md b/data/change_history/README.md deleted file mode 100644 index e69de29bb2..0000000000 From 4a99d74cf0e73ff8b91c501d575565596f59a909 Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Fri, 9 Aug 2024 13:24:20 -0500 Subject: [PATCH 03/10] update --- cost/aws/cloudtrail_read_logging/CHANGELOG.md | 5 + cost/aws/cloudtrail_read_logging/README.md | 62 + .../aws_cloudtrail_read_logging.pt | 517 +++++++ ...aws_cloudtrail_read_logging_meta_parent.pt | 1244 +++++++++++++++++ .../master_policy_permissions_list.json | 43 + .../master_policy_permissions_list.yaml | 25 + .../meta_parent_policy_compiler.rb | 1 + .../validated_policy_templates.yaml | 1 + 8 files changed, 1898 insertions(+) create mode 100644 cost/aws/cloudtrail_read_logging/CHANGELOG.md create mode 100644 cost/aws/cloudtrail_read_logging/README.md create mode 100644 cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt create mode 100644 cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging_meta_parent.pt diff --git a/cost/aws/cloudtrail_read_logging/CHANGELOG.md b/cost/aws/cloudtrail_read_logging/CHANGELOG.md new file mode 100644 index 0000000000..341687ee97 --- /dev/null +++ b/cost/aws/cloudtrail_read_logging/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## v0.1.0 + +- Initial Release diff --git a/cost/aws/cloudtrail_read_logging/README.md b/cost/aws/cloudtrail_read_logging/README.md new file mode 100644 index 0000000000..97a9fa17e2 --- /dev/null +++ b/cost/aws/cloudtrail_read_logging/README.md @@ -0,0 +1,62 @@ +# AWS CloudTrails With Read Logging Enabled + +## What It Does + +This policy template reports any AWS CloudTrail trails that log read actions. Logging read actions can greatly increase storage costs in some cases. Optionally, this report can be emailed and read logging can be disabled. + +## Input Parameters + +- *Email Addresses* - Email addresses of the recipients you wish to notify. +- *Account Number* - The Account number for use with the AWS STS Cross Account Role. Leave blank when using AWS IAM Access key and secret. It only needs to be passed when the desired AWS account is different than the one associated with the Flexera One credential. [More information is available in our documentation.](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_1982464505_1123608) +- *Allow/Deny Regions* - Whether to treat Allow/Deny Regions List parameter as allow or deny list. Has no effect if Allow/Deny Regions List is left empty. +- *Allow/Deny Regions List* - A list of regions to allow or deny for an AWS account. Please enter the regions code if SCP is enabled. See [Available Regions](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions) in AWS; otherwise, the policy may fail on regions that are disabled via SCP. Leave blank to consider all the regions. + +## Policy Actions + +- Sends an email notification. +- Disables read logging after approval. + +## Prerequisites + +This Policy Template uses [Credentials](https://docs.flexera.com/flexera/EN/Automation/ManagingCredentialsExternal.htm) for authenticating to datasources -- in order to apply this policy you must have a Credential registered in the system that is compatible with this policy. If there are no Credentials listed when you apply the policy, please contact your Flexera Org Admin and ask them to register a Credential that is compatible with this policy. The information below should be consulted when creating the credential(s). + +- [**AWS Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_1982464505_1121575) (*provider=aws*) which has the following permissions: + - `sts:GetCallerIdentity` + - `cloudtrail:DescribeTrails` + - `cloudtrail:GetEventSelectors` + - `cloudtrail:PutEventSelectors`* + + \* Only required for taking action; the policy will still function in a read-only capacity without these permissions. + + Example IAM Permission Policy: + + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sts:GetCallerIdentity", + "cloudtrail:DescribeTrails", + "cloudtrail:GetEventSelectors", + "cloudtrail:PutEventSelectors" + ], + "Resource": "*" + } + ] + } + ``` + +- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: + - `billing_center_viewer` + +The [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) page in the docs has detailed instructions for setting up Credentials for the most common providers. + +## Supported Clouds + +- AWS + +## Cost + +This Policy Template does not incur any cloud costs. diff --git a/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt b/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt new file mode 100644 index 0000000000..836aecb1da --- /dev/null +++ b/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt @@ -0,0 +1,517 @@ +name "AWS CloudTrails With Read Logging Enabled" +rs_pt_ver 20180301 +type "policy" +short_description "Reports AWS CloudTrail trails that are logging read events. See the [README](https://github.com/flexera-public/policy_templates/tree/master/cost/aws/cloudwatch_read_logging) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." +long_description "" +category "Cost" +severity "low" +default_frequency "weekly" +info( + version: "0.1.0", + provider: "AWS", + service: "CloudTrail", + policy_set: "Logging" +) + +############################################################################### +# Parameters +############################################################################### + +parameter "param_email" do + type "list" + category "Policy Settings" + label "Email Addresses" + description "A list of email addresses to notify." + default [] +end + +parameter "param_aws_account_number" do + type "string" + category "Policy Settings" + label "Account Number" + description "Leave blank; this is for automated use with Meta Policies. See README for more details." + default "" +end + +parameter "param_regions_allow_or_deny" do + type "string" + category "Filters" + label "Allow/Deny Regions" + description "Allow or Deny entered regions. See the README for more details" + allowed_values "Allow", "Deny" + default "Allow" +end + +parameter "param_regions_list" do + type "list" + category "Filters" + label "Allow/Deny Regions List" + description "A list of allowed or denied regions. See the README for more details" + allowed_pattern /^([a-zA-Z-_]+-[a-zA-Z0-9-_]+-[0-9-_]+,*|)+$/ + default [] +end + +parameter "param_automatic_action" do + type "list" + category "Actions" + label "Automatic Actions" + description "When this value is set, this policy will automatically take the selected action(s)" + allowed_values ["Disable Read Logging"] + default [] +end + +############################################################################### +# Authentication +############################################################################### + +credentials "auth_aws" do + schemes "aws", "aws_sts" + label "AWS" + description "Select the AWS Credential from the list" + tags "provider=aws" + aws_account_number $param_aws_account_number +end + +credentials "auth_flexera" do + schemes "oauth2" + label "Flexera" + description "Select Flexera One OAuth2 credentials" + tags "provider=flexera" +end + +############################################################################### +# Datasources & Scripts +############################################################################### + +# Get applied policy metadata for use later +datasource "ds_applied_policy" do + request do + auth $auth_flexera + host rs_governance_host + path join(["/api/governance/projects/", rs_project_id, "/applied_policies/", policy_id]) + header "Api-Version", "1.0" + end +end + +# Get region-specific Flexera API endpoints +datasource "ds_flexera_api_hosts" do + run_script $js_flexera_api_hosts, rs_optima_host +end + +script "js_flexera_api_hosts", type: "javascript" do + parameters "rs_optima_host" + result "result" + code <<-'EOS' + host_table = { + "api.optima.flexeraeng.com": { + flexera: "api.flexera.com", + fsm: "api.fsm.flexeraeng.com" + }, + "api.optima-eu.flexeraeng.com": { + flexera: "api.flexera.eu", + fsm: "api.fsm-eu.flexeraeng.com" + }, + "api.optima-apac.flexeraeng.com": { + flexera: "api.flexera.au", + fsm: "api.fsm-apac.flexeraeng.com" + } + } + + result = host_table[rs_optima_host] +EOS +end + +# Get AWS account info +datasource "ds_cloud_vendor_accounts" do + request do + auth $auth_flexera + host val($ds_flexera_api_hosts, 'flexera') + path join(["/finops-analytics/v1/orgs/", rs_org_id, "/cloud-vendor-accounts"]) + header "Api-Version", "1.0" + end + result do + encoding "json" + collect jmes_path(response, "values[*]") do + field "id", jmes_path(col_item, "aws.accountId") + field "name", jmes_path(col_item, "name") + field "tags", jmes_path(col_item, "tags") + end + end +end + +datasource "ds_get_caller_identity" do + request do + auth $auth_aws + verb "GET" + host "sts.amazonaws.com" + path "/" + query "Action", "GetCallerIdentity" + query "Version", "2011-06-15" + header "User-Agent", "RS Policies" + end + result do + encoding "xml" + collect xpath(response, "//GetCallerIdentityResponse/GetCallerIdentityResult") do + field "account", xpath(col_item, "Account") + end + end +end + +datasource "ds_aws_account" do + run_script $js_aws_account, $ds_cloud_vendor_accounts, $ds_get_caller_identity +end + +script "js_aws_account", type:"javascript" do + parameters "ds_cloud_vendor_accounts", "ds_get_caller_identity" + result "result" + code <<-'EOS' + result = _.find(ds_cloud_vendor_accounts, function(account) { + return account['id'] == ds_get_caller_identity[0]['account'] + }) + + // This is in case the API does not return the relevant account info + if (result == undefined) { + result = { + id: ds_get_caller_identity[0]['account'], + name: "", + tags: {} + } + } +EOS +end + +datasource "ds_trail_list" do + request do + auth $auth_aws + host "cloudtrail.us-east-1.amazonaws.com" + path "/" + query "Action", "DescribeTrails" + header "User-Agent", "RS Policies" + header "Accept", "application/json" + # Header X-Meta-Flexera has no affect on datasource query, but is required for Meta Policies + # Forces `ds_is_deleted` datasource to run first during policy execution + header "Meta-Flexera", val($ds_is_deleted, "path") + end + result do + encoding "json" + collect jmes_path(response, "DescribeTrailsResponse.DescribeTrailsResult.trailList") do + field "name", jmes_path(col_item, "Name") + field "arn", jmes_path(col_item, "TrailARN") + field "region", jmes_path(col_item, "HomeRegion") + field "s3_bucket", jmes_path(col_item, "S3BucketName") + end + end +end + +datasource "ds_trail_list_region_filtered" do + run_script $js_trail_list_region_filtered, $ds_trail_list, $param_regions_list, $param_regions_allow_or_deny +end + +script "js_trail_list_region_filtered", type:"javascript" do + parameters "ds_trail_list", "param_regions_list", "param_regions_allow_or_deny" + result "result" + code <<-EOS + allow_deny_test = { "Allow": true, "Deny": false } + + if (param_regions_list.length > 0) { + result = _.filter(ds_trail_list, function(item) { + return _.contains(param_regions_list, item['region']) == allow_deny_test[param_regions_allow_or_deny] + }) + } else { + result = ds_trail_list + } +EOS +end + +datasource "ds_trail_list_with_selectors" do + iterate $ds_trail_list_region_filtered + request do + auth $auth_aws + verb "POST" + host join(["cloudtrail.", val(iter_item, "region"), ".amazonaws.com"]) + path "/" + header "User-Agent", "RS Policies" + header "X-Amz-Target", "com.amazonaws.cloudtrail.v20131101.CloudTrail_20131101.GetEventSelectors" + header "Content-Type", "application/x-amz-json-1.1" + body_field "TrailName", val(iter_item, "arn") + end + result do + encoding "json" + field "event_selectors", jmes_path(response, "EventSelectors") + field "name", val(iter_item, "name") + field "arn", val(iter_item, "arn") + field "region", val(iter_item, "region") + field "s3_bucket", val(iter_item, "s3_bucket") + end +end + +datasource "ds_trails_incident" do + run_script $js_trails_incident, $ds_trail_list_with_selectors, $ds_aws_account, $ds_applied_policy +end + +script "js_trails_incident", type:"javascript" do + parameters "ds_trail_list_with_selectors", "ds_aws_account", "ds_applied_policy" + result "result" + code <<-EOS + result = [] + + _.each(ds_trail_list_with_selectors, function(trail) { + has_selectors = typeof(trail['event_selectors']) == 'object' + logging_read = false + logging_write = false + logging_all = false + event_selectors = [] + fixed_selectors = [] + + if (has_selectors) { + event_selectors = trail['event_selectors'] + selector_types = _.pluck(event_selectors, 'ReadWriteType') + logging_all = _.contains(selector_types, 'All') + logging_read = _.contains(selector_types, 'ReadOnly') || logging_all + logging_write = _.contains(selector_types, 'WriteOnly') || logging_all + + _.each(event_selectors, function(selector) { + if (selector['ReadWriteType'] == 'All' || selector['ReadWriteType'] == 'WriteOnly') { + fixed_selectors.push({ + "DataResources": selector['DataResources'], + "ExcludeManagementEventSources": selector['ExcludeManagementEventSources'], + "IncludeManagementEvents": selector['IncludeManagementEvents'], + "ReadWriteType": "WriteOnly" + }) + } + }) + } + + if (logging_read) { + result.push({ + accountID: ds_aws_account['id'], + accountName: ds_aws_account['name'], + name: trail['name'], + id: trail['arn'], + region: trail['region'], + has_selectors: has_selectors, + logging_read: logging_read, + logging_write: logging_write, + logging_all: logging_all, + event_selectors: JSON.stringify(event_selectors), + fixed_selectors: JSON.stringify(fixed_selectors), + fixed_selectors_object: fixed_selectors, + policy_name: ds_applied_policy['name'] + }) + } + }) +EOS +end + +############################################################################### +# Policy +############################################################################### + +policy "pol_trails_incident" do + validate_each $ds_trails_incident do + summary_template "{{ with index data 0 }}{{ .policy_name }}{{ end }}: {{ len data }} AWS CloudTrails With Read Logging Enabled Found" + check logic_or($ds_parent_policy_terminated, eq(val(item, "id"), "")) + escalate $esc_email + escalate $esc_disable_read_logging + export do + resource_level true + field "accountID" do + label "Account ID" + end + field "accountName" do + label "Account Name" + end + field "name" do + label "Name" + end + field "id" do + label "ARN" + end + field "region" do + label "Region" + end + field "logging_read" do + label "Read Logging" + end + field "logging_write" do + label "Write Logging" + end + field "has_selectors" do + label "Has Event Selectors?" + end + field "event_selectors" do + label "Current Event Selectors" + end + field "fixed_selectors" do + label "Recommended Event Selectors" + end + field "fixed_selectors_object" do + label "Recommended Event Selectors (Object)" + end + end + end +end + +############################################################################### +# Escalations +############################################################################### + +escalation "esc_email" do + automatic true + label "Send Email" + description "Send incident email" + email $param_email +end + +escalation "esc_disable_read_logging" do + automatic contains($param_automatic_action, "Disable Read Logging") + label "Disable Read Logging" + description "Approval to disable read logging on all selected CloudTrails" + run "disable_read_logging_on_trails", data +end + +############################################################################### +# Cloud Workflow +############################################################################### + +define disable_read_logging_on_trails($data) return $all_responses do + $$all_responses = [] + + foreach $clb in $data do + sub on_error: handle_error() do + call disable_read_logging_on_trail($trail) retrieve $response + end + end + + if inspect($$errors) != "null" + raise join($$errors, "\n") + end +end + +define disable_read_logging_on_trail($trail) return $response do + $host = "cloudtrail." + $trail["region"] + ".amazonaws.com" + $href = "/" + $url = $host + $href + task_label("POST " + $url) + + $response = http_request( + auth: $$auth_aws, + https: true, + verb: "post", + host: $host, + href: $href, + body: { + "TrailName": $trail["name"], + "EventSelectors": $trail["fixed_selectors_object"] + } + ) + + task_label("POST AWS CloudTrail response: " + $trail["name"] + " " + to_json($response)) + $$all_responses << to_json({"req": "POST " + $url, "resp": $response}) + + if $response["code"] != 204 && $response["code"] != 202 && $response["code"] != 200 + raise "Unexpected response from POST AWS CloudTrail: "+ $trail["name"] + " " + to_json($response) + else + task_label("POST AWS CloudTrail successful: " + $trail["name"]) + end +end + +define handle_error() do + if !$$errors + $$errors = [] + end + $$errors << $_error["type"] + ": " + $_error["message"] + # We check for errors at the end, and raise them all together + # Skip errors handled by this definition + $_error_behavior = "skip" +end + +############################################################################### +# Meta Policy [alpha] +# Not intended to be modified or used by policy developers +############################################################################### + +# If the meta_parent_policy_id is not set it will evaluate to an empty string and we will look for the policy itself, +# if it is set we will look for the parent policy. +datasource "ds_get_policy" do + request do + auth $auth_flexera + host rs_governance_host + ignore_status [404] + path join(["/api/governance/projects/", rs_project_id, "/applied_policies/", switch(ne(meta_parent_policy_id,""), meta_parent_policy_id, policy_id) ]) + header "Api-Version", "1.0" + end + result do + encoding "json" + field "id", jmes_path(response, "id") + end +end + +datasource "ds_parent_policy_terminated" do + run_script $js_decide_if_self_terminate, $ds_get_policy, policy_id, meta_parent_policy_id +end + +# If the policy was applied by a meta_parent_policy we confirm it exists if it doesn't we confirm we are deleting +# This information is used in two places: +# - determining whether or not we make a delete call +# - determining if we should create an incident (we don't want to create an incident on the run where we terminate) +script "js_decide_if_self_terminate", type: "javascript" do + parameters "found", "self_policy_id", "meta_parent_policy_id" + result "result" + code <<-EOS + var result + if (meta_parent_policy_id != "" && found.id == undefined) { + result = true + } else { + result = false + } + EOS +end + +# Two potentials ways to set this up: +# - this way and make a unneeded 'get' request when not deleting +# - make the delete request an interate and have it iterate over an empty array when not deleting and an array with one item when deleting +script "js_make_terminate_request", type: "javascript" do + parameters "should_delete", "policy_id", "rs_project_id", "rs_governance_host" + result "request" + code <<-EOS + + var request = { + auth: 'auth_flexera', + host: rs_governance_host, + path: "/api/governance/projects/" + rs_project_id + "/applied_policies/" + policy_id, + headers: { + "API-Version": "1.0", + "Content-Type":"application/json" + }, + } + + if (should_delete) { + request.verb = 'DELETE' + } + EOS +end + +datasource "ds_terminate_self" do + request do + run_script $js_make_terminate_request, $ds_parent_policy_terminated, policy_id, rs_project_id, rs_governance_host + end +end + +datasource "ds_is_deleted" do + run_script $js_check_deleted, $ds_terminate_self +end + +# This is just a way to have the check delete request connect to the farthest leaf from policy. +# We want the delete check to the first thing the policy does to avoid the policy erroring before it can decide whether or not it needs to self terminate +# Example a customer deletes a credential and then terminates the parent policy. We still want the children to self terminate +# The only way I could see this not happening is if the user who applied the parent_meta_policy was offboarded or lost policy access, the policies who are impersonating the user +# would not have access to self-terminate +# It may be useful for the backend to enable a mass terminate at some point for all meta_child_policies associated with an id. +script "js_check_deleted", type: "javascript" do + parameters "response" + result "result" + code <<-EOS + result = {"path":"/"} + EOS +end diff --git a/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging_meta_parent.pt b/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging_meta_parent.pt new file mode 100644 index 0000000000..8374c348f7 --- /dev/null +++ b/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging_meta_parent.pt @@ -0,0 +1,1244 @@ +name "Meta Parent: AWS CloudTrails With Read Logging Enabled" +rs_pt_ver 20180301 +type "policy" +short_description "**NOTE: Meta policies are an alpha feature. Please consult the [README](https://github.com/flexera-public/policy_templates/blob/master/README_META_POLICIES.md) before use.** Applies and manages \"child\" [AWS CloudTrails With Read Logging Enabled](https://github.com/flexera-public/policy_templates/tree/master/cost/aws/cloudtrail_read_logging) Policies." +severity "low" +category "Meta" +default_frequency "15 minutes" +info( + provider: "AWS", + version: "0.1.0", # This version of the Meta Parent Policy Template should match the version of the Child Policy Template as it appears in the Catalog for best reliability +) + +############################################################################## +# Parameters +############################################################################## + +## Meta Parent Parameters +## These are params specific to the meta parent policy. +parameter "param_combined_incident_email" do + type "list" + label "Email addresses for combined incident" + description "A list of email addresses to notify with the consolidated child policy incident." + default [] +end + +parameter "param_dimension_filter_includes" do + type "list" + label "Dimension Include Filters" + description <<-EOS + Filters [`dimension_name=dimension_value` and `dimension_name=~dimension_value` pairs] to determine which AWS Accounts returned by the Flexera Bill Analysis API to **INCLUDE** and be applied to. + Use = to match the entire value and =~ to match a substring contained in the value. + During each run this policy will select AWS Accounts who match **all** the filters defined and apply a child policy for each. + If no include filters are provided, then all AWS Accounts are included by default. + Most of the dimensions in Flexera can be used [default dimensions, custom tag dimensions, rule-based dimensions]. Full list of available dimensions documented in the [Bill Analysis API Docs](https://reference.rightscale.com/bill_analysis/). + EOS + default [] +end + +parameter "param_dimension_filter_excludes" do + type "list" + label "Dimension Exclude Filters" + description <<-EOS + Filters [`dimension_name=dimension_value` and `dimension_name=~dimension_value` pairs] to determine which AWS Accounts returned by the Flexera Bill Analysis API to **EXCLUDE** and *not* have policy applied to. + Use = to match the entire value and =~ to match a substring contained in the value. + During each run this policy will select AWS Accounts who match **all** the filters defined here and excludes them from results. + Can be used to exclude specific AWS Accounts [`vendor_account=123456789012`] + Most of the dimensions in Flexera can be used [default dimensions, custom tag dimensions, rule-based dimensions]. Full list of available dimensions documented in the [Bill Analysis API Docs](https://reference.rightscale.com/bill_analysis/). + EOS + default [] +end + +parameter "param_policy_schedule" do + type "string" + label "Child Policy Schedule" + description "The interval at which the child policy checks for conditions and generates incidents." + default "weekly" + allowed_values "daily", "weekly", "monthly" +end + +parameter "param_template_source" do + type "string" + label "Child Policy Template Source" + description "By default, will use the \"AWS CloudTrails With Read Logging Enabled\" Policy Template from Catalog. Optionally, you can use the \"AWS CloudTrails With Read Logging Enabled\" Policy Template uploaded in the current Flexera Project." + default "Published Catalog Template" + allowed_values "Published Catalog Template", "Uploaded Template" +end + +## Child Policy Parameters +parameter "param_regions_allow_or_deny" do + type "string" + category "Filters" + label "Allow/Deny Regions" + description "Allow or Deny entered regions. See the README for more details" + allowed_values "Allow", "Deny" + default "Allow" +end + +parameter "param_regions_list" do + type "list" + category "Filters" + label "Allow/Deny Regions List" + description "A list of allowed or denied regions. See the README for more details" + allowed_pattern /^([a-zA-Z-_]+-[a-zA-Z0-9-_]+-[0-9-_]+,*|)+$/ + default [] +end + +parameter "param_automatic_action" do + type "list" + category "Actions" + label "Automatic Actions" + description "When this value is set, this policy will automatically take the selected action(s)" + allowed_values ["Disable Read Logging"] + default [] +end + +############################################################################### +# Authentication +############################################################################### +credentials "auth_aws" do + schemes "aws", "aws_sts" + label "AWS" + description "Select the AWS Credential from the list" + tags "provider=aws" + aws_account_number $param_aws_account_number +end + +credentials "auth_flexera" do + schemes "oauth2" + label "Flexera" + description "Select Flexera One OAuth2 credentials" + tags "provider=flexera" +end + +############################################################################### +# Datasources +############################################################################### + +# Get Applied Parent Policy Details +datasource "ds_self_policy_information" do + request do + auth $auth_flexera + host rs_governance_host + path join(["/api/governance/projects/", rs_project_id, "/applied_policies/", policy_id]) + header "Api-Version", "1.0" + end + result do + encoding "json" + field "name", jmes_path(response, "name") + field "creator_id", jmes_path(response, "created_by.id") + field "credentials", jmes_path(response, "credentials") + field "options", jmes_path(response, "options") + end +end + +datasource "ds_child_policy_options" do + run_script $js_child_policy_options, $ds_self_policy_information +end + +script "js_child_policy_options", type: "javascript" do + parameters "ds_self_policy_information" + result "options" + code <<-EOS + // Filter Options that are not appropriate for Child Policy + var options = _.map(ds_self_policy_information.options, function(option){ + // param_combined_incident_email, param_dimension_filter_includes, param_dimension_filter_excludes, param_policy_schedule are exclusion to Meta Parent Policy Parameters + if (!_.contains(["param_combined_incident_email", "param_dimension_filter_includes", "param_dimension_filter_excludes", "param_policy_schedule", "param_template_source"], option.name)) { + return { "name": option.name, "value": option.value }; + } + }); + // Explicitly add param_email which is disabled/does not exist in meta parent policy + options.push({ + "name": "param_email", + "value": [] + }); + EOS +end + +datasource "ds_child_policy_options_map" do + run_script $js_child_policy_options_map, $ds_child_policy_options +end + +script "js_child_policy_options_map", type: "javascript" do + parameters "ds_child_policy_options" + result "options" + code <<-EOS + function format_options_keyvalue(options) { + var options_keyvalue_map = {}; + _.each(options, function(option) { + options_keyvalue_map[option.name] = option.value; + }); + return options_keyvalue_map; + } + var options = format_options_keyvalue(ds_child_policy_options) + EOS +end + +datasource "ds_format_self" do + run_script $js_format_self, $ds_self_policy_information, $ds_child_policy_options_map +end + +script "js_format_self", type: "javascript" do + parameters "ds_self_policy_information", "ds_child_policy_options_map" + result "formatted" + code <<-EOS + var formatted = { + "name": ds_self_policy_information["name"], + "creator_id": ds_self_policy_information["creator_id"], + "credentials": ds_self_policy_information["credentials"], + "options": ds_child_policy_options_map + }; + EOS +end + + +# Get Pulished Policy Details +datasource "ds_published_child_policy_information" do + request do + auth $auth_flexera + host rs_governance_host + path join(["/api/governance/orgs/", rs_org_id, "/published_templates"]) + header "Api-Version", "1.0" + end + result do + encoding "json" + # Select the published policy that is published by "support@flexera.com" and matches the name of the child policy template + collect jq(response, '.items[] | select(.name == "AWS CloudTrails With Read Logging Enabled" and .created_by.email == "support@flexera.com")' ) do + field "name", jmes_path(col_item, "name") + field "href", jmes_path(col_item, "href") + field "short_description", jmes_path(col_item, "short_description") + end + end +end + +# Get Uploaded Policy Details +datasource "ds_project_child_policy_information" do + request do + auth $auth_flexera + host rs_governance_host + path join(["/api/governance/projects/", rs_project_id, "/policy_templates"]) + header "Api-Version", "1.0" + end + result do + encoding "json" + # Select the uploaded policy that matches the name of the child policy template + collect jq(response, '.items[] | select(.name == "AWS CloudTrails With Read Logging Enabled")' ) do + field "name", jmes_path(col_item, "name") + field "href", jmes_path(col_item, "href") + field "short_description", jmes_path(col_item, "short_description") + end + end +end + +datasource "ds_get_billing_centers" do + request do + auth $auth_flexera + host rs_optima_host + path join(["/analytics/orgs/",rs_org_id,"/billing_centers"]) + header "Api-Version", "1.0" + header "User-Agent", "RS Policies" + query "view", "allocation_table" + ignore_status [403] + end + result do + encoding "json" + # Select the Billing Centers that have "parent_id" undefined or "" (i.e. top-level Billing Centers) + collect jq(response, '.[] | select(.parent_id == null)' ) do + field "href", jq(col_item,".href") + field "id", jq(col_item,".id") + field "name", jq(col_item,".name") + field "parent_id", jq(col_item,".parent_id") + end + end +end + +script "js_make_billing_center_request", type: "javascript" do + parameters "rs_org_id", "rs_optima_host", "billing_centers_unformatted", "param_dimension_filter_includes", "param_dimension_filter_excludes" + result "request" + code <<-EOS + + billing_centers_formatted = [] + + for (x=0; x< billing_centers_unformatted.length; x++) { + billing_centers_formatted.push(billing_centers_unformatted[x]["id"]) + } + + finish = new Date() + finishFormatted = finish.toJSON().split("T")[0] + start = new Date() + start.setDate(start.getDate() - 30) + startFormatted = start.toJSON().split("T")[0] + + // Default dimensions and filter expressions required for meta parent policy + var dimensions = ["vendor_account", "vendor_account_name"]; + var filter_expressions = [ + { dimension: "vendor", type: "equal", value: "AWS" } + ] + + // Append to default dimensions and filter expressions using parent policy params + _.each(param_dimension_filter_includes, function (v) { + // split key=value string + if (v.indexOf('=~') == -1) { + var split = v.split("="); + var type = "equal" + } else { + var split = v.split("=~"); + var type = "substring" + } + + var k = split[0]; + var v = split[1]; + + // append to lists + dimensions.push(k); + + if (type == "equal") { + filter_expressions.push({ dimension: k, type: "equal", value: v }); + } else { + filter_expressions.push({ dimension: k, type: "substring", substring: v }); + } + }); + + // Append to filter expressions using exclude policy params + _.each(param_dimension_filter_excludes, function (v) { + // split key=value string + if (v.indexOf('=~') == -1) { + var split = v.split("="); + var type = "equal" + } else { + var split = v.split("=~"); + var type = "substring" + } + + var k = split[0]; + var v = split[1]; + + // append to lists + dimensions.push(k); + + if (type == "equal") { + filter_expressions.push({ "type": "not", "expression": { "dimension": k, "type": "equal", "value": v } }); + } else { + filter_expressions.push({ "type": "not", "expression": { "dimension": k, "type": "substring", "substring": v } }); + } + }); + + // Produces a duplicate-free version of the array + dimensions = _.uniq(dimensions); + + var body = { + "dimensions": dimensions, + "granularity":"day", + "start_at": startFormatted, + "end_at": finishFormatted, + "metrics":["cost_amortized_unblended_adj"], + "billing_center_ids": billing_centers_formatted, + "filter": + { + "type": "and", + "expressions": filter_expressions + }, + "summarized": true + } + var request = { + auth: 'auth_flexera', + host: rs_optima_host, + scheme: 'https', + verb: 'POST', + path: "/bill-analysis/orgs/"+ rs_org_id + "/costs/aggregated", + headers: { + "API-Version": "1.0", + "Content-Type":"application/json" + }, + body: JSON.stringify(body) + } + EOS +end + +# Get the AWS acounts +datasource "ds_get_aws_accounts" do + request do + run_script $js_make_billing_center_request, rs_org_id, rs_optima_host, $ds_get_billing_centers, $param_dimension_filter_includes, $param_dimension_filter_excludes + end + result do + encoding "json" + collect jmes_path(response,"rows[*]") do + field "aws_account_id", jmes_path(col_item,"dimensions.vendor_account") + field "aws_account_name", jmes_path(col_item,"dimensions.vendor_account_name") + end + end +end + +# Get Child policies +datasource "ds_get_existing_policies" do + request do + auth $auth_flexera + host rs_governance_host + path join(["/api/governance/projects/", rs_project_id, "/applied_policies"]) + header "Api-Version", "1.0" + query "meta_parent_policy_id", policy_id + end + result do + collect jq(response, '.items[]?') do + field "name", jq(col_item, ".name") + field "applied_policy_id", jq(col_item, ".id") + field "options", jq(col_item, ".options") + field "updated_at", jq(col_item, ".updated_at") + field "status", jq(col_item, ".status") + end + end +end + +# Get Child policies incidents +datasource "ds_get_existing_policies_incidents" do + request do + auth $auth_flexera + host rs_governance_host + path join(["/api/governance/projects/", rs_project_id, "/incidents"]) + header "Api-Version", "1.0" + query "meta_parent_policy_id", policy_id + query "state", "triggered" + end + result do + collect jq(response, '.items[]?') do + field "incident_id", jq(col_item, ".id") + field "applied_policy_id", jq(col_item, ".applied_policy.id") + field "summary", jq(col_item, ".summary") + field "state", jq(col_item, ".state") + field "violation_data_count", jq(col_item, ".violation_data_count") + field "updated_at", jq(col_item, ".updated_at") + field "meta_parent_policy_id", jq(col_item, ".meta_parent_policy_id") + end + end +end + +datasource "ds_format_incidents" do + run_script $js_format_existing_policies_incidents, $ds_get_existing_policies_incidents +end + +script "js_format_existing_policies_incidents", type: "javascript" do + parameters "unformatted" + result "formatted" + code <<-EOS + formatted={} + + _.each(unformatted, function(incident) { + if (formatted[incident['applied_policy_id']] == undefined) { + formatted[incident['applied_policy_id']] = [] + } + + formatted[incident['applied_policy_id']].push(incident) + }) +EOS +end + +datasource "ds_format_existing_policies" do + run_script $js_format_existing_policies, $ds_get_existing_policies, $ds_format_incidents +end + +# format +# duplicates logic should compare updated at +# we can validate update here when destructring the existing policy options, don't need updated at +# format options +script "js_format_existing_policies", type: "javascript" do + parameters "ds_get_existing_policies", "ds_format_incidents" + result "result" + code <<-EOS + function format_options_keyvalue(options) { + var options_keyvalue_map = {}; + _.each(options, function(option) { + options_keyvalue_map[option.name] = option.value; + }); + return options_keyvalue_map; + } + + result = {} + formatted = {} + duplicates = [] + // tracking holds all existing policies and later can be used to determine if existing policies should be deleted [i.e. if cloud account was removed] + tracking = {} + + for (x=0; x newDate) { + duplicates.push({ + "applied_policy_id":ds_get_existing_policies[x]["applied_policy_id"], + "applied_policy_name":ds_get_existing_policies[x]["name"], + "status":ds_get_existing_policies[x]["status"], + "updated_at":ds_get_existing_policies[x]["updated_at"], + "incident": incident, + "incident2": incident2 + }) + } else { + duplicates.push({ + "applied_policy_id":current["applied_policy_id"], + "applied_policy_name":current["applied_policy_name"], + "status":current["status"], + "updated_at":current["updated_at"], + "incident": current["incident"], + "incident2": current["incident2"] + }) + formatted[aws_account_id] = { + "applied_policy_id":ds_get_existing_policies[x]["applied_policy_id"], + "applied_policy_name":ds_get_existing_policies[x]["name"], + "status":ds_get_existing_policies[x]["status"], + "updated_at":ds_get_existing_policies[x]["updated_at"], + "incident": incident, + "incident2": incident2, + "options": options + } + + } + } + } + + result.formatted=formatted + result.duplicates=duplicates + result.tracking=tracking + EOS +end + +datasource "ds_take_in_parameters" do + run_script $js_take_in_parameters, $ds_get_aws_accounts, $ds_format_self, first($ds_published_child_policy_information), first($ds_project_child_policy_information), $ds_format_existing_policies, $ds_child_policy_options, $ds_child_policy_options_map, $param_template_source, $param_policy_schedule, policy_id, f1_app_host, rs_org_id, rs_project_id +end + +# hardcode template href with id from catalog +# catalog policies show in customer's published templates with their org id +# "template_href": "/api/governance/orgs/" + rs_org_id + "/published_templates/62618616e3dff80001572bf0" +# update logic: the only reason we're going to update the child policies for is changes to options +# and only some options, email is always blank and aws_account_id is tied to the idenity of each policy, so: new account creation, removal of account: termination +# param_automatic_action is a list with only one action, unless the person is applying using an API and putting the same value multiple times this should either be a length of 0 or 1 +# param_log_to_cm_audit_entries is a String of Yes or No +# param_exclude_tags and param_allowed_regions are arrays. I'm doing an update on the order changing but the values remaining the same. +# If we only want to do an update on the values changing we could sort before doing the equality check. +script "js_take_in_parameters", type: "javascript" do + parameters "ds_get_aws_accounts", "ds_format_self", "ds_published_child_policy_information", "ds_project_child_policy_information", "ds_format_existing_policies", "ds_child_policy_options", "ds_child_policy_options_map", "param_template_source", "param_policy_schedule", "meta_parent_policy_id", "f1_app_host", "rs_org_id", "rs_project_id" + result "grid_and_cwf" + code <<-EOS + + // Set Child Policy Information based on param_template_source value + if (param_template_source == "Published Catalog Template") { + child_policy_information = ds_published_child_policy_information + } else { + child_policy_information = ds_project_child_policy_information + } + + max_actions = 50; + + grid_and_cwf={grid:[], to_create:[], to_update:[], to_delete:[], parent_policy:ds_format_self}; + + should_keep = ds_format_existing_policies.tracking; + + // Construct UI URL prefixes for policy template summary + ui_url_prefix = "https://" + f1_app_host + "/orgs/" + rs_org_id; + applied_policy_url_prefix = ui_url_prefix + "/automation/applied-policies/projects/" + rs_project_id + "?noIndex=1&policyId="; + incident_url_prefix = ui_url_prefix + "/automation/incidents/projects/" + rs_project_id + "?noIndex=1&incidentId="; + + function add_to_grid(ep, action) { + policy_status={ + "id": ep["applied_policy_id"], + "policy_name": ep["applied_policy_name"] + '||' + applied_policy_url_prefix + ep["applied_policy_id"], + "meta_policy_status": action, + "policy_status": ep["status"], + "policy_last_update": ep["updated_at"], + }; + + if (ep.incident != null && ep.incident != undefined) { + // Remove policy name from summary when applicable + summary_parts = ep.incident.summary.split(':') + summary = summary_parts[summary_parts.length - 1].trim() + + policy_status["incident_summary"] = summary + '||' + incident_url_prefix + ep.incident.incident_id; + policy_status["incident_state"] = ep.incident.state; + policy_status["incident_violation_data_count"] = ep.incident.violation_data_count; + policy_status["incident_last_update"] = ep.incident.updated_at; + } + + if (ep.incident2 != null && ep.incident2 != undefined) { + // Remove policy name from summary when applicable + summary_parts = ep.incident2.summary.split(':') + summary = summary_parts[summary_parts.length - 1].trim() + + policy_status["incident_summary"] = summary + '||' + incident_url_prefix + ep.incident2.incident_id; + policy_status["incident_state"] = ep.incident2.state; + policy_status["incident2_violation_data_count"] = ep.incident2.violation_data_count; + policy_status["incident2_last_update"] = ep.incident2.updated_at; + } + + grid_and_cwf.grid.push(policy_status); + } + + for (x=0; x -1) { + _.each(incident["violation_data"], function(violation) { + violation["incident_id"] = incident["id"]; + result.push(violation); + }); + } + }); +EOS +end + + +# Escalation for Disable Read Logging +escalation "esc_disable_read_logging" do + automatic false # Do not automatically action from meta parent. the child will handle automatic escalations if param is set + label "Disable Read Logging" + description "Approval to disable read logging on all selected CloudTrails" + + # Run declaration should go at end, after any parameters that may exist + run "esc_disable_read_logging", data, rs_governance_host, rs_project_id +end +define esc_disable_read_logging($data, $governance_host, $rs_project_id) do + $actions_options = [] + call child_run_action($data, $governance_host, $rs_project_id, "Disable Read Logging", $action_options) +end + + +# Summary and a conditional incident which will show up if any policy is being applied, updated or deleted. +# Minimum of 1 incident, max of four +# Could swap the summary to only showing running +# Could also just have one incident and use meta_status to determine which escalation happens +policy "policy_scheduled_report" do + # Consolidated Incident Check(s) + # Consolidated incident for AWS CloudTrails With Read Logging Enabled Found + validate $ds_trails_incident_combined_incidents do + summary_template "Consolidated Incident: {{ len data }} AWS CloudTrails With Read Logging Enabled Found" + escalate $esc_email + escalate $esc_disable_read_logging + check eq(size(data), 0) + export do + resource_level true + field "accountID" do + label "Account ID" + end + field "accountName" do + label "Account Name" + end + field "name" do + label "Name" + end + field "id" do + label "ARN" + end + field "region" do + label "Region" + end + field "logging_read" do + label "Read Logging" + end + field "logging_write" do + label "Write Logging" + end + field "has_selectors" do + label "Has Event Selectors?" + end + field "event_selectors" do + label "Current Event Selectors" + end + field "fixed_selectors" do + label "Recommended Event Selectors" + end + field "fixed_selectors_object" do + label "Recommend + field "incident_id" do + label "Child Incident ID" + end + end + end + + # Status Incident Check + validate $ds_take_in_parameters do + summary_template "{{ data.parent_policy.name }}: Status of Child Policies" + detail_template <<-EOS +The current status of Child Policies for **{{ data.parent_policy.name }}**: + +Total Child Applied Policies: {{ len data.grid }} +EOS + check false # always trigger this status incident + export "grid" do + resource_level true + field "id" do + label "Applied Policy ID" + end + field "policy_name" do + label "Applied Policy Name" + format "link-external" + end + field "meta_policy_status" do + label "Meta Child Policy Status" + end + field "policy_status" do + label "Policy Status" + end + field "policy_last_update" do + label "Policy Last Update" + end + field "incident_summary" do + label "Incident Summary" + format "link-external" + end + field "incident_state" do + label "Incident State" + end + field "incident_violation_data_count" do + label "Incident Violation Count" + end + field "incident_last_update" do + label "Incident Last Update" + end + field "incident2_summary" do + label "Incident 2 Summary" + format "link-external" + end + field "incident2_state" do + label "Incident 2 State" + end + field "incident2_violation_data_count" do + label "Incident 2 Violation Count" + end + field "incident2_last_update" do + label "Incident 2 Last Update" + end + end + end + + # Create Child Policies Incident Check + validate $ds_to_create do + summary_template "Policies being created" + detail_template <<-EOS + Policies Being Created: + + | Applied Policy | + | --------------- | + {{ range data -}} + | {{ .name }} | + {{ end -}} + EOS + escalate $create_policies + check eq(size(data),0) + end + + # Update Child Policies Incident Check + validate $ds_to_update do + summary_template "Policies being updated" + detail_template <<-EOS + Policies Being Updated: + + | Applied Policy | + | --------------- | + {{ range data -}} + | {{ .name }} | + {{ end -}} + EOS + escalate $update_policies + check eq(size(data),0) + end + + # Delete Child Policies Incident Check + validate $ds_to_delete do + summary_template "Policies being deleted" + detail_template <<-EOS + Policies being Deleted: + + | Applied Policy | + | --------------- | + {{ range data -}} + | {{ .name }} | + {{ end -}} + EOS + escalate $delete_policies + check eq(size(data),0) + end +end + +# Begin Shared Functions for Child Actions from Consolidated Incident +define groupByIncidentID($data) return $incidents do + # Empty hash to store incidents is incident_id + $incidents = {} + + task_label("Grouping items by Incident ID") + $index = 1 + foreach $item in $data do + task_label("Grouping items by Incident ID. "+to_s($index)+"/"+to_s(size($data))) + if !$incidents[$item["incident_id"]] + #task_label("Grouping items by Incident ID. "+to_s($index)+"/"+to_s(size($data))". New Incident: "+$item["incident_id"]) + $incidents[$item["incident_id"]] = {"id": $item["incident_id"], "resource_ids": []} + end + #task_label("Grouping items by Incident ID. "+to_s($index)+"/"+to_s(size($data))". Appending Resource: "+$item["id"]) + # Append resource id to the list for the incident + $incidents[$item["incident_id"]]["resource_ids"] = $incidents[$item["incident_id"]]["resource_ids"] + [$item["id"]] + end +end + +define child_run_action($data, $governance_host, $rs_project_id, $action_label, $action_options) do + # Empty global array for log strings, helpful for debugging + $$debug = [] + + # Group Resources by Incident ID + # This reduces the number of requests made to the Flexera API + call groupByIncidentID($data) retrieve $incidents + $$debug_incidents = to_json($incidents) + + call runActions($incidents, $action_label, $governance_host, $rs_project_id, $action_options) + + # If we encountered any errors, use `raise` to mark the CWF process as errored + if inspect($$errors) != "null" + raise join($$errors,"\n") + end + + # If we made it here, all actions completed successfully + # Celebrate Success! + task_label("All \""+$action_label+"\" actions completed successfully!") +end + +define runActions($incidents, $action_label, $governance_host, $rs_project_id, $action_options) do + foreach $id in keys($incidents) do + sub on_error: handle_error() do + $incident = $incidents[$id] + task_label("Triggering action \""+$action_label+"\" on "+size($incident["resource_ids"])+" count resources via incident "+$incident["id"]) + $request = { + auth: $$auth_flexera, + verb: "get", + https: true, + host: $governance_host, + href: join(["/api/governance/projects/", $rs_project_id, "/incidents/", $incident["id"]]), + headers: { "Api-Version": "1.0" }, + query_strings: { "view": "extended" } + } + $response = http_request($request) + $$debug << to_json({ + "request": $request, + "response": $response + }) + $action_id = "" + foreach $action in $response["body"]["available_actions"] do + # If we have not already found the action id, and the label matches, set the action id + # The first check is to prevent looking through the entire list if we already have the id + if $action["label"] == $action_label + $action_id = $action["id"] + end + end + if $action_id == "" + raise "Could not find action id for \""+$action_label+"\" response="+to_json($response) + end + # Now we are reach to trigger the action + $request = { + auth: $$auth_flexera, + verb: "post", + https: true, + host: $governance_host, + href: join(["/api/governance/projects/", $rs_project_id, "/incidents/", $incident["id"],"/actions/", $action_id,"/run_action"]), + headers: { "Api-Version": "1.0" }, + body: { "options":[{ "name": "ids", "value": $incident["resource_ids"] }] } + } + # If the action has parameters, add them to the request body + if type($action_options) == "array" && size($action_options) > 0 + $request["body"]["options"] = $request["body"]["options"] + $action_options + end + $response = http_request($request) + $$debug << to_json({ + "request": $request, + "response": $response + }) + # Get the action status from response header + $action_location = $response["headers"]["Location"] + + # Setup some variables for the wait loop + $action_status = "" + $loop_count = 0 + $loop_endtime = now() + (3600*2) # 2 hours from now + # [ queued, aborted, pending, running, completed, failed, denied ] + while ($action_status !~ /^(aborted|completed|failed|denied)/) && (now() <= $loop_endtime) do + # Using Loop Count to slowly increment the sleep time + # This is to prevent the loop from hammering our APIs + $loop_count = $loop_count + 1 + task_label("action_status=\""+$action_status+"\" Sleeping for "+to_s($loop_count)+" seconds") + sleep($loop_count) + task_label("action_status=\""+$action_status+"\" Getting action status") + $request = { + auth: $$auth_flexera, + verb: "get", + https: true, + host: $governance_host, + href: $action_location, + headers: { "Api-Version": "1.0" }, + query_strings: { "view": "extended" } + } + $response = http_request($request) + $$debug << to_json({ + "request": $request, + "response": $response + }) + $action_status = $response["body"]["status"] + end + if ($action_status != "completed") + # Check if we are out of time first + if (now() > $loop_endtime) + raise "action_status=\""+$action_status+"\" Action did not complete in time. Aborting to prevent endless loop. action_status_json="+to_json($response) + else + # If not, then it was aborted, failed or denied + raise "action_status=\""+$action_status+"\" Action did not complete as expected. action_status_json="+to_json($response) + end + end + # If we made it here, the action completed successfully + task_label("action_status=\""+$action_status+"\" Action completed successfully") + end + end +end +# End Shared Functions for Child Actions from Consolidated Incident + +# CWF function to handle errors +define handle_error() do + if !$$errors + $$errors = [] + end + $$errors << $_error["type"] + ": " + $_error["message"] + # We check for errors at the end, and raise them all together + # Skip errors handled by this definition + $_error_behavior = "skip" +end + +# Used only for emailing the combined child incident if so desired +escalation "esc_email" do + automatic true + label "Send Email" + description "Send incident email" + email $param_combined_incident_email +end + +escalation "create_policies" do + run "create_applied_policies", data, rs_governance_host, rs_project_id +end + +# if name !=null +define create_applied_policies($data, $governance_host, $rs_project_id) return $responses do + $responses = [] + $$debug = [] + $item_index = 0 + $item_total = size($data) + foreach $item in $data do + $item_index = $item_index + 1 + $status = to_s("("+$item_index+"/"+$item_total+")") + task_label($status+" Creating Applied Policy with Options: " + to_json($item["options"])) + $response = http_request( + auth: $$auth_flexera, + verb: "post", + https: true, + host: $governance_host, + href: join(["/api/governance/projects/", $rs_project_id, "/applied_policies"]), + headers: { "Api-Version": "1.0" }, + body: { + "name": $item["name"], + "description": $item["description"], + "template_href": $item["template_href"], + "frequency": $item["frequency"], + "options": $item["options"], + "credentials": $item["credentials"], + "meta_parent_policy_id": $item["meta_parent_policy_id"] + } + ) + $responses << $response + $$debug << to_json({ + "response": $response, + "item": $item, + "governance_host": $governance_host + }) + end +end + +escalation "update_policies" do + run "update_applied_policies", data, rs_governance_host, rs_project_id +end + +define update_applied_policies($data, $governance_host, $rs_project_id) return $responses do + $responses = [] + $$debug = [] + $item_index = 0 + $item_total = size($data) + foreach $item in $data do + $item_index = $item_index + 1 + $status = to_s("("+$item_index+"/"+$item_total+")") + task_label($status+" Updating Applied Policy with Options: " + to_json($item["options"])) + $response = http_request( + auth: $$auth_flexera, + verb: "patch", + https: true, + host: $governance_host, + href: join(["/api/governance/projects/", $rs_project_id, "/applied_policies/", $item["applied_policy_id"]]), + headers: { "Api-Version": "1.0" }, + body: { + "options": $item["options"] + } + ) + $responses << $response + $$debug << to_json({ + "response": $response, + "item": $item, + "governance_host": $governance_host + }) + end +end + +escalation "delete_policies" do + run "delete_applied_policies", data, rs_governance_host, rs_project_id +end + +define delete_applied_policies($data, $governance_host, $rs_project_id) return $responses do + $responses = [] + $$debug = [] + $item_index = 0 + $item_total = size($data) + foreach $item in $data do + $item_index = $item_index + 1 + $status = to_s("("+$item_index+"/"+$item_total+")") + task_label($status+" Deleting Applied Policy: " + $item["id"]) + $response = http_request( + auth: $$auth_flexera, + verb: "delete", + https: true, + host: $governance_host, + href: join(["/api/governance/projects/", $rs_project_id, "/applied_policies/", $item["id"]]), + headers: { "Api-Version": "1.0" } + ) + $responses << $response + $$debug << to_json({ + "response": $response, + "item": $item, + "governance_host": $governance_host + }) + end +end diff --git a/data/policy_permissions_list/master_policy_permissions_list.json b/data/policy_permissions_list/master_policy_permissions_list.json index 5983f009d7..4fb440b601 100644 --- a/data/policy_permissions_list/master_policy_permissions_list.json +++ b/data/policy_permissions_list/master_policy_permissions_list.json @@ -1381,6 +1381,49 @@ } ] }, + { + "id": "./cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt", + "name": "AWS CloudTrails With Read Logging Enabled", + "version": "0.1.0", + "providers": [ + { + "name": "aws", + "permissions": [ + { + "name": "sts:GetCallerIdentity", + "read_only": true, + "required": true + }, + { + "name": "cloudtrail:DescribeTrails", + "read_only": true, + "required": true + }, + { + "name": "cloudtrail:GetEventSelectors", + "read_only": true, + "required": true + }, + { + "name": "cloudtrail:PutEventSelectors", + "read_only": false, + "required": false, + "description": "Only required for taking action; the policy will still function in a read-only capacity without these permissions." + } + ] + }, + { + "name": "flexera", + "permissions": [ + { + "name": "billing_center_viewer", + "read_only": true, + "required": true + } + ] + } + ] + }, { "id": "./cost/aws/eks_without_spot/aws_eks_without_spot.pt", "name": "AWS EKS Clusters Without Spot Instances", diff --git a/data/policy_permissions_list/master_policy_permissions_list.yaml b/data/policy_permissions_list/master_policy_permissions_list.yaml index c64e8fa28d..6372bec248 100644 --- a/data/policy_permissions_list/master_policy_permissions_list.yaml +++ b/data/policy_permissions_list/master_policy_permissions_list.yaml @@ -797,6 +797,31 @@ - name: billing_center_viewer read_only: true required: true +- id: "./cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt" + name: AWS CloudTrails With Read Logging Enabled + version: 0.1.0 + :providers: + - :name: aws + :permissions: + - name: sts:GetCallerIdentity + read_only: true + required: true + - name: cloudtrail:DescribeTrails + read_only: true + required: true + - name: cloudtrail:GetEventSelectors + read_only: true + required: true + - name: cloudtrail:PutEventSelectors + read_only: false + required: false + description: Only required for taking action; the policy will still function + in a read-only capacity without these permissions. + - :name: flexera + :permissions: + - name: billing_center_viewer + read_only: true + required: true - id: "./cost/aws/eks_without_spot/aws_eks_without_spot.pt" name: AWS EKS Clusters Without Spot Instances version: 0.1.0 diff --git a/tools/meta_parent_policy_compiler/meta_parent_policy_compiler.rb b/tools/meta_parent_policy_compiler/meta_parent_policy_compiler.rb index 98efaadfc3..3b55d42155 100644 --- a/tools/meta_parent_policy_compiler/meta_parent_policy_compiler.rb +++ b/tools/meta_parent_policy_compiler/meta_parent_policy_compiler.rb @@ -15,6 +15,7 @@ "../../compliance/aws/rds_backup/aws_rds_backup.pt", "../../compliance/aws/untagged_resources/aws_untagged_resources.pt", "../../cost/aws/burstable_ec2_instances/aws_burstable_ec2_instances.pt", + "../../cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt", "../../cost/aws/eks_without_spot/aws_eks_without_spot.pt", "../../cost/aws/gp3_volume_upgrade/aws_upgrade_to_gp3_volume.pt", "../../cost/aws/idle_compute_instances/idle_compute_instances.pt", diff --git a/tools/policy_master_permission_generation/validated_policy_templates.yaml b/tools/policy_master_permission_generation/validated_policy_templates.yaml index e089f8c74d..b0b2c84082 100644 --- a/tools/policy_master_permission_generation/validated_policy_templates.yaml +++ b/tools/policy_master_permission_generation/validated_policy_templates.yaml @@ -16,6 +16,7 @@ validated_policy_templates: - "./compliance/aws/untagged_resources/aws_untagged_resources.pt" - "./cost/aws/burstable_ec2_instances/aws_burstable_ec2_instances.pt" - "./cost/aws/cheaper_regions/aws_cheaper_regions.pt" +- "./cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt" - "./cost/aws/eks_without_spot/aws_eks_without_spot.pt" - "./cost/aws/extended_support/aws_extended_support.pt" - "./cost/aws/gp3_volume_upgrade/aws_upgrade_to_gp3_volume.pt" From 3b1c72e896e63148ec329e6a15523c9af0a2aea7 Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Fri, 9 Aug 2024 13:25:55 -0500 Subject: [PATCH 04/10] fix --- cost/aws/cloudtrail_read_logging/CHANGELOG.md | 5 - cost/aws/cloudtrail_read_logging/README.md | 62 - .../aws_cloudtrail_read_logging.pt | 517 ------- ...aws_cloudtrail_read_logging_meta_parent.pt | 1244 ----------------- .../meta_parent_policy_compiler.rb | 1 - .../validated_policy_templates.yaml | 1 - 6 files changed, 1830 deletions(-) delete mode 100644 cost/aws/cloudtrail_read_logging/CHANGELOG.md delete mode 100644 cost/aws/cloudtrail_read_logging/README.md delete mode 100644 cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt delete mode 100644 cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging_meta_parent.pt diff --git a/cost/aws/cloudtrail_read_logging/CHANGELOG.md b/cost/aws/cloudtrail_read_logging/CHANGELOG.md deleted file mode 100644 index 341687ee97..0000000000 --- a/cost/aws/cloudtrail_read_logging/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -# Changelog - -## v0.1.0 - -- Initial Release diff --git a/cost/aws/cloudtrail_read_logging/README.md b/cost/aws/cloudtrail_read_logging/README.md deleted file mode 100644 index 97a9fa17e2..0000000000 --- a/cost/aws/cloudtrail_read_logging/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# AWS CloudTrails With Read Logging Enabled - -## What It Does - -This policy template reports any AWS CloudTrail trails that log read actions. Logging read actions can greatly increase storage costs in some cases. Optionally, this report can be emailed and read logging can be disabled. - -## Input Parameters - -- *Email Addresses* - Email addresses of the recipients you wish to notify. -- *Account Number* - The Account number for use with the AWS STS Cross Account Role. Leave blank when using AWS IAM Access key and secret. It only needs to be passed when the desired AWS account is different than the one associated with the Flexera One credential. [More information is available in our documentation.](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_1982464505_1123608) -- *Allow/Deny Regions* - Whether to treat Allow/Deny Regions List parameter as allow or deny list. Has no effect if Allow/Deny Regions List is left empty. -- *Allow/Deny Regions List* - A list of regions to allow or deny for an AWS account. Please enter the regions code if SCP is enabled. See [Available Regions](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions) in AWS; otherwise, the policy may fail on regions that are disabled via SCP. Leave blank to consider all the regions. - -## Policy Actions - -- Sends an email notification. -- Disables read logging after approval. - -## Prerequisites - -This Policy Template uses [Credentials](https://docs.flexera.com/flexera/EN/Automation/ManagingCredentialsExternal.htm) for authenticating to datasources -- in order to apply this policy you must have a Credential registered in the system that is compatible with this policy. If there are no Credentials listed when you apply the policy, please contact your Flexera Org Admin and ask them to register a Credential that is compatible with this policy. The information below should be consulted when creating the credential(s). - -- [**AWS Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_1982464505_1121575) (*provider=aws*) which has the following permissions: - - `sts:GetCallerIdentity` - - `cloudtrail:DescribeTrails` - - `cloudtrail:GetEventSelectors` - - `cloudtrail:PutEventSelectors`* - - \* Only required for taking action; the policy will still function in a read-only capacity without these permissions. - - Example IAM Permission Policy: - - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "sts:GetCallerIdentity", - "cloudtrail:DescribeTrails", - "cloudtrail:GetEventSelectors", - "cloudtrail:PutEventSelectors" - ], - "Resource": "*" - } - ] - } - ``` - -- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: - - `billing_center_viewer` - -The [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) page in the docs has detailed instructions for setting up Credentials for the most common providers. - -## Supported Clouds - -- AWS - -## Cost - -This Policy Template does not incur any cloud costs. diff --git a/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt b/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt deleted file mode 100644 index 836aecb1da..0000000000 --- a/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt +++ /dev/null @@ -1,517 +0,0 @@ -name "AWS CloudTrails With Read Logging Enabled" -rs_pt_ver 20180301 -type "policy" -short_description "Reports AWS CloudTrail trails that are logging read events. See the [README](https://github.com/flexera-public/policy_templates/tree/master/cost/aws/cloudwatch_read_logging) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." -long_description "" -category "Cost" -severity "low" -default_frequency "weekly" -info( - version: "0.1.0", - provider: "AWS", - service: "CloudTrail", - policy_set: "Logging" -) - -############################################################################### -# Parameters -############################################################################### - -parameter "param_email" do - type "list" - category "Policy Settings" - label "Email Addresses" - description "A list of email addresses to notify." - default [] -end - -parameter "param_aws_account_number" do - type "string" - category "Policy Settings" - label "Account Number" - description "Leave blank; this is for automated use with Meta Policies. See README for more details." - default "" -end - -parameter "param_regions_allow_or_deny" do - type "string" - category "Filters" - label "Allow/Deny Regions" - description "Allow or Deny entered regions. See the README for more details" - allowed_values "Allow", "Deny" - default "Allow" -end - -parameter "param_regions_list" do - type "list" - category "Filters" - label "Allow/Deny Regions List" - description "A list of allowed or denied regions. See the README for more details" - allowed_pattern /^([a-zA-Z-_]+-[a-zA-Z0-9-_]+-[0-9-_]+,*|)+$/ - default [] -end - -parameter "param_automatic_action" do - type "list" - category "Actions" - label "Automatic Actions" - description "When this value is set, this policy will automatically take the selected action(s)" - allowed_values ["Disable Read Logging"] - default [] -end - -############################################################################### -# Authentication -############################################################################### - -credentials "auth_aws" do - schemes "aws", "aws_sts" - label "AWS" - description "Select the AWS Credential from the list" - tags "provider=aws" - aws_account_number $param_aws_account_number -end - -credentials "auth_flexera" do - schemes "oauth2" - label "Flexera" - description "Select Flexera One OAuth2 credentials" - tags "provider=flexera" -end - -############################################################################### -# Datasources & Scripts -############################################################################### - -# Get applied policy metadata for use later -datasource "ds_applied_policy" do - request do - auth $auth_flexera - host rs_governance_host - path join(["/api/governance/projects/", rs_project_id, "/applied_policies/", policy_id]) - header "Api-Version", "1.0" - end -end - -# Get region-specific Flexera API endpoints -datasource "ds_flexera_api_hosts" do - run_script $js_flexera_api_hosts, rs_optima_host -end - -script "js_flexera_api_hosts", type: "javascript" do - parameters "rs_optima_host" - result "result" - code <<-'EOS' - host_table = { - "api.optima.flexeraeng.com": { - flexera: "api.flexera.com", - fsm: "api.fsm.flexeraeng.com" - }, - "api.optima-eu.flexeraeng.com": { - flexera: "api.flexera.eu", - fsm: "api.fsm-eu.flexeraeng.com" - }, - "api.optima-apac.flexeraeng.com": { - flexera: "api.flexera.au", - fsm: "api.fsm-apac.flexeraeng.com" - } - } - - result = host_table[rs_optima_host] -EOS -end - -# Get AWS account info -datasource "ds_cloud_vendor_accounts" do - request do - auth $auth_flexera - host val($ds_flexera_api_hosts, 'flexera') - path join(["/finops-analytics/v1/orgs/", rs_org_id, "/cloud-vendor-accounts"]) - header "Api-Version", "1.0" - end - result do - encoding "json" - collect jmes_path(response, "values[*]") do - field "id", jmes_path(col_item, "aws.accountId") - field "name", jmes_path(col_item, "name") - field "tags", jmes_path(col_item, "tags") - end - end -end - -datasource "ds_get_caller_identity" do - request do - auth $auth_aws - verb "GET" - host "sts.amazonaws.com" - path "/" - query "Action", "GetCallerIdentity" - query "Version", "2011-06-15" - header "User-Agent", "RS Policies" - end - result do - encoding "xml" - collect xpath(response, "//GetCallerIdentityResponse/GetCallerIdentityResult") do - field "account", xpath(col_item, "Account") - end - end -end - -datasource "ds_aws_account" do - run_script $js_aws_account, $ds_cloud_vendor_accounts, $ds_get_caller_identity -end - -script "js_aws_account", type:"javascript" do - parameters "ds_cloud_vendor_accounts", "ds_get_caller_identity" - result "result" - code <<-'EOS' - result = _.find(ds_cloud_vendor_accounts, function(account) { - return account['id'] == ds_get_caller_identity[0]['account'] - }) - - // This is in case the API does not return the relevant account info - if (result == undefined) { - result = { - id: ds_get_caller_identity[0]['account'], - name: "", - tags: {} - } - } -EOS -end - -datasource "ds_trail_list" do - request do - auth $auth_aws - host "cloudtrail.us-east-1.amazonaws.com" - path "/" - query "Action", "DescribeTrails" - header "User-Agent", "RS Policies" - header "Accept", "application/json" - # Header X-Meta-Flexera has no affect on datasource query, but is required for Meta Policies - # Forces `ds_is_deleted` datasource to run first during policy execution - header "Meta-Flexera", val($ds_is_deleted, "path") - end - result do - encoding "json" - collect jmes_path(response, "DescribeTrailsResponse.DescribeTrailsResult.trailList") do - field "name", jmes_path(col_item, "Name") - field "arn", jmes_path(col_item, "TrailARN") - field "region", jmes_path(col_item, "HomeRegion") - field "s3_bucket", jmes_path(col_item, "S3BucketName") - end - end -end - -datasource "ds_trail_list_region_filtered" do - run_script $js_trail_list_region_filtered, $ds_trail_list, $param_regions_list, $param_regions_allow_or_deny -end - -script "js_trail_list_region_filtered", type:"javascript" do - parameters "ds_trail_list", "param_regions_list", "param_regions_allow_or_deny" - result "result" - code <<-EOS - allow_deny_test = { "Allow": true, "Deny": false } - - if (param_regions_list.length > 0) { - result = _.filter(ds_trail_list, function(item) { - return _.contains(param_regions_list, item['region']) == allow_deny_test[param_regions_allow_or_deny] - }) - } else { - result = ds_trail_list - } -EOS -end - -datasource "ds_trail_list_with_selectors" do - iterate $ds_trail_list_region_filtered - request do - auth $auth_aws - verb "POST" - host join(["cloudtrail.", val(iter_item, "region"), ".amazonaws.com"]) - path "/" - header "User-Agent", "RS Policies" - header "X-Amz-Target", "com.amazonaws.cloudtrail.v20131101.CloudTrail_20131101.GetEventSelectors" - header "Content-Type", "application/x-amz-json-1.1" - body_field "TrailName", val(iter_item, "arn") - end - result do - encoding "json" - field "event_selectors", jmes_path(response, "EventSelectors") - field "name", val(iter_item, "name") - field "arn", val(iter_item, "arn") - field "region", val(iter_item, "region") - field "s3_bucket", val(iter_item, "s3_bucket") - end -end - -datasource "ds_trails_incident" do - run_script $js_trails_incident, $ds_trail_list_with_selectors, $ds_aws_account, $ds_applied_policy -end - -script "js_trails_incident", type:"javascript" do - parameters "ds_trail_list_with_selectors", "ds_aws_account", "ds_applied_policy" - result "result" - code <<-EOS - result = [] - - _.each(ds_trail_list_with_selectors, function(trail) { - has_selectors = typeof(trail['event_selectors']) == 'object' - logging_read = false - logging_write = false - logging_all = false - event_selectors = [] - fixed_selectors = [] - - if (has_selectors) { - event_selectors = trail['event_selectors'] - selector_types = _.pluck(event_selectors, 'ReadWriteType') - logging_all = _.contains(selector_types, 'All') - logging_read = _.contains(selector_types, 'ReadOnly') || logging_all - logging_write = _.contains(selector_types, 'WriteOnly') || logging_all - - _.each(event_selectors, function(selector) { - if (selector['ReadWriteType'] == 'All' || selector['ReadWriteType'] == 'WriteOnly') { - fixed_selectors.push({ - "DataResources": selector['DataResources'], - "ExcludeManagementEventSources": selector['ExcludeManagementEventSources'], - "IncludeManagementEvents": selector['IncludeManagementEvents'], - "ReadWriteType": "WriteOnly" - }) - } - }) - } - - if (logging_read) { - result.push({ - accountID: ds_aws_account['id'], - accountName: ds_aws_account['name'], - name: trail['name'], - id: trail['arn'], - region: trail['region'], - has_selectors: has_selectors, - logging_read: logging_read, - logging_write: logging_write, - logging_all: logging_all, - event_selectors: JSON.stringify(event_selectors), - fixed_selectors: JSON.stringify(fixed_selectors), - fixed_selectors_object: fixed_selectors, - policy_name: ds_applied_policy['name'] - }) - } - }) -EOS -end - -############################################################################### -# Policy -############################################################################### - -policy "pol_trails_incident" do - validate_each $ds_trails_incident do - summary_template "{{ with index data 0 }}{{ .policy_name }}{{ end }}: {{ len data }} AWS CloudTrails With Read Logging Enabled Found" - check logic_or($ds_parent_policy_terminated, eq(val(item, "id"), "")) - escalate $esc_email - escalate $esc_disable_read_logging - export do - resource_level true - field "accountID" do - label "Account ID" - end - field "accountName" do - label "Account Name" - end - field "name" do - label "Name" - end - field "id" do - label "ARN" - end - field "region" do - label "Region" - end - field "logging_read" do - label "Read Logging" - end - field "logging_write" do - label "Write Logging" - end - field "has_selectors" do - label "Has Event Selectors?" - end - field "event_selectors" do - label "Current Event Selectors" - end - field "fixed_selectors" do - label "Recommended Event Selectors" - end - field "fixed_selectors_object" do - label "Recommended Event Selectors (Object)" - end - end - end -end - -############################################################################### -# Escalations -############################################################################### - -escalation "esc_email" do - automatic true - label "Send Email" - description "Send incident email" - email $param_email -end - -escalation "esc_disable_read_logging" do - automatic contains($param_automatic_action, "Disable Read Logging") - label "Disable Read Logging" - description "Approval to disable read logging on all selected CloudTrails" - run "disable_read_logging_on_trails", data -end - -############################################################################### -# Cloud Workflow -############################################################################### - -define disable_read_logging_on_trails($data) return $all_responses do - $$all_responses = [] - - foreach $clb in $data do - sub on_error: handle_error() do - call disable_read_logging_on_trail($trail) retrieve $response - end - end - - if inspect($$errors) != "null" - raise join($$errors, "\n") - end -end - -define disable_read_logging_on_trail($trail) return $response do - $host = "cloudtrail." + $trail["region"] + ".amazonaws.com" - $href = "/" - $url = $host + $href - task_label("POST " + $url) - - $response = http_request( - auth: $$auth_aws, - https: true, - verb: "post", - host: $host, - href: $href, - body: { - "TrailName": $trail["name"], - "EventSelectors": $trail["fixed_selectors_object"] - } - ) - - task_label("POST AWS CloudTrail response: " + $trail["name"] + " " + to_json($response)) - $$all_responses << to_json({"req": "POST " + $url, "resp": $response}) - - if $response["code"] != 204 && $response["code"] != 202 && $response["code"] != 200 - raise "Unexpected response from POST AWS CloudTrail: "+ $trail["name"] + " " + to_json($response) - else - task_label("POST AWS CloudTrail successful: " + $trail["name"]) - end -end - -define handle_error() do - if !$$errors - $$errors = [] - end - $$errors << $_error["type"] + ": " + $_error["message"] - # We check for errors at the end, and raise them all together - # Skip errors handled by this definition - $_error_behavior = "skip" -end - -############################################################################### -# Meta Policy [alpha] -# Not intended to be modified or used by policy developers -############################################################################### - -# If the meta_parent_policy_id is not set it will evaluate to an empty string and we will look for the policy itself, -# if it is set we will look for the parent policy. -datasource "ds_get_policy" do - request do - auth $auth_flexera - host rs_governance_host - ignore_status [404] - path join(["/api/governance/projects/", rs_project_id, "/applied_policies/", switch(ne(meta_parent_policy_id,""), meta_parent_policy_id, policy_id) ]) - header "Api-Version", "1.0" - end - result do - encoding "json" - field "id", jmes_path(response, "id") - end -end - -datasource "ds_parent_policy_terminated" do - run_script $js_decide_if_self_terminate, $ds_get_policy, policy_id, meta_parent_policy_id -end - -# If the policy was applied by a meta_parent_policy we confirm it exists if it doesn't we confirm we are deleting -# This information is used in two places: -# - determining whether or not we make a delete call -# - determining if we should create an incident (we don't want to create an incident on the run where we terminate) -script "js_decide_if_self_terminate", type: "javascript" do - parameters "found", "self_policy_id", "meta_parent_policy_id" - result "result" - code <<-EOS - var result - if (meta_parent_policy_id != "" && found.id == undefined) { - result = true - } else { - result = false - } - EOS -end - -# Two potentials ways to set this up: -# - this way and make a unneeded 'get' request when not deleting -# - make the delete request an interate and have it iterate over an empty array when not deleting and an array with one item when deleting -script "js_make_terminate_request", type: "javascript" do - parameters "should_delete", "policy_id", "rs_project_id", "rs_governance_host" - result "request" - code <<-EOS - - var request = { - auth: 'auth_flexera', - host: rs_governance_host, - path: "/api/governance/projects/" + rs_project_id + "/applied_policies/" + policy_id, - headers: { - "API-Version": "1.0", - "Content-Type":"application/json" - }, - } - - if (should_delete) { - request.verb = 'DELETE' - } - EOS -end - -datasource "ds_terminate_self" do - request do - run_script $js_make_terminate_request, $ds_parent_policy_terminated, policy_id, rs_project_id, rs_governance_host - end -end - -datasource "ds_is_deleted" do - run_script $js_check_deleted, $ds_terminate_self -end - -# This is just a way to have the check delete request connect to the farthest leaf from policy. -# We want the delete check to the first thing the policy does to avoid the policy erroring before it can decide whether or not it needs to self terminate -# Example a customer deletes a credential and then terminates the parent policy. We still want the children to self terminate -# The only way I could see this not happening is if the user who applied the parent_meta_policy was offboarded or lost policy access, the policies who are impersonating the user -# would not have access to self-terminate -# It may be useful for the backend to enable a mass terminate at some point for all meta_child_policies associated with an id. -script "js_check_deleted", type: "javascript" do - parameters "response" - result "result" - code <<-EOS - result = {"path":"/"} - EOS -end diff --git a/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging_meta_parent.pt b/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging_meta_parent.pt deleted file mode 100644 index 8374c348f7..0000000000 --- a/cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging_meta_parent.pt +++ /dev/null @@ -1,1244 +0,0 @@ -name "Meta Parent: AWS CloudTrails With Read Logging Enabled" -rs_pt_ver 20180301 -type "policy" -short_description "**NOTE: Meta policies are an alpha feature. Please consult the [README](https://github.com/flexera-public/policy_templates/blob/master/README_META_POLICIES.md) before use.** Applies and manages \"child\" [AWS CloudTrails With Read Logging Enabled](https://github.com/flexera-public/policy_templates/tree/master/cost/aws/cloudtrail_read_logging) Policies." -severity "low" -category "Meta" -default_frequency "15 minutes" -info( - provider: "AWS", - version: "0.1.0", # This version of the Meta Parent Policy Template should match the version of the Child Policy Template as it appears in the Catalog for best reliability -) - -############################################################################## -# Parameters -############################################################################## - -## Meta Parent Parameters -## These are params specific to the meta parent policy. -parameter "param_combined_incident_email" do - type "list" - label "Email addresses for combined incident" - description "A list of email addresses to notify with the consolidated child policy incident." - default [] -end - -parameter "param_dimension_filter_includes" do - type "list" - label "Dimension Include Filters" - description <<-EOS - Filters [`dimension_name=dimension_value` and `dimension_name=~dimension_value` pairs] to determine which AWS Accounts returned by the Flexera Bill Analysis API to **INCLUDE** and be applied to. - Use = to match the entire value and =~ to match a substring contained in the value. - During each run this policy will select AWS Accounts who match **all** the filters defined and apply a child policy for each. - If no include filters are provided, then all AWS Accounts are included by default. - Most of the dimensions in Flexera can be used [default dimensions, custom tag dimensions, rule-based dimensions]. Full list of available dimensions documented in the [Bill Analysis API Docs](https://reference.rightscale.com/bill_analysis/). - EOS - default [] -end - -parameter "param_dimension_filter_excludes" do - type "list" - label "Dimension Exclude Filters" - description <<-EOS - Filters [`dimension_name=dimension_value` and `dimension_name=~dimension_value` pairs] to determine which AWS Accounts returned by the Flexera Bill Analysis API to **EXCLUDE** and *not* have policy applied to. - Use = to match the entire value and =~ to match a substring contained in the value. - During each run this policy will select AWS Accounts who match **all** the filters defined here and excludes them from results. - Can be used to exclude specific AWS Accounts [`vendor_account=123456789012`] - Most of the dimensions in Flexera can be used [default dimensions, custom tag dimensions, rule-based dimensions]. Full list of available dimensions documented in the [Bill Analysis API Docs](https://reference.rightscale.com/bill_analysis/). - EOS - default [] -end - -parameter "param_policy_schedule" do - type "string" - label "Child Policy Schedule" - description "The interval at which the child policy checks for conditions and generates incidents." - default "weekly" - allowed_values "daily", "weekly", "monthly" -end - -parameter "param_template_source" do - type "string" - label "Child Policy Template Source" - description "By default, will use the \"AWS CloudTrails With Read Logging Enabled\" Policy Template from Catalog. Optionally, you can use the \"AWS CloudTrails With Read Logging Enabled\" Policy Template uploaded in the current Flexera Project." - default "Published Catalog Template" - allowed_values "Published Catalog Template", "Uploaded Template" -end - -## Child Policy Parameters -parameter "param_regions_allow_or_deny" do - type "string" - category "Filters" - label "Allow/Deny Regions" - description "Allow or Deny entered regions. See the README for more details" - allowed_values "Allow", "Deny" - default "Allow" -end - -parameter "param_regions_list" do - type "list" - category "Filters" - label "Allow/Deny Regions List" - description "A list of allowed or denied regions. See the README for more details" - allowed_pattern /^([a-zA-Z-_]+-[a-zA-Z0-9-_]+-[0-9-_]+,*|)+$/ - default [] -end - -parameter "param_automatic_action" do - type "list" - category "Actions" - label "Automatic Actions" - description "When this value is set, this policy will automatically take the selected action(s)" - allowed_values ["Disable Read Logging"] - default [] -end - -############################################################################### -# Authentication -############################################################################### -credentials "auth_aws" do - schemes "aws", "aws_sts" - label "AWS" - description "Select the AWS Credential from the list" - tags "provider=aws" - aws_account_number $param_aws_account_number -end - -credentials "auth_flexera" do - schemes "oauth2" - label "Flexera" - description "Select Flexera One OAuth2 credentials" - tags "provider=flexera" -end - -############################################################################### -# Datasources -############################################################################### - -# Get Applied Parent Policy Details -datasource "ds_self_policy_information" do - request do - auth $auth_flexera - host rs_governance_host - path join(["/api/governance/projects/", rs_project_id, "/applied_policies/", policy_id]) - header "Api-Version", "1.0" - end - result do - encoding "json" - field "name", jmes_path(response, "name") - field "creator_id", jmes_path(response, "created_by.id") - field "credentials", jmes_path(response, "credentials") - field "options", jmes_path(response, "options") - end -end - -datasource "ds_child_policy_options" do - run_script $js_child_policy_options, $ds_self_policy_information -end - -script "js_child_policy_options", type: "javascript" do - parameters "ds_self_policy_information" - result "options" - code <<-EOS - // Filter Options that are not appropriate for Child Policy - var options = _.map(ds_self_policy_information.options, function(option){ - // param_combined_incident_email, param_dimension_filter_includes, param_dimension_filter_excludes, param_policy_schedule are exclusion to Meta Parent Policy Parameters - if (!_.contains(["param_combined_incident_email", "param_dimension_filter_includes", "param_dimension_filter_excludes", "param_policy_schedule", "param_template_source"], option.name)) { - return { "name": option.name, "value": option.value }; - } - }); - // Explicitly add param_email which is disabled/does not exist in meta parent policy - options.push({ - "name": "param_email", - "value": [] - }); - EOS -end - -datasource "ds_child_policy_options_map" do - run_script $js_child_policy_options_map, $ds_child_policy_options -end - -script "js_child_policy_options_map", type: "javascript" do - parameters "ds_child_policy_options" - result "options" - code <<-EOS - function format_options_keyvalue(options) { - var options_keyvalue_map = {}; - _.each(options, function(option) { - options_keyvalue_map[option.name] = option.value; - }); - return options_keyvalue_map; - } - var options = format_options_keyvalue(ds_child_policy_options) - EOS -end - -datasource "ds_format_self" do - run_script $js_format_self, $ds_self_policy_information, $ds_child_policy_options_map -end - -script "js_format_self", type: "javascript" do - parameters "ds_self_policy_information", "ds_child_policy_options_map" - result "formatted" - code <<-EOS - var formatted = { - "name": ds_self_policy_information["name"], - "creator_id": ds_self_policy_information["creator_id"], - "credentials": ds_self_policy_information["credentials"], - "options": ds_child_policy_options_map - }; - EOS -end - - -# Get Pulished Policy Details -datasource "ds_published_child_policy_information" do - request do - auth $auth_flexera - host rs_governance_host - path join(["/api/governance/orgs/", rs_org_id, "/published_templates"]) - header "Api-Version", "1.0" - end - result do - encoding "json" - # Select the published policy that is published by "support@flexera.com" and matches the name of the child policy template - collect jq(response, '.items[] | select(.name == "AWS CloudTrails With Read Logging Enabled" and .created_by.email == "support@flexera.com")' ) do - field "name", jmes_path(col_item, "name") - field "href", jmes_path(col_item, "href") - field "short_description", jmes_path(col_item, "short_description") - end - end -end - -# Get Uploaded Policy Details -datasource "ds_project_child_policy_information" do - request do - auth $auth_flexera - host rs_governance_host - path join(["/api/governance/projects/", rs_project_id, "/policy_templates"]) - header "Api-Version", "1.0" - end - result do - encoding "json" - # Select the uploaded policy that matches the name of the child policy template - collect jq(response, '.items[] | select(.name == "AWS CloudTrails With Read Logging Enabled")' ) do - field "name", jmes_path(col_item, "name") - field "href", jmes_path(col_item, "href") - field "short_description", jmes_path(col_item, "short_description") - end - end -end - -datasource "ds_get_billing_centers" do - request do - auth $auth_flexera - host rs_optima_host - path join(["/analytics/orgs/",rs_org_id,"/billing_centers"]) - header "Api-Version", "1.0" - header "User-Agent", "RS Policies" - query "view", "allocation_table" - ignore_status [403] - end - result do - encoding "json" - # Select the Billing Centers that have "parent_id" undefined or "" (i.e. top-level Billing Centers) - collect jq(response, '.[] | select(.parent_id == null)' ) do - field "href", jq(col_item,".href") - field "id", jq(col_item,".id") - field "name", jq(col_item,".name") - field "parent_id", jq(col_item,".parent_id") - end - end -end - -script "js_make_billing_center_request", type: "javascript" do - parameters "rs_org_id", "rs_optima_host", "billing_centers_unformatted", "param_dimension_filter_includes", "param_dimension_filter_excludes" - result "request" - code <<-EOS - - billing_centers_formatted = [] - - for (x=0; x< billing_centers_unformatted.length; x++) { - billing_centers_formatted.push(billing_centers_unformatted[x]["id"]) - } - - finish = new Date() - finishFormatted = finish.toJSON().split("T")[0] - start = new Date() - start.setDate(start.getDate() - 30) - startFormatted = start.toJSON().split("T")[0] - - // Default dimensions and filter expressions required for meta parent policy - var dimensions = ["vendor_account", "vendor_account_name"]; - var filter_expressions = [ - { dimension: "vendor", type: "equal", value: "AWS" } - ] - - // Append to default dimensions and filter expressions using parent policy params - _.each(param_dimension_filter_includes, function (v) { - // split key=value string - if (v.indexOf('=~') == -1) { - var split = v.split("="); - var type = "equal" - } else { - var split = v.split("=~"); - var type = "substring" - } - - var k = split[0]; - var v = split[1]; - - // append to lists - dimensions.push(k); - - if (type == "equal") { - filter_expressions.push({ dimension: k, type: "equal", value: v }); - } else { - filter_expressions.push({ dimension: k, type: "substring", substring: v }); - } - }); - - // Append to filter expressions using exclude policy params - _.each(param_dimension_filter_excludes, function (v) { - // split key=value string - if (v.indexOf('=~') == -1) { - var split = v.split("="); - var type = "equal" - } else { - var split = v.split("=~"); - var type = "substring" - } - - var k = split[0]; - var v = split[1]; - - // append to lists - dimensions.push(k); - - if (type == "equal") { - filter_expressions.push({ "type": "not", "expression": { "dimension": k, "type": "equal", "value": v } }); - } else { - filter_expressions.push({ "type": "not", "expression": { "dimension": k, "type": "substring", "substring": v } }); - } - }); - - // Produces a duplicate-free version of the array - dimensions = _.uniq(dimensions); - - var body = { - "dimensions": dimensions, - "granularity":"day", - "start_at": startFormatted, - "end_at": finishFormatted, - "metrics":["cost_amortized_unblended_adj"], - "billing_center_ids": billing_centers_formatted, - "filter": - { - "type": "and", - "expressions": filter_expressions - }, - "summarized": true - } - var request = { - auth: 'auth_flexera', - host: rs_optima_host, - scheme: 'https', - verb: 'POST', - path: "/bill-analysis/orgs/"+ rs_org_id + "/costs/aggregated", - headers: { - "API-Version": "1.0", - "Content-Type":"application/json" - }, - body: JSON.stringify(body) - } - EOS -end - -# Get the AWS acounts -datasource "ds_get_aws_accounts" do - request do - run_script $js_make_billing_center_request, rs_org_id, rs_optima_host, $ds_get_billing_centers, $param_dimension_filter_includes, $param_dimension_filter_excludes - end - result do - encoding "json" - collect jmes_path(response,"rows[*]") do - field "aws_account_id", jmes_path(col_item,"dimensions.vendor_account") - field "aws_account_name", jmes_path(col_item,"dimensions.vendor_account_name") - end - end -end - -# Get Child policies -datasource "ds_get_existing_policies" do - request do - auth $auth_flexera - host rs_governance_host - path join(["/api/governance/projects/", rs_project_id, "/applied_policies"]) - header "Api-Version", "1.0" - query "meta_parent_policy_id", policy_id - end - result do - collect jq(response, '.items[]?') do - field "name", jq(col_item, ".name") - field "applied_policy_id", jq(col_item, ".id") - field "options", jq(col_item, ".options") - field "updated_at", jq(col_item, ".updated_at") - field "status", jq(col_item, ".status") - end - end -end - -# Get Child policies incidents -datasource "ds_get_existing_policies_incidents" do - request do - auth $auth_flexera - host rs_governance_host - path join(["/api/governance/projects/", rs_project_id, "/incidents"]) - header "Api-Version", "1.0" - query "meta_parent_policy_id", policy_id - query "state", "triggered" - end - result do - collect jq(response, '.items[]?') do - field "incident_id", jq(col_item, ".id") - field "applied_policy_id", jq(col_item, ".applied_policy.id") - field "summary", jq(col_item, ".summary") - field "state", jq(col_item, ".state") - field "violation_data_count", jq(col_item, ".violation_data_count") - field "updated_at", jq(col_item, ".updated_at") - field "meta_parent_policy_id", jq(col_item, ".meta_parent_policy_id") - end - end -end - -datasource "ds_format_incidents" do - run_script $js_format_existing_policies_incidents, $ds_get_existing_policies_incidents -end - -script "js_format_existing_policies_incidents", type: "javascript" do - parameters "unformatted" - result "formatted" - code <<-EOS - formatted={} - - _.each(unformatted, function(incident) { - if (formatted[incident['applied_policy_id']] == undefined) { - formatted[incident['applied_policy_id']] = [] - } - - formatted[incident['applied_policy_id']].push(incident) - }) -EOS -end - -datasource "ds_format_existing_policies" do - run_script $js_format_existing_policies, $ds_get_existing_policies, $ds_format_incidents -end - -# format -# duplicates logic should compare updated at -# we can validate update here when destructring the existing policy options, don't need updated at -# format options -script "js_format_existing_policies", type: "javascript" do - parameters "ds_get_existing_policies", "ds_format_incidents" - result "result" - code <<-EOS - function format_options_keyvalue(options) { - var options_keyvalue_map = {}; - _.each(options, function(option) { - options_keyvalue_map[option.name] = option.value; - }); - return options_keyvalue_map; - } - - result = {} - formatted = {} - duplicates = [] - // tracking holds all existing policies and later can be used to determine if existing policies should be deleted [i.e. if cloud account was removed] - tracking = {} - - for (x=0; x newDate) { - duplicates.push({ - "applied_policy_id":ds_get_existing_policies[x]["applied_policy_id"], - "applied_policy_name":ds_get_existing_policies[x]["name"], - "status":ds_get_existing_policies[x]["status"], - "updated_at":ds_get_existing_policies[x]["updated_at"], - "incident": incident, - "incident2": incident2 - }) - } else { - duplicates.push({ - "applied_policy_id":current["applied_policy_id"], - "applied_policy_name":current["applied_policy_name"], - "status":current["status"], - "updated_at":current["updated_at"], - "incident": current["incident"], - "incident2": current["incident2"] - }) - formatted[aws_account_id] = { - "applied_policy_id":ds_get_existing_policies[x]["applied_policy_id"], - "applied_policy_name":ds_get_existing_policies[x]["name"], - "status":ds_get_existing_policies[x]["status"], - "updated_at":ds_get_existing_policies[x]["updated_at"], - "incident": incident, - "incident2": incident2, - "options": options - } - - } - } - } - - result.formatted=formatted - result.duplicates=duplicates - result.tracking=tracking - EOS -end - -datasource "ds_take_in_parameters" do - run_script $js_take_in_parameters, $ds_get_aws_accounts, $ds_format_self, first($ds_published_child_policy_information), first($ds_project_child_policy_information), $ds_format_existing_policies, $ds_child_policy_options, $ds_child_policy_options_map, $param_template_source, $param_policy_schedule, policy_id, f1_app_host, rs_org_id, rs_project_id -end - -# hardcode template href with id from catalog -# catalog policies show in customer's published templates with their org id -# "template_href": "/api/governance/orgs/" + rs_org_id + "/published_templates/62618616e3dff80001572bf0" -# update logic: the only reason we're going to update the child policies for is changes to options -# and only some options, email is always blank and aws_account_id is tied to the idenity of each policy, so: new account creation, removal of account: termination -# param_automatic_action is a list with only one action, unless the person is applying using an API and putting the same value multiple times this should either be a length of 0 or 1 -# param_log_to_cm_audit_entries is a String of Yes or No -# param_exclude_tags and param_allowed_regions are arrays. I'm doing an update on the order changing but the values remaining the same. -# If we only want to do an update on the values changing we could sort before doing the equality check. -script "js_take_in_parameters", type: "javascript" do - parameters "ds_get_aws_accounts", "ds_format_self", "ds_published_child_policy_information", "ds_project_child_policy_information", "ds_format_existing_policies", "ds_child_policy_options", "ds_child_policy_options_map", "param_template_source", "param_policy_schedule", "meta_parent_policy_id", "f1_app_host", "rs_org_id", "rs_project_id" - result "grid_and_cwf" - code <<-EOS - - // Set Child Policy Information based on param_template_source value - if (param_template_source == "Published Catalog Template") { - child_policy_information = ds_published_child_policy_information - } else { - child_policy_information = ds_project_child_policy_information - } - - max_actions = 50; - - grid_and_cwf={grid:[], to_create:[], to_update:[], to_delete:[], parent_policy:ds_format_self}; - - should_keep = ds_format_existing_policies.tracking; - - // Construct UI URL prefixes for policy template summary - ui_url_prefix = "https://" + f1_app_host + "/orgs/" + rs_org_id; - applied_policy_url_prefix = ui_url_prefix + "/automation/applied-policies/projects/" + rs_project_id + "?noIndex=1&policyId="; - incident_url_prefix = ui_url_prefix + "/automation/incidents/projects/" + rs_project_id + "?noIndex=1&incidentId="; - - function add_to_grid(ep, action) { - policy_status={ - "id": ep["applied_policy_id"], - "policy_name": ep["applied_policy_name"] + '||' + applied_policy_url_prefix + ep["applied_policy_id"], - "meta_policy_status": action, - "policy_status": ep["status"], - "policy_last_update": ep["updated_at"], - }; - - if (ep.incident != null && ep.incident != undefined) { - // Remove policy name from summary when applicable - summary_parts = ep.incident.summary.split(':') - summary = summary_parts[summary_parts.length - 1].trim() - - policy_status["incident_summary"] = summary + '||' + incident_url_prefix + ep.incident.incident_id; - policy_status["incident_state"] = ep.incident.state; - policy_status["incident_violation_data_count"] = ep.incident.violation_data_count; - policy_status["incident_last_update"] = ep.incident.updated_at; - } - - if (ep.incident2 != null && ep.incident2 != undefined) { - // Remove policy name from summary when applicable - summary_parts = ep.incident2.summary.split(':') - summary = summary_parts[summary_parts.length - 1].trim() - - policy_status["incident_summary"] = summary + '||' + incident_url_prefix + ep.incident2.incident_id; - policy_status["incident_state"] = ep.incident2.state; - policy_status["incident2_violation_data_count"] = ep.incident2.violation_data_count; - policy_status["incident2_last_update"] = ep.incident2.updated_at; - } - - grid_and_cwf.grid.push(policy_status); - } - - for (x=0; x -1) { - _.each(incident["violation_data"], function(violation) { - violation["incident_id"] = incident["id"]; - result.push(violation); - }); - } - }); -EOS -end - - -# Escalation for Disable Read Logging -escalation "esc_disable_read_logging" do - automatic false # Do not automatically action from meta parent. the child will handle automatic escalations if param is set - label "Disable Read Logging" - description "Approval to disable read logging on all selected CloudTrails" - - # Run declaration should go at end, after any parameters that may exist - run "esc_disable_read_logging", data, rs_governance_host, rs_project_id -end -define esc_disable_read_logging($data, $governance_host, $rs_project_id) do - $actions_options = [] - call child_run_action($data, $governance_host, $rs_project_id, "Disable Read Logging", $action_options) -end - - -# Summary and a conditional incident which will show up if any policy is being applied, updated or deleted. -# Minimum of 1 incident, max of four -# Could swap the summary to only showing running -# Could also just have one incident and use meta_status to determine which escalation happens -policy "policy_scheduled_report" do - # Consolidated Incident Check(s) - # Consolidated incident for AWS CloudTrails With Read Logging Enabled Found - validate $ds_trails_incident_combined_incidents do - summary_template "Consolidated Incident: {{ len data }} AWS CloudTrails With Read Logging Enabled Found" - escalate $esc_email - escalate $esc_disable_read_logging - check eq(size(data), 0) - export do - resource_level true - field "accountID" do - label "Account ID" - end - field "accountName" do - label "Account Name" - end - field "name" do - label "Name" - end - field "id" do - label "ARN" - end - field "region" do - label "Region" - end - field "logging_read" do - label "Read Logging" - end - field "logging_write" do - label "Write Logging" - end - field "has_selectors" do - label "Has Event Selectors?" - end - field "event_selectors" do - label "Current Event Selectors" - end - field "fixed_selectors" do - label "Recommended Event Selectors" - end - field "fixed_selectors_object" do - label "Recommend - field "incident_id" do - label "Child Incident ID" - end - end - end - - # Status Incident Check - validate $ds_take_in_parameters do - summary_template "{{ data.parent_policy.name }}: Status of Child Policies" - detail_template <<-EOS -The current status of Child Policies for **{{ data.parent_policy.name }}**: - -Total Child Applied Policies: {{ len data.grid }} -EOS - check false # always trigger this status incident - export "grid" do - resource_level true - field "id" do - label "Applied Policy ID" - end - field "policy_name" do - label "Applied Policy Name" - format "link-external" - end - field "meta_policy_status" do - label "Meta Child Policy Status" - end - field "policy_status" do - label "Policy Status" - end - field "policy_last_update" do - label "Policy Last Update" - end - field "incident_summary" do - label "Incident Summary" - format "link-external" - end - field "incident_state" do - label "Incident State" - end - field "incident_violation_data_count" do - label "Incident Violation Count" - end - field "incident_last_update" do - label "Incident Last Update" - end - field "incident2_summary" do - label "Incident 2 Summary" - format "link-external" - end - field "incident2_state" do - label "Incident 2 State" - end - field "incident2_violation_data_count" do - label "Incident 2 Violation Count" - end - field "incident2_last_update" do - label "Incident 2 Last Update" - end - end - end - - # Create Child Policies Incident Check - validate $ds_to_create do - summary_template "Policies being created" - detail_template <<-EOS - Policies Being Created: - - | Applied Policy | - | --------------- | - {{ range data -}} - | {{ .name }} | - {{ end -}} - EOS - escalate $create_policies - check eq(size(data),0) - end - - # Update Child Policies Incident Check - validate $ds_to_update do - summary_template "Policies being updated" - detail_template <<-EOS - Policies Being Updated: - - | Applied Policy | - | --------------- | - {{ range data -}} - | {{ .name }} | - {{ end -}} - EOS - escalate $update_policies - check eq(size(data),0) - end - - # Delete Child Policies Incident Check - validate $ds_to_delete do - summary_template "Policies being deleted" - detail_template <<-EOS - Policies being Deleted: - - | Applied Policy | - | --------------- | - {{ range data -}} - | {{ .name }} | - {{ end -}} - EOS - escalate $delete_policies - check eq(size(data),0) - end -end - -# Begin Shared Functions for Child Actions from Consolidated Incident -define groupByIncidentID($data) return $incidents do - # Empty hash to store incidents is incident_id - $incidents = {} - - task_label("Grouping items by Incident ID") - $index = 1 - foreach $item in $data do - task_label("Grouping items by Incident ID. "+to_s($index)+"/"+to_s(size($data))) - if !$incidents[$item["incident_id"]] - #task_label("Grouping items by Incident ID. "+to_s($index)+"/"+to_s(size($data))". New Incident: "+$item["incident_id"]) - $incidents[$item["incident_id"]] = {"id": $item["incident_id"], "resource_ids": []} - end - #task_label("Grouping items by Incident ID. "+to_s($index)+"/"+to_s(size($data))". Appending Resource: "+$item["id"]) - # Append resource id to the list for the incident - $incidents[$item["incident_id"]]["resource_ids"] = $incidents[$item["incident_id"]]["resource_ids"] + [$item["id"]] - end -end - -define child_run_action($data, $governance_host, $rs_project_id, $action_label, $action_options) do - # Empty global array for log strings, helpful for debugging - $$debug = [] - - # Group Resources by Incident ID - # This reduces the number of requests made to the Flexera API - call groupByIncidentID($data) retrieve $incidents - $$debug_incidents = to_json($incidents) - - call runActions($incidents, $action_label, $governance_host, $rs_project_id, $action_options) - - # If we encountered any errors, use `raise` to mark the CWF process as errored - if inspect($$errors) != "null" - raise join($$errors,"\n") - end - - # If we made it here, all actions completed successfully - # Celebrate Success! - task_label("All \""+$action_label+"\" actions completed successfully!") -end - -define runActions($incidents, $action_label, $governance_host, $rs_project_id, $action_options) do - foreach $id in keys($incidents) do - sub on_error: handle_error() do - $incident = $incidents[$id] - task_label("Triggering action \""+$action_label+"\" on "+size($incident["resource_ids"])+" count resources via incident "+$incident["id"]) - $request = { - auth: $$auth_flexera, - verb: "get", - https: true, - host: $governance_host, - href: join(["/api/governance/projects/", $rs_project_id, "/incidents/", $incident["id"]]), - headers: { "Api-Version": "1.0" }, - query_strings: { "view": "extended" } - } - $response = http_request($request) - $$debug << to_json({ - "request": $request, - "response": $response - }) - $action_id = "" - foreach $action in $response["body"]["available_actions"] do - # If we have not already found the action id, and the label matches, set the action id - # The first check is to prevent looking through the entire list if we already have the id - if $action["label"] == $action_label - $action_id = $action["id"] - end - end - if $action_id == "" - raise "Could not find action id for \""+$action_label+"\" response="+to_json($response) - end - # Now we are reach to trigger the action - $request = { - auth: $$auth_flexera, - verb: "post", - https: true, - host: $governance_host, - href: join(["/api/governance/projects/", $rs_project_id, "/incidents/", $incident["id"],"/actions/", $action_id,"/run_action"]), - headers: { "Api-Version": "1.0" }, - body: { "options":[{ "name": "ids", "value": $incident["resource_ids"] }] } - } - # If the action has parameters, add them to the request body - if type($action_options) == "array" && size($action_options) > 0 - $request["body"]["options"] = $request["body"]["options"] + $action_options - end - $response = http_request($request) - $$debug << to_json({ - "request": $request, - "response": $response - }) - # Get the action status from response header - $action_location = $response["headers"]["Location"] - - # Setup some variables for the wait loop - $action_status = "" - $loop_count = 0 - $loop_endtime = now() + (3600*2) # 2 hours from now - # [ queued, aborted, pending, running, completed, failed, denied ] - while ($action_status !~ /^(aborted|completed|failed|denied)/) && (now() <= $loop_endtime) do - # Using Loop Count to slowly increment the sleep time - # This is to prevent the loop from hammering our APIs - $loop_count = $loop_count + 1 - task_label("action_status=\""+$action_status+"\" Sleeping for "+to_s($loop_count)+" seconds") - sleep($loop_count) - task_label("action_status=\""+$action_status+"\" Getting action status") - $request = { - auth: $$auth_flexera, - verb: "get", - https: true, - host: $governance_host, - href: $action_location, - headers: { "Api-Version": "1.0" }, - query_strings: { "view": "extended" } - } - $response = http_request($request) - $$debug << to_json({ - "request": $request, - "response": $response - }) - $action_status = $response["body"]["status"] - end - if ($action_status != "completed") - # Check if we are out of time first - if (now() > $loop_endtime) - raise "action_status=\""+$action_status+"\" Action did not complete in time. Aborting to prevent endless loop. action_status_json="+to_json($response) - else - # If not, then it was aborted, failed or denied - raise "action_status=\""+$action_status+"\" Action did not complete as expected. action_status_json="+to_json($response) - end - end - # If we made it here, the action completed successfully - task_label("action_status=\""+$action_status+"\" Action completed successfully") - end - end -end -# End Shared Functions for Child Actions from Consolidated Incident - -# CWF function to handle errors -define handle_error() do - if !$$errors - $$errors = [] - end - $$errors << $_error["type"] + ": " + $_error["message"] - # We check for errors at the end, and raise them all together - # Skip errors handled by this definition - $_error_behavior = "skip" -end - -# Used only for emailing the combined child incident if so desired -escalation "esc_email" do - automatic true - label "Send Email" - description "Send incident email" - email $param_combined_incident_email -end - -escalation "create_policies" do - run "create_applied_policies", data, rs_governance_host, rs_project_id -end - -# if name !=null -define create_applied_policies($data, $governance_host, $rs_project_id) return $responses do - $responses = [] - $$debug = [] - $item_index = 0 - $item_total = size($data) - foreach $item in $data do - $item_index = $item_index + 1 - $status = to_s("("+$item_index+"/"+$item_total+")") - task_label($status+" Creating Applied Policy with Options: " + to_json($item["options"])) - $response = http_request( - auth: $$auth_flexera, - verb: "post", - https: true, - host: $governance_host, - href: join(["/api/governance/projects/", $rs_project_id, "/applied_policies"]), - headers: { "Api-Version": "1.0" }, - body: { - "name": $item["name"], - "description": $item["description"], - "template_href": $item["template_href"], - "frequency": $item["frequency"], - "options": $item["options"], - "credentials": $item["credentials"], - "meta_parent_policy_id": $item["meta_parent_policy_id"] - } - ) - $responses << $response - $$debug << to_json({ - "response": $response, - "item": $item, - "governance_host": $governance_host - }) - end -end - -escalation "update_policies" do - run "update_applied_policies", data, rs_governance_host, rs_project_id -end - -define update_applied_policies($data, $governance_host, $rs_project_id) return $responses do - $responses = [] - $$debug = [] - $item_index = 0 - $item_total = size($data) - foreach $item in $data do - $item_index = $item_index + 1 - $status = to_s("("+$item_index+"/"+$item_total+")") - task_label($status+" Updating Applied Policy with Options: " + to_json($item["options"])) - $response = http_request( - auth: $$auth_flexera, - verb: "patch", - https: true, - host: $governance_host, - href: join(["/api/governance/projects/", $rs_project_id, "/applied_policies/", $item["applied_policy_id"]]), - headers: { "Api-Version": "1.0" }, - body: { - "options": $item["options"] - } - ) - $responses << $response - $$debug << to_json({ - "response": $response, - "item": $item, - "governance_host": $governance_host - }) - end -end - -escalation "delete_policies" do - run "delete_applied_policies", data, rs_governance_host, rs_project_id -end - -define delete_applied_policies($data, $governance_host, $rs_project_id) return $responses do - $responses = [] - $$debug = [] - $item_index = 0 - $item_total = size($data) - foreach $item in $data do - $item_index = $item_index + 1 - $status = to_s("("+$item_index+"/"+$item_total+")") - task_label($status+" Deleting Applied Policy: " + $item["id"]) - $response = http_request( - auth: $$auth_flexera, - verb: "delete", - https: true, - host: $governance_host, - href: join(["/api/governance/projects/", $rs_project_id, "/applied_policies/", $item["id"]]), - headers: { "Api-Version": "1.0" } - ) - $responses << $response - $$debug << to_json({ - "response": $response, - "item": $item, - "governance_host": $governance_host - }) - end -end diff --git a/tools/meta_parent_policy_compiler/meta_parent_policy_compiler.rb b/tools/meta_parent_policy_compiler/meta_parent_policy_compiler.rb index 3b55d42155..98efaadfc3 100644 --- a/tools/meta_parent_policy_compiler/meta_parent_policy_compiler.rb +++ b/tools/meta_parent_policy_compiler/meta_parent_policy_compiler.rb @@ -15,7 +15,6 @@ "../../compliance/aws/rds_backup/aws_rds_backup.pt", "../../compliance/aws/untagged_resources/aws_untagged_resources.pt", "../../cost/aws/burstable_ec2_instances/aws_burstable_ec2_instances.pt", - "../../cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt", "../../cost/aws/eks_without_spot/aws_eks_without_spot.pt", "../../cost/aws/gp3_volume_upgrade/aws_upgrade_to_gp3_volume.pt", "../../cost/aws/idle_compute_instances/idle_compute_instances.pt", diff --git a/tools/policy_master_permission_generation/validated_policy_templates.yaml b/tools/policy_master_permission_generation/validated_policy_templates.yaml index b0b2c84082..e089f8c74d 100644 --- a/tools/policy_master_permission_generation/validated_policy_templates.yaml +++ b/tools/policy_master_permission_generation/validated_policy_templates.yaml @@ -16,7 +16,6 @@ validated_policy_templates: - "./compliance/aws/untagged_resources/aws_untagged_resources.pt" - "./cost/aws/burstable_ec2_instances/aws_burstable_ec2_instances.pt" - "./cost/aws/cheaper_regions/aws_cheaper_regions.pt" -- "./cost/aws/cloudtrail_read_logging/aws_cloudtrail_read_logging.pt" - "./cost/aws/eks_without_spot/aws_eks_without_spot.pt" - "./cost/aws/extended_support/aws_extended_support.pt" - "./cost/aws/gp3_volume_upgrade/aws_upgrade_to_gp3_volume.pt" From 6ddd523b0aa88e12512326757e551139d90bd3ff Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Tue, 13 Aug 2024 13:17:26 -0500 Subject: [PATCH 05/10] update --- automation/aws/aws_rbd_from_tag/CHANGELOG.md | 4 ++ .../aws/aws_rbd_from_tag/aws_rbd_from_tag.pt | 6 +- .../azure/azure_rbd_from_rg_tag/CHANGELOG.md | 4 ++ .../azure_rbd_from_rg_tag.pt | 60 ++++++++++--------- .../azure/azure_rbd_from_tag/CHANGELOG.md | 4 ++ .../azure_rbd_from_tag/azure_rbd_from_tag.pt | 22 +++---- .../google/google_rbd_from_label/CHANGELOG.md | 4 ++ .../google_rbd_from_label.pt | 22 +++---- 8 files changed, 75 insertions(+), 51 deletions(-) diff --git a/automation/aws/aws_rbd_from_tag/CHANGELOG.md b/automation/aws/aws_rbd_from_tag/CHANGELOG.md index 896755351a..827c354577 100644 --- a/automation/aws/aws_rbd_from_tag/CHANGELOG.md +++ b/automation/aws/aws_rbd_from_tag/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v2.3.1 + +- Fixed issue that would sometimes cause execution to fail if an account had no tag keys + ## v2.3.0 - Added option to retain original casing of tag values instead of normalizing them all to lowercase diff --git a/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt b/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt index a9fb5298a8..55b1055285 100644 --- a/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt +++ b/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt @@ -7,7 +7,7 @@ severity "low" category "Cost" default_frequency "daily" info( - version: "2.3.0", + version: "2.3.1", provider: "Flexera", service: "Optima", policy_set: "Automation", @@ -158,8 +158,8 @@ script "js_rbds", type: "javascript" do rules = [] _.each(accounts, function(account) { - if (typeof(account['id']) == 'string' && account['id'] != '') { - if (account['tags'][tag_key] != undefined && account['tags'][tag_key] != null) { + if (typeof(account['id']) == 'string' && account['id'] != '' && typeof(account['tags']) == 'object') { + if (typeof(account['tags'][tag_key]) == 'string') { if (account['tags'][tag_key].trim() != '') { value = account['tags'][tag_key].trim() if (param_normalize_case == "Yes") { value = value.toLowerCase() } diff --git a/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md b/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md index 6f6921bda4..722e5324ad 100644 --- a/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md +++ b/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v1.3.1 + +- Fixed issue that would sometimes cause execution to fail if an account had no tag keys + ## v1.3.0 - Added option to retain original casing of tag values instead of normalizing them all to lowercase diff --git a/automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt b/automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt index cb49e01ac7..6373bd8a39 100644 --- a/automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt +++ b/automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt @@ -7,7 +7,7 @@ severity "low" category "Cost" default_frequency "daily" info( - version: "1.3.0", + version: "1.3.1", provider: "Flexera", service: "Optima", policy_set: "Automation", @@ -291,33 +291,35 @@ script "js_rbds", type: "javascript" do rules = [] _.each(rgs, function(rg) { - if (rg['tags'][tag_key] != undefined && rg['tags'][tag_key] != null) { - if (rg['tags'][tag_key].trim() != '') { - value = rg['tags'][tag_key].trim() - if (param_normalize_case == "Yes") { value = value.toLowerCase() } - - rules.push({ - condition: { - type: "and", - expressions: [ - { type: "dimension_equals", dimension: "resource_group", value: rg['name'].trim() }, - { type: "dimension_equals", dimension: "vendor_account", value: rg['subscriptionId'].trim().toLowerCase() } - ] - }, - value: { text: value } - }) - - if (rg['name'].trim() != rg['name'].trim().toLowerCase()) { + if (typeof(rg['tags']) == 'object') { + if (typeof(rg['tags'][tag_key]) == 'string') { + if (rg['tags'][tag_key].trim() != '') { + value = rg['tags'][tag_key].trim() + if (param_normalize_case == "Yes") { value = value.toLowerCase() } + rules.push({ condition: { type: "and", expressions: [ - { type: "dimension_equals", dimension: "resource_group", value: rg['name'].trim().toLowerCase() }, + { type: "dimension_equals", dimension: "resource_group", value: rg['name'].trim() }, { type: "dimension_equals", dimension: "vendor_account", value: rg['subscriptionId'].trim().toLowerCase() } ] }, value: { text: value } }) + + if (rg['name'].trim() != rg['name'].trim().toLowerCase()) { + rules.push({ + condition: { + type: "and", + expressions: [ + { type: "dimension_equals", dimension: "resource_group", value: rg['name'].trim().toLowerCase() }, + { type: "dimension_equals", dimension: "vendor_account", value: rg['subscriptionId'].trim().toLowerCase() } + ] + }, + value: { text: value } + }) + } } } } @@ -325,15 +327,17 @@ script "js_rbds", type: "javascript" do if (param_subscription_fallback == "Yes") { _.each(accounts, function(account) { - if (account['tags'][tag_key] != undefined && account['tags'][tag_key] != null) { - if (account['tags'][tag_key].trim() != '') { - value = account['tags'][tag_key].trim() - if (param_normalize_case == "Yes") { value = value.toLowerCase() } - - rules.push({ - condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, - value: { text: value } - }) + if (typeof(account['tags']) == 'object') { + if (typeof(account['tags'][tag_key]) == 'string') { + if (account['tags'][tag_key].trim() != '') { + value = account['tags'][tag_key].trim() + if (param_normalize_case == "Yes") { value = value.toLowerCase() } + + rules.push({ + condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, + value: { text: value } + }) + } } } }) diff --git a/automation/azure/azure_rbd_from_tag/CHANGELOG.md b/automation/azure/azure_rbd_from_tag/CHANGELOG.md index 6f6921bda4..722e5324ad 100644 --- a/automation/azure/azure_rbd_from_tag/CHANGELOG.md +++ b/automation/azure/azure_rbd_from_tag/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v1.3.1 + +- Fixed issue that would sometimes cause execution to fail if an account had no tag keys + ## v1.3.0 - Added option to retain original casing of tag values instead of normalizing them all to lowercase diff --git a/automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt b/automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt index 31447edb97..467c1700d0 100644 --- a/automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt +++ b/automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt @@ -7,7 +7,7 @@ severity "low" category "Cost" default_frequency "daily" info( - version: "1.3.0", + version: "1.3.1", provider: "Flexera", service: "Optima", policy_set: "Automation", @@ -228,15 +228,17 @@ script "js_rbds", type: "javascript" do rules = [] _.each(accounts, function(account) { - if (account['tags'][tag_key] != undefined && account['tags'][tag_key] != null) { - if (account['tags'][tag_key].trim() != '') { - value = account['tags'][tag_key].trim() - if (param_normalize_case == "Yes") { value = value.toLowerCase() } - - rules.push({ - condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, - value: { text: value } - }) + if (typeof(account['tags']) == 'object') { + if (typeof(account['tags'][tag_key]) == 'string') { + if (account['tags'][tag_key].trim() != '') { + value = account['tags'][tag_key].trim() + if (param_normalize_case == "Yes") { value = value.toLowerCase() } + + rules.push({ + condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, + value: { text: value } + }) + } } } }) diff --git a/automation/google/google_rbd_from_label/CHANGELOG.md b/automation/google/google_rbd_from_label/CHANGELOG.md index 9eb4c138e2..831a79310a 100644 --- a/automation/google/google_rbd_from_label/CHANGELOG.md +++ b/automation/google/google_rbd_from_label/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v1.3.1 + +- Fixed issue that would sometimes cause execution to fail if an account had no tag keys + ## v1.3.0 - Added option to retain original casing of tag values instead of normalizing them all to lowercase diff --git a/automation/google/google_rbd_from_label/google_rbd_from_label.pt b/automation/google/google_rbd_from_label/google_rbd_from_label.pt index e80fb5b5a8..93be95a9b0 100644 --- a/automation/google/google_rbd_from_label/google_rbd_from_label.pt +++ b/automation/google/google_rbd_from_label/google_rbd_from_label.pt @@ -7,7 +7,7 @@ severity "low" category "Cost" default_frequency "daily" info( - version: "1.3.0", + version: "1.3.1", provider: "Flexera", service: "Optima", policy_set: "Automation", @@ -215,15 +215,17 @@ script "js_rbds", type: "javascript" do rules = [] _.each(accounts, function(account) { - if (account['tags'][tag_key] != undefined && account['tags'][tag_key] != null) { - if (account['tags'][tag_key].trim() != '') { - value = account['tags'][tag_key].trim() - if (param_normalize_case == "Yes") { value = value.toLowerCase() } - - rules.push({ - condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, - value: { text: value } - }) + if (typeof(account['tags']) == 'object') { + if (typeof(account['tags'][tag_key]) == 'string') { + if (account['tags'][tag_key].trim() != '') { + value = account['tags'][tag_key].trim() + if (param_normalize_case == "Yes") { value = value.toLowerCase() } + + rules.push({ + condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, + value: { text: value } + }) + } } } }) From 6ae60c32b3c6eb6de1327c4c252bd2579fa3a059 Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Tue, 13 Aug 2024 13:23:23 -0500 Subject: [PATCH 06/10] fix --- automation/aws/aws_rbd_from_tag/CHANGELOG.md | 4 -- .../aws/aws_rbd_from_tag/aws_rbd_from_tag.pt | 6 +- .../azure/azure_rbd_from_rg_tag/CHANGELOG.md | 4 -- .../azure_rbd_from_rg_tag.pt | 60 +++++++++---------- .../azure/azure_rbd_from_tag/CHANGELOG.md | 4 -- .../azure_rbd_from_tag/azure_rbd_from_tag.pt | 22 ++++--- .../google/google_rbd_from_label/CHANGELOG.md | 4 -- .../google_rbd_from_label.pt | 22 ++++--- 8 files changed, 51 insertions(+), 75 deletions(-) diff --git a/automation/aws/aws_rbd_from_tag/CHANGELOG.md b/automation/aws/aws_rbd_from_tag/CHANGELOG.md index 827c354577..896755351a 100644 --- a/automation/aws/aws_rbd_from_tag/CHANGELOG.md +++ b/automation/aws/aws_rbd_from_tag/CHANGELOG.md @@ -1,9 +1,5 @@ # Changelog -## v2.3.1 - -- Fixed issue that would sometimes cause execution to fail if an account had no tag keys - ## v2.3.0 - Added option to retain original casing of tag values instead of normalizing them all to lowercase diff --git a/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt b/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt index 55b1055285..a9fb5298a8 100644 --- a/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt +++ b/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt @@ -7,7 +7,7 @@ severity "low" category "Cost" default_frequency "daily" info( - version: "2.3.1", + version: "2.3.0", provider: "Flexera", service: "Optima", policy_set: "Automation", @@ -158,8 +158,8 @@ script "js_rbds", type: "javascript" do rules = [] _.each(accounts, function(account) { - if (typeof(account['id']) == 'string' && account['id'] != '' && typeof(account['tags']) == 'object') { - if (typeof(account['tags'][tag_key]) == 'string') { + if (typeof(account['id']) == 'string' && account['id'] != '') { + if (account['tags'][tag_key] != undefined && account['tags'][tag_key] != null) { if (account['tags'][tag_key].trim() != '') { value = account['tags'][tag_key].trim() if (param_normalize_case == "Yes") { value = value.toLowerCase() } diff --git a/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md b/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md index 722e5324ad..6f6921bda4 100644 --- a/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md +++ b/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md @@ -1,9 +1,5 @@ # Changelog -## v1.3.1 - -- Fixed issue that would sometimes cause execution to fail if an account had no tag keys - ## v1.3.0 - Added option to retain original casing of tag values instead of normalizing them all to lowercase diff --git a/automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt b/automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt index 6373bd8a39..cb49e01ac7 100644 --- a/automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt +++ b/automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt @@ -7,7 +7,7 @@ severity "low" category "Cost" default_frequency "daily" info( - version: "1.3.1", + version: "1.3.0", provider: "Flexera", service: "Optima", policy_set: "Automation", @@ -291,35 +291,33 @@ script "js_rbds", type: "javascript" do rules = [] _.each(rgs, function(rg) { - if (typeof(rg['tags']) == 'object') { - if (typeof(rg['tags'][tag_key]) == 'string') { - if (rg['tags'][tag_key].trim() != '') { - value = rg['tags'][tag_key].trim() - if (param_normalize_case == "Yes") { value = value.toLowerCase() } - + if (rg['tags'][tag_key] != undefined && rg['tags'][tag_key] != null) { + if (rg['tags'][tag_key].trim() != '') { + value = rg['tags'][tag_key].trim() + if (param_normalize_case == "Yes") { value = value.toLowerCase() } + + rules.push({ + condition: { + type: "and", + expressions: [ + { type: "dimension_equals", dimension: "resource_group", value: rg['name'].trim() }, + { type: "dimension_equals", dimension: "vendor_account", value: rg['subscriptionId'].trim().toLowerCase() } + ] + }, + value: { text: value } + }) + + if (rg['name'].trim() != rg['name'].trim().toLowerCase()) { rules.push({ condition: { type: "and", expressions: [ - { type: "dimension_equals", dimension: "resource_group", value: rg['name'].trim() }, + { type: "dimension_equals", dimension: "resource_group", value: rg['name'].trim().toLowerCase() }, { type: "dimension_equals", dimension: "vendor_account", value: rg['subscriptionId'].trim().toLowerCase() } ] }, value: { text: value } }) - - if (rg['name'].trim() != rg['name'].trim().toLowerCase()) { - rules.push({ - condition: { - type: "and", - expressions: [ - { type: "dimension_equals", dimension: "resource_group", value: rg['name'].trim().toLowerCase() }, - { type: "dimension_equals", dimension: "vendor_account", value: rg['subscriptionId'].trim().toLowerCase() } - ] - }, - value: { text: value } - }) - } } } } @@ -327,17 +325,15 @@ script "js_rbds", type: "javascript" do if (param_subscription_fallback == "Yes") { _.each(accounts, function(account) { - if (typeof(account['tags']) == 'object') { - if (typeof(account['tags'][tag_key]) == 'string') { - if (account['tags'][tag_key].trim() != '') { - value = account['tags'][tag_key].trim() - if (param_normalize_case == "Yes") { value = value.toLowerCase() } - - rules.push({ - condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, - value: { text: value } - }) - } + if (account['tags'][tag_key] != undefined && account['tags'][tag_key] != null) { + if (account['tags'][tag_key].trim() != '') { + value = account['tags'][tag_key].trim() + if (param_normalize_case == "Yes") { value = value.toLowerCase() } + + rules.push({ + condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, + value: { text: value } + }) } } }) diff --git a/automation/azure/azure_rbd_from_tag/CHANGELOG.md b/automation/azure/azure_rbd_from_tag/CHANGELOG.md index 722e5324ad..6f6921bda4 100644 --- a/automation/azure/azure_rbd_from_tag/CHANGELOG.md +++ b/automation/azure/azure_rbd_from_tag/CHANGELOG.md @@ -1,9 +1,5 @@ # Changelog -## v1.3.1 - -- Fixed issue that would sometimes cause execution to fail if an account had no tag keys - ## v1.3.0 - Added option to retain original casing of tag values instead of normalizing them all to lowercase diff --git a/automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt b/automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt index 467c1700d0..31447edb97 100644 --- a/automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt +++ b/automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt @@ -7,7 +7,7 @@ severity "low" category "Cost" default_frequency "daily" info( - version: "1.3.1", + version: "1.3.0", provider: "Flexera", service: "Optima", policy_set: "Automation", @@ -228,17 +228,15 @@ script "js_rbds", type: "javascript" do rules = [] _.each(accounts, function(account) { - if (typeof(account['tags']) == 'object') { - if (typeof(account['tags'][tag_key]) == 'string') { - if (account['tags'][tag_key].trim() != '') { - value = account['tags'][tag_key].trim() - if (param_normalize_case == "Yes") { value = value.toLowerCase() } - - rules.push({ - condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, - value: { text: value } - }) - } + if (account['tags'][tag_key] != undefined && account['tags'][tag_key] != null) { + if (account['tags'][tag_key].trim() != '') { + value = account['tags'][tag_key].trim() + if (param_normalize_case == "Yes") { value = value.toLowerCase() } + + rules.push({ + condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, + value: { text: value } + }) } } }) diff --git a/automation/google/google_rbd_from_label/CHANGELOG.md b/automation/google/google_rbd_from_label/CHANGELOG.md index 831a79310a..9eb4c138e2 100644 --- a/automation/google/google_rbd_from_label/CHANGELOG.md +++ b/automation/google/google_rbd_from_label/CHANGELOG.md @@ -1,9 +1,5 @@ # Changelog -## v1.3.1 - -- Fixed issue that would sometimes cause execution to fail if an account had no tag keys - ## v1.3.0 - Added option to retain original casing of tag values instead of normalizing them all to lowercase diff --git a/automation/google/google_rbd_from_label/google_rbd_from_label.pt b/automation/google/google_rbd_from_label/google_rbd_from_label.pt index 93be95a9b0..e80fb5b5a8 100644 --- a/automation/google/google_rbd_from_label/google_rbd_from_label.pt +++ b/automation/google/google_rbd_from_label/google_rbd_from_label.pt @@ -7,7 +7,7 @@ severity "low" category "Cost" default_frequency "daily" info( - version: "1.3.1", + version: "1.3.0", provider: "Flexera", service: "Optima", policy_set: "Automation", @@ -215,17 +215,15 @@ script "js_rbds", type: "javascript" do rules = [] _.each(accounts, function(account) { - if (typeof(account['tags']) == 'object') { - if (typeof(account['tags'][tag_key]) == 'string') { - if (account['tags'][tag_key].trim() != '') { - value = account['tags'][tag_key].trim() - if (param_normalize_case == "Yes") { value = value.toLowerCase() } - - rules.push({ - condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, - value: { text: value } - }) - } + if (account['tags'][tag_key] != undefined && account['tags'][tag_key] != null) { + if (account['tags'][tag_key].trim() != '') { + value = account['tags'][tag_key].trim() + if (param_normalize_case == "Yes") { value = value.toLowerCase() } + + rules.push({ + condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, + value: { text: value } + }) } } }) From 8a63c707043cc1bb4a7b948c5d1665758a4a69a4 Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Sun, 15 Sep 2024 17:21:18 -0500 Subject: [PATCH 07/10] update --- .../outdated_applied_policies/CHANGELOG.md | 5 + .../outdated_applied_policies/README.md | 33 +- .../outdated_applied_policies.pt | 338 +++++++++++++++++- .../master_policy_permissions_list.json | 49 +-- .../master_policy_permissions_list.yaml | 36 +- 5 files changed, 354 insertions(+), 107 deletions(-) diff --git a/automation/flexera/outdated_applied_policies/CHANGELOG.md b/automation/flexera/outdated_applied_policies/CHANGELOG.md index d4cec5ecf9..85a84d011f 100644 --- a/automation/flexera/outdated_applied_policies/CHANGELOG.md +++ b/automation/flexera/outdated_applied_policies/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.2.0 + +- Policy template now reports both outdated and deprecated policy templates with a parameter to choose which +- Automatic updates for policy templates with a major version change is now supported via parameter + ## v0.1.1 - Minor fix to wording in resulting incident detail diff --git a/automation/flexera/outdated_applied_policies/README.md b/automation/flexera/outdated_applied_policies/README.md index 5e7231e84b..c2824317b6 100644 --- a/automation/flexera/outdated_applied_policies/README.md +++ b/automation/flexera/outdated_applied_policies/README.md @@ -2,7 +2,10 @@ ## What It Does -This policy checks all applied policies against the same policy in the catalog to determine if the applied policy is using an outdated version of the catalog policy. An email is sent and an incident is raised with all outdated policies. Optionally, outdated policies can automatically be updated. +This policy template checks all applied policies for the following: + +- Whether the policy template is an older version than the current version in the policy catalog. +- Whether the policy template has been deprecated. The following policy types will always be ignored and not reported on by this policy: @@ -20,10 +23,16 @@ The list of outdated policies is generated as follows: - The list of catalog policies are obtained using the [Active Policy List JSON file](https://github.com/flexera-public/policy_templates/blob/master/data/active_policy_list/active_policy_list.json) in the [policy-templates Github Repository](https://github.com/flexera-public/policy_templates). - The list of applied policies is filtered for just those applied policies that were applied from a catalog policy and whose version number does not match the version number in the catalog. +The list of deprecated policies is generated as follows: + +- The list of applied policies are obtained using the [Flexera Policy API](https://reference.rightscale.com/governance-policies/). +- The list of catalog policies are obtained using the [Active Policy List JSON file](https://github.com/flexera-public/policy_templates/blob/master/data/active_policy_list/active_policy_list.json) in the [policy-templates Github Repository](https://github.com/flexera-public/policy_templates). +- The list of applied policies is filtered for just those applied policies marked as deprecated in the [Active Policy List JSON file](https://github.com/flexera-public/policy_templates/blob/master/data/active_policy_list/active_policy_list.json). + Updating an outdated policy is done as follows: -- The [major version](https://semver.org/) of the applied policy is compared to the catalog policy. If the major version has changed, an error is raised indicating that the update should be done manually. This is because a major version change usually involves major changes in functionality and input parameters that would require the user to intelligently determine how to apply the updated policy. -- If the major version has not changed, the catalog policy is applied with the exact same configuration and settings as the existing applied policy. +- The [major version](https://semver.org/) of the applied policy is compared to the catalog policy. If the major version has changed, and the `Allow Automated Major Version Updates` parameter is set to "Do Not Allow", an error is raised indicating that the update should be done manually. This is because a major version change usually involves major changes in functionality and input parameters that would require the user to intelligently determine how to apply the updated policy. +- If the major version has not changed, or the `Allow Automated Major Version Updates` parameter is set to "Allow", the catalog policy is applied with the exact same configuration and settings as the existing applied policy. - If the above action was successful, the existing applied policy is deleted. If the above action failed, an error is raised and the existing applied policy remains in place so that the user can manually update as needed. ## Input Parameters @@ -32,6 +41,8 @@ This policy has the following input parameters required when launching the polic - *Email Addresses* - A list of email addresses to notify. - *Policy Ignore List* - A list of applied policy names and/or IDs to ignore and not report on. Leave blank to assess all applied policies. +- *Policy Templates To Report* - Whether to report outdated policy templates, deprecated policy templates, or both. Separate incidents will be raised/emailed if both are selected and found. +- *Allow Automated Major Version Updates* - Whether to allow actions to automatically update outdated policy templates when there's been a major version change. This is not recommended in most cases. - *Automatic Actions* - When this value is set, this policy will automatically take the selected action(s). ## Prerequisites @@ -41,18 +52,10 @@ This Policy Template uses [Credentials](https://docs.flexera.com/flexera/EN/Auto ### Credential Configuration - [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: - - `governance:published_template:index` - - `governance:published_template:show` - - `governance:policy_aggregate:index` - - `governance:policy_aggregate:show` - - `governance:applied_policy:index` - - `governance:applied_policy:show` - - `governance:policy_aggregate:create`* - - `governance:policy_aggregate:delete`* - - `governance:applied_policy:create`* - - `governance:applied_policy:delete`* - -\* Only required for taking action (updating applied policies); the policy will still function in a read-only capacity without these permissions. + - `policy_viewer` + - `policy_manager`* + + \* Only required for taking action (updating applied policies); the policy will still function in a read-only capacity without these permissions. The [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) page in the docs has detailed instructions for setting up Credentials for the most common providers. diff --git a/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt b/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt index c1016c1672..3dbd19ca40 100644 --- a/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt +++ b/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt @@ -7,7 +7,7 @@ severity "low" category "Operational" default_frequency "weekly" info( - version: "0.1.1", + version: "0.2.0", provider: "Flexera", service: "Automation", policy_set: "Automation" @@ -33,13 +33,31 @@ parameter "param_ignore_list" do default [] end +parameter "param_report_filter" do + type "string" + category "Filters" + label "Policy Templates To Report" + description "Whether to report outdated policy templates, deprecated policy templates, or both. Separate incidents will be raised/emailed if both are selected and found." + allowed_values "Both", "Report Outdated Only", "Report Deprecated Only" + default "Both" +end + +parameter "param_allow_major" do + type "string" + category "Actions" + label "Allow Automated Major Version Updates" + description "Whether to allow actions to automatically update outdated policy templates when there's been a major version change. This is not recommended in most cases." + allowed_values "Allow", "Do Not Allow" + default "Do Not Allow" +end + parameter "param_automatic_action" do type "list" category "Actions" label "Automatic Actions" description "When this value is set, this policy will automatically take the selected action(s)" - default [] allowed_values ["Update Applied Policies"] + default [] end ############################################################################### @@ -150,16 +168,20 @@ datasource "ds_catalog_policies" do field "provider", jmes_path(col_item, "provider") field "service", jmes_path(col_item, "service") field "policy_set", jmes_path(col_item, "policy_set") + field "recommendation_type", jmes_path(col_item, "recommendation_type") + field "updated_at", jmes_path(col_item, "updated_at") + field "generally_recommended", jmes_path(col_item, "generally_recommended") + field "deprecated", jmes_path(col_item, "deprecated") end end end datasource "ds_outdated_policies" do - run_script $js_outdated_policies, $ds_applied_policies, $ds_catalog_policies, $ds_policy_aggregates, $ds_self_policy, $param_ignore_list, rs_org_id, rs_project_id, policy_id, rs_optima_host + run_script $js_outdated_policies, $ds_applied_policies, $ds_catalog_policies, $ds_policy_aggregates, $ds_self_policy, $param_ignore_list, $param_report_filter, rs_org_id, rs_project_id, policy_id, rs_optima_host end script "js_outdated_policies", type: "javascript" do - parameters "ds_applied_policies", "ds_catalog_policies", "ds_policy_aggregates", "ds_self_policy", "param_ignore_list", "rs_org_id", "rs_project_id", "policy_id", "rs_optima_host" + parameters "ds_applied_policies", "ds_catalog_policies", "ds_policy_aggregates", "ds_self_policy", "param_ignore_list", "param_report_filter", "rs_org_id", "rs_project_id", "policy_id", "rs_optima_host" result "result" code <<-'EOS' tld_table = { @@ -197,25 +219,30 @@ script "js_outdated_policies", type: "javascript" do // Exclude policies on the user-provided ignore list ignore_list = _.map(param_ignore_list, function(item) { return item.toLowerCase().trim() }) - if (_.contains(ignore_list, policy['name'].toLowerCase()) || _.contains(ignore_list, policy['id'].toLowerCase())) { + if (!reject_policy && (_.contains(ignore_list, policy['name'].toLowerCase()) || _.contains(ignore_list, policy['id'].toLowerCase()))) { reject_policy = true } // Exclude policies that were not applied from the catalog - if (policy['published_template'] == null) { + if (!reject_policy && policy['published_template'] == null) { reject_policy = true - } else { + } else if (!reject_policy) { // Exclude policies where we did not find a corresponding entry in the active policy list if (catalog_object[policy['published_template']['name']] == undefined) { reject_policy = true } // Exclude policies applied from a catalog other than the Flexera one - if (policy['published_template']['updated_by']['email'] != 'support@flexera.com') { + if (!reject_policy && policy['published_template']['updated_by']['email'] != 'support@flexera.com') { reject_policy = true } } + // Exclude all policies if the user opted not to report on outdated policies + if (!reject_policy && param_report_filter == "Report Deprecated Only") { + reject_policy = true + } + return reject_policy }) @@ -257,6 +284,16 @@ script "js_outdated_policies", type: "javascript" do template_href: policy['published_template']['href'] } + catalog_recommendation_type = "" + + if (typeof(catalog_policy['recommendation_type']) == 'string') { + catalog_recommendation_type = catalog_policy['recommendation_type'] + } + + boolean_table = { "true": "True", "false": "False" } + + catalog_url = "https://github.com/flexera-public/policy_templates/tree/master/" + catalog_policy['readme'] + return { id: policy['id'], name: policy['name'] + "||" + policy_url, @@ -276,8 +313,7 @@ script "js_outdated_policies", type: "javascript" do log_level: policy['log_level'], catalog_id: policy['published_template']['id'], catalog_href: policy['published_template']['href'], - catalog_updated_at: policy['published_template']['updated_at'], - catalog_name: catalog_policy['name'], + catalog_name: catalog_policy['name'] + "||" + catalog_url, catalog_file_name: catalog_policy['file_name'], catalog_version: catalog_policy['version'], catalog_change_log: catalog_policy['change_log'], @@ -288,6 +324,10 @@ script "js_outdated_policies", type: "javascript" do catalog_provider: catalog_policy['provider'], catalog_service: catalog_policy['service'], catalog_policy_set: catalog_policy['policy_set'], + catalog_recommendation_type: catalog_recommendation_type, + catalog_updated_at: catalog_policy['updated_at'], + catalog_generally_recommended: boolean_table[catalog_policy['generally_recommended'].toString()], + catalog_deprecated: boolean_table[catalog_policy['deprecated'].toString()], self_policy_name: ds_self_policy['name'], href: href, update_body: update_body, @@ -332,6 +372,200 @@ script "js_outdated_policies", type: "javascript" do EOS end +datasource "ds_deprecated_policies" do + run_script $js_deprecated_policies, $ds_applied_policies, $ds_catalog_policies, $ds_policy_aggregates, $ds_self_policy, $param_ignore_list, $param_report_filter, rs_org_id, rs_project_id, policy_id, rs_optima_host +end + +script "js_deprecated_policies", type: "javascript" do + parameters "ds_applied_policies", "ds_catalog_policies", "ds_policy_aggregates", "ds_self_policy", "param_ignore_list", "param_report_filter", "rs_org_id", "rs_project_id", "policy_id", "rs_optima_host" + result "result" + code <<-'EOS' + tld_table = { + "api.optima.flexeraeng.com": "app.flexera.com", + "api.optima-eu.flexeraeng.com": "app.flexera.eu", + "api.optima-apac.flexeraeng.com": "app.flexera.au" + } + + tld = tld_table[rs_optima_host] + + aggregate_object = {} + + _.each(ds_policy_aggregates, function(agg) { + if (typeof(agg['running_project_ids']) == 'object') { + if (agg['running_project_ids'].length == 1 && agg['running_project_ids'][0] == rs_project_id) { + aggregate_object[agg['name']] = agg['href'] + } + } + }) + + catalog_object = {} + + _.each(ds_catalog_policies, function(policy) { + catalog_object[policy['name']] = policy + }) + + filtered_policies = _.reject(ds_applied_policies, function(policy) { + reject_policy = false + + // Exclude this policy to avoid the policy trying to terminate itself if the user actions on it + if (policy['id'] == policy_id) { + reject_policy = true + } + + // Exclude policies on the user-provided ignore list + ignore_list = _.map(param_ignore_list, function(item) { return item.toLowerCase().trim() }) + + if (!reject_policy && (_.contains(ignore_list, policy['name'].toLowerCase()) || _.contains(ignore_list, policy['id'].toLowerCase()))) { + reject_policy = true + } + + // Exclude policies that were not applied from the catalog + if (!reject_policy && policy['published_template'] == null) { + reject_policy = true + } else if (!reject_policy) { + // Exclude policies where we did not find a corresponding entry in the active policy list + if (catalog_object[policy['published_template']['name']] == undefined) { + reject_policy = true + } + + // Exclude policies applied from a catalog other than the Flexera one + if (!reject_policy && policy['published_template']['updated_by']['email'] != 'support@flexera.com') { + reject_policy = true + } + } + + // Exclude all policies if the user opted not to report on deprecated policies + if (!reject_policy && param_report_filter == "Report Outdated Only") { + reject_policy = true + } + + // Exclude non-deprecated policies + if (!reject_policy && !catalog_object[policy['published_template']['name']]['deprecated']) { + reject_policy = true + } + + return reject_policy + }) + + result = _.map(filtered_policies, function(policy) { + catalog_policy = catalog_object[policy['published_template']['name']] + policy_url = "https://" + tld + "/orgs/" + rs_org_id + "/automation/applied-policies/projects/" + rs_project_id + "?policyId=" + policy['id'] + + recommendationDetails = [ + "Terminate applied policy ", policy['name'], " (", policy['id'], ")" + ].join('') + + href = null + + if (policy['scope'] == 'org' && typeof(aggregate_object[policy['name']]) == 'string') { + href = aggregate_object[policy['name']] + } + + if (policy['scope'] != 'org') { + href = policy['href'] + } + + update_body = { + credentials: policy['credentials'], + description: policy['description'], + dry_run: policy['dry_run'], + frequency: policy['frequency'], + log_level: policy['log_level'], + name: policy['name'], + options: policy['options'], + severity: policy['severity'], + skip_approvals: policy['skip_approvals'], + template_href: policy['published_template']['href'] + } + + catalog_recommendation_type = "" + + if (typeof(catalog_policy['recommendation_type']) == 'string') { + catalog_recommendation_type = catalog_policy['recommendation_type'] + } + + boolean_table = { "true": "True", "false": "False" } + + catalog_url = "https://github.com/flexera-public/policy_templates/tree/master/" + catalog_policy['readme'] + + return { + id: policy['id'], + name: policy['name'] + "||" + policy_url, + name_without_link: policy['name'], + description: policy['description'], + category: policy['category'], + created_at: policy['created_at'], + created_by: policy['created_by'], + frequency: policy['frequency'], + version: policy['version'], + credentials: policy['credentials'], + options: policy['options'], + severity: policy['severity'], + skip_approvals: policy['skip_approvals'], + scope: policy['scope'], + dry_run: policy['dry_run'], + log_level: policy['log_level'], + catalog_id: policy['published_template']['id'], + catalog_href: policy['published_template']['href'], + catalog_name: catalog_policy['name'] + "||" + catalog_url, + catalog_file_name: catalog_policy['file_name'], + catalog_version: catalog_policy['version'], + catalog_change_log: catalog_policy['change_log'], + catalog_description: catalog_policy['description'], + catalog_category: catalog_policy['category'], + catalog_severity: catalog_policy['severity'], + catalog_readme: catalog_policy['readme'], + catalog_provider: catalog_policy['provider'], + catalog_service: catalog_policy['service'], + catalog_policy_set: catalog_policy['policy_set'], + catalog_recommendation_type: catalog_recommendation_type, + catalog_updated_at: catalog_policy['updated_at'], + catalog_generally_recommended: boolean_table[catalog_policy['generally_recommended'].toString()], + catalog_deprecated: boolean_table[catalog_policy['deprecated'].toString()], + self_policy_name: ds_self_policy['name'], + href: href, + update_body: update_body, + recommendationDetails: recommendationDetails, + message: '' + } + }) + + if (result.length > 0) { + total_applied_policies = ds_applied_policies.length.toString() + total_deprecated = result.length.toString() + deprecated_percentage = (total_deprecated / total_applied_policies * 100).toFixed(2).toString() + '%' + + pol_noun = "policies" + if (total_applied_policies == 1) { pol_noun = "policy" } + + pol_verb = "are deprecated" + if (total_deprecated == 1) { pol_verb = "is deprecated" } + + pol_action = [ + "recommended for termination. ", + "Please consult the policy template README for information on what policy template to use instead. ", + "The README can be accessed by clicking on the link in the 'Catalog Policy Name' field" + ].join('') + + message = [ + "Out of ", total_applied_policies, " ", pol_noun, " analyzed, ", + total_deprecated, " (", deprecated_percentage, + ") ", pol_verb, " and ", pol_action, ".\n\n" + ].join('') + + settings = "No policies were filtered from this report.\n\n" + + if (param_ignore_list.length > 0) { + settings = "The following policies were filtered from this report: " + param_ignore_list.join(', ') + "\n\n" + } + + disclaimer = "Filtering can be adjusted by editing the applied policy and changing the appropriate parameters." + + result[0]['message'] = message + settings + disclaimer + } +EOS +end + ############################################################################## # Policy ############################################################################### @@ -358,6 +592,83 @@ policy "pol_outdated_policies" do field "catalog_updated_at" do label "Date Catalog Updated" end + field "catalog_deprecated" do + label "Deprecated" + end + field "version" do + label "Applied Policy Version" + end + field "catalog_version" do + label "Catalog Policy Version" + end + field "catalog_id" do + label "Catalog Policy ID" + end + field "catalog_name" do + label "Catalog Policy Name" + format "link-external" + end + field "catalog_href" do + label "Catalog Policy HREF" + end + field "id" do + label "Applied Policy ID" + end + field "href" do + label "Applied Policy HREF" + end + field "description" do + label "Applied Policy Description" + end + field "frequency" do + label "Applied Policy Frequency" + end + field "severity" do + label "Applied Policy Severity" + end + field "skip_approvals" do + label "Applied Policy Skip Approvals" + end + field "scope" do + label "Applied Policy Scope" + end + field "dry_run" do + label "Applied Policy Dry Run" + end + field "log_level" do + label "Applied Policy Log Level" + end + field "name_without_link" do + label "Applied Policy Name (Unlinked)" + end + field "update_body" do + label "Applied Policy Details" + end + end + end + validate_each $ds_deprecated_policies do + summary_template "{{ with index data 0 }}{{ .self_policy_name }}{{ end }}: {{ len data }} Deprecated Policies Found" + detail_template "{{ with index data 0 }}{{ .message }}{{ end }}" + check eq(val(item, "id"), "") + escalate $esc_email + export do + resource_level true + field "name" do + label "Applied Policy Name" + format "link-external" + end + field "recommendationDetails" do + label "Recommendation" + end + field "created_at" do + label "Date Applied" + end + field "catalog_updated_at" do + label "Date Catalog Updated" + end + field "catalog_deprecated" do + label "Deprecated" + end field "version" do label "Applied Policy Version" end @@ -369,6 +680,7 @@ policy "pol_outdated_policies" do end field "catalog_name" do label "Catalog Policy Name" + format "link-external" end field "catalog_href" do label "Catalog Policy HREF" @@ -425,20 +737,20 @@ escalation "esc_update_policies" do automatic contains($param_automatic_action, "Update Applied Policies") label "Update Applied Policies" description "Approval to update all selected applied policies to the latest version" - run "update_policies", data, rs_governance_host, rs_project_id + run "update_policies", data, $param_allow_major, rs_governance_host, rs_project_id end ############################################################################### # Cloud Workflow ############################################################################### -define update_policies($data, $rs_governance_host, $rs_project_id) return $all_responses do +define update_policies($data, $param_allow_major, $rs_governance_host, $rs_project_id) return $all_responses do $$all_responses = [] foreach $policy in $data do sub on_error: handle_error() do - if split($policy['version'], '.')[0] == split($policy['catalog_version'], '.')[0] + if split($policy['version'], '.')[0] == split($policy['catalog_version'], '.')[0] || $param_allow_major == "Allow" call apply_policy($policy, $rs_governance_host, $rs_project_id) retrieve $apply_response, $code if $code == 204 || $code == 202 || $code == 200 diff --git a/data/policy_permissions_list/master_policy_permissions_list.json b/data/policy_permissions_list/master_policy_permissions_list.json index a932b18ffa..b6d6c955fd 100644 --- a/data/policy_permissions_list/master_policy_permissions_list.json +++ b/data/policy_permissions_list/master_policy_permissions_list.json @@ -183,61 +183,18 @@ { "id": "./automation/flexera/outdated_applied_policies/outdated_applied_policies.pt", "name": "Flexera Automation Outdated Applied Policies", - "version": "0.1.1", + "version": "0.2.0", "providers": [ { "name": "flexera", "permissions": [ { - "name": "governance:published_template:index", - "read_only": true, - "required": true - }, - { - "name": "governance:published_template:show", - "read_only": true, - "required": true - }, - { - "name": "governance:policy_aggregate:index", - "read_only": true, - "required": true - }, - { - "name": "governance:policy_aggregate:show", - "read_only": true, - "required": true - }, - { - "name": "governance:applied_policy:index", - "read_only": true, - "required": true - }, - { - "name": "governance:applied_policy:show", + "name": "policy_viewer", "read_only": true, "required": true }, { - "name": "governance:policy_aggregate:create", - "read_only": false, - "required": false, - "description": "Only required for taking action (updating applied policies); the policy will still function in a read-only capacity without these permissions." - }, - { - "name": "governance:policy_aggregate:delete", - "read_only": false, - "required": false, - "description": "Only required for taking action (updating applied policies); the policy will still function in a read-only capacity without these permissions." - }, - { - "name": "governance:applied_policy:create", - "read_only": false, - "required": false, - "description": "Only required for taking action (updating applied policies); the policy will still function in a read-only capacity without these permissions." - }, - { - "name": "governance:applied_policy:delete", + "name": "policy_manager", "read_only": false, "required": false, "description": "Only required for taking action (updating applied policies); the policy will still function in a read-only capacity without these permissions." diff --git a/data/policy_permissions_list/master_policy_permissions_list.yaml b/data/policy_permissions_list/master_policy_permissions_list.yaml index 304d06cd6f..b8e90fe287 100644 --- a/data/policy_permissions_list/master_policy_permissions_list.yaml +++ b/data/policy_permissions_list/master_policy_permissions_list.yaml @@ -101,44 +101,14 @@ required: true - id: "./automation/flexera/outdated_applied_policies/outdated_applied_policies.pt" name: Flexera Automation Outdated Applied Policies - version: 0.1.1 + version: 0.2.0 :providers: - :name: flexera :permissions: - - name: governance:published_template:index - read_only: true - required: true - - name: governance:published_template:show + - name: policy_viewer read_only: true required: true - - name: governance:policy_aggregate:index - read_only: true - required: true - - name: governance:policy_aggregate:show - read_only: true - required: true - - name: governance:applied_policy:index - read_only: true - required: true - - name: governance:applied_policy:show - read_only: true - required: true - - name: governance:policy_aggregate:create - read_only: false - required: false - description: Only required for taking action (updating applied policies); the - policy will still function in a read-only capacity without these permissions. - - name: governance:policy_aggregate:delete - read_only: false - required: false - description: Only required for taking action (updating applied policies); the - policy will still function in a read-only capacity without these permissions. - - name: governance:applied_policy:create - read_only: false - required: false - description: Only required for taking action (updating applied policies); the - policy will still function in a read-only capacity without these permissions. - - name: governance:applied_policy:delete + - name: policy_manager read_only: false required: false description: Only required for taking action (updating applied policies); the From 606735f72f5d0df68f43d0af4d3d215de3245b8f Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Sun, 15 Sep 2024 17:29:35 -0500 Subject: [PATCH 08/10] update --- .spellignore | 2 ++ automation/flexera/outdated_applied_policies/README.md | 7 +++++-- .../outdated_applied_policies/outdated_applied_policies.pt | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.spellignore b/.spellignore index 63732d2cf4..eb91b54554 100644 --- a/.spellignore +++ b/.spellignore @@ -565,3 +565,5 @@ USD EUR CCO untagged +README +readme diff --git a/automation/flexera/outdated_applied_policies/README.md b/automation/flexera/outdated_applied_policies/README.md index c2824317b6..8a91fe0a0d 100644 --- a/automation/flexera/outdated_applied_policies/README.md +++ b/automation/flexera/outdated_applied_policies/README.md @@ -37,14 +37,17 @@ Updating an outdated policy is done as follows: ## Input Parameters -This policy has the following input parameters required when launching the policy. - - *Email Addresses* - A list of email addresses to notify. - *Policy Ignore List* - A list of applied policy names and/or IDs to ignore and not report on. Leave blank to assess all applied policies. - *Policy Templates To Report* - Whether to report outdated policy templates, deprecated policy templates, or both. Separate incidents will be raised/emailed if both are selected and found. - *Allow Automated Major Version Updates* - Whether to allow actions to automatically update outdated policy templates when there's been a major version change. This is not recommended in most cases. - *Automatic Actions* - When this value is set, this policy will automatically take the selected action(s). +## Policy Actions + +- Send an email report +- Update applied policy to the newest version after approval + ## Prerequisites This Policy Template uses [Credentials](https://docs.flexera.com/flexera/EN/Automation/ManagingCredentialsExternal.htm) for authenticating to datasources -- in order to apply this policy you must have a Credential registered in the system that is compatible with this policy. If there are no Credentials listed when you apply the policy, please contact your Flexera Org Admin and ask them to register a Credential that is compatible with this policy. The information below should be consulted when creating the credential(s). diff --git a/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt b/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt index 3dbd19ca40..367dee8444 100644 --- a/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt +++ b/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt @@ -566,7 +566,7 @@ script "js_deprecated_policies", type: "javascript" do EOS end -############################################################################## +############################################################################### # Policy ############################################################################### From d48d136831d2fe8c715c74a4a051f43c3500f633 Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Sun, 15 Sep 2024 17:54:53 -0500 Subject: [PATCH 09/10] update --- .dangerfile/policy_tests.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.dangerfile/policy_tests.rb b/.dangerfile/policy_tests.rb index 906daf14af..ca5b72ede3 100644 --- a/.dangerfile/policy_tests.rb +++ b/.dangerfile/policy_tests.rb @@ -69,7 +69,7 @@ def policy_bad_directory?(file) fail_message += "Policy is not located within a subdirectory specific to the cloud provider or service it is applicable for. For example, AWS cost policies should be in the `/cost/aws` subdirectory, Azure operational policies in the `/operational/azure` subdirectory, etc.\n\n" end - if (parts[1] == 'flexera' && parts[3].include?('.pt')) && parts[0] != "tools" + if (parts[1] == 'flexera' && parts[3].include?('.pt')) && parts[0] != "tools" && parts[0] != "automation" fail_message += "Flexera policy is not contained in a subdirectory specific to the Flexera service it is for. For example, Flexera CCO cost policies should be in the `/cost/flexera/cco` subdirectory.\n\n" end From 0dfacfa642164f08ed4bb89d1a846cc55e809fdf Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Mon, 23 Sep 2024 20:18:16 -0500 Subject: [PATCH 10/10] fix --- .spellignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.spellignore b/.spellignore index 29f5610b4c..aa62d0d992 100644 --- a/.spellignore +++ b/.spellignore @@ -565,10 +565,8 @@ USD EUR CCO untagged -<<<<<<< HEAD README readme -======= expiryDate licenseDuration licenseId @@ -586,4 +584,3 @@ FSM ByteCount PacketCount balancers ->>>>>>> master