From 732ba5d183b81606c1817a437097fb3b3fd4c62a Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Thu, 28 Mar 2024 07:47:19 -0500 Subject: [PATCH 01/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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 f510611e07e6d0b209aae11a985b2e191cffa671 Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Tue, 8 Oct 2024 03:08:27 -0500 Subject: [PATCH 07/11] update --- .spellignore | 29 +++++++++++++++++++ automation/aws/aws_missing_regions/README.md | 2 +- automation/aws/aws_rbd_from_tag/README.md | 2 +- .../azure_missing_subscriptions/CHANGELOG.md | 4 +++ .../azure_missing_subscriptions/README.md | 2 +- .../azure_missing_subscriptions.pt | 4 +-- .../azure/azure_rbd_from_rg_tag/README.md | 2 +- automation/azure/azure_rbd_from_tag/README.md | 2 +- .../delete_all_billing_centers/CHANGELOG.md | 4 +++ .../delete_all_billing_centers/README.md | 10 ++++++- .../delete_all_billing_centers.pt | 4 +-- .../outdated_applied_policies/README.md | 2 +- .../google/google_rbd_from_label/README.md | 2 +- 13 files changed, 57 insertions(+), 12 deletions(-) diff --git a/.spellignore b/.spellignore index 1c67184d4e..537c65c574 100644 --- a/.spellignore +++ b/.spellignore @@ -585,3 +585,32 @@ ByteCount PacketCount balancers backfill +FNMS +CBI +workspace +workspaces +Workspace +Workspaces +OCID +OAuth +oauth +BYOL +GCP +TLS +SSL +Balancer +balancer +LUN +Unmanaged +unmanaged +Oversized +oversized +AMI +actioning +actioned +VPN +VPNs +Dataset +dataset +Datasets +datasets diff --git a/automation/aws/aws_missing_regions/README.md b/automation/aws/aws_missing_regions/README.md index 22893aed73..4aa17c94e2 100644 --- a/automation/aws/aws_missing_regions/README.md +++ b/automation/aws/aws_missing_regions/README.md @@ -59,4 +59,4 @@ The [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automati ## Cost -This Policy Template does not incur any cloud costs. +This policy template does not incur any cloud costs. diff --git a/automation/aws/aws_rbd_from_tag/README.md b/automation/aws/aws_rbd_from_tag/README.md index 311f531d76..e8ebf29119 100644 --- a/automation/aws/aws_rbd_from_tag/README.md +++ b/automation/aws/aws_rbd_from_tag/README.md @@ -34,4 +34,4 @@ The [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automati ## Cost -This Policy Template does not launch any instances, and so does not incur any cloud costs. +This policy template does not incur any cloud costs. diff --git a/automation/azure/azure_missing_subscriptions/CHANGELOG.md b/automation/azure/azure_missing_subscriptions/CHANGELOG.md index 1ad604f8d4..f18747584b 100644 --- a/automation/azure/azure_missing_subscriptions/CHANGELOG.md +++ b/automation/azure/azure_missing_subscriptions/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.1.1 + +- Minor code improvements to conform with current standards. Functionality unchanged. + ## v0.1 - initial release diff --git a/automation/azure/azure_missing_subscriptions/README.md b/automation/azure/azure_missing_subscriptions/README.md index 5f33caf7f3..716d93e8f3 100644 --- a/automation/azure/azure_missing_subscriptions/README.md +++ b/automation/azure/azure_missing_subscriptions/README.md @@ -37,4 +37,4 @@ The [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automati ## Cost -This Policy Template does not incur any cloud costs. +This policy template does not incur any cloud costs. diff --git a/automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt b/automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt index 4cdf0a2821..31fa39c86c 100644 --- a/automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt +++ b/automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt @@ -7,7 +7,7 @@ severity "low" category "Cost" default_frequency "weekly" info( - version: "0.1", + version: "0.1.1", provider: "Flexera", service: "Optima", policy_set: "Automation", @@ -102,9 +102,9 @@ datasource "ds_billing_centers" do auth $auth_flexera host rs_optima_host path join(["/analytics/orgs/", rs_org_id, "/billing_centers"]) + query "view", "allocation_table" header "Api-Version", "1.0" header "User-Agent", "RS Policies" - query "view", "allocation_table" ignore_status [403] end result do diff --git a/automation/azure/azure_rbd_from_rg_tag/README.md b/automation/azure/azure_rbd_from_rg_tag/README.md index 98ae4beb88..12e7c4928a 100644 --- a/automation/azure/azure_rbd_from_rg_tag/README.md +++ b/automation/azure/azure_rbd_from_rg_tag/README.md @@ -40,4 +40,4 @@ The [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automati ## Cost -This Policy Template does not launch any instances, and so does not incur any cloud costs. +This policy template does not incur any cloud costs. diff --git a/automation/azure/azure_rbd_from_tag/README.md b/automation/azure/azure_rbd_from_tag/README.md index 270f1ef5ef..10d38b2d6d 100644 --- a/automation/azure/azure_rbd_from_tag/README.md +++ b/automation/azure/azure_rbd_from_tag/README.md @@ -39,4 +39,4 @@ The [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automati ## Cost -This Policy Template does not launch any instances, and so does not incur any cloud costs. +This policy template does not incur any cloud costs. diff --git a/automation/flexera/delete_all_billing_centers/CHANGELOG.md b/automation/flexera/delete_all_billing_centers/CHANGELOG.md index 3a41ea7330..6b508a1e70 100644 --- a/automation/flexera/delete_all_billing_centers/CHANGELOG.md +++ b/automation/flexera/delete_all_billing_centers/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v1.1.1 + +- Minor code improvements to conform with current standards. Functionality unchanged. + ## v1.1 - Updated policy metadata to make it more clear what Flexera service the policy is for diff --git a/automation/flexera/delete_all_billing_centers/README.md b/automation/flexera/delete_all_billing_centers/README.md index 45be0de250..878c9a5917 100644 --- a/automation/flexera/delete_all_billing_centers/README.md +++ b/automation/flexera/delete_all_billing_centers/README.md @@ -13,6 +13,14 @@ This policy deletes all Billing Centers in the Flexera organization it is execut - If this is the policy's second time executing, but for some reason the policy has failed to self-terminate, no action will be taken. - If this is the policy's first time executing, all of the Billing Centers in the Flexera organization are deleted. +## Input Parameters + +This policy template has no input parameters. + +## Policy Actions + +- Delete all Billing Centers + ## 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). @@ -31,4 +39,4 @@ The [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automati ## Cost -This Policy Template does not incur any cloud costs. +This policy template does not incur any cloud costs. diff --git a/automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt b/automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt index c39455a663..2eb4046e2c 100644 --- a/automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt +++ b/automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt @@ -7,7 +7,7 @@ severity "high" category "Cost" default_frequency "15 minutes" info( - version: "1.1", + version: "1.1.1", provider: "Flexera", service: "Cloud Cost Optimization", policy_set: "Automation", @@ -78,8 +78,8 @@ datasource "ds_self_terminate" do iterate $ds_self_terminate_boolean request do auth $auth_flexera - host rs_governance_host verb "DELETE" + host rs_governance_host path val(iter_item, 'id') header "Api-Version", "1.0" end diff --git a/automation/flexera/outdated_applied_policies/README.md b/automation/flexera/outdated_applied_policies/README.md index 8a91fe0a0d..91be793e28 100644 --- a/automation/flexera/outdated_applied_policies/README.md +++ b/automation/flexera/outdated_applied_policies/README.md @@ -68,4 +68,4 @@ The [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automati ## Cost -This Policy Template does not incur any cloud costs. Cloud costs may be incurred by the applied policies that this policy reports on and updates. Please consult the README of each policy for more information. +This policy template does not incur any cloud costs. Cloud costs may be incurred by the applied policies that this policy reports on and updates. Please consult the README of each policy for more information. diff --git a/automation/google/google_rbd_from_label/README.md b/automation/google/google_rbd_from_label/README.md index 16ec257e2c..0f4903041c 100644 --- a/automation/google/google_rbd_from_label/README.md +++ b/automation/google/google_rbd_from_label/README.md @@ -37,4 +37,4 @@ The [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automati ## Cost -This Policy Template does not launch any instances, and so does not incur any cloud costs. +This policy template does not incur any cloud costs. From 4af5e375f7e252f3bd0f10acb1f24b944738632f Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Tue, 8 Oct 2024 03:09:57 -0500 Subject: [PATCH 08/11] update --- .../aws/aws_missing_regions/CHANGELOG.md | 9 - automation/aws/aws_missing_regions/README.md | 62 - .../aws_missing_regions.pt | 359 ----- .../aws_missing_regions_meta_parent.pt | 1221 ----------------- automation/aws/aws_rbd_from_tag/CHANGELOG.md | 34 - automation/aws/aws_rbd_from_tag/README.md | 37 - .../aws/aws_rbd_from_tag/aws_rbd_from_tag.pt | 277 ---- .../azure_missing_subscriptions/CHANGELOG.md | 9 - .../azure_missing_subscriptions/README.md | 40 - .../azure_missing_subscriptions.pt | 340 ----- .../azure/azure_rbd_from_rg_tag/CHANGELOG.md | 25 - .../azure/azure_rbd_from_rg_tag/README.md | 43 - .../azure_rbd_from_rg_tag.pt | 418 ------ .../azure/azure_rbd_from_tag/CHANGELOG.md | 25 - automation/azure/azure_rbd_from_tag/README.md | 42 - .../azure_rbd_from_tag/azure_rbd_from_tag.pt | 318 ----- .../delete_all_billing_centers/CHANGELOG.md | 13 - .../delete_all_billing_centers/README.md | 42 - .../delete_all_billing_centers.pt | 233 ---- .../outdated_applied_policies/CHANGELOG.md | 14 - .../outdated_applied_policies/README.md | 71 - .../outdated_applied_policies.pt | 835 ----------- .../google/google_rbd_from_label/CHANGELOG.md | 26 - .../google/google_rbd_from_label/README.md | 40 - .../google_rbd_from_label.pt | 305 ---- 25 files changed, 4838 deletions(-) delete mode 100644 automation/aws/aws_missing_regions/CHANGELOG.md delete mode 100644 automation/aws/aws_missing_regions/README.md delete mode 100644 automation/aws/aws_missing_regions/aws_missing_regions.pt delete mode 100644 automation/aws/aws_missing_regions/aws_missing_regions_meta_parent.pt delete mode 100644 automation/aws/aws_rbd_from_tag/CHANGELOG.md delete mode 100644 automation/aws/aws_rbd_from_tag/README.md delete mode 100644 automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt delete mode 100644 automation/azure/azure_missing_subscriptions/CHANGELOG.md delete mode 100644 automation/azure/azure_missing_subscriptions/README.md delete mode 100644 automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt delete mode 100644 automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md delete mode 100644 automation/azure/azure_rbd_from_rg_tag/README.md delete mode 100644 automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt delete mode 100644 automation/azure/azure_rbd_from_tag/CHANGELOG.md delete mode 100644 automation/azure/azure_rbd_from_tag/README.md delete mode 100644 automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt delete mode 100644 automation/flexera/delete_all_billing_centers/CHANGELOG.md delete mode 100644 automation/flexera/delete_all_billing_centers/README.md delete mode 100644 automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt delete mode 100644 automation/flexera/outdated_applied_policies/CHANGELOG.md delete mode 100644 automation/flexera/outdated_applied_policies/README.md delete mode 100644 automation/flexera/outdated_applied_policies/outdated_applied_policies.pt delete mode 100644 automation/google/google_rbd_from_label/CHANGELOG.md delete mode 100644 automation/google/google_rbd_from_label/README.md delete mode 100644 automation/google/google_rbd_from_label/google_rbd_from_label.pt diff --git a/automation/aws/aws_missing_regions/CHANGELOG.md b/automation/aws/aws_missing_regions/CHANGELOG.md deleted file mode 100644 index c0e1662927..0000000000 --- a/automation/aws/aws_missing_regions/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# Changelog - -## v0.2.0 - -- Fixed issue with meta policy not working due to missing `Account Number` parameter - -## v0.1 - -- initial release diff --git a/automation/aws/aws_missing_regions/README.md b/automation/aws/aws_missing_regions/README.md deleted file mode 100644 index 4aa17c94e2..0000000000 --- a/automation/aws/aws_missing_regions/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# AWS Missing Regions - -## What It Does - -This policy checks the list of AWS regions returned by an AWS `DescribeRegions` API request and tests each region with an EC2 `DescribeInstances` request to see if the Flexera AWS credential can actually run API requests against that region. An incident is raised and email sent with any regions that are inaccessible. - -__NOTE: Meta Parent policy will only work if both the parent and the child are uploaded to the Flexera org and the "Uploaded Template" option is selected for the `Child Policy Template Source` parameter. This is because the child policy is *not* published in the catalog.__ - -## Input Parameters - -This policy has the following input parameters required when launching the policy. - -- *Email Addresses* - Email addresses of the recipients you wish to notify when new incidents are created. -- *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) -- *Region Ignore List* - A list of regions to never include in the results. Leave blank to not filter results - -## Policy Actions - -The following policy actions are taken on any resources found to be out of compliance. - -- Send an email report - -## 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: - - `ec2:DescribeRegions` - - `ec2:DescribeInstances` - - `sts:GetCallerIdentity` - - Example IAM Permission Policy: - - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:DescribeRegions", - "ec2:DescribeInstances", - "sts:GetCallerIdentity" - ], - "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/automation/aws/aws_missing_regions/aws_missing_regions.pt b/automation/aws/aws_missing_regions/aws_missing_regions.pt deleted file mode 100644 index 5b41cc8225..0000000000 --- a/automation/aws/aws_missing_regions/aws_missing_regions.pt +++ /dev/null @@ -1,359 +0,0 @@ -name "AWS Missing Regions" -rs_pt_ver 20180301 -type "policy" -short_description "Reports any AWS regions that are enabled but the policy engine is not able to access. [README](https://github.com/flexera-public/policy_templates/tree/master/automation/aws/aws_missing_regions/) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." -long_description "" -severity "low" -category "Cost" -default_frequency "weekly" -info( - version: "0.2.0", - provider: "Flexera", - service: "Optima", - policy_set: "Automation", - publish: "false" -) - -############################################################################### -# 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_region_list" do - type "list" - category "Filters" - label "Region Ignore List" - description "A list of regions to never include in the results. Leave blank to not filter results." - 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_regions" do - request do - auth $auth_aws - verb "GET" - host "ec2.amazonaws.com" - path "/" - query "Action", "DescribeRegions" - query "Version", "2016-11-15" - query "Filter.1.Name", "opt-in-status" - query "Filter.1.Value.1", "opt-in-not-required" - query "Filter.1.Value.2", "opted-in" - # 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 "xml" - collect xpath(response, "//DescribeRegionsResponse/regionInfo/item", "array") do - field "region", xpath(col_item, "regionName") - end - end -end - -# Make a general API call to list EC2 instances to test functionality -datasource "ds_instance_sets" do - iterate $ds_regions - request do - auth $auth_aws - host join(['ec2.', val(iter_item, 'region'), '.amazonaws.com']) - path '/' - query 'Action', 'DescribeInstances' - query 'Version', '2016-11-15' - header 'User-Agent', 'RS Policies' - header 'Content-Type', 'text/xml' - ignore_status [400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410] - end - result do - encoding "xml" - field "region", val(iter_item, "region") - end -end - -datasource "ds_missing_regions" do - run_script $js_missing_regions, $ds_regions, $ds_instance_sets, $ds_aws_account, $ds_applied_policy, $param_region_list -end - -script "js_missing_regions", type: "javascript" do - parameters "ds_regions", "ds_instance_sets", "ds_aws_account", "ds_applied_policy", "param_region_list" - result "result" - code <<-'EOS' - result = [] - - enabled_regions = _.uniq(_.pluck(ds_regions, 'region')) - found_regions = _.uniq(_.pluck(ds_instance_sets, 'region')) - - _.each(enabled_regions, function(region) { - if (!_.contains(found_regions, region) && !_.contains(param_region_list, region)) { - result.push({ - "accountID": ds_aws_account['id'], - "accountName": ds_aws_account['name'], - "region": region, - "policy_name": ds_applied_policy['name'] - }) - } - }) - - result = _.sortBy(result, 'region') - result = _.sortBy(result, 'accountID') -EOS -end - -############################################################################### -# Policy -############################################################################### - -policy "pol_missing_regions" do - validate_each $ds_missing_regions do - summary_template "{{ with index data 0 }}{{ .policy_name }}{{ end }}: {{ len data }} AWS Missing Regions Detected" - check logic_or($ds_parent_policy_terminated, eq(val(item, "accountID"), "")) - escalate $esc_email - export do - resource_level false - field "accountID" do - label "Account ID" - end - field "accountName" do - label "Account Name" - end - field "region" do - label "Region" - end - end - end -end - -############################################################################### -# Escalations -############################################################################### - -escalation "esc_email" do - automatic true - label "Send Email" - description "Send incident email" - email $param_email -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/automation/aws/aws_missing_regions/aws_missing_regions_meta_parent.pt b/automation/aws/aws_missing_regions/aws_missing_regions_meta_parent.pt deleted file mode 100644 index dbe5d526b0..0000000000 --- a/automation/aws/aws_missing_regions/aws_missing_regions_meta_parent.pt +++ /dev/null @@ -1,1221 +0,0 @@ -name "Meta Parent: AWS Missing Regions" -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 Missing Regions](https://github.com/flexera-public/policy_templates/tree/master/automation/aws/aws_missing_regions) Policies." -severity "low" -category "Meta" -default_frequency "15 minutes" -info( - provider: "AWS", - version: "0.2.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 - publish: "false", - deprecated: "false" -) - -############################################################################## -# 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 Missing Regions\" Policy Template from Catalog. Optionally, you can use the \"AWS Missing Regions\" 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_region_list" do - type "list" - category "Filters" - label "Region Ignore List" - description "A list of regions to never include in the results. Leave blank to not filter results." - 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_get_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" - collect jmes_path(response, "items[*]") do - field "name", jmes_path(col_item, "name") - field "created_by", jmes_path(col_item, "created_by.email") - field "href", jmes_path(col_item, "href") - field "short_description", jmes_path(col_item, "short_description") - end - end -end - -# Select the published policy that is published by "support@flexera.com" and matches the name of the child policy template -datasource "ds_published_child_policy_information" do - run_script $js_published_child_policy_information, $ds_get_published_child_policy_information -end - -script "js_published_child_policy_information", type: "javascript" do - parameters "ds_get_published_child_policy_information" - result "result" - code <<-EOS - result = _.filter(ds_get_published_child_policy_information, function(item) { - return item['name'] == "AWS Missing Regions" && item['created_by'] == "support@flexera.com" - }) -EOS -end - -# Get Uploaded Policy Details -datasource "ds_get_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" - collect jmes_path(response, "items[*]") 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 - -# Select the uploaded policy that matches the name of the child policy template -datasource "ds_project_child_policy_information" do - run_script $js_project_child_policy_information, $ds_get_project_child_policy_information -end - -script "js_project_child_policy_information", type: "javascript" do - parameters "ds_get_project_child_policy_information" - result "result" - code <<-EOS - result = _.filter(ds_get_project_child_policy_information, function(item) { - return item['name'] == "AWS Missing Regions" - }) -EOS -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 - encoding "json" - collect jmes_path(response, "items[*]") do - field "name", jmes_path(col_item, "name") - field "applied_policy_id", jmes_path(col_item, "id") - field "options", jmes_path(col_item, "options") - field "updated_at", jmes_path(col_item, "updated_at") - field "status", jmes_path(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 - encoding "json" - collect jmes_path(response, "items[*]") do - field "incident_id", jmes_path(col_item, "id") - field "applied_policy_id", jmes_path(col_item, "applied_policy.id") - field "summary", jmes_path(col_item, "summary") - field "state", jmes_path(col_item, "state") - field "violation_data_count", jmes_path(col_item, "violation_data_count") - field "updated_at", jmes_path(col_item, "updated_at") - field "meta_parent_policy_id", jmes_path(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 - - - - -# 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 Missing Regions Detected - validate $ds_missing_regions_combined_incidents do - summary_template "Consolidated Incident: {{ len data }} AWS Missing Regions Detected" - escalate $esc_email - - check eq(size(data), 0) - export do - resource_level false - field "accountID" do - label "Account ID" - end - field "accountName" do - label "Account Name" - end - field "region" do - label "Region" - end - 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/automation/aws/aws_rbd_from_tag/CHANGELOG.md b/automation/aws/aws_rbd_from_tag/CHANGELOG.md deleted file mode 100644 index 97fd6e9145..0000000000 --- a/automation/aws/aws_rbd_from_tag/CHANGELOG.md +++ /dev/null @@ -1,34 +0,0 @@ -# Changelog - -## v2.3.2 - -- Fixed issue where tag keys that were not lowercase would not be properly detected and used - -## v2.3.1 - -- Fixed issue that would sometimes cause execution to fail if an AWS Account had no tag keys - -## v2.3.0 - -- Added option to retain original casing of tag values instead of normalizing them all to lowercase - -## v2.2.1 - -- Updated policy template to use newer API endpoints. Functionality is unchanged. - -## v2.2 - -- added ability to specify names for the newly created dimensions - -## v2.1 - -- Corrected API issue when executing policy in APAC - -## v2.0 - -- Removed requirement for AWS credential -- Internal API is now used to gather AWS account and tag information - -## v1.0 - -- initial release diff --git a/automation/aws/aws_rbd_from_tag/README.md b/automation/aws/aws_rbd_from_tag/README.md deleted file mode 100644 index e8ebf29119..0000000000 --- a/automation/aws/aws_rbd_from_tag/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# AWS Rule-Based Dimension From Account Tags - -## What It Does - -This policy creates and updates custom Rule-Based Dimensions that surface the specified AWS Account tag keys in the Flexera One platform. This allows costs to be sliced by the values of the tag keys in question. - -## Input Parameters - -This policy has the following input parameters required when launching the policy. - -- *Effective Date* - The month and year in YYYY-MM format that you want the rules to apply. This should be left at its default value in most cases to ensure that the rules apply to all costs, including historical costs. -- *Tag Keys* - A list of AWS Account tag keys to create custom Rule-Based Dimensions for. -- *Dimension Names* - A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the `Tag Keys` field. Dimension names will be derived from tag keys directly if this list is left empty. -- *Lowercase Values* - Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail. - -## Policy Actions - -- Create/update rule-based dimensions - -## 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). - -- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: - - `observer` - - `billing_center_viewer` - - `rule_based_dimensions_manager` - -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/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt b/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt deleted file mode 100644 index 55aeb43074..0000000000 --- a/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt +++ /dev/null @@ -1,277 +0,0 @@ -name "AWS Rule-Based Dimension From Account Tags" -rs_pt_ver 20180301 -type "policy" -short_description "Creates and/or updates individual Rule-Based Dimensions based on AWS Account tags. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/aws/aws_rbd_from_tag) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." -long_description "" -severity "low" -category "Cost" -default_frequency "daily" -info( - version: "2.3.2", - provider: "Flexera", - service: "Optima", - policy_set: "Automation", - publish: "false" -) - -############################################################################### -# Parameters -############################################################################### - -parameter "param_tag_list" do - type "list" - category "Policy Settings" - label "Tag Keys" - description "A list of AWS account tag keys to build Rule-Based Dimensions from" - # No default value, user input required -end - -parameter "param_name_list" do - type "list" - category "Policy Settings" - label "Dimension Names" - description "A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the 'Tag Keys' field. Dimension names will be derived from tag keys directly if this list is left empty." - default [] -end - -parameter "param_effective_date" do - type "string" - category "Policy Settings" - label "Effective Date" - description "Year/month you want rules to start applying in YYYY-MM format" - default "2010-01" -end - -parameter "param_normalize_case" do - type "string" - category "Policy Settings" - label "Lowercase Values" - description "Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail." - allowed_values "Yes", "No" - default "Yes" -end - -############################################################################### -# Authentication -############################################################################### - -credentials "auth_flexera" do - schemes "oauth2" - label "flexera" - description "Select FlexeraOne OAuth2 credential." - tags "provider=flexera" -end - -############################################################################### -# Datasources & Scripts -############################################################################### - -# 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_cloud_vendor_accounts_normalized" do - run_script $js_cloud_vendor_accounts_normalized, $ds_cloud_vendor_accounts -end - -script "js_cloud_vendor_accounts_normalized", type: "javascript" do - parameters "accounts" - result "result" - code <<-'EOS' - result = [] - - _.each(accounts, function(account) { - tags = {} - - if (account['tags'] != null && account['tags'] != undefined) { - _.each(Object.keys(account['tags']), function(key) { - normalized_key = key.toLowerCase().trim() - tags[normalized_key] = account['tags'][key] - }) - } - - result.push({ - id: account['id'], - name: account['name'], - tags: tags - }) - }) -EOS -end - -datasource "ds_existing_rbds" do - request do - auth $auth_flexera - host rs_optima_host - path join(["/bill-analysis/orgs/", rs_org_id, "/settings/rule_based_dimensions"]) - header "Api-Version", "1.0" - header "content-type", "application/json" - end - result do - encoding "json" - collect jmes_path(response, "rule_based_dimensions") do - field "id", jmes_path(col_item, "id") - field "name", jmes_path(col_item, "name") - field "dated_rules", jmes_path(col_item, "dated_rules") - end - end -end - -datasource "ds_rbds" do - run_script $js_rbds, $ds_cloud_vendor_accounts_normalized, $ds_existing_rbds, $param_name_list, $param_tag_list, $param_effective_date, $param_normalize_case -end - -script "js_rbds", type: "javascript" do - parameters "accounts", "existing_rbds", "param_name_list", "param_tag_list", "param_effective_date", "param_normalize_case" - result "result" - code <<-'EOS' - result = [] - - rbd_id_list = _.pluck(existing_rbds, 'id') - - _.each(param_tag_list, function(tag, index) { - rbd_name = tag.replace('.', ' ').replace('-', ' ') - rbd_name = rbd_name.replace(/\W/g, " ").trim() - - // Use user-specified name instead of user provided one - if (param_tag_list.length == param_name_list.length) { rbd_name = param_name_list[index] } - - rbd_id = "rbd_" + rbd_name.toLowerCase().replace(/\s+/g, '').replace(/\W/g, "").replace('.', '').replace('-', '').trim() - - tag_key = tag.toLowerCase().trim() - verb = "POST" - if (_.contains(rbd_id_list, rbd_id)) { verb = "PATCH" } - - rules = [] - - _.each(accounts, function(account) { - 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() } - - rules.push({ - condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, - value: { text: value } - }) - } - } - } - }) - - if (rules.length > 0) { - result.push({ - id: rbd_id, - name: rbd_name, - verb: verb, - effective_at: param_effective_date, - rules: rules - }) - } - }) -EOS -end - -datasource "ds_create_rbds" do - iterate $ds_rbds - request do - run_script $js_create_rbds, val(iter_item, "id"), val(iter_item, "verb"), val(iter_item, "name"), val($ds_flexera_api_hosts, "flexera"), rs_org_id - end - result do - encoding "text" - end -end - -script "js_create_rbds", type: "javascript" do - parameters "rbd_id", "verb", "name", "api_host", "rs_org_id" - result "request" - code <<-EOS - var request = { - auth: "auth_flexera", - verb: verb, - host: api_host, - path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id].join(''), - body_fields: { name: name } - } -EOS -end - -datasource "ds_apply_rbds" do - iterate $ds_rbds - request do - # ds_create_rbds is a parameter to ensure that it executes before ds_apply_rbds does - run_script $js_apply_rbds, val(iter_item, "id"), val(iter_item, "effective_at"), val(iter_item, "rules"), val($ds_flexera_api_hosts, "flexera"), $ds_create_rbds, rs_org_id - end - result do - encoding "text" - end -end - -script "js_apply_rbds", type: "javascript" do - parameters "rbd_id", "effective_at", "rules", "api_host", "ds_create_rbds", "rs_org_id" - result "request" - code <<-EOS - var request = { - auth: "auth_flexera", - verb: "PUT", - host: api_host, - path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id, "/rules/", effective_at].join(''), - body_fields: { rules: rules } - } -EOS -end - -############################################################################### -# Policy -############################################################################### - -policy "pol_rbds" do - validate $ds_apply_rbds do - summary_template "RBDs Generated & Applied" - detail_template "" - check eq(0, 0) - end -end diff --git a/automation/azure/azure_missing_subscriptions/CHANGELOG.md b/automation/azure/azure_missing_subscriptions/CHANGELOG.md deleted file mode 100644 index f18747584b..0000000000 --- a/automation/azure/azure_missing_subscriptions/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# Changelog - -## v0.1.1 - -- Minor code improvements to conform with current standards. Functionality unchanged. - -## v0.1 - -- initial release diff --git a/automation/azure/azure_missing_subscriptions/README.md b/automation/azure/azure_missing_subscriptions/README.md deleted file mode 100644 index 716d93e8f3..0000000000 --- a/automation/azure/azure_missing_subscriptions/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Azure Missing Subscriptions - -## What It Does - -This policy checks the stored Flexera CCO billing data for Azure from 3 days ago to obtain a list of Azure Subscriptions that we have billing data for and compares that to the list of Azure Subscriptions returned by the Azure Resource Manager API. An incident is raised and email sent containing any subscriptions present in Flexera CCO but not returned by the Azure Resource Manager API, as well as subscriptions returned by the Azure Resource Manager API but not present in Flexera CCO. The user can select which of those two reports they'd like to produce. - -## Input Parameters - -This policy has the following input parameters required when launching the policy. - -- *Email Addresses* - Email addresses of the recipients you wish to notify when new incidents are created. -- *Azure Endpoint* - The endpoint to send Azure API requests to. Recommended to leave this at default unless using this policy with Azure China. -- *Report Selection* - Whether to report Subscriptions missing in the Azure API but present in CCO data, the opposite, or both. -- *Subscriptions Ignore List* - A list of Subscription IDs/names to never include in the results. Leave blank to not filter results - -## Policy Actions - -The following policy actions are taken on any resources found to be out of compliance. - -- Send an email report - -## 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). - -- [**Azure Resource Manager Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_109256743_1124668) (*provider=azure_rm*) which has the following permissions: - - `Microsoft.Resources/subscriptions/read` - -- [**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 - -- Azure - -## Cost - -This policy template does not incur any cloud costs. diff --git a/automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt b/automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt deleted file mode 100644 index 31fa39c86c..0000000000 --- a/automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt +++ /dev/null @@ -1,340 +0,0 @@ -name "Azure Missing Subscriptions" -rs_pt_ver 20180301 -type "policy" -short_description "Reports any Azure Subscriptions present in Flexera One that are not accessible via the Azure Resource Manager automation credential. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/azure/azure_missing_subscriptions) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." -long_description "" -severity "low" -category "Cost" -default_frequency "weekly" -info( - version: "0.1.1", - provider: "Flexera", - service: "Optima", - policy_set: "Automation", - publish: "false" -) - -############################################################################### -# 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_azure_endpoint" do - type "string" - category "Policy Settings" - label "Azure Endpoint" - description "Select the API endpoint to use for Azure. Use default value of management.azure.com unless using Azure China." - allowed_values "management.azure.com", "management.chinacloudapi.cn" - default "management.azure.com" -end - -parameter "param_report_selection" do - type "string" - category "Policy Settings" - label "Report Selection" - description "Whether to report Subscriptions missing in the Azure API but present in CCO data, the opposite, or both." - allowed_values "Missing in Azure API", "Missing in CCO", "Both" - default "Missing in Azure API" -end - -parameter "param_subscriptions_list" do - type "list" - category "Filters" - label "Subscriptions Ignore List" - description "A list of Subscription IDs/names to never include in the results. Leave blank to not filter results." - default [] -end - -############################################################################### -# Authentication -############################################################################### - -credentials "auth_azure" do - schemes "oauth2" - label "Azure" - description "Select the Azure Resource Manager Credential from the list." - tags "provider=azure_rm" -end - -credentials "auth_flexera" do - schemes "oauth2" - label "Flexera" - description "Select Flexera One OAuth2 credentials" - tags "provider=flexera" -end - -############################################################################### -# Pagination -############################################################################### - -pagination "pagination_azure" do - get_page_marker do - body_path "nextLink" - end - set_page_marker do - uri true - end -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 - -datasource "ds_billing_centers" do - request do - auth $auth_flexera - host rs_optima_host - path join(["/analytics/orgs/", rs_org_id, "/billing_centers"]) - query "view", "allocation_table" - header "Api-Version", "1.0" - header "User-Agent", "RS Policies" - ignore_status [403] - end - result do - encoding "json" - collect jmes_path(response, "[*]") do - field "href", jmes_path(col_item, "href") - field "id", jmes_path(col_item, "id") - field "name", jmes_path(col_item, "name") - field "parent_id", jmes_path(col_item, "parent_id") - end - end -end - -# Gather top level billing center IDs for when we pull cost data -datasource "ds_top_level_bcs" do - run_script $js_top_level_bcs, $ds_billing_centers -end - -script "js_top_level_bcs", type: "javascript" do - parameters "ds_billing_centers" - result "result" - code <<-EOS - filtered_bcs = _.filter(ds_billing_centers, function(bc) { - return bc['parent_id'] == null || bc['parent_id'] == undefined - }) - - result = _.compact(_.pluck(filtered_bcs, 'id')) -EOS -end - -datasource "ds_flexera_cco_data" do - request do - run_script $js_flexera_cco_data, $ds_top_level_bcs, rs_org_id, rs_optima_host - end - result do - encoding "json" - collect jmes_path(response, "rows[*]") do - field "bill_source", jmes_path(col_item, "dimensions.bill_source") - field "vendor_account", jmes_path(col_item, "dimensions.vendor_account") - field "vendor_account_name", jmes_path(col_item, "dimensions.vendor_account_name") - end - end -end - -script "js_flexera_cco_data", type: "javascript" do - parameters "ds_top_level_bcs", "rs_org_id", "rs_optima_host" - result "request" - code <<-EOS - end_date = new Date() - end_date.setDate(end_date.getDate() - 2) - end_date = end_date.toISOString().split('T')[0] - - start_date = new Date() - start_date.setDate(start_date.getDate() - 3) - start_date = start_date.toISOString().split('T')[0] - - var request = { - auth: "auth_flexera", - host: rs_optima_host, - verb: "POST", - path: "/bill-analysis/orgs/" + rs_org_id + "/costs/aggregated", - body_fields: { - dimensions: ["bill_source", "vendor_account", "vendor_account_name"], - granularity: "day", - start_at: start_date, - end_at: end_date, - metrics: ["cost_amortized_unblended_adj"], - billing_center_ids: ds_top_level_bcs, - limit: 100000, - filter: { - "type": "or", - "expressions": [ - { "dimension": "vendor", "type": "equal", "value": "Azure" }, - { "dimension": "vendor", "type": "equal", "value": "azure" }, - { "dimension": "vendor", "type": "equal", "value": "Microsoft Azure" }, - { "dimension": "vendor", "type": "equal", "value": "microsoft azure" } - ] - } - }, - headers: { - 'User-Agent': "RS Policies", - 'Api-Version': "1.0" - }, - ignore_status: [400] - } -EOS -end - -datasource "ds_azure_subscriptions" do - request do - auth $auth_azure - pagination $pagination_azure - host $param_azure_endpoint - path "/subscriptions/" - query "api-version","2020-01-01" - header "User-Agent", "RS Policies" - # Ignore status 400, 403, and 404 which can be returned in certain (legacy) types of Azure Subscriptions - ignore_status [400, 403, 404] - end - result do - encoding "json" - collect jmes_path(response, "value[*]") do - field "id", jmes_path(col_item, "subscriptionId") - field "name", jmes_path(col_item, "displayName") - field "state", jmes_path(col_item, "state") - end - end -end - -datasource "ds_missing_subscriptions_api" do - run_script $js_missing_subscriptions_api, $ds_flexera_cco_data, $ds_azure_subscriptions, $ds_applied_policy, $param_subscriptions_list, $param_report_selection -end - -script "js_missing_subscriptions_api", type: "javascript" do - parameters "ds_flexera_cco_data", "ds_azure_subscriptions", "ds_applied_policy", "param_subscriptions_list", "param_report_selection" - result "result" - code <<-'EOS' - result = [] - - if (param_report_selection != 'Missing in CCO') { - automation_ids = _.map(ds_azure_subscriptions, function(sub) { - return sub['id'].toLowerCase().trim() - }) - - missing_subs = _.reject(ds_flexera_cco_data, function(sub) { - return _.contains(automation_ids, sub['vendor_account'].toLowerCase().trim()) - }) - - result = _.map(missing_subs, function(sub) { - return { - accountID: sub['vendor_account'].toLowerCase().trim(), - accountName: sub['vendor_account_name'], - bill_source: sub['bill_source'], - policy_name: ds_applied_policy['name'] - } - }) - - // Remove filtered results - result = _.reject(result, function(sub) { - return _.contains(param_subscriptions_list, sub['accountID']) || _.contains(param_subscriptions_list, sub['accountName']) - }) - - result = _.sortBy(result, 'accountID') - result = _.sortBy(result, 'bill_source') - } -EOS -end - -datasource "ds_missing_subscriptions_cco" do - run_script $js_missing_subscriptions_cco, $ds_flexera_cco_data, $ds_azure_subscriptions, $ds_applied_policy, $param_subscriptions_list, $param_report_selection -end - -script "js_missing_subscriptions_cco", type: "javascript" do - parameters "ds_flexera_cco_data", "ds_azure_subscriptions", "ds_applied_policy", "param_subscriptions_list", "param_report_selection" - result "result" - code <<-'EOS' - result = [] - - if (param_report_selection != 'Missing in Azure API') { - cco_ids = _.map(ds_flexera_cco_data, function(sub) { - return sub['vendor_account'].toLowerCase().trim() - }) - - missing_subs = _.reject(ds_azure_subscriptions, function(sub) { - return _.contains(cco_ids, sub['id'].toLowerCase().trim()) - }) - - result = _.map(missing_subs, function(sub) { - return { - accountID: sub['id'].toLowerCase().trim(), - accountName: sub['name'], - policy_name: ds_applied_policy['name'] - } - }) - - // Remove filtered results - result = _.reject(result, function(sub) { - return _.contains(param_subscriptions_list, sub['accountID']) || _.contains(param_subscriptions_list, sub['accountName']) - }) - - result = _.sortBy(result, 'accountID') - } -EOS -end - -############################################################################### -# Policy -############################################################################### - -policy "pol_missing_subscriptions" do - validate_each $ds_missing_subscriptions_api do - summary_template "{{ with index data 0 }}{{ .policy_name }}{{ end }}: {{ len data }} Azure API Missing Subscriptions" - check eq(val(item, "accountID"), "") - escalate $esc_email - export do - resource_level false - field "accountID" do - label "Subscription ID" - end - field "accountName" do - label "Subscription Name" - end - field "bill_source" do - label "Bill Connection" - end - end - end - validate_each $ds_missing_subscriptions_cco do - summary_template "{{ with index data 0 }}{{ .policy_name }}{{ end }}: {{ len data }} Azure CCO Missing Subscriptions" - check eq(val(item, "accountID"), "") - escalate $esc_email - export do - resource_level false - field "accountID" do - label "Subscription ID" - end - field "accountName" do - label "Subscription Name" - end - end - end -end - -############################################################################### -# Escalations -############################################################################### - -escalation "esc_email" do - automatic true - label "Send Email" - description "Send incident email" - email $param_email -end diff --git a/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md b/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md deleted file mode 100644 index 9a2c4ba2ad..0000000000 --- a/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -# Changelog - -## v1.3.1 - -- Fixed issue that would sometimes cause execution to fail if an Azure Subscription or Resource Group had no tag keys - -## v1.3.0 - -- Added option to retain original casing of tag values instead of normalizing them all to lowercase - -## v1.2.1 - -- Updated policy template to use newer API endpoints. Functionality is unchanged. - -## v1.2 - -- Fixed error where policy would fail completely when trying to access resources credential does not have access to. Policy will now simply skip these resources. - -## v1.1 - -- added ability to specify names for the newly created dimensions - -## v1.0 - -- initial release diff --git a/automation/azure/azure_rbd_from_rg_tag/README.md b/automation/azure/azure_rbd_from_rg_tag/README.md deleted file mode 100644 index 12e7c4928a..0000000000 --- a/automation/azure/azure_rbd_from_rg_tag/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Azure Rule-Based Dimension From Resource Group Tags - -## What It Does - -This policy creates and updates custom Rule-Based Dimensions that surface the specified Azure Resource Group tag keys in the Flexera One platform. This allows costs to be sliced by the values of the tag keys in question. - -## Input Parameters - -This policy has the following input parameters required when launching the policy. - -- *Effective Date* - The month and year in YYYY-MM format that you want the rules to apply. This should be left at its default value in most cases to ensure that the rules apply to all costs, including historical costs. -- *Tag Keys* - A list of Azure Resource Group tag keys to create custom Rule-Based Dimensions for. -- *Dimension Names* - A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the `Tag Keys` field. Dimension names will be derived from tag keys directly if this list is left empty. -- *Subscription Fallback Rules* - Whether or not to create rules for Subscription tags as a fallback for untagged Resource Groups. These rules have the lowest priority; rules created for Resource Group tags will always take precedence. -- *Lowercase Values* - Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail. - -## Policy Actions - -- Create/update rule-based dimensions - -## 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). - -- [**Azure Resource Manager Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_109256743_1124668) (*provider=azure_rm*) which has the following permissions: - - `Microsoft.Resources/subscriptions/resources/read` - - `Microsoft.Resources/subscriptions/providers/read` - - `Microsoft.Resources/tags/read` - -- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: - - `observer` - - `billing_center_viewer` - - `rule_based_dimensions_manager` - -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 - -- Azure - -## Cost - -This policy template does not incur any cloud costs. 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 deleted file mode 100644 index 6373bd8a39..0000000000 --- a/automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt +++ /dev/null @@ -1,418 +0,0 @@ -name "Azure Rule-Based Dimension From Resource Group Tags" -rs_pt_ver 20180301 -type "policy" -short_description "Creates and/or updates individual Rule-Based Dimensions based on Azure Resource Group tags. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/azure/azure_rbd_from_rg_tag) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." -long_description "" -severity "low" -category "Cost" -default_frequency "daily" -info( - version: "1.3.1", - provider: "Flexera", - service: "Optima", - policy_set: "Automation", - publish: "false" -) - -############################################################################### -# Parameters -############################################################################### - -parameter "param_tag_list" do - type "list" - category "Policy Settings" - label "Tag Keys" - description "A list of Azure account tag keys to build Rule-Based Dimensions from" - # No default value, user input required -end - -parameter "param_name_list" do - type "list" - category "Policy Settings" - label "Dimension Names" - description "A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the 'Tag Keys' field. Dimension names will be derived from tag keys directly if this list is left empty." - default [] -end - -parameter "param_effective_date" do - type "string" - category "Policy Settings" - label "Effective Date" - description "Year/month you want rules to start applying in YYYY-MM format" - default "2010-01" -end - -parameter "param_subscription_fallback" do - type "string" - category "Policy Settings" - label "Subscription Fallback Rules" - description "Whether or not to create rules for Subscription tags as a fallback for untagged Resource Groups." - allowed_values "Yes", "No" - default "No" -end - -parameter "param_normalize_case" do - type "string" - category "Policy Settings" - label "Lowercase Values" - description "Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail." - allowed_values "Yes", "No" - default "Yes" -end - -############################################################################### -# Authentication -############################################################################### - -credentials "auth_azure" do - schemes "oauth2" - label "Azure" - description "Select the Azure Resource Manager Credential from the list." - tags "provider=azure_rm" -end - -credentials "auth_flexera" do - schemes "oauth2" - label "flexera" - description "Select FlexeraOne OAuth2 credential." - tags "provider=flexera" -end - -############################################################################### -# Pagination -############################################################################### - -pagination "pagination_azure" do - get_page_marker do - body_path "nextLink" - end - set_page_marker do - uri true - end -end - -############################################################################### -# Datasources & Scripts -############################################################################### - -# 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 - -datasource "ds_existing_rbds" do - request do - auth $auth_flexera - host rs_optima_host - path join(["/bill-analysis/orgs/", rs_org_id, "/settings/rule_based_dimensions"]) - header "Api-Version", "1.0" - header "content-type", "application/json" - end - result do - encoding "json" - collect jmes_path(response, "rule_based_dimensions") do - field "id", jmes_path(col_item, "id") - field "name", jmes_path(col_item, "name") - field "dated_rules", jmes_path(col_item, "dated_rules") - end - end -end - -datasource "ds_azure_subscriptions" do - request do - auth $auth_azure - pagination $pagination_azure - host "management.azure.com" - path "/subscriptions/" - query "api-version", "2018-06-01" - header "User-Agent", "RS Policies" - # Ignore status 400, 403, and 404 which can be returned in certain (legacy) types of Azure Subscriptions - ignore_status [400, 403, 404] - end - result do - encoding "json" - collect jmes_path(response, "value[*]") do - field "id", jmes_path(col_item, "subscriptionId") - field "name", jmes_path(col_item, "displayName") - end - end -end - -datasource "ds_azure_subscriptions_with_tags" do - iterate $ds_azure_subscriptions - request do - auth $auth_azure - pagination $pagination_azure - host "management.azure.com" - path join(["/subscriptions/", val(iter_item, "id"), "/providers/Microsoft.Resources/tags/default"]) - query "api-version", "2021-04-01" - header "User-Agent", "RS Policies" - ignore_status [400, 403, 404] - end - result do - encoding "json" - field "id", val(iter_item, "id") - field "name", val(iter_item, "name") - field "tags", jmes_path(response, "properties.tags") - end -end - -datasource "ds_azure_subscriptions_normalized" do - run_script $js_azure_subscriptions_normalized, $ds_azure_subscriptions_with_tags -end - -script "js_azure_subscriptions_normalized", type: "javascript" do - parameters "accounts" - result "result" - code <<-'EOS' - result = [] - - _.each(accounts, function(account) { - tags = {} - - if (account['tags'] != null && account['tags'] != undefined) { - _.each(Object.keys(account['tags']), function(key) { - normalized_key = key.toLowerCase().trim() - tags[normalized_key] = account['tags'][key] - }) - } - - result.push({ - id: account['id'], - name: account['name'], - tags: tags - }) - }) -EOS -end - -datasource "ds_azure_resource_groups" do - iterate $ds_azure_subscriptions - request do - auth $auth_azure - pagination $pagination_azure - host "management.azure.com" - path join(["/subscriptions/", val(iter_item, "id"), "/resourcegroups"]) - query "api-version", "2021-04-01" - header "User-Agent", "RS Policies" - ignore_status [400, 403, 404] - end - result do - encoding "json" - collect jmes_path(response, "value[*]") do - field "subscriptionId", val(iter_item, "id") - field "subscriptionName", val(iter_item, "name") - field "id", jmes_path(col_item, "id") - field "name", jmes_path(col_item, "name") - field "tags", jmes_path(col_item, "tags") - end - end -end - -datasource "ds_azure_resource_groups_normalized" do - run_script $js_azure_resource_groups_normalized, $ds_azure_resource_groups -end - -script "js_azure_resource_groups_normalized", type: "javascript" do - parameters "rgs" - result "result" - code <<-'EOS' - result = [] - - _.each(rgs, function(rg) { - tags = {} - - if (rg['tags'] != null && rg['tags'] != undefined) { - _.each(Object.keys(rg['tags']), function(key) { - normalized_key = key.toLowerCase().trim() - tags[normalized_key] = rg['tags'][key] - }) - } - - result.push({ - subscriptionId: rg['subscriptionId'], - subscriptionName: rg['subscriptionName'], - id: rg['id'], - name: rg['name'], - tags: tags - }) - }) -EOS -end - -datasource "ds_rbds" do - run_script $js_rbds, $ds_azure_resource_groups_normalized, $ds_azure_subscriptions_normalized, $ds_existing_rbds, $param_name_list, $param_tag_list, $param_effective_date, $param_subscription_fallback, $param_normalize_case -end - -script "js_rbds", type: "javascript" do - parameters "rgs", "accounts", "existing_rbds", "param_name_list", "param_tag_list", "param_effective_date", "param_subscription_fallback", "param_normalize_case" - result "result" - code <<-'EOS' - result = [] - - rbd_id_list = _.pluck(existing_rbds, 'id') - - _.each(param_tag_list, function(tag, index) { - rbd_name = tag.replace('.', ' ').replace('-', ' ') - rbd_name = rbd_name.replace(/\W/g, " ").trim() - - // Use user-specified name instead of user provided one - if (param_tag_list.length == param_name_list.length) { rbd_name = param_name_list[index] } - - rbd_id = "rbd_" + rbd_name.toLowerCase().replace(/\s+/g, '').replace(/\W/g, "").replace('.', '').replace('-', '').trim() - - tag_key = tag.toLowerCase().trim() - verb = "POST" - if (_.contains(rbd_id_list, rbd_id)) { verb = "PATCH" } - - 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() } - - 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().toLowerCase() }, - { type: "dimension_equals", dimension: "vendor_account", value: rg['subscriptionId'].trim().toLowerCase() } - ] - }, - value: { text: value } - }) - } - } - } - } - }) - - 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 (rules.length > 0) { - result.push({ - id: rbd_id, - name: rbd_name, - verb: verb, - effective_at: param_effective_date, - rules: rules - }) - } - }) -EOS -end - -datasource "ds_create_rbds" do - iterate $ds_rbds - request do - run_script $js_create_rbds, val(iter_item, "id"), val(iter_item, "verb"), val(iter_item, "name"), val($ds_flexera_api_hosts, "flexera"), rs_org_id - end - result do - encoding "text" - end -end - -script "js_create_rbds", type: "javascript" do - parameters "rbd_id", "verb", "name", "api_host", "rs_org_id" - result "request" - code <<-EOS - var request = { - auth: "auth_flexera", - verb: verb, - host: api_host, - path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id].join(''), - body_fields: { name: name } - } -EOS -end - -datasource "ds_apply_rbds" do - iterate $ds_rbds - request do - # ds_create_rbds is a parameter to ensure that it executes before ds_apply_rbds does - run_script $js_apply_rbds, val(iter_item, "id"), val(iter_item, "effective_at"), val(iter_item, "rules"), val($ds_flexera_api_hosts, "flexera"), $ds_create_rbds, rs_org_id - end - result do - encoding "text" - end -end - -script "js_apply_rbds", type: "javascript" do - parameters "rbd_id", "effective_at", "rules", "api_host", "ds_create_rbds", "rs_org_id" - result "request" - code <<-EOS - var request = { - auth: "auth_flexera", - verb: "PUT", - host: api_host, - path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id, "/rules/", effective_at].join(''), - body_fields: { rules: rules } - } -EOS -end - -############################################################################### -# Policy -############################################################################### - -policy "pol_rbds" do - validate $ds_apply_rbds do - summary_template "RBDs Generated & Applied" - detail_template "" - check eq(0, 0) - end -end diff --git a/automation/azure/azure_rbd_from_tag/CHANGELOG.md b/automation/azure/azure_rbd_from_tag/CHANGELOG.md deleted file mode 100644 index b2354a4856..0000000000 --- a/automation/azure/azure_rbd_from_tag/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -# Changelog - -## v1.3.1 - -- Fixed issue that would sometimes cause execution to fail if an Azure Subscription had no tag keys - -## v1.3.0 - -- Added option to retain original casing of tag values instead of normalizing them all to lowercase - -## v1.2.1 - -- Updated policy template to use newer API endpoints. Functionality is unchanged. - -## v1.2 - -- Fixed error where policy would fail completely when trying to access resources credential does not have access to. Policy will now simply skip these resources. - -## v1.1 - -- added ability to specify names for the newly created dimensions - -## v1.0 - -- initial release diff --git a/automation/azure/azure_rbd_from_tag/README.md b/automation/azure/azure_rbd_from_tag/README.md deleted file mode 100644 index 10d38b2d6d..0000000000 --- a/automation/azure/azure_rbd_from_tag/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Azure Rule-Based Dimension From Subscription Tags - -## What It Does - -This policy creates and updates custom Rule-Based Dimensions that surface the specified Azure Subscription tag keys in the Flexera One platform. This allows costs to be sliced by the values of the tag keys in question. - -## Input Parameters - -This policy has the following input parameters required when launching the policy. - -- *Effective Date* - The month and year in YYYY-MM format that you want the rules to apply. This should be left at its default value in most cases to ensure that the rules apply to all costs, including historical costs. -- *Tag Keys* - A list of Azure Subscription tag keys to create custom Rule-Based Dimensions for. -- *Dimension Names* - A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the `Tag Keys` field. Dimension names will be derived from tag keys directly if this list is left empty. -- *Lowercase Values* - Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail. - -## Policy Actions - -- Create/update rule-based dimensions - -## 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). - -- [**Azure Resource Manager Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_109256743_1124668) (*provider=azure_rm*) which has the following permissions: - - `Microsoft.Resources/subscriptions/resources/read` - - `Microsoft.Resources/subscriptions/providers/read` - - `Microsoft.Resources/tags/read` - -- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: - - `observer` - - `billing_center_viewer` - - `rule_based_dimensions_manager` - -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 - -- Azure - -## Cost - -This policy template does not incur any cloud costs. 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 deleted file mode 100644 index 467c1700d0..0000000000 --- a/automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt +++ /dev/null @@ -1,318 +0,0 @@ -name "Azure Rule-Based Dimension From Subscription Tags" -rs_pt_ver 20180301 -type "policy" -short_description "Creates and/or updates individual Rule-Based Dimensions based on Azure Subscription tags. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/azure/azure_rbd_from_tag) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." -long_description "" -severity "low" -category "Cost" -default_frequency "daily" -info( - version: "1.3.1", - provider: "Flexera", - service: "Optima", - policy_set: "Automation", - publish: "false" -) - -############################################################################### -# Parameters -############################################################################### - -parameter "param_tag_list" do - type "list" - category "Policy Settings" - label "Tag Keys" - description "A list of Azure account tag keys to build Rule-Based Dimensions from" - # No default value, user input required -end - -parameter "param_name_list" do - type "list" - category "Policy Settings" - label "Dimension Names" - description "A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the 'Tag Keys' field. Dimension names will be derived from tag keys directly if this list is left empty." - default [] -end - -parameter "param_effective_date" do - type "string" - category "Policy Settings" - label "Effective Date" - description "Year/month you want rules to start applying in YYYY-MM format" - default "2010-01" -end - -parameter "param_normalize_case" do - type "string" - category "Policy Settings" - label "Lowercase Values" - description "Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail." - allowed_values "Yes", "No" - default "Yes" -end - -############################################################################### -# Authentication -############################################################################### - -credentials "auth_azure" do - schemes "oauth2" - label "Azure" - description "Select the Azure Resource Manager Credential from the list." - tags "provider=azure_rm" -end - -credentials "auth_flexera" do - schemes "oauth2" - label "flexera" - description "Select FlexeraOne OAuth2 credential." - tags "provider=flexera" -end - -############################################################################### -# Pagination -############################################################################### - -pagination "pagination_azure" do - get_page_marker do - body_path "nextLink" - end - set_page_marker do - uri true - end -end - -############################################################################### -# Datasources & Scripts -############################################################################### - -# 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 - -datasource "ds_existing_rbds" do - request do - auth $auth_flexera - host rs_optima_host - path join(["/bill-analysis/orgs/", rs_org_id, "/settings/rule_based_dimensions"]) - header "Api-Version", "1.0" - header "content-type", "application/json" - end - result do - encoding "json" - collect jmes_path(response, "rule_based_dimensions") do - field "id", jmes_path(col_item, "id") - field "name", jmes_path(col_item, "name") - field "dated_rules", jmes_path(col_item, "dated_rules") - end - end -end - -datasource "ds_azure_subscriptions_without_tags" do - request do - auth $auth_azure - pagination $pagination_azure - host "management.azure.com" - path "/subscriptions/" - query "api-version", "2018-06-01" - header "User-Agent", "RS Policies" - # Ignore status 400, 403, and 404 which can be returned in certain (legacy) types of Azure Subscriptions - ignore_status [400, 403, 404] - end - result do - encoding "json" - collect jmes_path(response, "value[*]") do - field "id", jmes_path(col_item, "subscriptionId") - field "name", jmes_path(col_item, "displayName") - end - end -end - -datasource "ds_azure_subscriptions" do - iterate $ds_azure_subscriptions_without_tags - request do - auth $auth_azure - pagination $pagination_azure - host "management.azure.com" - path join(["/subscriptions/", val(iter_item, "id"), "/providers/Microsoft.Resources/tags/default"]) - query "api-version", "2021-04-01" - header "User-Agent", "RS Policies" - ignore_status [400, 403, 404] - end - result do - encoding "json" - field "id", val(iter_item, "id") - field "name", val(iter_item, "name") - field "tags", jmes_path(response, "properties.tags") - end -end - -datasource "ds_azure_subscriptions_normalized" do - run_script $js_azure_subscriptions_normalized, $ds_azure_subscriptions -end - -script "js_azure_subscriptions_normalized", type: "javascript" do - parameters "accounts" - result "result" - code <<-'EOS' - result = [] - - _.each(accounts, function(account) { - tags = {} - - if (account['tags'] != null && account['tags'] != undefined) { - _.each(Object.keys(account['tags']), function(key) { - normalized_key = key.toLowerCase().trim() - tags[normalized_key] = account['tags'][key] - }) - } - - result.push({ - id: account['id'], - name: account['name'], - tags: tags - }) - }) -EOS -end - -datasource "ds_rbds" do - run_script $js_rbds, $ds_azure_subscriptions_normalized, $ds_existing_rbds, $param_name_list, $param_tag_list, $param_effective_date, $param_normalize_case -end - -script "js_rbds", type: "javascript" do - parameters "accounts", "existing_rbds", "param_name_list", "param_tag_list", "param_effective_date", "param_normalize_case" - result "result" - code <<-'EOS' - result = [] - - rbd_id_list = _.pluck(existing_rbds, 'id') - - _.each(param_tag_list, function(tag, index) { - rbd_name = tag.replace('.', ' ').replace('-', ' ') - rbd_name = rbd_name.replace(/\W/g, " ").trim() - - // Use user-specified name instead of user provided one - if (param_tag_list.length == param_name_list.length) { rbd_name = param_name_list[index] } - - rbd_id = "rbd_" + rbd_name.toLowerCase().replace(/\s+/g, '').replace(/\W/g, "").replace('.', '').replace('-', '').trim() - - tag_key = tag.toLowerCase().trim() - verb = "POST" - if (_.contains(rbd_id_list, rbd_id)) { verb = "PATCH" } - - 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 (rules.length > 0) { - result.push({ - id: rbd_id, - name: rbd_name, - verb: verb, - effective_at: param_effective_date, - rules: rules - }) - } - }) -EOS -end - -datasource "ds_create_rbds" do - iterate $ds_rbds - request do - run_script $js_create_rbds, val(iter_item, "id"), val(iter_item, "verb"), val(iter_item, "name"), val($ds_flexera_api_hosts, "flexera"), rs_org_id - end - result do - encoding "text" - end -end - -script "js_create_rbds", type: "javascript" do - parameters "rbd_id", "verb", "name", "api_host", "rs_org_id" - result "request" - code <<-EOS - var request = { - auth: "auth_flexera", - verb: verb, - host: api_host, - path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id].join(''), - body_fields: { name: name } - } -EOS -end - -datasource "ds_apply_rbds" do - iterate $ds_rbds - request do - # ds_create_rbds is a parameter to ensure that it executes before ds_apply_rbds does - run_script $js_apply_rbds, val(iter_item, "id"), val(iter_item, "effective_at"), val(iter_item, "rules"), val($ds_flexera_api_hosts, "flexera"), $ds_create_rbds, rs_org_id - end - result do - encoding "text" - end -end - -script "js_apply_rbds", type: "javascript" do - parameters "rbd_id", "effective_at", "rules", "api_host", "ds_create_rbds", "rs_org_id" - result "request" - code <<-EOS - var request = { - auth: "auth_flexera", - verb: "PUT", - host: api_host, - path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id, "/rules/", effective_at].join(''), - body_fields: { rules: rules } - } -EOS -end - -############################################################################### -# Policy -############################################################################### - -policy "pol_rbds" do - validate $ds_apply_rbds do - summary_template "RBDs Generated & Applied" - detail_template "" - check eq(0, 0) - end -end diff --git a/automation/flexera/delete_all_billing_centers/CHANGELOG.md b/automation/flexera/delete_all_billing_centers/CHANGELOG.md deleted file mode 100644 index 6b508a1e70..0000000000 --- a/automation/flexera/delete_all_billing_centers/CHANGELOG.md +++ /dev/null @@ -1,13 +0,0 @@ -# Changelog - -## v1.1.1 - -- Minor code improvements to conform with current standards. Functionality unchanged. - -## v1.1 - -- Updated policy metadata to make it more clear what Flexera service the policy is for - -## v1.0 - -- initial release diff --git a/automation/flexera/delete_all_billing_centers/README.md b/automation/flexera/delete_all_billing_centers/README.md deleted file mode 100644 index 878c9a5917..0000000000 --- a/automation/flexera/delete_all_billing_centers/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Flexera CCO Delete All Billing Centers - -## What It Does - -This policy deletes all Billing Centers in the Flexera organization it is executed within. The policy will automatically self-terminate the second time it runs to avoid accidental future deletion of Billing Centers. - -## How It Works - -- During execution, the policy determines if this is the second time it has run since it was applied by comparing the applied policy's `created_at` date/time with the current date/time. If the difference between the two is greater than 1 minute, it is assumed the policy is running a second time. -- If this is the policy's second time running, the policy self-terminates before completing execution as a failsafe in case the user has forgotten to terminate the policy after usage. -- An incident is always raised, with the `Self Terminate` field in the incident table being true if this is the policy's second time executing since it was applied. -- Cloud Workflow is automatically kicked off. - - If this is the policy's second time executing, but for some reason the policy has failed to self-terminate, no action will be taken. - - If this is the policy's first time executing, all of the Billing Centers in the Flexera organization are deleted. - -## Input Parameters - -This policy template has no input parameters. - -## Policy Actions - -- Delete all Billing Centers - -## 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). - -### Credential configuration - -- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: - - `billing_center_admin` - - `policy_manager` - -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 - -- Flexera - -## Cost - -This policy template does not incur any cloud costs. diff --git a/automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt b/automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt deleted file mode 100644 index 2eb4046e2c..0000000000 --- a/automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt +++ /dev/null @@ -1,233 +0,0 @@ -name "Flexera CCO Delete All Billing Centers" -rs_pt_ver 20180301 -type "policy" -short_description "Deletes all Billing Centers in the Flexera organization. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/flexera/delete_all_billing_centers) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." -long_description "" -severity "high" -category "Cost" -default_frequency "15 minutes" -info( - version: "1.1.1", - provider: "Flexera", - service: "Cloud Cost Optimization", - policy_set: "Automation", - publish: "false" -) - -############################################################################### -# Parameters -############################################################################### - -############################################################################### -# Authentication -############################################################################### - -credentials "auth_flexera" do - schemes "oauth2" - label "Flexera" - description "Select FlexeraOne OAuth2 credential." - tags "provider=flexera" -end - -############################################################################### -# Datasources & Scripts -############################################################################### - -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 - -datasource "ds_second_execution" do - run_script $js_second_execution, $ds_applied_policy -end - -script "js_second_execution", type: "javascript" do - parameters "ds_applied_policy" - result "result" - code <<-'EOS' - result = [{ id: ds_applied_policy['href'], terminate: false }] - - now = new Date() - created_at = new Date(ds_applied_policy['created_at']) - - // If applied policy was created over 1 minute ago, assume this is the second time it is running - if (now - created_at > 60000) { result[0]['terminate'] = true } -EOS -end - -datasource "ds_self_terminate_boolean" do - run_script $js_self_terminate_boolean, $ds_second_execution -end - -script "js_self_terminate_boolean", type: "javascript" do - parameters "ds_second_execution" - result "result" - code <<-'EOS' - result = [] - - if (ds_second_execution[0]['terminate']) { result = ds_second_execution } -EOS -end - -datasource "ds_self_terminate" do - iterate $ds_self_terminate_boolean - request do - auth $auth_flexera - verb "DELETE" - host rs_governance_host - path val(iter_item, 'id') - header "Api-Version", "1.0" - end -end - -datasource "ds_incident" do - run_script $js_incident, $ds_second_execution, $ds_self_terminate -end - -script "js_incident", type: "javascript" do - parameters "ds_second_execution", "ds_self_terminate" - result "result" - code <<-'EOS' - result = ds_second_execution -EOS -end - -############################################################################### -# Policy -############################################################################### - -policy "pol_delete_bcs" do - validate_each $ds_incident do - summary_template "Deleting Billing Centers" - check eq(0, 1) - escalate $esc_delete_billing_centers - export do - resource_level true - field "id" do - label "Policy HREF" - end - field "terminate" do - label "Self Terminate" - end - end - end -end - -############################################################################### -# Escalations -############################################################################### - -escalation "esc_delete_billing_centers" do - automatic true - label "Delete Billing Centers" - description "Approval to delete all billing centers" - run "delete_billing_centers", data, rs_org_id, rs_optima_host -end - -############################################################################### -# Cloud Workflow -############################################################################### - -define delete_billing_centers($data, $rs_org_id, $rs_optima_host) return $all_responses do - $$all_responses = [] - - # Policy will only run if it isn't self-terminating - if $data[0]["terminate"] == false - call list_childless_billing_centers($rs_org_id, $rs_optima_host) retrieve $billing_centers - - while size($billing_centers) > 0 do - foreach $billing_center in $billing_centers do - sub on_error: handle_error() do - call delete_billing_center($billing_center, $rs_org_id, $rs_optima_host) retrieve $delete_response - end - end - - sleep(10) - - call list_childless_billing_centers($rs_org_id, $rs_optima_host) retrieve $billing_centers - end - end -end - -define list_childless_billing_centers($rs_org_id, $rs_optima_host) return $billing_centers do - $billing_centers = [] - - task_label("Listing All Billing Centers") - - $response = http_request( - auth: $$auth_flexera, - https: true, - verb: "get", - href: "/analytics/orgs/" + $rs_org_id + "/billing_centers", - host: $rs_optima_host, - headers: { - "Api-Version": "1.0", - "User-Agent": "RS Policies" - } - ) - - task_label("Listing All Billing Centers response: " + to_json($response)) - $$all_responses << to_json({"req": "GET /billing_centers", "resp": $response}) - - if $response["code"] != 204 && $response["code"] != 202 && $response["code"] != 200 - raise "Unexpected response Listing All Billing Centers: " + to_json($response) - else - task_label("Listing All Billing Centers successful") - - $parent_ids = [] - - foreach $item in $response["body"] do - if $item["name"] != "Unallocated" && $item["parent_id"] != null && $item["parent_id"] != "" - $parent_ids << $item["parent_id"] - end - end - - foreach $item in $response["body"] do - if $item["name"] != "Unallocated" && contains?($parent_ids, [ $item["id"] ]) == false - $billing_centers << $item - end - end - end -end - -define delete_billing_center($billing_center, $rs_org_id, $rs_optima_host) return $response do - $bc_label = $billing_center["name"] + " (" + $billing_center["id"] + ")" - - task_label("Delete Billing Center: " + $bc_label) - - $response = http_request( - auth: $$auth_flexera, - https: true, - verb: "delete", - href: "/analytics/orgs/" + $rs_org_id + "/billing_centers/" + $billing_center["id"], - host: $rs_optima_host, - headers: { - "Api-Version": "1.0", - "User-Agent": "RS Policies" - } - ) - - task_label("Delete Billing Center response: " + $bc_label + " " + to_json($response)) - $$all_responses << to_json({"req": "DELETE /billing_centers/" + $billing_center["id"], "resp": $response}) - - if $response["code"] != 204 && $response["code"] != 202 && $response["code"] != 200 - raise "Unexpected response Deleting Billing Centers: " + $bc_label + " " + to_json($response) - else - task_label("Delete Billing Center successful: " + $bc_label) - 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 diff --git a/automation/flexera/outdated_applied_policies/CHANGELOG.md b/automation/flexera/outdated_applied_policies/CHANGELOG.md deleted file mode 100644 index 85a84d011f..0000000000 --- a/automation/flexera/outdated_applied_policies/CHANGELOG.md +++ /dev/null @@ -1,14 +0,0 @@ -# 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 - -## v0.1 - -- initial release diff --git a/automation/flexera/outdated_applied_policies/README.md b/automation/flexera/outdated_applied_policies/README.md deleted file mode 100644 index 91be793e28..0000000000 --- a/automation/flexera/outdated_applied_policies/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Flexera Automation Outdated Applied Policies - -## What It Does - -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: - -- This policy itself. -- Policies applied from a source other than the Flexera Automation Catalog. -- Organization-specific policies published to that organization's own catalog. -- Flexera policies present in the [policy-templates Github Repository](https://github.com/flexera-public/policy_templates) but not published in the Flexera Automation Catalog, such as meta policies and other misc. utility policies. -- Policy aggregates applied across multiple projects. Aggregates applied only to the project this policy is applied in will still be included in the results and are actionable. - -## How It Works - -The list of outdated 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 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, 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 - -- *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). - -### Credential Configuration - -- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: - - `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. - -## Supported Clouds - -- Flexera - -## Cost - -This policy template does not incur any cloud costs. Cloud costs may be incurred by the applied policies that this policy reports on and updates. Please consult the README of each policy for more information. diff --git a/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt b/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt deleted file mode 100644 index 367dee8444..0000000000 --- a/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt +++ /dev/null @@ -1,835 +0,0 @@ -name "Flexera Automation Outdated Applied Policies" -rs_pt_ver 20180301 -type "policy" -short_description "Reports any applied policies in Flexera Automation that are not using the latest version of that policy from the catalog and, optionally, updates them. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/flexera/outdated_applied_policies) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." -long_description "" -severity "low" -category "Operational" -default_frequency "weekly" -info( - version: "0.2.0", - provider: "Flexera", - service: "Automation", - policy_set: "Automation" -) - -############################################################################### -# 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_ignore_list" do - type "list" - category "Filters" - label "Policy Ignore List" - description "A list of applied policy names and/or IDs to ignore and not report on. Leave blank to assess all applied policies." - 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)" - allowed_values ["Update Applied Policies"] - default [] -end - -############################################################################### -# Authentication -############################################################################### - -credentials "auth_flexera" do - schemes "oauth2" - label "Flexera" - description "Select Flexera One OAuth2 credentials" - tags "provider=flexera" -end - -############################################################################### -# Datasources & Scripts -############################################################################### - -datasource "ds_self_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 - -datasource "ds_applied_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" - end - result do - encoding "json" - collect jmes_path(response, "items[*]") do - field "id", jmes_path(col_item, "id") - field "href", jmes_path(col_item, "href") - field "name", jmes_path(col_item, "name") - field "description", jmes_path(col_item, "description") - field "category", jmes_path(col_item, "category") - field "created_at", jmes_path(col_item, "created_at") - field "created_by", jmes_path(col_item, "created_by.email") - field "frequency", jmes_path(col_item, "frequency") - field "category", jmes_path(col_item, "category") - field "credentials", jmes_path(col_item, "credentials") - field "options", jmes_path(col_item, "options") - field "severity", jmes_path(col_item, "severity") - field "skip_approvals", jmes_path(col_item, "skip_approvals") - field "scope", jmes_path(col_item, "scope") - field "dry_run", jmes_path(col_item, "dry_run") - field "log_level", jmes_path(col_item, "log_level") - field "version", jmes_path(col_item, "info.version") - field "policy_template", jmes_path(col_item, "policy_template") - field "published_template", jmes_path(col_item, "published_template") - end - end -end - -datasource "ds_policy_aggregates" do - request do - auth $auth_flexera - host rs_governance_host - path join(["/api/governance/orgs/", rs_org_id, "/policy_aggregates"]) - header "Api-Version", "1.0" - end - result do - encoding "json" - collect jmes_path(response, "items[*]") do - field "id", jmes_path(col_item, "id") - field "href", jmes_path(col_item, "href") - field "name", jmes_path(col_item, "name") - field "description", jmes_path(col_item, "description") - field "category", jmes_path(col_item, "category") - field "created_at", jmes_path(col_item, "created_at") - field "created_by", jmes_path(col_item, "created_by.email") - field "frequency", jmes_path(col_item, "frequency") - field "category", jmes_path(col_item, "category") - field "credentials", jmes_path(col_item, "credentials") - field "options", jmes_path(col_item, "options") - field "severity", jmes_path(col_item, "severity") - field "skip_approvals", jmes_path(col_item, "skip_approvals") - field "dry_run", jmes_path(col_item, "dry_run") - field "running_project_ids", jmes_path(col_item, "running_project_ids") - field "published_template", jmes_path(col_item, "published_template") - end - end -end - -datasource "ds_catalog_policies" do - request do - verb "GET" - host "raw.githubusercontent.com" - path "/flexera-public/policy_templates/master/data/active_policy_list/active_policy_list.json" - header "User-Agent", "RS Policies" - end - result do - encoding "json" - collect jmes_path(response, "policies[*]") do - field "name", jmes_path(col_item, "name") - field "file_name", jmes_path(col_item, "file_name") - field "version", jmes_path(col_item, "version") - field "change_log", jmes_path(col_item, "change_log") - field "description", jmes_path(col_item, "description") - field "category", jmes_path(col_item, "category") - field "severity", jmes_path(col_item, "severity") - field "readme", jmes_path(col_item, "readme") - 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, $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", "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 outdated policies - if (!reject_policy && param_report_filter == "Report Deprecated Only") { - reject_policy = true - } - - return reject_policy - }) - - combined_data = _.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'] - - rec_verb = "Update" - - if (policy['version'].split('.')[0] != catalog_policy['version'].split('.')[0]) { - rec_verb = "Manually update" - } - - recommendationDetails = [ - rec_verb, " applied policy ", policy['name'], " (", policy['id'], ") ", - "from version ", policy['version'], " to version ", catalog_policy['version'] - ].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: '' - } - }) - - result = _.filter(combined_data, function(policy) { - return policy['version'] != policy['catalog_version'] && typeof(policy['catalog_version']) == 'string' && policy['href'] != null - }) - - if (result.length > 0) { - total_applied_policies = ds_applied_policies.length.toString() - total_outdated = result.length.toString() - outdated_percentage = (total_outdated / total_applied_policies * 100).toFixed(2).toString() + '%' - - pol_noun = "policies" - if (total_applied_policies == 1) { pol_noun = "policy" } - - pol_verb = "are outdated" - if (total_outdated == 1) { pol_verb = "is outdated" } - - pol_action = "recommended to be replaced with the latest version from the Catalog" - - message = [ - "Out of ", total_applied_policies, " ", pol_noun, " analyzed, ", - total_outdated, " (", outdated_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 - -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 -############################################################################### - -policy "pol_outdated_policies" do - validate_each $ds_outdated_policies do - summary_template "{{ with index data 0 }}{{ .self_policy_name }}{{ end }}: {{ len data }} Outdated Policies Found" - detail_template "{{ with index data 0 }}{{ .message }}{{ end }}" - check eq(val(item, "id"), "") - escalate $esc_email - escalate $esc_update_policies - 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 - 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 - 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 -end - -############################################################################### -# Escalations -############################################################################### - -escalation "esc_email" do - automatic true - label "Send Email" - description "Send incident email" - email $param_email -end - -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, $param_allow_major, rs_governance_host, rs_project_id -end - -############################################################################### -# Cloud Workflow -############################################################################### - -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] || $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 - call delete_policy($policy, $rs_governance_host) retrieve $delete_response - end - else - $policy_name = $policy["name_without_link"] + " (" + $policy["id"] + ")" - raise "Applied Policy " + $policy_name + " was not updated due to a major version change. Please update manually." - end - end - end - - if inspect($$errors) != "null" - raise join($$errors, "\n") - end -end - -define apply_policy($policy, $rs_governance_host, $rs_project_id) return $response, $code do - $host = $rs_governance_host - $href = "/api/governance/projects/" + $rs_project_id + "/applied_policies" - $url = $host + $href - task_label("POST " + $url) - - $response = http_request( - auth: $$auth_flexera, - host: $host, - href: $href, - https: true, - verb: "post", - headers: { "Api-Version": "1.0" }, - body: $policy["update_body"] - ) - - $code = $response["code"] - $policy_name = $policy["catalog_name"] + " (" + $policy["catalog_id"] + ")" - - task_label("Apply Catalog Policy response: " + $policy_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 applying Catalog Policy: " + $policy_name + " " + to_json($response) - else - task_label("Apply Catalog Policy successful: " + $policy_name) - end -end - -define delete_policy($policy, $rs_governance_host) return $response do - $host = $rs_governance_host - $href = $policy['href'] - $url = $host + $href - task_label("DELETE " + $url) - - $response = http_request( - auth: $$auth_flexera, - host: $host, - href: $href, - https: true, - verb: "delete", - headers: { "Api-Version": "1.0" } - ) - - $policy_name = $policy["name_without_link"] + " (" + $policy["id"] + ")" - - task_label("Delete Applied Policy response: " + $policy_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 deleting Applied Policy: " + $policy_name + " " + to_json($response) - else - task_label("Delete Applied Policy successful: " + $policy_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 diff --git a/automation/google/google_rbd_from_label/CHANGELOG.md b/automation/google/google_rbd_from_label/CHANGELOG.md deleted file mode 100644 index 26beb076b9..0000000000 --- a/automation/google/google_rbd_from_label/CHANGELOG.md +++ /dev/null @@ -1,26 +0,0 @@ -# Changelog - -## v1.3.1 - -- Fixed issue that would sometimes cause execution to fail if a Google Project had no label keys - -## v1.3.0 - -- Added option to retain original casing of tag values instead of normalizing them all to lowercase - -## v1.2.1 - -- Updated policy template to use newer API endpoints. Functionality is unchanged. - -## v1.2 - -- fixed link to README in policy description - -## v1.1 - -- added ability to specify names for the newly created dimensions -- fixed issue where labels were incorrectly referred to as tags in parameter name - -## v1.0 - -- initial release diff --git a/automation/google/google_rbd_from_label/README.md b/automation/google/google_rbd_from_label/README.md deleted file mode 100644 index 0f4903041c..0000000000 --- a/automation/google/google_rbd_from_label/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Google Rule-Based Dimension From Project Labels - -## What It Does - -This policy creates and updates custom Rule-Based Dimensions that surface the specified Google Project label keys in the Flexera One platform. This allows costs to be sliced by the values of the label keys in question. - -## Input Parameters - -This policy has the following input parameters required when launching the policy. - -- *Effective Date* - The month and year in YYYY-MM format that you want the rules to apply. This should be left at its default value in most cases to ensure that the rules apply to all costs, including historical costs. -- *Label Keys* - A list of Google Project label keys to create custom Rule-Based Dimensions for. -- *Dimension Names* - A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the label keys in the `Label Keys` field. Dimension names will be derived from label keys directly if this list is left empty. -- *Lowercase Values* - Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail. - -## Policy Actions - -- Create/update rule-based dimensions - -## 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). - -- [**Google Cloud Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_4083446696_1121577) (*provider=gce*) which has the following: - - `resourcemanager.projects.get` - -- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: - - `observer` - - `billing_center_viewer` - - `rule_based_dimensions_manager` - -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 - -- Google - -## Cost - -This policy template does not incur any cloud costs. 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 deleted file mode 100644 index 93be95a9b0..0000000000 --- a/automation/google/google_rbd_from_label/google_rbd_from_label.pt +++ /dev/null @@ -1,305 +0,0 @@ -name "Google Rule-Based Dimension From Project Labels" -rs_pt_ver 20180301 -type "policy" -short_description "Creates and/or updates individual Rule-Based Dimensions based on Google Project labels. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/google/google_rbd_from_label) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." -long_description "" -severity "low" -category "Cost" -default_frequency "daily" -info( - version: "1.3.1", - provider: "Flexera", - service: "Optima", - policy_set: "Automation", - publish: "false" -) - -############################################################################### -# Parameters -############################################################################### - -parameter "param_tag_list" do - type "list" - category "Policy Settings" - label "Label Keys" - description "A list of Google account label keys to build Rule-Based Dimensions from" - # No default value, user input required -end - -parameter "param_name_list" do - type "list" - category "Policy Settings" - label "Dimension Names" - description "A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the label keys in the 'Label Keys' field. Dimension names will be derived from label keys directly if this list is left empty." - default [] -end - -parameter "param_effective_date" do - type "string" - category "Policy Settings" - label "Effective Date" - description "Year/month you want rules to start applying in YYYY-MM format" - default "2010-01" -end - -parameter "param_normalize_case" do - type "string" - category "Policy Settings" - label "Lowercase Values" - description "Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail." - allowed_values "Yes", "No" - default "Yes" -end - -############################################################################### -# Authentication -############################################################################### - -credentials "auth_google" do - schemes "oauth2" - label "Google" - description "Select the Google Cloud Credential from the list." - tags "provider=gce" -end - -credentials "auth_flexera" do - schemes "oauth2" - label "flexera" - description "Select FlexeraOne OAuth2 credential." - tags "provider=flexera" -end - -############################################################################### -# Pagination -############################################################################### - -pagination "pagination_google" do - get_page_marker do - body_path "nextPageToken" - end - set_page_marker do - query "pageToken" - end -end - -############################################################################### -# Datasources & Scripts -############################################################################### - -# 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 - -datasource "ds_existing_rbds" do - request do - auth $auth_flexera - host rs_optima_host - path join(["/bill-analysis/orgs/", rs_org_id, "/settings/rule_based_dimensions"]) - header "Api-Version", "1.0" - header "content-type", "application/json" - end - result do - encoding "json" - collect jmes_path(response, "rule_based_dimensions") do - field "id", jmes_path(col_item, "id") - field "name", jmes_path(col_item, "name") - field "dated_rules", jmes_path(col_item, "dated_rules") - end - end -end - -datasource "ds_google_projects" do - request do - auth $auth_google - pagination $pagination_google - host "cloudresourcemanager.googleapis.com" - path "/v1/projects/" - query "filter", "lifecycleState=ACTIVE" - end - result do - encoding "json" - collect jmes_path(response, "projects[*]") do - field "id", jmes_path(col_item, "projectId") - field "number", jmes_path(col_item, "projectNumber") - field "name", jmes_path(col_item, "name") - field "parent", jmes_path(col_item, "parent") - field "createTime", jmes_path(col_item, "createTime") - field "lifecycleState", jmes_path(col_item, "lifecycleState") - field "tags", jmes_path(col_item, "labels") - end - end -end - -datasource "ds_google_projects_normalized" do - run_script $js_google_projects_normalized, $ds_google_projects -end - -script "js_google_projects_normalized", type: "javascript" do - parameters "accounts" - result "result" - code <<-'EOS' - result = [] - - _.each(accounts, function(account) { - tags = {} - - if (account['tags'] != null && account['tags'] != undefined) { - _.each(Object.keys(account['tags']), function(key) { - normalized_key = key.toLowerCase().trim() - tags[normalized_key] = account['tags'][key] - }) - } - - result.push({ - id: account['id'], - number: account['number'], - name: account['name'], - parent: account['parent'], - createTime: account['createTime'], - lifecycleState: account['lifecycleState'], - tags: tags - }) - }) -EOS -end - -datasource "ds_rbds" do - run_script $js_rbds, $ds_google_projects_normalized, $ds_existing_rbds, $param_name_list, $param_tag_list, $param_effective_date, $param_normalize_case -end - -script "js_rbds", type: "javascript" do - parameters "accounts", "existing_rbds", "param_name_list", "param_tag_list", "param_effective_date", "param_normalize_case" - result "result" - code <<-'EOS' - result = [] - - rbd_id_list = _.pluck(existing_rbds, 'id') - - _.each(param_tag_list, function(tag, index) { - rbd_name = tag.replace('.', ' ').replace('-', ' ') - rbd_name = rbd_name.replace(/\W/g, " ").trim() - - // Use user-specified name instead of user provided one - if (param_tag_list.length == param_name_list.length) { rbd_name = param_name_list[index] } - - rbd_id = "rbd_" + rbd_name.toLowerCase().replace(/\s+/g, '').replace(/\W/g, "").replace('.', '').replace('-', '').trim() - - tag_key = tag.toLowerCase().trim() - verb = "POST" - if (_.contains(rbd_id_list, rbd_id)) { verb = "PATCH" } - - 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 (rules.length > 0) { - result.push({ - id: rbd_id, - name: rbd_name, - verb: verb, - effective_at: param_effective_date, - rules: rules - }) - } - }) -EOS -end - -datasource "ds_create_rbds" do - iterate $ds_rbds - request do - run_script $js_create_rbds, val(iter_item, "id"), val(iter_item, "verb"), val(iter_item, "name"), val($ds_flexera_api_hosts, "flexera"), rs_org_id - end - result do - encoding "text" - end -end - -script "js_create_rbds", type: "javascript" do - parameters "rbd_id", "verb", "name", "api_host", "rs_org_id" - result "request" - code <<-EOS - var request = { - auth: "auth_flexera", - verb: verb, - host: api_host, - path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id].join(''), - body_fields: { name: name } - } -EOS -end - -datasource "ds_apply_rbds" do - iterate $ds_rbds - request do - # ds_create_rbds is a parameter to ensure that it executes before ds_apply_rbds does - run_script $js_apply_rbds, val(iter_item, "id"), val(iter_item, "effective_at"), val(iter_item, "rules"), val($ds_flexera_api_hosts, "flexera"), $ds_create_rbds, rs_org_id - end - result do - encoding "text" - end -end - -script "js_apply_rbds", type: "javascript" do - parameters "rbd_id", "effective_at", "rules", "api_host", "ds_create_rbds", "rs_org_id" - result "request" - code <<-EOS - var request = { - auth: "auth_flexera", - verb: "PUT", - host: api_host, - path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id, "/rules/", effective_at].join(''), - body_fields: { rules: rules } - } -EOS -end - -############################################################################### -# Policy -############################################################################### - -policy "pol_rbds" do - validate $ds_apply_rbds do - summary_template "RBDs Generated & Applied" - detail_template "" - check eq(0, 0) - end -end From 27997df5715b53c313c23f00d22d91d325831249 Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Tue, 8 Oct 2024 03:11:11 -0500 Subject: [PATCH 09/11] update --- .spellignore | 29 - .../aws/aws_missing_regions/CHANGELOG.md | 9 + automation/aws/aws_missing_regions/README.md | 62 + .../aws_missing_regions.pt | 359 +++++ .../aws_missing_regions_meta_parent.pt | 1221 +++++++++++++++++ automation/aws/aws_rbd_from_tag/CHANGELOG.md | 34 + automation/aws/aws_rbd_from_tag/README.md | 37 + .../aws/aws_rbd_from_tag/aws_rbd_from_tag.pt | 277 ++++ .../azure_missing_subscriptions/CHANGELOG.md | 5 + .../azure_missing_subscriptions/README.md | 40 + .../azure_missing_subscriptions.pt | 340 +++++ .../azure/azure_rbd_from_rg_tag/CHANGELOG.md | 25 + .../azure/azure_rbd_from_rg_tag/README.md | 43 + .../azure_rbd_from_rg_tag.pt | 418 ++++++ .../azure/azure_rbd_from_tag/CHANGELOG.md | 25 + automation/azure/azure_rbd_from_tag/README.md | 42 + .../azure_rbd_from_tag/azure_rbd_from_tag.pt | 318 +++++ .../delete_all_billing_centers/CHANGELOG.md | 9 + .../delete_all_billing_centers/README.md | 34 + .../delete_all_billing_centers.pt | 233 ++++ .../outdated_applied_policies/CHANGELOG.md | 14 + .../outdated_applied_policies/README.md | 71 + .../outdated_applied_policies.pt | 835 +++++++++++ .../google/google_rbd_from_label/CHANGELOG.md | 26 + .../google/google_rbd_from_label/README.md | 40 + .../google_rbd_from_label.pt | 305 ++++ 26 files changed, 4822 insertions(+), 29 deletions(-) create mode 100644 automation/aws/aws_missing_regions/CHANGELOG.md create mode 100644 automation/aws/aws_missing_regions/README.md create mode 100644 automation/aws/aws_missing_regions/aws_missing_regions.pt create mode 100644 automation/aws/aws_missing_regions/aws_missing_regions_meta_parent.pt create mode 100644 automation/aws/aws_rbd_from_tag/CHANGELOG.md create mode 100644 automation/aws/aws_rbd_from_tag/README.md create mode 100644 automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt create mode 100644 automation/azure/azure_missing_subscriptions/CHANGELOG.md create mode 100644 automation/azure/azure_missing_subscriptions/README.md create mode 100644 automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt create mode 100644 automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md create mode 100644 automation/azure/azure_rbd_from_rg_tag/README.md create mode 100644 automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt create mode 100644 automation/azure/azure_rbd_from_tag/CHANGELOG.md create mode 100644 automation/azure/azure_rbd_from_tag/README.md create mode 100644 automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt create mode 100644 automation/flexera/delete_all_billing_centers/CHANGELOG.md create mode 100644 automation/flexera/delete_all_billing_centers/README.md create mode 100644 automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt create mode 100644 automation/flexera/outdated_applied_policies/CHANGELOG.md create mode 100644 automation/flexera/outdated_applied_policies/README.md create mode 100644 automation/flexera/outdated_applied_policies/outdated_applied_policies.pt create mode 100644 automation/google/google_rbd_from_label/CHANGELOG.md create mode 100644 automation/google/google_rbd_from_label/README.md create mode 100644 automation/google/google_rbd_from_label/google_rbd_from_label.pt diff --git a/.spellignore b/.spellignore index 537c65c574..1c67184d4e 100644 --- a/.spellignore +++ b/.spellignore @@ -585,32 +585,3 @@ ByteCount PacketCount balancers backfill -FNMS -CBI -workspace -workspaces -Workspace -Workspaces -OCID -OAuth -oauth -BYOL -GCP -TLS -SSL -Balancer -balancer -LUN -Unmanaged -unmanaged -Oversized -oversized -AMI -actioning -actioned -VPN -VPNs -Dataset -dataset -Datasets -datasets diff --git a/automation/aws/aws_missing_regions/CHANGELOG.md b/automation/aws/aws_missing_regions/CHANGELOG.md new file mode 100644 index 0000000000..c0e1662927 --- /dev/null +++ b/automation/aws/aws_missing_regions/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## v0.2.0 + +- Fixed issue with meta policy not working due to missing `Account Number` parameter + +## v0.1 + +- initial release diff --git a/automation/aws/aws_missing_regions/README.md b/automation/aws/aws_missing_regions/README.md new file mode 100644 index 0000000000..22893aed73 --- /dev/null +++ b/automation/aws/aws_missing_regions/README.md @@ -0,0 +1,62 @@ +# AWS Missing Regions + +## What It Does + +This policy checks the list of AWS regions returned by an AWS `DescribeRegions` API request and tests each region with an EC2 `DescribeInstances` request to see if the Flexera AWS credential can actually run API requests against that region. An incident is raised and email sent with any regions that are inaccessible. + +__NOTE: Meta Parent policy will only work if both the parent and the child are uploaded to the Flexera org and the "Uploaded Template" option is selected for the `Child Policy Template Source` parameter. This is because the child policy is *not* published in the catalog.__ + +## Input Parameters + +This policy has the following input parameters required when launching the policy. + +- *Email Addresses* - Email addresses of the recipients you wish to notify when new incidents are created. +- *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) +- *Region Ignore List* - A list of regions to never include in the results. Leave blank to not filter results + +## Policy Actions + +The following policy actions are taken on any resources found to be out of compliance. + +- Send an email report + +## 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: + - `ec2:DescribeRegions` + - `ec2:DescribeInstances` + - `sts:GetCallerIdentity` + + Example IAM Permission Policy: + + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:DescribeRegions", + "ec2:DescribeInstances", + "sts:GetCallerIdentity" + ], + "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/automation/aws/aws_missing_regions/aws_missing_regions.pt b/automation/aws/aws_missing_regions/aws_missing_regions.pt new file mode 100644 index 0000000000..5b41cc8225 --- /dev/null +++ b/automation/aws/aws_missing_regions/aws_missing_regions.pt @@ -0,0 +1,359 @@ +name "AWS Missing Regions" +rs_pt_ver 20180301 +type "policy" +short_description "Reports any AWS regions that are enabled but the policy engine is not able to access. [README](https://github.com/flexera-public/policy_templates/tree/master/automation/aws/aws_missing_regions/) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." +long_description "" +severity "low" +category "Cost" +default_frequency "weekly" +info( + version: "0.2.0", + provider: "Flexera", + service: "Optima", + policy_set: "Automation", + publish: "false" +) + +############################################################################### +# 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_region_list" do + type "list" + category "Filters" + label "Region Ignore List" + description "A list of regions to never include in the results. Leave blank to not filter results." + 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_regions" do + request do + auth $auth_aws + verb "GET" + host "ec2.amazonaws.com" + path "/" + query "Action", "DescribeRegions" + query "Version", "2016-11-15" + query "Filter.1.Name", "opt-in-status" + query "Filter.1.Value.1", "opt-in-not-required" + query "Filter.1.Value.2", "opted-in" + # 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 "xml" + collect xpath(response, "//DescribeRegionsResponse/regionInfo/item", "array") do + field "region", xpath(col_item, "regionName") + end + end +end + +# Make a general API call to list EC2 instances to test functionality +datasource "ds_instance_sets" do + iterate $ds_regions + request do + auth $auth_aws + host join(['ec2.', val(iter_item, 'region'), '.amazonaws.com']) + path '/' + query 'Action', 'DescribeInstances' + query 'Version', '2016-11-15' + header 'User-Agent', 'RS Policies' + header 'Content-Type', 'text/xml' + ignore_status [400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410] + end + result do + encoding "xml" + field "region", val(iter_item, "region") + end +end + +datasource "ds_missing_regions" do + run_script $js_missing_regions, $ds_regions, $ds_instance_sets, $ds_aws_account, $ds_applied_policy, $param_region_list +end + +script "js_missing_regions", type: "javascript" do + parameters "ds_regions", "ds_instance_sets", "ds_aws_account", "ds_applied_policy", "param_region_list" + result "result" + code <<-'EOS' + result = [] + + enabled_regions = _.uniq(_.pluck(ds_regions, 'region')) + found_regions = _.uniq(_.pluck(ds_instance_sets, 'region')) + + _.each(enabled_regions, function(region) { + if (!_.contains(found_regions, region) && !_.contains(param_region_list, region)) { + result.push({ + "accountID": ds_aws_account['id'], + "accountName": ds_aws_account['name'], + "region": region, + "policy_name": ds_applied_policy['name'] + }) + } + }) + + result = _.sortBy(result, 'region') + result = _.sortBy(result, 'accountID') +EOS +end + +############################################################################### +# Policy +############################################################################### + +policy "pol_missing_regions" do + validate_each $ds_missing_regions do + summary_template "{{ with index data 0 }}{{ .policy_name }}{{ end }}: {{ len data }} AWS Missing Regions Detected" + check logic_or($ds_parent_policy_terminated, eq(val(item, "accountID"), "")) + escalate $esc_email + export do + resource_level false + field "accountID" do + label "Account ID" + end + field "accountName" do + label "Account Name" + end + field "region" do + label "Region" + end + end + end +end + +############################################################################### +# Escalations +############################################################################### + +escalation "esc_email" do + automatic true + label "Send Email" + description "Send incident email" + email $param_email +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/automation/aws/aws_missing_regions/aws_missing_regions_meta_parent.pt b/automation/aws/aws_missing_regions/aws_missing_regions_meta_parent.pt new file mode 100644 index 0000000000..dbe5d526b0 --- /dev/null +++ b/automation/aws/aws_missing_regions/aws_missing_regions_meta_parent.pt @@ -0,0 +1,1221 @@ +name "Meta Parent: AWS Missing Regions" +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 Missing Regions](https://github.com/flexera-public/policy_templates/tree/master/automation/aws/aws_missing_regions) Policies." +severity "low" +category "Meta" +default_frequency "15 minutes" +info( + provider: "AWS", + version: "0.2.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 + publish: "false", + deprecated: "false" +) + +############################################################################## +# 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 Missing Regions\" Policy Template from Catalog. Optionally, you can use the \"AWS Missing Regions\" 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_region_list" do + type "list" + category "Filters" + label "Region Ignore List" + description "A list of regions to never include in the results. Leave blank to not filter results." + 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_get_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" + collect jmes_path(response, "items[*]") do + field "name", jmes_path(col_item, "name") + field "created_by", jmes_path(col_item, "created_by.email") + field "href", jmes_path(col_item, "href") + field "short_description", jmes_path(col_item, "short_description") + end + end +end + +# Select the published policy that is published by "support@flexera.com" and matches the name of the child policy template +datasource "ds_published_child_policy_information" do + run_script $js_published_child_policy_information, $ds_get_published_child_policy_information +end + +script "js_published_child_policy_information", type: "javascript" do + parameters "ds_get_published_child_policy_information" + result "result" + code <<-EOS + result = _.filter(ds_get_published_child_policy_information, function(item) { + return item['name'] == "AWS Missing Regions" && item['created_by'] == "support@flexera.com" + }) +EOS +end + +# Get Uploaded Policy Details +datasource "ds_get_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" + collect jmes_path(response, "items[*]") 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 + +# Select the uploaded policy that matches the name of the child policy template +datasource "ds_project_child_policy_information" do + run_script $js_project_child_policy_information, $ds_get_project_child_policy_information +end + +script "js_project_child_policy_information", type: "javascript" do + parameters "ds_get_project_child_policy_information" + result "result" + code <<-EOS + result = _.filter(ds_get_project_child_policy_information, function(item) { + return item['name'] == "AWS Missing Regions" + }) +EOS +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 + encoding "json" + collect jmes_path(response, "items[*]") do + field "name", jmes_path(col_item, "name") + field "applied_policy_id", jmes_path(col_item, "id") + field "options", jmes_path(col_item, "options") + field "updated_at", jmes_path(col_item, "updated_at") + field "status", jmes_path(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 + encoding "json" + collect jmes_path(response, "items[*]") do + field "incident_id", jmes_path(col_item, "id") + field "applied_policy_id", jmes_path(col_item, "applied_policy.id") + field "summary", jmes_path(col_item, "summary") + field "state", jmes_path(col_item, "state") + field "violation_data_count", jmes_path(col_item, "violation_data_count") + field "updated_at", jmes_path(col_item, "updated_at") + field "meta_parent_policy_id", jmes_path(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 + + + + +# 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 Missing Regions Detected + validate $ds_missing_regions_combined_incidents do + summary_template "Consolidated Incident: {{ len data }} AWS Missing Regions Detected" + escalate $esc_email + + check eq(size(data), 0) + export do + resource_level false + field "accountID" do + label "Account ID" + end + field "accountName" do + label "Account Name" + end + field "region" do + label "Region" + end + 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/automation/aws/aws_rbd_from_tag/CHANGELOG.md b/automation/aws/aws_rbd_from_tag/CHANGELOG.md new file mode 100644 index 0000000000..97fd6e9145 --- /dev/null +++ b/automation/aws/aws_rbd_from_tag/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog + +## v2.3.2 + +- Fixed issue where tag keys that were not lowercase would not be properly detected and used + +## v2.3.1 + +- Fixed issue that would sometimes cause execution to fail if an AWS Account had no tag keys + +## v2.3.0 + +- Added option to retain original casing of tag values instead of normalizing them all to lowercase + +## v2.2.1 + +- Updated policy template to use newer API endpoints. Functionality is unchanged. + +## v2.2 + +- added ability to specify names for the newly created dimensions + +## v2.1 + +- Corrected API issue when executing policy in APAC + +## v2.0 + +- Removed requirement for AWS credential +- Internal API is now used to gather AWS account and tag information + +## v1.0 + +- initial release diff --git a/automation/aws/aws_rbd_from_tag/README.md b/automation/aws/aws_rbd_from_tag/README.md new file mode 100644 index 0000000000..311f531d76 --- /dev/null +++ b/automation/aws/aws_rbd_from_tag/README.md @@ -0,0 +1,37 @@ +# AWS Rule-Based Dimension From Account Tags + +## What It Does + +This policy creates and updates custom Rule-Based Dimensions that surface the specified AWS Account tag keys in the Flexera One platform. This allows costs to be sliced by the values of the tag keys in question. + +## Input Parameters + +This policy has the following input parameters required when launching the policy. + +- *Effective Date* - The month and year in YYYY-MM format that you want the rules to apply. This should be left at its default value in most cases to ensure that the rules apply to all costs, including historical costs. +- *Tag Keys* - A list of AWS Account tag keys to create custom Rule-Based Dimensions for. +- *Dimension Names* - A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the `Tag Keys` field. Dimension names will be derived from tag keys directly if this list is left empty. +- *Lowercase Values* - Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail. + +## Policy Actions + +- Create/update rule-based dimensions + +## 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). + +- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: + - `observer` + - `billing_center_viewer` + - `rule_based_dimensions_manager` + +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 launch any instances, and so does not incur any cloud costs. 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 new file mode 100644 index 0000000000..55aeb43074 --- /dev/null +++ b/automation/aws/aws_rbd_from_tag/aws_rbd_from_tag.pt @@ -0,0 +1,277 @@ +name "AWS Rule-Based Dimension From Account Tags" +rs_pt_ver 20180301 +type "policy" +short_description "Creates and/or updates individual Rule-Based Dimensions based on AWS Account tags. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/aws/aws_rbd_from_tag) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." +long_description "" +severity "low" +category "Cost" +default_frequency "daily" +info( + version: "2.3.2", + provider: "Flexera", + service: "Optima", + policy_set: "Automation", + publish: "false" +) + +############################################################################### +# Parameters +############################################################################### + +parameter "param_tag_list" do + type "list" + category "Policy Settings" + label "Tag Keys" + description "A list of AWS account tag keys to build Rule-Based Dimensions from" + # No default value, user input required +end + +parameter "param_name_list" do + type "list" + category "Policy Settings" + label "Dimension Names" + description "A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the 'Tag Keys' field. Dimension names will be derived from tag keys directly if this list is left empty." + default [] +end + +parameter "param_effective_date" do + type "string" + category "Policy Settings" + label "Effective Date" + description "Year/month you want rules to start applying in YYYY-MM format" + default "2010-01" +end + +parameter "param_normalize_case" do + type "string" + category "Policy Settings" + label "Lowercase Values" + description "Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail." + allowed_values "Yes", "No" + default "Yes" +end + +############################################################################### +# Authentication +############################################################################### + +credentials "auth_flexera" do + schemes "oauth2" + label "flexera" + description "Select FlexeraOne OAuth2 credential." + tags "provider=flexera" +end + +############################################################################### +# Datasources & Scripts +############################################################################### + +# 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_cloud_vendor_accounts_normalized" do + run_script $js_cloud_vendor_accounts_normalized, $ds_cloud_vendor_accounts +end + +script "js_cloud_vendor_accounts_normalized", type: "javascript" do + parameters "accounts" + result "result" + code <<-'EOS' + result = [] + + _.each(accounts, function(account) { + tags = {} + + if (account['tags'] != null && account['tags'] != undefined) { + _.each(Object.keys(account['tags']), function(key) { + normalized_key = key.toLowerCase().trim() + tags[normalized_key] = account['tags'][key] + }) + } + + result.push({ + id: account['id'], + name: account['name'], + tags: tags + }) + }) +EOS +end + +datasource "ds_existing_rbds" do + request do + auth $auth_flexera + host rs_optima_host + path join(["/bill-analysis/orgs/", rs_org_id, "/settings/rule_based_dimensions"]) + header "Api-Version", "1.0" + header "content-type", "application/json" + end + result do + encoding "json" + collect jmes_path(response, "rule_based_dimensions") do + field "id", jmes_path(col_item, "id") + field "name", jmes_path(col_item, "name") + field "dated_rules", jmes_path(col_item, "dated_rules") + end + end +end + +datasource "ds_rbds" do + run_script $js_rbds, $ds_cloud_vendor_accounts_normalized, $ds_existing_rbds, $param_name_list, $param_tag_list, $param_effective_date, $param_normalize_case +end + +script "js_rbds", type: "javascript" do + parameters "accounts", "existing_rbds", "param_name_list", "param_tag_list", "param_effective_date", "param_normalize_case" + result "result" + code <<-'EOS' + result = [] + + rbd_id_list = _.pluck(existing_rbds, 'id') + + _.each(param_tag_list, function(tag, index) { + rbd_name = tag.replace('.', ' ').replace('-', ' ') + rbd_name = rbd_name.replace(/\W/g, " ").trim() + + // Use user-specified name instead of user provided one + if (param_tag_list.length == param_name_list.length) { rbd_name = param_name_list[index] } + + rbd_id = "rbd_" + rbd_name.toLowerCase().replace(/\s+/g, '').replace(/\W/g, "").replace('.', '').replace('-', '').trim() + + tag_key = tag.toLowerCase().trim() + verb = "POST" + if (_.contains(rbd_id_list, rbd_id)) { verb = "PATCH" } + + rules = [] + + _.each(accounts, function(account) { + 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() } + + rules.push({ + condition: { type: "dimension_equals", dimension: "vendor_account", value: account['id'] }, + value: { text: value } + }) + } + } + } + }) + + if (rules.length > 0) { + result.push({ + id: rbd_id, + name: rbd_name, + verb: verb, + effective_at: param_effective_date, + rules: rules + }) + } + }) +EOS +end + +datasource "ds_create_rbds" do + iterate $ds_rbds + request do + run_script $js_create_rbds, val(iter_item, "id"), val(iter_item, "verb"), val(iter_item, "name"), val($ds_flexera_api_hosts, "flexera"), rs_org_id + end + result do + encoding "text" + end +end + +script "js_create_rbds", type: "javascript" do + parameters "rbd_id", "verb", "name", "api_host", "rs_org_id" + result "request" + code <<-EOS + var request = { + auth: "auth_flexera", + verb: verb, + host: api_host, + path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id].join(''), + body_fields: { name: name } + } +EOS +end + +datasource "ds_apply_rbds" do + iterate $ds_rbds + request do + # ds_create_rbds is a parameter to ensure that it executes before ds_apply_rbds does + run_script $js_apply_rbds, val(iter_item, "id"), val(iter_item, "effective_at"), val(iter_item, "rules"), val($ds_flexera_api_hosts, "flexera"), $ds_create_rbds, rs_org_id + end + result do + encoding "text" + end +end + +script "js_apply_rbds", type: "javascript" do + parameters "rbd_id", "effective_at", "rules", "api_host", "ds_create_rbds", "rs_org_id" + result "request" + code <<-EOS + var request = { + auth: "auth_flexera", + verb: "PUT", + host: api_host, + path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id, "/rules/", effective_at].join(''), + body_fields: { rules: rules } + } +EOS +end + +############################################################################### +# Policy +############################################################################### + +policy "pol_rbds" do + validate $ds_apply_rbds do + summary_template "RBDs Generated & Applied" + detail_template "" + check eq(0, 0) + end +end diff --git a/automation/azure/azure_missing_subscriptions/CHANGELOG.md b/automation/azure/azure_missing_subscriptions/CHANGELOG.md new file mode 100644 index 0000000000..1ad604f8d4 --- /dev/null +++ b/automation/azure/azure_missing_subscriptions/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## v0.1 + +- initial release diff --git a/automation/azure/azure_missing_subscriptions/README.md b/automation/azure/azure_missing_subscriptions/README.md new file mode 100644 index 0000000000..5f33caf7f3 --- /dev/null +++ b/automation/azure/azure_missing_subscriptions/README.md @@ -0,0 +1,40 @@ +# Azure Missing Subscriptions + +## What It Does + +This policy checks the stored Flexera CCO billing data for Azure from 3 days ago to obtain a list of Azure Subscriptions that we have billing data for and compares that to the list of Azure Subscriptions returned by the Azure Resource Manager API. An incident is raised and email sent containing any subscriptions present in Flexera CCO but not returned by the Azure Resource Manager API, as well as subscriptions returned by the Azure Resource Manager API but not present in Flexera CCO. The user can select which of those two reports they'd like to produce. + +## Input Parameters + +This policy has the following input parameters required when launching the policy. + +- *Email Addresses* - Email addresses of the recipients you wish to notify when new incidents are created. +- *Azure Endpoint* - The endpoint to send Azure API requests to. Recommended to leave this at default unless using this policy with Azure China. +- *Report Selection* - Whether to report Subscriptions missing in the Azure API but present in CCO data, the opposite, or both. +- *Subscriptions Ignore List* - A list of Subscription IDs/names to never include in the results. Leave blank to not filter results + +## Policy Actions + +The following policy actions are taken on any resources found to be out of compliance. + +- Send an email report + +## 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). + +- [**Azure Resource Manager Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_109256743_1124668) (*provider=azure_rm*) which has the following permissions: + - `Microsoft.Resources/subscriptions/read` + +- [**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 + +- Azure + +## Cost + +This Policy Template does not incur any cloud costs. diff --git a/automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt b/automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt new file mode 100644 index 0000000000..4cdf0a2821 --- /dev/null +++ b/automation/azure/azure_missing_subscriptions/azure_missing_subscriptions.pt @@ -0,0 +1,340 @@ +name "Azure Missing Subscriptions" +rs_pt_ver 20180301 +type "policy" +short_description "Reports any Azure Subscriptions present in Flexera One that are not accessible via the Azure Resource Manager automation credential. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/azure/azure_missing_subscriptions) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." +long_description "" +severity "low" +category "Cost" +default_frequency "weekly" +info( + version: "0.1", + provider: "Flexera", + service: "Optima", + policy_set: "Automation", + publish: "false" +) + +############################################################################### +# 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_azure_endpoint" do + type "string" + category "Policy Settings" + label "Azure Endpoint" + description "Select the API endpoint to use for Azure. Use default value of management.azure.com unless using Azure China." + allowed_values "management.azure.com", "management.chinacloudapi.cn" + default "management.azure.com" +end + +parameter "param_report_selection" do + type "string" + category "Policy Settings" + label "Report Selection" + description "Whether to report Subscriptions missing in the Azure API but present in CCO data, the opposite, or both." + allowed_values "Missing in Azure API", "Missing in CCO", "Both" + default "Missing in Azure API" +end + +parameter "param_subscriptions_list" do + type "list" + category "Filters" + label "Subscriptions Ignore List" + description "A list of Subscription IDs/names to never include in the results. Leave blank to not filter results." + default [] +end + +############################################################################### +# Authentication +############################################################################### + +credentials "auth_azure" do + schemes "oauth2" + label "Azure" + description "Select the Azure Resource Manager Credential from the list." + tags "provider=azure_rm" +end + +credentials "auth_flexera" do + schemes "oauth2" + label "Flexera" + description "Select Flexera One OAuth2 credentials" + tags "provider=flexera" +end + +############################################################################### +# Pagination +############################################################################### + +pagination "pagination_azure" do + get_page_marker do + body_path "nextLink" + end + set_page_marker do + uri true + end +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 + +datasource "ds_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" + collect jmes_path(response, "[*]") do + field "href", jmes_path(col_item, "href") + field "id", jmes_path(col_item, "id") + field "name", jmes_path(col_item, "name") + field "parent_id", jmes_path(col_item, "parent_id") + end + end +end + +# Gather top level billing center IDs for when we pull cost data +datasource "ds_top_level_bcs" do + run_script $js_top_level_bcs, $ds_billing_centers +end + +script "js_top_level_bcs", type: "javascript" do + parameters "ds_billing_centers" + result "result" + code <<-EOS + filtered_bcs = _.filter(ds_billing_centers, function(bc) { + return bc['parent_id'] == null || bc['parent_id'] == undefined + }) + + result = _.compact(_.pluck(filtered_bcs, 'id')) +EOS +end + +datasource "ds_flexera_cco_data" do + request do + run_script $js_flexera_cco_data, $ds_top_level_bcs, rs_org_id, rs_optima_host + end + result do + encoding "json" + collect jmes_path(response, "rows[*]") do + field "bill_source", jmes_path(col_item, "dimensions.bill_source") + field "vendor_account", jmes_path(col_item, "dimensions.vendor_account") + field "vendor_account_name", jmes_path(col_item, "dimensions.vendor_account_name") + end + end +end + +script "js_flexera_cco_data", type: "javascript" do + parameters "ds_top_level_bcs", "rs_org_id", "rs_optima_host" + result "request" + code <<-EOS + end_date = new Date() + end_date.setDate(end_date.getDate() - 2) + end_date = end_date.toISOString().split('T')[0] + + start_date = new Date() + start_date.setDate(start_date.getDate() - 3) + start_date = start_date.toISOString().split('T')[0] + + var request = { + auth: "auth_flexera", + host: rs_optima_host, + verb: "POST", + path: "/bill-analysis/orgs/" + rs_org_id + "/costs/aggregated", + body_fields: { + dimensions: ["bill_source", "vendor_account", "vendor_account_name"], + granularity: "day", + start_at: start_date, + end_at: end_date, + metrics: ["cost_amortized_unblended_adj"], + billing_center_ids: ds_top_level_bcs, + limit: 100000, + filter: { + "type": "or", + "expressions": [ + { "dimension": "vendor", "type": "equal", "value": "Azure" }, + { "dimension": "vendor", "type": "equal", "value": "azure" }, + { "dimension": "vendor", "type": "equal", "value": "Microsoft Azure" }, + { "dimension": "vendor", "type": "equal", "value": "microsoft azure" } + ] + } + }, + headers: { + 'User-Agent': "RS Policies", + 'Api-Version': "1.0" + }, + ignore_status: [400] + } +EOS +end + +datasource "ds_azure_subscriptions" do + request do + auth $auth_azure + pagination $pagination_azure + host $param_azure_endpoint + path "/subscriptions/" + query "api-version","2020-01-01" + header "User-Agent", "RS Policies" + # Ignore status 400, 403, and 404 which can be returned in certain (legacy) types of Azure Subscriptions + ignore_status [400, 403, 404] + end + result do + encoding "json" + collect jmes_path(response, "value[*]") do + field "id", jmes_path(col_item, "subscriptionId") + field "name", jmes_path(col_item, "displayName") + field "state", jmes_path(col_item, "state") + end + end +end + +datasource "ds_missing_subscriptions_api" do + run_script $js_missing_subscriptions_api, $ds_flexera_cco_data, $ds_azure_subscriptions, $ds_applied_policy, $param_subscriptions_list, $param_report_selection +end + +script "js_missing_subscriptions_api", type: "javascript" do + parameters "ds_flexera_cco_data", "ds_azure_subscriptions", "ds_applied_policy", "param_subscriptions_list", "param_report_selection" + result "result" + code <<-'EOS' + result = [] + + if (param_report_selection != 'Missing in CCO') { + automation_ids = _.map(ds_azure_subscriptions, function(sub) { + return sub['id'].toLowerCase().trim() + }) + + missing_subs = _.reject(ds_flexera_cco_data, function(sub) { + return _.contains(automation_ids, sub['vendor_account'].toLowerCase().trim()) + }) + + result = _.map(missing_subs, function(sub) { + return { + accountID: sub['vendor_account'].toLowerCase().trim(), + accountName: sub['vendor_account_name'], + bill_source: sub['bill_source'], + policy_name: ds_applied_policy['name'] + } + }) + + // Remove filtered results + result = _.reject(result, function(sub) { + return _.contains(param_subscriptions_list, sub['accountID']) || _.contains(param_subscriptions_list, sub['accountName']) + }) + + result = _.sortBy(result, 'accountID') + result = _.sortBy(result, 'bill_source') + } +EOS +end + +datasource "ds_missing_subscriptions_cco" do + run_script $js_missing_subscriptions_cco, $ds_flexera_cco_data, $ds_azure_subscriptions, $ds_applied_policy, $param_subscriptions_list, $param_report_selection +end + +script "js_missing_subscriptions_cco", type: "javascript" do + parameters "ds_flexera_cco_data", "ds_azure_subscriptions", "ds_applied_policy", "param_subscriptions_list", "param_report_selection" + result "result" + code <<-'EOS' + result = [] + + if (param_report_selection != 'Missing in Azure API') { + cco_ids = _.map(ds_flexera_cco_data, function(sub) { + return sub['vendor_account'].toLowerCase().trim() + }) + + missing_subs = _.reject(ds_azure_subscriptions, function(sub) { + return _.contains(cco_ids, sub['id'].toLowerCase().trim()) + }) + + result = _.map(missing_subs, function(sub) { + return { + accountID: sub['id'].toLowerCase().trim(), + accountName: sub['name'], + policy_name: ds_applied_policy['name'] + } + }) + + // Remove filtered results + result = _.reject(result, function(sub) { + return _.contains(param_subscriptions_list, sub['accountID']) || _.contains(param_subscriptions_list, sub['accountName']) + }) + + result = _.sortBy(result, 'accountID') + } +EOS +end + +############################################################################### +# Policy +############################################################################### + +policy "pol_missing_subscriptions" do + validate_each $ds_missing_subscriptions_api do + summary_template "{{ with index data 0 }}{{ .policy_name }}{{ end }}: {{ len data }} Azure API Missing Subscriptions" + check eq(val(item, "accountID"), "") + escalate $esc_email + export do + resource_level false + field "accountID" do + label "Subscription ID" + end + field "accountName" do + label "Subscription Name" + end + field "bill_source" do + label "Bill Connection" + end + end + end + validate_each $ds_missing_subscriptions_cco do + summary_template "{{ with index data 0 }}{{ .policy_name }}{{ end }}: {{ len data }} Azure CCO Missing Subscriptions" + check eq(val(item, "accountID"), "") + escalate $esc_email + export do + resource_level false + field "accountID" do + label "Subscription ID" + end + field "accountName" do + label "Subscription Name" + end + end + end +end + +############################################################################### +# Escalations +############################################################################### + +escalation "esc_email" do + automatic true + label "Send Email" + description "Send incident email" + email $param_email +end diff --git a/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md b/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md new file mode 100644 index 0000000000..9a2c4ba2ad --- /dev/null +++ b/automation/azure/azure_rbd_from_rg_tag/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +## v1.3.1 + +- Fixed issue that would sometimes cause execution to fail if an Azure Subscription or Resource Group had no tag keys + +## v1.3.0 + +- Added option to retain original casing of tag values instead of normalizing them all to lowercase + +## v1.2.1 + +- Updated policy template to use newer API endpoints. Functionality is unchanged. + +## v1.2 + +- Fixed error where policy would fail completely when trying to access resources credential does not have access to. Policy will now simply skip these resources. + +## v1.1 + +- added ability to specify names for the newly created dimensions + +## v1.0 + +- initial release diff --git a/automation/azure/azure_rbd_from_rg_tag/README.md b/automation/azure/azure_rbd_from_rg_tag/README.md new file mode 100644 index 0000000000..98ae4beb88 --- /dev/null +++ b/automation/azure/azure_rbd_from_rg_tag/README.md @@ -0,0 +1,43 @@ +# Azure Rule-Based Dimension From Resource Group Tags + +## What It Does + +This policy creates and updates custom Rule-Based Dimensions that surface the specified Azure Resource Group tag keys in the Flexera One platform. This allows costs to be sliced by the values of the tag keys in question. + +## Input Parameters + +This policy has the following input parameters required when launching the policy. + +- *Effective Date* - The month and year in YYYY-MM format that you want the rules to apply. This should be left at its default value in most cases to ensure that the rules apply to all costs, including historical costs. +- *Tag Keys* - A list of Azure Resource Group tag keys to create custom Rule-Based Dimensions for. +- *Dimension Names* - A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the `Tag Keys` field. Dimension names will be derived from tag keys directly if this list is left empty. +- *Subscription Fallback Rules* - Whether or not to create rules for Subscription tags as a fallback for untagged Resource Groups. These rules have the lowest priority; rules created for Resource Group tags will always take precedence. +- *Lowercase Values* - Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail. + +## Policy Actions + +- Create/update rule-based dimensions + +## 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). + +- [**Azure Resource Manager Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_109256743_1124668) (*provider=azure_rm*) which has the following permissions: + - `Microsoft.Resources/subscriptions/resources/read` + - `Microsoft.Resources/subscriptions/providers/read` + - `Microsoft.Resources/tags/read` + +- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: + - `observer` + - `billing_center_viewer` + - `rule_based_dimensions_manager` + +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 + +- Azure + +## Cost + +This Policy Template does not launch any instances, and so does not incur any cloud costs. 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 new file mode 100644 index 0000000000..6373bd8a39 --- /dev/null +++ b/automation/azure/azure_rbd_from_rg_tag/azure_rbd_from_rg_tag.pt @@ -0,0 +1,418 @@ +name "Azure Rule-Based Dimension From Resource Group Tags" +rs_pt_ver 20180301 +type "policy" +short_description "Creates and/or updates individual Rule-Based Dimensions based on Azure Resource Group tags. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/azure/azure_rbd_from_rg_tag) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." +long_description "" +severity "low" +category "Cost" +default_frequency "daily" +info( + version: "1.3.1", + provider: "Flexera", + service: "Optima", + policy_set: "Automation", + publish: "false" +) + +############################################################################### +# Parameters +############################################################################### + +parameter "param_tag_list" do + type "list" + category "Policy Settings" + label "Tag Keys" + description "A list of Azure account tag keys to build Rule-Based Dimensions from" + # No default value, user input required +end + +parameter "param_name_list" do + type "list" + category "Policy Settings" + label "Dimension Names" + description "A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the 'Tag Keys' field. Dimension names will be derived from tag keys directly if this list is left empty." + default [] +end + +parameter "param_effective_date" do + type "string" + category "Policy Settings" + label "Effective Date" + description "Year/month you want rules to start applying in YYYY-MM format" + default "2010-01" +end + +parameter "param_subscription_fallback" do + type "string" + category "Policy Settings" + label "Subscription Fallback Rules" + description "Whether or not to create rules for Subscription tags as a fallback for untagged Resource Groups." + allowed_values "Yes", "No" + default "No" +end + +parameter "param_normalize_case" do + type "string" + category "Policy Settings" + label "Lowercase Values" + description "Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail." + allowed_values "Yes", "No" + default "Yes" +end + +############################################################################### +# Authentication +############################################################################### + +credentials "auth_azure" do + schemes "oauth2" + label "Azure" + description "Select the Azure Resource Manager Credential from the list." + tags "provider=azure_rm" +end + +credentials "auth_flexera" do + schemes "oauth2" + label "flexera" + description "Select FlexeraOne OAuth2 credential." + tags "provider=flexera" +end + +############################################################################### +# Pagination +############################################################################### + +pagination "pagination_azure" do + get_page_marker do + body_path "nextLink" + end + set_page_marker do + uri true + end +end + +############################################################################### +# Datasources & Scripts +############################################################################### + +# 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 + +datasource "ds_existing_rbds" do + request do + auth $auth_flexera + host rs_optima_host + path join(["/bill-analysis/orgs/", rs_org_id, "/settings/rule_based_dimensions"]) + header "Api-Version", "1.0" + header "content-type", "application/json" + end + result do + encoding "json" + collect jmes_path(response, "rule_based_dimensions") do + field "id", jmes_path(col_item, "id") + field "name", jmes_path(col_item, "name") + field "dated_rules", jmes_path(col_item, "dated_rules") + end + end +end + +datasource "ds_azure_subscriptions" do + request do + auth $auth_azure + pagination $pagination_azure + host "management.azure.com" + path "/subscriptions/" + query "api-version", "2018-06-01" + header "User-Agent", "RS Policies" + # Ignore status 400, 403, and 404 which can be returned in certain (legacy) types of Azure Subscriptions + ignore_status [400, 403, 404] + end + result do + encoding "json" + collect jmes_path(response, "value[*]") do + field "id", jmes_path(col_item, "subscriptionId") + field "name", jmes_path(col_item, "displayName") + end + end +end + +datasource "ds_azure_subscriptions_with_tags" do + iterate $ds_azure_subscriptions + request do + auth $auth_azure + pagination $pagination_azure + host "management.azure.com" + path join(["/subscriptions/", val(iter_item, "id"), "/providers/Microsoft.Resources/tags/default"]) + query "api-version", "2021-04-01" + header "User-Agent", "RS Policies" + ignore_status [400, 403, 404] + end + result do + encoding "json" + field "id", val(iter_item, "id") + field "name", val(iter_item, "name") + field "tags", jmes_path(response, "properties.tags") + end +end + +datasource "ds_azure_subscriptions_normalized" do + run_script $js_azure_subscriptions_normalized, $ds_azure_subscriptions_with_tags +end + +script "js_azure_subscriptions_normalized", type: "javascript" do + parameters "accounts" + result "result" + code <<-'EOS' + result = [] + + _.each(accounts, function(account) { + tags = {} + + if (account['tags'] != null && account['tags'] != undefined) { + _.each(Object.keys(account['tags']), function(key) { + normalized_key = key.toLowerCase().trim() + tags[normalized_key] = account['tags'][key] + }) + } + + result.push({ + id: account['id'], + name: account['name'], + tags: tags + }) + }) +EOS +end + +datasource "ds_azure_resource_groups" do + iterate $ds_azure_subscriptions + request do + auth $auth_azure + pagination $pagination_azure + host "management.azure.com" + path join(["/subscriptions/", val(iter_item, "id"), "/resourcegroups"]) + query "api-version", "2021-04-01" + header "User-Agent", "RS Policies" + ignore_status [400, 403, 404] + end + result do + encoding "json" + collect jmes_path(response, "value[*]") do + field "subscriptionId", val(iter_item, "id") + field "subscriptionName", val(iter_item, "name") + field "id", jmes_path(col_item, "id") + field "name", jmes_path(col_item, "name") + field "tags", jmes_path(col_item, "tags") + end + end +end + +datasource "ds_azure_resource_groups_normalized" do + run_script $js_azure_resource_groups_normalized, $ds_azure_resource_groups +end + +script "js_azure_resource_groups_normalized", type: "javascript" do + parameters "rgs" + result "result" + code <<-'EOS' + result = [] + + _.each(rgs, function(rg) { + tags = {} + + if (rg['tags'] != null && rg['tags'] != undefined) { + _.each(Object.keys(rg['tags']), function(key) { + normalized_key = key.toLowerCase().trim() + tags[normalized_key] = rg['tags'][key] + }) + } + + result.push({ + subscriptionId: rg['subscriptionId'], + subscriptionName: rg['subscriptionName'], + id: rg['id'], + name: rg['name'], + tags: tags + }) + }) +EOS +end + +datasource "ds_rbds" do + run_script $js_rbds, $ds_azure_resource_groups_normalized, $ds_azure_subscriptions_normalized, $ds_existing_rbds, $param_name_list, $param_tag_list, $param_effective_date, $param_subscription_fallback, $param_normalize_case +end + +script "js_rbds", type: "javascript" do + parameters "rgs", "accounts", "existing_rbds", "param_name_list", "param_tag_list", "param_effective_date", "param_subscription_fallback", "param_normalize_case" + result "result" + code <<-'EOS' + result = [] + + rbd_id_list = _.pluck(existing_rbds, 'id') + + _.each(param_tag_list, function(tag, index) { + rbd_name = tag.replace('.', ' ').replace('-', ' ') + rbd_name = rbd_name.replace(/\W/g, " ").trim() + + // Use user-specified name instead of user provided one + if (param_tag_list.length == param_name_list.length) { rbd_name = param_name_list[index] } + + rbd_id = "rbd_" + rbd_name.toLowerCase().replace(/\s+/g, '').replace(/\W/g, "").replace('.', '').replace('-', '').trim() + + tag_key = tag.toLowerCase().trim() + verb = "POST" + if (_.contains(rbd_id_list, rbd_id)) { verb = "PATCH" } + + 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() } + + 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().toLowerCase() }, + { type: "dimension_equals", dimension: "vendor_account", value: rg['subscriptionId'].trim().toLowerCase() } + ] + }, + value: { text: value } + }) + } + } + } + } + }) + + 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 (rules.length > 0) { + result.push({ + id: rbd_id, + name: rbd_name, + verb: verb, + effective_at: param_effective_date, + rules: rules + }) + } + }) +EOS +end + +datasource "ds_create_rbds" do + iterate $ds_rbds + request do + run_script $js_create_rbds, val(iter_item, "id"), val(iter_item, "verb"), val(iter_item, "name"), val($ds_flexera_api_hosts, "flexera"), rs_org_id + end + result do + encoding "text" + end +end + +script "js_create_rbds", type: "javascript" do + parameters "rbd_id", "verb", "name", "api_host", "rs_org_id" + result "request" + code <<-EOS + var request = { + auth: "auth_flexera", + verb: verb, + host: api_host, + path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id].join(''), + body_fields: { name: name } + } +EOS +end + +datasource "ds_apply_rbds" do + iterate $ds_rbds + request do + # ds_create_rbds is a parameter to ensure that it executes before ds_apply_rbds does + run_script $js_apply_rbds, val(iter_item, "id"), val(iter_item, "effective_at"), val(iter_item, "rules"), val($ds_flexera_api_hosts, "flexera"), $ds_create_rbds, rs_org_id + end + result do + encoding "text" + end +end + +script "js_apply_rbds", type: "javascript" do + parameters "rbd_id", "effective_at", "rules", "api_host", "ds_create_rbds", "rs_org_id" + result "request" + code <<-EOS + var request = { + auth: "auth_flexera", + verb: "PUT", + host: api_host, + path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id, "/rules/", effective_at].join(''), + body_fields: { rules: rules } + } +EOS +end + +############################################################################### +# Policy +############################################################################### + +policy "pol_rbds" do + validate $ds_apply_rbds do + summary_template "RBDs Generated & Applied" + detail_template "" + check eq(0, 0) + end +end diff --git a/automation/azure/azure_rbd_from_tag/CHANGELOG.md b/automation/azure/azure_rbd_from_tag/CHANGELOG.md new file mode 100644 index 0000000000..b2354a4856 --- /dev/null +++ b/automation/azure/azure_rbd_from_tag/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +## v1.3.1 + +- Fixed issue that would sometimes cause execution to fail if an Azure Subscription had no tag keys + +## v1.3.0 + +- Added option to retain original casing of tag values instead of normalizing them all to lowercase + +## v1.2.1 + +- Updated policy template to use newer API endpoints. Functionality is unchanged. + +## v1.2 + +- Fixed error where policy would fail completely when trying to access resources credential does not have access to. Policy will now simply skip these resources. + +## v1.1 + +- added ability to specify names for the newly created dimensions + +## v1.0 + +- initial release diff --git a/automation/azure/azure_rbd_from_tag/README.md b/automation/azure/azure_rbd_from_tag/README.md new file mode 100644 index 0000000000..270f1ef5ef --- /dev/null +++ b/automation/azure/azure_rbd_from_tag/README.md @@ -0,0 +1,42 @@ +# Azure Rule-Based Dimension From Subscription Tags + +## What It Does + +This policy creates and updates custom Rule-Based Dimensions that surface the specified Azure Subscription tag keys in the Flexera One platform. This allows costs to be sliced by the values of the tag keys in question. + +## Input Parameters + +This policy has the following input parameters required when launching the policy. + +- *Effective Date* - The month and year in YYYY-MM format that you want the rules to apply. This should be left at its default value in most cases to ensure that the rules apply to all costs, including historical costs. +- *Tag Keys* - A list of Azure Subscription tag keys to create custom Rule-Based Dimensions for. +- *Dimension Names* - A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the `Tag Keys` field. Dimension names will be derived from tag keys directly if this list is left empty. +- *Lowercase Values* - Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail. + +## Policy Actions + +- Create/update rule-based dimensions + +## 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). + +- [**Azure Resource Manager Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_109256743_1124668) (*provider=azure_rm*) which has the following permissions: + - `Microsoft.Resources/subscriptions/resources/read` + - `Microsoft.Resources/subscriptions/providers/read` + - `Microsoft.Resources/tags/read` + +- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: + - `observer` + - `billing_center_viewer` + - `rule_based_dimensions_manager` + +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 + +- Azure + +## Cost + +This Policy Template does not launch any instances, and so does not incur any cloud costs. 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 new file mode 100644 index 0000000000..467c1700d0 --- /dev/null +++ b/automation/azure/azure_rbd_from_tag/azure_rbd_from_tag.pt @@ -0,0 +1,318 @@ +name "Azure Rule-Based Dimension From Subscription Tags" +rs_pt_ver 20180301 +type "policy" +short_description "Creates and/or updates individual Rule-Based Dimensions based on Azure Subscription tags. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/azure/azure_rbd_from_tag) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." +long_description "" +severity "low" +category "Cost" +default_frequency "daily" +info( + version: "1.3.1", + provider: "Flexera", + service: "Optima", + policy_set: "Automation", + publish: "false" +) + +############################################################################### +# Parameters +############################################################################### + +parameter "param_tag_list" do + type "list" + category "Policy Settings" + label "Tag Keys" + description "A list of Azure account tag keys to build Rule-Based Dimensions from" + # No default value, user input required +end + +parameter "param_name_list" do + type "list" + category "Policy Settings" + label "Dimension Names" + description "A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the tag keys in the 'Tag Keys' field. Dimension names will be derived from tag keys directly if this list is left empty." + default [] +end + +parameter "param_effective_date" do + type "string" + category "Policy Settings" + label "Effective Date" + description "Year/month you want rules to start applying in YYYY-MM format" + default "2010-01" +end + +parameter "param_normalize_case" do + type "string" + category "Policy Settings" + label "Lowercase Values" + description "Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail." + allowed_values "Yes", "No" + default "Yes" +end + +############################################################################### +# Authentication +############################################################################### + +credentials "auth_azure" do + schemes "oauth2" + label "Azure" + description "Select the Azure Resource Manager Credential from the list." + tags "provider=azure_rm" +end + +credentials "auth_flexera" do + schemes "oauth2" + label "flexera" + description "Select FlexeraOne OAuth2 credential." + tags "provider=flexera" +end + +############################################################################### +# Pagination +############################################################################### + +pagination "pagination_azure" do + get_page_marker do + body_path "nextLink" + end + set_page_marker do + uri true + end +end + +############################################################################### +# Datasources & Scripts +############################################################################### + +# 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 + +datasource "ds_existing_rbds" do + request do + auth $auth_flexera + host rs_optima_host + path join(["/bill-analysis/orgs/", rs_org_id, "/settings/rule_based_dimensions"]) + header "Api-Version", "1.0" + header "content-type", "application/json" + end + result do + encoding "json" + collect jmes_path(response, "rule_based_dimensions") do + field "id", jmes_path(col_item, "id") + field "name", jmes_path(col_item, "name") + field "dated_rules", jmes_path(col_item, "dated_rules") + end + end +end + +datasource "ds_azure_subscriptions_without_tags" do + request do + auth $auth_azure + pagination $pagination_azure + host "management.azure.com" + path "/subscriptions/" + query "api-version", "2018-06-01" + header "User-Agent", "RS Policies" + # Ignore status 400, 403, and 404 which can be returned in certain (legacy) types of Azure Subscriptions + ignore_status [400, 403, 404] + end + result do + encoding "json" + collect jmes_path(response, "value[*]") do + field "id", jmes_path(col_item, "subscriptionId") + field "name", jmes_path(col_item, "displayName") + end + end +end + +datasource "ds_azure_subscriptions" do + iterate $ds_azure_subscriptions_without_tags + request do + auth $auth_azure + pagination $pagination_azure + host "management.azure.com" + path join(["/subscriptions/", val(iter_item, "id"), "/providers/Microsoft.Resources/tags/default"]) + query "api-version", "2021-04-01" + header "User-Agent", "RS Policies" + ignore_status [400, 403, 404] + end + result do + encoding "json" + field "id", val(iter_item, "id") + field "name", val(iter_item, "name") + field "tags", jmes_path(response, "properties.tags") + end +end + +datasource "ds_azure_subscriptions_normalized" do + run_script $js_azure_subscriptions_normalized, $ds_azure_subscriptions +end + +script "js_azure_subscriptions_normalized", type: "javascript" do + parameters "accounts" + result "result" + code <<-'EOS' + result = [] + + _.each(accounts, function(account) { + tags = {} + + if (account['tags'] != null && account['tags'] != undefined) { + _.each(Object.keys(account['tags']), function(key) { + normalized_key = key.toLowerCase().trim() + tags[normalized_key] = account['tags'][key] + }) + } + + result.push({ + id: account['id'], + name: account['name'], + tags: tags + }) + }) +EOS +end + +datasource "ds_rbds" do + run_script $js_rbds, $ds_azure_subscriptions_normalized, $ds_existing_rbds, $param_name_list, $param_tag_list, $param_effective_date, $param_normalize_case +end + +script "js_rbds", type: "javascript" do + parameters "accounts", "existing_rbds", "param_name_list", "param_tag_list", "param_effective_date", "param_normalize_case" + result "result" + code <<-'EOS' + result = [] + + rbd_id_list = _.pluck(existing_rbds, 'id') + + _.each(param_tag_list, function(tag, index) { + rbd_name = tag.replace('.', ' ').replace('-', ' ') + rbd_name = rbd_name.replace(/\W/g, " ").trim() + + // Use user-specified name instead of user provided one + if (param_tag_list.length == param_name_list.length) { rbd_name = param_name_list[index] } + + rbd_id = "rbd_" + rbd_name.toLowerCase().replace(/\s+/g, '').replace(/\W/g, "").replace('.', '').replace('-', '').trim() + + tag_key = tag.toLowerCase().trim() + verb = "POST" + if (_.contains(rbd_id_list, rbd_id)) { verb = "PATCH" } + + 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 (rules.length > 0) { + result.push({ + id: rbd_id, + name: rbd_name, + verb: verb, + effective_at: param_effective_date, + rules: rules + }) + } + }) +EOS +end + +datasource "ds_create_rbds" do + iterate $ds_rbds + request do + run_script $js_create_rbds, val(iter_item, "id"), val(iter_item, "verb"), val(iter_item, "name"), val($ds_flexera_api_hosts, "flexera"), rs_org_id + end + result do + encoding "text" + end +end + +script "js_create_rbds", type: "javascript" do + parameters "rbd_id", "verb", "name", "api_host", "rs_org_id" + result "request" + code <<-EOS + var request = { + auth: "auth_flexera", + verb: verb, + host: api_host, + path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id].join(''), + body_fields: { name: name } + } +EOS +end + +datasource "ds_apply_rbds" do + iterate $ds_rbds + request do + # ds_create_rbds is a parameter to ensure that it executes before ds_apply_rbds does + run_script $js_apply_rbds, val(iter_item, "id"), val(iter_item, "effective_at"), val(iter_item, "rules"), val($ds_flexera_api_hosts, "flexera"), $ds_create_rbds, rs_org_id + end + result do + encoding "text" + end +end + +script "js_apply_rbds", type: "javascript" do + parameters "rbd_id", "effective_at", "rules", "api_host", "ds_create_rbds", "rs_org_id" + result "request" + code <<-EOS + var request = { + auth: "auth_flexera", + verb: "PUT", + host: api_host, + path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id, "/rules/", effective_at].join(''), + body_fields: { rules: rules } + } +EOS +end + +############################################################################### +# Policy +############################################################################### + +policy "pol_rbds" do + validate $ds_apply_rbds do + summary_template "RBDs Generated & Applied" + detail_template "" + check eq(0, 0) + end +end diff --git a/automation/flexera/delete_all_billing_centers/CHANGELOG.md b/automation/flexera/delete_all_billing_centers/CHANGELOG.md new file mode 100644 index 0000000000..3a41ea7330 --- /dev/null +++ b/automation/flexera/delete_all_billing_centers/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## v1.1 + +- Updated policy metadata to make it more clear what Flexera service the policy is for + +## v1.0 + +- initial release diff --git a/automation/flexera/delete_all_billing_centers/README.md b/automation/flexera/delete_all_billing_centers/README.md new file mode 100644 index 0000000000..45be0de250 --- /dev/null +++ b/automation/flexera/delete_all_billing_centers/README.md @@ -0,0 +1,34 @@ +# Flexera CCO Delete All Billing Centers + +## What It Does + +This policy deletes all Billing Centers in the Flexera organization it is executed within. The policy will automatically self-terminate the second time it runs to avoid accidental future deletion of Billing Centers. + +## How It Works + +- During execution, the policy determines if this is the second time it has run since it was applied by comparing the applied policy's `created_at` date/time with the current date/time. If the difference between the two is greater than 1 minute, it is assumed the policy is running a second time. +- If this is the policy's second time running, the policy self-terminates before completing execution as a failsafe in case the user has forgotten to terminate the policy after usage. +- An incident is always raised, with the `Self Terminate` field in the incident table being true if this is the policy's second time executing since it was applied. +- Cloud Workflow is automatically kicked off. + - If this is the policy's second time executing, but for some reason the policy has failed to self-terminate, no action will be taken. + - If this is the policy's first time executing, all of the Billing Centers in the Flexera organization are deleted. + +## 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). + +### Credential configuration + +- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: + - `billing_center_admin` + - `policy_manager` + +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 + +- Flexera + +## Cost + +This Policy Template does not incur any cloud costs. diff --git a/automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt b/automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt new file mode 100644 index 0000000000..c39455a663 --- /dev/null +++ b/automation/flexera/delete_all_billing_centers/delete_all_billing_centers.pt @@ -0,0 +1,233 @@ +name "Flexera CCO Delete All Billing Centers" +rs_pt_ver 20180301 +type "policy" +short_description "Deletes all Billing Centers in the Flexera organization. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/flexera/delete_all_billing_centers) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." +long_description "" +severity "high" +category "Cost" +default_frequency "15 minutes" +info( + version: "1.1", + provider: "Flexera", + service: "Cloud Cost Optimization", + policy_set: "Automation", + publish: "false" +) + +############################################################################### +# Parameters +############################################################################### + +############################################################################### +# Authentication +############################################################################### + +credentials "auth_flexera" do + schemes "oauth2" + label "Flexera" + description "Select FlexeraOne OAuth2 credential." + tags "provider=flexera" +end + +############################################################################### +# Datasources & Scripts +############################################################################### + +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 + +datasource "ds_second_execution" do + run_script $js_second_execution, $ds_applied_policy +end + +script "js_second_execution", type: "javascript" do + parameters "ds_applied_policy" + result "result" + code <<-'EOS' + result = [{ id: ds_applied_policy['href'], terminate: false }] + + now = new Date() + created_at = new Date(ds_applied_policy['created_at']) + + // If applied policy was created over 1 minute ago, assume this is the second time it is running + if (now - created_at > 60000) { result[0]['terminate'] = true } +EOS +end + +datasource "ds_self_terminate_boolean" do + run_script $js_self_terminate_boolean, $ds_second_execution +end + +script "js_self_terminate_boolean", type: "javascript" do + parameters "ds_second_execution" + result "result" + code <<-'EOS' + result = [] + + if (ds_second_execution[0]['terminate']) { result = ds_second_execution } +EOS +end + +datasource "ds_self_terminate" do + iterate $ds_self_terminate_boolean + request do + auth $auth_flexera + host rs_governance_host + verb "DELETE" + path val(iter_item, 'id') + header "Api-Version", "1.0" + end +end + +datasource "ds_incident" do + run_script $js_incident, $ds_second_execution, $ds_self_terminate +end + +script "js_incident", type: "javascript" do + parameters "ds_second_execution", "ds_self_terminate" + result "result" + code <<-'EOS' + result = ds_second_execution +EOS +end + +############################################################################### +# Policy +############################################################################### + +policy "pol_delete_bcs" do + validate_each $ds_incident do + summary_template "Deleting Billing Centers" + check eq(0, 1) + escalate $esc_delete_billing_centers + export do + resource_level true + field "id" do + label "Policy HREF" + end + field "terminate" do + label "Self Terminate" + end + end + end +end + +############################################################################### +# Escalations +############################################################################### + +escalation "esc_delete_billing_centers" do + automatic true + label "Delete Billing Centers" + description "Approval to delete all billing centers" + run "delete_billing_centers", data, rs_org_id, rs_optima_host +end + +############################################################################### +# Cloud Workflow +############################################################################### + +define delete_billing_centers($data, $rs_org_id, $rs_optima_host) return $all_responses do + $$all_responses = [] + + # Policy will only run if it isn't self-terminating + if $data[0]["terminate"] == false + call list_childless_billing_centers($rs_org_id, $rs_optima_host) retrieve $billing_centers + + while size($billing_centers) > 0 do + foreach $billing_center in $billing_centers do + sub on_error: handle_error() do + call delete_billing_center($billing_center, $rs_org_id, $rs_optima_host) retrieve $delete_response + end + end + + sleep(10) + + call list_childless_billing_centers($rs_org_id, $rs_optima_host) retrieve $billing_centers + end + end +end + +define list_childless_billing_centers($rs_org_id, $rs_optima_host) return $billing_centers do + $billing_centers = [] + + task_label("Listing All Billing Centers") + + $response = http_request( + auth: $$auth_flexera, + https: true, + verb: "get", + href: "/analytics/orgs/" + $rs_org_id + "/billing_centers", + host: $rs_optima_host, + headers: { + "Api-Version": "1.0", + "User-Agent": "RS Policies" + } + ) + + task_label("Listing All Billing Centers response: " + to_json($response)) + $$all_responses << to_json({"req": "GET /billing_centers", "resp": $response}) + + if $response["code"] != 204 && $response["code"] != 202 && $response["code"] != 200 + raise "Unexpected response Listing All Billing Centers: " + to_json($response) + else + task_label("Listing All Billing Centers successful") + + $parent_ids = [] + + foreach $item in $response["body"] do + if $item["name"] != "Unallocated" && $item["parent_id"] != null && $item["parent_id"] != "" + $parent_ids << $item["parent_id"] + end + end + + foreach $item in $response["body"] do + if $item["name"] != "Unallocated" && contains?($parent_ids, [ $item["id"] ]) == false + $billing_centers << $item + end + end + end +end + +define delete_billing_center($billing_center, $rs_org_id, $rs_optima_host) return $response do + $bc_label = $billing_center["name"] + " (" + $billing_center["id"] + ")" + + task_label("Delete Billing Center: " + $bc_label) + + $response = http_request( + auth: $$auth_flexera, + https: true, + verb: "delete", + href: "/analytics/orgs/" + $rs_org_id + "/billing_centers/" + $billing_center["id"], + host: $rs_optima_host, + headers: { + "Api-Version": "1.0", + "User-Agent": "RS Policies" + } + ) + + task_label("Delete Billing Center response: " + $bc_label + " " + to_json($response)) + $$all_responses << to_json({"req": "DELETE /billing_centers/" + $billing_center["id"], "resp": $response}) + + if $response["code"] != 204 && $response["code"] != 202 && $response["code"] != 200 + raise "Unexpected response Deleting Billing Centers: " + $bc_label + " " + to_json($response) + else + task_label("Delete Billing Center successful: " + $bc_label) + 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 diff --git a/automation/flexera/outdated_applied_policies/CHANGELOG.md b/automation/flexera/outdated_applied_policies/CHANGELOG.md new file mode 100644 index 0000000000..85a84d011f --- /dev/null +++ b/automation/flexera/outdated_applied_policies/CHANGELOG.md @@ -0,0 +1,14 @@ +# 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 + +## v0.1 + +- initial release diff --git a/automation/flexera/outdated_applied_policies/README.md b/automation/flexera/outdated_applied_policies/README.md new file mode 100644 index 0000000000..8a91fe0a0d --- /dev/null +++ b/automation/flexera/outdated_applied_policies/README.md @@ -0,0 +1,71 @@ +# Flexera Automation Outdated Applied Policies + +## What It Does + +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: + +- This policy itself. +- Policies applied from a source other than the Flexera Automation Catalog. +- Organization-specific policies published to that organization's own catalog. +- Flexera policies present in the [policy-templates Github Repository](https://github.com/flexera-public/policy_templates) but not published in the Flexera Automation Catalog, such as meta policies and other misc. utility policies. +- Policy aggregates applied across multiple projects. Aggregates applied only to the project this policy is applied in will still be included in the results and are actionable. + +## How It Works + +The list of outdated 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 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, 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 + +- *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). + +### Credential Configuration + +- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: + - `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. + +## Supported Clouds + +- Flexera + +## Cost + +This Policy Template does not incur any cloud costs. Cloud costs may be incurred by the applied policies that this policy reports on and updates. Please consult the README of each policy for more information. diff --git a/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt b/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt new file mode 100644 index 0000000000..367dee8444 --- /dev/null +++ b/automation/flexera/outdated_applied_policies/outdated_applied_policies.pt @@ -0,0 +1,835 @@ +name "Flexera Automation Outdated Applied Policies" +rs_pt_ver 20180301 +type "policy" +short_description "Reports any applied policies in Flexera Automation that are not using the latest version of that policy from the catalog and, optionally, updates them. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/flexera/outdated_applied_policies) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." +long_description "" +severity "low" +category "Operational" +default_frequency "weekly" +info( + version: "0.2.0", + provider: "Flexera", + service: "Automation", + policy_set: "Automation" +) + +############################################################################### +# 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_ignore_list" do + type "list" + category "Filters" + label "Policy Ignore List" + description "A list of applied policy names and/or IDs to ignore and not report on. Leave blank to assess all applied policies." + 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)" + allowed_values ["Update Applied Policies"] + default [] +end + +############################################################################### +# Authentication +############################################################################### + +credentials "auth_flexera" do + schemes "oauth2" + label "Flexera" + description "Select Flexera One OAuth2 credentials" + tags "provider=flexera" +end + +############################################################################### +# Datasources & Scripts +############################################################################### + +datasource "ds_self_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 + +datasource "ds_applied_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" + end + result do + encoding "json" + collect jmes_path(response, "items[*]") do + field "id", jmes_path(col_item, "id") + field "href", jmes_path(col_item, "href") + field "name", jmes_path(col_item, "name") + field "description", jmes_path(col_item, "description") + field "category", jmes_path(col_item, "category") + field "created_at", jmes_path(col_item, "created_at") + field "created_by", jmes_path(col_item, "created_by.email") + field "frequency", jmes_path(col_item, "frequency") + field "category", jmes_path(col_item, "category") + field "credentials", jmes_path(col_item, "credentials") + field "options", jmes_path(col_item, "options") + field "severity", jmes_path(col_item, "severity") + field "skip_approvals", jmes_path(col_item, "skip_approvals") + field "scope", jmes_path(col_item, "scope") + field "dry_run", jmes_path(col_item, "dry_run") + field "log_level", jmes_path(col_item, "log_level") + field "version", jmes_path(col_item, "info.version") + field "policy_template", jmes_path(col_item, "policy_template") + field "published_template", jmes_path(col_item, "published_template") + end + end +end + +datasource "ds_policy_aggregates" do + request do + auth $auth_flexera + host rs_governance_host + path join(["/api/governance/orgs/", rs_org_id, "/policy_aggregates"]) + header "Api-Version", "1.0" + end + result do + encoding "json" + collect jmes_path(response, "items[*]") do + field "id", jmes_path(col_item, "id") + field "href", jmes_path(col_item, "href") + field "name", jmes_path(col_item, "name") + field "description", jmes_path(col_item, "description") + field "category", jmes_path(col_item, "category") + field "created_at", jmes_path(col_item, "created_at") + field "created_by", jmes_path(col_item, "created_by.email") + field "frequency", jmes_path(col_item, "frequency") + field "category", jmes_path(col_item, "category") + field "credentials", jmes_path(col_item, "credentials") + field "options", jmes_path(col_item, "options") + field "severity", jmes_path(col_item, "severity") + field "skip_approvals", jmes_path(col_item, "skip_approvals") + field "dry_run", jmes_path(col_item, "dry_run") + field "running_project_ids", jmes_path(col_item, "running_project_ids") + field "published_template", jmes_path(col_item, "published_template") + end + end +end + +datasource "ds_catalog_policies" do + request do + verb "GET" + host "raw.githubusercontent.com" + path "/flexera-public/policy_templates/master/data/active_policy_list/active_policy_list.json" + header "User-Agent", "RS Policies" + end + result do + encoding "json" + collect jmes_path(response, "policies[*]") do + field "name", jmes_path(col_item, "name") + field "file_name", jmes_path(col_item, "file_name") + field "version", jmes_path(col_item, "version") + field "change_log", jmes_path(col_item, "change_log") + field "description", jmes_path(col_item, "description") + field "category", jmes_path(col_item, "category") + field "severity", jmes_path(col_item, "severity") + field "readme", jmes_path(col_item, "readme") + 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, $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", "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 outdated policies + if (!reject_policy && param_report_filter == "Report Deprecated Only") { + reject_policy = true + } + + return reject_policy + }) + + combined_data = _.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'] + + rec_verb = "Update" + + if (policy['version'].split('.')[0] != catalog_policy['version'].split('.')[0]) { + rec_verb = "Manually update" + } + + recommendationDetails = [ + rec_verb, " applied policy ", policy['name'], " (", policy['id'], ") ", + "from version ", policy['version'], " to version ", catalog_policy['version'] + ].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: '' + } + }) + + result = _.filter(combined_data, function(policy) { + return policy['version'] != policy['catalog_version'] && typeof(policy['catalog_version']) == 'string' && policy['href'] != null + }) + + if (result.length > 0) { + total_applied_policies = ds_applied_policies.length.toString() + total_outdated = result.length.toString() + outdated_percentage = (total_outdated / total_applied_policies * 100).toFixed(2).toString() + '%' + + pol_noun = "policies" + if (total_applied_policies == 1) { pol_noun = "policy" } + + pol_verb = "are outdated" + if (total_outdated == 1) { pol_verb = "is outdated" } + + pol_action = "recommended to be replaced with the latest version from the Catalog" + + message = [ + "Out of ", total_applied_policies, " ", pol_noun, " analyzed, ", + total_outdated, " (", outdated_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 + +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 +############################################################################### + +policy "pol_outdated_policies" do + validate_each $ds_outdated_policies do + summary_template "{{ with index data 0 }}{{ .self_policy_name }}{{ end }}: {{ len data }} Outdated Policies Found" + detail_template "{{ with index data 0 }}{{ .message }}{{ end }}" + check eq(val(item, "id"), "") + escalate $esc_email + escalate $esc_update_policies + 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 + 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 + 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 +end + +############################################################################### +# Escalations +############################################################################### + +escalation "esc_email" do + automatic true + label "Send Email" + description "Send incident email" + email $param_email +end + +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, $param_allow_major, rs_governance_host, rs_project_id +end + +############################################################################### +# Cloud Workflow +############################################################################### + +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] || $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 + call delete_policy($policy, $rs_governance_host) retrieve $delete_response + end + else + $policy_name = $policy["name_without_link"] + " (" + $policy["id"] + ")" + raise "Applied Policy " + $policy_name + " was not updated due to a major version change. Please update manually." + end + end + end + + if inspect($$errors) != "null" + raise join($$errors, "\n") + end +end + +define apply_policy($policy, $rs_governance_host, $rs_project_id) return $response, $code do + $host = $rs_governance_host + $href = "/api/governance/projects/" + $rs_project_id + "/applied_policies" + $url = $host + $href + task_label("POST " + $url) + + $response = http_request( + auth: $$auth_flexera, + host: $host, + href: $href, + https: true, + verb: "post", + headers: { "Api-Version": "1.0" }, + body: $policy["update_body"] + ) + + $code = $response["code"] + $policy_name = $policy["catalog_name"] + " (" + $policy["catalog_id"] + ")" + + task_label("Apply Catalog Policy response: " + $policy_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 applying Catalog Policy: " + $policy_name + " " + to_json($response) + else + task_label("Apply Catalog Policy successful: " + $policy_name) + end +end + +define delete_policy($policy, $rs_governance_host) return $response do + $host = $rs_governance_host + $href = $policy['href'] + $url = $host + $href + task_label("DELETE " + $url) + + $response = http_request( + auth: $$auth_flexera, + host: $host, + href: $href, + https: true, + verb: "delete", + headers: { "Api-Version": "1.0" } + ) + + $policy_name = $policy["name_without_link"] + " (" + $policy["id"] + ")" + + task_label("Delete Applied Policy response: " + $policy_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 deleting Applied Policy: " + $policy_name + " " + to_json($response) + else + task_label("Delete Applied Policy successful: " + $policy_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 diff --git a/automation/google/google_rbd_from_label/CHANGELOG.md b/automation/google/google_rbd_from_label/CHANGELOG.md new file mode 100644 index 0000000000..26beb076b9 --- /dev/null +++ b/automation/google/google_rbd_from_label/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +## v1.3.1 + +- Fixed issue that would sometimes cause execution to fail if a Google Project had no label keys + +## v1.3.0 + +- Added option to retain original casing of tag values instead of normalizing them all to lowercase + +## v1.2.1 + +- Updated policy template to use newer API endpoints. Functionality is unchanged. + +## v1.2 + +- fixed link to README in policy description + +## v1.1 + +- added ability to specify names for the newly created dimensions +- fixed issue where labels were incorrectly referred to as tags in parameter name + +## v1.0 + +- initial release diff --git a/automation/google/google_rbd_from_label/README.md b/automation/google/google_rbd_from_label/README.md new file mode 100644 index 0000000000..16ec257e2c --- /dev/null +++ b/automation/google/google_rbd_from_label/README.md @@ -0,0 +1,40 @@ +# Google Rule-Based Dimension From Project Labels + +## What It Does + +This policy creates and updates custom Rule-Based Dimensions that surface the specified Google Project label keys in the Flexera One platform. This allows costs to be sliced by the values of the label keys in question. + +## Input Parameters + +This policy has the following input parameters required when launching the policy. + +- *Effective Date* - The month and year in YYYY-MM format that you want the rules to apply. This should be left at its default value in most cases to ensure that the rules apply to all costs, including historical costs. +- *Label Keys* - A list of Google Project label keys to create custom Rule-Based Dimensions for. +- *Dimension Names* - A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the label keys in the `Label Keys` field. Dimension names will be derived from label keys directly if this list is left empty. +- *Lowercase Values* - Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail. + +## Policy Actions + +- Create/update rule-based dimensions + +## 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). + +- [**Google Cloud Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm#automationadmin_4083446696_1121577) (*provider=gce*) which has the following: + - `resourcemanager.projects.get` + +- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (*provider=flexera*) which has the following roles: + - `observer` + - `billing_center_viewer` + - `rule_based_dimensions_manager` + +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 + +- Google + +## Cost + +This Policy Template does not launch any instances, and so does not incur any cloud costs. 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 new file mode 100644 index 0000000000..93be95a9b0 --- /dev/null +++ b/automation/google/google_rbd_from_label/google_rbd_from_label.pt @@ -0,0 +1,305 @@ +name "Google Rule-Based Dimension From Project Labels" +rs_pt_ver 20180301 +type "policy" +short_description "Creates and/or updates individual Rule-Based Dimensions based on Google Project labels. See the [README](https://github.com/flexera-public/policy_templates/tree/master/automation/google/google_rbd_from_label) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." +long_description "" +severity "low" +category "Cost" +default_frequency "daily" +info( + version: "1.3.1", + provider: "Flexera", + service: "Optima", + policy_set: "Automation", + publish: "false" +) + +############################################################################### +# Parameters +############################################################################### + +parameter "param_tag_list" do + type "list" + category "Policy Settings" + label "Label Keys" + description "A list of Google account label keys to build Rule-Based Dimensions from" + # No default value, user input required +end + +parameter "param_name_list" do + type "list" + category "Policy Settings" + label "Dimension Names" + description "A list of names to give the Rule-Based Dimensions in the Flexera platform. Enter names in the same order as the label keys in the 'Label Keys' field. Dimension names will be derived from label keys directly if this list is left empty." + default [] +end + +parameter "param_effective_date" do + type "string" + category "Policy Settings" + label "Effective Date" + description "Year/month you want rules to start applying in YYYY-MM format" + default "2010-01" +end + +parameter "param_normalize_case" do + type "string" + category "Policy Settings" + label "Lowercase Values" + description "Whether or not to normalize all values by converting them to lowercase. Note that, if the same value appears multiple times with different casing, and this option is disabled, the rule-based dimension will be rejected and this policy template will fail." + allowed_values "Yes", "No" + default "Yes" +end + +############################################################################### +# Authentication +############################################################################### + +credentials "auth_google" do + schemes "oauth2" + label "Google" + description "Select the Google Cloud Credential from the list." + tags "provider=gce" +end + +credentials "auth_flexera" do + schemes "oauth2" + label "flexera" + description "Select FlexeraOne OAuth2 credential." + tags "provider=flexera" +end + +############################################################################### +# Pagination +############################################################################### + +pagination "pagination_google" do + get_page_marker do + body_path "nextPageToken" + end + set_page_marker do + query "pageToken" + end +end + +############################################################################### +# Datasources & Scripts +############################################################################### + +# 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 + +datasource "ds_existing_rbds" do + request do + auth $auth_flexera + host rs_optima_host + path join(["/bill-analysis/orgs/", rs_org_id, "/settings/rule_based_dimensions"]) + header "Api-Version", "1.0" + header "content-type", "application/json" + end + result do + encoding "json" + collect jmes_path(response, "rule_based_dimensions") do + field "id", jmes_path(col_item, "id") + field "name", jmes_path(col_item, "name") + field "dated_rules", jmes_path(col_item, "dated_rules") + end + end +end + +datasource "ds_google_projects" do + request do + auth $auth_google + pagination $pagination_google + host "cloudresourcemanager.googleapis.com" + path "/v1/projects/" + query "filter", "lifecycleState=ACTIVE" + end + result do + encoding "json" + collect jmes_path(response, "projects[*]") do + field "id", jmes_path(col_item, "projectId") + field "number", jmes_path(col_item, "projectNumber") + field "name", jmes_path(col_item, "name") + field "parent", jmes_path(col_item, "parent") + field "createTime", jmes_path(col_item, "createTime") + field "lifecycleState", jmes_path(col_item, "lifecycleState") + field "tags", jmes_path(col_item, "labels") + end + end +end + +datasource "ds_google_projects_normalized" do + run_script $js_google_projects_normalized, $ds_google_projects +end + +script "js_google_projects_normalized", type: "javascript" do + parameters "accounts" + result "result" + code <<-'EOS' + result = [] + + _.each(accounts, function(account) { + tags = {} + + if (account['tags'] != null && account['tags'] != undefined) { + _.each(Object.keys(account['tags']), function(key) { + normalized_key = key.toLowerCase().trim() + tags[normalized_key] = account['tags'][key] + }) + } + + result.push({ + id: account['id'], + number: account['number'], + name: account['name'], + parent: account['parent'], + createTime: account['createTime'], + lifecycleState: account['lifecycleState'], + tags: tags + }) + }) +EOS +end + +datasource "ds_rbds" do + run_script $js_rbds, $ds_google_projects_normalized, $ds_existing_rbds, $param_name_list, $param_tag_list, $param_effective_date, $param_normalize_case +end + +script "js_rbds", type: "javascript" do + parameters "accounts", "existing_rbds", "param_name_list", "param_tag_list", "param_effective_date", "param_normalize_case" + result "result" + code <<-'EOS' + result = [] + + rbd_id_list = _.pluck(existing_rbds, 'id') + + _.each(param_tag_list, function(tag, index) { + rbd_name = tag.replace('.', ' ').replace('-', ' ') + rbd_name = rbd_name.replace(/\W/g, " ").trim() + + // Use user-specified name instead of user provided one + if (param_tag_list.length == param_name_list.length) { rbd_name = param_name_list[index] } + + rbd_id = "rbd_" + rbd_name.toLowerCase().replace(/\s+/g, '').replace(/\W/g, "").replace('.', '').replace('-', '').trim() + + tag_key = tag.toLowerCase().trim() + verb = "POST" + if (_.contains(rbd_id_list, rbd_id)) { verb = "PATCH" } + + 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 (rules.length > 0) { + result.push({ + id: rbd_id, + name: rbd_name, + verb: verb, + effective_at: param_effective_date, + rules: rules + }) + } + }) +EOS +end + +datasource "ds_create_rbds" do + iterate $ds_rbds + request do + run_script $js_create_rbds, val(iter_item, "id"), val(iter_item, "verb"), val(iter_item, "name"), val($ds_flexera_api_hosts, "flexera"), rs_org_id + end + result do + encoding "text" + end +end + +script "js_create_rbds", type: "javascript" do + parameters "rbd_id", "verb", "name", "api_host", "rs_org_id" + result "request" + code <<-EOS + var request = { + auth: "auth_flexera", + verb: verb, + host: api_host, + path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id].join(''), + body_fields: { name: name } + } +EOS +end + +datasource "ds_apply_rbds" do + iterate $ds_rbds + request do + # ds_create_rbds is a parameter to ensure that it executes before ds_apply_rbds does + run_script $js_apply_rbds, val(iter_item, "id"), val(iter_item, "effective_at"), val(iter_item, "rules"), val($ds_flexera_api_hosts, "flexera"), $ds_create_rbds, rs_org_id + end + result do + encoding "text" + end +end + +script "js_apply_rbds", type: "javascript" do + parameters "rbd_id", "effective_at", "rules", "api_host", "ds_create_rbds", "rs_org_id" + result "request" + code <<-EOS + var request = { + auth: "auth_flexera", + verb: "PUT", + host: api_host, + path: ["/finops-customizations/v1/orgs/", rs_org_id, "/rule-based-dimensions/", rbd_id, "/rules/", effective_at].join(''), + body_fields: { rules: rules } + } +EOS +end + +############################################################################### +# Policy +############################################################################### + +policy "pol_rbds" do + validate $ds_apply_rbds do + summary_template "RBDs Generated & Applied" + detail_template "" + check eq(0, 0) + end +end From dc7737423b2932e2a0215e2e2d75b7090eb86518 Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Wed, 16 Oct 2024 11:06:25 -0500 Subject: [PATCH 10/11] update --- cost/aws/rightsize_rds_instances/CHANGELOG.md | 4 + cost/aws/rightsize_rds_instances/README.md | 1 + .../aws_rightsize_rds_instances.pt | 202 +++++++++--------- 3 files changed, 110 insertions(+), 97 deletions(-) diff --git a/cost/aws/rightsize_rds_instances/CHANGELOG.md b/cost/aws/rightsize_rds_instances/CHANGELOG.md index 706c87e05a..d9044a0ab5 100644 --- a/cost/aws/rightsize_rds_instances/CHANGELOG.md +++ b/cost/aws/rightsize_rds_instances/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v5.5.0 + +- Added support for downsizing multiple sizes where appropriate + ## v5.4.2 - Minor code improvements to conform with current standards. Functionality unchanged. diff --git a/cost/aws/rightsize_rds_instances/README.md b/cost/aws/rightsize_rds_instances/README.md index ebd1b7b025..520e1f1c48 100644 --- a/cost/aws/rightsize_rds_instances/README.md +++ b/cost/aws/rightsize_rds_instances/README.md @@ -41,6 +41,7 @@ This policy has the following input parameters required when launching the polic - `Key=~/Regex/` - Filter all resources where the value for the specified key matches the specified regex string. - `Key!~/Regex/` - Filter all resources where the value for the specified key does not match the specified regex string. This will also filter all resources missing the specified tag key. - *Exclusion Tags: Any / All* - Whether to filter instances containing any of the specified tags or only those that contain all of them. Only applicable if more than one value is entered in the `Exclusion Tags` field. +- *Skip Instance Sizes* - Whether to recommend downsizing multiple sizes. When set to 'No', only the next smaller size will ever be recommended for downsizing. When set to 'Yes', more aggressive downsizing recommendations will be made when appropriate. - *Report Unused or Underutilized* - Whether to report on unused instances, underutilized instances, or both. If both are selected, unused instances will not appear in the list of underutilized instances regardless of CPU usage. - *Underutilized Instance CPU Threshold (%)* - The CPU threshold at which to consider an instance to be underutilized and therefore be flagged for downsizing. - *Statistic Lookback Period* - How many days back to look at statistical data for instances to determine if they are underutilized or unused. This value cannot be set higher than 90 because AWS does not retain metrics for longer than 90 days. diff --git a/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances.pt b/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances.pt index a737465c7a..f9accb5d16 100644 --- a/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances.pt +++ b/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances.pt @@ -7,7 +7,7 @@ severity "low" category "Cost" default_frequency "weekly" info( - version: "5.4.2", + version: "5.5.0", provider: "AWS", service: "RDS", policy_set: "Rightsize Database Instances", @@ -43,6 +43,15 @@ parameter "param_min_savings" do default 0 end +parameter "param_downsize_multiple" do + type "string" + category "Policy Settings" + label "Skip Instance Sizes" + description "Whether to recommend downsizing multiple sizes. When set to 'No', only the next smaller size will ever be recommended for downsizing. When set to 'Yes', more aggressive downsizing recommendations will be made when appropriate." + allowed_values "Yes", "No" + default "No" +end + parameter "param_regions_allow_or_deny" do type "string" category "Filters" @@ -1164,11 +1173,11 @@ EOS end datasource "ds_rds_nonidle_instances_with_metrics" do - run_script $js_rds_nonidle_instances_with_metrics, $ds_rds_nonidle_instances, $ds_cloudwatch_underutil_cpu_sorted, $ds_cloudwatch_underutil_mem_sorted, $ds_cloudwatch_underutil_netin_sorted, $ds_cloudwatch_underutil_netout_sorted, $ds_instance_costs_grouped, $ds_aws_instance_size_map + run_script $js_rds_nonidle_instances_with_metrics, $ds_rds_nonidle_instances, $ds_cloudwatch_underutil_cpu_sorted, $ds_cloudwatch_underutil_mem_sorted, $ds_cloudwatch_underutil_netin_sorted, $ds_cloudwatch_underutil_netout_sorted end script "js_rds_nonidle_instances_with_metrics", type: "javascript" do - parameters "ds_rds_nonidle_instances", "ds_cloudwatch_underutil_cpu_sorted", "ds_cloudwatch_underutil_mem_sorted", "ds_cloudwatch_underutil_netin_sorted", "ds_cloudwatch_underutil_netout_sorted", "ds_instance_costs_grouped", "ds_aws_instance_size_map" + parameters "ds_rds_nonidle_instances", "ds_cloudwatch_underutil_cpu_sorted", "ds_cloudwatch_underutil_mem_sorted", "ds_cloudwatch_underutil_netin_sorted", "ds_cloudwatch_underutil_netout_sorted" result "result" code <<-EOS // Merge original instance list with cloudwatch data into a single list @@ -1176,134 +1185,133 @@ script "js_rds_nonidle_instances_with_metrics", type: "javascript" do _.each(ds_rds_nonidle_instances, function(instance) { region = instance['region'] - id = instance['instanceId'] cloudwatch_name = instance['name'].toLowerCase() - resourceType = instance['resourceType'] - newResourceType = null - - if (typeof(ds_aws_instance_size_map[resourceType]) == 'object') { - newResourceType = ds_aws_instance_size_map[resourceType]["down"] - } // Only proceed if the CloudWatch data actually has the region and instance id. // Otherwise, we have no usage data on the instance and thus dont include it in the results. - if (ds_cloudwatch_underutil_cpu_sorted[region] != undefined) { - if (ds_cloudwatch_underutil_cpu_sorted[region][cloudwatch_name] != undefined) { - instance_cpu_metrics = ds_cloudwatch_underutil_cpu_sorted[region][cloudwatch_name] - instance_mem_metrics = ds_cloudwatch_underutil_mem_sorted[region][cloudwatch_name] - instance_netin_metrics = ds_cloudwatch_underutil_netin_sorted[region][cloudwatch_name] - instance_netout_metrics = ds_cloudwatch_underutil_netout_sorted[region][cloudwatch_name] - - savings = 0.0 - - if (typeof(newResourceType) == 'string') { - cost_name = "db:" + instance['name'].toLowerCase() - cost = 0.0 + if (ds_cloudwatch_underutil_cpu_sorted[region] && ds_cloudwatch_underutil_cpu_sorted[region][cloudwatch_name]) { + instance_cpu_metrics = ds_cloudwatch_underutil_cpu_sorted[region][cloudwatch_name] + instance_mem_metrics = ds_cloudwatch_underutil_mem_sorted[region][cloudwatch_name] + instance_netin_metrics = ds_cloudwatch_underutil_netin_sorted[region][cloudwatch_name] + instance_netout_metrics = ds_cloudwatch_underutil_netout_sorted[region][cloudwatch_name] + + // Create object we're going to return + merged_instance = { + instanceId: instance['instanceId'], + instanceArn: instance['instanceArn'], + resourceType: instance['resourceType'], + name: instance['name'], + status: instance['status'], + databaseEngine: instance['databaseEngine'], + engineVersion: instance['engineVersion'], + privateDnsName: instance['privateDnsName'], + region: instance['region'], + tags: instance['tags'], + availabilityZone: instance['availabilityZone'], + licenseModel: instance['licenseModel'], + processorFeatures: instance['processorFeatures'], + vcpus: instance['vcpus'] + } - if (typeof(ds_instance_costs_grouped[cost_name]) == 'number') { - cost = ds_instance_costs_grouped[cost_name] - } + // Grab usage data for the instance if it is present. + // Note: We don't simply name them the same as Cloudwatch does because + // prior versions of this policy also didn't, and we want to ensure + // that exported incident data looks the same as it used to. + cpu_stats_list = [ "Average", "Minimum", "Maximum", "p99", "p95", "p90" ] - cost_per_nfu = cost / ds_aws_instance_size_map[resourceType]['nfu'] + _.each(cpu_stats_list, function(stat) { + incident_statname = "cpu" + "_" + stat.toLowerCase() - new_cost = cost_per_nfu * ds_aws_instance_size_map[newResourceType]['nfu'] - savings = cost - new_cost - } + merged_instance[incident_statname] = null - // Create object we're going to return - merged_instance = { - instanceId: instance['instanceId'], - instanceArn: instance['instanceArn'], - resourceType: instance['resourceType'], - name: instance['name'], - status: instance['status'], - databaseEngine: instance['databaseEngine'], - engineVersion: instance['engineVersion'], - privateDnsName: instance['privateDnsName'], - region: instance['region'], - tags: instance['tags'], - availabilityZone: instance['availabilityZone'], - licenseModel: instance['licenseModel'], - processorFeatures: instance['processorFeatures'], - vcpus: instance['vcpus'], - savings: savings, - newResourceType: newResourceType + if (typeof(instance_cpu_metrics[stat]) == 'number') { + merged_instance[incident_statname] = Math.round(instance_cpu_metrics[stat] * 100) / 100 } + }) - // Grab usage data for the instance if it is present. - // Note: We don't simply name them the same as Cloudwatch does because - // prior versions of this policy also didn't, and we want to ensure - // that exported incident data looks the same as it used to. - cpu_stats_list = [ "Average", "Minimum", "Maximum", "p99", "p95", "p90" ] - - _.each(cpu_stats_list, function(stat) { - incident_statname = "cpu" + "_" + stat.toLowerCase() - - merged_instance[incident_statname] = null - - if (typeof(instance_cpu_metrics[stat]) == 'number') { - merged_instance[incident_statname] = Math.round(instance_cpu_metrics[stat] * 100) / 100 - } - }) - - mem_stats_list = [ "Average", "Minimum", "Maximum" ] + mem_stats_list = [ "Average", "Minimum", "Maximum" ] - _.each(mem_stats_list, function(stat) { - incident_statname = "mem" + "_" + stat.toLowerCase() + _.each(mem_stats_list, function(stat) { + incident_statname = "mem" + "_" + stat.toLowerCase() - merged_instance[incident_statname] = null + merged_instance[incident_statname] = null - if (typeof(instance_mem_metrics[stat]) == 'number') { - merged_instance[incident_statname] = Math.round(instance_mem_metrics[stat] / 1024 / 1024 * 100) / 100 - } - }) + if (typeof(instance_mem_metrics[stat]) == 'number') { + merged_instance[incident_statname] = Math.round(instance_mem_metrics[stat] / 1024 / 1024 * 100) / 100 + } + }) - netin_stats_list = [ "Average", "Minimum", "Maximum" ] + netin_stats_list = [ "Average", "Minimum", "Maximum" ] - _.each(netin_stats_list, function(stat) { - incident_statname = "netin" + "_" + stat.toLowerCase() + _.each(netin_stats_list, function(stat) { + incident_statname = "netin" + "_" + stat.toLowerCase() - merged_instance[incident_statname] = null + merged_instance[incident_statname] = null - if (typeof(instance_netin_metrics[stat]) == 'number') { - merged_instance[incident_statname] = Math.round(instance_netin_metrics[stat] * 100) / 100 - } - }) + if (typeof(instance_netin_metrics[stat]) == 'number') { + merged_instance[incident_statname] = Math.round(instance_netin_metrics[stat] * 100) / 100 + } + }) - netout_stats_list = [ "Average", "Minimum", "Maximum" ] + netout_stats_list = [ "Average", "Minimum", "Maximum" ] - _.each(netout_stats_list, function(stat) { - incident_statname = "netout" + "_" + stat.toLowerCase() + _.each(netout_stats_list, function(stat) { + incident_statname = "netout" + "_" + stat.toLowerCase() - merged_instance[incident_statname] = null + merged_instance[incident_statname] = null - if (typeof(instance_netout_metrics[stat]) == 'number') { - merged_instance[incident_statname] = Math.round(instance_netout_metrics[stat] * 100) / 100 - } - }) + if (typeof(instance_netout_metrics[stat]) == 'number') { + merged_instance[incident_statname] = Math.round(instance_netout_metrics[stat] * 100) / 100 + } + }) - // Send the instance information with the CloudWatch data into the final result. - // Also adds in the account ID and currency symbol since itll be needed for the incident. - result.push(merged_instance) - } + // Send the instance information with the CloudWatch data into the final result. + // Also adds in the account ID and currency symbol since itll be needed for the incident. + result.push(merged_instance) } }) EOS end datasource "ds_rds_underutil_instances" do - run_script $js_rds_underutil_instances, $ds_rds_nonidle_instances_with_metrics, $param_stats_threshold, $param_stats_underutil_threshold_cpu_value + run_script $js_rds_underutil_instances, $ds_rds_nonidle_instances_with_metrics, $ds_aws_instance_size_map, $ds_instance_costs_grouped, $param_stats_threshold, $param_stats_underutil_threshold_cpu_value, $param_downsize_multiple end script "js_rds_underutil_instances", type: "javascript" do - parameters "ds_rds_nonidle_instances_with_metrics", "param_stats_threshold", "param_stats_underutil_threshold_cpu_value" + parameters "ds_rds_nonidle_instances_with_metrics", "ds_aws_instance_size_map", "ds_instance_costs_grouped", "param_stats_threshold", "param_stats_underutil_threshold_cpu_value", "param_downsize_multiple" result "result" code <<-'EOS' - // Filter above list to just underutilized instances - result = _.filter(ds_rds_nonidle_instances_with_metrics, function(instance) { + result = [] + + _.each(ds_rds_nonidle_instances_with_metrics, function(instance) { cpu_value = instance['cpu_' + param_stats_threshold.toLowerCase()] - cpu_undertilized = cpu_value < param_stats_underutil_threshold_cpu_value || cpu_value == null - return cpu_undertilized && typeof(instance['newResourceType']) == 'string' + if (typeof(cpu_value) != 'number') { cpu_value = 0 } + + if (cpu_value < param_stats_underutil_threshold_cpu_value && ds_aws_instance_size_map[instance["resourceType"]]["down"]) { + newResourceType = ds_aws_instance_size_map[instance["resourceType"]]["down"] + + if (param_downsize_multiple == "Yes") { + while (ds_aws_instance_size_map[newResourceType]["down"] && cpu_value * 2 < param_stats_underutil_threshold_cpu_value) { + cpu_value = cpu_value * 2 + newResourceType = ds_aws_instance_size_map[newResourceType]['down'] + } + } + + cost_name = "db:" + instance['name'].toLowerCase() + savings = 0.0 + + cost = 0.0 + if (typeof(ds_instance_costs_grouped[cost_name]) == 'number') { cost = ds_instance_costs_grouped[cost_name] } + + cost_per_nfu = cost / ds_aws_instance_size_map[instance["resourceType"]]['nfu'] + + new_cost = cost_per_nfu * ds_aws_instance_size_map[newResourceType]['nfu'] + savings = cost - new_cost + + new_instance = { newResourceType: newResourceType, savings: savings } + _.each(_.keys(instance), function(key) { new_instance[key] = instance[key] }) + result.push(new_instance) + } }) EOS end From 866eb59bc774c753fc680ff089ab5a3c54bb9aac Mon Sep 17 00:00:00 2001 From: Shawn Huckabay Date: Wed, 16 Oct 2024 11:09:05 -0500 Subject: [PATCH 11/11] update --- .../aws_rightsize_rds_instances.pt | 8 ++++---- .../aws_rightsize_rds_instances_meta_parent.pt | 11 ++++++++++- .../master_policy_permissions_list.json | 2 +- .../master_policy_permissions_list.yaml | 2 +- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances.pt b/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances.pt index f9accb5d16..7650f90b1e 100644 --- a/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances.pt +++ b/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances.pt @@ -1482,7 +1482,7 @@ script "js_rds_idle_incident", type: "javascript" do licenseModel: instance['licenseModel'], processorFeatures: instance['processorFeatures'], vcpus: instance['vcpus'], - savings: parseFloat(savings.toFixed(3)), + savings: Math.round(savings * 1000) / 1000, savingsCurrency: ds_currency['symbol'], lookbackPeriod: param_stats_lookback, newResourceType: "Terminate RDS Instance", @@ -1523,7 +1523,7 @@ script "js_rds_idle_incident", type: "javascript" do savings_message = [ ds_currency['symbol'], ' ', - formatNumber(parseFloat(total_savings).toFixed(2), ds_currency['separator']) + formatNumber(Math.round(total_savings * 100) / 100, ds_currency['separator']) ].join('') // Sort by descending order of savings value @@ -1623,7 +1623,7 @@ script "js_rds_underutil_incident", type: "javascript" do licenseModel: instance['licenseModel'], processorFeatures: instance['processorFeatures'], vcpus: instance['vcpus'], - savings: parseFloat(savings.toFixed(3)), + savings: Math.round(savings * 1000) / 1000, savingsCurrency: ds_currency['symbol'], lookbackPeriod: param_stats_lookback, service: "AmazonRDS", @@ -1669,7 +1669,7 @@ script "js_rds_underutil_incident", type: "javascript" do savings_message = [ ds_currency['symbol'], ' ', - formatNumber(parseFloat(total_savings).toFixed(2), ds_currency['separator']) + formatNumber(Math.round(total_savings * 100) / 100, ds_currency['separator']) ].join('') // Sort by descending order of savings value diff --git a/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances_meta_parent.pt b/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances_meta_parent.pt index f83ed8c71f..4f415c6b26 100644 --- a/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances_meta_parent.pt +++ b/cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances_meta_parent.pt @@ -7,7 +7,7 @@ category "Meta" default_frequency "15 minutes" info( provider: "AWS", - version: "5.4.2", # 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 + version: "5.5.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 publish: "true", deprecated: "false" ) @@ -77,6 +77,15 @@ parameter "param_min_savings" do default 0 end +parameter "param_downsize_multiple" do + type "string" + category "Policy Settings" + label "Skip Instance Sizes" + description "Whether to recommend downsizing multiple sizes. When set to 'No', only the next smaller size will ever be recommended for downsizing. When set to 'Yes', more aggressive downsizing recommendations will be made when appropriate." + allowed_values "Yes", "No" + default "No" +end + parameter "param_regions_allow_or_deny" do type "string" category "Filters" diff --git a/data/policy_permissions_list/master_policy_permissions_list.json b/data/policy_permissions_list/master_policy_permissions_list.json index 9ca4ccf205..cbee206e26 100644 --- a/data/policy_permissions_list/master_policy_permissions_list.json +++ b/data/policy_permissions_list/master_policy_permissions_list.json @@ -2347,7 +2347,7 @@ { "id": "./cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances.pt", "name": "AWS Rightsize RDS Instances", - "version": "5.4.2", + "version": "5.5.0", "providers": [ { "name": "aws", diff --git a/data/policy_permissions_list/master_policy_permissions_list.yaml b/data/policy_permissions_list/master_policy_permissions_list.yaml index 5267a4b242..fa33725d77 100644 --- a/data/policy_permissions_list/master_policy_permissions_list.yaml +++ b/data/policy_permissions_list/master_policy_permissions_list.yaml @@ -1346,7 +1346,7 @@ required: true - id: "./cost/aws/rightsize_rds_instances/aws_rightsize_rds_instances.pt" name: AWS Rightsize RDS Instances - version: 5.4.2 + version: 5.5.0 :providers: - :name: aws :permissions: