inspec/lib/resources/virtualization.rb

252 lines
8.4 KiB
Ruby
Raw Normal View History

# encoding: utf-8
# author: Takaaki Furukawa
require 'hashie/mash'
module Inspec::Resources
class Virtualization < Inspec.resource(1)
name 'virtualization'
desc 'Use the virtualization InSpec audit resource to test the virtualization platform on which the system is running'
example "
describe virtualization do
its('system') { should eq 'docker' }
end
describe virtualization do
its('role') { should eq 'guest' }
end
control 'test' do
describe file('/var/tmp/foo') do
it { should be_file }
end
only_if { virtualization.system == 'docker' }
end
"
def initialize
unless inspec.os.linux?
skip_resource 'The `virtualization` resource is not supported on your OS yet.'
else
collect_data_linux
end
end
# add helper methods for easy access of properties
# allows users to use virtualization.role, virtualization.system
%w{role system}.each do |property|
define_method(property.to_sym) do
@virtualization_data[property.to_sym]
end
end
def params
collect_data_linux
end
def to_s
'Virtualization Detection'
end
private
def lxc_version_exists?
inspec.command('lxc-version').exist?
end
def docker_exists?
inspec.command('docker').exist?
end
def nova_exists?
inspec.command('nova').exist?
end
# Detect Xen
# /proc/xen is an empty dir for EL6 + Linode Guests + Paravirt EC2 instances
# Notes:
# - cpuid of guests, if we could get it, would also be a clue
# - may be able to determine if under paravirt from /dev/xen/evtchn (See OHAI-253)
# - Additional edge cases likely should not change the above assumptions
# but rather be additive - btm
def detect_xen
return false unless inspec.file('/proc/xen').exist?
@virtualization_data[:system] = 'xen'
@virtualization_data[:role] = 'guest'
# This file should exist on most Xen systems, normally empty for guests
if inspec.file('/proc/xen/capabilities').exist? &&
inspec.file('/proc/xen/capabilities').content =~ /control_d/i # rubocop:disable Layout/MultilineOperationIndentation
@virtualization_data[:role] = 'host'
end
true
end
# Detect Virtualbox from kernel module
def detect_virtualbox
return false unless inspec.file('/proc/modules').exist?
modules = inspec.file('/proc/modules').content
if modules =~ /^vboxdrv/
Inspec::Log.debug('Plugin Virtualization: /proc/modules contains vboxdrv. Detecting as vbox host')
@virtualization_data[:system] = 'vbox'
@virtualization_data[:role] = 'host'
elsif modules =~ /^vboxguest/
Inspec::Log.debug('Plugin Virtualization: /proc/modules contains vboxguest. Detecting as vbox guest')
@virtualization_data[:system] = 'vbox'
@virtualization_data[:role] = 'guest'
else
return false
end
true
end
# if nova binary is present we're on an openstack host
def detect_openstack
return false unless nova_exists?
@virtualization_data[:system] = 'openstack'
@virtualization_data[:role] = 'host'
true
end
# Detect paravirt KVM/QEMU from cpuinfo, report as KVM
def detect_kvm_from_cpuinfo
return false unless inspec.file('/proc/cpuinfo').content =~ /QEMU Virtual CPU|Common KVM processor|Common 32-bit KVM processor/
@virtualization_data[:system] = 'kvm'
@virtualization_data[:role] = 'guest'
true
end
# Detect KVM systems via /sys
# guests will have the hypervisor cpu feature that hosts don't have
def detect_kvm_from_sys
return false unless inspec.file('/sys/devices/virtual/misc/kvm').exist?
@virtualization_data[:system] = 'kvm'
if inspec.file('/proc/cpuinfo').content =~ /hypervisor/
@virtualization_data[:role] = 'guest'
else
@virtualization_data[:role] = 'host'
end
true
end
# Detect OpenVZ / Virtuozzo.
# http://wiki.openvz.org/BC_proc_entries
def detect_openvz
if inspec.file('/proc/bc/0').exist?
@virtualization_data[:system] = 'openvz'
@virtualization_data[:role] = 'host'
elsif inspec.file('/proc/vz').exist?
@virtualization_data[:system] = 'openvz'
@virtualization_data[:role] = 'guest'
else
return false
end
true
end
# Detect Parallels virtual machine from pci devices
def detect_parallels
return false unless inspec.file('/proc/bus/pci/devices').content =~ /1ab84000/
@virtualization_data[:system] = 'parallels'
@virtualization_data[:role] = 'guest'
true
end
# Detect Linux-VServer
def detect_linux_vserver
return false unless inspec.file('/proc/self/status').exist?
proc_self_status = inspec.file('/proc/self/status').content
vxid = proc_self_status.match(/^(s_context|VxID):\s*(\d+)$/)
return false unless vxid && vxid[2]
@virtualization_data[:system] = 'linux-vserver'
if vxid[2] == '0'
@virtualization_data[:role] = 'host'
else
@virtualization_data[:role] = 'guest'
end
true
end
# Detect LXC/Docker
#
# /proc/self/cgroup will look like this inside a docker container:
# <index #>:<subsystem>:/lxc/<hexadecimal container id>
#
# /proc/self/cgroup could have a name including alpha/digit/dashes
# <index #>:<subsystem>:/lxc/<named container id>
#
# /proc/self/cgroup could have a non-lxc cgroup name indicating other uses
# of cgroups. This is probably not LXC/Docker.
# <index #>:<subsystem>:/Charlie
#
# A host which supports cgroups, and has capacity to host lxc containers,
# will show the subsystems and root (/) namespace.
# <index #>:<subsystem>:/
#
# Full notes, https://tickets.opscode.com/browse/OHAI-551
# Kernel docs, https://www.kernel.org/doc/Documentation/cgroups
def detect_lxc_docker
return false unless inspec.file('/proc/self/cgroup').exist?
cgroup_content = inspec.file('/proc/self/cgroup').content
if cgroup_content =~ %r{^\d+:[^:]+:/(lxc|docker)/.+$} ||
cgroup_content =~ %r{^\d+:[^:]+:/[^/]+/(lxc|docker)-.+$} # rubocop:disable Layout/MultilineOperationIndentation
@virtualization_data[:system] = $1 # rubocop:disable Style/PerlBackrefs
@virtualization_data[:role] = 'guest'
elsif lxc_version_exists? && cgroup_content =~ %r{\d:[^:]+:/$}
# lxc-version shouldn't be installed by default
# Even so, it is likely we are on an LXC capable host that is not being used as such
# So we're cautious here to not overwrite other existing values (OHAI-573)
unless @virtualization_data[:system] && @virtualization_data[:role]
@virtualization_data[:system] = 'lxc'
@virtualization_data[:role] = 'host'
end
else
return false
end
true
end
def detect_docker
return false unless inspec.file('/.dockerenv').exist? || inspec.file('/.dockerinit').exist?
@virtualization_data[:system] = 'docker'
@virtualization_data[:role] = 'guest'
true
end
# Detect LXD
# See https://github.com/lxc/lxd/blob/master/doc/dev-lxd.md
def detect_lxd
if inspec.file('/dev/lxd/sock').exist?
@virtualization_data[:system] = 'lxd'
@virtualization_data[:role] = 'guest'
elsif inspec.file('/var/lib/lxd/devlxd').exist?
@virtualization_data[:system] = 'lxd'
@virtualization_data[:role] = 'host'
else
return false
end
true
end
def collect_data_linux # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
# cache data in an instance var to avoid doing multiple detections for a single test
@virtualization_data ||= Hashie::Mash.new
return unless @virtualization_data.empty?
# each detect method will return true if it matched and was successfully
# able to populate @virtualization_data with stuff.
return if detect_xen
return if detect_virtualbox
return if detect_openstack
return if detect_kvm_from_cpuinfo
return if detect_kvm_from_sys
return if detect_openvz
return if detect_parallels
return if detect_linux_vserver
return if detect_lxc_docker
return if detect_docker
return if detect_lxd
end
end
end