mirror of
https://github.com/inspec/inspec
synced 2024-11-26 22:50:36 +00:00
grub_conf resource: fix menuentry detection (#2408)
* Fix `grub_conf` menuentry detection This does the following: - Corrects Grub2 bug where last entry was always selected - Adds support for specifying a Grub2 menu entry by name - Adds support for using `GRUB_DEFAULT=saved` with Grub2 - Adds more Unit tests * Add error if menuentry name cannot be extracted * Add handling for missing/unreadable grubenv * Add defensive code for failed menuentry extraction
This commit is contained in:
parent
2cfc0acaae
commit
944dfdc987
8 changed files with 329 additions and 37 deletions
|
@ -36,6 +36,7 @@ class GrubConfig < Inspec.resource(1)
|
|||
elsif os.debian?
|
||||
@conf_path = path || '/boot/grub/grub.cfg'
|
||||
@defaults_path = '/etc/default/grub'
|
||||
@grubenv_path = '/boot/grub2/grubenv'
|
||||
@version = 'grub2'
|
||||
elsif os[:name] == 'amazon'
|
||||
@conf_path = path || '/etc/grub.conf'
|
||||
|
@ -52,6 +53,7 @@ class GrubConfig < Inspec.resource(1)
|
|||
else
|
||||
@conf_path = path || '/boot/grub2/grub.cfg'
|
||||
@defaults_path = '/etc/default/grub'
|
||||
@grubenv_path = '/boot/grub2/grubenv'
|
||||
@version = 'grub2'
|
||||
end
|
||||
end
|
||||
|
@ -71,35 +73,73 @@ class GrubConfig < Inspec.resource(1)
|
|||
######################################################################
|
||||
|
||||
def grub2_parse_kernel_lines(content, conf)
|
||||
# Find all "menuentry" lines and then parse them into arrays
|
||||
menu_entry = 0
|
||||
menu_entries = extract_menu_entries(content)
|
||||
|
||||
if @kernel == 'default'
|
||||
default_menu_entry(menu_entries, conf['GRUB_DEFAULT'])
|
||||
else
|
||||
menu_entries.find { |entry| entry['name'] == @kernel }
|
||||
end
|
||||
end
|
||||
|
||||
def extract_menu_entries(content)
|
||||
menu_entries = []
|
||||
|
||||
lines = content.split("\n")
|
||||
kernel_opts = {}
|
||||
kernel_opts['insmod'] = []
|
||||
lines.each_with_index do |file_line, index|
|
||||
next unless file_line =~ /(^|\s)menuentry\s.*/
|
||||
lines.drop(index+1).each do |kernel_line|
|
||||
next if kernel_line =~ /(^|\s)(menu|}).*/
|
||||
if menu_entry == conf['GRUB_DEFAULT'].to_i && @kernel == 'default'
|
||||
if kernel_line =~ /(^|\s)initrd.*/
|
||||
kernel_opts['initrd'] = kernel_line.split(' ')[1]
|
||||
end
|
||||
if kernel_line =~ /(^|\s)linux.*/
|
||||
kernel_opts['kernel'] = kernel_line.split
|
||||
end
|
||||
if kernel_line =~ /(^|\s)set root=.*/
|
||||
kernel_opts['root'] = kernel_line.split('=')[1].tr('\'', '')
|
||||
end
|
||||
if kernel_line =~ /(^|\s)insmod.*/
|
||||
kernel_opts['insmod'].push(kernel_line.split(' ')[1])
|
||||
end
|
||||
else
|
||||
menu_entry += 1
|
||||
break
|
||||
lines.each_with_index do |line, index|
|
||||
next unless line =~ /^menuentry\s+.*/
|
||||
entry = {}
|
||||
entry['insmod'] = []
|
||||
|
||||
# Extract name from menuentry line
|
||||
capture_data = line.match(/(?:^|\s+).*menuentry\s*['|"](.*)['|"]\s*--/)
|
||||
if capture_data.nil? || capture_data.captures[0].nil?
|
||||
raise Inspec::Exceptions::ResourceFailed "Failed to extract menuentry name from #{line}"
|
||||
end
|
||||
|
||||
entry['name'] = capture_data.captures[0]
|
||||
|
||||
# Begin processing from index forward until a `}` line is met
|
||||
lines.drop(index+1).each do |mline|
|
||||
break if mline =~ /^\s*}\s*$/
|
||||
case mline
|
||||
when /(?:^|\s*)initrd.*/
|
||||
entry['initrd'] = mline.split(' ')[1]
|
||||
when /(?:^|\s*)linux.*/
|
||||
entry['kernel'] = mline.split
|
||||
when /(?:^|\s*)set root=.*/
|
||||
entry['root'] = mline.split('=')[1].tr('\'', '')
|
||||
when /(?:^|\s*)insmod.*/
|
||||
entry['insmod'] << mline.split(' ')[1]
|
||||
end
|
||||
end
|
||||
|
||||
menu_entries << entry
|
||||
end
|
||||
kernel_opts
|
||||
|
||||
menu_entries
|
||||
end
|
||||
|
||||
def default_menu_entry(menu_entries, default)
|
||||
# If the default entry isn't `saved` then a number is used as an index.
|
||||
# By default this is `0`, which would be the first item in the list.
|
||||
return menu_entries[default.to_i] unless default == 'saved'
|
||||
|
||||
grubenv_contents = inspec.file(@grubenv_path).content
|
||||
|
||||
# The location of the grubenv file is not guaranteed. In the case that
|
||||
# the file does not exist this will return the 0th entry. This will also
|
||||
# return the 0th entry if InSpec lacks permission to read the file. Both
|
||||
# of these reflect the default Grub2 behavior.
|
||||
return menu_entries[0] if grubenv_contents.nil?
|
||||
|
||||
default_name = SimpleConfig.new(grubenv_contents).params['saved_entry']
|
||||
default_entry = menu_entries.select { |k| k['name'] == default_name }[0]
|
||||
return default_entry unless default_entry.nil?
|
||||
|
||||
# It is possible for the saved entry to not be valid . For example, grubenv
|
||||
# not being up to date. If so, the 0th entry is the default.
|
||||
menu_entries[0]
|
||||
end
|
||||
|
||||
###################################################################
|
||||
|
|
|
@ -125,6 +125,11 @@ class MockLoader
|
|||
'/etc/inetd.conf' => mockfile.call('inetd.conf'),
|
||||
'/etc/group' => mockfile.call('etcgroup'),
|
||||
'/etc/grub.conf' => mockfile.call('grub.conf'),
|
||||
'/boot/grub2/grub.cfg' => mockfile.call('grub2.cfg'),
|
||||
'/boot/grub2/grubenv' => mockfile.call('grubenv'),
|
||||
'/boot/grub2/grubenv_invalid' => mockfile.call('grubenv_invalid'),
|
||||
'/etc/default/grub' => mockfile.call('grub_defaults'),
|
||||
'/etc/default/grub_with_saved' => mockfile.call('grub_defaults_with_saved'),
|
||||
'/etc/audit/auditd.conf' => mockfile.call('auditd.conf'),
|
||||
'/etc/mysql/my.cnf' => mockfile.call('mysql.conf'),
|
||||
'/etc/mysql/mysql2.conf' => mockfile.call('mysql2.conf'),
|
||||
|
|
144
test/unit/mock/files/grub2.cfg
Normal file
144
test/unit/mock/files/grub2.cfg
Normal file
|
@ -0,0 +1,144 @@
|
|||
#
|
||||
# DO NOT EDIT THIS FILE
|
||||
#
|
||||
# It is automatically generated by grub2-mkconfig using templates
|
||||
# from /etc/grub.d and settings from /etc/default/grub
|
||||
#
|
||||
|
||||
### BEGIN /etc/grub.d/00_header ###
|
||||
set pager=1
|
||||
|
||||
if [ -s $prefix/grubenv ]; then
|
||||
load_env
|
||||
fi
|
||||
if [ "${next_entry}" ] ; then
|
||||
set default="${next_entry}"
|
||||
set next_entry=
|
||||
save_env next_entry
|
||||
set boot_once=true
|
||||
else
|
||||
set default="${saved_entry}"
|
||||
fi
|
||||
|
||||
if [ x"${feature_menuentry_id}" = xy ]; then
|
||||
menuentry_id_option="--id"
|
||||
else
|
||||
menuentry_id_option=""
|
||||
fi
|
||||
|
||||
export menuentry_id_option
|
||||
|
||||
if [ "${prev_saved_entry}" ]; then
|
||||
set saved_entry="${prev_saved_entry}"
|
||||
save_env saved_entry
|
||||
set prev_saved_entry=
|
||||
save_env prev_saved_entry
|
||||
set boot_once=true
|
||||
fi
|
||||
|
||||
function savedefault {
|
||||
if [ -z "${boot_once}" ]; then
|
||||
saved_entry="${chosen}"
|
||||
save_env saved_entry
|
||||
fi
|
||||
}
|
||||
|
||||
function load_video {
|
||||
if [ x$feature_all_video_module = xy ]; then
|
||||
insmod all_video
|
||||
else
|
||||
insmod efi_gop
|
||||
insmod efi_uga
|
||||
insmod ieee1275_fb
|
||||
insmod vbe
|
||||
insmod vga
|
||||
insmod video_bochs
|
||||
insmod video_cirrus
|
||||
fi
|
||||
}
|
||||
|
||||
terminal_output console
|
||||
if [ x$feature_timeout_style = xy ] ; then
|
||||
set timeout_style=menu
|
||||
set timeout=5
|
||||
# Fallback normal timeout code in case the timeout_style feature is
|
||||
# unavailable.
|
||||
else
|
||||
set timeout=5
|
||||
fi
|
||||
### END /etc/grub.d/00_header ###
|
||||
|
||||
### BEGIN /etc/grub.d/00_tuned ###
|
||||
set tuned_params=""
|
||||
### END /etc/grub.d/00_tuned ###
|
||||
|
||||
### BEGIN /etc/grub.d/10_linux ###
|
||||
menuentry 'Test GRUB_DEFAULT of 0' --class rhel fedora --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-229.el7.x86_64-advanced-c0bb4384-69a4-4aff-93ea-7ec2ea8e3a63' {
|
||||
load_video
|
||||
set gfxpayload=keep
|
||||
insmod gzio
|
||||
insmod part_msdos
|
||||
insmod xfs
|
||||
set root='hd0,msdos1'
|
||||
if [ x$feature_platform_search_hint = xy ]; then
|
||||
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,msdos1 --hint-efi=hd0,msdos1 --hint-baremetal=ahci0,msdos1 --hint='hd0,msdos1' 57ba6944-e05b-4f02-95d4-ea9505fe584f
|
||||
else
|
||||
search --no-floppy --fs-uuid --set=root 57ba6944-e05b-4f02-95d4-ea9505fe584f
|
||||
fi
|
||||
linux16 /vmlinuz-yup-kernel-works root=/dev/mapper/centos-root ro rd.lvm.lv=centos/root rd.lvm.lv=centos/swap crashkernel=auto rhgb quiet LANG=en_US.UTF-8
|
||||
initrd16 /initramfs-yup-initrd-works
|
||||
}
|
||||
menuentry 'CentOS Linux 7 (Core), with Linux 3.10.0-229.el7.x86_64' --class rhel fedora --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-229.el7.x86_64-advanced-c0bb4384-69a4-4aff-93ea-7ec2ea8e3a63' {
|
||||
load_video
|
||||
set gfxpayload=keep
|
||||
insmod gzio
|
||||
insmod part_msdos
|
||||
insmod xfs
|
||||
set root='hd0,msdos1'
|
||||
if [ x$feature_platform_search_hint = xy ]; then
|
||||
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,msdos1 --hint-efi=hd0,msdos1 --hint-baremetal=ahci0,msdos1 --hint='hd0,msdos1' 57ba6944-e05b-4f02-95d4-ea9505fe584f
|
||||
else
|
||||
search --no-floppy --fs-uuid --set=root 57ba6944-e05b-4f02-95d4-ea9505fe584f
|
||||
fi
|
||||
linux16 /vmlinuz-3.10.0-229.el7.x86_64 root=/dev/mapper/centos-root ro rd.lvm.lv=centos/root rd.lvm.lv=centos/swap crashkernel=auto rhgb quiet LANG=en_US.UTF-8
|
||||
initrd16 /initramfs-3.10.0-229.el7.x86_64.img
|
||||
}
|
||||
menuentry 'CentOS Linux 7 (Core), with Linux 0-rescue' --class rhel fedora --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-0-rescue-02ab91023f244febb8c38be42e37cc2d-advanced-c0bb4384-69a4-4aff-93ea-7ec2ea8e3a63' {
|
||||
load_video
|
||||
insmod gzio
|
||||
insmod part_msdos
|
||||
insmod xfs
|
||||
set root='hd0,msdos1'
|
||||
if [ x$feature_platform_search_hint = xy ]; then
|
||||
search --no-floppy --fs-uuid --set=root --hint-bios=hd0,msdos1 --hint-efi=hd0,msdos1 --hint-baremetal=ahci0,msdos1 --hint='hd0,msdos1' 57ba6944-e05b-4f02-95d4-ea9505fe584f
|
||||
else
|
||||
search --no-floppy --fs-uuid --set=root 57ba6944-e05b-4f02-95d4-ea9505fe584f
|
||||
fi
|
||||
linux16 /vmlinuz-0-rescue root=/dev/mapper/centos-root ro rd.lvm.lv=centos/root rd.lvm.lv=centos/swap crashkernel=auto rhgb quiet
|
||||
initrd16 /initramfs-0-rescue.img
|
||||
}
|
||||
|
||||
### END /etc/grub.d/10_linux ###
|
||||
|
||||
### BEGIN /etc/grub.d/20_linux_xen ###
|
||||
### END /etc/grub.d/20_linux_xen ###
|
||||
|
||||
### BEGIN /etc/grub.d/20_ppc_terminfo ###
|
||||
### END /etc/grub.d/20_ppc_terminfo ###
|
||||
|
||||
### BEGIN /etc/grub.d/30_os-prober ###
|
||||
### END /etc/grub.d/30_os-prober ###
|
||||
|
||||
### BEGIN /etc/grub.d/40_custom ###
|
||||
# This file provides an easy way to add custom menu entries. Simply type the
|
||||
# menu entries you want to add after this comment. Be careful not to change
|
||||
# the 'exec tail' line above.
|
||||
### END /etc/grub.d/40_custom ###
|
||||
|
||||
### BEGIN /etc/grub.d/41_custom ###
|
||||
if [ -f ${config_directory}/custom.cfg ]; then
|
||||
source ${config_directory}/custom.cfg
|
||||
elif [ -z "${config_directory}" -a -f $prefix/custom.cfg ]; then
|
||||
source $prefix/custom.cfg;
|
||||
fi
|
||||
### END /etc/grub.d/41_custom ###
|
34
test/unit/mock/files/grub_defaults
Normal file
34
test/unit/mock/files/grub_defaults
Normal file
|
@ -0,0 +1,34 @@
|
|||
# If you change this file, run 'update-grub' afterwards to update
|
||||
# /boot/grub/grub.cfg.
|
||||
# For full documentation of the options in this file, see:
|
||||
# info -f grub -n 'Simple configuration'
|
||||
|
||||
GRUB_DEFAULT=0
|
||||
GRUB_HIDDEN_TIMEOUT=0
|
||||
GRUB_HIDDEN_TIMEOUT_QUIET=true
|
||||
GRUB_TIMEOUT=10
|
||||
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
|
||||
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
|
||||
GRUB_CMDLINE_LINUX=""
|
||||
|
||||
# Uncomment to enable BadRAM filtering, modify to suit your needs
|
||||
# This works with Linux (no patch required) and with any kernel that obtains
|
||||
# the memory map information from GRUB (GNU Mach, kernel of FreeBSD ...)
|
||||
#GRUB_BADRAM="0x01234567,0xfefefefe,0x89abcdef,0xefefefef"
|
||||
|
||||
# Uncomment to disable graphical terminal (grub-pc only)
|
||||
#GRUB_TERMINAL=console
|
||||
|
||||
# The resolution used on graphical terminal
|
||||
# note that you can use only modes which your graphic card supports via VBE
|
||||
# you can see them in real GRUB with the command `vbeinfo'
|
||||
#GRUB_GFXMODE=640x480
|
||||
|
||||
# Uncomment if you don't want GRUB to pass "root=UUID=xxx" parameter to Linux
|
||||
#GRUB_DISABLE_LINUX_UUID=true
|
||||
|
||||
# Uncomment to disable generation of recovery mode menu entries
|
||||
#GRUB_DISABLE_RECOVERY="true"
|
||||
|
||||
# Uncomment to get a beep at grub start
|
||||
#GRUB_INIT_TUNE="480 440 1"
|
6
test/unit/mock/files/grub_defaults_with_saved
Normal file
6
test/unit/mock/files/grub_defaults_with_saved
Normal file
|
@ -0,0 +1,6 @@
|
|||
GRUB_TIMEOUT=5
|
||||
GRUB_DEFAULT=saved
|
||||
GRUB_DISABLE_SUBMENU=true
|
||||
GRUB_TERMINAL_OUTPUT="console"
|
||||
GRUB_CMDLINE_LINUX="rd.lvm.lv=centos/root rd.lvm.lv=centos/swap crashkernel=auto rhgb quiet"
|
||||
GRUB_DISABLE_RECOVERY="true"
|
3
test/unit/mock/files/grubenv
Normal file
3
test/unit/mock/files/grubenv
Normal file
|
@ -0,0 +1,3 @@
|
|||
# GRUB Environment Block
|
||||
saved_entry=CentOS Linux 7 (Core), with Linux 3.10.0-229.el7.x86_64
|
||||
############################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################
|
3
test/unit/mock/files/grubenv_invalid
Normal file
3
test/unit/mock/files/grubenv_invalid
Normal file
|
@ -0,0 +1,3 @@
|
|||
# GRUB Environment Block
|
||||
saved_entry=Nope, not a real entry
|
||||
############################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################
|
|
@ -6,24 +6,81 @@ require 'inspec/resource'
|
|||
|
||||
describe 'Inspec::Resources::GrubConfig' do
|
||||
|
||||
it 'verify kernel include' do
|
||||
resource = MockLoader.new(:centos6).load_resource('grub_conf')
|
||||
_(resource.kernel).must_be_kind_of Array
|
||||
# Grub2 with `GRUB_DEFAULT=0`
|
||||
it 'parses correctly with grub2 and `GRUB_DEfAULT=0`' do
|
||||
resource = MockLoader.new(:centos7).load_resource('grub_conf')
|
||||
|
||||
resource.kernel.must_include '/vmlinuz-yup-kernel-works'
|
||||
resource.initrd.must_equal '/initramfs-yup-initrd-works'
|
||||
end
|
||||
|
||||
it 'verify initrd include' do
|
||||
resource = MockLoader.new(:centos6).load_resource('grub_conf')
|
||||
_(resource.initrd).must_be_kind_of String
|
||||
# Grub2 with `GRUB_DEFAULT=saved`
|
||||
it 'parses correctly with grub2 and `saved` as the `GRUB_DEFAULT`' do
|
||||
resource = MockLoader.new(:centos7).load_resource('grub_conf')
|
||||
|
||||
# Both Grub1 and Grub2 use `/etc/default/grub`.
|
||||
# This overrides the Grub1 default for testing.
|
||||
resource.instance_variable_set(
|
||||
:@defaults_path,
|
||||
'/etc/default/grub_with_saved'
|
||||
)
|
||||
|
||||
resource.kernel.must_include '/vmlinuz-3.10.0-229.el7.x86_64'
|
||||
resource.initrd.must_equal '/initramfs-3.10.0-229.el7.x86_64.img'
|
||||
end
|
||||
|
||||
it 'verify default' do
|
||||
resource = MockLoader.new(:centos6).load_resource('grub_conf')
|
||||
_(resource.default).must_equal '0'
|
||||
it 'parses correctly with grub2 and an invalid grubenv entry' do
|
||||
resource = MockLoader.new(:centos7).load_resource('grub_conf')
|
||||
|
||||
# Both Grub1 and Grub2 use `/etc/default/grub`.
|
||||
# This overrides the Grub1 default for testing.
|
||||
resource.instance_variable_set(
|
||||
:@defaults_path,
|
||||
'/etc/default/grub_with_saved'
|
||||
)
|
||||
|
||||
resource.instance_variable_set(
|
||||
:@grubenv_path,
|
||||
'/boot/grub2/grubenv_invalid'
|
||||
)
|
||||
|
||||
resource.kernel.must_include '/vmlinuz-yup-kernel-works'
|
||||
resource.initrd.must_equal '/initramfs-yup-initrd-works'
|
||||
end
|
||||
|
||||
it 'verify timeout' do
|
||||
resource = MockLoader.new(:centos6).load_resource('grub_conf')
|
||||
_(resource.timeout).must_equal '5'
|
||||
# Grub2 with a specified kernel
|
||||
it 'parses data correctly with grub2 and a specified kernel' do
|
||||
resource = MockLoader.new(:centos7).load_resource(
|
||||
'grub_conf',
|
||||
'/boot/grub2/grub.cfg',
|
||||
'CentOS Linux 7 (Core), with Linux 0-rescue'
|
||||
)
|
||||
|
||||
resource.kernel.must_include '/vmlinuz-0-rescue'
|
||||
resource.initrd.must_equal '/initramfs-0-rescue.img'
|
||||
end
|
||||
|
||||
# Legacy Grub
|
||||
it 'parses correctly with grub1 (aka legacy-grub)' do
|
||||
resource = MockLoader.new(:centos6).load_resource('grub_conf')
|
||||
|
||||
resource.kernel.must_include '/vmlinuz-2.6.32-573.7.1.el6.x86_64'
|
||||
resource.initrd.must_equal '/initramfs-2.6.32-573.7.1.el6.x86_64.img'
|
||||
resource.default.must_equal '0'
|
||||
resource.timeout.must_equal '5'
|
||||
end
|
||||
|
||||
# Legacy Grub with a specified kernel
|
||||
it 'parses data correctly with grub1 and a specified kernel' do
|
||||
resource = MockLoader.new(:centos6).load_resource(
|
||||
'grub_conf',
|
||||
'/etc/grub.conf',
|
||||
'CentOS 6 (2.6.32-573.el6.x86_64)'
|
||||
)
|
||||
|
||||
resource.kernel.must_include '/vmlinuz-2.6.32-573.el6.x86_64'
|
||||
resource.initrd.must_equal '/initramfs-2.6.32-573.el6.x86_64.img'
|
||||
resource.default.must_equal '0'
|
||||
resource.timeout.must_equal '5'
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue