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.
- checksums.yaml +4 -4
- data/Gemfile +19 -17
- data/inspec-core.gemspec +22 -22
- data/lib/inspec/base_cli.rb +19 -17
- data/lib/inspec/cli.rb +27 -25
- data/lib/inspec/dependencies/dependency_set.rb +2 -2
- data/lib/inspec/dsl.rb +9 -5
- data/lib/inspec/enhanced_outcomes.rb +19 -0
- data/lib/inspec/env_printer.rb +1 -1
- data/lib/inspec/exceptions.rb +2 -0
- data/lib/inspec/formatters/base.rb +69 -16
- data/lib/inspec/plugin/v2/loader.rb +19 -8
- data/lib/inspec/plugin/v2/plugin_types/reporter.rb +1 -0
- data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +54 -0
- data/lib/inspec/profile.rb +9 -8
- data/lib/inspec/reporters/base.rb +1 -0
- data/lib/inspec/reporters/cli.rb +94 -3
- data/lib/inspec/reporters/json.rb +3 -1
- data/lib/inspec/reporters/yaml.rb +3 -1
- data/lib/inspec/reporters.rb +2 -1
- data/lib/inspec/resources/file.rb +1 -1
- data/lib/inspec/resources/http.rb +5 -5
- data/lib/inspec/resources/lxc.rb +65 -9
- data/lib/inspec/resources/mongodb_session.rb +5 -0
- data/lib/inspec/resources/nftables.rb +251 -0
- data/lib/inspec/resources/oracledb_session.rb +13 -4
- data/lib/inspec/resources/podman.rb +353 -0
- data/lib/inspec/resources/podman_container.rb +84 -0
- data/lib/inspec/resources/podman_image.rb +108 -0
- data/lib/inspec/resources/podman_network.rb +81 -0
- data/lib/inspec/resources/podman_pod.rb +101 -0
- data/lib/inspec/resources/podman_volume.rb +87 -0
- data/lib/inspec/resources/postgres_session.rb +2 -1
- data/lib/inspec/resources/service.rb +1 -1
- data/lib/inspec/resources.rb +1 -0
- data/lib/inspec/rule.rb +54 -17
- data/lib/inspec/run_data/control.rb +6 -0
- data/lib/inspec/run_data/statistics.rb +8 -2
- data/lib/inspec/runner.rb +18 -8
- data/lib/inspec/runner_rspec.rb +3 -2
- data/lib/inspec/schema/exec_json.rb +78 -2
- data/lib/inspec/schema/output_schema.rb +4 -1
- data/lib/inspec/schema/profile_json.rb +46 -0
- data/lib/inspec/schema.rb +91 -0
- data/lib/inspec/utils/convert.rb +8 -0
- data/lib/inspec/utils/podman.rb +24 -0
- data/lib/inspec/utils/simpleconfig.rb +10 -2
- data/lib/inspec/utils/waivers/csv_file_reader.rb +34 -0
- data/lib/inspec/utils/waivers/excel_file_reader.rb +39 -0
- data/lib/inspec/utils/waivers/json_file_reader.rb +15 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/inspec/waiver_file_reader.rb +61 -0
- data/lib/matchers/matchers.rb +15 -2
- data/lib/plugins/inspec-init/templates/profiles/alicloud/README.md +27 -0
- data/lib/plugins/inspec-init/templates/profiles/alicloud/controls/example.rb +10 -0
- data/lib/plugins/inspec-init/templates/profiles/alicloud/inputs.yml +1 -0
- data/lib/plugins/inspec-init/templates/profiles/alicloud/inspec.yml +14 -0
- data/lib/plugins/inspec-reporter-html2/README.md +1 -1
- data/lib/plugins/inspec-reporter-html2/templates/body.html.erb +7 -1
- data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +10 -6
- data/lib/plugins/inspec-reporter-html2/templates/default.css +12 -0
- data/lib/plugins/inspec-reporter-html2/templates/selector.html.erb +7 -1
- data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +5 -2
- data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +39 -13
- 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 "
|
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
|