2016-02-05 07:38:45 +00:00
# encoding: utf-8
# author: Christoph Hartmann
# author: Dominik Richter
require 'thor'
2016-03-23 15:40:01 +00:00
require 'erb'
2016-02-05 07:38:45 +00:00
module Compliance
2016-02-05 10:06:00 +00:00
class ComplianceCLI < Inspec :: BaseCLI # rubocop:disable Metrics/ClassLength
2016-02-05 07:38:45 +00:00
namespace 'compliance'
2016-08-18 18:10:09 +00:00
# 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
2017-10-26 15:32:47 +00:00
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
2016-03-01 19:51:23 +00:00
option :insecure , aliases : :k , type : :boolean ,
desc : 'Explicitly allows InSpec to perform "insecure" SSL connections and transfers'
2016-04-08 12:03:41 +00:00
option :user , type : :string , required : false ,
2017-10-26 15:32:47 +00:00
desc : 'Username'
2016-04-08 12:03:41 +00:00
option :password , type : :string , required : false ,
2017-10-26 15:32:47 +00:00
desc : 'Password (Chef Compliance Only)'
2016-04-08 12:03:41 +00:00
option :token , type : :string , required : false ,
2017-10-26 15:32:47 +00:00
desc : 'Access token'
2016-04-08 12:03:41 +00:00
option :refresh_token , type : :string , required : false ,
2017-10-26 15:32:47 +00:00
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 )
2016-04-08 12:03:41 +00:00
options [ 'server' ] = server
2017-10-26 15:32:47 +00:00
Compliance :: API . login ( options )
2016-02-05 07:38:45 +00:00
end
2017-10-26 15:32:47 +00:00
desc " login_automate https://SERVER --insecure --user='USER' --ent='ENTERPRISE' --usertoken='TOKEN' " , 'Log in to a Chef Automate SERVER (DEPRECATED: Please use `login`)'
2017-04-13 15:24:17 +00:00
long_desc <<-LONGDESC
2017-10-26 15:32:47 +00:00
This commmand is deprecated and will be removed , please use ` --login ` .
` login_automate ` allows you to use InSpec with Chef Automate .
2017-04-13 15:24:17 +00:00
2017-10-26 15:32:47 +00:00
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
2017-04-13 15:24:17 +00:00
LONGDESC
2016-11-30 14:30:11 +00:00
option :insecure , aliases : :k , type : :boolean ,
desc : 'Explicitly allows InSpec to perform "insecure" SSL connections and transfers'
2017-10-26 15:32:47 +00:00
option :user , type : :string , required : true ,
desc : 'Username'
option :usertoken , type : :string , required : false ,
desc : 'Access token (DEPRECATED: Please use `--token`)'
option :token , type : :string , required : false ,
desc : 'Access token'
option :dctoken , type : :string , required : false ,
desc : 'Data Collector token'
option :ent , type : :string , required : true ,
desc : 'Enterprise for Chef Automate reporting'
def login_automate ( server )
warn '[DEPRECATION] `inspec compliance login_automate` is deprecated. Please use `inspec compliance login`'
2016-11-15 19:19:39 +00:00
options [ 'server' ] = server
2017-10-26 15:32:47 +00:00
options [ 'token' ] = options [ 'usertoken' ] if options [ 'usertoken' ]
2017-04-13 15:24:17 +00:00
2017-10-26 15:32:47 +00:00
Compliance :: API . login ( options )
2016-11-15 19:19:39 +00:00
end
2016-02-05 07:38:45 +00:00
desc 'profiles' , 'list all available profiles in Chef Compliance'
2017-11-07 19:01:55 +00:00
option :owner , type : :string , required : false ,
desc : 'owner whose profiles to list'
2016-02-05 07:38:45 +00:00
def profiles
2016-04-13 11:47:33 +00:00
config = Compliance :: Configuration . new
2016-04-29 00:20:25 +00:00
return if ! loggedin ( config )
2017-11-07 19:01:55 +00:00
# set owner to config
config [ 'owner' ] = options [ 'owner' ] || config [ 'user' ]
2016-08-17 16:15:11 +00:00
msg , profiles = Compliance :: API . profiles ( config )
2017-04-13 15:24:17 +00:00
profiles . sort_by! { | hsh | hsh [ 'title' ] }
2016-02-05 07:38:45 +00:00
if ! profiles . empty?
# iterate over profiles
2016-02-05 10:06:00 +00:00
headline ( 'Available profiles:' )
2016-02-05 07:38:45 +00:00
profiles . each { | profile |
2017-09-13 21:52:45 +00:00
li ( " #{ profile [ 'title' ] } v #{ profile [ 'version' ] } ( #{ mark_text ( profile [ 'owner_id' ] + '/' + profile [ 'name' ] ) } ) " )
2016-02-05 07:38:45 +00:00
}
else
2016-08-17 16:15:11 +00:00
puts msg , 'Could not find any profiles'
2016-08-18 16:34:09 +00:00
exit 1
2016-02-05 07:38:45 +00:00
end
2017-05-16 21:56:56 +00:00
rescue Compliance :: ServerConfigurationMissing
2017-10-26 15:32:47 +00:00
STDERR . puts " \n Server configuration information is missing. Please login using `inspec compliance login` "
2017-05-16 21:56:56 +00:00
exit 1
2016-02-05 07:38:45 +00:00
end
desc 'exec PROFILE' , 'executes a Chef Compliance profile'
2016-03-06 14:07:12 +00:00
exec_options
2016-02-05 07:38:45 +00:00
def exec ( * tests )
2016-04-29 00:20:25 +00:00
config = Compliance :: Configuration . new
return if ! loggedin ( config )
2016-02-05 07:38:45 +00:00
# iterate over tests and add compliance scheme
2017-05-16 21:56:56 +00:00
tests = tests . map { | t | 'compliance://' + Compliance :: API . sanitize_profile_name ( t ) }
2016-02-05 07:38:45 +00:00
# execute profile from inspec exec implementation
diagnose
2016-02-21 22:17:01 +00:00
run_tests ( tests , opts )
2016-02-05 07:38:45 +00:00
end
2017-01-05 11:37:43 +00:00
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 )
2017-05-16 21:56:56 +00:00
profile_name = Compliance :: API . sanitize_profile_name ( profile_name )
2017-01-05 11:37:43 +00:00
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
2016-02-05 07:38:45 +00:00
desc 'upload PATH' , 'uploads a local profile to Chef Compliance'
2016-02-05 10:06:00 +00:00
option :overwrite , type : :boolean , default : false ,
2017-11-07 19:01:55 +00:00
desc : 'Overwrite existing profile on Server.'
option :owner , type : :string , required : false ,
desc : 'Owner that should own the profile'
2016-04-29 00:20:25 +00:00
def upload ( path ) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, PerceivedComplexity, Metrics/CyclomaticComplexity
config = Compliance :: Configuration . new
return if ! loggedin ( config )
2017-11-07 19:01:55 +00:00
# set owner to config
config [ 'owner' ] = options [ 'owner' ] || config [ 'user' ]
2016-04-13 14:54:29 +00:00
unless File . exist? ( path )
puts " Directory #{ path } does not exist. "
exit 1
end
2016-12-01 13:33:35 +00:00
vendor_deps ( path , options ) if File . directory? ( path )
2016-11-29 02:08:10 +00:00
2016-02-05 10:06:00 +00:00
o = options . dup
configure_logger ( o )
# check the profile, we only allow to upload valid profiles
2016-02-22 20:11:49 +00:00
profile = Inspec :: Profile . for_target ( path , o )
2016-02-05 10:06:00 +00:00
# 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
2017-01-18 01:38:34 +00:00
if ( config [ 'token' ] . nil? && config [ 'refresh_token' ] . nil? ) || config [ 'user' ] . nil?
2016-02-05 10:06:00 +00:00
error . call ( 'Please login via `inspec compliance login`' )
end
# read profile name from inspec.yml
profile_name = profile . params [ :name ]
# check that the profile is not uploaded already,
# confirm upload to the user (overwrite with --force)
2017-11-07 19:01:55 +00:00
if Compliance :: API . exist? ( config , " #{ config [ 'owner' ] } / #{ profile_name } " ) && ! options [ 'overwrite' ]
2016-02-05 10:06:00 +00:00
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
2016-02-05 07:38:45 +00:00
2016-02-05 10:06:00 +00:00
# if it is a directory, tar it to tmp directory
if File . directory? ( path )
2016-03-23 15:13:23 +00:00
archive_path = Dir :: Tmpname . create ( [ profile_name , '.tar.gz' ] ) { }
2016-02-05 10:06:00 +00:00
puts " Generate temporary profile archive at #{ archive_path } "
2016-03-23 15:13:23 +00:00
profile . archive ( { output : archive_path , ignore_errors : false , overwrite : true } )
2016-02-05 10:06:00 +00:00
else
archive_path = path
end
2017-11-07 19:01:55 +00:00
puts " Start upload to #{ config [ 'owner' ] } / #{ profile_name } "
2016-03-23 15:40:01 +00:00
pname = ERB :: Util . url_encode ( profile_name )
2016-02-05 10:06:00 +00:00
2017-05-16 21:56:56 +00:00
Compliance :: API . is_automate_server? ( config ) ? upload_msg = 'Uploading to Chef Automate' : upload_msg = 'Uploading to Chef Compliance'
2016-11-15 19:19:39 +00:00
puts upload_msg
2017-11-07 19:01:55 +00:00
success , msg = Compliance :: API . upload ( config , config [ 'owner' ] , pname , archive_path )
2016-02-05 07:38:45 +00:00
2016-02-05 10:06:00 +00:00
if success
2016-02-05 07:38:45 +00:00
puts 'Successfully uploaded profile'
else
2016-02-05 10:06:00 +00:00
puts 'Error during profile upload:'
puts msg
2016-08-18 16:34:09 +00:00
exit 1
2016-02-05 07:38:45 +00:00
end
end
desc 'version' , 'displays the version of the Chef Compliance server'
def version
2016-04-04 15:29:13 +00:00
config = Compliance :: Configuration . new
2017-05-16 21:56:56 +00:00
info = Compliance :: API . version ( config )
if ! info . nil? && info [ 'version' ]
puts " Name: #{ info [ 'api' ] } "
puts " Version: #{ info [ 'version' ] } "
2016-02-05 07:38:45 +00:00
else
2017-05-16 21:56:56 +00:00
puts 'Could not determine server version.'
exit 1
2016-02-05 07:38:45 +00:00
end
2017-05-16 21:56:56 +00:00
rescue Compliance :: ServerConfigurationMissing
puts " \n Server configuration information is missing. Please login using `inspec compliance login` "
exit 1
2016-02-05 07:38:45 +00:00
end
desc 'logout' , 'user logout from Chef Compliance'
def logout
2016-04-13 14:54:29 +00:00
config = Compliance :: Configuration . new
2016-11-29 14:35:16 +00:00
unless config . supported? ( :oidc ) || config [ 'token' ] . nil? || config [ 'server_type' ] == 'automate'
2016-04-13 11:47:33 +00:00
config = Compliance :: Configuration . new
url = " #{ config [ 'server' ] } /logout "
Compliance :: API . post ( url , config [ 'token' ] , config [ 'insecure' ] , ! config . supported? ( :oidc ) )
2016-04-04 15:29:13 +00:00
end
2016-04-13 11:47:33 +00:00
success = config . destroy
2016-04-04 15:29:13 +00:00
if success
2016-02-05 07:38:45 +00:00
puts 'Successfully logged out'
else
puts 'Could not log out'
end
end
2016-04-13 11:47:33 +00:00
private
2016-04-29 00:20:25 +00:00
def loggedin ( config )
serverknown = ! config [ 'server' ] . nil?
2017-10-26 15:32:47 +00:00
puts 'You need to login first with `inspec compliance login`' if ! serverknown
2016-04-29 00:20:25 +00:00
serverknown
end
2016-02-05 07:38:45 +00:00
end
2016-02-05 13:48:55 +00:00
# register the subcommand to Inspec CLI registry
2016-02-08 21:25:07 +00:00
Inspec :: Plugins :: CLI . add_subcommand ( ComplianceCLI , 'compliance' , 'compliance SUBCOMMAND ...' , 'Chef Compliance commands' , { } )
2016-02-05 07:38:45 +00:00
end