1
votes

Im working on a project that requires the comparison of values within two hashes. The hash key 'title' has an array as its value, with the title of two issues within it. What I intend to achieve, is to say 'if the title in issue_yaml exists in fwparse_issues, keep the title in issue_yaml (the array inside the hash), else delete it'.

Example code:

  fwparse_issues = [
  {:section_title=>"Security Audit",
  :ref=>"FILTER.LOG.DROP",
  :title=>"Filter Drop Rules Were Configured Without Logging"},
 {:section_title=>"Security Audit",
  :ref=>"LOGGING.SYSLOG.NO.ENCRYPTION",
  :title=>"Syslog Logging Configured With No Encryption"},
 {:section_title=>"Security Audit",
  :ref=>"FILTER.RULE.EENE",
  :title=>"Filter Rules Allow Packets To A Network Destination"},
 {:section_title=>"Security Audit",
  :ref=>"FILTER.RULE.EEER",
  :title=>"Filter Rules Allow Packets To A Port Range"},
 {:section_title=>"Security Audit",
  :ref=>"BANNER.NO.POST.LOGON.MESSAGE",
  :title=>"No Post Logon Banner Message"},
 {:section_title=>"Security Audit",
  :ref=>"LOGGING.SYSLOG.SEVERITY",
  :title=>"Weak Syslog Severity Level Configured"},
 {:section_title=>"Security Audit",
  :ref=>"FILTER.RULE.NEEE",
  :title=>"Filter Rules Allow Packets From A Network Source"}]

issue_yaml = {"ABC-1234"=>
  {"title"=>["No Pre-Logon Banner Message", "No Post Logon Banner Message"],
   "desc"=>"some text",
   "rec"=>"recommendations go here",
   "ref"=>"references"},
 "ABC-5678"=>
  {"title"=>"SSH Protocol Version 1 Supported",
   "desc"=>"some text\nwhich spans\nmultiple lines\n",
   "rec"=>"recommendations go here",
   "ref"=>"references"}}

    fwparse_issues.each do |issue|
      issue_yaml.keys.each do |key|
        if issue_yaml[key]["title"].is_a?(Array)
          unless issue_yaml[key]["title"].include?(issue[:title])
            issue_yaml[key]["title"].delete(issue[:title])
          end
        end 
      end
    end

What I needed to end up with was:

{"ABC-1234"=>
  **{"title"=>["No Post Logon Banner Message"],**
   "desc"=>"some text",
   "rec"=>"recommendations go here",
   "ref"=>"references"}}

But instead the bold line ends up being:

{"ABC-1234"=>
  {"title"=>["No Pre-Logon Banner Message", "No Post Logon Banner Message"],
   "desc"=>"some text",
   "rec"=>"recommendations go here",
   "ref"=>"references"},

In essence, the unless bit isn't working. One of those situations where I have spent so long looking at it I can't even think straight anymore. If I change the 'unless' to an 'if' it deletes "No Post Logon Banner Message" from issue_yaml so the reverse seems to work!

EDIT Corrected the expected output!

2
Why would the title SSH Protocol Version 1 Supported still remain if it doesn't exist in fwparse_issues? - Ho Man
It wont, which is kind of what the script is meant to do. It takes a user defined list of issues, and then looks for them in the fwparse_issues array of hashes. So in this use case, SSH Protocol Version 1 wont make the cut, and neither will "No Pre-Logon Banner Message". Its down to the user to add mappings for these issues. Sorry I wasn't clear on that :) - hatlord

2 Answers

0
votes

This should get you what you need.

valid_titles = fwparse_issues.map { |i| i[:title] }
issue_yaml.each do |k, v|
  v['title'] = [v['title']].flatten.select {|vt| valid_titles.include?(vt)}
end

EDIT: Changed it with in place answer instead.

Which yields:

{
  "ABC-1234"=>{"title"=>["No Post Logon Banner Message"], ...}, 
  "ABC-5678"=>{"title"=>[], ... "}
}
0
votes

Suppose fwparse_issues is as given in the example and issue_yaml is defined as follows.

issue_yaml =
  {"ABC-1234"=>
     {"title"=>["No Pre-Logon Banner Message",
                "No Post Logon Banner Message"],
      "desc"=>"some text",
      "rec"=>"recommendations go here",
      "ref"=>"references"},
   "ABC-5678"=>
     {"title"=>"SSH Protocol Version 1 Supported",
      "desc"=>"some text\nwhich spans\nmultiple lines\n",
      "rec"=>"recommendations go here",
      "ref"=>"references"},
   "ABC-5679"=>
     {"title"=>"Weak Syslog Severity Level Configured",
      "desc"=>"some text\nwhich spans\nmultiple lines\n",
      "rec"=>"recommendations go here",
      "ref"=>"references"},
   "ABC-1230"=>
     {"title"=>["No Post Logon Banner Message",
                "No Post Logon Banner Message"],
      "desc"=>"some text",
      "rec"=>"recommendations go here",
      "ref"=>"references"}
  }

We first construct an array of titles from fwparse_issues.

titles = fwparse_issues.map { |h| h[:title] }
  #=> ["Filter Drop Rules Were Configured Without Logging",
  #    "Syslog Logging Configured With No Encryption",
  #    "Filter Rules Allow Packets To A Network Destination",
  #    "Filter Rules Allow Packets To A Port Range",
  #    "No Post Logon Banner Message",
  #    "Weak Syslog Severity Level Configured",
  #    "Filter Rules Allow Packets From A Network Source"]

We can now construct the desired hash.

issue_yaml.each_with_object({}) do |(k,v),h|
  keepers = [*v["title"]].select { |s| titles.include?(s) }
  h[k] = v.merge("title"=>keepers.size==1 ? keepers.first : keepers) unless keepers.empty?
end
  #=> {"ABC-1234"=>{"title"=>"No Post Logon Banner Message",
  #                 "desc"=>"some text",
  #                 "rec"=>"recommendations go here",
  #                 "ref"=>"references"},
  #    "ABC-5679"=>{"title"=>"Weak Syslog Severity Level Configured",
  #                 "desc"=>"some text\nwhich spans\nmultiple lines\n",
  #                 "rec"=>"recommendations go here", "ref"=>"references"},
  #    "ABC-1230"=>{"title"=>["No Post Logon Banner Message",
  #                           "No Post Logon Banner Message"],
  #                 "desc"=>"some text",
  #                 "rec"=>"recommendations go here",
  #                 "ref"=>"references"}
  #   }

If s = v["title"] is a string, [*v["title"]] returns [s]. If arr = v["title"] is an array, [*v["title"]] returns v. For example, if v = "hi", [*v] #=> ['hi']; if v = ['hi', 'ho'], [*v["title"]] returns ['hi', 'ho'].

Let's examine more closely the calculations that are performed. We first create an enumerator.

enum = issue_yaml.each_with_object({})
  #=> enum=#<Enumerator:0x00000000e5be38>

We can inspect the values that will be generated by the enumerator enum by converting it to an array (or executing enum.entries).

enum.to_a
  #=> [[["ABC-1234", {"title"=>["No Pre-Logon Banner Message",
  #                             "No Post Logon Banner Message"],
  #                   "desc" =>"some text",
  #                   "rec"  =>"recommendations go here",
  #                   "ref"  =>"references"}], {}],
  #    [["ABC-5678", {"title"=>"SSH Protocol Version 1 Supported",
  #                   "desc" =>"some text\nwhich spans\nmultiple lines\n",
  #                   "rec"  =>"recommendations go here",
  #                   "ref"  =>"references"}], {}],
  #    [["ABC-5679", {"title"=>"Weak Syslog Severity Level Configured", 
  #                   "desc" =>"some text\nwhich spans\nmultiple lines\n",
  #                   "rec"  =>"recommendations go here",
  #                   "ref"=>"references"}], {}],
  #    [["ABC-1230", {"title"=>["No Post Logon Banner Message",
  #                             "No Post Logon Banner Message"],
  #                   "desc"=>"some text",
  #                   "rec"=>"recommendations go here",
  #                   "ref"=>"references"}], {}]]

enum generates a sequence of elements that are passed to the block and assigned to the block variables. The first element generated and passed to the block is the following.

(k,v), h = enum.next
  #=> [["ABC-1234", {"title"=>["No Pre-Logon Banner Message",
  #                            "No Post Logon Banner Message"],
  #                  "desc"=>"some text",
  #                  "rec" =>"recommendations go here",
  #                  "ref" =>"references"}], {}]

Ruby disambiguates this array and assigns values to the block variables.

k #=> ABC-1234
v #=> {"title"=>["No Pre-Logon Banner Message",
  #              "No Post Logon Banner Message"],
  #    "desc"=>"some text", 
  #    "rec" =>"recommendations go here",
  #    "ref"=>"references"}
h #=> {}

We may now perform the block calculation.

a = v["title"]
  #=> ["No Pre-Logon Banner Message", "No Post Logon Banner Message"]
b = [*a]
  #=> ["No Pre-Logon Banner Message", "No Post Logon Banner Message"]
keepers = b.select { |s| titles.include?(s) }
  #=> ["No Post Logon Banner Message"]
keepers.empty?
  #=> false
keepers.size == 1,
  #=> true
c = keepers.first
  #=> No Post Logon Banner Message
h[k] = v.merge("title"=>c)
  #=> {"title"=>"No Post Logon Banner Message",
  #    "desc"=>"some text",
  #    "rec"=>"recommendations go here", "ref"=>"references"}
h #=> {"ABC-1234"=>{"title"=>"No Post Logon Banner Message",
  #                 "desc" =>"some texte",
  #                 "ref"=>"references"}}

The remaining calculations are similar.

It may be more convenient to make all values of the key "titles" arrays (of strings), even when those arrays contain only a single string.