Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to chef_handler from resource-based audit #71

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 24 additions & 65 deletions .kitchen.yml
Original file line number Diff line number Diff line change
@@ -1,78 +1,37 @@
---
driver:
name: dokken
name: vagrant
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a step backwards? Or was this just for your testing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't run dokken on my windows machine. Plus I'm not convinced this ever worked, at least for the chef server. So it was for my testing. I'd be happy to roll it back, but don't see dokken as adding any benefit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually docker tests are faster / I think you can run it in travis

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah so faster tests + CI but no windows development support.I would understand if the former was deemed more valuable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose to add multiple kitchen files as we are doing in https://github.com/chef/inspec
Reason: we cannot run vagrant in travis. Still like to see the vagrant option within the cookbook. Just move it to .kitchen.vagrant.yml

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted back to dokken per @chris-rock and @bigbam505 suggestions. There is an alternative for vagrant called .kitchen.vagrant.yml

chef_version: 12.7.2
privileged: true # because Docker and SystemD/Upstart

transport:
name: dokken

provisioner:
name: dokken
name: chef_zero

verifier:
name: inspec
sudo: false

platforms:
- name: ubuntu-12.04
driver:
image: ubuntu:12.04
- name: ubuntu-14.04
driver:
image: ubuntu:14.04
- name: ubuntu-15.10
driver:
image: ubuntu:15.10
pid_one_command: /bin/systemd
- name: centos-6.6
driver:
image: centos:6.6
- name: centos-6.7
driver:
image: centos:6.7
intermediate_instructions:
- RUN yum install -y initscripts
- name: centos-7
driver:
image: centos:7
pid_one_command: /usr/lib/systemd/systemd
- name: oracle-6.6
driver:
image: oraclelinux:6.6
- name: oracle-6.7
driver:
image: oraclelinux:6.7
- name: oracle-7.1
driver:
image: oraclelinux:7.1
pid_one_command: /usr/lib/systemd/systemd
- name: debian-7
driver:
image: debian:7
intermediate_instructions:
- RUN /usr/bin/apt-get update
- RUN /usr/bin/apt-get install -y procps
- name: debian-8
driver:
image: debian:8
pid_one_command: /bin/systemd
- name: centos-7.2

suites:
- name: default # compliance reporting via chef-server
run_list:
- recipe[audit::default]
attributes:
audit:
profiles: &profiles
base/ssh: true
base/linux: true
- name: compliance # compliance direct reporting
run_list:
- recipe[audit::default]
attributes:
audit:
server: <%= ENV['COMPLIANCE_API'] %>
token: <%= ENV['COMPLIANCE_ACCESSTOKEN'] %>
owner: admin
profiles: *profiles
# - name: default # compliance reporting via chef-server
# run_list:
# - recipe[audit::default]
# attributes:
# audit:
# server: <%= ENV['CHEF_SERVER_URL'] %>
# profiles: &profiles
# base/ssh: true
# base/linux: true
- name: default # compliance direct reporting
run_list:
- recipe[audit::default]
attributes:
audit:
server: <%= ENV['COMPLIANCE_API'] %>
token: <%= ENV['COMPLIANCE_ACCESSTOKEN'] %>
owner: admin
profiles: &profiles
base/ssh: true
base/linux: true
# profiles: *profiles
21 changes: 7 additions & 14 deletions attributes/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,23 @@
# limitations under the License.
#

# Compliance server if you want to report directly to it
default['audit']['server'] = nil
# Compliance token for accessing compliance, used only when reporting directly to compliance
default['audit']['token'] = nil
# Compliance refresh token, used when reporting directly to the compliance server
default['audit']['refresh_token'] = nil
# The owner of the scanned node when reporting directly to the compliance server
default['audit']['owner'] = nil
default['audit']['quiet'] = nil
# Whether to silence report results
default['audit']['quiet'] = false
# The profiles to scan
default['audit']['profiles'] = {}

# raise exception if Compliance API endpoint is unreachable
# while fetching profiles or posting report
default['audit']['raise_if_unreachable'] = true

# fail converge if downloaded profile is not present
default['audit']['fail_if_not_present'] = false

# fail converge after posting report if any audits have failed
default['audit']['fail_if_any_audits_failed'] = false

# inspec gem version to install(e.g. '0.22.1') or 'latest'
default['audit']['inspec_version'] = '0.22.1'

# by default run audit every time
default['audit']['interval']['enabled'] = false
# by default run compliance once a day
default['audit']['interval']['time'] = 1440

# quiet mode, on by default because this is testing, resources aren't converged in the normal chef sense
default['audit']['quiet'] = true
131 changes: 131 additions & 0 deletions files/default/audit_report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# encoding: utf-8

require 'chef/handler'

class Chef
class Handler
# Creates a compliance audit report
class AuditReport < ::Chef::Handler
def report
return if audit_scheduler.should_skip_audit?

ensure_inspec_installed
report_results = initialize_report_results
# fetch first so dependencies can be used
compliance_profiles.each do |profile|
profile.fetch
end
# add results of executed profiles to final report results
compliance_profiles.each do |profile|
report_results[:reports][profile.name] = profile.execute
end
report_results[:profile] = profile_owners_by_profile
server_connection.report report_results
audit_scheduler.record_completed_run
end

def audit_scheduler
@audit_scheduler ||= ::Audit::AuditScheduler.new(node['audit']['interval']['enabled'], node['audit']['interval']['time'])
end

def initialize_report_results
value = {
node: node['name'],
os: {
release: node['platform_version'],
family: node['platform'],
},
environment: node['environment'],
reports: {},
profile: {},
}
Chef::Log.debug "Initialized report results on node #{value['node']} and environment #{value['environment']}"
value
end

def inspec_version
node['audit']['inspec_version']
end

def ensure_inspec_installed
Chef::Log.debug "Ensuring inspec #{inspec_version} is installed"
require 'inspec'
# load the supermarket plugin
require 'bundles/inspec-supermarket/api'
require 'bundles/inspec-supermarket/target'

# load the compliance api plugin
require 'bundles/inspec-compliance/api'

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).'
else
Chef::Log.warn "Using inspec version: (#{Inspec::VERSION})"
end
end

def compliance_profiles
@compliance_profiles ||= initialize_compliance_profiles
end

def profile_owners_by_profile
owners_by_profile = {}
compliance_profiles.each do |profile|
owners_by_profile[profile.name] = profile.owner
end
owners_by_profile
end

def initialize_compliance_profiles
profiles = []
node['audit']['profiles'].each do |owner_profile, value|
case value
when Hash
enabled = !value['disabled']
path = value['source']
else
enabled = value
end
fail "Invalid profile name '#{owner_profile}'. "\
"Must contain /, e.g. 'john/ssh'" if owner_profile !~ %r{\/}
owner, name = owner_profile.split('/').last(2)
platform_windows = node['platform'] == 'windows'
quiet = node['audit']['quiet']
profiles.push ::Audit::ComplianceProfile.new(owner, name, enabled, path, server_connection, platform_windows, quiet)
end
profiles
end

def server_connection
@server_connection ||= initialize_server_connection
end

def initialize_server_connection
token = node['audit']['token']
server = node['audit']['server']
unless token.nil?
Chef::Log.info "Connecting to compliance server #{server} with provided token"
org = node['audit']['owner']
org = parse_org(server) if org.nil?
::Audit::ComplianceServerConnection.new(server, org, token, node['audit']['refresh_token'])
else
server = chef_server_url if server.nil?
org = parse_org(server)
base_server = server
base_server.slice!("/organizations/#{org}")
Chef::Log.info "Connecting to chef server #{server}"
::Audit::ChefServerConnection.new(base_server, org)
end
end

def chef_server_url
Chef::Config[:chef_server_url]
end

def parse_org(url)
url.split('/').last
end
end
end
end
52 changes: 52 additions & 0 deletions libraries/audit_scheduler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# encoding: utf-8
module Audit
class AuditScheduler
def initialize(interval_enabled, interval_time)
@interval_enabled = interval_enabled
@interval_time = interval_time
end

def should_skip_audit?
if should_run_on_schedule?
if scheduled_to_run?
Chef::Log.warn 'Running the compliance audit of this node since it is set to run '\
"every #{schedule_interval} seconds and last ran on #{last_run_time}."
else
Chef::Log.warn 'Skipping The compliance audit of this node since it is set to run '\
"every #{schedule_interval} seconds and is scheduled to run during a chef-client "\
"run after #{next_scheduled_time}"
return true
end
else
Chef::Log.warn 'Running the compliance audit of this node every time chef-client runs.'\
'To change this, configure it to run on an interval'
end
false
end

def record_completed_run
FileUtils.touch schedule_file
end

def should_run_on_schedule?
@interval_enabled
end

def schedule_interval
@interval_time
end

def scheduled_to_run?
schedule_file_modified = ::File.mtime(schedule_file)
seconds_since_last_run = Time.now - schedule_file_modified
scheduled = schedule_interval < seconds_since_last_run
Chef::Log.debug "Run scheduled is #{scheduled} where interval is set to #{schedule_interval} "\
"and it has been #{seconds_since_last_run} seconds since #{schedule_file} last ran "\
"at #{schedule_file_modified}"
end

def schedule_file
::File.join(Chef::Config[:file_cache_path], 'compliance', 'schedule')
end
end
end
48 changes: 48 additions & 0 deletions libraries/chef_server_connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# encoding: utf-8

require 'uri'

module Audit
class ChefServerConnection
attr_accessor :server, :org
def initialize(server, org)
@server = server
@org = org
puts "Creating chef server connection for server #{@server} and org #{@org}"
end

def fetch(profile)
reqpath ="organizations/#{org}/owners/#{profile.owner}/compliance/#{profile.name}/tar"
url = construct_url(server + '/compliance/', reqpath)
Chef::Log.info "Load profile from: #{url}"
Chef::Config[:verify_api_cert] = false # FIXME
Chef::Config[:ssl_verify_mode] = :verify_none # FIXME
rest = Chef::ServerAPI.new(url, Chef::Config)
tf = ::Audit::HttpProcessor.with_http_rescue do
rest.binmode_streaming_request(url)
end
tf
end

def construct_url(server, path)
path.sub!(%r{^/}, '') # sanitize input
puts 'Using server ' + server
server = URI(server)
server.path = server.path + path if path
server
end

def report(report_results)
Chef::Config[:verify_api_cert] = false
Chef::Config[:ssl_verify_mode] = :verify_none

url = construct_url(server + '/compliance/', ::File.join('organizations', org, 'inspec'))
Chef::Log.info "Report to: #{url}"

rest = Chef::ServerAPI.new(url, Chef::Config)
::Audit::HttpProcessor.with_http_rescue do
rest.post(url, report_results)
end
end
end
end
Loading