Merge pull request #5935 from inspec/ss/resource-cgroup

CFINSPEC-73: Add cgroup resource
This commit is contained in:
Clinton Wolfe 2022-03-23 09:39:32 -04:00 committed by GitHub
commit 03d0f2374b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 401 additions and 0 deletions

View file

@ -0,0 +1,79 @@
+++
title = "cgroup resource"
draft = false
gh_repo = "inspec"
platform = "linux"
[menu]
[menu.inspec]
title = "cgroup"
identifier = "inspec/resources/os/cgroup.md cgroup resource"
parent = "inspec/resources/os"
+++
Use the `cgroup` Chef InSpec audit resource to test the different parameters values of the control group (cgroup) resource controllers. A cgroup is a Linux kernel feature that limits, accounts, and isolates the resource usage (such as CPU, memory, disk I/O, network) of a collection of processes.
## Availability
### Installation
This resource is distributed with Chef InSpec.
## Syntax
describe cgroup("CARROTKING") do
its("cpuset.cpus") { should eq 0 }
end
where
- `cpuset.cpus` is a property of this resource and a parameter of the *cpuset* resource controller.
- `CARROTKING` is the name of cgroup directory.
## Properties
- All parameters of the cgroup resource controller are valid properties of this resource. Some of them are: `cpuset.cpus`, `memory.limit_in_bytes`, `memory.stat`, `freezer.state`, `cpu.stat`, `cpuacct.usage`, `pids.current`, `blkio.throttle.io_service_bytes`.
## Matchers
- For a full list of available matchers, refer [matchers page](https://docs.chef.io/inspec/matchers/).
- The matchers applicable for this resource are: `eq`, `cmp`, and `match`.
### eq
`eq` tests whether the two values are of same data type and includes configuration entries that are numbers. It fails if the types do not match. Use `cmp` for less restrictive comparisons that ignores data type while comparing.
### cmp
Unlike `eq`, `cmp` is a matcher for less-restrictive comparisons. This matcher attempts to fit the actual value to the comparing type and meant to relieve the user from having to write type-casts and resolutions.
### match
`match` checks if a string matches a regular expression. Use `match` when the output of `cgget -n -r [subsystem.parameters] [cgroup-name]` is a multi-line output.
## Examples
The following examples show how to use this Chef InSpec audit resource.
### Example 1
Use `eq` to test for parameters that have a single line integer value. The value considered is the output obtained on `cgget -n -r [subsystem.parameters] [cgroup-name]`.
describe cgroup("CARROTKING") do
its("cpuset.cpus") { should eq 0 }
end
### Example 2
Use `cmp` to test for parameters with less-restrictive comparisons and has a single line integer value. The value considered is the output obtained on `cgget -n -r [subsystem.parameters] [cgroup-name]`.
describe cgroup("CARROTKING") do
its("memory.limit_in_bytes") { should cmp 9223372036854771712 }
end
### Example 3
Use `match` to test for parameters that have multi-line values and can be passed as *regex*. The value considered is the output obtained on `cgget -n -r [subsystem.parameters] [cgroup-name]`.
describe cgroup("CARROTKING") do
its("memory.stat") { should match /\bhierarchical_memory_limit 9223372036854771712\b/ }
end

View file

@ -0,0 +1,101 @@
require "inspec/resources/command"
module Inspec::Resources
class Cgroup < Inspec.resource(1)
name "cgroup"
# Restrict to only run on the below platform
supports platform: "linux"
desc "Use the cgroup InSpec audit resource to test cgroup subsytem's parameters."
example <<~EXAMPLE
describe cgroup("foo") do
its("cpuset.cpus") { should eq 0 }
its("memory.limit_in_bytes") { should eq 499712 }
its("memory.limit_in_bytes") { should be <= 500000 }
its("memory.numa_stat") { should match /total=0/ }
end
EXAMPLE
# Resource initialization.
def initialize(cgroup_name)
raise Inspec::Exceptions::ResourceSkipped, "The `cgroup` resource is not supported on your OS yet." unless inspec.os.linux?
@cgroup_name = cgroup_name
@valid_queries, @valid_queries_split = [], []
find_valid_queries
# Used to track the method calls in an "its" query
@cgroup_info_query = []
end
def resource_id
@cgroup_name
end
def to_s
"cgroup #{resource_id}"
end
def method_missing(param)
# Add the latest param we've seen to the list and form the query with all the params we've seen so far.
@cgroup_info_query << param.to_s
query = @cgroup_info_query.join(".")
# The ith level param must match with atleast one row's ith column of @valid_queries_split
# Else there is no way, we would find any valid query in further iteration, so raise exception.
if @valid_queries_split.map { |e| e[@cgroup_info_query.length - 1] }.include?(param.to_s)
# If the query form so far is part of @valid_queries, we are good to trigger find_cgroup_info
# else go for next level of param
if @valid_queries.include?(query)
@cgroup_info_query = []
find_cgroup_info(query)
else
self
end
else
@cgroup_info_query = []
raise Inspec::Exceptions::ResourceFailed, "The query #{query} does not appear to be valid."
end
end
private
# Method to find cgget tool
def find_cgget_or_error
%w{/usr/sbin/cgget /sbin/cgget cgget}.each do |cmd|
return cmd if inspec.command(cmd).exist?
end
raise Inspec::Exceptions::ResourceFailed, "Could not find `cgget`"
end
# find the cgroup info of the query which is given as input by the user
def find_cgroup_info(query)
bin = find_cgget_or_error
cgget_cmd = "#{bin} -n -r #{query} #{@cgroup_name}"
cmd = inspec.command(cgget_cmd)
raise Inspec::Exceptions::ResourceFailed, "Executing cgget failed: #{cmd.stderr}" if cmd.exit_status.to_i != 0
# For complex returns the user must use match /the_regex/
param_value = cmd.stdout.split(":")
return nil if param_value.nil? || param_value.empty?
param_value = param_value[1].strip.split("\t").join
param_value.match(/^\d+$/) ? param_value.to_i : param_value
end
# find all the information about all relevant controllers for the current cgroup
def find_valid_queries
bin = find_cgget_or_error
cgget_all_cmd = "#{bin} -n -a #{@cgroup_name}"
cmd = inspec.command(cgget_all_cmd)
raise Inspec::Exceptions::ResourceFailed, "Executing cgget failed: #{cmd.stderr}" if cmd.exit_status.to_i != 0
queries = cmd.stdout.to_s.gsub(/:.*/, "").gsub(/^\s+.*/, "").split("\n")
# store the relevant controller parameters in @valid_queries and the dot splitted paramters into @valid_queries_split
@valid_queries = queries.map { |q| q if q.length > 0 }.compact
@valid_queries_split = @valid_queries.map { |q| q.split(".") }.compact
end
end
end

146
test/fixtures/cmd/cgget-n-a vendored Normal file
View file

@ -0,0 +1,146 @@
devices.list: a *:* rwm
cpuset.memory_pressure: 0
cpuset.memory_migrate: 0
cpuset.mem_exclusive: 0
cpuset.memory_spread_slab: 0
cpuset.cpu_exclusive: 0
cpuset.effective_mems: 0
cpuset.effective_cpus: 0
cpuset.sched_load_balance: 1
cpuset.mems: 0
cpuset.mem_hardwall: 0
cpuset.sched_relax_domain_level: -1
cpuset.cpus: 0
cpuset.memory_spread_page: 0
freezer.self_freezing: 0
freezer.parent_freezing: 0
freezer.state: THAWED
hugetlb.2MB.limit_in_bytes: 9223372036852678656
hugetlb.2MB.usage_in_bytes: 0
hugetlb.2MB.rsvd.failcnt: 0
hugetlb.2MB.max_usage_in_bytes: 0
hugetlb.2MB.rsvd.limit_in_bytes: 9223372036852678656
hugetlb.2MB.rsvd.usage_in_bytes: 0
hugetlb.2MB.rsvd.max_usage_in_bytes: 0
hugetlb.2MB.failcnt: 0
net_cls.classid: 0
net_prio.prioidx: 7
net_prio.ifpriomap: lo 0
eth0 0
lxdbr0 0
vethb7b9a0f5 0
lxcbr0 0
veth72d3ce6b 0
blkio.throttle.read_iops_device:
blkio.throttle.io_service_bytes: Total 0
blkio.throttle.write_iops_device:
blkio.throttle.read_bps_device:
blkio.throttle.write_bps_device:
blkio.throttle.io_serviced: Total 0
blkio.throttle.io_service_bytes_recursive: Total 0
blkio.throttle.io_serviced_recursive: Total 0
rdma.current:
rdma.max:
cpu.cfs_period_us: 100000
cpu.stat: nr_periods 0
nr_throttled 0
throttled_time 0
cpu.shares: 1024
cpu.cfs_quota_us: -1
cpu.uclamp.min: 0.00
cpu.uclamp.max: max
cpuacct.usage_percpu_sys: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
cpuacct.usage_percpu: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
cpuacct.stat: user 0
system 0
cpuacct.usage: 0
cpuacct.usage_sys: 0
cpuacct.usage_all: cpu user system
0 0 0
1 0 0
2 0 0
3 0 0
4 0 0
5 0 0
6 0 0
7 0 0
8 0 0
9 0 0
10 0 0
11 0 0
12 0 0
13 0 0
14 0 0
cpuacct.usage_percpu_user: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
cpuacct.usage_user: 0
memory.use_hierarchy: 1
memory.kmem.tcp.usage_in_bytes: 0
memory.soft_limit_in_bytes: 9223372036854771712
memory.move_charge_at_immigrate: 0
memory.memsw.failcnt: 0
memory.kmem.tcp.max_usage_in_bytes: 0
memory.max_usage_in_bytes: 0
memory.oom_control: oom_kill_disable 0
under_oom 0
oom_kill 0
memory.stat: cache 0
rss 0
rss_huge 0
shmem 0
mapped_file 0
dirty 0
writeback 0
swap 0
pgpgin 0
pgpgout 0
pgfault 0
pgmajfault 0
inactive_anon 0
active_anon 0
inactive_file 0
active_file 0
unevictable 0
hierarchical_memory_limit 9223372036854771712
hierarchical_memsw_limit 9223372036854771712
total_cache 0
total_rss 0
total_rss_huge 0
total_shmem 0
total_mapped_file 0
total_dirty 0
total_writeback 0
total_swap 0
total_pgpgin 0
total_pgpgout 0
total_pgfault 0
total_pgmajfault 0
total_inactive_anon 0
total_active_anon 0
total_inactive_file 0
total_active_file 0
total_unevictable 0
memory.kmem.slabinfo:
memory.limit_in_bytes: 9223372036854771712
memory.swappiness: 60
memory.memsw.max_usage_in_bytes: 0
memory.numa_stat: total=0 N0=0
file=0 N0=0
anon=0 N0=0
unevictable=0 N0=0
hierarchical_total=0 N0=0
hierarchical_file=0 N0=0
hierarchical_anon=0 N0=0
hierarchical_unevictable=0 N0=0
memory.kmem.failcnt: 0
memory.kmem.max_usage_in_bytes: 0
memory.usage_in_bytes: 0
memory.memsw.limit_in_bytes: 9223372036854771712
memory.failcnt: 0
memory.kmem.tcp.failcnt: 0
memory.kmem.limit_in_bytes: 9223372036854771712
memory.memsw.usage_in_bytes: 0
memory.kmem.usage_in_bytes: 0
memory.kmem.tcp.limit_in_bytes: 9223372036854771712
pids.current: 0
pids.events: max 0
pids.max: max

1
test/fixtures/cmd/cgget-n-r vendored Normal file
View file

@ -0,0 +1 @@
cpuset.cpus: 0

36
test/fixtures/cmd/cgget-n-r-stat vendored Normal file
View file

@ -0,0 +1,36 @@
memory.stat: cache 0
rss 0
rss_huge 0
shmem 0
mapped_file 0
dirty 0
writeback 0
swap 0
pgpgin 0
pgpgout 0
pgfault 0
pgmajfault 0
inactive_anon 0
active_anon 0
inactive_file 0
active_file 0
unevictable 0
hierarchical_memory_limit 9223372036854771712
hierarchical_memsw_limit 9223372036854771712
total_cache 0
total_rss 0
total_rss_huge 0
total_shmem 0
total_mapped_file 0
total_dirty 0
total_writeback 0
total_swap 0
total_pgpgin 0
total_pgpgout 0
total_pgfault 0
total_pgmajfault 0
total_inactive_anon 0
total_active_anon 0
total_inactive_file 0
total_active_file 0
total_unevictable 0

View file

@ -378,6 +378,11 @@ class MockLoader
# lxc
"/usr/sbin/lxc info my-ubuntu-container | grep -i Status" => cmd.call("lxcinfo"),
%{sh -c 'type "/usr/sbin/lxc"'} => empty.call,
# cgroup
"cgget -n -a carrotking" => cmd.call("cgget-n-a"),
"cgget -n -r cpuset.cpus carrotking" => cmd.call("cgget-n-r"),
"cgget -n -r memory.stat carrotking" => cmd.call("cgget-n-r-stat"),
%{sh -c 'type "cgget"'} => empty.call,
# apache_conf
"sh -c 'find /etc/apache2/ports.conf -type f -maxdepth 1'" => cmd.call("find-apache2-ports-conf"),
"sh -c 'find /etc/httpd/conf.d/*.conf -type f -maxdepth 1'" => cmd.call("find-httpd-ssl-conf"),

View file

@ -0,0 +1,33 @@
require "inspec/globals"
require "#{Inspec.src_root}/test/helper"
require_relative "../../../lib/inspec/resources/cgroup"
describe Inspec::Resources::Cgroup do
# ubuntu
it "check carrotking cgroup information on ubuntu" do
resource = MockLoader.new("ubuntu".to_sym).load_resource("cgroup", "carrotking")
_(resource.cpuset.cpus).must_equal 0
_(resource.memory.stat).must_match(/hierarchical_memory_limit 9223372036854771712/)
end
# debian
it "check carrotking cgroup information on debian" do
resource = MockLoader.new("debian8".to_sym).load_resource("cgroup", "carrotking")
_(resource.cpuset.cpus).must_equal 0
_(resource.memory.stat).must_match(/hierarchical_memory_limit 9223372036854771712/)
end
# windows
it "check carrotking cgroup information on windows" do
resource = MockLoader.new("windows".to_sym).load_resource("cgroup", "carrotking")
_(resource.resource_skipped?).must_equal true
_(resource.resource_exception_message).must_equal "The `cgroup` resource is not supported on your OS yet."
end
# undefined
it "check carrotking cgroup information on unsupported os" do
resource = MockLoader.new("undefined".to_sym).load_resource("cgroup", "carrotking")
_(resource.resource_skipped?).must_equal true
_(resource.resource_exception_message).must_equal "The `cgroup` resource is not supported on your OS yet."
end
end