inspec-core 5.18.14 → 5.22.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +19 -17
  3. data/inspec-core.gemspec +22 -22
  4. data/lib/inspec/base_cli.rb +19 -17
  5. data/lib/inspec/cli.rb +27 -25
  6. data/lib/inspec/dependencies/dependency_set.rb +2 -2
  7. data/lib/inspec/dsl.rb +9 -5
  8. data/lib/inspec/enhanced_outcomes.rb +19 -0
  9. data/lib/inspec/env_printer.rb +1 -1
  10. data/lib/inspec/exceptions.rb +2 -0
  11. data/lib/inspec/formatters/base.rb +69 -16
  12. data/lib/inspec/plugin/v2/loader.rb +19 -8
  13. data/lib/inspec/plugin/v2/plugin_types/reporter.rb +1 -0
  14. data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +54 -0
  15. data/lib/inspec/profile.rb +9 -8
  16. data/lib/inspec/reporters/base.rb +1 -0
  17. data/lib/inspec/reporters/cli.rb +94 -3
  18. data/lib/inspec/reporters/json.rb +3 -1
  19. data/lib/inspec/reporters/yaml.rb +3 -1
  20. data/lib/inspec/reporters.rb +2 -1
  21. data/lib/inspec/resources/file.rb +1 -1
  22. data/lib/inspec/resources/http.rb +5 -5
  23. data/lib/inspec/resources/lxc.rb +65 -9
  24. data/lib/inspec/resources/mongodb_session.rb +5 -0
  25. data/lib/inspec/resources/nftables.rb +251 -0
  26. data/lib/inspec/resources/oracledb_session.rb +13 -4
  27. data/lib/inspec/resources/podman.rb +353 -0
  28. data/lib/inspec/resources/podman_container.rb +84 -0
  29. data/lib/inspec/resources/podman_image.rb +108 -0
  30. data/lib/inspec/resources/podman_network.rb +81 -0
  31. data/lib/inspec/resources/podman_pod.rb +101 -0
  32. data/lib/inspec/resources/podman_volume.rb +87 -0
  33. data/lib/inspec/resources/postgres_session.rb +2 -1
  34. data/lib/inspec/resources/service.rb +1 -1
  35. data/lib/inspec/resources.rb +1 -0
  36. data/lib/inspec/rule.rb +54 -17
  37. data/lib/inspec/run_data/control.rb +6 -0
  38. data/lib/inspec/run_data/statistics.rb +8 -2
  39. data/lib/inspec/runner.rb +18 -8
  40. data/lib/inspec/runner_rspec.rb +3 -2
  41. data/lib/inspec/schema/exec_json.rb +78 -2
  42. data/lib/inspec/schema/output_schema.rb +4 -1
  43. data/lib/inspec/schema/profile_json.rb +46 -0
  44. data/lib/inspec/schema.rb +91 -0
  45. data/lib/inspec/utils/convert.rb +8 -0
  46. data/lib/inspec/utils/podman.rb +24 -0
  47. data/lib/inspec/utils/simpleconfig.rb +10 -2
  48. data/lib/inspec/utils/waivers/csv_file_reader.rb +34 -0
  49. data/lib/inspec/utils/waivers/excel_file_reader.rb +39 -0
  50. data/lib/inspec/utils/waivers/json_file_reader.rb +15 -0
  51. data/lib/inspec/version.rb +1 -1
  52. data/lib/inspec/waiver_file_reader.rb +61 -0
  53. data/lib/matchers/matchers.rb +15 -2
  54. data/lib/plugins/inspec-init/templates/profiles/alicloud/README.md +27 -0
  55. data/lib/plugins/inspec-init/templates/profiles/alicloud/controls/example.rb +10 -0
  56. data/lib/plugins/inspec-init/templates/profiles/alicloud/inputs.yml +1 -0
  57. data/lib/plugins/inspec-init/templates/profiles/alicloud/inspec.yml +14 -0
  58. data/lib/plugins/inspec-reporter-html2/README.md +1 -1
  59. data/lib/plugins/inspec-reporter-html2/templates/body.html.erb +7 -1
  60. data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +10 -6
  61. data/lib/plugins/inspec-reporter-html2/templates/default.css +12 -0
  62. data/lib/plugins/inspec-reporter-html2/templates/selector.html.erb +7 -1
  63. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +5 -2
  64. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +39 -13
  65. metadata +26 -9
@@ -0,0 +1,251 @@
1
+ require "inspec/resources/command"
2
+ require "json" unless defined?(JSON)
3
+
4
+ # @see https://wiki.nftables.org/
5
+ # @see https://www.netfilter.org/projects/nftables/manpage.html
6
+
7
+ # rubocop:disable Style/ClassVars
8
+
9
+ module Inspec::Resources
10
+ class NfTables < Inspec.resource(1)
11
+ name "nftables"
12
+ supports platform: "linux"
13
+ desc "Use the nftables InSpec audit resource to test rules and sets that are defined in nftables, which maintains tables of IP packet filtering rules. There may be more than one table. Each table contains one (or more) chains. A chain is a list of rules that match packets. When the rule matches, the rule defines what target to assign to the packet."
14
+ example <<~EXAMPLE
15
+ describe nftables(family:'inet', table:'filter', chain: 'INPUT') do
16
+ its('type') { should eq 'filter' }
17
+ its('hook') { should eq 'input' }
18
+ its('prio') { should eq 0 } # filter
19
+ its('policy') { should eq 'drop' }
20
+ it { should have_rule('tcp dport { 22, 80, 443 } accept') }
21
+ end
22
+ describe nftables(family: 'inet', table: 'filter', set: 'OPEN_PORTS') do
23
+ its('type') { should eq 'ipv4_addr . inet_proto . inet_service' }
24
+ its('flags') { should include 'interval' }
25
+ it { should have_element('1.1.1.1 . tcp . 25-27') }
26
+ end
27
+ EXAMPLE
28
+
29
+ @@bin = nil
30
+ @@nft_params = {}
31
+ @@nft_params["json"] = ""
32
+ @@nft_params["stateless"] = ""
33
+ @@nft_params["num"] = ""
34
+
35
+ def initialize(params = {})
36
+ @family = params[:family] || nil
37
+ @table = params[:table] || nil
38
+ @chain = params[:chain] || nil
39
+ @set = params[:set] || nil
40
+ @ignore_comments = params[:ignore_comments] || false
41
+ unless @@bin
42
+ @@bin = find_nftables_or_error
43
+ end
44
+
45
+ # Some old versions of `nft` do not support JSON output or stateless modifier
46
+ res = inspec.command("#{@@bin} --version").stdout
47
+ version = Gem::Version.new(/^nftables v(\S+) .*/.match(res)[1])
48
+ case
49
+ when version < Gem::Version.new("0.8.0")
50
+ @@nft_params["num"] = "-nn"
51
+ when version < Gem::Version.new("0.9.0")
52
+ @@nft_params["stateless"] = "-s"
53
+ @@nft_params["num"] = "-nn"
54
+ when version < Gem::Version.new("0.9.3")
55
+ @@nft_params["json"] = "-j"
56
+ @@nft_params["stateless"] = "-s"
57
+ @@nft_params["num"] = "-nn"
58
+ when version >= Gem::Version.new("0.9.3")
59
+ @@nft_params["json"] = "-j"
60
+ @@nft_params["stateless"] = "-s"
61
+ @@nft_params["num"] = "-y"
62
+ ## --terse
63
+ end
64
+
65
+ # family and table attributes are mandatory
66
+ fail_resource "nftables family and table are mandatory." if @family.nil? || @family.empty? || @table.nil? || @table.empty?
67
+ # chain name or set name has to be specified and are mutually exclusive
68
+ fail_resource "You must specify either a chain or a set name." if (@chain.nil? || @chain.empty?) && (@set.nil? || @set.empty?)
69
+ fail_resource "You must specify either a chain or a set name, not both." if !(@chain.nil? || @chain.empty?) && !(@set.nil? || @set.empty?)
70
+
71
+ # we're done if we are on linux
72
+ return if inspec.os.linux?
73
+
74
+ # ensures, all calls are aborted for non-supported os
75
+ @nftables_cache = {}
76
+ skip_resource "The `nftables` resource is not supported on your OS yet."
77
+ end
78
+
79
+ # Let's have a generic method to retrieve attributes for chains and sets
80
+ def _get_attr(name)
81
+ # Some attributes are valid for chains only, for sets only or for both
82
+ valid = {
83
+ "chains" => %w{hook policy prio type},
84
+ "sets" => %w{flags size type},
85
+ }
86
+
87
+ target_obj = @set.nil? ? "chains" : "sets"
88
+
89
+ if valid[target_obj].include?(name)
90
+ attrs = @set.nil? ? retrieve_chain_attrs : retrieve_set_attrs
91
+ else
92
+ raise Inspec::Exceptions::ResourceSkipped, "`#{name}` attribute is not valid for #{target_obj}"
93
+ end
94
+ # flags attribute is an array, if not retrieved ensure we return an empty array
95
+ # otherwise return an empty string
96
+ default = name == "flags" ? [] : ""
97
+ val = attrs.key?(name) ? attrs[name] : default
98
+ # When set type is has multiple data types it's retrieved as an array, make humans life easier
99
+ # by returning a string representation
100
+ if name == "type" && target_obj == "sets" && val.is_a?(Array)
101
+ return val.join(" . ")
102
+ end
103
+
104
+ val
105
+ end
106
+
107
+ # Create a method for each attribute
108
+ %i{flags hook policy prio size type}.each do |attr_method|
109
+ define_method attr_method do
110
+ _get_attr(attr_method.to_s)
111
+ end
112
+ end
113
+
114
+ def has_rule?(rule = nil, _family = nil, _table = nil, _chain = nil)
115
+ # checks if the rule is part of the chain
116
+ # for now, we expect an exact match
117
+ retrieve_chain_rules.any? { |line| line.casecmp(rule) == 0 }
118
+ end
119
+
120
+ def has_element?(element = nil, _family = nil, _table = nil, _chain = nil)
121
+ # checks if the element is part of the set
122
+ # for now, we expect an exact match
123
+ retrieve_set_elements.any? { |line| line.casecmp(element) == 0 }
124
+ end
125
+
126
+ def retrieve_set_elements
127
+ idx = "set_#{@family}_#{@table}_#{@set}"
128
+ return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
129
+
130
+ @nftables_cache = {} unless defined?(@nftables_cache)
131
+
132
+ elem_cmd = "list set #{@family} #{@table} #{@set}"
133
+ nftables_cmd = format("%s %s %s", @@bin, @@nft_params["stateless"], elem_cmd).strip
134
+
135
+ cmd = inspec.command(nftables_cmd)
136
+ return [] if cmd.exit_status.to_i != 0
137
+
138
+ @nftables_cache[idx] = cmd.stdout.gsub("\t", "").split("\n").reject { |line| line =~ /^(table|set|type|size|flags|typeof|auto-merge)/ || line =~ /^}$/ }.map { |line| line.sub("elements = {", "").sub("}", "").split(",") }.flatten.map(&:strip)
139
+ end
140
+
141
+ def retrieve_chain_rules
142
+ idx = "rule_#{@family}_#{@table}_#{@chain}"
143
+ return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
144
+
145
+ @nftables_cache = {} unless defined?(@nftables_cache)
146
+
147
+ # construct nftables command to read all rules of the given chain
148
+ chain_cmd = "list chain #{@family} #{@table} #{@chain}"
149
+ nftables_cmd = format("%s %s %s %s", @@bin, @@nft_params["stateless"], @@nft_params["num"], chain_cmd).strip
150
+
151
+ cmd = inspec.command(nftables_cmd)
152
+ return [] if cmd.exit_status.to_i != 0
153
+
154
+ rules = cmd.stdout.gsub("\t", "").split("\n").reject { |line| line =~ /^(table|chain)/ || line =~ /^}$/ }
155
+
156
+ if @ignore_comments
157
+ # split rules, returns array or rules without any comment
158
+ @nftables_cache[idx] = remove_comments_from_rules(rules)
159
+ else
160
+ # split rules, returns array or rules
161
+ @nftables_cache[idx] = rules.map(&:strip)
162
+ end
163
+ end
164
+
165
+ def retrieve_chain_attrs
166
+ idx = "chain_attrs_#{@family}_#{@table}_#{@chain}"
167
+ return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
168
+
169
+ @nftables_cache = {} unless defined?(@nftables_cache)
170
+
171
+ chain_cmd = "list chain #{@family} #{@table} #{@chain}"
172
+ nftables_cmd = format("%s %s %s %s", @@bin, @@nft_params["stateless"], @@nft_params["json"], chain_cmd).strip
173
+
174
+ cmd = inspec.command(nftables_cmd)
175
+ return {} if cmd.exit_status.to_i != 0
176
+
177
+ if @@nft_params["json"].empty?
178
+ res = cmd.stdout.gsub("\t", "").split("\n").select { |line| line =~ /^type/ }[0]
179
+ parsed = /type (\S+) hook (\S+) priority (\S+); policy (\S+);/.match(res)
180
+ @nftables_cache[idx] = { "type" => parsed[1], "hook" => parsed[2], "prio" => parsed[3].to_i, "policy" => parsed[4] }
181
+ else
182
+ @nftables_cache[idx] = JSON.parse(cmd.stdout)["nftables"].select { |line| line.key?("chain") }[0]["chain"]
183
+ end
184
+ end
185
+
186
+ def retrieve_set_attrs
187
+ idx = "set_attrs_#{@family}_#{@table}_#{@chain}"
188
+ return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
189
+
190
+ @nftables_cache = {} unless defined?(@nftables_cache)
191
+
192
+ chain_cmd = "list set #{@family} #{@table} #{@set}"
193
+ nftables_cmd = format("%s %s %s %s", @@bin, @@nft_params["stateless"], @@nft_params["json"], chain_cmd).strip
194
+
195
+ cmd = inspec.command(nftables_cmd)
196
+ return {} if cmd.exit_status.to_i != 0
197
+
198
+ if @@nft_params["json"].empty?
199
+ type = ""
200
+ size = 0
201
+ flags = []
202
+ res = cmd.stdout.gsub("\t", "").split("\n").select { |line| line =~ /^(type|size|flags)/ }
203
+ res.each do |line|
204
+ parsed = /^type (.*)/.match(line)
205
+ if parsed
206
+ type = parsed[1]
207
+ end
208
+ parsed = /^flags (.*)/.match(line)
209
+ if parsed
210
+ flags = parsed[1].split(",")
211
+ end
212
+ parsed = /^size (.*)/.match(line)
213
+ if parsed
214
+ size = parsed[1].to_i
215
+ end
216
+ end
217
+ @nftables_cache[idx] = { "type" => type, "size" => size, "flags" => flags }
218
+ else
219
+ @nftables_cache[idx] = JSON.parse(cmd.stdout)["nftables"].select { |line| line.key?("set") }[0]["set"]
220
+ end
221
+ end
222
+
223
+ def resource_id
224
+ to_s || "nftables"
225
+ end
226
+
227
+ def to_s
228
+ format("nftables (%s %s %s %s)", @family && "family: #{@family}", @table && "table: #{@table}", @chain && "chain: #{@chain}", @set && "set: #{@set}").strip
229
+ end
230
+
231
+ private
232
+
233
+ def remove_comments_from_rules(rules)
234
+ rules.each do |rule|
235
+ next if rule.nil?
236
+
237
+ rule.gsub!(/ comment "([^"]*)"/, "")
238
+ rule.strip
239
+ end
240
+ rules
241
+ end
242
+
243
+ def find_nftables_or_error
244
+ %w{/usr/sbin/nft /sbin/nft nft}.each do |cmd|
245
+ return cmd if inspec.command(cmd).exist?
246
+ end
247
+
248
+ raise Inspec::Exceptions::ResourceFailed, "Could not find `nft`"
249
+ end
250
+ end
251
+ end
@@ -101,22 +101,31 @@ module Inspec::Resources
101
101
  verified_query = verify_query(escaped_query)
102
102
  end
103
103
 
104
- sql_prefix, sql_postfix = "", ""
104
+ sql_prefix, sql_postfix, oracle_echo_str = "", "", ""
105
105
  if inspec.os.windows?
106
106
  sql_prefix = %{@'\n#{format_options}\n#{verified_query}\nEXIT\n'@ | }
107
107
  else
108
108
  sql_postfix = %{ <<'EOC'\n#{format_options}\n#{verified_query}\nEXIT\nEOC}
109
+ # oracle_query_string is echoed to be able to extract the query output clearly
110
+ oracle_echo_str = %{echo 'oracle_query_string';}
111
+ end
112
+
113
+ # Resetting sql_postfix if system is using AIX OS and C shell installation for oracle
114
+ if inspec.os.aix?
115
+ command_to_fetch_shell = @su_user ? %{su - #{@su_user} -c "env | grep SHELL"} : %{env | grep SHELL}
116
+ shell_is_csh = inspec.command(command_to_fetch_shell).stdout&.include? "/csh"
117
+ sql_postfix = %{ <<'EOC'\n#{format_options}\n#{verified_query}\nEXIT\n'EOC'} if shell_is_csh
109
118
  end
110
119
 
111
120
  if @db_role.nil?
112
- %{#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service}#{sql_postfix}}
121
+ %{#{oracle_echo_str}#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service}#{sql_postfix}}
113
122
  elsif @su_user.nil?
114
- %{#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service} as #{@db_role}#{sql_postfix}}
123
+ %{#{oracle_echo_str}#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service} as #{@db_role}#{sql_postfix}}
115
124
  else
116
125
  # oracle_query_string is echoed to be able to extract the query output clearly
117
126
  # su - su_user in certain versions of oracle returns a message
118
127
  # Example of msg with query output: The Oracle base remains unchanged with value /oracle\n\nVALUE\n3\n
119
- %{su - #{@su_user} -c "echo 'oracle_query_string'; env ORACLE_SID=#{@service} #{@bin} / as #{@db_role}#{sql_postfix}"}
128
+ %{su - #{@su_user} -c "#{oracle_echo_str} env ORACLE_SID=#{@service} #{@bin} / as #{@db_role}#{sql_postfix}"}
120
129
  end
121
130
  end
122
131
 
@@ -0,0 +1,353 @@
1
+ require "inspec/resources/command"
2
+ require "inspec/utils/filter"
3
+ require "hashie/mash"
4
+
5
+ module Inspec::Resources
6
+ class Podman < Inspec.resource(1)
7
+ # Resource requires an internal name.
8
+ name "podman"
9
+
10
+ # Restrict to only run on the below platforms (if none were given,
11
+ # all OS's and cloud API's supported)
12
+ supports platform: "unix"
13
+
14
+ desc "A resource to retrieve information about podman"
15
+
16
+ example <<~EXAMPLE
17
+ describe podman.containers do
18
+ its('images') { should include "docker.io/library/ubuntu:latest" }
19
+ end
20
+
21
+ describe podman.images do
22
+ its('names') { should_not include "docker.io/library/ubuntu:latest" }
23
+ end
24
+
25
+ describe podman.pods do
26
+ its("ids") { should include "95cadbb84df71e6374fceb3fd89ee3b8f2c7e1a831062cd9cea7d0e3e4b1dbcc" }
27
+ end
28
+
29
+ describe podman.info.host do
30
+ its("os") { should eq "linux"}
31
+ end
32
+
33
+ describe podman.version do
34
+ its("Client.Version") { should eq "4.1.0"}
35
+ end
36
+
37
+ podman.containers.ids.each do |id|
38
+ # call podman inspect for a specific container id
39
+ describe podman.object(id) do
40
+ its("State.OciVersion") { should eq "1.0.2-dev" }
41
+ its("State.Running") { should eq true}
42
+ end
43
+ end
44
+ EXAMPLE
45
+
46
+ def containers
47
+ PodmanContainerFilter.new(parse_containers)
48
+ end
49
+
50
+ def images
51
+ PodmanImageFilter.new(parse_images)
52
+ end
53
+
54
+ def networks
55
+ PodmanNetworkFilter.new(parse_networks)
56
+ end
57
+
58
+ def pods
59
+ PodmanPodFilter.new(parse_pods)
60
+ end
61
+
62
+ def volumes
63
+ PodmanVolumeFilter.new(parse_volumes)
64
+ end
65
+
66
+ def version
67
+ return @version if defined?(@version)
68
+
69
+ sub_cmd = "version --format json"
70
+ output = run_command(sub_cmd)
71
+ @version = Hashie::Mash.new(JSON.parse(output))
72
+ rescue JSON::ParserError => _e
73
+ Hashie::Mash.new({})
74
+ end
75
+
76
+ def info
77
+ return @info if defined?(@info)
78
+
79
+ sub_cmd = "info --format json"
80
+ output = run_command(sub_cmd)
81
+ @info = Hashie::Mash.new(JSON.parse(output))
82
+ rescue JSON::ParserError => _e
83
+ Hashie::Mash.new({})
84
+ end
85
+
86
+ # returns information about podman objects
87
+ def object(id)
88
+ return @inspect if defined?(@inspect)
89
+
90
+ output = run_command("inspect #{id} --format json")
91
+ data = JSON.parse(output)
92
+ data = data[0] if data.is_a?(Array)
93
+ @inspect = Hashie::Mash.new(data)
94
+ rescue JSON::ParserError => _e
95
+ Hashie::Mash.new({})
96
+ end
97
+
98
+ def to_s
99
+ "Podman"
100
+ end
101
+
102
+ private
103
+
104
+ # Calls the run_command method to get all podman containers and parse the command output.
105
+ # Returns the parsed command output.
106
+ def parse_containers
107
+ labels = %w{ID Image ImageID Command CreatedAt RunningFor Status Pod Ports Size Names Networks Labels Mounts}
108
+ parse_json_command(labels, "ps -a --no-trunc --size")
109
+ end
110
+
111
+ # Calls the run_command method to get all podman images and parse the command output.
112
+ # Returns the parsed command output.
113
+ def parse_images
114
+ labels = %w{ID Repository Tag Size Digest CreatedAt CreatedSince History}
115
+ parse_json_command(labels, "images -a --no-trunc")
116
+ end
117
+
118
+ # Calls the run_command method to get all podman network list and parse the command output.
119
+ # Returns the parsed command output.
120
+ def parse_networks
121
+ labels = %w{ID Name Driver Labels Options IPAMOptions Created Internal IPv6Enabled DNSEnabled NetworkInterface Subnets}
122
+ parse_json_command(labels, "network ls --no-trunc")
123
+ end
124
+
125
+ # Calls the run_command method to get all podman pod list and parse the command output.
126
+ # Returns the parsed command output.
127
+ def parse_pods
128
+ sub_cmd = "pod ps --no-trunc --format json"
129
+ output = run_command(sub_cmd)
130
+ parse(output)
131
+ end
132
+
133
+ # Calls the run_command method to get all podman volume list and parse the command output.
134
+ # Returns the parsed command output.
135
+ def parse_volumes
136
+ sub_cmd = "volume ls --format json"
137
+ output = run_command(sub_cmd)
138
+ parse(output)
139
+ end
140
+
141
+ # Runs the given podman command on the host machine on which podman is installed
142
+ # Returns the command output or raises the command execution error.
143
+ def run_command(subcommand)
144
+ result = inspec.command("podman #{subcommand}")
145
+ if result.stderr.empty?
146
+ result.stdout
147
+ else
148
+ raise "Error while running command \'podman #{subcommand}\' : #{result.stderr}"
149
+ end
150
+ end
151
+
152
+ def parse_json_command(labels, subcommand)
153
+ # build command
154
+ format = labels.map { |label| "\"#{label}\": {{json .#{label}}}" }
155
+ raw = inspec.command("podman #{subcommand} --format '{#{format.join(", ")}}'").stdout
156
+ output = []
157
+
158
+ raw.each_line do |entry|
159
+ # convert all keys to lower_case to work well with ruby and filter table
160
+ row = JSON.parse(entry).map do |key, value|
161
+ [key.downcase, value]
162
+ end.to_h
163
+
164
+ # ensure all keys are there
165
+ row = ensure_keys(row, labels)
166
+ output.push(row)
167
+ end
168
+
169
+ output
170
+ rescue JSON::ParserError => _e
171
+ warn "Could not parse `podman #{subcommand}` output"
172
+ []
173
+ end
174
+
175
+ def ensure_keys(entry, labels)
176
+ labels.each do |key|
177
+ entry[key.downcase] = nil unless entry.key?(key.downcase)
178
+ end
179
+ entry
180
+ end
181
+
182
+ # Method to parse JDON content.
183
+ # Returns: Parsed data.
184
+ def parse(content)
185
+ require "json" unless defined?(JSON)
186
+ output = JSON.parse(content)
187
+ parsed_output = []
188
+ output.each do |entry|
189
+ entry = entry.map do |k, v|
190
+ [k.downcase, v]
191
+ end.to_h
192
+ parsed_output << entry
193
+ end
194
+ parsed_output
195
+ rescue => e
196
+ raise Inspec::Exceptions::ResourceFailed, "Unable to parse command JSON output: #{e.message}"
197
+ end
198
+ end
199
+
200
+ # class for podman.containers plural resource
201
+ class PodmanContainerFilter
202
+ filter = FilterTable.create
203
+ filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
204
+ filter.register_column(:commands, field: "command")
205
+ .register_column(:ids, field: "id")
206
+ .register_column(:created_at, field: "createdat")
207
+ .register_column(:images, field: "image")
208
+ .register_column(:names, field: "names")
209
+ .register_column(:status, field: "status")
210
+ .register_column(:image_ids, field: "image_id")
211
+ .register_column(:labels, field: "labels", style: :simple)
212
+ .register_column(:mounts, field: "mounts")
213
+ .register_column(:networks, field: "networks")
214
+ .register_column(:pods, field: "pod")
215
+ .register_column(:ports, field: "ports")
216
+ .register_column(:sizes, field: "size")
217
+ .register_column(:running_for, field: "running_for")
218
+ .register_custom_matcher(:running?) do |x|
219
+ x.where { status.downcase.start_with?("up") }
220
+ end
221
+ filter.install_filter_methods_on_resource(self, :containers)
222
+
223
+ attr_reader :containers
224
+ def initialize(containers)
225
+ @containers = containers
226
+ end
227
+
228
+ def to_s
229
+ "Podman Containers"
230
+ end
231
+
232
+ def resource_id
233
+ "Podman Containers"
234
+ end
235
+ end
236
+
237
+ # class for podman.images plural resource
238
+ class PodmanImageFilter
239
+ filter = FilterTable.create
240
+ filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
241
+ filter.register_column(:ids, field: "id")
242
+ .register_column(:repositories, field: "repository")
243
+ .register_column(:tags, field: "tag")
244
+ .register_column(:sizes, field: "size")
245
+ .register_column(:digests, field: "digest")
246
+ .register_column(:created_at, field: "createdat")
247
+ .register_column(:created_since, field: "createdsince")
248
+ .register_column(:history, field: "history")
249
+ filter.install_filter_methods_on_resource(self, :images)
250
+
251
+ attr_reader :images
252
+ def initialize(images)
253
+ @images = images
254
+ end
255
+
256
+ def to_s
257
+ "Podman Images"
258
+ end
259
+
260
+ def resource_id
261
+ "Podman Images"
262
+ end
263
+ end
264
+
265
+ class PodmanNetworkFilter
266
+ filter = FilterTable.create
267
+ filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
268
+ .register_column(:ids, field: "id")
269
+ .register_column(:names, field: "name")
270
+ .register_column(:drivers, field: "driver")
271
+ .register_column(:network_interfaces, field: "networkinterface")
272
+ .register_column(:created, field: "created")
273
+ .register_column(:subnets, field: "subnets")
274
+ .register_column(:ipv6_enabled, field: "ipv6enabled")
275
+ .register_column(:internal, field: "internal")
276
+ .register_column(:dns_enabled, field: "dnsenabled")
277
+ .register_column(:ipam_options, field: "ipamoptions")
278
+ .register_column(:options, field: "options")
279
+ .register_column(:labels, field: "labels")
280
+ filter.install_filter_methods_on_resource(self, :networks)
281
+
282
+ attr_reader :networks
283
+ def initialize(networks)
284
+ @networks = networks
285
+ end
286
+
287
+ def to_s
288
+ "Podman Networks"
289
+ end
290
+
291
+ def resource_id
292
+ "Podman Networks"
293
+ end
294
+ end
295
+
296
+ class PodmanPodFilter
297
+ filter = FilterTable.create
298
+ filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
299
+ .register_column(:ids, field: "id")
300
+ .register_column(:cgroups, field: "cgroup")
301
+ .register_column(:containers, field: "containers")
302
+ .register_column(:created, field: "created")
303
+ .register_column(:infraids, field: "infraid")
304
+ .register_column(:names, field: "name")
305
+ .register_column(:namespaces, field: "namespace")
306
+ .register_column(:networks, field: "networks")
307
+ .register_column(:status, field: "status")
308
+ .register_column(:labels, field: "labels")
309
+ filter.install_filter_methods_on_resource(self, :pods)
310
+
311
+ attr_reader :pods
312
+ def initialize(pods)
313
+ @pods = pods
314
+ end
315
+
316
+ def to_s
317
+ "Podman Pods"
318
+ end
319
+
320
+ def resource_id
321
+ "Podman Pods"
322
+ end
323
+ end
324
+
325
+ class PodmanVolumeFilter
326
+ filter = FilterTable.create
327
+ filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
328
+ .register_column(:names, field: "name")
329
+ .register_column(:drivers, field: "driver")
330
+ .register_column(:mountpoints, field: "mountpoint")
331
+ .register_column(:createdat, field: "createdat")
332
+ .register_column(:labels, field: "labels")
333
+ .register_column(:scopes, field: "scope")
334
+ .register_column(:options, field: "options")
335
+ .register_column(:mountcount, field: "mountcount")
336
+ .register_column(:needscopyup, field: "needscopyup")
337
+ .register_column(:needschown, field: "needschown")
338
+ filter.install_filter_methods_on_resource(self, :volumes)
339
+
340
+ attr_reader :volumes
341
+ def initialize(volumes)
342
+ @volumes = volumes
343
+ end
344
+
345
+ def to_s
346
+ "Podman Volumes"
347
+ end
348
+
349
+ def resource_id
350
+ "Podman Volumes"
351
+ end
352
+ end
353
+ end