Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for virtualization resource #1803

Merged
merged 4 commits into from Jun 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
71 changes: 71 additions & 0 deletions docs/resources/virtualization.md.erb
@@ -0,0 +1,71 @@
---
title: About the virtualization Resource
---

# virtualization

Use the `virtualization` InSpec audit resource to test the virtualization platform on which the system is running.

## Syntax

An `virtualization` resource block declares the virtualization platform that should be tested:

describe virtualization do
its('system') { should MATCHER 'value' }
end

where

* `('system')` is the name of the system information of the virtualization platform (e.g. docker, lxc, vbox, kvm, etc)
* `MATCHER` is a valid matcher for this resource
* `'value'` is the value to be tested

## Matchers

This InSpec audit resource has the following matchers:

### be

<%= partial "/shared/matcher_be" %>

### cmp

<%= partial "/shared/matcher_cmp" %>

### eq

<%= partial "/shared/matcher_eq" %>

### include

<%= partial "/shared/matcher_include" %>

### match

<%= partial "/shared/matcher_match" %>

## Examples

The following examples show how to use this InSpec audit resource.

### Test for Docker

describe virtualization do
its('system') { should eq 'docker' }
end

### Test for VirtualBox

describe virtualization do
its('system') { should eq 'vbox' }
its('role') { should eq 'guest' }
end

### Detect the virtualization platform

if virtualization.system == 'vbox'
describe package('name') do
it { should be_installed }
end
end

1 change: 1 addition & 0 deletions lib/inspec/resource.rb
Expand Up @@ -137,6 +137,7 @@ def self.validate_resource_dsl_version!(version)
require 'resources/sys_info'
require 'resources/users'
require 'resources/vbscript'
require 'resources/virtualization'
require 'resources/windows_feature'
require 'resources/windows_task'
require 'resources/xinetd'
Expand Down
252 changes: 252 additions & 0 deletions lib/resources/virtualization.rb
@@ -0,0 +1,252 @@
# encoding: utf-8
# author: Takaaki Furukawa

require 'hashie/mash'

module Inspec::Resources
class Virtualization < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
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 Style/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?
if inspec.file('/proc/cpuinfo').content =~ /hypervisor/
@virtualization_data[:system] = 'kvm'
@virtualization_data[:role] = 'guest'
else
@virtualization_data[:system] = 'kvm'
@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 Style/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
7 changes: 7 additions & 0 deletions test/integration/default/virtualization_spec.rb
@@ -0,0 +1,7 @@
# encoding: utf-8
if ENV['DOCKER'] && os.linux?
describe virtualization do
its('system') { should eq 'docker' }
its('role') { should eq 'guest' }
end
end