diff --git a/examples/profile_upload/.kitchen.yml b/examples/profile_upload/.kitchen.yml new file mode 100644 index 00000000..9d49ad58 --- /dev/null +++ b/examples/profile_upload/.kitchen.yml @@ -0,0 +1,28 @@ +--- +driver: + name: vagrant + synced_folders: + - ["./profile_tar", "/opt", 'create: true'] + +provisioner: + name: chef_zero + +platforms: + - name: ubuntu-14.04 + - name: centos-7.2 + +suites: + - name: default + run_list: + - recipe[profile_upload::default] + attributes: + audit: + collector: 'chef-compliance' + server: "https://192.168.33.201/api" + inspec_version: 1.2.1 + insecure: true + refresh_token: '4/0N2sqBYyHpmxwBO0KegXs7IPJ9tKF6qQDB9iCOA72pDJLGcHQ69XZWOIOIT6JEwlmBFnLd7o_UN5MlwMTM_ofg==' + owner: admin + profiles: + - name: admin/hardening + path: /opt/ssh-hardening.tar.gz diff --git a/examples/profile_upload/Berksfile b/examples/profile_upload/Berksfile new file mode 100644 index 00000000..b420cc14 --- /dev/null +++ b/examples/profile_upload/Berksfile @@ -0,0 +1,5 @@ +source 'https://supermarket.chef.io' + +metadata + +cookbook 'audit', path: '../../../audit' diff --git a/examples/profile_upload/README.md b/examples/profile_upload/README.md new file mode 100644 index 00000000..50847a65 --- /dev/null +++ b/examples/profile_upload/README.md @@ -0,0 +1,19 @@ +# profile_upload + +Example cookbook used to upload a profile to chef-compliance. + +To use this cookbook: + +1) ensure you update the .kitchen.yml with your: + * chef-compliance server url + * refresh_token + * user (owner) + +2) create a directory in this directory named 'profile_tar', and stick an archived + profile in there (something like: ssh-hardening.tar.gz) + (Note: you can easily archive an existing profile using `inspec archive PATH`) + +3) run `kitchen converge` + +4) go look in chef-compliance, under the compliance profiles section, and see your + uploaded profile! tada! \ No newline at end of file diff --git a/examples/profile_upload/metadata.rb b/examples/profile_upload/metadata.rb new file mode 100644 index 00000000..223d7383 --- /dev/null +++ b/examples/profile_upload/metadata.rb @@ -0,0 +1,17 @@ +name 'profile_upload' +maintainer 'The Authors' +maintainer_email 'you@example.com' +license 'all_rights' +description 'Installs/Configures test-profiles' +long_description 'Installs/Configures test-profiles' +version '0.1.0' + +# If you upload to Supermarket you should set this so your cookbook +# gets a `View Issues` link +# issues_url 'https://github.com//test-profiles/issues' if respond_to?(:issues_url) + +# If you upload to Supermarket you should set this so your cookbook +# gets a `View Source` link +# source_url 'https://github.com//test-profiles' if respond_to?(:source_url) + +depends 'audit' diff --git a/examples/profile_upload/recipes/default.rb b/examples/profile_upload/recipes/default.rb new file mode 100644 index 00000000..c835b6ad --- /dev/null +++ b/examples/profile_upload/recipes/default.rb @@ -0,0 +1,9 @@ +# +# Cookbook Name:: test-profiles +# Recipe:: default +# +# Copyright (c) 2016 The Authors, All Rights Reserved. + +# package 'git' + +include_recipe 'audit::upload' diff --git a/libraries/compliance.rb b/libraries/compliance.rb new file mode 100644 index 00000000..def96533 --- /dev/null +++ b/libraries/compliance.rb @@ -0,0 +1,34 @@ +# encoding: utf-8 + +# load all the inspec and compliance bundle requirements +def load_inspec_libs + require 'inspec' + require 'bundles/inspec-compliance/api' + require 'bundles/inspec-compliance/http' + require 'bundles/inspec-compliance/configuration' +end + +# exchanges refresh token for access token. access token is needed +# to get a proper config to talk with the compliance api +def retrieve_access_token(server_url, refresh_token, insecure) + success, msg, access_token = Compliance::API.get_token_via_refresh_token(server_url, refresh_token, insecure) + unless success + Chef::Log.error("Unable to get a Chef Compliance API access_token: #{msg}") + end + access_token +end + +# used for compliance config +def compliance_version + Compliance::API.version(server, insecure) +end + +# check if profile already exists on compliance server +def check_existence(config, path) + Compliance::API.exist?(config, path) +end + +# upload profile to compliance server +def upload_profile(config, owner, profile_name, path) + Compliance::API.upload(config, owner, profile_name, path) +end diff --git a/libraries/helper.rb b/libraries/helper.rb index 283d95db..6b8937e6 100644 --- a/libraries/helper.rb +++ b/libraries/helper.rb @@ -24,6 +24,7 @@ def entity_uuid end end + # Convert the strings in the profile definitions into symbols for handling def tests_for_runner tests = node['audit']['profiles'] tests_for_runner = tests.map { |test| Hash[test.map { |k, v| [k.to_sym, v] }] } diff --git a/recipes/default.rb b/recipes/default.rb index 4b42a7b1..faa98a84 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -1,6 +1,6 @@ include_recipe 'chef_handler' -# install inspec if require +# install inspec inspec 'inspec' do version node['audit']['inspec_version'] action :install diff --git a/recipes/upload.rb b/recipes/upload.rb new file mode 100644 index 00000000..ecf8d87b --- /dev/null +++ b/recipes/upload.rb @@ -0,0 +1,46 @@ +# encoding: utf-8 +# +# Cookbook Name:: audit +# Recipe:: upload +# +# Copyright 2016 Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ensure inspec is available +inspec 'inspec' do + version node['audit']['inspec_version'] + action :install +end + +# iterate over all selected profiles and upload them +node['audit']['profiles'].each do |profile| + profile_owner = profile[:name] + profile_path = profile[:path] + Chef::Log.error "Invalid profile name '#{profile_owner}'. "\ + "Must contain /, e.g. 'john/ssh'" if profile_owner !~ %r{\/} + _o, p = profile_owner.split('/').last(2) + Chef::Log.error "Invalid path '#{profile_path}'" if profile_path.nil? + + # upload profile + compliance_upload p do + profile_name p + owner node['audit']['owner'] + server node['audit']['server'] + path profile[:path] + insecure node['audit']['insecure'] + overwrite node['audit']['overwrite'] + refresh_token node['audit']['refresh_token'] + action :upload + end +end diff --git a/resources/inspec.rb b/resources/inspec.rb index a11082b2..5e1e8874 100644 --- a/resources/inspec.rb +++ b/resources/inspec.rb @@ -7,7 +7,7 @@ default_action :install -# installs inspec if required +# installs inspec action :install do converge_by 'install/update inspec' do chef_gem 'inspec' do @@ -24,7 +24,7 @@ def verify_inspec_version(inspec_version) require 'inspec' - # check we have the right inspec version + # check that we have the right inspec version if Inspec::VERSION != inspec_version && inspec_version !='latest' Chef::Log.warn "Wrong version of inspec (#{Inspec::VERSION}), please "\ 'remove old versions (/opt/chef/embedded/bin/gem uninstall inspec).' diff --git a/resources/upload.rb b/resources/upload.rb new file mode 100644 index 00000000..c55d8b12 --- /dev/null +++ b/resources/upload.rb @@ -0,0 +1,49 @@ +# encoding: utf-8 + +provides :compliance_upload +resource_name :compliance_upload + +property :path, String +property :owner, String, required: true +property :server, [String, URI, nil] +property :insecure, [TrueClass, FalseClass], default: false +property :overwrite, [TrueClass, FalseClass], default: true +property :refresh_token, String +property :profile_name, String + +default_action :upload + +# upload profile to compliance server +action :upload do + converge_by 'run profile validation checks' do + load_inspec_libs + Chef::Log.error 'Path to profile archive not specified' if path.nil? + Chef::Log.error "Profile archive file #{path} does not exist." unless ::File.exist?(path) + profile = Inspec::Profile.for_target(path, {}) + result = profile.check + Chef::Log.info result[:summary].inspect + Chef::Log.error 'Profile check failed' unless result[:summary][:valid] + Chef::Log.info 'Profile is valid' + end + + converge_by 'upload compliance profile' do + access_token = retrieve_access_token(server, refresh_token, insecure) + + Chef::Log.error 'Unable to read access token, aborting upload' unless access_token + config = Compliance::Configuration.new + config['token'] = access_token + config['insecure'] = insecure + config['server'] = server + config['version'] = compliance_version + if check_existence(config, "#{profile_name}/#{path}") && !overwrite + Chef::Log.error 'Profile exists on the server, use property `overwrite`' + else + success, msg = upload_profile(config, owner, profile_name, path) + if success + Chef::Log.info 'Successfully uploaded profile' + else + Chef::Log.error "Error during profile upload: #{msg}" + end + end + end +end