diff --git a/Dangerfile b/Dangerfile index f409dccea0..f0af2fb7d3 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,9 +1,36 @@ +# DangerFile +# https://danger.systems/reference.html + require 'uri' require 'yaml' - require_relative 'tools/lib/policy_parser' -# DangerFile -# https://danger.systems/reference.html + +# Method for finding code blocks that are missing necessary fields +def block_missing_field?(policy_code, block_name, field_name) + in_block = false + present = false + + policy_code.each_line do |line| + # Check if we're entering the block + if line.strip.start_with?(block_name + ' ') && line.strip.end_with?('do') + in_block = true + present = false + end + + # Check for the field if we're in a block + present = true if in_block && line.strip.start_with?(field_name + ' ') + + # When we reach the end of a block, check if field was present + if line.strip == 'end' && in_block + return true unless present + in_block = false + end + end + + # If we've gone through all lines without returning, no block is missing the field + false +end + # get list of old names that were renamed renamed_files = (git.renamed_files.collect{|r| r[:before]}) # get list of all files changes minus the old files renamed @@ -86,6 +113,45 @@ changed_files.each do |file| end end +# check for name field. +#only check .pt files +has_app_changes.each do |file| + pp.parse(file) + name = pp.parsed_name + if ! name + fail "Please add a name field. #{file}" + end + if name && name == "" + fail "Please add a value other than an empty string to the name field. #{file}" + end +end + +# check for short_description field. +#only check .pt files +has_app_changes.each do |file| + pp.parse(file) + short_description = pp.parsed_short_description + if ! short_description + fail "Please add a short_description field. #{file}" + end + if short_description && short_description == "" + fail "Please add a value other than an empty string to the short_description field. #{file}" + end +end + +# check for long_description field. +#only check .pt files +has_app_changes.each do |file| + pp.parse(file) + long_description = pp.parsed_long_description + if ! long_description + fail "Please add a long_description field with an empty string as its value. #{file}" + end + if long_description && long_description != "" + fail "Please make the long_description field an empty string. #{file}" + end +end + # check for valid category values. # must be one of the following categories # when adding a new category update the Rakefile generate_policy_list task and @@ -114,6 +180,49 @@ has_app_changes.each do |file| end end +# check for valid default_frequency values. +# must be one of the following default_frequency +frequencies = [ + '15 minutes', + 'hourly', + 'daily', + 'weekly', + 'monthly' +].sort +#only check .pt files +has_app_changes.each do |file| + pp.parse(file) + default_frequency = pp.parsed_default_frequency + if ! default_frequency + fail "Please add a default_frequency field. #{file}" + end + # check default_frequency meets the expected list + if default_frequency && !frequencies.include?(default_frequency) + fail "The default_frequency is not valid: #{default_frequency}. Valid frequencies include #{frequencies.join(", ")}" + end +end + +# check for valid severity values. +# must be one of the following severity +severities = [ + 'low', + 'medium', + 'high', + 'critical' +].sort +#only check .pt files +has_app_changes.each do |file| + pp.parse(file) + severity = pp.parsed_severity + if ! severity + fail "Please add a severity field. #{file}" + end + # check default_frequency meets the expected list + if severity && !severities.include?(severity) + fail "The severity is not valid: #{severity}. Valid severities include #{severities.join(", ")}" + end +end + # check markdown of .md files with markdown lint # .md files should follow these rules https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md mdl = nil @@ -150,23 +259,6 @@ has_app_changes.each do |file| end end -# check for default_frequency -# only check .pt files -frequencies = [ - '15 minutes', - 'hourly', - 'daily', - 'weekly', - 'monthly' -].sort -has_app_changes.each do |file| - pp.parse(file) - default_frequency = pp.parsed_default_frequency - if ! default_frequency - fail "Please add a 'default_frequency' property field and value. #{file}" - end -end - # check for info field required fields has_app_changes.each do |file| # get info field data @@ -225,3 +317,131 @@ has_app_changes.each do |file| end end end + +# check policy code directly for issues +has_app_changes.each do |file| + if file.end_with?(".pt") && !file.end_with?("_meta_parent.pt") + file_contents = File.read(file) + + # Regex to test whether particular kinds of code blocks exist + # We don't have to check for the entire block because fpt will generate an error if the block is not valid + param_regex = /^parameter\s+"[^"]*"\s+do$/ + auth_regex = /^credentials\s+"[^"]*"\s+do$/ + pagination_regex = /^pagination\s+"[^"]*"\s+do$/ + datasource_regex = /^datasource\s+"[^"]*"\s+do$/ + policy_regex = /^policy\s+"[^"]*"\s+do$/ + escalation_regex = /^escalation\s+"[^"]*"\s+do$/ + cwf_regex = /^define\s+\w+\(\s*([$]\w+\s*,\s*)*([$]\w+\s*)?\)\s*(return\s+([$]\w+\s*,\s*)*([$]\w+\s*)?)?do$/ + permission_regex = /^permission\s+"[^"]*"\s+do$/ + resources_regex = /^resources\s+"[^"]*",\s+type:\s+"[^"]*"\s+do$/ + + # Regex to test whether the policy section comments exist + param_comment_regex = /^\#{79}\n# Parameters\n\#{79}$/ + auth_comment_regex = /^\#{79}\n# Authentication\n\#{79}$/ + pagination_comment_regex = /^\#{79}\n# Pagination\n\#{79}$/ + datasource_comment_regex = /^\#{79}\n# Datasources & Scripts\n\#{79}$/ + policy_comment_regex = /^\#{79}\n# Policy\n\#{79}$/ + escalation_comment_regex = /^\#{79}\n# Escalations\n\#{79}$/ + cwf_comment_regex = /^\#{79}\n# Cloud Workflow\n\#{79}$/ + + # Report on invalid/deprecated code blocks + if permission_regex.match?(file_contents) + warn("Policy Template file `#{file}` has deprecated `permission` code blocks. It is recommended that the policy be refactored to no longer use these code blocks.") + end + + if resources_regex.match?(file_contents) + warn("Policy Template file `#{file}` has deprecated `resources` code blocks. It is recommended that the policy be refactored to no longer use these code blocks.") + end + + # Report on missing policy section comments + hash_string = "###############################################################################" + + if param_regex.match?(file_contents) && !param_comment_regex.match?(file_contents) + fail "Policy Template file `#{file}` does **not** have a comment indicating where the Parameters begin. Please add a comment like the below before the parameters blocks:\n\n#{hash_string}
\# Parameters
#{hash_string}" + end + + if auth_regex.match?(file_contents) && !auth_comment_regex.match?(file_contents) + fail "Policy Template file `#{file}` does **not** have a comment indicating where the Authentication begins. Please add a comment like the below before the credentials blocks:\n\n#{hash_string}
\# Authentication
#{hash_string}" + end + + if pagination_regex.match?(file_contents) && !pagination_comment_regex.match?(file_contents) + fail "Policy Template file `#{file}` does **not** have a comment indicating where the Pagination begins. Please add a comment like the below before the pagination blocks:\n\n#{hash_string}
\# Pagination
#{hash_string}" + end + + if !datasource_comment_regex.match?(file_contents) + fail "Policy Template file `#{file}` does **not** have a comment indicating where the Datasources & Scripts begin. Please add a comment like the below before the datasources blocks:\n\n#{hash_string}
\# Datasources & Scripts
#{hash_string}" + end + + if !policy_comment_regex.match?(file_contents) + fail "Policy Template file `#{file}` does **not** have a comment indicating where the Policy begins. Please add a comment like the below before the policy block:\n\n#{hash_string}
\# Policy
#{hash_string}" + end + + if escalation_regex.match?(file_contents) && !escalation_comment_regex.match?(file_contents) + fail "Policy Template file `#{file}` does **not** have a comment indicating where the Escalations begin. Please add a comment like the below before the escalation blocks:\n\n#{hash_string}
\# Escalations
#{hash_string}" + end + + if cwf_regex.match?(file_contents) && !cwf_comment_regex.match?(file_contents) + fail "Policy Template file `#{file}` does **not** have a comment indicating where the Cloud Workflow begins. Please add a comment like the below before the cloud workflow blocks:\n\n#{hash_string}
\# Cloud Workflow
#{hash_string}" + end + + # Regex to test whether code blocks have invalid names + # These should come back 'true' for invalidly named code blocks + param_name_regex = /^parameter\s+"(?!param_[^"]+")[^"]*"\s+do$/ + auth_name_regex = /^credentials\s+"(?!auth_[^"]+")[^"]*"\s+do$/ + pagination_name_regex = /^pagination\s+"(?!pagination_[^"]+")[^"]*"\s+do$/ + datasource_name_regex = /^datasource\s+"(?!ds_[^"]+")[^"]*"\s+do$/ + script_name_regex = /^script\s+"(?!js_[^"]+)([^"]*)",\s+type:\s+"javascript"\s+do$/ + policy_name_regex = /^policy\s+"(?!pol_[^"]+")[^"]*"\s+do$/ + escalation_name_regex = /^escalation\s+"(?!esc_[^"]+")[^"]*"\s+do$/ + + # Report on invalidly named code blocks + if param_name_regex.match?(file_contents) + fail "Policy Template file `#{file}` has invalidly named parameter blocks. Please ensure all parameter blocks have names that begin with param_" + end + + if auth_name_regex.match?(file_contents) + fail "Policy Template file `#{file}` has invalidly named credentials blocks. Please ensure all credentials blocks have names that begin with auth_" + end + + if pagination_name_regex.match?(file_contents) + fail "Policy Template file `#{file}` has invalidly named pagination blocks. Please ensure all pagination blocks have names that begin with pagination_" + end + + if datasource_name_regex.match?(file_contents) + fail "Policy Template file `#{file}` has invalidly named datasource blocks. Please ensure all datasource blocks have names that begin with ds_" + end + + if script_name_regex.match?(file_contents) + fail "Policy Template file `#{file}` has invalidly named script blocks. Please ensure all script blocks have names that begin with js_" + end + + if policy_name_regex.match?(file_contents) + fail "Policy Template file `#{file}` has an invalidly named policy block. Please ensure the policy block has a name that begins with pol_" + end + + if escalation_name_regex.match?(file_contents) + fail "Policy Template file `#{file}` has invalidly named escalation blocks. Please ensure all escalation blocks have names that begin with esc_" + end + + # Report on missing fields in code blocks + fields_to_check = [ + { block: "parameter", fields: ["type", "category", "label", "description"] }, + { block: "credentials", fields: ["schemes", "tags", "label", "description"] }, + { block: "escalation", fields: ["automatic", "label", "description"] } + ] + + fields_to_check.each do |item| + item[:fields].each do |field| + if block_missing_field?(file_contents, item[:block], field) + fail "Policy Template file `#{file}` has #{item[:block]} block that is missing the #{field} field. Please add this field to all #{item[:block]} blocks" + end + end + end + + # Raise warning, not error, if parameter block is missing a default field. + # This is because there are occasionally legitimate reasons to not have a default + if block_missing_field?(file_contents, "parameter", "default") + warn("Policy Template file `#{file}` has parameter block that is missing the default field. It is recommended that every parameter have a default value unless user input for that parameter is required and too specific for any default value to make sense") + end + end +end