Merge pull request #1297 from chef/vj/support-asset-store-compliance-cli

enable inspec compliance cli support automate
This commit is contained in:
Christoph Hartmann 2016-11-30 18:22:51 +01:00 committed by GitHub
commit 939e6ca91e
6 changed files with 150 additions and 43 deletions

View file

@ -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"
# TODO, api should not be dependent on .supported?
response = Compliance::HTTP.get(url, config['token'], config['insecure'], !config.supported?(:oidc))
config['server_type'] == 'automate' ? url = "#{config['server']}/#{config['user']}" : url = "#{config['server']}/user/compliance"
headers = get_headers(config)
response = Compliance::HTTP.get(url, headers, config['insecure'])
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['server_type'] == 'automate'
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,9 @@ 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['server_type'] == 'automate' ? url = "#{config['server']}/#{config['user']}" : url = "#{config['server']}/owners/#{owner}/compliance/#{profile_name}/tar"
headers = get_headers(config)
res = Compliance::HTTP.post_file(url, headers, archive_path, config['insecure'])
[res.is_a?(Net::HTTPSuccess), res.body]
end
@ -121,5 +128,20 @@ Please login using `inspec compliance login https://compliance.test --user admin
[success, msg, access_token]
end
def self.get_headers(config)
if config['server_type'] == 'automate'
headers = { 'chef-delivery-enterprise' => config['automate']['ent'] }
if config['automate']['token_type'] == 'dctoken'
headers['x-data-collector-token'] = config['token']
else
headers['chef-delivery-user'] = config['user']
headers['chef-delivery-token'] = config['token']
end
else
headers = { 'Authorization' => "Bearer #{config['token']}" }
end
headers
end
end
end

View file

@ -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,35 @@ 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'
option :insecure, aliases: :k, type: :boolean,
desc: 'Explicitly allows InSpec to perform "insecure" SSL connections and transfers'
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'], options['insecure'])
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 login_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 +109,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 +182,8 @@ module Compliance
puts "Start upload to #{owner}/#{profile_name}"
pname = ERB::Util.url_encode(profile_name)
puts 'Uploading to Chef Compliance'
config['server_type'] == '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 +198,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['server_type'] == '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['server_type'] == '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 +230,32 @@ module Compliance
private
def login_automate_config(url, user, dctoken, usertoken, ent, insecure) # rubocop:disable Metrics/ParameterLists
config = Compliance::Configuration.new
config['user'] = user
config['server'] = url
config['automate'] = {}
config['automate']['ent'] = ent
config['server_type'] = 'automate'
config['insecure'] = insecure
# 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']['token_type'] = token_type
config.store
msg = "Stored configuration for Chef Automate: '#{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
@ -206,6 +264,7 @@ module Compliance
config['token'] = access_token
config['insecure'] = options['insecure']
config['version'] = Compliance::API.version(url, options['insecure'])
config['server_type'] = 'compliance'
config.store
end
@ -221,6 +280,7 @@ module Compliance
config['token'] = api_token
config['insecure'] = insecure
config['version'] = Compliance::API.version(url, insecure)
config['server_type'] = 'compliance'
config.store
success = true
end
@ -267,7 +327,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 login_automate`' if !serverknown
serverknown
end
end

View file

@ -9,16 +9,13 @@ 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, headers = nil, insecure)
uri = URI.parse(url)
req = Net::HTTP::Get.new(uri.path)
return send_request(uri, req, insecure) if token.nil?
if basic_auth
req.basic_auth(token, '')
else
req['Authorization'] = "Bearer #{token}"
if !headers.nil?
headers.each do |key, value|
req.add_field(key, value)
end
end
send_request(uri, req, insecure)
end
@ -39,7 +36,7 @@ module Compliance
end
# post a file
def self.post_file(url, token, file_path, insecure, basic_auth = false)
def self.post_file(url, headers, file_path, insecure)
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,10 +46,8 @@ module Compliance
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if insecure
req = Net::HTTP::Post.new(uri.path)
if basic_auth
req.basic_auth token, ''
else
req['Authorization'] = "Bearer #{token}"
headers.each do |key, value|
req.add_field(key, value)
end
req.body_stream=File.open(file_path, 'rb')

View file

@ -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['server_type'] == 'automate'
server = 'automate'
msg = 'inspec compliance login_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['server_type'] == 'automate'
target = "#{config['server']}/#{profile}/tar"
else
owner, id = profile.split('/')
target = "#{config['server']}/owners/#{owner}/compliance/#{id}/tar"
end
target
end
#

View file

@ -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['server_type'] == 'automate'
http_opts['chef-delivery-enterprise'] = @config['automate']['ent']
if @config['automate']['token_type'] == '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
elsif @token
http_opts['Authorization'] = "Bearer #{@token}"
end
end
remote = open(@target, http_opts)
@archive_type = file_type_from_remote(remote) # side effect :(
archive = Tempfile.new(['inspec-dl-', @archive_type])

View file

@ -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`'