mirror of
https://github.com/inspec/inspec
synced 2024-11-27 15:10:44 +00:00
f63a8ad1d5
before this regexp change, a service called "running" (hello integration tests) would always be "running" ;)
663 lines
19 KiB
Ruby
663 lines
19 KiB
Ruby
# encoding: utf-8
|
|
# author: Christoph Hartmann
|
|
# author: Dominik Richter
|
|
# author: Stephan Renatus
|
|
# license: All rights reserved
|
|
|
|
# Usage:
|
|
# describe service('dhcp') do
|
|
# it { should be_enabled }
|
|
# it { should be_installed }
|
|
# it { should be_running }
|
|
# end
|
|
#
|
|
# We detect the init system for each operating system, based on the operating
|
|
# system.
|
|
#
|
|
# Fedora 15 : systemd
|
|
# RedHat 7 : systemd
|
|
# Ubuntu 15.04 : systemd
|
|
# Ubuntu < 15.04 : upstart
|
|
#
|
|
# TODO: extend the logic to detect the running init system, independently of OS
|
|
class Service < Inspec.resource(1)
|
|
name 'service'
|
|
desc 'Use the service InSpec audit resource to test if the named service is installed, running and/or enabled.'
|
|
example "
|
|
describe service('service_name') do
|
|
it { should be_installed }
|
|
it { should be_enabled }
|
|
it { should be_running }
|
|
end
|
|
"
|
|
|
|
attr_reader :service_ctl
|
|
|
|
def initialize(service_name, service_ctl = nil)
|
|
@service_name = service_name
|
|
@service_mgmt = nil
|
|
@service_ctl ||= service_ctl
|
|
@cache = nil
|
|
@service_mgmt = select_service_mgmt
|
|
|
|
return skip_resource 'The `service` resource is not supported on your OS yet.' if @service_mgmt.nil?
|
|
end
|
|
|
|
def select_service_mgmt # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
os = inspec.os
|
|
family = os[:family]
|
|
|
|
# Ubuntu
|
|
# @see: https://wiki.ubuntu.com/SystemdForUpstartUsers
|
|
# Ubuntu 15.04 : Systemd
|
|
# Systemd runs with PID 1 as /sbin/init.
|
|
# Upstart runs with PID 1 as /sbin/upstart.
|
|
# Ubuntu < 15.04 : Upstart
|
|
# Upstart runs with PID 1 as /sbin/init.
|
|
# Systemd runs with PID 1 as /lib/systemd/systemd.
|
|
if %w{ubuntu}.include?(family)
|
|
version = inspec.os[:release].to_f
|
|
if version < 15.04
|
|
Upstart.new(inspec, service_ctl)
|
|
else
|
|
Systemd.new(inspec, service_ctl)
|
|
end
|
|
elsif %w{debian}.include?(family)
|
|
version = inspec.os[:release].to_i
|
|
if version > 7
|
|
Systemd.new(inspec, service_ctl)
|
|
else
|
|
SysV.new(inspec, service_ctl || '/usr/sbin/service')
|
|
end
|
|
elsif %w{redhat fedora centos}.include?(family)
|
|
version = inspec.os[:release].to_i
|
|
if (%w{ redhat centos }.include?(family) && version >= 7) || (family == 'fedora' && version >= 15)
|
|
Systemd.new(inspec, service_ctl)
|
|
else
|
|
SysV.new(inspec, service_ctl || '/sbin/service')
|
|
end
|
|
elsif %w{wrlinux}.include?(family)
|
|
SysV.new(inspec, service_ctl)
|
|
elsif %w{darwin}.include?(family)
|
|
LaunchCtl.new(inspec, service_ctl)
|
|
elsif os.windows?
|
|
WindowsSrv.new(inspec)
|
|
elsif %w{freebsd}.include?(family)
|
|
BSDInit.new(inspec, service_ctl)
|
|
elsif %w{arch opensuse}.include?(family)
|
|
Systemd.new(inspec, service_ctl)
|
|
elsif %w{aix}.include?(family)
|
|
SrcMstr.new(inspec)
|
|
elsif os.solaris?
|
|
Svcs.new(inspec)
|
|
end
|
|
end
|
|
|
|
def info
|
|
return nil if @service_mgmt.nil?
|
|
@cache ||= @service_mgmt.info(@service_name)
|
|
end
|
|
|
|
# verifies the service is enabled
|
|
def enabled?(_level = nil)
|
|
return false if info.nil?
|
|
info[:enabled]
|
|
end
|
|
|
|
# verifies the service is registered
|
|
def installed?(_name = nil, _version = nil)
|
|
return false if info.nil?
|
|
info[:installed]
|
|
end
|
|
|
|
# verifies the service is currently running
|
|
def running?(_under = nil)
|
|
return false if info.nil?
|
|
info[:running]
|
|
end
|
|
|
|
def to_s
|
|
"Service #{@service_name}"
|
|
end
|
|
end
|
|
|
|
class ServiceManager
|
|
attr_reader :inspec, :service_ctl
|
|
def initialize(inspec, service_ctl = nil)
|
|
@inspec = inspec
|
|
@service_ctl ||= service_ctl
|
|
end
|
|
end
|
|
|
|
# @see: http://www.freedesktop.org/software/systemd/man/systemctl.html
|
|
# @see: http://www.freedesktop.org/software/systemd/man/systemd-system.conf.html
|
|
class Systemd < ServiceManager
|
|
def initialize(inspec, service_ctl = nil)
|
|
@service_ctl ||= 'systemctl'
|
|
super
|
|
end
|
|
|
|
def info(service_name)
|
|
cmd = inspec.command("#{service_ctl} show --all #{service_name}")
|
|
return nil if cmd.exit_status.to_i != 0
|
|
|
|
# parse data
|
|
params = SimpleConfig.new(
|
|
cmd.stdout.chomp,
|
|
assignment_re: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
|
|
multiple_values: false,
|
|
).params
|
|
|
|
# LoadState values eg. loaded, not-found
|
|
installed = params['LoadState'] == 'loaded'
|
|
# test via 'systemctl is-active service'
|
|
# SubState values running
|
|
running = params['SubState'] == 'running'
|
|
# test via systemctl --quiet is-enabled
|
|
# ActiveState values eg.g inactive, active
|
|
enabled = params['UnitFileState'] == 'enabled'
|
|
|
|
{
|
|
name: params['Id'],
|
|
description: params['Description'],
|
|
installed: installed,
|
|
running: running,
|
|
enabled: enabled,
|
|
type: 'systemd',
|
|
}
|
|
end
|
|
end
|
|
|
|
# AIX services
|
|
class SrcMstr < ServiceManager
|
|
attr_reader :name
|
|
|
|
def info(service_name)
|
|
@name = service_name
|
|
running = status?
|
|
return nil if running.nil?
|
|
|
|
{
|
|
name: service_name,
|
|
description: nil,
|
|
installed: true,
|
|
running: running,
|
|
enabled: enabled?,
|
|
type: 'srcmstr',
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def status?
|
|
status_cmd = inspec.command("lssrc -s #{@name}")
|
|
return nil if status_cmd.exit_status.to_i != 0
|
|
status_cmd.stdout.split(/\n/).last.chomp =~ /active$/ ? true : false
|
|
end
|
|
|
|
def enabled?
|
|
enabled_rc_tcpip? || enabled_inittab?
|
|
end
|
|
|
|
# #rubocop:disable Style/TrailingComma
|
|
def enabled_rc_tcpip?
|
|
inspec.command(
|
|
"grep -v ^# /etc/rc.tcpip | grep 'start ' | grep -Eq '(/{0,1}| )#{name} '",
|
|
).exit_status == 0
|
|
end
|
|
|
|
def enabled_inittab?
|
|
inspec.command("lsitab #{name}").exit_status == 0
|
|
end
|
|
end
|
|
|
|
# @see: http://upstart.ubuntu.com
|
|
class Upstart < ServiceManager
|
|
def initialize(service_name, service_ctl = nil)
|
|
@service_ctl ||= 'initctl'
|
|
super
|
|
end
|
|
|
|
def info(service_name)
|
|
# get the status of upstart service
|
|
status = inspec.command("#{service_ctl} status #{service_name}")
|
|
|
|
# fallback for systemv services, those are not handled via `initctl`
|
|
return SysV.new(inspec).info(service_name) if status.exit_status.to_i != 0
|
|
|
|
# @see: http://upstart.ubuntu.com/cookbook/#job-states
|
|
# grep for running to indicate the service is there
|
|
running = !status.stdout[%r{start/running}].nil?
|
|
|
|
{
|
|
name: service_name,
|
|
description: nil,
|
|
installed: true,
|
|
running: running,
|
|
enabled: info_enabled(status, service_name),
|
|
type: 'upstart',
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def info_enabled(status, service_name)
|
|
# check if a service is enabled
|
|
# http://upstart.ubuntu.com/cookbook/#determine-if-a-job-is-disabled
|
|
# $ initctl show-config $job | grep -q "^ start on" && echo enabled || echo disabled
|
|
# Ubuntu 10.04 show-config is not supported
|
|
# @see http://manpages.ubuntu.com/manpages/maverick/man8/initctl.8.html
|
|
support_for_show_config = Gem::Version.new('1.3')
|
|
|
|
if version >= support_for_show_config
|
|
config = inspec.command("#{service_ctl} show-config #{service_name}").stdout
|
|
else # use config file as fallback
|
|
config = inspec.file("/etc/init/#{service_name}.conf").content
|
|
end
|
|
|
|
enabled = !config[/^\s*start on/].nil?
|
|
|
|
# implement fallback for Ubuntu 10.04
|
|
if inspec.os[:family] == 'ubuntu' &&
|
|
inspec.os[:release].to_f >= 10.04 &&
|
|
inspec.os[:release].to_f < 12.04 &&
|
|
status.exit_status == 0
|
|
enabled = true
|
|
end
|
|
|
|
enabled
|
|
end
|
|
|
|
def version
|
|
@version ||= Gem::Version.new(inspec.command("#{service_ctl} --version")
|
|
.stdout.match(/\(upstart ([^\)]+)\)/)[1])
|
|
end
|
|
end
|
|
|
|
class SysV < ServiceManager
|
|
def initialize(service_name, service_ctl = nil)
|
|
@service_ctl ||= 'service'
|
|
super
|
|
end
|
|
|
|
def info(service_name)
|
|
# check if service is installed
|
|
# read all available services via ls /etc/init.d/
|
|
srvlist = inspec.command('ls -1 /etc/init.d/')
|
|
return nil if srvlist.exit_status != 0
|
|
|
|
# check if the service is in list
|
|
service = srvlist.stdout.split("\n").select { |srv| srv == service_name }
|
|
|
|
# abort if we could not find any service
|
|
return nil if service.empty?
|
|
|
|
# read all enabled services from runlevel
|
|
# on rhel via: 'chkconfig --list', is not installed by default
|
|
# bash: for i in `find /etc/rc*.d -name S*`; do basename $i | sed -r 's/^S[0-9]+//'; done | sort | uniq
|
|
enabled_services_cmd = inspec.command('find /etc/rc*.d -name S*')
|
|
enabled_services = enabled_services_cmd.stdout.split("\n").select { |line|
|
|
/(^.*#{service_name}.*)/.match(line)
|
|
}
|
|
enabled = !enabled_services.empty?
|
|
|
|
# check if service is really running
|
|
# service throws an exit code if the service is not installed or
|
|
# not enabled
|
|
|
|
cmd = inspec.command("#{service_ctl} #{service_name} status")
|
|
running = cmd.exit_status == 0
|
|
{
|
|
name: service_name,
|
|
description: nil,
|
|
installed: true,
|
|
running: running,
|
|
enabled: enabled,
|
|
type: 'sysv',
|
|
}
|
|
end
|
|
end
|
|
|
|
# @see: https://www.freebsd.org/doc/en/articles/linux-users/startup.html
|
|
# @see: https://www.freebsd.org/cgi/man.cgi?query=rc.conf&sektion=5
|
|
class BSDInit < ServiceManager
|
|
def initialize(service_name, service_ctl = nil)
|
|
@service_ctl ||= 'service'
|
|
super
|
|
end
|
|
|
|
def info(service_name)
|
|
# check if service is enabled
|
|
# services are enabled in /etc/rc.conf and /etc/defaults/rc.conf
|
|
# via #{service_name}_enable="YES"
|
|
# service SERVICE status returns the following result if not activated:
|
|
# Cannot 'status' sshd. Set sshd_enable to YES in /etc/rc.conf or use 'onestatus' instead of 'status'.
|
|
# gather all enabled services
|
|
cmd = inspec.command("#{service_ctl} -e")
|
|
return nil if cmd.exit_status != 0
|
|
|
|
# search for the service
|
|
srv = /(^.*#{service_name}$)/.match(cmd.stdout)
|
|
return nil if srv.nil? || srv[0].nil?
|
|
enabled = true
|
|
|
|
# check if the service is running
|
|
# if the service is not available or not running, we always get an error code
|
|
cmd = inspec.command("#{service_ctl} #{service_name} onestatus")
|
|
running = cmd.exit_status == 0
|
|
|
|
{
|
|
name: service_name,
|
|
description: nil,
|
|
installed: true,
|
|
running: running,
|
|
enabled: enabled,
|
|
type: 'bsd-init',
|
|
}
|
|
end
|
|
end
|
|
|
|
class Runit < ServiceManager
|
|
def initialize(service_name, service_ctl = nil)
|
|
@service_ctl ||= 'sv'
|
|
super
|
|
end
|
|
|
|
# rubocop:disable Style/DoubleNegation
|
|
def info(service_name)
|
|
# get the status of runit service
|
|
cmd = inspec.command("#{service_ctl} status #{service_name}")
|
|
# return nil unless cmd.exit_status == 0 # NOTE(sr) why do we do this?
|
|
|
|
installed = cmd.exit_status == 0
|
|
running = installed && !!(cmd.stdout =~ /^run:/)
|
|
enabled = installed && (running || !!(cmd.stdout =~ /normally up/) || !!(cmd.stdout =~ /want up/))
|
|
|
|
{
|
|
name: service_name,
|
|
description: nil,
|
|
installed: installed,
|
|
running: running,
|
|
enabled: enabled,
|
|
type: 'runit',
|
|
}
|
|
end
|
|
end
|
|
|
|
# MacOS / Darwin
|
|
# new launctl on macos 10.10
|
|
class LaunchCtl < ServiceManager
|
|
def initialize(service_name, service_ctl = nil)
|
|
@service_ctl ||= 'launchctl'
|
|
super
|
|
end
|
|
|
|
def info(service_name)
|
|
# get the status of upstart service
|
|
cmd = inspec.command("#{service_ctl} list")
|
|
return nil if cmd.exit_status != 0
|
|
|
|
# search for the service
|
|
srv = /(^.*#{service_name}.*)/.match(cmd.stdout)
|
|
return nil if srv.nil? || srv[0].nil?
|
|
|
|
# extract values from service
|
|
parsed_srv = /^(?<pid>[0-9-]+)\t(?<exit>[0-9]+)\t(?<name>\S*)$/.match(srv[0])
|
|
enabled = !parsed_srv['name'].nil? # it's in the list
|
|
|
|
# check if the service is running
|
|
pid = parsed_srv['pid']
|
|
running = pid != '-'
|
|
|
|
# extract service label
|
|
srv = parsed_srv['name'] || service_name
|
|
|
|
{
|
|
name: srv,
|
|
description: nil,
|
|
installed: true,
|
|
running: running,
|
|
enabled: enabled,
|
|
type: 'darwin',
|
|
}
|
|
end
|
|
end
|
|
|
|
# Determine the service state from Windows
|
|
# Uses Powershell to retrieve the information
|
|
class WindowsSrv < ServiceManager
|
|
# Determine service details
|
|
# PS: Get-Service -Name 'dhcp'| Select-Object -Property Name, DisplayName, Status | ConvertTo-Json
|
|
# {
|
|
# "Name": "dhcp",
|
|
# "DisplayName": "DHCP Client",
|
|
# "Status": 4
|
|
# }
|
|
#
|
|
# Until StartMode is not added to Get-Service, we need to do a workaround
|
|
# @see: https://connect.microsoft.com/PowerShell/feedback/details/424948/i-would-like-to-see-the-property-starttype-added-to-get-services
|
|
# Use the following powershell to determine the start mode
|
|
# PS: Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $name -or $_.DisplayName -eq $name} | Select-Object -Prop
|
|
# erty Name, StartMode, State, Status | ConvertTo-Json
|
|
# {
|
|
# "Name": "Dhcp",
|
|
# "StartMode": "Auto",
|
|
# "State": "Running",
|
|
# "Status": "OK"
|
|
# }
|
|
#
|
|
# Windows Services have the following status code:
|
|
# @see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms685996(v=vs.85).aspx
|
|
# - 1: Stopped
|
|
# - 2: Starting
|
|
# - 3: Stopping
|
|
# - 4: Running
|
|
# - 5: Continue Pending
|
|
# - 6: Pause Pending
|
|
# - 7: Paused
|
|
def info(service_name)
|
|
cmd = inspec.command("New-Object -Type PSObject | Add-Member -MemberType NoteProperty -Name Service -Value (Get-Service -Name #{service_name}| Select-Object -Property Name, DisplayName, Status) -PassThru | Add-Member -MemberType NoteProperty -Name WMI -Value (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq '#{service_name}' -or $_.DisplayName -eq '#{service_name}'} | Select-Object -Property StartMode) -PassThru | ConvertTo-Json")
|
|
|
|
# cannot rely on exit code for now, successful command returns exit code 1
|
|
# return nil if cmd.exit_status != 0
|
|
# try to parse json
|
|
begin
|
|
service = JSON.parse(cmd.stdout)
|
|
rescue JSON::ParserError => _e
|
|
return nil
|
|
end
|
|
|
|
# check that we got a response
|
|
return nil if service.nil? || service['Service'].nil?
|
|
|
|
{
|
|
name: service['Service']['Name'],
|
|
description: service['Service']['DisplayName'],
|
|
installed: true,
|
|
running: service_running?(service),
|
|
enabled: service_enabled?(service),
|
|
type: 'windows',
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
# detect if service is enabled
|
|
def service_enabled?(service)
|
|
!service['WMI'].nil? &&
|
|
!service['WMI']['StartMode'].nil? &&
|
|
service['WMI']['StartMode'] == 'Auto'
|
|
end
|
|
|
|
# detect if service is running
|
|
def service_running?(service)
|
|
!service['Service']['Status'].nil? && service['Service']['Status'] == 4
|
|
end
|
|
end
|
|
|
|
# Solaris services
|
|
class Svcs < ServiceManager
|
|
def initialize(service_name, service_ctl = nil)
|
|
@service_ctl ||= 'svcs'
|
|
super
|
|
end
|
|
|
|
def info(service_name)
|
|
# get the status of runit service
|
|
cmd = inspec.command("#{service_ctl} -l #{service_name}")
|
|
return nil if cmd.exit_status != 0
|
|
|
|
params = SimpleConfig.new(
|
|
cmd.stdout.chomp,
|
|
assignment_re: /^(\w+)\s*(.*)$/,
|
|
multiple_values: false,
|
|
).params
|
|
|
|
installed = cmd.exit_status == 0
|
|
running = installed && (params['state'] == 'online')
|
|
enabled = installed && (params['enabled'] == 'true')
|
|
|
|
{
|
|
name: service_name,
|
|
description: params['name'],
|
|
installed: installed,
|
|
running: running,
|
|
enabled: enabled,
|
|
type: 'svcs',
|
|
}
|
|
end
|
|
end
|
|
|
|
# specific resources for specific service managers
|
|
|
|
class SystemdService < Service
|
|
name 'systemd_service'
|
|
desc 'Use the systemd_service InSpec audit resource to test if the named service (controlled by systemd) is installed, running and/or enabled.'
|
|
example "
|
|
# to override service mgmt auto-detection
|
|
describe systemd_service('service_name') do
|
|
it { should be_installed }
|
|
it { should be_enabled }
|
|
it { should be_running }
|
|
end
|
|
|
|
# to set a non-standard systemctl path
|
|
describe systemd_service('service_name', '/path/to/systemctl') do
|
|
it { should be_running }
|
|
end
|
|
"
|
|
|
|
def select_service_mgmt
|
|
Systemd.new(inspec, service_ctl)
|
|
end
|
|
end
|
|
|
|
class UpstartService < Service
|
|
name 'upstart_service'
|
|
desc 'Use the upstart_service InSpec audit resource to test if the named service (controlled by upstart) is installed, running and/or enabled.'
|
|
example "
|
|
# to override service mgmt auto-detection
|
|
describe upstart_service('service_name') do
|
|
it { should be_installed }
|
|
it { should be_enabled }
|
|
it { should be_running }
|
|
end
|
|
|
|
# to set a non-standard initctl path
|
|
describe upstart_service('service_name', '/path/to/initctl') do
|
|
it { should be_running }
|
|
end
|
|
"
|
|
|
|
def select_service_mgmt
|
|
Upstart.new(inspec, service_ctl)
|
|
end
|
|
end
|
|
|
|
class SysVService < Service
|
|
name 'sysv_service'
|
|
desc 'Use the sysv_service InSpec audit resource to test if the named service (controlled by SysV) is installed, running and/or enabled.'
|
|
example "
|
|
# to override service mgmt auto-detection
|
|
describe sysv_service('service_name') do
|
|
it { should be_installed }
|
|
it { should be_enabled }
|
|
it { should be_running }
|
|
end
|
|
|
|
# to set a non-standard service path
|
|
describe sysv_service('service_name', '/path/to/service') do
|
|
it { should be_running }
|
|
end
|
|
"
|
|
|
|
def select_service_mgmt
|
|
SysV.new(inspec, service_ctl)
|
|
end
|
|
end
|
|
|
|
class BSDService < Service
|
|
name 'bsd_service'
|
|
desc 'Use the bsd_service InSpec audit resource to test if the named service (controlled by BSD init) is installed, running and/or enabled.'
|
|
example "
|
|
# to override service mgmt auto-detection
|
|
describe bsd_service('service_name') do
|
|
it { should be_installed }
|
|
it { should be_enabled }
|
|
it { should be_running }
|
|
end
|
|
|
|
# to set a non-standard service path
|
|
describe bsd_service('service_name', '/path/to/service') do
|
|
it { should be_running }
|
|
end
|
|
"
|
|
|
|
def select_service_mgmt
|
|
BSDInit.new(inspec, service_ctl)
|
|
end
|
|
end
|
|
|
|
class LaunchdService < Service
|
|
name 'launchd_service'
|
|
desc 'Use the launchd_service InSpec audit resource to test if the named service (controlled by launchd) is installed, running and/or enabled.'
|
|
example "
|
|
# to override service mgmt auto-detection
|
|
describe launchd_service('service_name') do
|
|
it { should be_installed }
|
|
it { should be_enabled }
|
|
it { should be_running }
|
|
end
|
|
|
|
# to set a non-standard launchctl path
|
|
describe launchd_service('service_name', '/path/to/launchctl') do
|
|
it { should be_running }
|
|
end
|
|
"
|
|
|
|
def select_service_mgmt
|
|
LaunchCtl.new(inspec, service_ctl)
|
|
end
|
|
end
|
|
|
|
class RunitService < Service
|
|
name 'runit_service'
|
|
desc 'Use the runit_service InSpec audit resource to test if the named service (controlled by runit) is installed, running and/or enabled.'
|
|
example "
|
|
# to override service mgmt auto-detection
|
|
describe runit_service('service_name') do
|
|
it { should be_installed }
|
|
it { should be_enabled }
|
|
it { should be_running }
|
|
end
|
|
|
|
# to set a non-standard sv path
|
|
describe runit_service('service_name', '/path/to/sv') do
|
|
it { should be_running }
|
|
end
|
|
"
|
|
|
|
def select_service_mgmt
|
|
Runit.new(inspec, service_ctl)
|
|
end
|
|
end
|