diff --git a/lib/heimdall_tools/aws_config_mapper.rb b/lib/heimdall_tools/aws_config_mapper.rb index e80fb66..1c478c8 100644 --- a/lib/heimdall_tools/aws_config_mapper.rb +++ b/lib/heimdall_tools/aws_config_mapper.rb @@ -185,21 +185,26 @@ def fetch_all_compliance_info(config_rules) # # Returns: The same config_rules array with `results` key added to each rule. def add_results_to_config_rules(config_rules) + resource_name_map = {} config_rules.each do |rule| response = @client.get_compliance_details_by_config_rule(config_rule_name: rule[:config_rule_name], limit: 100) rule_results = response.to_h[:evaluation_results] while response.next_token - response = @client.get_compliance_details_by_config_rule(next_token: response.next_token, limit: 100) + response = @client.get_compliance_details_by_config_rule(config_rule_name: rule[:config_rule_name], next_token: response.next_token, limit: 100) rule_results += response.to_h[:evaluation_results] end + resource_name_map = get_resource_name_map(rule_results, resource_name_map) + rule[:results] = [] rule_results.each do |result| hdf_result = {} # code_desc - hdf_result['code_desc'] = result.dig(:evaluation_result_identifier, :evaluation_result_qualifier)&.map do |k, v| - "#{k}: #{v}" - end&.join(', ') + code_desc = result.dig(:evaluation_result_identifier, :evaluation_result_qualifier)&.map do |k, v| + "#{k}: #{v}" + end&.join(', ') || '' + code_desc += ", resource_name: #{resource_name_map[result.dig(:evaluation_result_identifier, :evaluation_result_qualifier, :resource_id)] || 'unknown'}" + hdf_result['code_desc'] = code_desc # start_time hdf_result['start_time'] = if result.key?(:config_rule_invoked_time) DateTime.parse(result[:config_rule_invoked_time].to_s).strftime('%Y-%m-%dT%H:%M:%S%:z') @@ -246,6 +251,48 @@ def add_results_to_config_rules(config_rules) config_rules end + ## + # Takes in `rule_results` from a `get_compliance_details_by_config_rule` API call, + # then makes `list_discovered_resources` API calls to get the `resource_name` for + # each resouce ID. + # + # Resource ID alone without resource name commonly makes it inconvenient to figure out + # what AWS resource that the rule is referencing in its results. + def get_resource_name_map(rule_results, current_resource_name_map) + # Should be in the format: { resource_id: resource_name } + resource_map = {} + + # Group by resource Type because the API call requires a type to be specified + groups = rule_results.group_by { |e| e.dig(:evaluation_result_identifier, :evaluation_result_qualifier, :resource_type) } + # Trim down values to just the ids + groups = groups.map do |resource_type, rule_results_arr| + [ + resource_type, + rule_results_arr.map { |result| result.dig(:evaluation_result_identifier, :evaluation_result_qualifier, :resource_id) }.uniq, + ] + end + # Reject any ids that are already in `current_resource_name_map` + groups = groups.map do |resource_type, resource_ids| + [ + resource_type, + resource_ids.reject { |id| current_resource_name_map.include? id }, + ] + end + + groups.each do |resource_type, resource_ids| + # API endpoint reports a max size of 20 + resource_ids.each_slice(20) do |resource_ids_slice| + # Don't send an API call for an empty array + next if resource_ids_slice.empty? + + response = @client.list_discovered_resources(resource_type: resource_type, resource_ids: resource_ids_slice, limit: 100) + resource_map = resource_map.merge(response.resource_identifiers.map { |resource| [resource.resource_id, resource.resource_name] }.to_h) + end + end + + current_resource_name_map.merge resource_map + end + ## # Takes in a config rule and pulls out tags that are useful for HDF. #