diff --git a/attributes/default.rb b/attributes/default.rb index 5fdf62af..f691b5f7 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -360,6 +360,10 @@ default['datadog']['process_agent']['container_interval'] = nil default['datadog']['process_agent']['rtcontainer_interval'] = nil +# Cloud Workload Security functionality settings +default['datadog']['security_agent']['cws']['enabled'] = false +default['datadog']['security_agent']['cspm']['enabled'] = false + # System probe functionality settings # Whether this cookbook should write system-probe.yaml or not. diff --git a/recipes/dd-agent.rb b/recipes/dd-agent.rb index 5f190188..1f8986ec 100644 --- a/recipes/dd-agent.rb +++ b/recipes/dd-agent.rb @@ -166,6 +166,9 @@ def template_vars # system-probe is a dependency of the agent on Linux or Windows include_recipe '::system-probe' if system_probe_managed && system_probe_supported +# security-agent is a dependency of the agent on Linux or Windows +include_recipe '::security-agent' + # Installation metadata to let know the agent about installation method and its version include_recipe '::install_info' diff --git a/recipes/security-agent.rb b/recipes/security-agent.rb new file mode 100644 index 00000000..fb6557ce --- /dev/null +++ b/recipes/security-agent.rb @@ -0,0 +1,77 @@ +# +# Cookbook:: datadog +# Recipe:: security-agent +# +# Copyright:: 2011-2022, Datadog +# +# 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. +# + +is_windows = platform_family?('windows') + +# Set the correct agent startup action +security_agent_enabled = !is_windows && node['datadog']['security_agent']['cws']['enabled'] || node['datadog']['security_agent']['cspm']['enabled'] +security_agent_start = security_agent_enabled ? :start : :stop + +# +# Configures security-agent agent +security_agent_config_file = '/etc/datadog-agent/security-agent.yaml' +security_agent_config_file_exists = ::File.exist?(security_agent_config_file) + +template security_agent_config_file do + runtime_security_extra_config = {} + if node['datadog']['extra_config'] && node['datadog']['extra_config']['security_agent'] && node['datadog']['extra_config']['security_agent']['runtime_security_config'] + node['datadog']['extra_config']['security_agent']['runtime_security_config'].each do |k, v| + next if v.nil? + runtime_security_extra_config[k] = v + end + end + + compliance_extra_config = {} + if node['datadog']['extra_config'] && node['datadog']['extra_config']['security_agent'] && node['datadog']['extra_config']['security_agent']['compliance_config'] + node['datadog']['extra_config']['security_agent']['compliance_config'].each do |k, v| + next if v.nil? + compliance_extra_config[k] = v + end + end + + source 'security-agent.yaml.erb' + variables( + runtime_security_enabled: node['datadog']['security_agent']['cws']['enabled'], + runtime_security_extra_config: runtime_security_extra_config, + compliance_enabled: node['datadog']['security_agent']['cspm']['enabled'], + compliance_extra_config: compliance_extra_config + ) + + owner 'root' + group 'dd-agent' + mode '640' + + notifies :restart, 'service[datadog-agent-security]', :delayed if security_agent_enabled + + # Security agent is not enabled and the file doesn't exists, don't create it + not_if { !security_agent_enabled && !security_agent_config_file_exists } +end + +# Common configuration +service_provider = Chef::Datadog.service_provider(node) + +service_name = 'datadog-agent-security' + +service 'datadog-agent-security' do + service_name service_name + action [security_agent_start] + provider service_provider unless service_provider.nil? + supports :restart => true, :status => true, :start => true, :stop => true + subscribes :restart, "template[#{security_agent_config_file}]", :delayed if security_agent_enabled +end diff --git a/recipes/system-probe.rb b/recipes/system-probe.rb index 3477480d..f55ee997 100644 --- a/recipes/system-probe.rb +++ b/recipes/system-probe.rb @@ -20,10 +20,11 @@ is_windows = platform_family?('windows') # Set the correct agent startup action +cws_enabled = node['datadog']['security_agent']['cws']['enabled'] sysprobe_enabled = if is_windows node['datadog']['system_probe']['network_enabled'] else - node['datadog']['system_probe']['enabled'] || node['datadog']['system_probe']['network_enabled'] + node['datadog']['system_probe']['enabled'] || node['datadog']['system_probe']['network_enabled'] || cws_enabled end sysprobe_agent_start = sysprobe_enabled ? :start : :stop @@ -47,6 +48,14 @@ end end + runtime_security_extra_config = {} + if node['datadog']['extra_config'] && node['datadog']['extra_config']['security_agent'] && node['datadog']['extra_config']['security_agent']['runtime_security_config'] + node['datadog']['extra_config']['security_agent']['runtime_security_config'].each do |k, v| + next if v.nil? + runtime_security_extra_config[k] = v + end + end + source 'system_probe.yaml.erb' variables( enabled: node['datadog']['system_probe']['enabled'], @@ -54,7 +63,9 @@ debug_port: node['datadog']['system_probe']['debug_port'], bpf_debug: node['datadog']['system_probe']['bpf_debug'], enable_conntrack: node['datadog']['system_probe']['enable_conntrack'], - extra_config: extra_config + system_probe_extra_config: extra_config, + runtime_security_enabled: cws_enabled, + runtime_security_extra_config: runtime_security_extra_config ) if is_windows @@ -70,6 +81,7 @@ notifies :restart, 'service[datadog-agent-sysprobe]', :delayed if sysprobe_enabled # since process-agent collects network info through system-probe, enabling system-probe should also restart process-agent notifies :restart, 'service[datadog-agent]', :delayed if sysprobe_enabled + notifies :restart, 'service[datadog-agent-security]', :delayed if cws_enabled # System probe is not enabled and the file doesn't exists, don't create it not_if { !sysprobe_enabled && !system_probe_config_file_exists } diff --git a/spec/security-agent_spec.rb b/spec/security-agent_spec.rb new file mode 100644 index 00000000..af6b3349 --- /dev/null +++ b/spec/security-agent_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe 'datadog::security-agent' do + context 'with CWS enabled' do + cached(:solo) do + ChefSpec::SoloRunner.new( + platform: 'ubuntu', + version: '16.04' + ) do |node| + node.name 'chef-nodename' # expected to be used as the hostname in `datadog.yaml` + node.normal['datadog'] = { + 'api_key' => 'somethingnotnil', + 'agent_major_version' => 6, + 'security_agent' => { + 'cws' => { + 'enabled' => true, + } + }, + 'extra_config' => { + 'security_agent' => { + 'runtime_security_config' => { + 'activity_dump' => { + 'enabled' => true, + } + } + } + } + } + end + end + + cached(:chef_run) do + solo.converge(described_recipe) do + solo.resource_collection.insert( + Chef::Resource::Service.new('datadog-agent', solo.run_context)) + end + end + + it 'security-agent.yaml is created' do + expect(chef_run).to create_template('/etc/datadog-agent/security-agent.yaml') + end + + it 'security-agent.yaml contains expected YAML configuration' do + expected_yaml = <<-EOF + compliance_config: + enabled: false + runtime_security_config: + enabled: true + activity_dump: + enabled: true + EOF + + expect(chef_run).to(render_file('/etc/datadog-agent/security-agent.yaml').with_content { |content| + expect(YAML.safe_load(content).to_json).to be_json_eql(YAML.safe_load(expected_yaml).to_json) + }) + end + end +end diff --git a/templates/default/security-agent.yaml.erb b/templates/default/security-agent.yaml.erb new file mode 100644 index 00000000..dafb4379 --- /dev/null +++ b/templates/default/security-agent.yaml.erb @@ -0,0 +1,13 @@ +<% +## Populate system_probe_config ## +security_agent_config = { + runtime_security_config: { + enabled: @runtime_security_enabled, + }.merge(@runtime_security_extra_config), + compliance_config: { + enabled: @compliance_enabled, + }.merge(@compliance_extra_config), +} +-%> + +<%= JSON.parse(security_agent_config.to_json).to_yaml %> \ No newline at end of file diff --git a/templates/default/system_probe.yaml.erb b/templates/default/system_probe.yaml.erb index 801c8363..ce1b72eb 100644 --- a/templates/default/system_probe.yaml.erb +++ b/templates/default/system_probe.yaml.erb @@ -7,9 +7,15 @@ system_probe_config = { debug_port: @debug_port, bpf_debug: @bpf_debug, enable_conntrack: @enable_conntrack, - }.merge(@extra_config), + }.merge(@system_probe_extra_config), } +if @runtime_security_enabled + system_probe_config['runtime_security_config'] = { + enabled: @runtime_security_enabled + }.merge(@runtime_security_extra_config) +end + # We let the Agent default value take effect if user didn't explicitly # specify something else than nil for 'network_enabled' if !node['datadog']['system_probe']['network_enabled'].nil?