mirror of
https://github.com/inspec/inspec
synced 2024-11-26 06:30:26 +00:00
Move compliance to v2 plugin (#3423)
* Move compliance pluging to v2 system. * Update kitchen-inspec to test. * Add legacy require patsh. * Fix unit test Signed-off-by: Jared Quick <jquick@chef.io>
This commit is contained in:
parent
92249898d9
commit
cb12ada2fe
31 changed files with 1430 additions and 1483 deletions
3
Rakefile
3
Rakefile
|
@ -58,7 +58,7 @@ Rake::TestTask.new do |t|
|
|||
'test/unit/**/*_test.rb',
|
||||
'lib/plugins/inspec-*/test/unit/**/*_test.rb',
|
||||
])
|
||||
t.warning = true
|
||||
t.warning = false
|
||||
t.verbose = true
|
||||
t.ruby_opts = ['--dev'] if defined?(JRUBY_VERSION)
|
||||
end
|
||||
|
@ -91,7 +91,6 @@ namespace :test do
|
|||
'test/functional/inspec_exec_json_test.rb',
|
||||
'test/functional/inspec_detect_test.rb',
|
||||
'test/functional/inspec_vendor_test.rb',
|
||||
'test/functional/inspec_compliance_test.rb',
|
||||
'test/functional/inspec_check_test.rb',
|
||||
'test/functional/filter_table_test.rb',
|
||||
]
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# encoding: utf-8
|
||||
# author: Christoph Hartmann
|
||||
# author: Dominik Richter
|
||||
|
||||
libdir = File.dirname(__FILE__)
|
||||
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
||||
|
||||
module Compliance
|
||||
autoload :Configuration, 'inspec-compliance/configuration'
|
||||
autoload :HTTP, 'inspec-compliance/http'
|
||||
autoload :Support, 'inspec-compliance/support'
|
||||
autoload :API, 'inspec-compliance/api'
|
||||
end
|
||||
|
||||
require 'inspec-compliance/cli'
|
||||
require 'inspec-compliance/target'
|
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
driver:
|
||||
name: vagrant
|
||||
synced_folders:
|
||||
- ['../../../', '/inspec']
|
||||
network:
|
||||
- ['private_network', {ip: '192.168.251.2'}]
|
||||
|
||||
provisioner:
|
||||
name: shell
|
||||
|
||||
verifier:
|
||||
name: inspec
|
||||
|
||||
platforms:
|
||||
- name: ubuntu-14.04
|
||||
suites:
|
||||
- name: default
|
||||
run_list:
|
||||
attributes:
|
356
lib/bundles/inspec-compliance/api.rb
Executable file → Normal file
356
lib/bundles/inspec-compliance/api.rb
Executable file → Normal file
|
@ -1,354 +1,4 @@
|
|||
# encoding: utf-8
|
||||
# author: Christoph Hartmann
|
||||
# author: Dominik Richter
|
||||
# This file has been moved to the v2.0 plugins. This redirect allows for legacy use.
|
||||
# TODO: Remove in inspec 4.0
|
||||
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
require 'json'
|
||||
|
||||
require_relative 'api/login'
|
||||
|
||||
module Compliance
|
||||
class ServerConfigurationMissing < StandardError; end
|
||||
|
||||
# API Implementation does not hold any state by itself,
|
||||
# everything will be stored in local Configuration store
|
||||
class API
|
||||
extend Compliance::API::Login
|
||||
|
||||
# return all compliance profiles available for the user
|
||||
# the user is either specified in the options hash or by default
|
||||
# the username of the account is used that is logged in
|
||||
def self.profiles(config, profile_filter = nil) # rubocop:disable PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength
|
||||
owner = config['owner'] || config['user']
|
||||
|
||||
# Chef Compliance
|
||||
if is_compliance_server?(config)
|
||||
url = "#{config['server']}/user/compliance"
|
||||
# Chef Automate2
|
||||
elsif is_automate2_server?(config)
|
||||
url = "#{config['server']}/compliance/profiles/search"
|
||||
# Chef Automate
|
||||
elsif is_automate_server?(config)
|
||||
url = "#{config['server']}/profiles/#{owner}"
|
||||
else
|
||||
raise ServerConfigurationMissing
|
||||
end
|
||||
|
||||
headers = get_headers(config)
|
||||
if profile_filter
|
||||
_owner, id, ver = profile_split(profile_filter)
|
||||
else
|
||||
id, ver = nil
|
||||
end
|
||||
|
||||
if is_automate2_server?(config)
|
||||
body = { owner: owner, name: id }.to_json
|
||||
response = Compliance::HTTP.post_with_headers(url, headers, body, config['insecure'])
|
||||
else
|
||||
response = Compliance::HTTP.get(url, headers, config['insecure'])
|
||||
end
|
||||
data = response.body
|
||||
response_code = response.code
|
||||
case response_code
|
||||
when '200'
|
||||
msg = 'success'
|
||||
profiles = JSON.parse(data)
|
||||
# iterate over profiles
|
||||
if is_compliance_server?(config)
|
||||
mapped_profiles = []
|
||||
profiles.values.each { |org|
|
||||
mapped_profiles += org.values
|
||||
}
|
||||
# Chef Automate pre 0.8.0
|
||||
elsif is_automate_server_pre_080?(config)
|
||||
mapped_profiles = profiles.values.flatten
|
||||
elsif is_automate2_server?(config)
|
||||
mapped_profiles = []
|
||||
profiles['profiles'].each { |p|
|
||||
mapped_profiles << p
|
||||
}
|
||||
else
|
||||
mapped_profiles = profiles.map { |e|
|
||||
e['owner_id'] = owner
|
||||
e
|
||||
}
|
||||
end
|
||||
# filter by name and version if they were specified in profile_filter
|
||||
mapped_profiles.select! do |p|
|
||||
(!ver || p['version'] == ver) && (!id || p['name'] == id)
|
||||
end
|
||||
return msg, mapped_profiles
|
||||
when '401'
|
||||
msg = '401 Unauthorized. Please check your token.'
|
||||
return msg, []
|
||||
else
|
||||
msg = "An unexpected error occurred (HTTP #{response_code}): #{response.message}"
|
||||
return msg, []
|
||||
end
|
||||
end
|
||||
|
||||
# return the server api version
|
||||
# NB this method does not use Compliance::Configuration to allow for using
|
||||
# it before we know the version (e.g. oidc or not)
|
||||
def self.version(config)
|
||||
url = config['server']
|
||||
insecure = config['insecure']
|
||||
|
||||
raise ServerConfigurationMissing if url.nil?
|
||||
|
||||
headers = get_headers(config)
|
||||
response = Compliance::HTTP.get(url+'/version', headers, insecure)
|
||||
return {} if response.code == '404'
|
||||
|
||||
data = response.body
|
||||
return {} if data.nil? || data.empty?
|
||||
|
||||
parsed = JSON.parse(data)
|
||||
return {} unless parsed.key?('version') && !parsed['version'].empty?
|
||||
|
||||
parsed
|
||||
end
|
||||
|
||||
# verifies that a profile exists
|
||||
def self.exist?(config, profile)
|
||||
_msg, profiles = Compliance::API.profiles(config, profile)
|
||||
!profiles.empty?
|
||||
end
|
||||
|
||||
def self.upload(config, owner, profile_name, archive_path)
|
||||
# Chef Compliance
|
||||
if is_compliance_server?(config)
|
||||
url = "#{config['server']}/owners/#{owner}/compliance/#{profile_name}/tar"
|
||||
# Chef Automate pre 0.8.0
|
||||
elsif is_automate_server_pre_080?(config)
|
||||
url = "#{config['server']}/#{owner}"
|
||||
elsif is_automate2_server?(config)
|
||||
url = "#{config['server']}/compliance/profiles?owner=#{owner}"
|
||||
# Chef Automate
|
||||
else
|
||||
url = "#{config['server']}/profiles/#{owner}"
|
||||
end
|
||||
|
||||
headers = get_headers(config)
|
||||
if is_automate2_server?(config)
|
||||
res = Compliance::HTTP.post_multipart_file(url, headers, archive_path, config['insecure'])
|
||||
else
|
||||
res = Compliance::HTTP.post_file(url, headers, archive_path, config['insecure'])
|
||||
end
|
||||
|
||||
[res.is_a?(Net::HTTPSuccess), res.body]
|
||||
end
|
||||
|
||||
# Use username and refresh_token to get an API access token
|
||||
def self.get_token_via_refresh_token(url, refresh_token, insecure)
|
||||
uri = URI.parse("#{url}/login")
|
||||
req = Net::HTTP::Post.new(uri.path)
|
||||
req.body = { token: refresh_token }.to_json
|
||||
access_token = nil
|
||||
response = Compliance::HTTP.send_request(uri, req, insecure)
|
||||
data = response.body
|
||||
if response.code == '200'
|
||||
begin
|
||||
tokendata = JSON.parse(data)
|
||||
access_token = tokendata['access_token']
|
||||
msg = 'Successfully fetched API access token'
|
||||
success = true
|
||||
rescue JSON::ParserError => e
|
||||
success = false
|
||||
msg = e.message
|
||||
end
|
||||
else
|
||||
success = false
|
||||
msg = "Failed to authenticate to #{url} \n\
|
||||
Response code: #{response.code}\n Body: #{response.body}"
|
||||
end
|
||||
|
||||
[success, msg, access_token]
|
||||
end
|
||||
|
||||
# Use username and password to get an API access token
|
||||
def self.get_token_via_password(url, username, password, insecure)
|
||||
uri = URI.parse("#{url}/login")
|
||||
req = Net::HTTP::Post.new(uri.path)
|
||||
req.body = { userid: username, password: password }.to_json
|
||||
access_token = nil
|
||||
response = Compliance::HTTP.send_request(uri, req, insecure)
|
||||
data = response.body
|
||||
if response.code == '200'
|
||||
access_token = data
|
||||
msg = 'Successfully fetched an API access token valid for 12 hours'
|
||||
success = true
|
||||
else
|
||||
success = false
|
||||
msg = "Failed to authenticate to #{url} \n\
|
||||
Response code: #{response.code}\n Body: #{response.body}"
|
||||
end
|
||||
|
||||
[success, msg, access_token]
|
||||
end
|
||||
|
||||
def self.get_headers(config)
|
||||
token = get_token(config)
|
||||
if is_automate_server?(config) || is_automate2_server?(config)
|
||||
headers = { 'chef-delivery-enterprise' => config['automate']['ent'] }
|
||||
if config['automate']['token_type'] == 'dctoken'
|
||||
headers['x-data-collector-token'] = token
|
||||
else
|
||||
headers['chef-delivery-user'] = config['user']
|
||||
headers['chef-delivery-token'] = token
|
||||
end
|
||||
else
|
||||
headers = { 'Authorization' => "Bearer #{token}" }
|
||||
end
|
||||
headers
|
||||
end
|
||||
|
||||
def self.get_token(config)
|
||||
return config['token'] unless config['refresh_token']
|
||||
_success, _msg, token = get_token_via_refresh_token(config['server'], config['refresh_token'], config['insecure'])
|
||||
token
|
||||
end
|
||||
|
||||
def self.target_url(config, profile)
|
||||
owner, id, ver = profile_split(profile)
|
||||
|
||||
return "#{config['server']}/compliance/profiles/tar" if is_automate2_server?(config)
|
||||
return "#{config['server']}/owners/#{owner}/compliance/#{id}/tar" unless is_automate_server?(config)
|
||||
|
||||
if ver.nil?
|
||||
"#{config['server']}/profiles/#{owner}/#{id}/tar"
|
||||
else
|
||||
"#{config['server']}/profiles/#{owner}/#{id}/version/#{ver}/tar"
|
||||
end
|
||||
end
|
||||
|
||||
def self.profile_split(profile)
|
||||
owner, id = profile.split('/')
|
||||
id, version = id.split('#')
|
||||
[owner, id, version]
|
||||
end
|
||||
|
||||
# returns a parsed url for `admin/profile` or `compliance://admin/profile`
|
||||
def self.sanitize_profile_name(profile)
|
||||
if URI(profile).scheme == 'compliance'
|
||||
uri = URI(profile)
|
||||
else
|
||||
uri = URI("compliance://#{profile}")
|
||||
end
|
||||
uri.to_s.sub(%r{^compliance:\/\/}, '')
|
||||
end
|
||||
|
||||
def self.is_compliance_server?(config)
|
||||
config['server_type'] == 'compliance'
|
||||
end
|
||||
|
||||
def self.is_automate_server_pre_080?(config)
|
||||
# Automate versions before 0.8.x do not have a valid version in the config
|
||||
return false unless config['server_type'] == 'automate'
|
||||
server_version_from_config(config).nil?
|
||||
end
|
||||
|
||||
def self.is_automate_server_080_and_later?(config)
|
||||
# Automate versions 0.8.x and later will have a "version" key in the config
|
||||
# that is properly parsed out via server_version_from_config below
|
||||
return false unless config['server_type'] == 'automate'
|
||||
!server_version_from_config(config).nil?
|
||||
end
|
||||
|
||||
def self.is_automate2_server?(config)
|
||||
config['server_type'] == 'automate2'
|
||||
end
|
||||
|
||||
def self.is_automate_server?(config)
|
||||
config['server_type'] == 'automate'
|
||||
end
|
||||
|
||||
def self.server_version_from_config(config)
|
||||
# Automate versions 0.8.x and later will have a "version" key in the config
|
||||
# that looks like: "version":{"api":"compliance","version":"0.8.24"}
|
||||
return nil unless config.key?('version')
|
||||
return nil unless config['version'].is_a?(Hash)
|
||||
config['version']['version']
|
||||
end
|
||||
|
||||
def self.determine_server_type(url, insecure)
|
||||
if target_is_automate2_server?(url, insecure)
|
||||
:automate2
|
||||
elsif target_is_automate_server?(url, insecure)
|
||||
:automate
|
||||
elsif target_is_compliance_server?(url, insecure)
|
||||
:compliance
|
||||
else
|
||||
Inspec::Log.debug('Could not determine server type using known endpoints')
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.target_is_automate2_server?(url, insecure)
|
||||
automate_endpoint = '/dex/auth'
|
||||
response = Compliance::HTTP.get(url + automate_endpoint, nil, insecure)
|
||||
if response.code == '400'
|
||||
Inspec::Log.debug(
|
||||
"Received 400 from #{url}#{automate_endpoint} - " \
|
||||
'assuming target is a Chef Automate2 instance',
|
||||
)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def self.target_is_automate_server?(url, insecure)
|
||||
automate_endpoint = '/compliance/version'
|
||||
response = Compliance::HTTP.get(url + automate_endpoint, nil, insecure)
|
||||
case response.code
|
||||
when '401'
|
||||
Inspec::Log.debug(
|
||||
"Received 401 from #{url}#{automate_endpoint} - " \
|
||||
'assuming target is a Chef Automate instance',
|
||||
)
|
||||
true
|
||||
when '200'
|
||||
# Chef Automate currently returns 401 for `/compliance/version` but some
|
||||
# versions of OpsWorks Chef Automate return 200 and a Chef Manage page
|
||||
# when unauthenticated requests are received.
|
||||
if response.body.include?('Are You Looking For the Chef Server?')
|
||||
Inspec::Log.debug(
|
||||
"Received 200 from #{url}#{automate_endpoint} - " \
|
||||
'assuming target is an OpsWorks Chef Automate instance',
|
||||
)
|
||||
true
|
||||
else
|
||||
Inspec::Log.debug(
|
||||
"Received 200 from #{url}#{automate_endpoint} " \
|
||||
'but did not receive the Chef Manage page - ' \
|
||||
'assuming target is not a Chef Automate instance',
|
||||
)
|
||||
false
|
||||
end
|
||||
else
|
||||
Inspec::Log.debug(
|
||||
"Received unexpected status code #{response.code} " \
|
||||
"from #{url}#{automate_endpoint} - " \
|
||||
'assuming target is not a Chef Automate instance',
|
||||
)
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def self.target_is_compliance_server?(url, insecure)
|
||||
# All versions of Chef Compliance return 200 for `/api/version`
|
||||
compliance_endpoint = '/api/version'
|
||||
|
||||
response = Compliance::HTTP.get(url + compliance_endpoint, nil, insecure)
|
||||
return false unless response.code == '200'
|
||||
|
||||
Inspec::Log.debug(
|
||||
"Received 200 from #{url}#{compliance_endpoint} - " \
|
||||
'assuming target is a Compliance server',
|
||||
)
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
require 'plugins/inspec-compliance/lib/inspec-compliance/api'
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
# encoding: utf-8
|
||||
# author: Christoph Hartmann
|
||||
# author: Dominik Ricter
|
||||
# author: Jerry Aldrich
|
||||
|
||||
module Compliance
|
||||
class API
|
||||
module Login
|
||||
class CannotDetermineServerType < StandardError; end
|
||||
|
||||
def login(options)
|
||||
raise ArgumentError, 'Please specify a server using `inspec compliance login https://SERVER`' unless options['server']
|
||||
|
||||
options['server'] = URI("https://#{options['server']}").to_s if URI(options['server']).scheme.nil?
|
||||
|
||||
options['server_type'] = Compliance::API.determine_server_type(options['server'], options['insecure'])
|
||||
|
||||
case options['server_type']
|
||||
when :automate2
|
||||
Login::Automate2Server.login(options)
|
||||
when :automate
|
||||
Login::AutomateServer.login(options)
|
||||
when :compliance
|
||||
Login::ComplianceServer.login(options)
|
||||
else
|
||||
raise CannotDetermineServerType, "Unable to determine if #{options['server']} is a Chef Automate or Chef Compliance server"
|
||||
end
|
||||
end
|
||||
|
||||
module Automate2Server
|
||||
def self.login(options)
|
||||
verify_thor_options(options)
|
||||
|
||||
options['url'] = options['server'] + '/api/v0'
|
||||
token = options['dctoken'] || options['token']
|
||||
store_access_token(options, token)
|
||||
end
|
||||
|
||||
def self.store_access_token(options, token)
|
||||
config = Compliance::Configuration.new
|
||||
config.clean
|
||||
|
||||
config['automate'] = {}
|
||||
config['automate']['ent'] = 'automate'
|
||||
config['automate']['token_type'] = 'dctoken'
|
||||
config['server'] = options['url']
|
||||
config['user'] = options['user']
|
||||
config['owner'] = options['user']
|
||||
config['insecure'] = options['insecure'] || false
|
||||
config['server_type'] = options['server_type'].to_s
|
||||
config['token'] = token
|
||||
config['version'] = '0'
|
||||
|
||||
config.store
|
||||
config
|
||||
end
|
||||
|
||||
def self.verify_thor_options(o)
|
||||
error_msg = []
|
||||
|
||||
error_msg.push('Please specify a user using `--user=\'USER\'`') if o['user'].nil?
|
||||
|
||||
if o['token'].nil? && o['dctoken'].nil?
|
||||
error_msg.push('Please specify a token using `--token=\'APITOKEN\'`')
|
||||
end
|
||||
|
||||
raise ArgumentError, error_msg.join("\n") unless error_msg.empty?
|
||||
end
|
||||
end
|
||||
|
||||
module AutomateServer
|
||||
def self.login(options)
|
||||
verify_thor_options(options)
|
||||
|
||||
options['url'] = options['server'] + '/compliance'
|
||||
token = options['dctoken'] || options['token']
|
||||
store_access_token(options, token)
|
||||
end
|
||||
|
||||
def self.store_access_token(options, token)
|
||||
token_type = if options['token']
|
||||
'usertoken'
|
||||
else
|
||||
'dctoken'
|
||||
end
|
||||
|
||||
config = Compliance::Configuration.new
|
||||
|
||||
config.clean
|
||||
|
||||
config['automate'] = {}
|
||||
config['automate']['ent'] = options['ent']
|
||||
config['automate']['token_type'] = token_type
|
||||
config['server'] = options['url']
|
||||
config['user'] = options['user']
|
||||
config['insecure'] = options['insecure'] || false
|
||||
config['server_type'] = options['server_type'].to_s
|
||||
config['token'] = token
|
||||
config['version'] = Compliance::API.version(config)
|
||||
|
||||
config.store
|
||||
config
|
||||
end
|
||||
|
||||
# Automate login requires `--ent`, `--user`, and either `--token` or `--dctoken`
|
||||
def self.verify_thor_options(o)
|
||||
error_msg = []
|
||||
|
||||
error_msg.push('Please specify a user using `--user=\'USER\'`') if o['user'].nil?
|
||||
error_msg.push('Please specify an enterprise using `--ent=\'automate\'`') if o['ent'].nil?
|
||||
|
||||
if o['token'].nil? && o['dctoken'].nil?
|
||||
error_msg.push('Please specify a token using `--token=\'AUTOMATE_TOKEN\'` or `--dctoken=\'DATA_COLLECTOR_TOKEN\'`')
|
||||
end
|
||||
|
||||
raise ArgumentError, error_msg.join("\n") unless error_msg.empty?
|
||||
end
|
||||
end
|
||||
|
||||
module ComplianceServer
|
||||
def self.login(options)
|
||||
compliance_verify_thor_options(options)
|
||||
|
||||
options['url'] = options['server'] + '/api'
|
||||
|
||||
if options['user'] && options['token']
|
||||
compliance_store_access_token(options, options['token'])
|
||||
elsif options['user'] && options['password']
|
||||
compliance_login_user_pass(options)
|
||||
elsif options['refresh_token']
|
||||
compliance_login_refresh_token(options)
|
||||
end
|
||||
end
|
||||
|
||||
def self.compliance_login_user_pass(options)
|
||||
success, msg, token = Compliance::API.get_token_via_password(
|
||||
options['url'],
|
||||
options['user'],
|
||||
options['password'],
|
||||
options['insecure'],
|
||||
)
|
||||
|
||||
raise msg unless success
|
||||
compliance_store_access_token(options, token)
|
||||
end
|
||||
|
||||
def self.compliance_login_refresh_token(options)
|
||||
success, msg, token = Compliance::API.get_token_via_refresh_token(
|
||||
options['url'],
|
||||
options['refresh_token'],
|
||||
options['insecure'],
|
||||
)
|
||||
|
||||
raise msg unless success
|
||||
compliance_store_access_token(options, token)
|
||||
end
|
||||
|
||||
def self.compliance_store_access_token(options, token)
|
||||
config = Compliance::Configuration.new
|
||||
config.clean
|
||||
|
||||
config['user'] = options['user'] if options['user']
|
||||
config['server'] = options['url']
|
||||
config['insecure'] = options['insecure'] || false
|
||||
config['server_type'] = options['server_type'].to_s
|
||||
config['token'] = token
|
||||
config['version'] = Compliance::API.version(config)
|
||||
|
||||
config.store
|
||||
config
|
||||
end
|
||||
|
||||
# Compliance login requires `--user` or `--refresh_token`
|
||||
# If `--user` then either `--password`, `--token`, or `--refresh-token`, is required
|
||||
def self.compliance_verify_thor_options(o)
|
||||
error_msg = []
|
||||
|
||||
error_msg.push('Please specify a server using `inspec compliance login https://SERVER`') if o['server'].nil?
|
||||
|
||||
if o['user'].nil? && o['refresh_token'].nil?
|
||||
error_msg.push('Please specify a `--user=\'USER\'` or a `--refresh-token=\'TOKEN\'`')
|
||||
end
|
||||
|
||||
if o['user'] && o['password'].nil? && o['token'].nil? && o['refresh_token'].nil?
|
||||
error_msg.push('Please specify either a `--password`, `--token`, or `--refresh-token`')
|
||||
end
|
||||
|
||||
raise ArgumentError, error_msg.join("\n") unless error_msg.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,41 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "Installing Chef Compliance $deb"
|
||||
# select latest package from cache directory
|
||||
# deb=$(find /inspec/.cache -name '*.deb' | tail -1)
|
||||
# sudo dpkg -i $deb
|
||||
|
||||
# use chef compliance package repository
|
||||
sudo apt-get install -y apt-transport-https
|
||||
sudo apt-get install wget
|
||||
wget -qO - https://downloads.chef.io/packages-chef-io-public.key | sudo apt-key add -
|
||||
CHANNEL=${CHANNEL:-stable}
|
||||
DISTRIBUTION=$(lsb_release --codename | cut -f2)
|
||||
echo "found $DISTRIBUTION"
|
||||
echo "use $CHANNEL channel"
|
||||
echo "deb https://packages.chef.io/$CHANNEL-apt $DISTRIBUTION main" > /etc/apt/sources.list.d/chef-$CHANNEL.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install chef-compliance
|
||||
|
||||
sudo chef-compliance-ctl reconfigure --accept-license
|
||||
sudo chef-compliance-ctl restart
|
||||
|
||||
# finalize setup
|
||||
cd /
|
||||
/opt/chef-compliance/embedded/service/core/bin/core setup --endpoint "http://127.0.0.1:10500/setup" --login "admin" --password "admin" --name "John Doe" --accept-eula
|
||||
|
||||
# wget --no-check-certificate http://127.0.0.1/api/version
|
||||
# cat version
|
||||
|
||||
# install ruby 2.3
|
||||
sudo apt-get install -y software-properties-common
|
||||
sudo apt-add-repository -y ppa:brightbox/ruby-ng
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ruby2.3 ruby2.3-dev
|
||||
ruby2.3 -v
|
||||
|
||||
# prepare the usage of bundler
|
||||
sudo gem install bundler
|
||||
cd /inspec
|
||||
bundle install
|
||||
BUNDLE_GEMFILE=/inspec/Gemfile bundle exec inspec version
|
|
@ -1,276 +0,0 @@
|
|||
# encoding: utf-8
|
||||
# author: Christoph Hartmann
|
||||
# author: Dominik Richter
|
||||
|
||||
require 'thor'
|
||||
require 'erb'
|
||||
require 'inspec/base_cli'
|
||||
|
||||
module Compliance
|
||||
class ComplianceCLI < Inspec::BaseCLI
|
||||
namespace 'compliance'
|
||||
|
||||
# TODO: find another solution, once https://github.com/erikhuda/thor/issues/261 is fixed
|
||||
def self.banner(command, _namespace = nil, _subcommand = false)
|
||||
"#{basename} #{subcommand_prefix} #{command.usage}"
|
||||
end
|
||||
|
||||
def self.subcommand_prefix
|
||||
namespace
|
||||
end
|
||||
|
||||
desc "login https://SERVER --insecure --user='USER' --ent='ENTERPRISE' --token='TOKEN'", 'Log in to a Chef Compliance/Chef Automate SERVER'
|
||||
long_desc <<-LONGDESC
|
||||
`login` allows you to use InSpec with Chef Automate or a Chef Compliance Server
|
||||
|
||||
You need to a token for communication. More information about token retrieval
|
||||
is available at:
|
||||
https://docs.chef.io/api_automate.html#authentication-methods
|
||||
https://docs.chef.io/api_compliance.html#obtaining-an-api-token
|
||||
LONGDESC
|
||||
option :insecure, aliases: :k, type: :boolean,
|
||||
desc: 'Explicitly allows InSpec to perform "insecure" SSL connections and transfers'
|
||||
option :user, type: :string, required: false,
|
||||
desc: 'Username'
|
||||
option :password, type: :string, required: false,
|
||||
desc: 'Password (Chef Compliance Only)'
|
||||
option :token, type: :string, required: false,
|
||||
desc: 'Access token'
|
||||
option :refresh_token, type: :string, required: false,
|
||||
desc: 'Chef Compliance refresh token (Chef Compliance Only)'
|
||||
option :dctoken, type: :string, required: false,
|
||||
desc: 'Data Collector token (Chef Automate Only)'
|
||||
option :ent, type: :string, required: false,
|
||||
desc: 'Enterprise for Chef Automate reporting (Chef Automate Only)'
|
||||
def login(server)
|
||||
options['server'] = server
|
||||
Compliance::API.login(options)
|
||||
config = Compliance::Configuration.new
|
||||
puts "Stored configuration for Chef #{config['server_type'].capitalize}: #{config['server']}' with user: '#{config['user']}'"
|
||||
end
|
||||
|
||||
desc 'profiles', 'list all available profiles in Chef Compliance'
|
||||
option :owner, type: :string, required: false,
|
||||
desc: 'owner whose profiles to list'
|
||||
def profiles
|
||||
config = Compliance::Configuration.new
|
||||
return if !loggedin(config)
|
||||
|
||||
# set owner to config
|
||||
config['owner'] = options['owner'] || config['user']
|
||||
|
||||
msg, profiles = Compliance::API.profiles(config)
|
||||
profiles.sort_by! { |hsh| hsh['title'] }
|
||||
if !profiles.empty?
|
||||
# iterate over profiles
|
||||
headline('Available profiles:')
|
||||
profiles.each { |profile|
|
||||
owner = profile['owner_id'] || profile['owner']
|
||||
li("#{profile['title']} v#{profile['version']} (#{mark_text(owner + '/' + profile['name'])})")
|
||||
}
|
||||
else
|
||||
puts msg if msg != 'success'
|
||||
puts 'Could not find any profiles'
|
||||
exit 1
|
||||
end
|
||||
rescue Compliance::ServerConfigurationMissing
|
||||
STDERR.puts "\nServer configuration information is missing. Please login using `inspec compliance login`"
|
||||
exit 1
|
||||
end
|
||||
|
||||
desc 'exec PROFILE', 'executes a Chef Compliance profile'
|
||||
exec_options
|
||||
def exec(*tests)
|
||||
config = Compliance::Configuration.new
|
||||
return if !loggedin(config)
|
||||
o = opts(:exec).dup
|
||||
diagnose(o)
|
||||
configure_logger(o)
|
||||
|
||||
# iterate over tests and add compliance scheme
|
||||
tests = tests.map { |t| 'compliance://' + Compliance::API.sanitize_profile_name(t) }
|
||||
|
||||
runner = Inspec::Runner.new(o)
|
||||
tests.each { |target| runner.add_target(target) }
|
||||
|
||||
exit runner.run
|
||||
rescue ArgumentError, RuntimeError, Train::UserError => e
|
||||
$stderr.puts e.message
|
||||
exit 1
|
||||
end
|
||||
|
||||
desc 'download PROFILE', 'downloads a profile from Chef Compliance'
|
||||
option :name, type: :string,
|
||||
desc: 'Name of the archive filename (file type will be added)'
|
||||
def download(profile_name)
|
||||
o = options.dup
|
||||
configure_logger(o)
|
||||
|
||||
config = Compliance::Configuration.new
|
||||
return if !loggedin(config)
|
||||
|
||||
profile_name = Compliance::API.sanitize_profile_name(profile_name)
|
||||
if Compliance::API.exist?(config, profile_name)
|
||||
puts "Downloading `#{profile_name}`"
|
||||
|
||||
fetcher = Compliance::Fetcher.resolve(
|
||||
{
|
||||
compliance: profile_name,
|
||||
},
|
||||
)
|
||||
|
||||
# we provide a name, the fetcher adds the extension
|
||||
_owner, id = profile_name.split('/')
|
||||
file_name = fetcher.fetch(o.name || id)
|
||||
puts "Profile stored to #{file_name}"
|
||||
else
|
||||
puts "Profile #{profile_name} is not available in Chef Compliance."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
desc 'upload PATH', 'uploads a local profile to Chef Compliance'
|
||||
option :overwrite, type: :boolean, default: false,
|
||||
desc: 'Overwrite existing profile on Server.'
|
||||
option :owner, type: :string, required: false,
|
||||
desc: 'Owner that should own the profile'
|
||||
def upload(path) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, PerceivedComplexity, Metrics/CyclomaticComplexity
|
||||
config = Compliance::Configuration.new
|
||||
return if !loggedin(config)
|
||||
|
||||
# set owner to config
|
||||
config['owner'] = options['owner'] || config['user']
|
||||
|
||||
unless File.exist?(path)
|
||||
puts "Directory #{path} does not exist."
|
||||
exit 1
|
||||
end
|
||||
|
||||
vendor_deps(path, options) if File.directory?(path)
|
||||
|
||||
o = options.dup
|
||||
configure_logger(o)
|
||||
|
||||
# only run against the mock backend, otherwise we run against the local system
|
||||
o[:backend] = Inspec::Backend.create(target: 'mock://')
|
||||
o[:check_mode] = true
|
||||
o[:vendor_cache] = Inspec::Cache.new(o[:vendor_cache])
|
||||
|
||||
# check the profile, we only allow to upload valid profiles
|
||||
profile = Inspec::Profile.for_target(path, o)
|
||||
|
||||
# start verification process
|
||||
error_count = 0
|
||||
error = lambda { |msg|
|
||||
error_count += 1
|
||||
puts msg
|
||||
}
|
||||
|
||||
result = profile.check
|
||||
unless result[:summary][:valid]
|
||||
error.call('Profile check failed. Please fix the profile before upload.')
|
||||
else
|
||||
puts('Profile is valid')
|
||||
end
|
||||
|
||||
# determine user information
|
||||
if (config['token'].nil? && config['refresh_token'].nil?) || config['user'].nil?
|
||||
error.call('Please login via `inspec compliance login`')
|
||||
end
|
||||
|
||||
# read profile name from inspec.yml
|
||||
profile_name = profile.params[:name]
|
||||
|
||||
# read profile version from inspec.yml
|
||||
profile_version = profile.params[:version]
|
||||
|
||||
# check that the profile is not uploaded already,
|
||||
# confirm upload to the user (overwrite with --force)
|
||||
if Compliance::API.exist?(config, "#{config['owner']}/#{profile_name}##{profile_version}") && !options['overwrite']
|
||||
error.call('Profile exists on the server, use --overwrite')
|
||||
end
|
||||
|
||||
# abort if we found an error
|
||||
if error_count > 0
|
||||
puts "Found #{error_count} error(s)"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# if it is a directory, tar it to tmp directory
|
||||
generated = false
|
||||
if File.directory?(path)
|
||||
generated = true
|
||||
archive_path = Dir::Tmpname.create([profile_name, '.tar.gz']) {}
|
||||
puts "Generate temporary profile archive at #{archive_path}"
|
||||
profile.archive({ output: archive_path, ignore_errors: false, overwrite: true })
|
||||
else
|
||||
archive_path = path
|
||||
end
|
||||
|
||||
puts "Start upload to #{config['owner']}/#{profile_name}"
|
||||
pname = ERB::Util.url_encode(profile_name)
|
||||
|
||||
if Compliance::API.is_automate_server?(config) || Compliance::API.is_automate2_server?(config)
|
||||
puts 'Uploading to Chef Automate'
|
||||
else
|
||||
puts 'Uploading to Chef Compliance'
|
||||
end
|
||||
success, msg = Compliance::API.upload(config, config['owner'], pname, archive_path)
|
||||
|
||||
# delete temp file if it was temporary generated
|
||||
File.delete(archive_path) if generated && File.exist?(archive_path)
|
||||
|
||||
if success
|
||||
puts 'Successfully uploaded profile'
|
||||
else
|
||||
puts 'Error during profile upload:'
|
||||
puts msg
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
desc 'version', 'displays the version of the Chef Compliance server'
|
||||
def version
|
||||
config = Compliance::Configuration.new
|
||||
info = Compliance::API.version(config)
|
||||
if !info.nil? && info['version']
|
||||
puts "Name: #{info['api']}"
|
||||
puts "Version: #{info['version']}"
|
||||
else
|
||||
puts 'Could not determine server version.'
|
||||
exit 1
|
||||
end
|
||||
rescue Compliance::ServerConfigurationMissing
|
||||
puts "\nServer configuration information is missing. Please login using `inspec compliance login`"
|
||||
exit 1
|
||||
end
|
||||
|
||||
desc 'logout', 'user logout from Chef Compliance'
|
||||
def logout
|
||||
config = Compliance::Configuration.new
|
||||
unless config.supported?(:oidc) || config['token'].nil? || config['server_type'] == 'automate'
|
||||
config = Compliance::Configuration.new
|
||||
url = "#{config['server']}/logout"
|
||||
Compliance::HTTP.post(url, config['token'], config['insecure'], !config.supported?(:oidc))
|
||||
end
|
||||
success = config.destroy
|
||||
|
||||
if success
|
||||
puts 'Successfully logged out'
|
||||
else
|
||||
puts 'Could not log out'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def loggedin(config)
|
||||
serverknown = !config['server'].nil?
|
||||
puts 'You need to login first with `inspec compliance login`' if !serverknown
|
||||
serverknown
|
||||
end
|
||||
end
|
||||
|
||||
# register the subcommand to Inspec CLI registry
|
||||
Inspec::Plugins::CLI.add_subcommand(ComplianceCLI, 'compliance', 'compliance SUBCOMMAND ...', 'Chef Compliance commands', {})
|
||||
end
|
|
@ -1,103 +1,4 @@
|
|||
# encoding: utf-8
|
||||
# author: Christoph Hartmann
|
||||
# author: Dominik Richter
|
||||
# This file has been moved to the v2.0 plugins. This redirect allows for legacy use.
|
||||
# TODO: Remove in inspec 4.0
|
||||
|
||||
module Compliance
|
||||
# stores configuration on local filesystem
|
||||
class Configuration
|
||||
def initialize
|
||||
@config_path = File.join(Dir.home, '.inspec', 'compliance')
|
||||
# ensure the directory is available
|
||||
unless File.directory?(@config_path)
|
||||
FileUtils.mkdir_p(@config_path)
|
||||
end
|
||||
# set config file path
|
||||
@config_file = File.join(@config_path, '/config.json')
|
||||
@config = {}
|
||||
|
||||
# load the data
|
||||
get
|
||||
end
|
||||
|
||||
# direct access to config
|
||||
def [](key)
|
||||
@config[key]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@config[key] = value
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
@config.key?(key)
|
||||
end
|
||||
|
||||
def clean
|
||||
@config = {}
|
||||
end
|
||||
|
||||
# return the json data
|
||||
def get
|
||||
if File.exist?(@config_file)
|
||||
file = File.read(@config_file)
|
||||
@config = JSON.parse(file)
|
||||
end
|
||||
@config
|
||||
end
|
||||
|
||||
# stores a hash to json
|
||||
def store
|
||||
File.open(@config_file, 'w') do |f|
|
||||
f.chmod(0600)
|
||||
f.write(@config.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
# deletes data
|
||||
def destroy
|
||||
if File.exist?(@config_file)
|
||||
File.delete(@config_file)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# return if the (stored) api version does not support a certain feature
|
||||
def supported?(feature)
|
||||
sup = version_with_support(feature)
|
||||
|
||||
# we do not know the version, therefore we do not know if its possible to use the feature
|
||||
return if self['version'].nil? || self['version']['version'].nil?
|
||||
|
||||
if sup.is_a?(Array)
|
||||
Gem::Version.new(self['version']['version']) >= sup[0] &&
|
||||
Gem::Version.new(self['version']['version']) < sup[1]
|
||||
else
|
||||
Gem::Version.new(self['version']['version']) >= sup
|
||||
end
|
||||
end
|
||||
|
||||
# exit 1 if the version of compliance that we're working with doesn't support odic
|
||||
def legacy_check!(feature)
|
||||
return if supported?(feature)
|
||||
|
||||
puts "This feature (#{feature}) is not available for legacy installations."
|
||||
puts 'Please upgrade to a recent version of Chef Compliance.'
|
||||
exit 1
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# for a feature, returns either:
|
||||
# - a version v0: v supports v0 iff v0 <= v
|
||||
# - an array [v0, v1] of two versions: v supports [v0, v1] iff v0 <= v < v1
|
||||
def version_with_support(feature)
|
||||
case feature.to_sym
|
||||
when :oidc
|
||||
Gem::Version.new('0.16.19')
|
||||
else
|
||||
Gem::Version.new('0.0.0')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
require 'plugins/inspec-compliance/lib/inspec-compliance/configuration'
|
||||
|
|
|
@ -1,116 +1,4 @@
|
|||
# encoding: utf-8
|
||||
# author: Christoph Hartmann
|
||||
# author: Dominik Richter
|
||||
# This file has been moved to the v2.0 plugins. This redirect allows for legacy use.
|
||||
# TODO: Remove in inspec 4.0
|
||||
|
||||
require 'net/http'
|
||||
require 'net/http/post/multipart'
|
||||
require 'uri'
|
||||
|
||||
module Compliance
|
||||
# implements a simple http abstraction on top of Net::HTTP
|
||||
class HTTP
|
||||
# generic get requires
|
||||
def self.get(url, headers = nil, insecure)
|
||||
uri = _parse_url(url)
|
||||
req = Net::HTTP::Get.new(uri.path)
|
||||
headers&.each do |key, value|
|
||||
req.add_field(key, value)
|
||||
end
|
||||
send_request(uri, req, insecure)
|
||||
end
|
||||
|
||||
# generic post request
|
||||
def self.post(url, token, insecure, basic_auth = false)
|
||||
# form request
|
||||
uri = _parse_url(url)
|
||||
req = Net::HTTP::Post.new(uri.path)
|
||||
if basic_auth
|
||||
req.basic_auth token, ''
|
||||
else
|
||||
req['Authorization'] = "Bearer #{token}"
|
||||
end
|
||||
req.form_data={}
|
||||
|
||||
send_request(uri, req, insecure)
|
||||
end
|
||||
|
||||
def self.post_with_headers(url, headers, body, insecure)
|
||||
uri = _parse_url(url)
|
||||
req = Net::HTTP::Post.new(uri.path)
|
||||
req.body = body unless body.nil?
|
||||
headers&.each do |key, value|
|
||||
req.add_field(key, value)
|
||||
end
|
||||
send_request(uri, req, insecure)
|
||||
end
|
||||
|
||||
# post a file
|
||||
def self.post_file(url, headers, file_path, insecure)
|
||||
uri = _parse_url(url)
|
||||
raise "Unable to parse URL: #{url}" if uri.nil? || uri.host.nil?
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
||||
# set connection flags
|
||||
http.use_ssl = (uri.scheme == 'https')
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if insecure
|
||||
|
||||
req = Net::HTTP::Post.new(uri.path)
|
||||
headers.each do |key, value|
|
||||
req.add_field(key, value)
|
||||
end
|
||||
|
||||
req.body_stream=File.open(file_path, 'rb')
|
||||
req.add_field('Content-Length', File.size(file_path))
|
||||
req.add_field('Content-Type', 'application/x-gzip')
|
||||
|
||||
boundary = 'INSPEC-PROFILE-UPLOAD'
|
||||
req.add_field('session', boundary)
|
||||
res=http.request(req)
|
||||
res
|
||||
end
|
||||
|
||||
def self.post_multipart_file(url, headers, file_path, insecure)
|
||||
uri = _parse_url(url)
|
||||
raise "Unable to parse URL: #{url}" if uri.nil? || uri.host.nil?
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
||||
# set connection flags
|
||||
http.use_ssl = (uri.scheme == 'https')
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if insecure
|
||||
|
||||
File.open(file_path) do |tar|
|
||||
req = Net::HTTP::Post::Multipart.new(uri, 'file' => UploadIO.new(tar, 'application/x-gzip', File.basename(file_path)))
|
||||
headers.each do |key, value|
|
||||
req.add_field(key, value)
|
||||
end
|
||||
res = http.request(req)
|
||||
return res
|
||||
end
|
||||
end
|
||||
|
||||
# sends a http requests
|
||||
def self.send_request(uri, req, insecure)
|
||||
opts = {
|
||||
use_ssl: uri.scheme == 'https',
|
||||
}
|
||||
opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if insecure
|
||||
|
||||
raise "Unable to parse URI: #{uri}" if uri.nil? || uri.host.nil?
|
||||
res = Net::HTTP.start(uri.host, uri.port, opts) { |http|
|
||||
http.request(req)
|
||||
}
|
||||
res
|
||||
rescue OpenSSL::SSL::SSLError => e
|
||||
raise e unless e.message.include? 'certificate verify failed'
|
||||
|
||||
puts "Error: Failed to connect to #{uri}."
|
||||
puts 'If the server uses a self-signed certificate, please re-run the login command with the --insecure option.'
|
||||
exit 1
|
||||
end
|
||||
|
||||
def self._parse_url(url)
|
||||
url = "https://#{url}" if URI.parse(url).scheme.nil?
|
||||
URI.parse(url)
|
||||
end
|
||||
end
|
||||
end
|
||||
require 'plugins/inspec-compliance/lib/inspec-compliance/http'
|
||||
|
|
|
@ -1,36 +1,4 @@
|
|||
# encoding: utf-8
|
||||
# author: Christoph Hartmann
|
||||
# author: Dominik Richter
|
||||
# This file has been moved to the v2.0 plugins. This redirect allows for legacy use.
|
||||
# TODO: Remove in inspec 4.0
|
||||
|
||||
module Compliance
|
||||
# is a helper that provides information which version of compliance supports
|
||||
# which feature
|
||||
class Support
|
||||
# for a feature, returns either:
|
||||
# - a version v0: v supports v0 iff v0 <= v
|
||||
# - an array [v0, v1] of two versions: v supports [v0, v1] iff v0 <= v < v1
|
||||
def self.version_with_support(feature)
|
||||
case feature.to_sym
|
||||
when :oidc # open id connect authentication
|
||||
Gem::Version.new('0.16.19')
|
||||
else
|
||||
Gem::Version.new('0.0.0')
|
||||
end
|
||||
end
|
||||
|
||||
# determines if the given version support a certain feature
|
||||
def self.supported?(feature, version)
|
||||
sup = version_with_support(feature)
|
||||
|
||||
if sup.is_a?(Array)
|
||||
Gem::Version.new(version) >= sup[0] &&
|
||||
Gem::Version.new(version) < sup[1]
|
||||
else
|
||||
Gem::Version.new(version) >= sup
|
||||
end
|
||||
end
|
||||
|
||||
# we do not know the version, therefore we do not know if its possible to use the feature
|
||||
# return if self['version'].nil? || self['version']['version'].nil?
|
||||
end
|
||||
end
|
||||
require 'plugins/inspec-compliance/lib/inspec-compliance/support'
|
||||
|
|
|
@ -1,143 +1,4 @@
|
|||
# encoding: utf-8
|
||||
# author: Christoph Hartmann
|
||||
# author: Dominik Richter
|
||||
# This file has been moved to the v2.0 plugins. This redirect allows for legacy use.
|
||||
# TODO: Remove in inspec 4.0
|
||||
|
||||
require 'uri'
|
||||
require 'inspec/fetcher'
|
||||
require 'inspec/errors'
|
||||
|
||||
# InSpec Target Helper for Chef Compliance
|
||||
# reuses UrlHelper, but it knows the target server and the access token already
|
||||
# similar to `inspec exec http://localhost:2134/owners/%base%/compliance/%ssh%/tar --user %token%`
|
||||
module Compliance
|
||||
class Fetcher < Fetchers::Url
|
||||
name 'compliance'
|
||||
priority 500
|
||||
attr_reader :upstream_sha256
|
||||
|
||||
def initialize(target, opts)
|
||||
super(target, opts)
|
||||
@upstream_sha256 = ''
|
||||
if target.is_a?(Hash) && target.key?(:url)
|
||||
@target = target[:url]
|
||||
@upstream_sha256 = target[:sha256]
|
||||
elsif target.is_a?(String)
|
||||
@target = target
|
||||
end
|
||||
end
|
||||
|
||||
def sha256
|
||||
upstream_sha256.empty? ? super : upstream_sha256
|
||||
end
|
||||
|
||||
def self.check_compliance_token(uri, config)
|
||||
if config['token'].nil? && config['refresh_token'].nil?
|
||||
if config['server_type'] == 'automate'
|
||||
server = 'automate'
|
||||
msg = 'inspec compliance login https://your_automate_server --user USER --ent ENT --dctoken DCTOKEN or --token USERTOKEN'
|
||||
elsif config['server_type'] == 'automate2'
|
||||
server = 'automate2'
|
||||
msg = 'inspec compliance login https://your_automate2_server --user USER --token APITOKEN'
|
||||
else
|
||||
server = 'compliance'
|
||||
msg = "inspec compliance login https://your_compliance_server --user admin --insecure --token 'PASTE TOKEN HERE' "
|
||||
end
|
||||
raise Inspec::FetcherFailure, <<~EOF
|
||||
|
||||
Cannot fetch #{uri} because your #{server} token has not been
|
||||
configured.
|
||||
|
||||
Please login using
|
||||
|
||||
#{msg}
|
||||
EOF
|
||||
end
|
||||
end
|
||||
|
||||
def self.get_target_uri(target)
|
||||
if target.is_a?(String) && URI(target).scheme == 'compliance'
|
||||
URI(target)
|
||||
elsif target.respond_to?(:key?) && target.key?(:compliance)
|
||||
URI("compliance://#{target[:compliance]}")
|
||||
end
|
||||
end
|
||||
|
||||
def self.resolve(target)
|
||||
uri = get_target_uri(target)
|
||||
return nil if uri.nil?
|
||||
|
||||
config = Compliance::Configuration.new
|
||||
profile = Compliance::API.sanitize_profile_name(uri)
|
||||
profile_fetch_url = Compliance::API.target_url(config, profile)
|
||||
# we have detailed information available in our lockfile, no need to ask the server
|
||||
if target.respond_to?(:key?) && target.key?(:sha256)
|
||||
profile_checksum = target[:sha256]
|
||||
else
|
||||
check_compliance_token(uri, config)
|
||||
# verifies that the target e.g base/ssh exists
|
||||
# Call profiles directly instead of exist? to capture the results
|
||||
# so we can access the upstream sha256 from the results.
|
||||
_msg, profile_result = Compliance::API.profiles(config, profile)
|
||||
if profile_result.empty?
|
||||
raise Inspec::FetcherFailure, "The compliance profile #{profile} was not found on the configured compliance server"
|
||||
else
|
||||
# Guarantee sorting by verison and grab the latest.
|
||||
# If version was specified, it will be the first and only result.
|
||||
# Note we are calling the sha256 as a string, not a symbol since
|
||||
# it was returned as json from the Compliance API.
|
||||
profile_info = profile_result.sort_by { |x| Gem::Version.new(x['version']) }[0]
|
||||
profile_checksum = profile_info.key?('sha256') ? profile_info['sha256'] : ''
|
||||
end
|
||||
end
|
||||
# We need to pass the token to the fetcher
|
||||
config['token'] = Compliance::API.get_token(config)
|
||||
|
||||
# Needed for automate2 post request
|
||||
profile_stub = profile || target[:compliance]
|
||||
config['profile'] = Compliance::API.profile_split(profile_stub)
|
||||
|
||||
new({ url: profile_fetch_url, sha256: profile_checksum }, config)
|
||||
rescue URI::Error => _e
|
||||
nil
|
||||
end
|
||||
|
||||
# We want to save compliance: in the lockfile rather than url: to
|
||||
# make sure we go back through the Compliance API handling.
|
||||
def resolved_source
|
||||
@resolved_source ||= {
|
||||
compliance: compliance_profile_name,
|
||||
url: @target,
|
||||
sha256: sha256,
|
||||
}
|
||||
end
|
||||
|
||||
def to_s
|
||||
'Chef Compliance Profile Loader'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# determine the owner_id and the profile name from the url
|
||||
def compliance_profile_name
|
||||
m = if Compliance::API.is_automate_server_pre_080?(@config)
|
||||
%r{^#{@config['server']}/(?<owner>[^/]+)/(?<id>[^/]+)/tar$}
|
||||
elsif Compliance::API.is_automate_server_080_and_later?(@config)
|
||||
%r{^#{@config['server']}/profiles/(?<owner>[^/]+)/(?<id>[^/]+)/tar$}
|
||||
else
|
||||
%r{^#{@config['server']}/owners/(?<owner>[^/]+)/compliance/(?<id>[^/]+)/tar$}
|
||||
end.match(@target)
|
||||
|
||||
if Compliance::API.is_automate2_server?(@config)
|
||||
m = {}
|
||||
m[:owner] = @config['profile'][0]
|
||||
m[:id] = @config['profile'][1]
|
||||
end
|
||||
|
||||
raise 'Unable to determine compliance profile name. This can be caused by ' \
|
||||
'an incorrect server in your configuration. Try to login to compliance ' \
|
||||
'via the `inspec compliance login` command.' if m.nil?
|
||||
|
||||
"#{m[:owner]}/#{m[:id]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
require 'plugins/inspec-compliance/lib/inspec-compliance/target'
|
||||
|
|
|
@ -292,7 +292,10 @@ module Inspec
|
|||
end
|
||||
|
||||
# check for compliance settings
|
||||
Compliance::API.login(o['compliance']) if o['compliance']
|
||||
if o['compliance']
|
||||
require 'plugins/inspec-compliance/lib/inspec-compliance/api'
|
||||
InspecPlugins::Compliance::API.login(o['compliance'])
|
||||
end
|
||||
|
||||
o
|
||||
end
|
||||
|
|
12
lib/plugins/inspec-compliance/lib/inspec-compliance.rb
Normal file
12
lib/plugins/inspec-compliance/lib/inspec-compliance.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
module InspecPlugins
|
||||
module Compliance
|
||||
class Plugin < Inspec.plugin(2)
|
||||
plugin_name :'inspec-compliance'
|
||||
|
||||
cli_command :compliance do
|
||||
require_relative 'inspec-compliance/cli'
|
||||
InspecPlugins::Compliance::CLI
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
358
lib/plugins/inspec-compliance/lib/inspec-compliance/api.rb
Normal file
358
lib/plugins/inspec-compliance/lib/inspec-compliance/api.rb
Normal file
|
@ -0,0 +1,358 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
require 'json'
|
||||
|
||||
require_relative 'api/login'
|
||||
require_relative 'configuration'
|
||||
require_relative 'http'
|
||||
require_relative 'target'
|
||||
require_relative 'support'
|
||||
|
||||
module InspecPlugins
|
||||
module Compliance
|
||||
class ServerConfigurationMissing < StandardError; end
|
||||
|
||||
# API Implementation does not hold any state by itself,
|
||||
# everything will be stored in local Configuration store
|
||||
class API
|
||||
extend InspecPlugins::Compliance::API::Login
|
||||
|
||||
# return all compliance profiles available for the user
|
||||
# the user is either specified in the options hash or by default
|
||||
# the username of the account is used that is logged in
|
||||
def self.profiles(config, profile_filter = nil) # rubocop:disable PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength
|
||||
owner = config['owner'] || config['user']
|
||||
|
||||
# Chef Compliance
|
||||
if is_compliance_server?(config)
|
||||
url = "#{config['server']}/user/compliance"
|
||||
# Chef Automate2
|
||||
elsif is_automate2_server?(config)
|
||||
url = "#{config['server']}/compliance/profiles/search"
|
||||
# Chef Automate
|
||||
elsif is_automate_server?(config)
|
||||
url = "#{config['server']}/profiles/#{owner}"
|
||||
else
|
||||
raise ServerConfigurationMissing
|
||||
end
|
||||
|
||||
headers = get_headers(config)
|
||||
if profile_filter
|
||||
_owner, id, ver = profile_split(profile_filter)
|
||||
else
|
||||
id, ver = nil
|
||||
end
|
||||
|
||||
if is_automate2_server?(config)
|
||||
body = { owner: owner, name: id }.to_json
|
||||
response = InspecPlugins::Compliance::HTTP.post_with_headers(url, headers, body, config['insecure'])
|
||||
else
|
||||
response = InspecPlugins::Compliance::HTTP.get(url, headers, config['insecure'])
|
||||
end
|
||||
data = response.body
|
||||
response_code = response.code
|
||||
case response_code
|
||||
when '200'
|
||||
msg = 'success'
|
||||
profiles = JSON.parse(data)
|
||||
# iterate over profiles
|
||||
if is_compliance_server?(config)
|
||||
mapped_profiles = []
|
||||
profiles.values.each { |org|
|
||||
mapped_profiles += org.values
|
||||
}
|
||||
# Chef Automate pre 0.8.0
|
||||
elsif is_automate_server_pre_080?(config)
|
||||
mapped_profiles = profiles.values.flatten
|
||||
elsif is_automate2_server?(config)
|
||||
mapped_profiles = []
|
||||
profiles['profiles'].each { |p|
|
||||
mapped_profiles << p
|
||||
}
|
||||
else
|
||||
mapped_profiles = profiles.map { |e|
|
||||
e['owner_id'] = owner
|
||||
e
|
||||
}
|
||||
end
|
||||
# filter by name and version if they were specified in profile_filter
|
||||
mapped_profiles.select! do |p|
|
||||
(!ver || p['version'] == ver) && (!id || p['name'] == id)
|
||||
end
|
||||
return msg, mapped_profiles
|
||||
when '401'
|
||||
msg = '401 Unauthorized. Please check your token.'
|
||||
return msg, []
|
||||
else
|
||||
msg = "An unexpected error occurred (HTTP #{response_code}): #{response.message}"
|
||||
return msg, []
|
||||
end
|
||||
end
|
||||
|
||||
# return the server api version
|
||||
# NB this method does not use Compliance::Configuration to allow for using
|
||||
# it before we know the version (e.g. oidc or not)
|
||||
def self.version(config)
|
||||
url = config['server']
|
||||
insecure = config['insecure']
|
||||
|
||||
raise ServerConfigurationMissing if url.nil?
|
||||
|
||||
headers = get_headers(config)
|
||||
response = InspecPlugins::Compliance::HTTP.get(url+'/version', headers, insecure)
|
||||
return {} if response.code == '404'
|
||||
|
||||
data = response.body
|
||||
return {} if data.nil? || data.empty?
|
||||
|
||||
parsed = JSON.parse(data)
|
||||
return {} unless parsed.key?('version') && !parsed['version'].empty?
|
||||
|
||||
parsed
|
||||
end
|
||||
|
||||
# verifies that a profile exists
|
||||
def self.exist?(config, profile)
|
||||
_msg, profiles = InspecPlugins::Compliance::API.profiles(config, profile)
|
||||
!profiles.empty?
|
||||
end
|
||||
|
||||
def self.upload(config, owner, profile_name, archive_path)
|
||||
# Chef Compliance
|
||||
if is_compliance_server?(config)
|
||||
url = "#{config['server']}/owners/#{owner}/compliance/#{profile_name}/tar"
|
||||
# Chef Automate pre 0.8.0
|
||||
elsif is_automate_server_pre_080?(config)
|
||||
url = "#{config['server']}/#{owner}"
|
||||
elsif is_automate2_server?(config)
|
||||
url = "#{config['server']}/compliance/profiles?owner=#{owner}"
|
||||
# Chef Automate
|
||||
else
|
||||
url = "#{config['server']}/profiles/#{owner}"
|
||||
end
|
||||
|
||||
headers = get_headers(config)
|
||||
if is_automate2_server?(config)
|
||||
res = InspecPlugins::Compliance::HTTP.post_multipart_file(url, headers, archive_path, config['insecure'])
|
||||
else
|
||||
res = InspecPlugins::Compliance::HTTP.post_file(url, headers, archive_path, config['insecure'])
|
||||
end
|
||||
|
||||
[res.is_a?(Net::HTTPSuccess), res.body]
|
||||
end
|
||||
|
||||
# Use username and refresh_token to get an API access token
|
||||
def self.get_token_via_refresh_token(url, refresh_token, insecure)
|
||||
uri = URI.parse("#{url}/login")
|
||||
req = Net::HTTP::Post.new(uri.path)
|
||||
req.body = { token: refresh_token }.to_json
|
||||
access_token = nil
|
||||
response = InspecPlugins::Compliance::HTTP.send_request(uri, req, insecure)
|
||||
data = response.body
|
||||
if response.code == '200'
|
||||
begin
|
||||
tokendata = JSON.parse(data)
|
||||
access_token = tokendata['access_token']
|
||||
msg = 'Successfully fetched API access token'
|
||||
success = true
|
||||
rescue JSON::ParserError => e
|
||||
success = false
|
||||
msg = e.message
|
||||
end
|
||||
else
|
||||
success = false
|
||||
msg = "Failed to authenticate to #{url} \n\
|
||||
Response code: #{response.code}\n Body: #{response.body}"
|
||||
end
|
||||
|
||||
[success, msg, access_token]
|
||||
end
|
||||
|
||||
# Use username and password to get an API access token
|
||||
def self.get_token_via_password(url, username, password, insecure)
|
||||
uri = URI.parse("#{url}/login")
|
||||
req = Net::HTTP::Post.new(uri.path)
|
||||
req.body = { userid: username, password: password }.to_json
|
||||
access_token = nil
|
||||
response = InspecPlugins::Compliance::HTTP.send_request(uri, req, insecure)
|
||||
data = response.body
|
||||
if response.code == '200'
|
||||
access_token = data
|
||||
msg = 'Successfully fetched an API access token valid for 12 hours'
|
||||
success = true
|
||||
else
|
||||
success = false
|
||||
msg = "Failed to authenticate to #{url} \n\
|
||||
Response code: #{response.code}\n Body: #{response.body}"
|
||||
end
|
||||
|
||||
[success, msg, access_token]
|
||||
end
|
||||
|
||||
def self.get_headers(config)
|
||||
token = get_token(config)
|
||||
if is_automate_server?(config) || is_automate2_server?(config)
|
||||
headers = { 'chef-delivery-enterprise' => config['automate']['ent'] }
|
||||
if config['automate']['token_type'] == 'dctoken'
|
||||
headers['x-data-collector-token'] = token
|
||||
else
|
||||
headers['chef-delivery-user'] = config['user']
|
||||
headers['chef-delivery-token'] = token
|
||||
end
|
||||
else
|
||||
headers = { 'Authorization' => "Bearer #{token}" }
|
||||
end
|
||||
headers
|
||||
end
|
||||
|
||||
def self.get_token(config)
|
||||
return config['token'] unless config['refresh_token']
|
||||
_success, _msg, token = get_token_via_refresh_token(config['server'], config['refresh_token'], config['insecure'])
|
||||
token
|
||||
end
|
||||
|
||||
def self.target_url(config, profile)
|
||||
owner, id, ver = profile_split(profile)
|
||||
|
||||
return "#{config['server']}/compliance/profiles/tar" if is_automate2_server?(config)
|
||||
return "#{config['server']}/owners/#{owner}/compliance/#{id}/tar" unless is_automate_server?(config)
|
||||
|
||||
if ver.nil?
|
||||
"#{config['server']}/profiles/#{owner}/#{id}/tar"
|
||||
else
|
||||
"#{config['server']}/profiles/#{owner}/#{id}/version/#{ver}/tar"
|
||||
end
|
||||
end
|
||||
|
||||
def self.profile_split(profile)
|
||||
owner, id = profile.split('/')
|
||||
id, version = id.split('#')
|
||||
[owner, id, version]
|
||||
end
|
||||
|
||||
# returns a parsed url for `admin/profile` or `compliance://admin/profile`
|
||||
def self.sanitize_profile_name(profile)
|
||||
if URI(profile).scheme == 'compliance'
|
||||
uri = URI(profile)
|
||||
else
|
||||
uri = URI("compliance://#{profile}")
|
||||
end
|
||||
uri.to_s.sub(%r{^compliance:\/\/}, '')
|
||||
end
|
||||
|
||||
def self.is_compliance_server?(config)
|
||||
config['server_type'] == 'compliance'
|
||||
end
|
||||
|
||||
def self.is_automate_server_pre_080?(config)
|
||||
# Automate versions before 0.8.x do not have a valid version in the config
|
||||
return false unless config['server_type'] == 'automate'
|
||||
server_version_from_config(config).nil?
|
||||
end
|
||||
|
||||
def self.is_automate_server_080_and_later?(config)
|
||||
# Automate versions 0.8.x and later will have a "version" key in the config
|
||||
# that is properly parsed out via server_version_from_config below
|
||||
return false unless config['server_type'] == 'automate'
|
||||
!server_version_from_config(config).nil?
|
||||
end
|
||||
|
||||
def self.is_automate2_server?(config)
|
||||
config['server_type'] == 'automate2'
|
||||
end
|
||||
|
||||
def self.is_automate_server?(config)
|
||||
config['server_type'] == 'automate'
|
||||
end
|
||||
|
||||
def self.server_version_from_config(config)
|
||||
# Automate versions 0.8.x and later will have a "version" key in the config
|
||||
# that looks like: "version":{"api":"compliance","version":"0.8.24"}
|
||||
return nil unless config.key?('version')
|
||||
return nil unless config['version'].is_a?(Hash)
|
||||
config['version']['version']
|
||||
end
|
||||
|
||||
def self.determine_server_type(url, insecure)
|
||||
if target_is_automate2_server?(url, insecure)
|
||||
:automate2
|
||||
elsif target_is_automate_server?(url, insecure)
|
||||
:automate
|
||||
elsif target_is_compliance_server?(url, insecure)
|
||||
:compliance
|
||||
else
|
||||
Inspec::Log.debug('Could not determine server type using known endpoints')
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.target_is_automate2_server?(url, insecure)
|
||||
automate_endpoint = '/dex/auth'
|
||||
response = InspecPlugins::Compliance::HTTP.get(url + automate_endpoint, nil, insecure)
|
||||
if response.code == '400'
|
||||
Inspec::Log.debug(
|
||||
"Received 400 from #{url}#{automate_endpoint} - " \
|
||||
'assuming target is a Chef Automate2 instance',
|
||||
)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def self.target_is_automate_server?(url, insecure)
|
||||
automate_endpoint = '/compliance/version'
|
||||
response = InspecPlugins::Compliance::HTTP.get(url + automate_endpoint, nil, insecure)
|
||||
case response.code
|
||||
when '401'
|
||||
Inspec::Log.debug(
|
||||
"Received 401 from #{url}#{automate_endpoint} - " \
|
||||
'assuming target is a Chef Automate instance',
|
||||
)
|
||||
true
|
||||
when '200'
|
||||
# Chef Automate currently returns 401 for `/compliance/version` but some
|
||||
# versions of OpsWorks Chef Automate return 200 and a Chef Manage page
|
||||
# when unauthenticated requests are received.
|
||||
if response.body.include?('Are You Looking For the Chef Server?')
|
||||
Inspec::Log.debug(
|
||||
"Received 200 from #{url}#{automate_endpoint} - " \
|
||||
'assuming target is an OpsWorks Chef Automate instance',
|
||||
)
|
||||
true
|
||||
else
|
||||
Inspec::Log.debug(
|
||||
"Received 200 from #{url}#{automate_endpoint} " \
|
||||
'but did not receive the Chef Manage page - ' \
|
||||
'assuming target is not a Chef Automate instance',
|
||||
)
|
||||
false
|
||||
end
|
||||
else
|
||||
Inspec::Log.debug(
|
||||
"Received unexpected status code #{response.code} " \
|
||||
"from #{url}#{automate_endpoint} - " \
|
||||
'assuming target is not a Chef Automate instance',
|
||||
)
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def self.target_is_compliance_server?(url, insecure)
|
||||
# All versions of Chef Compliance return 200 for `/api/version`
|
||||
compliance_endpoint = '/api/version'
|
||||
|
||||
response = InspecPlugins::Compliance::HTTP.get(url + compliance_endpoint, nil, insecure)
|
||||
return false unless response.code == '200'
|
||||
|
||||
Inspec::Log.debug(
|
||||
"Received 200 from #{url}#{compliance_endpoint} - " \
|
||||
'assuming target is a Compliance server',
|
||||
)
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
192
lib/plugins/inspec-compliance/lib/inspec-compliance/api/login.rb
Normal file
192
lib/plugins/inspec-compliance/lib/inspec-compliance/api/login.rb
Normal file
|
@ -0,0 +1,192 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module InspecPlugins
|
||||
module Compliance
|
||||
class API
|
||||
module Login
|
||||
class CannotDetermineServerType < StandardError; end
|
||||
|
||||
def login(options)
|
||||
raise ArgumentError, 'Please specify a server using `inspec compliance login https://SERVER`' unless options['server']
|
||||
|
||||
options['server'] = URI("https://#{options['server']}").to_s if URI(options['server']).scheme.nil?
|
||||
|
||||
options['server_type'] = InspecPlugins::Compliance::API.determine_server_type(options['server'], options['insecure'])
|
||||
|
||||
case options['server_type']
|
||||
when :automate2
|
||||
Login::Automate2Server.login(options)
|
||||
when :automate
|
||||
Login::AutomateServer.login(options)
|
||||
when :compliance
|
||||
Login::ComplianceServer.login(options)
|
||||
else
|
||||
raise CannotDetermineServerType, "Unable to determine if #{options['server']} is a Chef Automate or Chef Compliance server"
|
||||
end
|
||||
end
|
||||
|
||||
module Automate2Server
|
||||
def self.login(options)
|
||||
verify_thor_options(options)
|
||||
|
||||
options['url'] = options['server'] + '/api/v0'
|
||||
token = options['dctoken'] || options['token']
|
||||
store_access_token(options, token)
|
||||
end
|
||||
|
||||
def self.store_access_token(options, token)
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
config.clean
|
||||
|
||||
config['automate'] = {}
|
||||
config['automate']['ent'] = 'automate'
|
||||
config['automate']['token_type'] = 'dctoken'
|
||||
config['server'] = options['url']
|
||||
config['user'] = options['user']
|
||||
config['owner'] = options['user']
|
||||
config['insecure'] = options['insecure'] || false
|
||||
config['server_type'] = options['server_type'].to_s
|
||||
config['token'] = token
|
||||
config['version'] = '0'
|
||||
|
||||
config.store
|
||||
config
|
||||
end
|
||||
|
||||
def self.verify_thor_options(o)
|
||||
error_msg = []
|
||||
|
||||
error_msg.push('Please specify a user using `--user=\'USER\'`') if o['user'].nil?
|
||||
|
||||
if o['token'].nil? && o['dctoken'].nil?
|
||||
error_msg.push('Please specify a token using `--token=\'APITOKEN\'`')
|
||||
end
|
||||
|
||||
raise ArgumentError, error_msg.join("\n") unless error_msg.empty?
|
||||
end
|
||||
end
|
||||
|
||||
module AutomateServer
|
||||
def self.login(options)
|
||||
verify_thor_options(options)
|
||||
|
||||
options['url'] = options['server'] + '/compliance'
|
||||
token = options['dctoken'] || options['token']
|
||||
store_access_token(options, token)
|
||||
end
|
||||
|
||||
def self.store_access_token(options, token)
|
||||
token_type = if options['token']
|
||||
'usertoken'
|
||||
else
|
||||
'dctoken'
|
||||
end
|
||||
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
|
||||
config.clean
|
||||
|
||||
config['automate'] = {}
|
||||
config['automate']['ent'] = options['ent']
|
||||
config['automate']['token_type'] = token_type
|
||||
config['server'] = options['url']
|
||||
config['user'] = options['user']
|
||||
config['insecure'] = options['insecure'] || false
|
||||
config['server_type'] = options['server_type'].to_s
|
||||
config['token'] = token
|
||||
config['version'] = InspecPlugins::Compliance::API.version(config)
|
||||
|
||||
config.store
|
||||
config
|
||||
end
|
||||
|
||||
# Automate login requires `--ent`, `--user`, and either `--token` or `--dctoken`
|
||||
def self.verify_thor_options(o)
|
||||
error_msg = []
|
||||
|
||||
error_msg.push('Please specify a user using `--user=\'USER\'`') if o['user'].nil?
|
||||
error_msg.push('Please specify an enterprise using `--ent=\'automate\'`') if o['ent'].nil?
|
||||
|
||||
if o['token'].nil? && o['dctoken'].nil?
|
||||
error_msg.push('Please specify a token using `--token=\'AUTOMATE_TOKEN\'` or `--dctoken=\'DATA_COLLECTOR_TOKEN\'`')
|
||||
end
|
||||
|
||||
raise ArgumentError, error_msg.join("\n") unless error_msg.empty?
|
||||
end
|
||||
end
|
||||
|
||||
module ComplianceServer
|
||||
def self.login(options)
|
||||
compliance_verify_thor_options(options)
|
||||
|
||||
options['url'] = options['server'] + '/api'
|
||||
|
||||
if options['user'] && options['token']
|
||||
compliance_store_access_token(options, options['token'])
|
||||
elsif options['user'] && options['password']
|
||||
compliance_login_user_pass(options)
|
||||
elsif options['refresh_token']
|
||||
compliance_login_refresh_token(options)
|
||||
end
|
||||
end
|
||||
|
||||
def self.compliance_login_user_pass(options)
|
||||
success, msg, token = InspecPlugins::Compliance::API.get_token_via_password(
|
||||
options['url'],
|
||||
options['user'],
|
||||
options['password'],
|
||||
options['insecure'],
|
||||
)
|
||||
|
||||
raise msg unless success
|
||||
compliance_store_access_token(options, token)
|
||||
end
|
||||
|
||||
def self.compliance_login_refresh_token(options)
|
||||
success, msg, token = InspecPlugins::Compliance::API.get_token_via_refresh_token(
|
||||
options['url'],
|
||||
options['refresh_token'],
|
||||
options['insecure'],
|
||||
)
|
||||
|
||||
raise msg unless success
|
||||
compliance_store_access_token(options, token)
|
||||
end
|
||||
|
||||
def self.compliance_store_access_token(options, token)
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
config.clean
|
||||
|
||||
config['user'] = options['user'] if options['user']
|
||||
config['server'] = options['url']
|
||||
config['insecure'] = options['insecure'] || false
|
||||
config['server_type'] = options['server_type'].to_s
|
||||
config['token'] = token
|
||||
config['version'] = InspecPlugins::Compliance::API.version(config)
|
||||
|
||||
config.store
|
||||
config
|
||||
end
|
||||
|
||||
# Compliance login requires `--user` or `--refresh_token`
|
||||
# If `--user` then either `--password`, `--token`, or `--refresh-token`, is required
|
||||
def self.compliance_verify_thor_options(o)
|
||||
error_msg = []
|
||||
|
||||
error_msg.push('Please specify a server using `inspec compliance login https://SERVER`') if o['server'].nil?
|
||||
|
||||
if o['user'].nil? && o['refresh_token'].nil?
|
||||
error_msg.push('Please specify a `--user=\'USER\'` or a `--refresh-token=\'TOKEN\'`')
|
||||
end
|
||||
|
||||
if o['user'] && o['password'].nil? && o['token'].nil? && o['refresh_token'].nil?
|
||||
error_msg.push('Please specify either a `--password`, `--token`, or `--refresh-token`')
|
||||
end
|
||||
|
||||
raise ArgumentError, error_msg.join("\n") unless error_msg.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
266
lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb
Normal file
266
lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb
Normal file
|
@ -0,0 +1,266 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require_relative 'api'
|
||||
|
||||
module InspecPlugins
|
||||
module Compliance
|
||||
class CLI < Inspec.plugin(2, :cli_command)
|
||||
subcommand_desc 'compliance SUBCOMMAND', 'Chef Compliance commands'
|
||||
|
||||
# desc "login https://SERVER --insecure --user='USER' --ent='ENTERPRISE' --token='TOKEN'", 'Log in to a Chef Compliance/Chef Automate SERVER'
|
||||
desc 'login', 'Log in to a Chef Compliance/Chef Automate SERVER'
|
||||
long_desc <<-LONGDESC
|
||||
`login` allows you to use InSpec with Chef Automate or a Chef Compliance Server
|
||||
|
||||
You need to a token for communication. More information about token retrieval
|
||||
is available at:
|
||||
https://docs.chef.io/api_automate.html#authentication-methods
|
||||
https://docs.chef.io/api_compliance.html#obtaining-an-api-token
|
||||
LONGDESC
|
||||
option :insecure, aliases: :k, type: :boolean,
|
||||
desc: 'Explicitly allows InSpec to perform "insecure" SSL connections and transfers'
|
||||
option :user, type: :string, required: false,
|
||||
desc: 'Username'
|
||||
option :password, type: :string, required: false,
|
||||
desc: 'Password (Chef Compliance Only)'
|
||||
option :token, type: :string, required: false,
|
||||
desc: 'Access token'
|
||||
option :refresh_token, type: :string, required: false,
|
||||
desc: 'Chef Compliance refresh token (Chef Compliance Only)'
|
||||
option :dctoken, type: :string, required: false,
|
||||
desc: 'Data Collector token (Chef Automate Only)'
|
||||
option :ent, type: :string, required: false,
|
||||
desc: 'Enterprise for Chef Automate reporting (Chef Automate Only)'
|
||||
def login(server)
|
||||
options['server'] = server
|
||||
InspecPlugins::Compliance::API.login(options)
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
puts "Stored configuration for Chef #{config['server_type'].capitalize}: #{config['server']}' with user: '#{config['user']}'"
|
||||
end
|
||||
|
||||
desc 'profiles', 'list all available profiles in Chef Compliance'
|
||||
option :owner, type: :string, required: false,
|
||||
desc: 'owner whose profiles to list'
|
||||
def profiles
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
return if !loggedin(config)
|
||||
|
||||
# set owner to config
|
||||
config['owner'] = options['owner'] || config['user']
|
||||
|
||||
msg, profiles = InspecPlugins::Compliance::API.profiles(config)
|
||||
profiles.sort_by! { |hsh| hsh['title'] }
|
||||
if !profiles.empty?
|
||||
# iterate over profiles
|
||||
headline('Available profiles:')
|
||||
profiles.each { |profile|
|
||||
owner = profile['owner_id'] || profile['owner']
|
||||
li("#{profile['title']} v#{profile['version']} (#{mark_text(owner + '/' + profile['name'])})")
|
||||
}
|
||||
else
|
||||
puts msg if msg != 'success'
|
||||
puts 'Could not find any profiles'
|
||||
exit 1
|
||||
end
|
||||
rescue InspecPlugins::Compliance::ServerConfigurationMissing
|
||||
STDERR.puts "\nServer configuration information is missing. Please login using `inspec compliance login`"
|
||||
exit 1
|
||||
end
|
||||
|
||||
desc 'exec PROFILE', 'executes a Chef Compliance profile'
|
||||
exec_options
|
||||
def exec(*tests)
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
return if !loggedin(config)
|
||||
o = opts(:exec).dup
|
||||
diagnose(o)
|
||||
configure_logger(o)
|
||||
|
||||
# iterate over tests and add compliance scheme
|
||||
tests = tests.map { |t| 'compliance://' + InspecPlugins::Compliance::API.sanitize_profile_name(t) }
|
||||
|
||||
runner = Inspec::Runner.new(o)
|
||||
tests.each { |target| runner.add_target(target) }
|
||||
|
||||
exit runner.run
|
||||
rescue ArgumentError, RuntimeError, Train::UserError => e
|
||||
$stderr.puts e.message
|
||||
exit 1
|
||||
end
|
||||
|
||||
desc 'download PROFILE', 'downloads a profile from Chef Compliance'
|
||||
option :name, type: :string,
|
||||
desc: 'Name of the archive filename (file type will be added)'
|
||||
def download(profile_name)
|
||||
o = options.dup
|
||||
configure_logger(o)
|
||||
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
return if !loggedin(config)
|
||||
|
||||
profile_name = InspecPlugins::Compliance::API.sanitize_profile_name(profile_name)
|
||||
if InspecPlugins::Compliance::API.exist?(config, profile_name)
|
||||
puts "Downloading `#{profile_name}`"
|
||||
|
||||
fetcher = InspecPlugins::Compliance::Fetcher.resolve(
|
||||
{
|
||||
compliance: profile_name,
|
||||
},
|
||||
)
|
||||
|
||||
# we provide a name, the fetcher adds the extension
|
||||
_owner, id = profile_name.split('/')
|
||||
file_name = fetcher.fetch(o.name || id)
|
||||
puts "Profile stored to #{file_name}"
|
||||
else
|
||||
puts "Profile #{profile_name} is not available in Chef Compliance."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
desc 'upload PATH', 'uploads a local profile to Chef Compliance'
|
||||
option :overwrite, type: :boolean, default: false,
|
||||
desc: 'Overwrite existing profile on Server.'
|
||||
option :owner, type: :string, required: false,
|
||||
desc: 'Owner that should own the profile'
|
||||
def upload(path) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, PerceivedComplexity, Metrics/CyclomaticComplexity
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
return if !loggedin(config)
|
||||
|
||||
# set owner to config
|
||||
config['owner'] = options['owner'] || config['user']
|
||||
|
||||
unless File.exist?(path)
|
||||
puts "Directory #{path} does not exist."
|
||||
exit 1
|
||||
end
|
||||
|
||||
vendor_deps(path, options) if File.directory?(path)
|
||||
|
||||
o = options.dup
|
||||
configure_logger(o)
|
||||
|
||||
# only run against the mock backend, otherwise we run against the local system
|
||||
o[:backend] = Inspec::Backend.create(target: 'mock://')
|
||||
o[:check_mode] = true
|
||||
o[:vendor_cache] = Inspec::Cache.new(o[:vendor_cache])
|
||||
|
||||
# check the profile, we only allow to upload valid profiles
|
||||
profile = Inspec::Profile.for_target(path, o)
|
||||
|
||||
# start verification process
|
||||
error_count = 0
|
||||
error = lambda { |msg|
|
||||
error_count += 1
|
||||
puts msg
|
||||
}
|
||||
|
||||
result = profile.check
|
||||
unless result[:summary][:valid]
|
||||
error.call('Profile check failed. Please fix the profile before upload.')
|
||||
else
|
||||
puts('Profile is valid')
|
||||
end
|
||||
|
||||
# determine user information
|
||||
if (config['token'].nil? && config['refresh_token'].nil?) || config['user'].nil?
|
||||
error.call('Please login via `inspec compliance login`')
|
||||
end
|
||||
|
||||
# read profile name from inspec.yml
|
||||
profile_name = profile.params[:name]
|
||||
|
||||
# read profile version from inspec.yml
|
||||
profile_version = profile.params[:version]
|
||||
|
||||
# check that the profile is not uploaded already,
|
||||
# confirm upload to the user (overwrite with --force)
|
||||
if InspecPlugins::Compliance::API.exist?(config, "#{config['owner']}/#{profile_name}##{profile_version}") && !options['overwrite']
|
||||
error.call('Profile exists on the server, use --overwrite')
|
||||
end
|
||||
|
||||
# abort if we found an error
|
||||
if error_count > 0
|
||||
puts "Found #{error_count} error(s)"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# if it is a directory, tar it to tmp directory
|
||||
generated = false
|
||||
if File.directory?(path)
|
||||
generated = true
|
||||
archive_path = Dir::Tmpname.create([profile_name, '.tar.gz']) {}
|
||||
puts "Generate temporary profile archive at #{archive_path}"
|
||||
profile.archive({ output: archive_path, ignore_errors: false, overwrite: true })
|
||||
else
|
||||
archive_path = path
|
||||
end
|
||||
|
||||
puts "Start upload to #{config['owner']}/#{profile_name}"
|
||||
pname = ERB::Util.url_encode(profile_name)
|
||||
|
||||
if InspecPlugins::Compliance::API.is_automate_server?(config) || InspecPlugins::Compliance::API.is_automate2_server?(config)
|
||||
puts 'Uploading to Chef Automate'
|
||||
else
|
||||
puts 'Uploading to Chef Compliance'
|
||||
end
|
||||
success, msg = InspecPlugins::Compliance::API.upload(config, config['owner'], pname, archive_path)
|
||||
|
||||
# delete temp file if it was temporary generated
|
||||
File.delete(archive_path) if generated && File.exist?(archive_path)
|
||||
|
||||
if success
|
||||
puts 'Successfully uploaded profile'
|
||||
else
|
||||
puts 'Error during profile upload:'
|
||||
puts msg
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
desc 'version', 'displays the version of the Chef Compliance server'
|
||||
def version
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
info = InspecPlugins::Compliance::API.version(config)
|
||||
if !info.nil? && info['version']
|
||||
puts "Name: #{info['api']}"
|
||||
puts "Version: #{info['version']}"
|
||||
else
|
||||
puts 'Could not determine server version.'
|
||||
exit 1
|
||||
end
|
||||
rescue InspecPlugins::Compliance::ServerConfigurationMissing
|
||||
puts "\nServer configuration information is missing. Please login using `inspec compliance login`"
|
||||
exit 1
|
||||
end
|
||||
|
||||
desc 'logout', 'user logout from Chef Compliance'
|
||||
def logout
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
unless config.supported?(:oidc) || config['token'].nil? || config['server_type'] == 'automate'
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
url = "#{config['server']}/logout"
|
||||
InspecPlugins::Compliance::HTTP.post(url, config['token'], config['insecure'], !config.supported?(:oidc))
|
||||
end
|
||||
success = config.destroy
|
||||
|
||||
if success
|
||||
puts 'Successfully logged out'
|
||||
else
|
||||
puts 'Could not log out'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def loggedin(config)
|
||||
serverknown = !config['server'].nil?
|
||||
puts 'You need to login first with `inspec compliance login`' if !serverknown
|
||||
serverknown
|
||||
end
|
||||
end
|
||||
|
||||
# register the subcommand to Inspec CLI registry
|
||||
# Inspec::Plugins::CLI.add_subcommand(InspecPlugins::ComplianceCLI, 'compliance', 'compliance SUBCOMMAND ...', 'Chef InspecPlugins::Compliance commands', {})
|
||||
end
|
||||
end
|
|
@ -0,0 +1,103 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module InspecPlugins
|
||||
module Compliance
|
||||
# stores configuration on local filesystem
|
||||
class Configuration
|
||||
def initialize
|
||||
@config_path = File.join(Dir.home, '.inspec', 'compliance')
|
||||
# ensure the directory is available
|
||||
unless File.directory?(@config_path)
|
||||
FileUtils.mkdir_p(@config_path)
|
||||
end
|
||||
# set config file path
|
||||
@config_file = File.join(@config_path, '/config.json')
|
||||
@config = {}
|
||||
|
||||
# load the data
|
||||
get
|
||||
end
|
||||
|
||||
# direct access to config
|
||||
def [](key)
|
||||
@config[key]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@config[key] = value
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
@config.key?(key)
|
||||
end
|
||||
|
||||
def clean
|
||||
@config = {}
|
||||
end
|
||||
|
||||
# return the json data
|
||||
def get
|
||||
if File.exist?(@config_file)
|
||||
file = File.read(@config_file)
|
||||
@config = JSON.parse(file)
|
||||
end
|
||||
@config
|
||||
end
|
||||
|
||||
# stores a hash to json
|
||||
def store
|
||||
File.open(@config_file, 'w') do |f|
|
||||
f.chmod(0600)
|
||||
f.write(@config.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
# deletes data
|
||||
def destroy
|
||||
if File.exist?(@config_file)
|
||||
File.delete(@config_file)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# return if the (stored) api version does not support a certain feature
|
||||
def supported?(feature)
|
||||
sup = version_with_support(feature)
|
||||
|
||||
# we do not know the version, therefore we do not know if its possible to use the feature
|
||||
return if self['version'].nil? || self['version']['version'].nil?
|
||||
|
||||
if sup.is_a?(Array)
|
||||
Gem::Version.new(self['version']['version']) >= sup[0] &&
|
||||
Gem::Version.new(self['version']['version']) < sup[1]
|
||||
else
|
||||
Gem::Version.new(self['version']['version']) >= sup
|
||||
end
|
||||
end
|
||||
|
||||
# exit 1 if the version of compliance that we're working with doesn't support odic
|
||||
def legacy_check!(feature)
|
||||
return if supported?(feature)
|
||||
|
||||
puts "This feature (#{feature}) is not available for legacy installations."
|
||||
puts 'Please upgrade to a recent version of Chef Compliance.'
|
||||
exit 1
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# for a feature, returns either:
|
||||
# - a version v0: v supports v0 iff v0 <= v
|
||||
# - an array [v0, v1] of two versions: v supports [v0, v1] iff v0 <= v < v1
|
||||
def version_with_support(feature)
|
||||
case feature.to_sym
|
||||
when :oidc
|
||||
Gem::Version.new('0.16.19')
|
||||
else
|
||||
Gem::Version.new('0.0.0')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
116
lib/plugins/inspec-compliance/lib/inspec-compliance/http.rb
Normal file
116
lib/plugins/inspec-compliance/lib/inspec-compliance/http.rb
Normal file
|
@ -0,0 +1,116 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'net/http'
|
||||
require 'net/http/post/multipart'
|
||||
require 'uri'
|
||||
|
||||
module InspecPlugins
|
||||
module Compliance
|
||||
# implements a simple http abstraction on top of Net::HTTP
|
||||
class HTTP
|
||||
# generic get requires
|
||||
def self.get(url, headers = nil, insecure)
|
||||
uri = _parse_url(url)
|
||||
req = Net::HTTP::Get.new(uri.path)
|
||||
headers&.each do |key, value|
|
||||
req.add_field(key, value)
|
||||
end
|
||||
send_request(uri, req, insecure)
|
||||
end
|
||||
|
||||
# generic post request
|
||||
def self.post(url, token, insecure, basic_auth = false)
|
||||
# form request
|
||||
uri = _parse_url(url)
|
||||
req = Net::HTTP::Post.new(uri.path)
|
||||
if basic_auth
|
||||
req.basic_auth token, ''
|
||||
else
|
||||
req['Authorization'] = "Bearer #{token}"
|
||||
end
|
||||
req.form_data={}
|
||||
|
||||
send_request(uri, req, insecure)
|
||||
end
|
||||
|
||||
def self.post_with_headers(url, headers, body, insecure)
|
||||
uri = _parse_url(url)
|
||||
req = Net::HTTP::Post.new(uri.path)
|
||||
req.body = body unless body.nil?
|
||||
headers&.each do |key, value|
|
||||
req.add_field(key, value)
|
||||
end
|
||||
send_request(uri, req, insecure)
|
||||
end
|
||||
|
||||
# post a file
|
||||
def self.post_file(url, headers, file_path, insecure)
|
||||
uri = _parse_url(url)
|
||||
raise "Unable to parse URL: #{url}" if uri.nil? || uri.host.nil?
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
||||
# set connection flags
|
||||
http.use_ssl = (uri.scheme == 'https')
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if insecure
|
||||
|
||||
req = Net::HTTP::Post.new(uri.path)
|
||||
headers.each do |key, value|
|
||||
req.add_field(key, value)
|
||||
end
|
||||
|
||||
req.body_stream=File.open(file_path, 'rb')
|
||||
req.add_field('Content-Length', File.size(file_path))
|
||||
req.add_field('Content-Type', 'application/x-gzip')
|
||||
|
||||
boundary = 'INSPEC-PROFILE-UPLOAD'
|
||||
req.add_field('session', boundary)
|
||||
res=http.request(req)
|
||||
res
|
||||
end
|
||||
|
||||
def self.post_multipart_file(url, headers, file_path, insecure)
|
||||
uri = _parse_url(url)
|
||||
raise "Unable to parse URL: #{url}" if uri.nil? || uri.host.nil?
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
||||
# set connection flags
|
||||
http.use_ssl = (uri.scheme == 'https')
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if insecure
|
||||
|
||||
File.open(file_path) do |tar|
|
||||
req = Net::HTTP::Post::Multipart.new(uri, 'file' => UploadIO.new(tar, 'application/x-gzip', File.basename(file_path)))
|
||||
headers.each do |key, value|
|
||||
req.add_field(key, value)
|
||||
end
|
||||
res = http.request(req)
|
||||
return res
|
||||
end
|
||||
end
|
||||
|
||||
# sends a http requests
|
||||
def self.send_request(uri, req, insecure)
|
||||
opts = {
|
||||
use_ssl: uri.scheme == 'https',
|
||||
}
|
||||
opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if insecure
|
||||
|
||||
raise "Unable to parse URI: #{uri}" if uri.nil? || uri.host.nil?
|
||||
res = Net::HTTP.start(uri.host, uri.port, opts) { |http|
|
||||
http.request(req)
|
||||
}
|
||||
res
|
||||
rescue OpenSSL::SSL::SSLError => e
|
||||
raise e unless e.message.include? 'certificate verify failed'
|
||||
|
||||
puts "Error: Failed to connect to #{uri}."
|
||||
puts 'If the server uses a self-signed certificate, please re-run the login command with the --insecure option.'
|
||||
exit 1
|
||||
end
|
||||
|
||||
def self._parse_url(url)
|
||||
url = "https://#{url}" if URI.parse(url).scheme.nil?
|
||||
URI.parse(url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
@ -0,0 +1,36 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module InspecPlugins
|
||||
module Compliance
|
||||
# is a helper that provides information which version of compliance supports
|
||||
# which feature
|
||||
class Support
|
||||
# for a feature, returns either:
|
||||
# - a version v0: v supports v0 iff v0 <= v
|
||||
# - an array [v0, v1] of two versions: v supports [v0, v1] iff v0 <= v < v1
|
||||
def self.version_with_support(feature)
|
||||
case feature.to_sym
|
||||
when :oidc # open id connect authentication
|
||||
Gem::Version.new('0.16.19')
|
||||
else
|
||||
Gem::Version.new('0.0.0')
|
||||
end
|
||||
end
|
||||
|
||||
# determines if the given version support a certain feature
|
||||
def self.supported?(feature, version)
|
||||
sup = version_with_support(feature)
|
||||
|
||||
if sup.is_a?(Array)
|
||||
Gem::Version.new(version) >= sup[0] &&
|
||||
Gem::Version.new(version) < sup[1]
|
||||
else
|
||||
Gem::Version.new(version) >= sup
|
||||
end
|
||||
end
|
||||
|
||||
# we do not know the version, therefore we do not know if its possible to use the feature
|
||||
# return if self['version'].nil? || self['version']['version'].nil?
|
||||
end
|
||||
end
|
||||
end
|
143
lib/plugins/inspec-compliance/lib/inspec-compliance/target.rb
Normal file
143
lib/plugins/inspec-compliance/lib/inspec-compliance/target.rb
Normal file
|
@ -0,0 +1,143 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'uri'
|
||||
require 'inspec/fetcher'
|
||||
require 'inspec/errors'
|
||||
|
||||
# InSpec Target Helper for Chef Compliance
|
||||
# reuses UrlHelper, but it knows the target server and the access token already
|
||||
# similar to `inspec exec http://localhost:2134/owners/%base%/compliance/%ssh%/tar --user %token%`
|
||||
module InspecPlugins
|
||||
module Compliance
|
||||
class Fetcher < Fetchers::Url
|
||||
name 'compliance'
|
||||
priority 500
|
||||
attr_reader :upstream_sha256
|
||||
|
||||
def initialize(target, opts)
|
||||
super(target, opts)
|
||||
@upstream_sha256 = ''
|
||||
if target.is_a?(Hash) && target.key?(:url)
|
||||
@target = target[:url]
|
||||
@upstream_sha256 = target[:sha256]
|
||||
elsif target.is_a?(String)
|
||||
@target = target
|
||||
end
|
||||
end
|
||||
|
||||
def sha256
|
||||
upstream_sha256.empty? ? super : upstream_sha256
|
||||
end
|
||||
|
||||
def self.check_compliance_token(uri, config)
|
||||
if config['token'].nil? && config['refresh_token'].nil?
|
||||
if config['server_type'] == 'automate'
|
||||
server = 'automate'
|
||||
msg = 'inspec compliance login https://your_automate_server --user USER --ent ENT --dctoken DCTOKEN or --token USERTOKEN'
|
||||
elsif config['server_type'] == 'automate2'
|
||||
server = 'automate2'
|
||||
msg = 'inspec compliance login https://your_automate2_server --user USER --token APITOKEN'
|
||||
else
|
||||
server = 'compliance'
|
||||
msg = "inspec compliance login https://your_compliance_server --user admin --insecure --token 'PASTE TOKEN HERE' "
|
||||
end
|
||||
raise Inspec::FetcherFailure, <<~EOF
|
||||
|
||||
Cannot fetch #{uri} because your #{server} token has not been
|
||||
configured.
|
||||
|
||||
Please login using
|
||||
|
||||
#{msg}
|
||||
EOF
|
||||
end
|
||||
end
|
||||
|
||||
def self.get_target_uri(target)
|
||||
if target.is_a?(String) && URI(target).scheme == 'compliance'
|
||||
URI(target)
|
||||
elsif target.respond_to?(:key?) && target.key?(:compliance)
|
||||
URI("compliance://#{target[:compliance]}")
|
||||
end
|
||||
end
|
||||
|
||||
def self.resolve(target)
|
||||
uri = get_target_uri(target)
|
||||
return nil if uri.nil?
|
||||
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
profile = InspecPlugins::Compliance::API.sanitize_profile_name(uri)
|
||||
profile_fetch_url = InspecPlugins::Compliance::API.target_url(config, profile)
|
||||
# we have detailed information available in our lockfile, no need to ask the server
|
||||
if target.respond_to?(:key?) && target.key?(:sha256)
|
||||
profile_checksum = target[:sha256]
|
||||
else
|
||||
check_compliance_token(uri, config)
|
||||
# verifies that the target e.g base/ssh exists
|
||||
# Call profiles directly instead of exist? to capture the results
|
||||
# so we can access the upstream sha256 from the results.
|
||||
_msg, profile_result = InspecPlugins::Compliance::API.profiles(config, profile)
|
||||
if profile_result.empty?
|
||||
raise Inspec::FetcherFailure, "The compliance profile #{profile} was not found on the configured compliance server"
|
||||
else
|
||||
# Guarantee sorting by verison and grab the latest.
|
||||
# If version was specified, it will be the first and only result.
|
||||
# Note we are calling the sha256 as a string, not a symbol since
|
||||
# it was returned as json from the Compliance API.
|
||||
profile_info = profile_result.sort_by { |x| Gem::Version.new(x['version']) }[0]
|
||||
profile_checksum = profile_info.key?('sha256') ? profile_info['sha256'] : ''
|
||||
end
|
||||
end
|
||||
# We need to pass the token to the fetcher
|
||||
config['token'] = InspecPlugins::Compliance::API.get_token(config)
|
||||
|
||||
# Needed for automate2 post request
|
||||
profile_stub = profile || target[:compliance]
|
||||
config['profile'] = InspecPlugins::Compliance::API.profile_split(profile_stub)
|
||||
|
||||
new({ url: profile_fetch_url, sha256: profile_checksum }, config)
|
||||
rescue URI::Error => _e
|
||||
nil
|
||||
end
|
||||
|
||||
# We want to save compliance: in the lockfile rather than url: to
|
||||
# make sure we go back through the Compliance API handling.
|
||||
def resolved_source
|
||||
@resolved_source ||= {
|
||||
compliance: compliance_profile_name,
|
||||
url: @target,
|
||||
sha256: sha256,
|
||||
}
|
||||
end
|
||||
|
||||
def to_s
|
||||
'Chef Compliance Profile Loader'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# determine the owner_id and the profile name from the url
|
||||
def compliance_profile_name
|
||||
m = if InspecPlugins::Compliance::API.is_automate_server_pre_080?(@config)
|
||||
%r{^#{@config['server']}/(?<owner>[^/]+)/(?<id>[^/]+)/tar$}
|
||||
elsif InspecPlugins::Compliance::API.is_automate_server_080_and_later?(@config)
|
||||
%r{^#{@config['server']}/profiles/(?<owner>[^/]+)/(?<id>[^/]+)/tar$}
|
||||
else
|
||||
%r{^#{@config['server']}/owners/(?<owner>[^/]+)/compliance/(?<id>[^/]+)/tar$}
|
||||
end.match(@target)
|
||||
|
||||
if InspecPlugins::Compliance::API.is_automate2_server?(@config)
|
||||
m = {}
|
||||
m[:owner] = @config['profile'][0]
|
||||
m[:id] = @config['profile'][1]
|
||||
end
|
||||
|
||||
raise 'Unable to determine compliance profile name. This can be caused by ' \
|
||||
'an incorrect server in your configuration. Try to login to compliance ' \
|
||||
'via the `inspec compliance login` command.' if m.nil?
|
||||
|
||||
"#{m[:owner]}/#{m[:id]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require_relative '../../../shared/core_plugin_test_helper.rb'
|
||||
|
||||
class ComplianceCli < MiniTest::Test
|
||||
include CorePluginFunctionalHelper
|
||||
|
||||
def test_help_output
|
||||
out = run_inspec_process('compliance help')
|
||||
assert_equal out.exit_status, 0
|
||||
assert_includes out.stdout, 'inspec compliance exec PROFILE'
|
||||
end
|
||||
|
||||
def test_logout_command
|
||||
out = run_inspec_process('compliance logout')
|
||||
assert_equal out.exit_status, 0
|
||||
assert_includes out.stdout, ''
|
||||
end
|
||||
|
||||
def test_error_login_with_invalid_url
|
||||
out = run_inspec_process('compliance login')
|
||||
assert_equal out.exit_status, 1
|
||||
assert_includes out.stderr, 'ERROR: "inspec compliance login" was called with no arguments'
|
||||
end
|
||||
|
||||
def test_profile_list_without_auth
|
||||
out = run_inspec_process('compliance profiles')
|
||||
assert_equal out.exit_status, 0 # TODO: make this error
|
||||
assert_includes out.stdout, 'You need to login first with `inspec compliance login`'
|
||||
end
|
||||
|
||||
def test_error_upload_without_args
|
||||
out = run_inspec_process('compliance upload')
|
||||
assert_equal out.exit_status, 1
|
||||
assert_includes out.stderr, 'ERROR: "inspec compliance upload" was called with no arguments'
|
||||
end
|
||||
|
||||
def test_error_upload_with_fake_path
|
||||
out = run_inspec_process('compliance upload /path/to/dir')
|
||||
assert_equal out.exit_status, 0 # TODO: make this error
|
||||
assert_includes out.stdout, 'You need to login first with `inspec compliance login`'
|
||||
end
|
||||
end
|
|
@ -1,6 +1,9 @@
|
|||
require 'helper'
|
||||
require 'minitest/autorun'
|
||||
require 'mocha/setup'
|
||||
require 'webmock/minitest'
|
||||
require_relative '../../../lib/inspec-compliance/api.rb'
|
||||
|
||||
describe Compliance::API do
|
||||
describe InspecPlugins::Compliance::API do
|
||||
let(:automate_options) do
|
||||
{
|
||||
'server' => 'https://automate.example.com',
|
||||
|
@ -49,13 +52,13 @@ describe Compliance::API do
|
|||
describe '.login' do
|
||||
describe 'when target is a Chef Automate2 server' do
|
||||
before do
|
||||
Compliance::API.expects(:determine_server_type).returns(:automate2)
|
||||
InspecPlugins::Compliance::API.expects(:determine_server_type).returns(:automate2)
|
||||
end
|
||||
|
||||
it 'raises an error if `--user` is missing' do
|
||||
options = automate_options
|
||||
options.delete('user')
|
||||
err = proc { Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err = proc { InspecPlugins::Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err.message.must_match(/Please specify a user.*/)
|
||||
err.message.lines.length.must_equal(1)
|
||||
end
|
||||
|
@ -64,7 +67,7 @@ describe Compliance::API do
|
|||
options = automate_options
|
||||
options.delete('token')
|
||||
options.delete('dctoken')
|
||||
err = proc { Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err = proc { InspecPlugins::Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err.message.must_match(/Please specify a token.*/)
|
||||
err.message.lines.length.must_equal(1)
|
||||
end
|
||||
|
@ -73,9 +76,9 @@ describe Compliance::API do
|
|||
stub_request(:get, automate_options['server'] + '/compliance/version')
|
||||
.to_return(status: 200, body: '', headers: {})
|
||||
options = automate_options
|
||||
Compliance::Configuration.expects(:new).returns(fake_config)
|
||||
InspecPlugins::Compliance::Configuration.expects(:new).returns(fake_config)
|
||||
|
||||
Compliance::API.login(options)
|
||||
InspecPlugins::Compliance::API.login(options)
|
||||
fake_config['automate']['ent'].must_equal('automate')
|
||||
fake_config['automate']['token_type'].must_equal('dctoken')
|
||||
fake_config['user'].must_equal('someone')
|
||||
|
@ -87,13 +90,13 @@ describe Compliance::API do
|
|||
|
||||
describe 'when target is a Chef Automate server' do
|
||||
before do
|
||||
Compliance::API.expects(:determine_server_type).returns(:automate)
|
||||
InspecPlugins::Compliance::API.expects(:determine_server_type).returns(:automate)
|
||||
end
|
||||
|
||||
it 'raises an error if `--user` is missing' do
|
||||
options = automate_options
|
||||
options.delete('user')
|
||||
err = proc { Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err = proc { InspecPlugins::Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err.message.must_match(/Please specify a user.*/)
|
||||
err.message.lines.length.must_equal(1)
|
||||
end
|
||||
|
@ -101,7 +104,7 @@ describe Compliance::API do
|
|||
it 'raises an error if `--ent` is missing' do
|
||||
options = automate_options
|
||||
options.delete('ent')
|
||||
err = proc { Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err = proc { InspecPlugins::Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err.message.must_match(/Please specify an enterprise.*/)
|
||||
err.message.lines.length.must_equal(1)
|
||||
end
|
||||
|
@ -110,7 +113,7 @@ describe Compliance::API do
|
|||
options = automate_options
|
||||
options.delete('token')
|
||||
options.delete('dctoken')
|
||||
err = proc { Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err = proc { InspecPlugins::Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err.message.must_match(/Please specify a token.*/)
|
||||
err.message.lines.length.must_equal(1)
|
||||
end
|
||||
|
@ -119,9 +122,9 @@ describe Compliance::API do
|
|||
stub_request(:get, automate_options['server'] + '/compliance/version')
|
||||
.to_return(status: 200, body: '', headers: {})
|
||||
options = automate_options
|
||||
Compliance::Configuration.expects(:new).returns(fake_config)
|
||||
InspecPlugins::Compliance::Configuration.expects(:new).returns(fake_config)
|
||||
|
||||
Compliance::API.login(options)
|
||||
InspecPlugins::Compliance::API.login(options)
|
||||
fake_config['automate']['ent'].must_equal('automate')
|
||||
fake_config['automate']['token_type'].must_equal('usertoken')
|
||||
fake_config['user'].must_equal('someone')
|
||||
|
@ -133,14 +136,14 @@ describe Compliance::API do
|
|||
|
||||
describe 'when target is a Chef Compliance server' do
|
||||
before do
|
||||
Compliance::API.expects(:determine_server_type).returns(:compliance)
|
||||
InspecPlugins::Compliance::API.expects(:determine_server_type).returns(:compliance)
|
||||
end
|
||||
|
||||
it 'raises an error if `--user` and `--refresh-token` are missing' do
|
||||
options = automate_options
|
||||
options.delete('user')
|
||||
options.delete('refresh_token')
|
||||
err = proc { Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err = proc { InspecPlugins::Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err.message.must_match(/Please specify a.*--user.*--refresh-token.*/)
|
||||
err.message.lines.length.must_equal(1)
|
||||
end
|
||||
|
@ -150,7 +153,7 @@ describe Compliance::API do
|
|||
options.delete('password')
|
||||
options.delete('token')
|
||||
options.delete('refresh_token')
|
||||
err = proc { Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err = proc { InspecPlugins::Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err.message.must_match(/Please specify.*--password.*--token.*--refresh-token.*/)
|
||||
err.message.lines.length.must_equal(1)
|
||||
end
|
||||
|
@ -159,9 +162,9 @@ describe Compliance::API do
|
|||
stub_request(:get, compliance_options['server'] + '/api/version')
|
||||
.to_return(status: 200, body: '', headers: {})
|
||||
options = compliance_options
|
||||
Compliance::Configuration.expects(:new).returns(fake_config)
|
||||
InspecPlugins::Compliance::Configuration.expects(:new).returns(fake_config)
|
||||
|
||||
Compliance::API.login(options)
|
||||
InspecPlugins::Compliance::API.login(options)
|
||||
fake_config['user'].must_equal('someone')
|
||||
fake_config['server'].must_equal('https://compliance.example.com/api')
|
||||
fake_config['server_type'].must_equal('compliance')
|
||||
|
@ -172,14 +175,14 @@ describe Compliance::API do
|
|||
describe 'when target is neither a Chef Compliance nor Chef Automate server' do
|
||||
it 'raises an error if `https://SERVER` is missing' do
|
||||
options = {}
|
||||
err = proc { Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err = proc { InspecPlugins::Compliance::API.login(options) }.must_raise(ArgumentError)
|
||||
err.message.must_match(/Please specify a server.*/)
|
||||
err.message.lines.length.must_equal(1)
|
||||
end
|
||||
|
||||
it 'rasies a `CannotDetermineServerType` error' do
|
||||
Compliance::API.expects(:determine_server_type).returns(nil)
|
||||
err = proc { Compliance::API.login(automate_options) }.must_raise(StandardError)
|
||||
InspecPlugins::Compliance::API.expects(:determine_server_type).returns(nil)
|
||||
err = proc { InspecPlugins::Compliance::API.login(automate_options) }.must_raise(StandardError)
|
||||
err.message.must_match(/Unable to determine/)
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
require 'helper'
|
||||
require 'minitest/autorun'
|
||||
require 'mocha/setup'
|
||||
require_relative '../../lib/inspec-compliance/api.rb'
|
||||
|
||||
describe Compliance::API do
|
||||
describe InspecPlugins::Compliance::API do
|
||||
let(:profiles_response) do
|
||||
[{ 'name'=>'apache-baseline',
|
||||
'title'=>'DevSec Apache Baseline',
|
||||
|
@ -49,15 +51,15 @@ describe Compliance::API do
|
|||
end
|
||||
|
||||
before do
|
||||
Compliance::API.expects(:get_headers).returns(headers)
|
||||
InspecPlugins::Compliance::API.expects(:get_headers).returns(headers)
|
||||
end
|
||||
|
||||
describe 'when a 404 is received' do
|
||||
it 'should return an empty hash' do
|
||||
response = mock
|
||||
response.stubs(:code).returns('404')
|
||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
Compliance::API.version(config).must_equal({})
|
||||
InspecPlugins::Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
InspecPlugins::Compliance::API.version(config).must_equal({})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -66,8 +68,8 @@ describe Compliance::API do
|
|||
response = mock
|
||||
response.stubs(:code).returns('200')
|
||||
response.stubs(:body).returns(nil)
|
||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
Compliance::API.version(config).must_equal({})
|
||||
InspecPlugins::Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
InspecPlugins::Compliance::API.version(config).must_equal({})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -76,8 +78,8 @@ describe Compliance::API do
|
|||
response = mock
|
||||
response.stubs(:code).returns('200')
|
||||
response.stubs(:body).returns('')
|
||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
Compliance::API.version(config).must_equal({})
|
||||
InspecPlugins::Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
InspecPlugins::Compliance::API.version(config).must_equal({})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -86,8 +88,8 @@ describe Compliance::API do
|
|||
response = mock
|
||||
response.stubs(:code).returns('200')
|
||||
response.stubs(:body).returns('{"api":"compliance"}')
|
||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
Compliance::API.version(config).must_equal({})
|
||||
InspecPlugins::Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
InspecPlugins::Compliance::API.version(config).must_equal({})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -96,8 +98,8 @@ describe Compliance::API do
|
|||
response = mock
|
||||
response.stubs(:code).returns('200')
|
||||
response.stubs(:body).returns('{"api":"compliance","version":""}')
|
||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
Compliance::API.version(config).must_equal({})
|
||||
InspecPlugins::Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
InspecPlugins::Compliance::API.version(config).must_equal({})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -106,8 +108,8 @@ describe Compliance::API do
|
|||
response = mock
|
||||
response.stubs(:code).returns('200')
|
||||
response.stubs(:body).returns('{"api":"compliance","version":"1.2.3"}')
|
||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
Compliance::API.version(config).must_equal({ 'version' => '1.2.3', 'api' => 'compliance' })
|
||||
InspecPlugins::Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||
InspecPlugins::Compliance::API.version(config).must_equal({ 'version' => '1.2.3', 'api' => 'compliance' })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -115,80 +117,80 @@ describe Compliance::API do
|
|||
describe 'automate/compliance is? checks' do
|
||||
describe 'when the config has a compliance server_type' do
|
||||
it 'automate/compliance server is? methods return correctly' do
|
||||
config = Compliance::Configuration.new
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
config.clean
|
||||
config['server_type'] = 'compliance'
|
||||
Compliance::API.is_compliance_server?(config).must_equal true
|
||||
Compliance::API.is_automate_server?(config).must_equal false
|
||||
Compliance::API.is_automate_server_pre_080?(config).must_equal false
|
||||
Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
||||
Compliance::API.is_automate2_server?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal true
|
||||
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate_server_pre_080?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate2_server?(config).must_equal false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the config has a automate2 server_type' do
|
||||
it 'automate/compliance server is? methods return correctly' do
|
||||
config = Compliance::Configuration.new
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
config.clean
|
||||
config['server_type'] = 'automate2'
|
||||
Compliance::API.is_compliance_server?(config).must_equal false
|
||||
Compliance::API.is_automate_server?(config).must_equal false
|
||||
Compliance::API.is_automate_server_pre_080?(config).must_equal false
|
||||
Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
||||
Compliance::API.is_automate2_server?(config).must_equal true
|
||||
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate_server_pre_080?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate2_server?(config).must_equal true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the config has an automate server_type and no version key' do
|
||||
it 'automate/compliance server is? methods return correctly' do
|
||||
config = Compliance::Configuration.new
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
config.clean
|
||||
config['server_type'] = 'automate'
|
||||
Compliance::API.is_compliance_server?(config).must_equal false
|
||||
Compliance::API.is_automate_server?(config).must_equal true
|
||||
Compliance::API.is_automate_server_pre_080?(config).must_equal true
|
||||
Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
||||
Compliance::API.is_automate2_server?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal true
|
||||
InspecPlugins::Compliance::API.is_automate_server_pre_080?(config).must_equal true
|
||||
InspecPlugins::Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate2_server?(config).must_equal false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the config has an automate server_type and a version key that is not a hash' do
|
||||
it 'automate/compliance server is? methods return correctly' do
|
||||
config = Compliance::Configuration.new
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
config.clean
|
||||
config['server_type'] = 'automate'
|
||||
config['version'] = '1.2.3'
|
||||
Compliance::API.is_compliance_server?(config).must_equal false
|
||||
Compliance::API.is_automate_server?(config).must_equal true
|
||||
Compliance::API.is_automate_server_pre_080?(config).must_equal true
|
||||
Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
||||
Compliance::API.is_automate2_server?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal true
|
||||
InspecPlugins::Compliance::API.is_automate_server_pre_080?(config).must_equal true
|
||||
InspecPlugins::Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate2_server?(config).must_equal false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the config has an automate server_type and a version hash with no version' do
|
||||
it 'automate/compliance server is? methods return correctly' do
|
||||
config = Compliance::Configuration.new
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
config.clean
|
||||
config['server_type'] = 'automate'
|
||||
config['version'] = {}
|
||||
Compliance::API.is_compliance_server?(config).must_equal false
|
||||
Compliance::API.is_automate_server?(config).must_equal true
|
||||
Compliance::API.is_automate_server_pre_080?(config).must_equal true
|
||||
Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal true
|
||||
InspecPlugins::Compliance::API.is_automate_server_pre_080?(config).must_equal true
|
||||
InspecPlugins::Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the config has an automate server_type and a version hash with a version' do
|
||||
it 'automate/compliance server is? methods return correctly' do
|
||||
config = Compliance::Configuration.new
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
config.clean
|
||||
config['server_type'] = 'automate'
|
||||
config['version'] = { 'version' => '0.8.1' }
|
||||
Compliance::API.is_compliance_server?(config).must_equal false
|
||||
Compliance::API.is_automate_server?(config).must_equal true
|
||||
Compliance::API.is_automate_server_pre_080?(config).must_equal false
|
||||
Compliance::API.is_automate_server_080_and_later?(config).must_equal true
|
||||
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal true
|
||||
InspecPlugins::Compliance::API.is_automate_server_pre_080?(config).must_equal false
|
||||
InspecPlugins::Compliance::API.is_automate_server_080_and_later?(config).must_equal true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -196,54 +198,54 @@ describe Compliance::API do
|
|||
describe '.server_version_from_config' do
|
||||
it 'returns nil when the config has no version key' do
|
||||
config = {}
|
||||
Compliance::API.server_version_from_config(config).must_be_nil
|
||||
InspecPlugins::Compliance::API.server_version_from_config(config).must_be_nil
|
||||
end
|
||||
|
||||
it 'returns nil when the version value is not a hash' do
|
||||
config = { 'version' => '123' }
|
||||
Compliance::API.server_version_from_config(config).must_be_nil
|
||||
InspecPlugins::Compliance::API.server_version_from_config(config).must_be_nil
|
||||
end
|
||||
|
||||
it 'returns nil when the version value is a hash but has no version key inside' do
|
||||
config = { 'version' => {} }
|
||||
Compliance::API.server_version_from_config(config).must_be_nil
|
||||
InspecPlugins::Compliance::API.server_version_from_config(config).must_be_nil
|
||||
end
|
||||
|
||||
it 'returns the version if the version value is a hash containing a version' do
|
||||
config = { 'version' => { 'version' => '1.2.3' } }
|
||||
Compliance::API.server_version_from_config(config).must_equal '1.2.3'
|
||||
InspecPlugins::Compliance::API.server_version_from_config(config).must_equal '1.2.3'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'profile_split' do
|
||||
it 'handles a profile without version' do
|
||||
Compliance::API.profile_split('admin/apache-baseline').must_equal ['admin', 'apache-baseline', nil]
|
||||
InspecPlugins::Compliance::API.profile_split('admin/apache-baseline').must_equal ['admin', 'apache-baseline', nil]
|
||||
end
|
||||
|
||||
it 'handles a profile with a version' do
|
||||
Compliance::API.profile_split('admin/apache-baseline#2.0.1').must_equal ['admin', 'apache-baseline', '2.0.1']
|
||||
InspecPlugins::Compliance::API.profile_split('admin/apache-baseline#2.0.1').must_equal ['admin', 'apache-baseline', '2.0.1']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'target_url' do
|
||||
it 'handles a automate profile with and without version' do
|
||||
config = Compliance::Configuration.new
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
config.clean
|
||||
config['server_type'] = 'automate'
|
||||
config['server'] = 'https://myautomate'
|
||||
config['version'] = '1.6.99'
|
||||
Compliance::API.target_url(config, 'admin/apache-baseline').must_equal 'https://myautomate/profiles/admin/apache-baseline/tar'
|
||||
Compliance::API.target_url(config, 'admin/apache-baseline#2.0.2').must_equal 'https://myautomate/profiles/admin/apache-baseline/version/2.0.2/tar'
|
||||
InspecPlugins::Compliance::API.target_url(config, 'admin/apache-baseline').must_equal 'https://myautomate/profiles/admin/apache-baseline/tar'
|
||||
InspecPlugins::Compliance::API.target_url(config, 'admin/apache-baseline#2.0.2').must_equal 'https://myautomate/profiles/admin/apache-baseline/version/2.0.2/tar'
|
||||
end
|
||||
|
||||
it 'handles a chef-compliance profile with and without version' do
|
||||
config = Compliance::Configuration.new
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
config.clean
|
||||
config['server_type'] = 'compliance'
|
||||
config['server'] = 'https://mychefcompliance'
|
||||
config['version'] = '1.1.2'
|
||||
Compliance::API.target_url(config, 'admin/apache-baseline').must_equal 'https://mychefcompliance/owners/admin/compliance/apache-baseline/tar'
|
||||
Compliance::API.target_url(config, 'admin/apache-baseline#2.0.2').must_equal 'https://mychefcompliance/owners/admin/compliance/apache-baseline/tar'
|
||||
InspecPlugins::Compliance::API.target_url(config, 'admin/apache-baseline').must_equal 'https://mychefcompliance/owners/admin/compliance/apache-baseline/tar'
|
||||
InspecPlugins::Compliance::API.target_url(config, 'admin/apache-baseline#2.0.2').must_equal 'https://mychefcompliance/owners/admin/compliance/apache-baseline/tar'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -253,7 +255,7 @@ describe Compliance::API do
|
|||
# skipping for that specific version
|
||||
return if RUBY_VERSION = '2.3.3'
|
||||
|
||||
config = Compliance::Configuration.new
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
config.clean
|
||||
config['owner'] = 'admin'
|
||||
config['server_type'] = 'automate'
|
||||
|
@ -266,10 +268,10 @@ describe Compliance::API do
|
|||
.with(headers: { 'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Chef-Delivery-Enterprise'=>'automate', 'User-Agent'=>'Ruby', 'X-Data-Collector-Token'=>'' })
|
||||
.to_return(status: 200, body: profiles_response.to_json, headers: {})
|
||||
|
||||
Compliance::API.exist?(config, 'admin/apache-baseline').must_equal true
|
||||
Compliance::API.exist?(config, 'admin/apache-baseline#2.0.1').must_equal true
|
||||
Compliance::API.exist?(config, 'admin/apache-baseline#2.0.999').must_equal false
|
||||
Compliance::API.exist?(config, 'admin/missing-in-action').must_equal false
|
||||
InspecPlugins::Compliance::API.exist?(config, 'admin/apache-baseline').must_equal true
|
||||
InspecPlugins::Compliance::API.exist?(config, 'admin/apache-baseline#2.0.1').must_equal true
|
||||
InspecPlugins::Compliance::API.exist?(config, 'admin/apache-baseline#2.0.999').must_equal false
|
||||
InspecPlugins::Compliance::API.exist?(config, 'admin/missing-in-action').must_equal false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -288,25 +290,25 @@ describe Compliance::API do
|
|||
it 'returns `:automate2` when a 400 is received from `https://URL/dex/auth`' do
|
||||
good_response.stubs(:code).returns('400')
|
||||
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + automate2_endpoint, headers, insecure)
|
||||
.returns(good_response)
|
||||
|
||||
Compliance::API.determine_server_type(url, insecure).must_equal(:automate2)
|
||||
InspecPlugins::Compliance::API.determine_server_type(url, insecure).must_equal(:automate2)
|
||||
end
|
||||
|
||||
it 'returns `:automate` when a 401 is received from `https://URL/compliance/version`' do
|
||||
good_response.stubs(:code).returns('401')
|
||||
bad_response.stubs(:code).returns('404')
|
||||
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + automate2_endpoint, headers, insecure)
|
||||
.returns(bad_response)
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + automate_endpoint, headers, insecure)
|
||||
.returns(good_response)
|
||||
|
||||
Compliance::API.determine_server_type(url, insecure).must_equal(:automate)
|
||||
InspecPlugins::Compliance::API.determine_server_type(url, insecure).must_equal(:automate)
|
||||
end
|
||||
|
||||
# Chef Automate currently returns 401 for `/compliance/version` but some
|
||||
|
@ -317,67 +319,67 @@ describe Compliance::API do
|
|||
good_response.stubs(:code).returns('200')
|
||||
good_response.stubs(:body).returns('Are You Looking For the Chef Server?')
|
||||
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + automate2_endpoint, headers, insecure)
|
||||
.returns(bad_response)
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + automate_endpoint, headers, insecure)
|
||||
.returns(good_response)
|
||||
|
||||
Compliance::API.determine_server_type(url, insecure).must_equal(:automate)
|
||||
InspecPlugins::Compliance::API.determine_server_type(url, insecure).must_equal(:automate)
|
||||
end
|
||||
|
||||
it 'returns `nil` if a 200 is received from `https://URL/compliance/version` but not redirected to Chef Manage' do
|
||||
bad_response.stubs(:code).returns('200')
|
||||
bad_response.stubs(:body).returns('No Chef Manage here')
|
||||
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + automate_endpoint, headers, insecure)
|
||||
.returns(bad_response)
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + automate2_endpoint, headers, insecure)
|
||||
.returns(bad_response)
|
||||
|
||||
mock_compliance_response = mock
|
||||
mock_compliance_response.stubs(:code).returns('404')
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + compliance_endpoint, headers, insecure)
|
||||
.returns(mock_compliance_response)
|
||||
|
||||
Compliance::API.determine_server_type(url, insecure).must_be_nil
|
||||
InspecPlugins::Compliance::API.determine_server_type(url, insecure).must_be_nil
|
||||
end
|
||||
|
||||
it 'returns `:compliance` when a 200 is received from `https://URL/api/version`' do
|
||||
good_response.stubs(:code).returns('200')
|
||||
bad_response.stubs(:code).returns('404')
|
||||
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + automate_endpoint, headers, insecure)
|
||||
.returns(bad_response)
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + automate2_endpoint, headers, insecure)
|
||||
.returns(bad_response)
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + compliance_endpoint, headers, insecure)
|
||||
.returns(good_response)
|
||||
|
||||
Compliance::API.determine_server_type(url, insecure).must_equal(:compliance)
|
||||
InspecPlugins::Compliance::API.determine_server_type(url, insecure).must_equal(:compliance)
|
||||
end
|
||||
|
||||
it 'returns `nil` if it cannot determine the server type' do
|
||||
bad_response.stubs(:code).returns('404')
|
||||
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + automate2_endpoint, headers, insecure)
|
||||
.returns(bad_response)
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + automate_endpoint, headers, insecure)
|
||||
.returns(bad_response)
|
||||
Compliance::HTTP.expects(:get)
|
||||
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||
.with(url + compliance_endpoint, headers, insecure)
|
||||
.returns(bad_response)
|
||||
|
||||
Compliance::API.determine_server_type(url, insecure).must_be_nil
|
||||
InspecPlugins::Compliance::API.determine_server_type(url, insecure).must_be_nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,10 +1,12 @@
|
|||
require 'helper'
|
||||
require 'minitest/autorun'
|
||||
require 'mocha/setup'
|
||||
require_relative '../../lib/inspec-compliance/api.rb'
|
||||
|
||||
describe Compliance::Fetcher do
|
||||
describe InspecPlugins::Compliance::Fetcher do
|
||||
let(:config) { { 'server' => 'myserver' } }
|
||||
|
||||
describe 'the check_compliance_token method' do
|
||||
let(:fetcher) { fetcher = Compliance::Fetcher.new('a/bad/url', config) }
|
||||
let(:fetcher) { fetcher = InspecPlugins::Compliance::Fetcher.new('a/bad/url', config) }
|
||||
|
||||
it 'returns without error if token is set' do
|
||||
config['token'] = 'my-token'
|
||||
|
@ -18,59 +20,59 @@ describe Compliance::Fetcher do
|
|||
end
|
||||
|
||||
describe 'when the server is an automate2 server' do
|
||||
before { Compliance::API.expects(:is_automate2_server?).with(config).returns(true) }
|
||||
before { InspecPlugins::Compliance::API.expects(:is_automate2_server?).with(config).returns(true) }
|
||||
|
||||
it 'returns the correct owner and profile name' do
|
||||
config['profile'] = ['admin', 'ssh-baseline', nil]
|
||||
fetcher = Compliance::Fetcher.new('myserver/profile', config)
|
||||
fetcher = InspecPlugins::Compliance::Fetcher.new('myserver/profile', config)
|
||||
fetcher.send(:compliance_profile_name).must_equal 'admin/ssh-baseline'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the server is an automate server pre-0.8.0' do
|
||||
before { Compliance::API.expects(:is_automate_server_pre_080?).with(config).returns(true) }
|
||||
before { InspecPlugins::Compliance::API.expects(:is_automate_server_pre_080?).with(config).returns(true) }
|
||||
|
||||
it 'returns the correct profile name when the url is correct' do
|
||||
fetcher = Compliance::Fetcher.new('myserver/myowner/myprofile/tar', config)
|
||||
fetcher = InspecPlugins::Compliance::Fetcher.new('myserver/myowner/myprofile/tar', config)
|
||||
fetcher.send(:compliance_profile_name).must_equal 'myowner/myprofile'
|
||||
end
|
||||
|
||||
it 'raises an exception if the url is malformed' do
|
||||
fetcher = Compliance::Fetcher.new('a/bad/url', config)
|
||||
fetcher = InspecPlugins::Compliance::Fetcher.new('a/bad/url', config)
|
||||
proc { fetcher.send(:compliance_profile_name) }.must_raise RuntimeError
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the server is an automate server 0.8.0-or-later' do
|
||||
before do
|
||||
Compliance::API.expects(:is_automate_server_pre_080?).with(config).returns(false)
|
||||
Compliance::API.expects(:is_automate_server_080_and_later?).with(config).returns(true)
|
||||
InspecPlugins::Compliance::API.expects(:is_automate_server_pre_080?).with(config).returns(false)
|
||||
InspecPlugins::Compliance::API.expects(:is_automate_server_080_and_later?).with(config).returns(true)
|
||||
end
|
||||
|
||||
it 'returns the correct profile name when the url is correct' do
|
||||
fetcher = Compliance::Fetcher.new('myserver/profiles/myowner/myprofile/tar', config)
|
||||
fetcher = InspecPlugins::Compliance::Fetcher.new('myserver/profiles/myowner/myprofile/tar', config)
|
||||
fetcher.send(:compliance_profile_name).must_equal 'myowner/myprofile'
|
||||
end
|
||||
|
||||
it 'raises an exception if the url is malformed' do
|
||||
fetcher = Compliance::Fetcher.new('a/bad/url', config)
|
||||
fetcher = InspecPlugins::Compliance::Fetcher.new('a/bad/url', config)
|
||||
proc { fetcher.send(:compliance_profile_name) }.must_raise RuntimeError
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the server is not an automate server (likely a compliance server)' do
|
||||
before do
|
||||
Compliance::API.expects(:is_automate_server_pre_080?).with(config).returns(false)
|
||||
Compliance::API.expects(:is_automate_server_080_and_later?).with(config).returns(false)
|
||||
InspecPlugins::Compliance::API.expects(:is_automate_server_pre_080?).with(config).returns(false)
|
||||
InspecPlugins::Compliance::API.expects(:is_automate_server_080_and_later?).with(config).returns(false)
|
||||
end
|
||||
|
||||
it 'returns the correct profile name when the url is correct' do
|
||||
fetcher = Compliance::Fetcher.new('myserver/owners/myowner/compliance/myprofile/tar', config)
|
||||
fetcher = InspecPlugins::Compliance::Fetcher.new('myserver/owners/myowner/compliance/myprofile/tar', config)
|
||||
fetcher.send(:compliance_profile_name).must_equal 'myowner/myprofile'
|
||||
end
|
||||
|
||||
it 'raises an exception if the url is malformed' do
|
||||
fetcher = Compliance::Fetcher.new('a/bad/url', config)
|
||||
fetcher = InspecPlugins::Compliance::Fetcher.new('a/bad/url', config)
|
||||
proc { fetcher.send(:compliance_profile_name) }.must_raise RuntimeError
|
||||
end
|
||||
end
|
||||
|
@ -95,24 +97,24 @@ describe Compliance::Fetcher do
|
|||
'latest_version'=>'' }]
|
||||
end
|
||||
before do
|
||||
Compliance::Configuration.expects(:new).returns({ 'token' => '123abc', 'server' => 'https://a2.instance.com' })
|
||||
InspecPlugins::Compliance::Configuration.expects(:new).returns({ 'token' => '123abc', 'server' => 'https://a2.instance.com' })
|
||||
end
|
||||
|
||||
it 'returns the correct profile name when parsing url' do
|
||||
Compliance::API.stubs(:profiles).returns(['success', profiles_result])
|
||||
fetcher = Compliance::Fetcher.resolve('compliance://admin/ssh-baseline')
|
||||
InspecPlugins::Compliance::API.stubs(:profiles).returns(['success', profiles_result])
|
||||
fetcher = InspecPlugins::Compliance::Fetcher.resolve('compliance://admin/ssh-baseline')
|
||||
assert = ['admin', 'ssh-baseline', nil]
|
||||
fetcher.instance_variable_get(:"@config")['profile'].must_equal assert
|
||||
end
|
||||
|
||||
it 'returns the correct profile name when parsing compliance hash' do
|
||||
Compliance::API.stubs(:profiles).returns(['success', profiles_result])
|
||||
InspecPlugins::Compliance::API.stubs(:profiles).returns(['success', profiles_result])
|
||||
hash = {
|
||||
target: 'https://a2.instance.com/api/v0/compliance/tar',
|
||||
compliance: 'admin/ssh-baseline',
|
||||
sha256: '132j1kjdasfasdoaefaewo12312',
|
||||
}
|
||||
fetcher = Compliance::Fetcher.resolve(hash)
|
||||
fetcher = InspecPlugins::Compliance::Fetcher.resolve(hash)
|
||||
assert = ['admin', 'ssh-baseline', nil]
|
||||
fetcher.instance_variable_get(:"@config")['profile'].must_equal assert
|
||||
end
|
||||
|
@ -139,14 +141,14 @@ describe Compliance::Fetcher do
|
|||
end
|
||||
|
||||
before do
|
||||
Compliance::Configuration.expects(:new).returns({ 'token' => '123abc', 'server' => 'https://a2.instance.com' })
|
||||
InspecPlugins::Compliance::Configuration.expects(:new).returns({ 'token' => '123abc', 'server' => 'https://a2.instance.com' })
|
||||
end
|
||||
|
||||
it 'contains the upstream_sha256' do
|
||||
Compliance::API.stubs(:profiles).returns(['success', profiles_result])
|
||||
InspecPlugins::Compliance::API.stubs(:profiles).returns(['success', profiles_result])
|
||||
prof = profiles_result[0]
|
||||
target = "compliance://#{prof['owner']}/#{prof['name']}"
|
||||
fetcher = Compliance::Fetcher.resolve(target)
|
||||
fetcher = InspecPlugins::Compliance::Fetcher.resolve(target)
|
||||
fetcher.upstream_sha256.must_equal prof['sha256']
|
||||
end
|
||||
end
|
|
@ -1,53 +0,0 @@
|
|||
# encoding: utf-8
|
||||
# author: Dominik Richter
|
||||
# author: Christoph Hartmann
|
||||
|
||||
require 'functional/helper'
|
||||
|
||||
# basic testing without availability of any server
|
||||
describe 'inspec compliance' do
|
||||
include FunctionalHelper
|
||||
|
||||
it 'help' do
|
||||
out = inspec('compliance help')
|
||||
out.exit_status.must_equal 0
|
||||
out.stdout.must_include 'inspec compliance exec PROFILE'
|
||||
end
|
||||
|
||||
# ensure we are logged out
|
||||
it 'logout' do
|
||||
out = inspec('compliance logout')
|
||||
out.exit_status.must_equal 0
|
||||
out.stdout.must_include ''
|
||||
end
|
||||
|
||||
it 'login server url missing' do
|
||||
out = inspec('compliance login')
|
||||
out.exit_status.must_equal 1
|
||||
out.stderr.must_include 'ERROR: "inspec compliance login" was called with no arguments'
|
||||
end
|
||||
|
||||
it 'inspec compliance profiles without authentication' do
|
||||
out = inspec('compliance profile')
|
||||
out.stdout.must_include 'You need to login first with `inspec compliance login`'
|
||||
out.exit_status.must_equal 0
|
||||
end
|
||||
|
||||
it 'try to upload a profile without directory' do
|
||||
out = inspec('compliance upload')
|
||||
out.stderr.must_include 'ERROR: "inspec compliance upload" was called with no arguments'
|
||||
out.exit_status.must_equal 1
|
||||
end
|
||||
|
||||
it 'try to upload a profile a non-existing path' do
|
||||
out = inspec('compliance upload /path/to/dir')
|
||||
out.stdout.must_include 'You need to login first with `inspec compliance login`'
|
||||
out.exit_status.must_equal 0
|
||||
end
|
||||
|
||||
it 'logout' do
|
||||
out = inspec('compliance logout')
|
||||
out.exit_status.must_equal 0
|
||||
out.stdout.must_include ''
|
||||
end
|
||||
end
|
|
@ -37,7 +37,7 @@ require 'inspec/impact'
|
|||
require 'fetchers/mock'
|
||||
require 'inspec/dependencies/cache'
|
||||
|
||||
require_relative '../lib/bundles/inspec-compliance'
|
||||
require_relative '../lib/bundles/inspec-supermarket'
|
||||
|
||||
require 'train'
|
||||
CMD = Train.create('local', command_runner: :generic).connection
|
||||
|
|
|
@ -31,7 +31,7 @@ describe 'BaseCLI' do
|
|||
default_options = { mock: { compliance: 'mock' } }
|
||||
Inspec::BaseCLI.stubs(:default_options).returns(default_options)
|
||||
|
||||
Compliance::API.expects(:login).with('mock')
|
||||
InspecPlugins::Compliance::API.expects(:login).with('mock')
|
||||
|
||||
cli.send(:opts, :mock)
|
||||
end
|
||||
|
|
|
@ -25,11 +25,11 @@ class PluginLoaderTests < MiniTest::Test
|
|||
|
||||
@config_dir_path = File.join(mock_path, 'config_dirs')
|
||||
@bundled_plugins = [
|
||||
:'inspec-compliance',
|
||||
:'inspec-supermarket',
|
||||
]
|
||||
@core_plugins = [
|
||||
:'inspec-artifact',
|
||||
:'inspec-compliance',
|
||||
:'inspec-habitat',
|
||||
:'inspec-init',
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue