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',
|
'test/unit/**/*_test.rb',
|
||||||
'lib/plugins/inspec-*/test/unit/**/*_test.rb',
|
'lib/plugins/inspec-*/test/unit/**/*_test.rb',
|
||||||
])
|
])
|
||||||
t.warning = true
|
t.warning = false
|
||||||
t.verbose = true
|
t.verbose = true
|
||||||
t.ruby_opts = ['--dev'] if defined?(JRUBY_VERSION)
|
t.ruby_opts = ['--dev'] if defined?(JRUBY_VERSION)
|
||||||
end
|
end
|
||||||
|
@ -91,7 +91,6 @@ namespace :test do
|
||||||
'test/functional/inspec_exec_json_test.rb',
|
'test/functional/inspec_exec_json_test.rb',
|
||||||
'test/functional/inspec_detect_test.rb',
|
'test/functional/inspec_detect_test.rb',
|
||||||
'test/functional/inspec_vendor_test.rb',
|
'test/functional/inspec_vendor_test.rb',
|
||||||
'test/functional/inspec_compliance_test.rb',
|
|
||||||
'test/functional/inspec_check_test.rb',
|
'test/functional/inspec_check_test.rb',
|
||||||
'test/functional/filter_table_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
|
# This file has been moved to the v2.0 plugins. This redirect allows for legacy use.
|
||||||
# author: Christoph Hartmann
|
# TODO: Remove in inspec 4.0
|
||||||
# author: Dominik Richter
|
|
||||||
|
|
||||||
require 'net/http'
|
require 'plugins/inspec-compliance/lib/inspec-compliance/api'
|
||||||
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
|
|
||||||
|
|
|
@ -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
|
# This file has been moved to the v2.0 plugins. This redirect allows for legacy use.
|
||||||
# author: Christoph Hartmann
|
# TODO: Remove in inspec 4.0
|
||||||
# author: Dominik Richter
|
|
||||||
|
|
||||||
module Compliance
|
require 'plugins/inspec-compliance/lib/inspec-compliance/configuration'
|
||||||
# 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
|
|
||||||
|
|
|
@ -1,116 +1,4 @@
|
||||||
# encoding: utf-8
|
# This file has been moved to the v2.0 plugins. This redirect allows for legacy use.
|
||||||
# author: Christoph Hartmann
|
# TODO: Remove in inspec 4.0
|
||||||
# author: Dominik Richter
|
|
||||||
|
|
||||||
require 'net/http'
|
require 'plugins/inspec-compliance/lib/inspec-compliance/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
|
|
||||||
|
|
|
@ -1,36 +1,4 @@
|
||||||
# encoding: utf-8
|
# This file has been moved to the v2.0 plugins. This redirect allows for legacy use.
|
||||||
# author: Christoph Hartmann
|
# TODO: Remove in inspec 4.0
|
||||||
# author: Dominik Richter
|
|
||||||
|
|
||||||
module Compliance
|
require 'plugins/inspec-compliance/lib/inspec-compliance/support'
|
||||||
# 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
|
|
||||||
|
|
|
@ -1,143 +1,4 @@
|
||||||
# encoding: utf-8
|
# This file has been moved to the v2.0 plugins. This redirect allows for legacy use.
|
||||||
# author: Christoph Hartmann
|
# TODO: Remove in inspec 4.0
|
||||||
# author: Dominik Richter
|
|
||||||
|
|
||||||
require 'uri'
|
require 'plugins/inspec-compliance/lib/inspec-compliance/target'
|
||||||
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
|
|
||||||
|
|
|
@ -292,7 +292,10 @@ module Inspec
|
||||||
end
|
end
|
||||||
|
|
||||||
# check for compliance settings
|
# 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
|
o
|
||||||
end
|
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
|
let(:automate_options) do
|
||||||
{
|
{
|
||||||
'server' => 'https://automate.example.com',
|
'server' => 'https://automate.example.com',
|
||||||
|
@ -49,13 +52,13 @@ describe Compliance::API do
|
||||||
describe '.login' do
|
describe '.login' do
|
||||||
describe 'when target is a Chef Automate2 server' do
|
describe 'when target is a Chef Automate2 server' do
|
||||||
before do
|
before do
|
||||||
Compliance::API.expects(:determine_server_type).returns(:automate2)
|
InspecPlugins::Compliance::API.expects(:determine_server_type).returns(:automate2)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises an error if `--user` is missing' do
|
it 'raises an error if `--user` is missing' do
|
||||||
options = automate_options
|
options = automate_options
|
||||||
options.delete('user')
|
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.must_match(/Please specify a user.*/)
|
||||||
err.message.lines.length.must_equal(1)
|
err.message.lines.length.must_equal(1)
|
||||||
end
|
end
|
||||||
|
@ -64,7 +67,7 @@ describe Compliance::API do
|
||||||
options = automate_options
|
options = automate_options
|
||||||
options.delete('token')
|
options.delete('token')
|
||||||
options.delete('dctoken')
|
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.must_match(/Please specify a token.*/)
|
||||||
err.message.lines.length.must_equal(1)
|
err.message.lines.length.must_equal(1)
|
||||||
end
|
end
|
||||||
|
@ -73,9 +76,9 @@ describe Compliance::API do
|
||||||
stub_request(:get, automate_options['server'] + '/compliance/version')
|
stub_request(:get, automate_options['server'] + '/compliance/version')
|
||||||
.to_return(status: 200, body: '', headers: {})
|
.to_return(status: 200, body: '', headers: {})
|
||||||
options = automate_options
|
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']['ent'].must_equal('automate')
|
||||||
fake_config['automate']['token_type'].must_equal('dctoken')
|
fake_config['automate']['token_type'].must_equal('dctoken')
|
||||||
fake_config['user'].must_equal('someone')
|
fake_config['user'].must_equal('someone')
|
||||||
|
@ -87,13 +90,13 @@ describe Compliance::API do
|
||||||
|
|
||||||
describe 'when target is a Chef Automate server' do
|
describe 'when target is a Chef Automate server' do
|
||||||
before do
|
before do
|
||||||
Compliance::API.expects(:determine_server_type).returns(:automate)
|
InspecPlugins::Compliance::API.expects(:determine_server_type).returns(:automate)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises an error if `--user` is missing' do
|
it 'raises an error if `--user` is missing' do
|
||||||
options = automate_options
|
options = automate_options
|
||||||
options.delete('user')
|
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.must_match(/Please specify a user.*/)
|
||||||
err.message.lines.length.must_equal(1)
|
err.message.lines.length.must_equal(1)
|
||||||
end
|
end
|
||||||
|
@ -101,7 +104,7 @@ describe Compliance::API do
|
||||||
it 'raises an error if `--ent` is missing' do
|
it 'raises an error if `--ent` is missing' do
|
||||||
options = automate_options
|
options = automate_options
|
||||||
options.delete('ent')
|
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.must_match(/Please specify an enterprise.*/)
|
||||||
err.message.lines.length.must_equal(1)
|
err.message.lines.length.must_equal(1)
|
||||||
end
|
end
|
||||||
|
@ -110,7 +113,7 @@ describe Compliance::API do
|
||||||
options = automate_options
|
options = automate_options
|
||||||
options.delete('token')
|
options.delete('token')
|
||||||
options.delete('dctoken')
|
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.must_match(/Please specify a token.*/)
|
||||||
err.message.lines.length.must_equal(1)
|
err.message.lines.length.must_equal(1)
|
||||||
end
|
end
|
||||||
|
@ -119,9 +122,9 @@ describe Compliance::API do
|
||||||
stub_request(:get, automate_options['server'] + '/compliance/version')
|
stub_request(:get, automate_options['server'] + '/compliance/version')
|
||||||
.to_return(status: 200, body: '', headers: {})
|
.to_return(status: 200, body: '', headers: {})
|
||||||
options = automate_options
|
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']['ent'].must_equal('automate')
|
||||||
fake_config['automate']['token_type'].must_equal('usertoken')
|
fake_config['automate']['token_type'].must_equal('usertoken')
|
||||||
fake_config['user'].must_equal('someone')
|
fake_config['user'].must_equal('someone')
|
||||||
|
@ -133,14 +136,14 @@ describe Compliance::API do
|
||||||
|
|
||||||
describe 'when target is a Chef Compliance server' do
|
describe 'when target is a Chef Compliance server' do
|
||||||
before do
|
before do
|
||||||
Compliance::API.expects(:determine_server_type).returns(:compliance)
|
InspecPlugins::Compliance::API.expects(:determine_server_type).returns(:compliance)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises an error if `--user` and `--refresh-token` are missing' do
|
it 'raises an error if `--user` and `--refresh-token` are missing' do
|
||||||
options = automate_options
|
options = automate_options
|
||||||
options.delete('user')
|
options.delete('user')
|
||||||
options.delete('refresh_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 a.*--user.*--refresh-token.*/)
|
err.message.must_match(/Please specify a.*--user.*--refresh-token.*/)
|
||||||
err.message.lines.length.must_equal(1)
|
err.message.lines.length.must_equal(1)
|
||||||
end
|
end
|
||||||
|
@ -150,7 +153,7 @@ describe Compliance::API do
|
||||||
options.delete('password')
|
options.delete('password')
|
||||||
options.delete('token')
|
options.delete('token')
|
||||||
options.delete('refresh_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.must_match(/Please specify.*--password.*--token.*--refresh-token.*/)
|
||||||
err.message.lines.length.must_equal(1)
|
err.message.lines.length.must_equal(1)
|
||||||
end
|
end
|
||||||
|
@ -159,9 +162,9 @@ describe Compliance::API do
|
||||||
stub_request(:get, compliance_options['server'] + '/api/version')
|
stub_request(:get, compliance_options['server'] + '/api/version')
|
||||||
.to_return(status: 200, body: '', headers: {})
|
.to_return(status: 200, body: '', headers: {})
|
||||||
options = compliance_options
|
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['user'].must_equal('someone')
|
||||||
fake_config['server'].must_equal('https://compliance.example.com/api')
|
fake_config['server'].must_equal('https://compliance.example.com/api')
|
||||||
fake_config['server_type'].must_equal('compliance')
|
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
|
describe 'when target is neither a Chef Compliance nor Chef Automate server' do
|
||||||
it 'raises an error if `https://SERVER` is missing' do
|
it 'raises an error if `https://SERVER` is missing' do
|
||||||
options = {}
|
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.must_match(/Please specify a server.*/)
|
||||||
err.message.lines.length.must_equal(1)
|
err.message.lines.length.must_equal(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rasies a `CannotDetermineServerType` error' do
|
it 'rasies a `CannotDetermineServerType` error' do
|
||||||
Compliance::API.expects(:determine_server_type).returns(nil)
|
InspecPlugins::Compliance::API.expects(:determine_server_type).returns(nil)
|
||||||
err = proc { Compliance::API.login(automate_options) }.must_raise(StandardError)
|
err = proc { InspecPlugins::Compliance::API.login(automate_options) }.must_raise(StandardError)
|
||||||
err.message.must_match(/Unable to determine/)
|
err.message.must_match(/Unable to determine/)
|
||||||
end
|
end
|
||||||
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
|
let(:profiles_response) do
|
||||||
[{ 'name'=>'apache-baseline',
|
[{ 'name'=>'apache-baseline',
|
||||||
'title'=>'DevSec Apache Baseline',
|
'title'=>'DevSec Apache Baseline',
|
||||||
|
@ -49,15 +51,15 @@ describe Compliance::API do
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Compliance::API.expects(:get_headers).returns(headers)
|
InspecPlugins::Compliance::API.expects(:get_headers).returns(headers)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when a 404 is received' do
|
describe 'when a 404 is received' do
|
||||||
it 'should return an empty hash' do
|
it 'should return an empty hash' do
|
||||||
response = mock
|
response = mock
|
||||||
response.stubs(:code).returns('404')
|
response.stubs(:code).returns('404')
|
||||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
InspecPlugins::Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||||
Compliance::API.version(config).must_equal({})
|
InspecPlugins::Compliance::API.version(config).must_equal({})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -66,8 +68,8 @@ describe Compliance::API do
|
||||||
response = mock
|
response = mock
|
||||||
response.stubs(:code).returns('200')
|
response.stubs(:code).returns('200')
|
||||||
response.stubs(:body).returns(nil)
|
response.stubs(:body).returns(nil)
|
||||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
InspecPlugins::Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||||
Compliance::API.version(config).must_equal({})
|
InspecPlugins::Compliance::API.version(config).must_equal({})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -76,8 +78,8 @@ describe Compliance::API do
|
||||||
response = mock
|
response = mock
|
||||||
response.stubs(:code).returns('200')
|
response.stubs(:code).returns('200')
|
||||||
response.stubs(:body).returns('')
|
response.stubs(:body).returns('')
|
||||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
InspecPlugins::Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||||
Compliance::API.version(config).must_equal({})
|
InspecPlugins::Compliance::API.version(config).must_equal({})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -86,8 +88,8 @@ describe Compliance::API do
|
||||||
response = mock
|
response = mock
|
||||||
response.stubs(:code).returns('200')
|
response.stubs(:code).returns('200')
|
||||||
response.stubs(:body).returns('{"api":"compliance"}')
|
response.stubs(:body).returns('{"api":"compliance"}')
|
||||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
InspecPlugins::Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||||
Compliance::API.version(config).must_equal({})
|
InspecPlugins::Compliance::API.version(config).must_equal({})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -96,8 +98,8 @@ describe Compliance::API do
|
||||||
response = mock
|
response = mock
|
||||||
response.stubs(:code).returns('200')
|
response.stubs(:code).returns('200')
|
||||||
response.stubs(:body).returns('{"api":"compliance","version":""}')
|
response.stubs(:body).returns('{"api":"compliance","version":""}')
|
||||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
InspecPlugins::Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
||||||
Compliance::API.version(config).must_equal({})
|
InspecPlugins::Compliance::API.version(config).must_equal({})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -106,8 +108,8 @@ describe Compliance::API do
|
||||||
response = mock
|
response = mock
|
||||||
response.stubs(:code).returns('200')
|
response.stubs(:code).returns('200')
|
||||||
response.stubs(:body).returns('{"api":"compliance","version":"1.2.3"}')
|
response.stubs(:body).returns('{"api":"compliance","version":"1.2.3"}')
|
||||||
Compliance::HTTP.expects(:get).with('myserver/version', 'test-headers', true).returns(response)
|
InspecPlugins::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::API.version(config).must_equal({ 'version' => '1.2.3', 'api' => 'compliance' })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -115,80 +117,80 @@ describe Compliance::API do
|
||||||
describe 'automate/compliance is? checks' do
|
describe 'automate/compliance is? checks' do
|
||||||
describe 'when the config has a compliance server_type' do
|
describe 'when the config has a compliance server_type' do
|
||||||
it 'automate/compliance server is? methods return correctly' do
|
it 'automate/compliance server is? methods return correctly' do
|
||||||
config = Compliance::Configuration.new
|
config = InspecPlugins::Compliance::Configuration.new
|
||||||
config.clean
|
config.clean
|
||||||
config['server_type'] = 'compliance'
|
config['server_type'] = 'compliance'
|
||||||
Compliance::API.is_compliance_server?(config).must_equal true
|
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal true
|
||||||
Compliance::API.is_automate_server?(config).must_equal false
|
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal false
|
||||||
Compliance::API.is_automate_server_pre_080?(config).must_equal false
|
InspecPlugins::Compliance::API.is_automate_server_pre_080?(config).must_equal false
|
||||||
Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
InspecPlugins::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_automate2_server?(config).must_equal false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when the config has a automate2 server_type' do
|
describe 'when the config has a automate2 server_type' do
|
||||||
it 'automate/compliance server is? methods return correctly' do
|
it 'automate/compliance server is? methods return correctly' do
|
||||||
config = Compliance::Configuration.new
|
config = InspecPlugins::Compliance::Configuration.new
|
||||||
config.clean
|
config.clean
|
||||||
config['server_type'] = 'automate2'
|
config['server_type'] = 'automate2'
|
||||||
Compliance::API.is_compliance_server?(config).must_equal false
|
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal false
|
||||||
Compliance::API.is_automate_server?(config).must_equal false
|
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal false
|
||||||
Compliance::API.is_automate_server_pre_080?(config).must_equal false
|
InspecPlugins::Compliance::API.is_automate_server_pre_080?(config).must_equal false
|
||||||
Compliance::API.is_automate_server_080_and_later?(config).must_equal false
|
InspecPlugins::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_automate2_server?(config).must_equal true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when the config has an automate server_type and no version key' do
|
describe 'when the config has an automate server_type and no version key' do
|
||||||
it 'automate/compliance server is? methods return correctly' do
|
it 'automate/compliance server is? methods return correctly' do
|
||||||
config = Compliance::Configuration.new
|
config = InspecPlugins::Compliance::Configuration.new
|
||||||
config.clean
|
config.clean
|
||||||
config['server_type'] = 'automate'
|
config['server_type'] = 'automate'
|
||||||
Compliance::API.is_compliance_server?(config).must_equal false
|
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal false
|
||||||
Compliance::API.is_automate_server?(config).must_equal true
|
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal true
|
||||||
Compliance::API.is_automate_server_pre_080?(config).must_equal true
|
InspecPlugins::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_automate_server_080_and_later?(config).must_equal false
|
||||||
Compliance::API.is_automate2_server?(config).must_equal false
|
InspecPlugins::Compliance::API.is_automate2_server?(config).must_equal false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when the config has an automate server_type and a version key that is not a hash' do
|
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
|
it 'automate/compliance server is? methods return correctly' do
|
||||||
config = Compliance::Configuration.new
|
config = InspecPlugins::Compliance::Configuration.new
|
||||||
config.clean
|
config.clean
|
||||||
config['server_type'] = 'automate'
|
config['server_type'] = 'automate'
|
||||||
config['version'] = '1.2.3'
|
config['version'] = '1.2.3'
|
||||||
Compliance::API.is_compliance_server?(config).must_equal false
|
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal false
|
||||||
Compliance::API.is_automate_server?(config).must_equal true
|
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal true
|
||||||
Compliance::API.is_automate_server_pre_080?(config).must_equal true
|
InspecPlugins::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_automate_server_080_and_later?(config).must_equal false
|
||||||
Compliance::API.is_automate2_server?(config).must_equal false
|
InspecPlugins::Compliance::API.is_automate2_server?(config).must_equal false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when the config has an automate server_type and a version hash with no version' do
|
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
|
it 'automate/compliance server is? methods return correctly' do
|
||||||
config = Compliance::Configuration.new
|
config = InspecPlugins::Compliance::Configuration.new
|
||||||
config.clean
|
config.clean
|
||||||
config['server_type'] = 'automate'
|
config['server_type'] = 'automate'
|
||||||
config['version'] = {}
|
config['version'] = {}
|
||||||
Compliance::API.is_compliance_server?(config).must_equal false
|
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal false
|
||||||
Compliance::API.is_automate_server?(config).must_equal true
|
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal true
|
||||||
Compliance::API.is_automate_server_pre_080?(config).must_equal true
|
InspecPlugins::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_automate_server_080_and_later?(config).must_equal false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when the config has an automate server_type and a version hash with a version' do
|
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
|
it 'automate/compliance server is? methods return correctly' do
|
||||||
config = Compliance::Configuration.new
|
config = InspecPlugins::Compliance::Configuration.new
|
||||||
config.clean
|
config.clean
|
||||||
config['server_type'] = 'automate'
|
config['server_type'] = 'automate'
|
||||||
config['version'] = { 'version' => '0.8.1' }
|
config['version'] = { 'version' => '0.8.1' }
|
||||||
Compliance::API.is_compliance_server?(config).must_equal false
|
InspecPlugins::Compliance::API.is_compliance_server?(config).must_equal false
|
||||||
Compliance::API.is_automate_server?(config).must_equal true
|
InspecPlugins::Compliance::API.is_automate_server?(config).must_equal true
|
||||||
Compliance::API.is_automate_server_pre_080?(config).must_equal false
|
InspecPlugins::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_automate_server_080_and_later?(config).must_equal true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -196,54 +198,54 @@ describe Compliance::API do
|
||||||
describe '.server_version_from_config' do
|
describe '.server_version_from_config' do
|
||||||
it 'returns nil when the config has no version key' do
|
it 'returns nil when the config has no version key' do
|
||||||
config = {}
|
config = {}
|
||||||
Compliance::API.server_version_from_config(config).must_be_nil
|
InspecPlugins::Compliance::API.server_version_from_config(config).must_be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nil when the version value is not a hash' do
|
it 'returns nil when the version value is not a hash' do
|
||||||
config = { 'version' => '123' }
|
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
|
end
|
||||||
|
|
||||||
it 'returns nil when the version value is a hash but has no version key inside' do
|
it 'returns nil when the version value is a hash but has no version key inside' do
|
||||||
config = { 'version' => {} }
|
config = { 'version' => {} }
|
||||||
Compliance::API.server_version_from_config(config).must_be_nil
|
InspecPlugins::Compliance::API.server_version_from_config(config).must_be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the version if the version value is a hash containing a version' do
|
it 'returns the version if the version value is a hash containing a version' do
|
||||||
config = { 'version' => { 'version' => '1.2.3' } }
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'profile_split' do
|
describe 'profile_split' do
|
||||||
it 'handles a profile without version' 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
|
end
|
||||||
|
|
||||||
it 'handles a profile with a version' do
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'target_url' do
|
describe 'target_url' do
|
||||||
it 'handles a automate profile with and without version' do
|
it 'handles a automate profile with and without version' do
|
||||||
config = Compliance::Configuration.new
|
config = InspecPlugins::Compliance::Configuration.new
|
||||||
config.clean
|
config.clean
|
||||||
config['server_type'] = 'automate'
|
config['server_type'] = 'automate'
|
||||||
config['server'] = 'https://myautomate'
|
config['server'] = 'https://myautomate'
|
||||||
config['version'] = '1.6.99'
|
config['version'] = '1.6.99'
|
||||||
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').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#2.0.2').must_equal 'https://myautomate/profiles/admin/apache-baseline/version/2.0.2/tar'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles a chef-compliance profile with and without version' do
|
it 'handles a chef-compliance profile with and without version' do
|
||||||
config = Compliance::Configuration.new
|
config = InspecPlugins::Compliance::Configuration.new
|
||||||
config.clean
|
config.clean
|
||||||
config['server_type'] = 'compliance'
|
config['server_type'] = 'compliance'
|
||||||
config['server'] = 'https://mychefcompliance'
|
config['server'] = 'https://mychefcompliance'
|
||||||
config['version'] = '1.1.2'
|
config['version'] = '1.1.2'
|
||||||
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').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#2.0.2').must_equal 'https://mychefcompliance/owners/admin/compliance/apache-baseline/tar'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -253,7 +255,7 @@ describe Compliance::API do
|
||||||
# skipping for that specific version
|
# skipping for that specific version
|
||||||
return if RUBY_VERSION = '2.3.3'
|
return if RUBY_VERSION = '2.3.3'
|
||||||
|
|
||||||
config = Compliance::Configuration.new
|
config = InspecPlugins::Compliance::Configuration.new
|
||||||
config.clean
|
config.clean
|
||||||
config['owner'] = 'admin'
|
config['owner'] = 'admin'
|
||||||
config['server_type'] = 'automate'
|
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'=>'' })
|
.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: {})
|
.to_return(status: 200, body: profiles_response.to_json, headers: {})
|
||||||
|
|
||||||
Compliance::API.exist?(config, 'admin/apache-baseline').must_equal true
|
InspecPlugins::Compliance::API.exist?(config, 'admin/apache-baseline').must_equal true
|
||||||
Compliance::API.exist?(config, 'admin/apache-baseline#2.0.1').must_equal true
|
InspecPlugins::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
|
InspecPlugins::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/missing-in-action').must_equal false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -288,25 +290,25 @@ describe Compliance::API do
|
||||||
it 'returns `:automate2` when a 400 is received from `https://URL/dex/auth`' do
|
it 'returns `:automate2` when a 400 is received from `https://URL/dex/auth`' do
|
||||||
good_response.stubs(:code).returns('400')
|
good_response.stubs(:code).returns('400')
|
||||||
|
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + automate2_endpoint, headers, insecure)
|
.with(url + automate2_endpoint, headers, insecure)
|
||||||
.returns(good_response)
|
.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
|
end
|
||||||
|
|
||||||
it 'returns `:automate` when a 401 is received from `https://URL/compliance/version`' do
|
it 'returns `:automate` when a 401 is received from `https://URL/compliance/version`' do
|
||||||
good_response.stubs(:code).returns('401')
|
good_response.stubs(:code).returns('401')
|
||||||
bad_response.stubs(:code).returns('404')
|
bad_response.stubs(:code).returns('404')
|
||||||
|
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + automate2_endpoint, headers, insecure)
|
.with(url + automate2_endpoint, headers, insecure)
|
||||||
.returns(bad_response)
|
.returns(bad_response)
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + automate_endpoint, headers, insecure)
|
.with(url + automate_endpoint, headers, insecure)
|
||||||
.returns(good_response)
|
.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
|
end
|
||||||
|
|
||||||
# Chef Automate currently returns 401 for `/compliance/version` but some
|
# 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(:code).returns('200')
|
||||||
good_response.stubs(:body).returns('Are You Looking For the Chef Server?')
|
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)
|
.with(url + automate2_endpoint, headers, insecure)
|
||||||
.returns(bad_response)
|
.returns(bad_response)
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + automate_endpoint, headers, insecure)
|
.with(url + automate_endpoint, headers, insecure)
|
||||||
.returns(good_response)
|
.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
|
end
|
||||||
|
|
||||||
it 'returns `nil` if a 200 is received from `https://URL/compliance/version` but not redirected to Chef Manage' do
|
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(:code).returns('200')
|
||||||
bad_response.stubs(:body).returns('No Chef Manage here')
|
bad_response.stubs(:body).returns('No Chef Manage here')
|
||||||
|
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + automate_endpoint, headers, insecure)
|
.with(url + automate_endpoint, headers, insecure)
|
||||||
.returns(bad_response)
|
.returns(bad_response)
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + automate2_endpoint, headers, insecure)
|
.with(url + automate2_endpoint, headers, insecure)
|
||||||
.returns(bad_response)
|
.returns(bad_response)
|
||||||
|
|
||||||
mock_compliance_response = mock
|
mock_compliance_response = mock
|
||||||
mock_compliance_response.stubs(:code).returns('404')
|
mock_compliance_response.stubs(:code).returns('404')
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + compliance_endpoint, headers, insecure)
|
.with(url + compliance_endpoint, headers, insecure)
|
||||||
.returns(mock_compliance_response)
|
.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
|
end
|
||||||
|
|
||||||
it 'returns `:compliance` when a 200 is received from `https://URL/api/version`' do
|
it 'returns `:compliance` when a 200 is received from `https://URL/api/version`' do
|
||||||
good_response.stubs(:code).returns('200')
|
good_response.stubs(:code).returns('200')
|
||||||
bad_response.stubs(:code).returns('404')
|
bad_response.stubs(:code).returns('404')
|
||||||
|
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + automate_endpoint, headers, insecure)
|
.with(url + automate_endpoint, headers, insecure)
|
||||||
.returns(bad_response)
|
.returns(bad_response)
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + automate2_endpoint, headers, insecure)
|
.with(url + automate2_endpoint, headers, insecure)
|
||||||
.returns(bad_response)
|
.returns(bad_response)
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + compliance_endpoint, headers, insecure)
|
.with(url + compliance_endpoint, headers, insecure)
|
||||||
.returns(good_response)
|
.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
|
end
|
||||||
|
|
||||||
it 'returns `nil` if it cannot determine the server type' do
|
it 'returns `nil` if it cannot determine the server type' do
|
||||||
bad_response.stubs(:code).returns('404')
|
bad_response.stubs(:code).returns('404')
|
||||||
|
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + automate2_endpoint, headers, insecure)
|
.with(url + automate2_endpoint, headers, insecure)
|
||||||
.returns(bad_response)
|
.returns(bad_response)
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + automate_endpoint, headers, insecure)
|
.with(url + automate_endpoint, headers, insecure)
|
||||||
.returns(bad_response)
|
.returns(bad_response)
|
||||||
Compliance::HTTP.expects(:get)
|
InspecPlugins::Compliance::HTTP.expects(:get)
|
||||||
.with(url + compliance_endpoint, headers, insecure)
|
.with(url + compliance_endpoint, headers, insecure)
|
||||||
.returns(bad_response)
|
.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
|
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' } }
|
let(:config) { { 'server' => 'myserver' } }
|
||||||
|
|
||||||
describe 'the check_compliance_token method' do
|
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
|
it 'returns without error if token is set' do
|
||||||
config['token'] = 'my-token'
|
config['token'] = 'my-token'
|
||||||
|
@ -18,59 +20,59 @@ describe Compliance::Fetcher do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when the server is an automate2 server' do
|
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
|
it 'returns the correct owner and profile name' do
|
||||||
config['profile'] = ['admin', 'ssh-baseline', nil]
|
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'
|
fetcher.send(:compliance_profile_name).must_equal 'admin/ssh-baseline'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when the server is an automate server pre-0.8.0' do
|
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
|
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'
|
fetcher.send(:compliance_profile_name).must_equal 'myowner/myprofile'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises an exception if the url is malformed' do
|
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
|
proc { fetcher.send(:compliance_profile_name) }.must_raise RuntimeError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when the server is an automate server 0.8.0-or-later' do
|
describe 'when the server is an automate server 0.8.0-or-later' do
|
||||||
before do
|
before do
|
||||||
Compliance::API.expects(:is_automate_server_pre_080?).with(config).returns(false)
|
InspecPlugins::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_080_and_later?).with(config).returns(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the correct profile name when the url is correct' do
|
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'
|
fetcher.send(:compliance_profile_name).must_equal 'myowner/myprofile'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises an exception if the url is malformed' do
|
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
|
proc { fetcher.send(:compliance_profile_name) }.must_raise RuntimeError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when the server is not an automate server (likely a compliance server)' do
|
describe 'when the server is not an automate server (likely a compliance server)' do
|
||||||
before do
|
before do
|
||||||
Compliance::API.expects(:is_automate_server_pre_080?).with(config).returns(false)
|
InspecPlugins::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_080_and_later?).with(config).returns(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the correct profile name when the url is correct' do
|
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'
|
fetcher.send(:compliance_profile_name).must_equal 'myowner/myprofile'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises an exception if the url is malformed' do
|
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
|
proc { fetcher.send(:compliance_profile_name) }.must_raise RuntimeError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -95,24 +97,24 @@ describe Compliance::Fetcher do
|
||||||
'latest_version'=>'' }]
|
'latest_version'=>'' }]
|
||||||
end
|
end
|
||||||
before do
|
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
|
end
|
||||||
|
|
||||||
it 'returns the correct profile name when parsing url' do
|
it 'returns the correct profile name when parsing url' do
|
||||||
Compliance::API.stubs(:profiles).returns(['success', profiles_result])
|
InspecPlugins::Compliance::API.stubs(:profiles).returns(['success', profiles_result])
|
||||||
fetcher = Compliance::Fetcher.resolve('compliance://admin/ssh-baseline')
|
fetcher = InspecPlugins::Compliance::Fetcher.resolve('compliance://admin/ssh-baseline')
|
||||||
assert = ['admin', 'ssh-baseline', nil]
|
assert = ['admin', 'ssh-baseline', nil]
|
||||||
fetcher.instance_variable_get(:"@config")['profile'].must_equal assert
|
fetcher.instance_variable_get(:"@config")['profile'].must_equal assert
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the correct profile name when parsing compliance hash' do
|
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 = {
|
hash = {
|
||||||
target: 'https://a2.instance.com/api/v0/compliance/tar',
|
target: 'https://a2.instance.com/api/v0/compliance/tar',
|
||||||
compliance: 'admin/ssh-baseline',
|
compliance: 'admin/ssh-baseline',
|
||||||
sha256: '132j1kjdasfasdoaefaewo12312',
|
sha256: '132j1kjdasfasdoaefaewo12312',
|
||||||
}
|
}
|
||||||
fetcher = Compliance::Fetcher.resolve(hash)
|
fetcher = InspecPlugins::Compliance::Fetcher.resolve(hash)
|
||||||
assert = ['admin', 'ssh-baseline', nil]
|
assert = ['admin', 'ssh-baseline', nil]
|
||||||
fetcher.instance_variable_get(:"@config")['profile'].must_equal assert
|
fetcher.instance_variable_get(:"@config")['profile'].must_equal assert
|
||||||
end
|
end
|
||||||
|
@ -139,14 +141,14 @@ describe Compliance::Fetcher do
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
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
|
end
|
||||||
|
|
||||||
it 'contains the upstream_sha256' do
|
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]
|
prof = profiles_result[0]
|
||||||
target = "compliance://#{prof['owner']}/#{prof['name']}"
|
target = "compliance://#{prof['owner']}/#{prof['name']}"
|
||||||
fetcher = Compliance::Fetcher.resolve(target)
|
fetcher = InspecPlugins::Compliance::Fetcher.resolve(target)
|
||||||
fetcher.upstream_sha256.must_equal prof['sha256']
|
fetcher.upstream_sha256.must_equal prof['sha256']
|
||||||
end
|
end
|
||||||
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 'fetchers/mock'
|
||||||
require 'inspec/dependencies/cache'
|
require 'inspec/dependencies/cache'
|
||||||
|
|
||||||
require_relative '../lib/bundles/inspec-compliance'
|
require_relative '../lib/bundles/inspec-supermarket'
|
||||||
|
|
||||||
require 'train'
|
require 'train'
|
||||||
CMD = Train.create('local', command_runner: :generic).connection
|
CMD = Train.create('local', command_runner: :generic).connection
|
||||||
|
|
|
@ -31,7 +31,7 @@ describe 'BaseCLI' do
|
||||||
default_options = { mock: { compliance: 'mock' } }
|
default_options = { mock: { compliance: 'mock' } }
|
||||||
Inspec::BaseCLI.stubs(:default_options).returns(default_options)
|
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)
|
cli.send(:opts, :mock)
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,11 +25,11 @@ class PluginLoaderTests < MiniTest::Test
|
||||||
|
|
||||||
@config_dir_path = File.join(mock_path, 'config_dirs')
|
@config_dir_path = File.join(mock_path, 'config_dirs')
|
||||||
@bundled_plugins = [
|
@bundled_plugins = [
|
||||||
:'inspec-compliance',
|
|
||||||
:'inspec-supermarket',
|
:'inspec-supermarket',
|
||||||
]
|
]
|
||||||
@core_plugins = [
|
@core_plugins = [
|
||||||
:'inspec-artifact',
|
:'inspec-artifact',
|
||||||
|
:'inspec-compliance',
|
||||||
:'inspec-habitat',
|
:'inspec-habitat',
|
||||||
:'inspec-init',
|
:'inspec-init',
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue