chef 16.7.61 → 16.8.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -2
- data/README.md +1 -1
- data/chef.gemspec +2 -1
- data/lib/chef/application/base.rb +1 -1
- data/lib/chef/client.rb +3 -0
- data/lib/chef/compliance/default_attributes.rb +89 -0
- data/lib/chef/compliance/fetcher/automate.rb +69 -0
- data/lib/chef/compliance/fetcher/chef_server.rb +134 -0
- data/lib/chef/compliance/reporter/automate.rb +202 -0
- data/lib/chef/compliance/reporter/chef_server_automate.rb +92 -0
- data/lib/chef/compliance/reporter/compliance_enforcer.rb +20 -0
- data/lib/chef/compliance/reporter/json_file.rb +19 -0
- data/lib/chef/compliance/runner.rb +250 -0
- data/lib/chef/cookbook_manifest.rb +1 -0
- data/lib/chef/encrypted_data_bag_item/assertions.rb +1 -1
- data/lib/chef/exceptions.rb +4 -0
- data/lib/chef/http/ssl_policies.rb +6 -0
- data/lib/chef/knife/bootstrap/train_connector.rb +1 -1
- data/lib/chef/knife/core/ui.rb +4 -1
- data/lib/chef/knife/ssh.rb +1 -1
- data/lib/chef/mixin/powershell_exec.rb +3 -1
- data/lib/chef/platform/query_helpers.rb +4 -4
- data/lib/chef/powershell.rb +2 -0
- data/lib/chef/provider/dsc_resource.rb +12 -24
- data/lib/chef/provider/dsc_script.rb +16 -20
- data/lib/chef/provider/git.rb +5 -5
- data/lib/chef/resource/chef_client_config.rb +1 -1
- data/lib/chef/resource/dsc_script.rb +8 -1
- data/lib/chef/resource/hostname.rb +3 -3
- data/lib/chef/resource/template.rb +2 -2
- data/lib/chef/resource/windows_certificate.rb +7 -1
- data/lib/chef/resource_collection/resource_set.rb +1 -1
- data/lib/chef/util/dsc/configuration_generator.rb +52 -11
- data/lib/chef/util/dsc/lcm_output_parser.rb +3 -4
- data/lib/chef/util/dsc/local_configuration_manager.rb +17 -14
- data/lib/chef/util/dsc/resource_store.rb +5 -11
- data/lib/chef/version.rb +1 -1
- data/lib/chef/win32/api/file.rb +4 -0
- data/spec/functional/resource/dsc_script_spec.rb +3 -6
- data/spec/integration/client/client_spec.rb +2 -1
- data/spec/integration/compliance/compliance_spec.rb +81 -0
- data/spec/integration/recipes/recipe_dsl_spec.rb +1 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/client_spec.rb +1 -0
- data/spec/unit/compliance/fetcher/automate_spec.rb +134 -0
- data/spec/unit/compliance/fetcher/chef_server_spec.rb +93 -0
- data/spec/unit/compliance/reporter/automate_spec.rb +427 -0
- data/spec/unit/compliance/reporter/chef_server_automate_spec.rb +177 -0
- data/spec/unit/compliance/reporter/compliance_enforcer_spec.rb +48 -0
- data/spec/unit/compliance/runner_spec.rb +113 -0
- data/spec/unit/http/ssl_policies_spec.rb +11 -0
- data/spec/unit/knife/core/node_editor_spec.rb +1 -1
- data/spec/unit/mixin/powershell_exec_spec.rb +1 -1
- data/spec/unit/platform/query_helpers_spec.rb +11 -12
- data/spec/unit/provider/dsc_resource_spec.rb +10 -27
- data/spec/unit/provider/dsc_script_spec.rb +1 -1
- data/spec/unit/provider/mount/windows_spec.rb +1 -0
- data/spec/unit/provider/systemd_unit_spec.rb +1 -1
- data/spec/unit/resource/windows_certificate_spec.rb +12 -0
- data/spec/unit/util/dsc/configuration_generator_spec.rb +79 -0
- data/spec/unit/util/dsc/local_configuration_manager_spec.rb +27 -35
- metadata +37 -12
- data/lib/chef/util/powershell/cmdlet.rb +0 -169
- data/lib/chef/util/powershell/cmdlet_result.rb +0 -61
- data/spec/functional/util/powershell/cmdlet_spec.rb +0 -111
- data/spec/unit/util/powershell/cmdlet_spec.rb +0 -106
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c13a95870faf2ddc93377295ea1821d3ee962c422af54147a9a5852266ef9abc
|
4
|
+
data.tar.gz: 06d9ed38997dd63cef3a9d54cf7ffb064c5ca13b668cbea8ee42173ba757d880
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed8ed48612596fdd94dfab56edda9b66ba3adff6bcca4475bf4d4a9b5ab9dc51f59b3db9d54aa6f468ab33aa4a8f2821a5ab9942379c0d70f641a04f8bfb48d0
|
7
|
+
data.tar.gz: 8f1ebbbdbfe60ed00c66edab06a41821f5d8e9f62ab74d4c322b187b75e4f894e0f59cdf90c61b034a8496ece30e0a6eb71c3c91336a40e966fb6f9001e26d9a
|
data/Gemfile
CHANGED
@@ -27,8 +27,7 @@ gem "chef-telemetry", ">=1.0.8" # 1.0.8 removes the http dep
|
|
27
27
|
group(:omnibus_package) do
|
28
28
|
gem "appbundler"
|
29
29
|
gem "rb-readline"
|
30
|
-
gem "inspec-core", "~> 4.
|
31
|
-
gem "inspec-core-bin", "~> 4.18" # need to provide the binaries for inspec
|
30
|
+
gem "inspec-core-bin", "~> 4.24" # need to provide the binaries for inspec
|
32
31
|
gem "chef-vault"
|
33
32
|
end
|
34
33
|
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
[![Code Climate](https://codeclimate.com/github/chef/chef.svg)](https://codeclimate.com/github/chef/chef)
|
3
3
|
[![Build Status](https://badge.buildkite.com/c82093430ceec7d27af05febb9dcafe3aa331fff9d74c0ab9d.svg?branch=master)](https://buildkite.com/chef-oss/chef-chef-master-verify)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/chef.svg)](https://badge.fury.io/rb/chef)
|
5
|
-
[![](https://img.shields.io/badge/Release%20Policy-Cadence%20Release-brightgreen.svg)](https://github.com/chef/chef/blob/
|
5
|
+
[![](https://img.shields.io/badge/Release%20Policy-Cadence%20Release-brightgreen.svg)](https://github.com/chef/chef/blob/master/docs/dev/design_documents/client_release_cadence.md)
|
6
6
|
|
7
7
|
**Umbrella Project**: [Chef Infra](https://github.com/chef/chef-oss-practices/blob/master/projects/chef-infra.md)
|
8
8
|
|
data/chef.gemspec
CHANGED
@@ -27,10 +27,11 @@ Gem::Specification.new do |s|
|
|
27
27
|
s.add_dependency "mixlib-shellout", ">= 3.1.1", "< 4.0"
|
28
28
|
s.add_dependency "mixlib-archive", ">= 0.4", "< 2.0"
|
29
29
|
s.add_dependency "ohai", "~> 16.0"
|
30
|
+
s.add_dependency "inspec-core", "~> 4.23"
|
30
31
|
|
31
32
|
s.add_dependency "ffi", ">= 1.9.25"
|
32
33
|
s.add_dependency "ffi-yajl", "~> 2.2"
|
33
|
-
s.add_dependency "net-ssh", ">=
|
34
|
+
s.add_dependency "net-ssh", ">= 5.1", "< 7"
|
34
35
|
s.add_dependency "net-ssh-multi", "~> 1.2", ">= 1.2.1"
|
35
36
|
s.add_dependency "net-sftp", ">= 2.1.2", "< 4.0"
|
36
37
|
s.add_dependency "ed25519", "~> 1.2" # ed25519 ssh key support
|
@@ -366,7 +366,7 @@ class Chef::Application::Base < Chef::Application
|
|
366
366
|
Chef::Log.trace("Download recipes tarball from #{url} to #{path}")
|
367
367
|
if File.exist?(url)
|
368
368
|
FileUtils.cp(url, path)
|
369
|
-
elsif URI.
|
369
|
+
elsif URI::DEFAULT_PARSER.make_regexp.match?(url)
|
370
370
|
File.open(path, "wb") do |f|
|
371
371
|
open(url) do |r|
|
372
372
|
f.write(r.read)
|
data/lib/chef/client.rb
CHANGED
@@ -57,6 +57,8 @@ require "ohai" unless defined?(Ohai::System)
|
|
57
57
|
require "rbconfig" unless defined?(RbConfig)
|
58
58
|
require "forwardable" unless defined?(Forwardable)
|
59
59
|
|
60
|
+
require_relative "compliance/runner"
|
61
|
+
|
60
62
|
class Chef
|
61
63
|
# == Chef::Client
|
62
64
|
# The main object in a Chef run. Preps a Chef::Node and Chef::RunContext,
|
@@ -235,6 +237,7 @@ class Chef
|
|
235
237
|
|
236
238
|
events.register(Chef::DataCollector::Reporter.new(events))
|
237
239
|
events.register(Chef::ActionCollection.new(events))
|
240
|
+
events.register(Chef::Compliance::Runner.new)
|
238
241
|
|
239
242
|
run_status.run_id = request_id = Chef::RequestID.instance.request_id
|
240
243
|
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Author:: Stephan Renatus <srenatus@chef.io>
|
2
|
+
# Copyright:: (c) 2016-2019, Chef Software Inc. <legal@chef.io>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require "chef/node/attribute_collections" # for VividMash
|
17
|
+
require "chef/util/path_helper"
|
18
|
+
|
19
|
+
class Chef
|
20
|
+
module Compliance
|
21
|
+
DEFAULT_ATTRIBUTES = Chef::Node::VividMash.new(
|
22
|
+
# If enabled, a cache is built for all backend calls. This should only be
|
23
|
+
# disabled if you are expecting unique results from the same backend call.
|
24
|
+
# Under the covers, this controls :command and :file caching on Chef InSpec's
|
25
|
+
# Train connection.
|
26
|
+
"inspec_backend_cache" => true,
|
27
|
+
|
28
|
+
# Controls what is done with the resulting report after the Chef InSpec run.
|
29
|
+
# Accepts a single string value or an array of multiple values.
|
30
|
+
# Accepted values: 'chef-server-automate', 'chef-automate', 'json-file', 'audit-enforcer'
|
31
|
+
"reporter" => "json-file",
|
32
|
+
|
33
|
+
# Controls if Chef InSpec profiles should be fetched from Chef Automate or Chef Infra Server
|
34
|
+
# in addition to the default fetch locations provided by Chef Inspec.
|
35
|
+
# Accepted values: nil, 'chef-server', 'chef-automate'
|
36
|
+
"fetcher" => nil,
|
37
|
+
|
38
|
+
# Allow for connections to HTTPS endpoints using self-signed ssl certificates.
|
39
|
+
"insecure" => nil,
|
40
|
+
|
41
|
+
# Controls verbosity of Chef InSpec runner.
|
42
|
+
"quiet" => true,
|
43
|
+
|
44
|
+
# Chef Inspec Compliance profiles to be used for scan of node.
|
45
|
+
# See README.md for details
|
46
|
+
"profiles" => {},
|
47
|
+
|
48
|
+
# Extra inputs passed to Chef InSpec to allow finer-grained control over behavior.
|
49
|
+
# These are mapped to Chef InSpec's inputs, but are named attributes here for legacy reasons.
|
50
|
+
# See Chef Inspec's documentation for more information: https://docs.chef.io/inspec/inputs/
|
51
|
+
"attributes" => {},
|
52
|
+
|
53
|
+
# A string path or an array of paths to Chef InSpec waiver files.
|
54
|
+
# See Chef Inspec's documentation for more information: https://docs.chef.io/inspec/waivers/
|
55
|
+
"waiver_file" => nil,
|
56
|
+
|
57
|
+
"json_file" => {
|
58
|
+
# The location on disk that Chef InSpec's json reports are saved to when using the
|
59
|
+
# 'json-file' reporter. Defaults to:
|
60
|
+
# <chef_cache_path>/compliance_reports/compliance-<timestamp>.json
|
61
|
+
"location" => Chef::Util::PathHelper.join(
|
62
|
+
Chef::Config[:cache_path],
|
63
|
+
"compliance_reports",
|
64
|
+
Time.now.utc.strftime("compliance-%Y%m%d%H%M%S.json")
|
65
|
+
),
|
66
|
+
},
|
67
|
+
|
68
|
+
# Control results that have a `run_time` below this limit will
|
69
|
+
# be stripped of the `start_time` and `run_time` fields to
|
70
|
+
# reduce the size of the reports being sent to Chef Automate.
|
71
|
+
"run_time_limit" => 1.0,
|
72
|
+
|
73
|
+
# A control result message that exceeds this character limit will be truncated.
|
74
|
+
# This helps keep reports to a reasonable size. On rare occasions, we've seen messages exceeding 9 MB in size,
|
75
|
+
# causing the report to not be ingested in the backend because of the 4 MB report size rpc limitation.
|
76
|
+
# Chef InSpec will append this text at the end of any truncated messages: `[Truncated to 10000 characters]`
|
77
|
+
"result_message_limit" => 10000,
|
78
|
+
|
79
|
+
# When a Chef InSpec resource throws an exception, results will contain a short error message and a
|
80
|
+
# detailed ruby stacktrace of the error. This attribute instructs Chef InSpec not to include the detailed stacktrace in order
|
81
|
+
# to keep the overall report to a manageable size.
|
82
|
+
"result_include_backtrace" => false,
|
83
|
+
|
84
|
+
# The array of results per control will be truncated at this limit to avoid large reports that cannot be
|
85
|
+
# processed by Chef Automate. A summary of removed results will be sent with each impacted control.
|
86
|
+
"control_results_limit" => 50
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "uri" unless defined?(URI)
|
2
|
+
require "plugins/inspec-compliance/lib/inspec-compliance"
|
3
|
+
|
4
|
+
class Chef
|
5
|
+
module Compliance
|
6
|
+
module Fetcher
|
7
|
+
class Automate < ::InspecPlugins::Compliance::Fetcher
|
8
|
+
name "chef-automate"
|
9
|
+
|
10
|
+
# it positions itself before `compliance` fetcher
|
11
|
+
# only load it, if you want to use audit cookbook in Chef Solo with Chef Automate
|
12
|
+
priority 502
|
13
|
+
|
14
|
+
CONFIG = {
|
15
|
+
"insecure" => true,
|
16
|
+
"token" => nil,
|
17
|
+
"server_type" => "automate",
|
18
|
+
"automate" => {
|
19
|
+
"ent" => "default",
|
20
|
+
"token_type" => "dctoken",
|
21
|
+
},
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
def self.resolve(target)
|
25
|
+
uri = get_target_uri(target)
|
26
|
+
return nil if uri.nil?
|
27
|
+
|
28
|
+
config = CONFIG.dup
|
29
|
+
|
30
|
+
# we have detailed information available in our lockfile, no need to ask the server
|
31
|
+
if target.respond_to?(:key?) && target.key?(:url)
|
32
|
+
profile_fetch_url = target[:url]
|
33
|
+
else
|
34
|
+
# verifies that the target e.g base/ssh exists
|
35
|
+
base_path = "/compliance/profiles/#{uri.host}#{uri.path}"
|
36
|
+
|
37
|
+
profile_path = if target.respond_to?(:key?) && target.key?(:version)
|
38
|
+
"#{base_path}/version/#{target[:version]}/tar"
|
39
|
+
else
|
40
|
+
"#{base_path}/tar"
|
41
|
+
end
|
42
|
+
|
43
|
+
url = URI(Chef::Config[:data_collector][:server_url])
|
44
|
+
url.path = profile_path
|
45
|
+
profile_fetch_url = url.to_s
|
46
|
+
|
47
|
+
config["token"] = Chef::Config[:data_collector][:token]
|
48
|
+
|
49
|
+
if config["token"].nil?
|
50
|
+
raise Inspec::FetcherFailure,
|
51
|
+
"No data-collector token set, which is required by the chef-automate fetcher. " \
|
52
|
+
"Set the `data_collector.token` configuration parameter in your client.rb " \
|
53
|
+
'or use the "chef-server-automate" reporter which does not require any ' \
|
54
|
+
"data-collector settings and uses #{ChefUtils::Dist::Server::PRODUCT} to fetch profiles."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
new(profile_fetch_url, config)
|
59
|
+
rescue URI::Error => _e
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
"#{ChefUtils::Dist::Automate::PRODUCT} for #{ChefUtils::Dist::Solo::PRODUCT} Fetcher"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require "uri" unless defined?(URI)
|
2
|
+
require "plugins/inspec-compliance/lib/inspec-compliance"
|
3
|
+
|
4
|
+
# This class implements an InSpec fetcher for Chef Server. The implementation
|
5
|
+
# is based on the Chef Compliance fetcher and only adapts the calls to redirect
|
6
|
+
# the requests via Chef Server.
|
7
|
+
#
|
8
|
+
# This implementation depends on chef-client runtime, therefore it is only executable
|
9
|
+
# inside of a chef-client run
|
10
|
+
|
11
|
+
class Chef
|
12
|
+
module Compliance
|
13
|
+
module Fetcher
|
14
|
+
class ChefServer < ::InspecPlugins::Compliance::Fetcher
|
15
|
+
name "chef-server"
|
16
|
+
|
17
|
+
# it positions itself before `compliance` fetcher
|
18
|
+
# only load it, if the Chef Server is integrated with Chef Compliance
|
19
|
+
priority 501
|
20
|
+
|
21
|
+
CONFIG = { "insecure" => true }.freeze
|
22
|
+
|
23
|
+
# Accepts URLs to compliance profiles in one of two forms:
|
24
|
+
# * a String URL with a compliance scheme, like "compliance://namespace/profile_name"
|
25
|
+
# * a Hash with a key of `compliance` and a value like "compliance/profile_name" and optionally a `version` key with a String value
|
26
|
+
def self.resolve(target)
|
27
|
+
profile_uri = get_target_uri(target)
|
28
|
+
return nil if profile_uri.nil?
|
29
|
+
|
30
|
+
organization = Chef::Config[:chef_server_url].split("/").last
|
31
|
+
owner = profile_uri.user ? "#{profile_uri.user}@#{profile_uri.host}" : profile_uri.host
|
32
|
+
version = target[:version] if target.respond_to?(:key?)
|
33
|
+
|
34
|
+
path_parts = [""]
|
35
|
+
path_parts << "compliance" if chef_server_reporter? || chef_server_fetcher?
|
36
|
+
path_parts << "organizations"
|
37
|
+
path_parts << organization
|
38
|
+
path_parts << "owners"
|
39
|
+
path_parts << owner
|
40
|
+
path_parts << "compliance"
|
41
|
+
path_parts << profile_uri.path
|
42
|
+
path_parts << "version/#{version}" if version
|
43
|
+
path_parts << "tar"
|
44
|
+
|
45
|
+
target_url = URI(Chef::Config[:chef_server_url])
|
46
|
+
target_url.path = File.join(path_parts)
|
47
|
+
Chef::Log.info("Fetching profile from: #{target_url}")
|
48
|
+
|
49
|
+
new(target_url, CONFIG)
|
50
|
+
rescue URI::Error => _e
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# We want to save compliance: in the lockfile rather than url: to
|
56
|
+
# make sure we go back through the ComplianceAPI handling.
|
57
|
+
#
|
58
|
+
def resolved_source
|
59
|
+
{ compliance: chef_server_url }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Downloads archive to temporary file using a Chef::ServerAPI
|
63
|
+
# client so that Chef Server's header-based authentication can be
|
64
|
+
# used.
|
65
|
+
def download_archive_to_temp
|
66
|
+
return @temp_archive_path unless @temp_archive_path.nil?
|
67
|
+
|
68
|
+
rest = Chef::ServerAPI.new(@target, Chef::Config.merge(ssl_verify_mode: :verify_none))
|
69
|
+
archive = with_http_rescue do
|
70
|
+
rest.streaming_request(@target)
|
71
|
+
end
|
72
|
+
@archive_type = ".tar.gz"
|
73
|
+
|
74
|
+
if archive.nil?
|
75
|
+
path = @target.respond_to?(:path) ? @target.path : path
|
76
|
+
raise Inspec::FetcherFailure, "Unable to find requested profile on path: '#{path}' on the #{ChefUtils::Dist::Automate::PRODUCT} system."
|
77
|
+
end
|
78
|
+
|
79
|
+
Inspec::Log.debug("Archive stored at temporary location: #{archive.path}")
|
80
|
+
@temp_archive_path = archive.path
|
81
|
+
end
|
82
|
+
|
83
|
+
def with_http_rescue
|
84
|
+
response = yield
|
85
|
+
if response.respond_to?(:code)
|
86
|
+
# handle non 200 error codes, they are not raised as Net::HTTPClientException
|
87
|
+
handle_http_error_code(response.code) if response.code.to_i >= 300
|
88
|
+
end
|
89
|
+
response
|
90
|
+
rescue Net::HTTPClientException => e
|
91
|
+
Chef::Log.error e
|
92
|
+
handle_http_error_code(e.response.code)
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_http_error_code(code)
|
96
|
+
case code
|
97
|
+
when /401|403/
|
98
|
+
Chef::Log.error "Auth issue: see audit cookbook TROUBLESHOOTING.md"
|
99
|
+
when /404/
|
100
|
+
Chef::Log.error "Object does not exist on remote server."
|
101
|
+
when /413/
|
102
|
+
Chef::Log.error "You most likely hit the erchef request size in #{ChefUtils::Dist::Server::PRODUCT} that defaults to ~2MB. To increase this limit see audit cookbook TROUBLESHOOTING.md OR https://docs.chef.io/config_rb_server.html"
|
103
|
+
when /429/
|
104
|
+
Chef::Log.error "This error typically means the data sent was larger than #{ChefUtils::Dist::Automate::PRODUCT}'s limit (4 MB). Run InSpec locally to identify any controls producing large diffs."
|
105
|
+
end
|
106
|
+
msg = "Received HTTP error #{code}"
|
107
|
+
Chef::Log.error msg
|
108
|
+
raise Inspec::FetcherFailure, msg
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_s
|
112
|
+
"#{ChefUtils::Dist::Server::PRODUCT}/Compliance Profile Loader"
|
113
|
+
end
|
114
|
+
|
115
|
+
CHEF_SERVER_REPORTERS = %w{chef-server chef-server-compliance chef-server-visibility chef-server-automate}.freeze
|
116
|
+
def self.chef_server_reporter?
|
117
|
+
(Array(Chef.node.attributes["audit"]["reporter"]) & CHEF_SERVER_REPORTERS).any?
|
118
|
+
end
|
119
|
+
|
120
|
+
CHEF_SERVER_FETCHERS = %w{chef-server chef-server-compliance chef-server-visibility chef-server-automate}.freeze
|
121
|
+
def self.chef_server_fetcher?
|
122
|
+
CHEF_SERVER_FETCHERS.include?(Chef.node.attributes["audit"]["fetcher"])
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def chef_server_url
|
128
|
+
m = %r{^#{@config['server']}/owners/(?<owner>[^/]+)/compliance/(?<id>[^/]+)/tar$}.match(@target)
|
129
|
+
"#{m[:owner]}/#{m[:id]}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
class Chef
|
2
|
+
module Compliance
|
3
|
+
module Reporter
|
4
|
+
#
|
5
|
+
# Used to send inspec reports to Chef Automate via the data_collector service
|
6
|
+
#
|
7
|
+
class Automate
|
8
|
+
def initialize(opts)
|
9
|
+
@entity_uuid = opts[:entity_uuid]
|
10
|
+
@run_id = opts[:run_id]
|
11
|
+
@node_name = opts[:node_info][:node]
|
12
|
+
@environment = opts[:node_info][:environment]
|
13
|
+
@roles = opts[:node_info][:roles]
|
14
|
+
@recipes = opts[:node_info][:recipes]
|
15
|
+
@insecure = opts[:insecure]
|
16
|
+
@chef_tags = opts[:node_info][:chef_tags]
|
17
|
+
@policy_group = opts[:node_info][:policy_group]
|
18
|
+
@policy_name = opts[:node_info][:policy_name]
|
19
|
+
@source_fqdn = opts[:node_info][:source_fqdn]
|
20
|
+
@organization_name = opts[:node_info][:organization_name]
|
21
|
+
@ipaddress = opts[:node_info][:ipaddress]
|
22
|
+
@fqdn = opts[:node_info][:fqdn]
|
23
|
+
@run_time_limit = opts[:run_time_limit]
|
24
|
+
@control_results_limit = opts[:control_results_limit]
|
25
|
+
@timestamp = opts.fetch(:timestamp) { Time.now }
|
26
|
+
|
27
|
+
@url = Chef::Config[:data_collector][:server_url]
|
28
|
+
@token = Chef::Config[:data_collector][:token]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Method used in order to send the inspec report to the data_collector server
|
32
|
+
def send_report(report)
|
33
|
+
unless @entity_uuid && @run_id
|
34
|
+
Chef::Log.error "entity_uuid(#{@entity_uuid}) or run_id(#{@run_id}) can't be nil, not sending report to #{ChefUtils::Dist::Automate::PRODUCT}"
|
35
|
+
return false
|
36
|
+
end
|
37
|
+
|
38
|
+
unless @url && @token
|
39
|
+
Chef::Log.warn "data_collector.token and data_collector.server_url must be defined in client.rb!"
|
40
|
+
Chef::Log.warn "Further information: https://github.com/chef-cookbooks/audit#direct-reporting-to-chef-automate"
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
|
44
|
+
headers = {
|
45
|
+
"Content-Type" => "application/json",
|
46
|
+
"x-data-collector-auth" => "version=1.0",
|
47
|
+
"x-data-collector-token" => @token,
|
48
|
+
}
|
49
|
+
|
50
|
+
all_report_shas = report[:profiles].map { |p| p[:sha256] }
|
51
|
+
missing_report_shas = missing_automate_profiles(headers, all_report_shas)
|
52
|
+
|
53
|
+
full_report = truncate_controls_results(enriched_report(report), @control_results_limit)
|
54
|
+
full_report = strip_profiles_meta(full_report, missing_report_shas, @run_time_limit)
|
55
|
+
json_report = Chef::JSONCompat.to_json(full_report, validate_utf8: false)
|
56
|
+
|
57
|
+
# Automate GRPC currently has a message limit of ~4MB
|
58
|
+
# https://github.com/chef/automate/issues/1417#issuecomment-541908157
|
59
|
+
if json_report.bytesize > 4 * 1024 * 1024
|
60
|
+
Chef::Log.warn "Generated report size is #{(json_report.bytesize / (1024 * 1024.0)).round(2)} MB. #{ChefUtils::Dist::Automate::PRODUCT} has an internal 4MB limit that is not currently configurable."
|
61
|
+
end
|
62
|
+
|
63
|
+
unless json_report
|
64
|
+
Chef::Log.warn "Something went wrong, report can't be nil"
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
|
68
|
+
begin
|
69
|
+
Chef::Log.info "Report to #{ChefUtils::Dist::Automate::PRODUCT}: #{@url}"
|
70
|
+
Chef::Log.debug "Compliance Report: #{json_report}"
|
71
|
+
http_client.post(nil, json_report, headers)
|
72
|
+
true
|
73
|
+
rescue => e
|
74
|
+
Chef::Log.error "send_report: POST to #{@url} returned: #{e.message}"
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def http_client(url = @url)
|
80
|
+
if @insecure
|
81
|
+
Chef::HTTP.new(url, ssl_verify_mode: :verify_none)
|
82
|
+
else
|
83
|
+
Chef::HTTP.new(url)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def enriched_report(final_report)
|
88
|
+
final_report[:profiles].compact!
|
89
|
+
|
90
|
+
# Label this content as an inspec_report
|
91
|
+
final_report[:type] = "inspec_report"
|
92
|
+
|
93
|
+
final_report[:node_name] = @node_name
|
94
|
+
final_report[:end_time] = @timestamp.utc.strftime("%FT%TZ")
|
95
|
+
final_report[:node_uuid] = @entity_uuid
|
96
|
+
final_report[:environment] = @environment
|
97
|
+
final_report[:roles] = @roles
|
98
|
+
final_report[:recipes] = @recipes
|
99
|
+
final_report[:report_uuid] = @run_id
|
100
|
+
final_report[:source_fqdn] = @source_fqdn
|
101
|
+
final_report[:organization_name] = @organization_name
|
102
|
+
final_report[:policy_group] = @policy_group
|
103
|
+
final_report[:policy_name] = @policy_name
|
104
|
+
final_report[:chef_tags] = @chef_tags
|
105
|
+
final_report[:ipaddress] = @ipaddress
|
106
|
+
final_report[:fqdn] = @fqdn
|
107
|
+
|
108
|
+
final_report
|
109
|
+
end
|
110
|
+
|
111
|
+
CONTROL_RESULT_SORT_ORDER = %w{ failed skipped passed }.freeze
|
112
|
+
|
113
|
+
# Truncates the number of results per control in the report when they exceed max_results.
|
114
|
+
# The truncation prioritizes failed and skipped results over passed ones.
|
115
|
+
# Controls where results have been truncated will get a new object 'removed_results_counts'
|
116
|
+
# with the status counts of the truncated results
|
117
|
+
def truncate_controls_results(report, max_results)
|
118
|
+
return report unless max_results.is_a?(Integer) && max_results > 0
|
119
|
+
|
120
|
+
report.fetch(:profiles, []).each do |profile|
|
121
|
+
profile.fetch(:controls, []).each do |control|
|
122
|
+
# Only bother with truncation if the number of results exceed max_results
|
123
|
+
next unless control[:results].length > max_results
|
124
|
+
|
125
|
+
res = control[:results]
|
126
|
+
res.sort_by! { |r| CONTROL_RESULT_SORT_ORDER.index(r[:status]) }
|
127
|
+
|
128
|
+
# Count the results that will be truncated
|
129
|
+
truncated = { failed: 0, skipped: 0, passed: 0 }
|
130
|
+
(max_results..res.length - 1).each do |i|
|
131
|
+
case res[i][:status]
|
132
|
+
when "failed"
|
133
|
+
truncated[:failed] += 1
|
134
|
+
when "skipped"
|
135
|
+
truncated[:skipped] += 1
|
136
|
+
when "passed"
|
137
|
+
truncated[:passed] += 1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
# Truncate the results array now
|
141
|
+
control[:results] = res[0..max_results - 1]
|
142
|
+
control[:removed_results_counts] = truncated
|
143
|
+
end
|
144
|
+
end
|
145
|
+
report
|
146
|
+
end
|
147
|
+
|
148
|
+
# Contacts the metasearch Automate API to check which of the inspec profile sha256 ids
|
149
|
+
# passed in via `report_shas` are missing from the Automate profiles metadata database.
|
150
|
+
def missing_automate_profiles(headers, report_shas)
|
151
|
+
Chef::Log.debug "Checking the #{ChefUtils::Dist::Automate::PRODUCT} profiles metadata for: #{report_shas}"
|
152
|
+
meta_url = URI(@url)
|
153
|
+
meta_url.path = "/compliance/profiles/metasearch"
|
154
|
+
response_str = http_client(meta_url.to_s).post(nil, "{\"sha256\": #{report_shas}}", headers)
|
155
|
+
missing_shas = Chef::JSONCompat.parse(response_str)["missing_sha256"]
|
156
|
+
unless missing_shas.empty?
|
157
|
+
Chef::Log.info "#{ChefUtils::Dist::Automate::PRODUCT} is missing metadata for the following profile ids: #{missing_shas}"
|
158
|
+
end
|
159
|
+
missing_shas
|
160
|
+
rescue => e
|
161
|
+
Chef::Log.error "missing_automate_profiles error: #{e.message}"
|
162
|
+
# If we get an error it's safer to assume none of the profile shas exist in Automate
|
163
|
+
report_shas
|
164
|
+
end
|
165
|
+
|
166
|
+
# Profile 'name' is a required property.
|
167
|
+
# By not sending it in the report, we make it clear to the ingestion backend that the profile metadata has been stripped from this profile in the report.
|
168
|
+
# Profile 'title' and 'version' are still kept for troubleshooting purposes in the backend.
|
169
|
+
SEEN_PROFILE_UNNECESSARY_FIELDS = %i{ copyright copyright_email groups license maintainer name summary supports}.freeze
|
170
|
+
|
171
|
+
SEEN_PROFILE_UNNECESSARY_CONTROL_FIELDS = %i{ code desc descriptions impact refs source_location tags title }.freeze
|
172
|
+
|
173
|
+
# TODO: This mutates the report and probably doesn't need to.
|
174
|
+
def strip_profiles_meta(report, missing_report_shas, run_time_limit)
|
175
|
+
report[:profiles].each do |p|
|
176
|
+
next if missing_report_shas.include?(p[:sha256])
|
177
|
+
|
178
|
+
p.delete_if { |f| SEEN_PROFILE_UNNECESSARY_FIELDS.include?(f) }
|
179
|
+
|
180
|
+
next unless p[:controls].is_a?(Array)
|
181
|
+
|
182
|
+
p[:controls].each do |c|
|
183
|
+
c.delete_if { |f| SEEN_PROFILE_UNNECESSARY_CONTROL_FIELDS.include?(f) }
|
184
|
+
c.delete(:waiver_data) if c[:waiver_data] == {}
|
185
|
+
|
186
|
+
next unless c[:results].is_a?(Array)
|
187
|
+
|
188
|
+
c[:results].each do |r|
|
189
|
+
if r[:run_time].is_a?(Float) && r[:run_time] < run_time_limit
|
190
|
+
r.delete(:start_time)
|
191
|
+
r.delete(:run_time)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
report[:run_time_limit] = run_time_limit
|
197
|
+
report
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|