inspec/lib/vulcano/backend/specinfra.rb

336 lines
8.9 KiB
Ruby
Raw Normal View History

# encoding: utf-8
2015-09-04 07:59:30 +00:00
require 'shellwords'
2015-09-14 12:50:08 +00:00
require 'specinfra'
require 'specinfra/helper'
require 'specinfra/helper/set'
require 'winrm'
2015-09-14 14:36:54 +00:00
module Specinfra
module Helper
module Os
def os
2015-09-25 17:46:46 +00:00
property[:os] = {} if !property[:os]
if !property[:os].include?(:family)
2015-09-14 14:36:54 +00:00
property[:os] = detect_os
end
property[:os]
end
private
2015-09-25 17:46:46 +00:00
2015-09-14 14:36:54 +00:00
def detect_os
backend = Specinfra.configuration.backend
if backend == :cmd || backend == :winrm
2015-09-25 17:46:46 +00:00
return { family: 'windows', release: nil, arch: nil }
2015-09-14 14:36:54 +00:00
end
Specinfra::Helper::DetectOs.subclasses.each do |c|
res = c.detect
if res
res[:arch] ||= Specinfra.backend.run_command('uname -m').stdout.strip
return res
end
end
2015-09-25 17:46:46 +00:00
fail NotImplementedError, 'Specinfra failed os detection.'
2015-09-14 14:36:54 +00:00
end
end
end
end
module Vulcano::Backends
class SpecinfraHelper < Vulcano.backend(1)
name 'specinfra'
def initialize(conf)
@conf = conf
@files = {}
type = @conf['backend'].to_s
reset_backend(type)
configure_shared_options
# configure the given backend, if we can handle it
# e.g. backend = exec ==> try to call configure_exec
# if we don't support it, error out
m = "configure_#{type}"
if self.respond_to?(m.to_sym)
send(m)
else
2015-09-03 21:24:42 +00:00
fail "Cannot configure Specinfra backend #{type}: it isn't supported yet."
end
end
2015-09-14 14:36:54 +00:00
def os
Specinfra::Helper::Os.os
end
def file(path)
@files[path] ||= File.new(self, path)
end
def run_command(cmd)
Specinfra::Runner.run_command(cmd)
end
def to_s
'SpecInfra Backend Runner'
end
def reset_backend(type)
# may be less nice, but avoid eval...
case type
when 'exec'
Specinfra::Backend::Exec.instance_variable_set(:@instance, nil)
when 'docker'
Specinfra::Backend::Docker.instance_variable_set(:@instance, nil)
when 'ssh'
Specinfra::Backend::Ssh.instance_variable_set(:@instance, nil)
when 'winrm'
Specinfra::Backend::Winrm.instance_variable_set(:@instance, nil)
end
end
def configure_shared_options
Specinfra::Backend::Cmd.send(:include, Specinfra::Helper::Set)
# Force specinfra to disregard any locally detected backend and instead
# retry backend detection.
Specinfra::Properties.instance.properties({})
si = Specinfra.configuration
if @conf['disable_sudo']
si.disable_sudo = true
else
si.sudo_password = @conf['sudo_password']
si.sudo_options = @conf['sudo_options']
end
end
def configure_docker
host = @conf['host'].to_s
Specinfra.configuration.backend = :docker
Specinfra.configuration.docker_container = host
end
def configure_exec
Specinfra.configuration.backend = :exec
end
def validate_ssh_options(ssh_opts)
unless ssh_opts[:port] > 0
2015-09-03 21:24:42 +00:00
fail "Port must be > 0 (not #{ssh_opts[:port]})"
end
if ssh_opts[:user].to_s.empty?
2015-09-05 14:07:54 +00:00
fail 'User must not be empty.'
end
unless ssh_opts[:keys].empty?
ssh_opts[:auth_methods].push('publickey')
ssh_opts[:keys_only] = true if ssh_opts[:password].nil?
end
unless ssh_opts[:password].nil?
ssh_opts[:auth_methods].push('password')
end
# rubocop:disable Style/GuardClause
if ssh_opts[:keys].empty? and ssh_opts[:password].nil?
2015-09-05 14:07:54 +00:00
fail 'You must configure at least one authentication method' \
': Password or key.'
end
# rubocop:enable Style/GuardClause
end
def configure_ssh
si = Specinfra.configuration
si.backend = :ssh
si.request_pty = true
host = @conf['host'].to_s
fail 'You must configure a target host.' if host.empty?
si.host = host
ssh_opts = {
port: @conf['port'] || 22,
auth_methods: ['none'],
user_known_hosts_file: '/dev/null',
global_known_hosts_file: '/dev/null',
number_of_password_prompts: 0,
2015-09-10 11:28:39 +00:00
user: @conf['user'] || 'root',
password: @conf['password'],
keys: [@conf['key_file']].compact,
}
validate_ssh_options(ssh_opts)
si.ssh_options = ssh_opts
end
def winrm_url(conf)
host = conf['host'].to_s
port = conf['port']
fail 'You must configure a target host.' if host.empty?
# SSL configuration
if conf['winrm_ssl']
scheme = 'https'
2015-09-04 07:59:30 +00:00
port ||= 5986
else
scheme = 'http'
2015-09-04 07:59:30 +00:00
port ||= 5985
end
"#{scheme}://#{host}:#{port}/wsman"
end
def configure_winrm
si = Specinfra.configuration
si.backend = :winrm
si.os = { family: 'windows' }
# validation
user = @conf['user'].to_s
pass = @conf['password'].to_s
2015-09-10 11:28:39 +00:00
if user.empty?
warn "We use default 'Administrator' as WinRM user for login."
user = 'Administrator'
end
fail 'You must configure a WinRM password.' if pass.empty?
# create the connection
endpoint = winrm_url(@conf)
winrm = ::WinRM::WinRMWebService.new(
endpoint,
:ssl,
user: user,
pass: pass,
basic_auth_only: true,
no_ssl_peer_verification: @conf['winrm_self_signed'],
)
si.winrm = winrm
end
end
class SpecinfraHelper
class File < LinuxFile
def initialize(backend, path)
super(backend, path)
end
def exist?
Specinfra::Runner.check_file_exists(@path)
end
def mode
return bsd_stat[:mode] unless bsd_stat.nil?
m = Specinfra::Runner.get_file_mode(@path).stdout.strip
return nil if m.empty? || m.include?('cannot stat')
m.to_i(8)
end
def owner
return bsd_stat[:owner] unless bsd_stat.nil?
o = Specinfra::Runner.get_file_owner_user(@path).stdout.strip
return nil if o.empty? || o.include?('cannot stat')
o
end
def group
return bsd_stat[:group] unless bsd_stat.nil?
g = Specinfra::Runner.get_file_owner_group(@path).stdout.strip
return nil if g.empty? || g.include?('cannot stat')
g
end
def link_path
return nil unless symlink?
path = Shellwords.escape(@path)
Specinfra::Runner.run_command("readlink #{path}").stdout.strip
end
def content
s = Specinfra::Runner.get_file_content(@path).stdout
# if we get some content, return it
return s unless s.empty?
# if we didn't get any content, we have to decide if this is
# really an empty file (i.e. where content == empty string)
# or if something else is going on.
# in case it is a folder or the path doesn't exist, always
# return nil instead of empty content
return nil if directory? or !exist?
# in case we can't get the size, something is wrong, so return nil
# in case the size is non-zero, we couldn't read the file, so
# return nil to indicate that
i = size
return nil if i.nil? or i > 0
# return the empty string, as the file doesn't contain anything
s
end
def md5sum
s = Specinfra::Runner.get_file_md5sum(@path).stdout.strip
return nil if s.empty? or s.include?(' ')
s
end
def sha256sum
s = Specinfra::Runner.get_file_sha256sum(@path).stdout.strip
return nil if s.empty? or s.include?(' ')
s
end
def mtime
mt = Specinfra::Runner.get_file_mtime(@path).stdout.strip
return nil if mt.empty? || mt.include?(' ')
mt.to_i
end
def size
s = Specinfra::Runner.get_file_size(@path).stdout.strip
return nil if s.empty? || s.include?(' ')
s.to_i
end
def selinux_label
res = Specinfra::Runner.get_file_selinuxlabel(@path).stdout.strip
return nil if res.empty? or res == '?' or
res.include?('failed to get security context') or
res.include?('cannot stat')
res
rescue NotImplementedError => _
nil
end
def mounted?(opts = {}, only_with = nil)
Specinfra::Runner.check_file_is_mounted(@path, opts, only_with)
end
def immutable?
Specinfra::Runner.get_file_immutable(@path)
end
def product_version
return nil unless @backend.os[:family] == 'windows'
res = Specinfra::Runner.
run_command("(Get-Command '#{@path}').FileVersionInfo.ProductVersion").
stdout.strip
res.empty? ? nil : res
end
def file_version
return nil unless @backend.os[:family] == 'windows'
res = Specinfra::Runner.
run_command("(Get-Command '#{@path}').FileVersionInfo.FileVersion").
stdout.strip
res.empty? ? nil : res
end
end
end
end