mirror of
https://github.com/inspec/inspec
synced 2024-11-10 07:04:15 +00:00
Extend inspec compliance cli to support automate backend
Signed-off-by: Victoria Jeffrey <vjeffrey@chef.io>
This commit is contained in:
parent
fa733c8692
commit
bdf5eae15e
6 changed files with 138 additions and 33 deletions
|
@ -8,12 +8,12 @@ require 'uri'
|
|||
module Compliance
|
||||
# API Implementation does not hold any state by itself,
|
||||
# everything will be stored in local Configuration store
|
||||
class API
|
||||
class API # rubocop:disable Metrics/ClassLength
|
||||
# return all compliance profiles available for the user
|
||||
def self.profiles(config)
|
||||
url = "#{config['server']}/user/compliance"
|
||||
config['automate'][0] ? url = "#{config['server']}/#{config['user']}" : url = "#{config['server']}/user/compliance"
|
||||
# TODO, api should not be dependent on .supported?
|
||||
response = Compliance::HTTP.get(url, config['token'], config['insecure'], !config.supported?(:oidc))
|
||||
response = Compliance::HTTP.get(url, config['token'], config['insecure'], config['user'], !config.supported?(:oidc), config['automate'], config['ent'])
|
||||
data = response.body
|
||||
response_code = response.code
|
||||
case response_code
|
||||
|
@ -21,11 +21,17 @@ module Compliance
|
|||
msg = 'success'
|
||||
profiles = JSON.parse(data)
|
||||
# iterate over profiles
|
||||
mapped_profiles = profiles.map do |owner, ps|
|
||||
ps.keys.map do |name|
|
||||
{ org: owner, name: name }
|
||||
end
|
||||
end.flatten
|
||||
if config['automate'][0]
|
||||
mapped_profiles = profiles.map do |owner, ps|
|
||||
{ org: ps['owner_id'], name: owner }
|
||||
end.flatten
|
||||
else
|
||||
mapped_profiles = profiles.map do |owner, ps|
|
||||
ps.keys.map do |name|
|
||||
{ org: owner, name: name }
|
||||
end
|
||||
end.flatten
|
||||
end
|
||||
return msg, mapped_profiles
|
||||
when '401'
|
||||
msg = '401 Unauthorized. Please check your token.'
|
||||
|
@ -69,8 +75,8 @@ Please login using `inspec compliance login https://compliance.test --user admin
|
|||
|
||||
def self.upload(config, owner, profile_name, archive_path)
|
||||
# upload the tar to Chef Compliance
|
||||
url = "#{config['server']}/owners/#{owner}/compliance/#{profile_name}/tar"
|
||||
res = Compliance::HTTP.post_file(url, config['token'], archive_path, config['insecure'], !config.supported?(:oidc))
|
||||
config['automate'][0] ? url = "#{config['server']}/#{config['user']}" : url = "#{config['server']}/owners/#{owner}/compliance/#{profile_name}/tar"
|
||||
res = Compliance::HTTP.post_file(url, config['token'], config['user'], archive_path, config['insecure'], !config.supported?(:oidc), config['automate'], config['ent'])
|
||||
[res.is_a?(Net::HTTPSuccess), res.body]
|
||||
end
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ module Compliance
|
|||
|
||||
options['server'] = server
|
||||
url = options['server'] + options['apipath']
|
||||
|
||||
if !options['user'].nil? && !options['password'].nil?
|
||||
# username / password
|
||||
_success, msg = login_username_password(url, options['user'], options['password'], options['insecure'])
|
||||
|
@ -56,6 +57,33 @@ module Compliance
|
|||
puts '', msg
|
||||
end
|
||||
|
||||
desc "login_automate SERVER --user='USER' --ent='ENT' --dctoken or --usertoken='TOKEN'", 'Log in to an Automate SERVER'
|
||||
option :dctoken, type: :string,
|
||||
desc: 'Data Collector token'
|
||||
option :usertoken, type: :string,
|
||||
desc: 'Automate user token'
|
||||
option :user, type: :string,
|
||||
desc: 'Automate username'
|
||||
option :ent, type: :string,
|
||||
desc: 'Enterprise for Chef Automate reporting'
|
||||
def login_automate(server) # rubocop:disable Metrics/AbcSize
|
||||
options['server'] = server
|
||||
url = options['server'] + '/compliance/profiles'
|
||||
|
||||
if url && !options['user'].nil? && !options['ent'].nil?
|
||||
if !options['dctoken'].nil? || !options['usertoken'].nil?
|
||||
msg = login_automate_config(url, options['user'], options['dctoken'], options['usertoken'], options['ent'])
|
||||
else
|
||||
puts "Please specify a token using --dctoken='DATA_COLLECTOR_TOKEN' or usertoken='AUTOMATE_TOKEN' "
|
||||
exit 1
|
||||
end
|
||||
else
|
||||
puts "Please login to your automate instance using 'inspec compliance automate SERVER --user AUTOMATE_USER --ent AUTOMATE_ENT --dctoken DC_TOKEN or --usertoken USER_TOKEN' "
|
||||
exit 1
|
||||
end
|
||||
puts '', msg
|
||||
end
|
||||
|
||||
desc 'profiles', 'list all available profiles in Chef Compliance'
|
||||
def profiles
|
||||
config = Compliance::Configuration.new
|
||||
|
@ -79,10 +107,8 @@ module Compliance
|
|||
def exec(*tests)
|
||||
config = Compliance::Configuration.new
|
||||
return if !loggedin(config)
|
||||
|
||||
# iterate over tests and add compliance scheme
|
||||
tests = tests.map { |t| 'compliance://' + t }
|
||||
|
||||
# execute profile from inspec exec implementation
|
||||
diagnose
|
||||
run_tests(tests, opts)
|
||||
|
@ -154,7 +180,8 @@ module Compliance
|
|||
puts "Start upload to #{owner}/#{profile_name}"
|
||||
pname = ERB::Util.url_encode(profile_name)
|
||||
|
||||
puts 'Uploading to Chef Compliance'
|
||||
config['automate'] ? upload_msg = 'Uploading to Chef Automate' : upload_msg = 'Uploading to Chef Compliance'
|
||||
puts upload_msg
|
||||
success, msg = Compliance::API.upload(config, owner, pname, archive_path)
|
||||
|
||||
if success
|
||||
|
@ -169,24 +196,27 @@ module Compliance
|
|||
desc 'version', 'displays the version of the Chef Compliance server'
|
||||
def version
|
||||
config = Compliance::Configuration.new
|
||||
info = Compliance::API.version(config['server'], config['insecure'])
|
||||
if !info.nil? && info['version']
|
||||
puts "Chef Compliance version: #{info['version']}"
|
||||
if config['automate']
|
||||
puts 'Version not available when logged in with Automate.'
|
||||
else
|
||||
puts 'Could not determine server version.'
|
||||
exit 1
|
||||
info = Compliance::API.version(config['server'], config['insecure'])
|
||||
if !info.nil? && info['version']
|
||||
puts "Chef Compliance version: #{info['version']}"
|
||||
else
|
||||
puts 'Could not determine server version.'
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'logout', 'user logout from Chef Compliance'
|
||||
def logout
|
||||
config = Compliance::Configuration.new
|
||||
unless config.supported?(:oidc) || config['token'].nil?
|
||||
unless config.supported?(:oidc) || config['token'].nil? || config['automate']
|
||||
config = Compliance::Configuration.new
|
||||
url = "#{config['server']}/logout"
|
||||
Compliance::API.post(url, config['token'], config['insecure'], !config.supported?(:oidc))
|
||||
end
|
||||
|
||||
success = config.destroy
|
||||
|
||||
if success
|
||||
|
@ -198,6 +228,29 @@ module Compliance
|
|||
|
||||
private
|
||||
|
||||
def login_automate_config(url, user, dctoken, usertoken, ent)
|
||||
config = Compliance::Configuration.new
|
||||
config['server'] = url
|
||||
config['ent'] = ent
|
||||
config['user'] = user
|
||||
|
||||
# determine token method being used
|
||||
if !dctoken.nil?
|
||||
config['token'] = dctoken
|
||||
token_type = 'dctoken'
|
||||
token_msg = 'data collector token'
|
||||
else
|
||||
config['token'] = usertoken
|
||||
token_type = 'usertoken'
|
||||
token_msg = 'automate user token'
|
||||
end
|
||||
|
||||
config['automate'] = [true, token_type]
|
||||
config.store
|
||||
msg = "You have logged into your automate instance: '#{url}' with user: '#{user}', ent: '#{ent}' and your #{token_msg}"
|
||||
msg
|
||||
end
|
||||
|
||||
def login_refreshtoken(url, options)
|
||||
success, msg, access_token = Compliance::API.get_token_via_refresh_token(url, options['refresh_token'], options['insecure'])
|
||||
if success
|
||||
|
@ -267,7 +320,7 @@ module Compliance
|
|||
|
||||
def loggedin(config)
|
||||
serverknown = !config['server'].nil?
|
||||
puts 'You need to login first with `inspec compliance login`' if !serverknown
|
||||
puts 'You need to login first with `inspec compliance login` or `inspec compliance automate`' if !serverknown
|
||||
serverknown
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,13 +9,21 @@ module Compliance
|
|||
# implements a simple http abstraction on top of Net::HTTP
|
||||
class HTTP
|
||||
# generic get requires
|
||||
def self.get(url, token, insecure, basic_auth = false)
|
||||
def self.get(url, token, insecure, user, basic_auth = false, automate = false, ent = nil) # rubocop:disable Metrics/ParameterLists
|
||||
uri = URI.parse(url)
|
||||
req = Net::HTTP::Get.new(uri.path)
|
||||
|
||||
return send_request(uri, req, insecure) if token.nil?
|
||||
|
||||
if basic_auth
|
||||
if automate[0]
|
||||
req.add_field('chef-delivery-enterprise', ent)
|
||||
if automate[1] == 'dctoken'
|
||||
req.add_field('x-data-collector-token', token)
|
||||
else
|
||||
req.add_field('chef-delivery-user', user)
|
||||
req.add_field('chef-delivery-token', token)
|
||||
end
|
||||
elsif basic_auth
|
||||
req.basic_auth(token, '')
|
||||
else
|
||||
req['Authorization'] = "Bearer #{token}"
|
||||
|
@ -39,7 +47,7 @@ module Compliance
|
|||
end
|
||||
|
||||
# post a file
|
||||
def self.post_file(url, token, file_path, insecure, basic_auth = false)
|
||||
def self.post_file(url, token, user, file_path, insecure, basic_auth = false, automate = false, ent = nil) # rubocop:disable Metrics/ParameterLists
|
||||
uri = URI.parse(url)
|
||||
fail "Unable to parse URL: #{url}" if uri.nil? || uri.host.nil?
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
@ -49,7 +57,15 @@ module Compliance
|
|||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if insecure
|
||||
|
||||
req = Net::HTTP::Post.new(uri.path)
|
||||
if basic_auth
|
||||
if automate[0]
|
||||
req.add_field('chef-delivery-enterprise', ent)
|
||||
if automate[1] == 'dctoken'
|
||||
req.add_field('x-data-collector-token', token)
|
||||
else
|
||||
req.add_field('chef-delivery-user', user)
|
||||
req.add_field('chef-delivery-token', token)
|
||||
end
|
||||
elsif basic_auth
|
||||
req.basic_auth token, ''
|
||||
else
|
||||
req['Authorization'] = "Bearer #{token}"
|
||||
|
|
|
@ -13,8 +13,7 @@ module Compliance
|
|||
class Fetcher < Fetchers::Url
|
||||
name 'compliance'
|
||||
priority 500
|
||||
|
||||
def self.resolve(target)
|
||||
def self.resolve(target) # rubocop:disable PerceivedComplexity
|
||||
uri = if target.is_a?(String) && URI(target).scheme == 'compliance'
|
||||
URI(target)
|
||||
elsif target.respond_to?(:key?) && target.key?(:compliance)
|
||||
|
@ -26,14 +25,21 @@ module Compliance
|
|||
# check if we have a compliance token
|
||||
config = Compliance::Configuration.new
|
||||
if config['token'].nil?
|
||||
if config['automate'][0]
|
||||
server = 'automate'
|
||||
msg = 'inspec compliance automate https://your_automate_server --user USER --ent ENT --dctoken DCTOKEN or --usertoken USERTOKEN'
|
||||
else
|
||||
server = 'compliance'
|
||||
msg = "inspec compliance login https://your_compliance_server --user admin --insecure --token 'PASTE TOKEN HERE' "
|
||||
end
|
||||
fail Inspec::FetcherFailure, <<EOF
|
||||
|
||||
Cannot fetch #{uri} because your compliance token has not been
|
||||
Cannot fetch #{uri} because your #{server} token has not been
|
||||
configured.
|
||||
|
||||
Please login using
|
||||
|
||||
inspec compliance login https://your_compliance_server --user admin --insecure --token 'PASTE TOKEN HERE'
|
||||
#{msg}
|
||||
EOF
|
||||
end
|
||||
|
||||
|
@ -48,8 +54,13 @@ EOF
|
|||
end
|
||||
|
||||
def self.target_url(profile, config)
|
||||
owner, id = profile.split('/')
|
||||
"#{config['server']}/owners/#{owner}/compliance/#{id}/tar"
|
||||
if config['automate'][0]
|
||||
target = "#{config['server']}/#{profile}/tar"
|
||||
else
|
||||
owner, id = profile.split('/')
|
||||
target = "#{config['server']}/owners/#{owner}/compliance/#{id}/tar"
|
||||
end
|
||||
target
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -8,7 +8,7 @@ require 'tempfile'
|
|||
require 'open-uri'
|
||||
|
||||
module Fetchers
|
||||
class Url < Inspec.fetcher(1)
|
||||
class Url < Inspec.fetcher(1) # rubocop:disable Metrics/ClassLength
|
||||
MIME_TYPES = {
|
||||
'application/x-zip-compressed' => '.zip',
|
||||
'application/zip' => '.zip',
|
||||
|
@ -126,7 +126,19 @@ module Fetchers
|
|||
Inspec::Log.debug("Fetching URL: #{@target}")
|
||||
http_opts = {}
|
||||
http_opts['ssl_verify_mode'.to_sym] = OpenSSL::SSL::VERIFY_NONE if @insecure
|
||||
http_opts['Authorization'] = "Bearer #{@token}" if @token
|
||||
if @config
|
||||
if @config['automate']
|
||||
automate = true
|
||||
http_opts['chef-delivery-enterprise'] = @config['ent']
|
||||
if @config['automate'][1] == 'dctoken'
|
||||
http_opts['x-data-collector-token'] = @config['token']
|
||||
else
|
||||
http_opts['chef-delivery-user'] = @config['user']
|
||||
http_opts['chef-delivery-token'] = @config['token']
|
||||
end
|
||||
end
|
||||
end
|
||||
http_opts['Authorization'] = "Bearer #{@token}" if @token && automate.nil?
|
||||
remote = open(@target, http_opts)
|
||||
@archive_type = file_type_from_remote(remote) # side effect :(
|
||||
archive = Tempfile.new(['inspec-dl-', @archive_type])
|
||||
|
|
|
@ -35,6 +35,13 @@ describe 'inspec compliance' do
|
|||
out.stdout.must_include 'Please run `inspec compliance login SERVER` with options'
|
||||
end
|
||||
|
||||
it 'automate with missing parameters' do
|
||||
out = inspec('compliance login_automate http://example.com')
|
||||
out.exit_status.must_equal 1
|
||||
#TODO: inspec should really use stderr for errors
|
||||
out.stdout.must_include 'Please login to your automate instance using'
|
||||
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`'
|
||||
|
|
Loading…
Reference in a new issue