From 03cb22c10fad5a2b0e9c23f897ffbea5ecb3da1f Mon Sep 17 00:00:00 2001 From: Eric Renfro Date: Sun, 24 Jul 2016 16:11:12 -0400 Subject: [PATCH] Initial commit as ossec-ng --- .gitignore | 20 + Berksfile | 8 + README.md | 691 ++++++++++++++++++++ attributes/ossec.rb | 267 ++++++++ libraries/core.rb | 266 ++++++++ metadata.rb | 33 + recipes/agent.rb | 116 ++++ recipes/default.rb | 6 + recipes/server.rb | 181 +++++ recipes/supervisor_logs.rb | 36 + templates/default/client.keys.erb | 7 + templates/default/internal_options.conf.erb | 115 ++++ templates/default/local_decoder.xml.erb | 21 + templates/default/local_rules.xml.erb | 30 + templates/default/ossec-agent.conf.erb | 91 +++ templates/default/ossec-server.conf.erb | 200 ++++++ 16 files changed, 2088 insertions(+) create mode 100644 .gitignore create mode 100644 Berksfile create mode 100644 README.md create mode 100644 attributes/ossec.rb create mode 100644 libraries/core.rb create mode 100644 metadata.rb create mode 100644 recipes/agent.rb create mode 100644 recipes/default.rb create mode 100644 recipes/server.rb create mode 100644 recipes/supervisor_logs.rb create mode 100644 templates/default/client.keys.erb create mode 100644 templates/default/internal_options.conf.erb create mode 100644 templates/default/local_decoder.xml.erb create mode 100644 templates/default/local_rules.xml.erb create mode 100644 templates/default/ossec-agent.conf.erb create mode 100644 templates/default/ossec-server.conf.erb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbccf1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ +pkg/ + +# Berkshelf +.vagrant +/cookbooks +Berksfile.lock + +# Bundler +Gemfile.lock +bin/* +.bundle/* + +.kitchen/ +.kitchen.local.yml diff --git a/Berksfile b/Berksfile new file mode 100644 index 0000000..0974c9d --- /dev/null +++ b/Berksfile @@ -0,0 +1,8 @@ +source "https://supermarket.chef.io" + +metadata + +cookbook "selinux_policy", "~> 0.9.5" +cookbook "yum-atomic", "~> 0.1.2" +cookbook "yum-epel" +cookbook "apt-atomic", "~> 0.1.2" diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab1237c --- /dev/null +++ b/README.md @@ -0,0 +1,691 @@ +Description +=========== +Fully automated Installation and configuration of ossec-servers and ossec-agents +Manage the key generation and distribution between a server and multiple agents +Clean queues on the server if needed (rid) + +Requirements +============ +Any of: +* Ubuntu 12.04+ +* Debian 7.0+ +* CentOS 6.0+ +(should work with ossec systems if you have the packages) + +Attributes +========== +# General Attributes + +The attributes below follow the same namespace syntax that OSSEC does. Refer to +the official [OSSEC Documentation](http://www.ossec.net/doc/syntax/ossec_config.html) +for more information. + +Default attributes from the cookbook: + + default["ossec"]["version"] = "2.8" + default["ossec"]["syslog_output"]["ip"] = "127.0.0.1" + default["ossec"]["syslog_output"]["port"] = "514" + default["ossec"]["syslog_output"]["min_level"] = "5" + default["ossec"]["receiver_port"] = "1514" + default["ossec"]["log_alert_level"] = "1" + default["ossec"]["email_alert_level"] = "7" + default["ossec"]["agents"] = {} + +Default attributes from the ossec-server role: + + "ossec" => { + "email_notification" => 'yes', + "email_to" => [ + 'ossec@example.net', + ], + "email_from" => 'ossec-server@example.net', + "smtp_server" => 'localhost', + "white_list" => [ + '127.0.0.1', + '10.1.0.0/16' + ], + "email_alerts" => { + 'recipient@example.net' => { + 'level' => '9', + 'group' => 'syscheck', + 'event_location_tag' => 'reputation', + 'event_location_search' => 'roles:*mongodb*', + 'format' => 'sms', + 'rule_id' => '100001', + 'tags' => [ + 'do_not_delay', + 'do_not_group' + ] + } + }, + "server" => { + "service_name" => 'ossec-hids-server' + }, + "syscheck" => { + "frequency" => '7200', + "alert_new_files" => 'yes', + "auto_ignore" => 'no', + "directories" => { + '/bin' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/sbin' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/usr/bin' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/usr/sbin' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/etc' => { + 'report_changes' => 'yes', + 'realtime' => 'yes' + }, + '/tmp' => { + 'report_changes' => 'yes', + 'realtime' => 'no' + } + }, + "ignore" => { + '/etc/openvpn/openvpn-status.log' => {}, + '/etc/motd' => {}, + '/etc/mcollective/facts.yaml' => {}, + '/etc/blkid.tab' => {}, + '/etc/mtab' => {}, + '/etc/mail/statistics => {}', + '/etc/random-seed' => {}, + '/etc/adjtime' => {}, + '/etc/prelink.cache' => {}, + '/etc/dnscache/stats' => {}, + '/etc/dnscache/log' => {}, + '/etc/dnscache2/stats' => {}, + '/etc/dnscache2/log' => {}, + '/etc/tinydns/stats' => {}, + '/etc/tinydns/log' => {} + } + }, + "syslog_files" => { + '/var/log/syslog' => {}, + '/var/log/auth.log' => {}, + '/var/log/daemon.log' => {}, + '/var/log/kern.log' => {}, + '/var/log/mail.log' => {}, + '/var/log/user.log' => {}, + '/var/log/cron.log' => {} + } + +```email_alerts``` is a hash of recipients and servers. Each recipient will +receive all of the alert for the listed location (the list is a regex). +```event_location_tag``` must contain a valid chef tag. All the nodes listed by +that tag will generate a separate ```email_alerts``` rule. +This is additional to the default list ```email_to``` and is used to send alert to +specific recipients for a limited number of hosts only. + +# Local Rules Definitions +Rules are defined in Ruby Hash format and replicate the XML format of regular +[OSSEC Rules Syntax](http://www.ossec.net/doc/syntax/head_rules.html) +Each rule has a head, a body, tags and info (the last 2 being optional) + + head= + body= Test Rule + body= Big Error + body= server1 + tags= + tags= + info= http://IjustGotHacked.com + + +The section below are parsed by the template. The following items are mandatory: + * head/level + * body/description + +``` + "ossec" => + "rules" => { + "100001" => { + "head" => { + "level" => "7", + "maxsize" => "65536", + "frequency" => "100", + "timeframe" => "3600", + "ignore" => "5", + "overwrite" => "68321" + }, + "body" => { + "hostname_search" => "recipes:mms-agent", + "description" => "Super Security Rule for application XYZ", + "match" => "super dangerous error happened", + "regex" => "^\d+Hello World$", + "decoded_as" => "vsftpd", + "category" => "windows", + "srcip" => "192.168.1.254", + "dstip" => "10.1.6.23", + "user" => "bob", + "program_name" => "nginx", + "time" => "09:00-18:00", + "weekday" => "monday,tuesday", + "id" => "404", + "url" => "/changepassword.php", + "if_sid" => "100238", + "if_group" => "authentication_success", + "if_level" => "13", + "if_matched_sid" => "12037", + "if_matched_group" => "adduser", + "if_matched_level" => "7", + "options" => "no_email_alert", + "check_diff" => "true", + "group" => "syscheck" + }, + "tags" => [ + "same_source_ip", + "same_source_port", + "same_dst_port", + "same_location" + ], + "infos" => { + "link" => "http://trac.example.net/ticket/12345", + "text" => "the link above contains additional information" + } + } + } +``` + +## hostname_search + +To the exception of __hostname_search__, all attributes use the same syntax as the +ossec rule in XML format does. +__hostname_search__ in this cookbook represents a search query that is executed by +the server recipe to populate the `````` with the proper list of hosts, +dynamically pulled from chef. Search criterias can be anything that a chef search +can take. Example: ```recipe:mongodb\:\:replicaset and tags:reputation``` + +# Local Decoders Definitions +Decoders are defined in JSON format and replicate the XML format of regular +[OSSEC Decoder Syntax](http://www.ossec.net/doc/syntax/head_decoders.html) + + "ossec" => { + "decoders" => { + 'apache-errorlog' => { + "program_name" => '^httpd|^apache2', + "prematch" => { + "parser" => '^\S+ [\w+\s*\d+ \S+ \d+] [\S+] |^[warn] |^[notice] |^[error]' + }, + + }, + 'apache-errorlog-ip-custom' => { + "parent" => 'apache-errorlog', + "prematch" => { + "offset" => 'after_parent', + "parser" => '^[client' + }, + "regex" => { + "offset" => 'after_prematch', + "parser" => '^ (\d+.\d+.\d+.\d+)]' + }, + "order" => 'srcip' + }, + 'web-accesslog-custom' => { + "parent" => 'web-accesslog', + "type" => 'web-log', + "prematch" => { + "parser" => '^\d+.\d+.\d+.\d+ |^::ffff:\d+.\d+.\d+.\d+' + }, + "regex" => { + "parser" => '^\d+.\d+.\d+.\d+ \S+ (\d+.\d+.\d+.\d+) \S+ \S+ \S+ [\S+ \S\d+] "\w+ (\S+) HTTP\S+ (\d+) \S+ "(\S+)"' + }, + "order" => 'srcip, url, id, extra_data' + } + } + } + +```prematch``` and ```regex``` are hashes that can have an ```offset``` value and +always have a ```parser``` value. See the ossec documentation for more information. + +# Local Syslog Files and Syscheck Ignore Files +If you want specific log files to be monitored on specific agents, you can use +a `syslog_files` block in the agent node attributes. The `apply_to` +parameter of this block is a `Chef::Search()` that will expand to a list of +hosts. If the given agent belong to the list of hosts, it will add the logfile +to its local ossec configuration. log_format will default to "syslog" if not set. + +If you want to ignore specific files in syscheck, similarly you can use the same +`apply_to` option as shown below: + +``` +default_attributes( + "ossec" => { + "syslog_files" => { + '/var/log/supervisor/supervisor.log' => { + 'apply_to' => 'supervisor:*', + 'log_format' => 'syslog' + } + }, + "syscheck" => { + "ignore" => { + '/etc/openvpn/openvpn-status.log' => { + 'apply_to' => 'roles:vpn-server' + }, + '^/opt/graphite/storage/' => { + 'apply_to' => 'roles:graphite-server OR roles:statsd-server', + 'type' => 'sregex' + } + } + } + } +) +``` + +# OSSEC Rules Enable/Disable +You can also choose what rules OSSEC it configured to load. Each of the rules in +/var/ossec/rules can be enabled and disabled at will with a very simple format. +Note that these do not have an apply_to option and are just boolean values, this +is because only the OSSEC server loads the rules into action. The following is +an example that will enable the normally disabled `symantec-av-rules.xml` and +disable the `web_rules.xml`: + +``` +default_attributes( + "ossec" => { + "load_rules" => { + 'symantec-av_rules.xml' => true, + 'web_rules.xml' => false + } + } +) +``` + +# Commands and Auto-Response +Since OSSEC is an HIDS system, you can also enable and disable commands and +active-response functionality and define your own as needed. You can enable +or disable existing defined commands and active-responses using the +`enable` flag, and target where they are set using the `apply_to` flag. You +can also define any of the allowed active-response options dynamically, such +as adding agent_id, rules_group, etc. +Here is an example of enabling the firewall-stop command and its auto-response +and changing it from the default local to all hosts and adding a `rules_group` +to the active-response definition so it only triggers on authentication +failures: + +``` +default_attributes( + "ossec" => { + "command" => { + "firewall-stop" => { + "enabled" => true + } + }, + "auto-response => { + "firewall-stop" => { + "enabled" => true, + "location" => "all", + "rules_group" => "authentication_failed,authentication_failures" + } + } + } +) +``` + +Usage +===== +* `recipe[ossec-server]` should be a stand alone installation +* `recipe[ossec-agent]` should be added (via role[ossec-agent]) to all the nodes of the +environment + +# Example Roles +## ossec-server +This role can be used to provision an ossec server: + +```ruby +name 'ossec-server' +description 'OSSEC Server' +run_list( + 'recipe[ossec-ng::server]', + 'role[postfix]' +) +override_attributes( + "ossec" => { + "agent" => { + "enable" => false + } + } +) +default_attributes( + "ossec" => { + "email_notification" => 'yes', + "email_to" => [ + 'ossec-alerts@example.net', + ], + "email_from" => 'ossec-server', + "smtp_server" => 'localhost', + "white_list" => [ + '127.0.0.1', + '10.0.0.0/0' + ], + "email_alerts" => { + 'bob@example.net' => { + 'event_location_tag' => 'project1', + }, + 'alice@example.net' => { + 'event_location_tag' => 'project1', + 'group' => 'developers', + }, + 'eve@example.net' => { + 'event_location_tag' => 'project2', + 'group' => 'developers', + }, + 'mike@example.net' => { + 'event_location_search' => 'tags:project1 OR tags:project2 OR tags:project3', + 'group' => 'developers', + }, + 'group2@example.net' => { + 'event_location_search' => 'roles:application-server AND roles:python-django', + 'group' => 'frontend-group', + }, + }, + "decoders" => { + 1 => { + "name" => 'apache-errorlog', + "program_name" => '^httpd|^apache2', + "prematch" => { + "parser" => '^\S+ [\w+\s*\d+ \S+ \d+] [\S+] |^[warn] |^[notice] |^[error]' + }, + + }, + 2 => { + "name" => 'apache-errorlog-ip-custom', + "parent" => 'apache-errorlog', + "prematch" => { + "offset" => 'after_parent', + "parser" => '^[client' + }, + "regex" => { + "offset" => 'after_prematch', + "parser" => '^ (\d+.\d+.\d+.\d+)]' + }, + "order" => 'srcip' + }, + 3 => { + "name" => 'web-accesslog-custom', + "parent" => 'web-accesslog', + "type" => 'web-log', + "prematch" => { + "parser" => '^\d+.\d+.\d+.\d+ |^::ffff:\d+.\d+.\d+.\d+' + }, + "regex" => { + "parser" => '^\d+.\d+.\d+.\d+ \S+ (\d+.\d+.\d+.\d+) \S+ \S+ \S+ [\S+ \S\d+] "\w+ (\S+) HTTP\S+ (\d+) \S+ "(\S+)"' + }, + "order" => 'srcip, url, id, extra_data' + } + }, + "rules" => { + 1002 => { + "head" => { + "level" => '2', + "overwrite" => 'yes' + }, + "body" => { + "description" => 'Unknown problem somewhere in the system.', + "match" => 'core_dumped|failure|error|Error|attack|bad |illegal |denied|refused|unauthorized|fatal|fail|Segmentation Fault|Corrupted|Traceback|raise', + "options" => 'alert_by_email' + } + }, + 1003 => { + "head" => { + "level" => '6', + "maxsize" => '16384', + "overwrite" => 'yes' + }, + "body" => { + "description" => 'Non standard syslog message (larger than 16kB).' + } + }, + 100003 => { + "head" => { + "level" => '10' + }, + "body" => { + "description" => 'Successful sudo during non-business hours 6pm to 8am', + "if_sid" => '5402,5403', + "time" => '10pm - 12am' + } + }, + 100004 => { + "head" => { + "level" => '10' + }, + "body" => { + "description" => 'Successful sudo during weekend.', + "if_sid" => '5402,5403', + "weekday" => 'weekends' + } + }, + 100005 => { + "head" => { + "level" => '0' + }, + "body" => { + "description" => 'Silencing sudo errors from accounts allowed to sudo anytime', + "if_sid" => '100004,100005', + "match" => 'nagios' + } + }, + 100006 => { + "head" => { + "level" => '0' + }, + "body" => { + "description" => 'Silencing ossec agent stop/start during business hours 8am to 6pm', + "if_sid" => '502,503,504', + "time" => '12:00-22:00', + "weekday" => 'monday,tuesday,wednesday,thursday,friday' + } + }, + 100007 => { + "head" => { + "level" => '8' + }, + "body" => { + "description" => 'Login outside of business hours 6pm to 8am', + "if_sid" => '5501', + "time" => '22:00-12:00' + } + }, + 100008 => { + "head" => { + "level" => '8' + }, + "body" => { + "description" => 'Login during weekend.', + "if_sid" => '5501', + "weekday" => 'weekends' + } + }, + 100009 => { + "head" => { + "level" => '0' + }, + "body" => { + "description" => 'Ignore logins alerts for systems accounts', + "if_sid" => '100007,100008', + "match" => 'ubuntu|nagios' + } + } + } + } +) +``` + +## ossec-agent +This role can be used to provision an ossec-agent + +```ruby +name "ossec-agent" +description "OSSEC Agent" +run_list( + "recipe[ossec-ng::agent]" +) +default_attributes( + "ossec" => { + "client" => { + "service_name" => 'ossec-hids-client' + }, + "syscheck" => { + "frequency" => '7200', + "alert_new_files" => 'yes', + "auto_ignore" => 'no', + "directories" => { + '/bin' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/boot' => { + 'report_changes' => 'no', + 'realtime' => 'no' + }, + '/etc' => { + 'report_changes' => 'yes', + 'realtime' => 'no' + }, + '/lib/lsb' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/lib/modules' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/lib/plymouth' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/lib/security' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/lib/terminfo' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/lib/ufw' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/lib/xtables' => { + 'report_changes' => 'no', + 'realtime' => 'no' + }, + '/media' => { + 'report_changes' => 'no', + 'realtime' => 'no' + }, + '/opt' => { + 'report_changes' => 'no', + 'realtime' => 'no' + }, + '/root' => { + 'report_changes' => 'yes', + 'realtime' => 'no' + }, + '/srv' => { + 'report_changes' => 'no', + 'realtime' => 'no' + }, + '/sbin' => { + 'report_changes' => 'no', + 'realtime' => 'yes' + }, + '/usr/' => { + 'report_changes' => 'yes', + 'realtime' => 'yes' + }, + '/tmp' => { + 'report_changes' => 'no', + 'realtime' => 'no' + } + }, + "ignore" => { + '/etc/openvpn/openvpn-status.log' => {} + '/etc/motd' => {}, + '/etc/blkid.tab' => {}, + '/etc/mtab' => {}, + '/etc/mail/statistics' => {}, + '/etc/random-seed' => {}, + '/etc/adjtime' => {}, + '/etc/prelink.cache' => {}, + '/root/.bash_history' => {} + '^/opt/graphite/storage/' => { + 'apply_to' => 'roles:graphite-server OR roles:statsd-server', + 'type' => 'sregex' + }, + '^/usr/lib/elasticsearch' => { + 'apply_to' => 'roles:elastic-search-cluster', + 'type' => 'sregex' + }, + '^/etc/chef/cache/checksums/' => { + 'apply_to' => 'roles:chef-client', + 'type' => 'sregex' + }, + '^/srv/rsyslog/' => { + 'apply_to' => 'roles:rsyslog-server', + 'type' => 'sregex' + }, + '^/etc/djbdns/public-dnscache/supervise/|^/etc/djbdns/tinydns-internal/supervise/|^/etc/djbdns/public-dnscache/log|^/etc/djbdns/tinydns-internal/log|^/etc/djbdns/tinydns-internal/root/data' => { + 'apply_to' => 'roles:djbdns-server', + 'type' => 'sregex' + } + } + }, + "syslog_files" => { + '/var/log/syslog' => {}, + '/var/log/auth.log' => {}, + '/var/log/daemon.log' => {}, + '/var/log/kern.log' => {}, + '/var/log/mail.log' => {}, + '/var/log/user.log' => {}, + '/var/log/cron.log' => {}, + '/var/log/chef/client.log' => {}, + '/var/log/supervisor/supervisor.log' => { + 'apply_to' => 'supervisor:*', + 'log_format' => 'syslog' + }, + '/var/log/rabbitmq/rabbit1.log' => { + 'apply_to' => 'recipes:rabbitmq', + 'log_format' => 'multi-line:3' + }, + '/var/log/nginx/access.log' => { + 'apply_to' => 'nginx:*', + 'log_format' => 'syslog' + }, + '/var/log/nginx/error.log' => { + 'apply_to' => 'nginx:*', + 'log_format' => 'syslog' + }, + '/var/log/nagios3/nagios.log' => { + 'apply_to' => 'roles:nagios-server', + 'log_format' => 'syslog' + }, + '/var/log/nagios3/apache_access.log' => { + 'apply_to' => 'roles:nagios-server', + 'log_format' => 'syslog' + }, + '/var/log/nagios3/apache_error.log' => { + 'apply_to' => 'roles:nagios-server', + 'log_format' => 'syslog' + } + } + } +) +``` + +Author +====== +Eric Renfro - psi-jack@linux-help.org - https://linux-help.org + +Derived from works from: +Julien Vehent - julien@linuxwall.info - http://jve.linuxwall.info + diff --git a/attributes/ossec.rb b/attributes/ossec.rb new file mode 100644 index 0000000..ad91c1d --- /dev/null +++ b/attributes/ossec.rb @@ -0,0 +1,267 @@ +default["ossec"]["version"] = "2.8" +default["ossec"]["syslog_output"]["ip"] = "172.16.254.254" +default["ossec"]["syslog_output"]["port"] = "514" +default["ossec"]["syslog_output"]["min_level"] = "5" +default["ossec"]["receiver_port"] = "1514" +default["ossec"]["log_alert_level"] = "1" +default["ossec"]["email_alert_level"] = "7" +default["ossec"]["email_maxperhour"] = "9999" +default["ossec"]["memory_size"] = "100000" +default["ossec"]["remote"]["connection"] = "secure" +default["ossec"]["agents"] = {} +default["ossec"]["rules"] = {} +default["ossec"]["email_alerts"] = {} +if node["roles"].include?("ossec-server") + default["ossec"]["agent"]["enable"] = false +else + default["ossec"]["agent"]["enable"] = true +end + +if platform_family?('debian') + default["ossec"]["server"]["service_name"] = "ossec-hids-server" + default["ossec"]["client"]["service_name"] = "ossec-hids-client" +elsif platform_family?('rhel') + default["ossec"]["server"]["service_name"] = "ossec-hids" + default["ossec"]["client"]["service_name"] = "ossec-hids" +end + +# Sane defaults for each distribution platform for syslog files +if platform_family?('debian') + default["ossec"]["syslog_files"]["/var/log/auth.log"] = {} + default["ossec"]["syslog_files"]["/var/log/daemon.log"] = {} + default["ossec"]["syslog_files"]["/var/log/kern.log"] = {} + default["ossec"]["syslog_files"]["/var/log/mail.log"] = {} + default["ossec"]["syslog_files"]["/var/log/syslog"] = {} + default["ossec"]["syslog_files"]["/var/log/user.log"] = {} + default["ossec"]["syslog_files"]["/var/log/chef/client.log"] = {} + +elsif platform_family?('rhel') + default["ossec"]["syslog_files"]["/var/log/cron"] = {} + default["ossec"]["syslog_files"]["/var/log/dmesg"] = {} + default["ossec"]["syslog_files"]["/var/log/maillog"] = {} + default["ossec"]["syslog_files"]["/var/log/messages"] = {} + default["ossec"]["syslog_files"]["/var/log/secure"] = {} + default["ossec"]["syslog_files"]["/var/log/spooler"] = {} + default["ossec"]["syslog_files"]["/var/log/yum.log"] = {} + default["ossec"]["syslog_files"]["/var/log/chef/client.log"] = {} +end + +# Sane defaults for syscheck +default["ossec"]["syscheck"]["frequency"] = '7200' +default["ossec"]["syscheck"]["alert_new_files"] = 'yes' +default["ossec"]["syscheck"]["auto_ignore"] = 'no' + +default["ossec"]["syscheck"]["directories"]['/bin'] = { + 'report_changes' => 'no', + 'realtime' => 'yes' +} +default["ossec"]["syscheck"]["directories"]['/boot'] = { + 'report_changes' => 'no', + 'realtime' => 'no' +} +default["ossec"]["syscheck"]["directories"]['/etc'] = { + 'report_changes' => 'yes', + 'realtime' => 'no' +} +default["ossec"]["syscheck"]["directories"]['/lib/lsb'] = { + 'report_changes' => 'no', + 'realtime' => 'yes' +} +default["ossec"]["syscheck"]["directories"]['/lib/modules'] = { + 'report_changes' => 'no', + 'realtime' => 'yes' +} +default["ossec"]["syscheck"]["directories"]['/lib/plymouth'] = { + 'report_changes' => 'no', + 'realtime' => 'yes' +} +default["ossec"]["syscheck"]["directories"]['/lib/security'] = { + 'report_changes' => 'no', + 'realtime' => 'yes' +} +default["ossec"]["syscheck"]["directories"]['/lib/terminfo'] = { + 'report_changes' => 'no', + 'realtime' => 'yes' +} +default["ossec"]["syscheck"]["directories"]['/lib/ufw'] = { + 'report_changes' => 'no', + 'realtime' => 'yes' +} +default["ossec"]["syscheck"]["directories"]['/lib/xtables'] = { + 'report_changes' => 'no', + 'realtime' => 'no' +} +default["ossec"]["syscheck"]["directories"]['/media'] = { + 'report_changes' => 'no', + 'realtime' => 'no' +} +default["ossec"]["syscheck"]["directories"]['/opt'] = { + 'report_changes' => 'no', + 'realtime' => 'no' +} +default["ossec"]["syscheck"]["directories"]['/root'] = { + 'report_changes' => 'yes', + 'realtime' => 'no' +} +default["ossec"]["syscheck"]["directories"]['/srv'] = { + 'report_changes' => 'no', + 'realtime' => 'no' +} +default["ossec"]["syscheck"]["directories"]['/sbin'] = { + 'report_changes' => 'no', + 'realtime' => 'yes' +} +default["ossec"]["syscheck"]["directories"]['/usr/'] = { + 'report_changes' => 'yes', + 'realtime' => 'yes' +} +default["ossec"]["syscheck"]["directories"]['/tmp'] = { + 'report_changes' => 'no', + 'realtime' => 'no' +} + +# Syscheck Ignore Files +default["ossec"]["syscheck"]["ignore"]['/etc/openvpn/openvpn-status.log'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/motd'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/blkid.tab'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/mtab'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/mail/statistics'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/random-seed'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/adjtime'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/prelink.cache'] = {} +default["ossec"]["syscheck"]["ignore"]['/root/.bash_history'] = {} +default["ossec"]["syscheck"]["ignore"]['/root/.viminfo'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/dnscache/stats'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/dnscache/log'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/dnscache2/stats'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/dnscache2/log'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/tinydns/stats'] = {} +default["ossec"]["syscheck"]["ignore"]['/etc/tinydns/log'] = {} + +# Commands +default["ossec"]["command"]["host-deny"]["enabled"] = false +default["ossec"]["command"]["host-deny"]["executable"] = 'host-deny.sh' +default["ossec"]["command"]["host-deny"]["expect"] = 'srcip' +default["ossec"]["command"]["host-deny"]["timeout_allowed"] = 'yes' + +default["ossec"]["command"]["firewall-stop"]["enabled"] = true +default["ossec"]["command"]["firewall-stop"]["executable"] = 'firewall-drop.sh' +default["ossec"]["command"]["firewall-stop"]["expect"] = 'srcip' +default["ossec"]["command"]["firewall-stop"]["timeout_allowed"] = 'yes' + +default["ossec"]["command"]["disable-account"]["enabled"] = false +default["ossec"]["command"]["disable-account"]["executable"] = 'disable-account.sh' +default["ossec"]["command"]["disable-account"]["expect"] = 'user' +default["ossec"]["command"]["disable-account"]["timeout_allowed"] = 'yes' + +default["ossec"]["local_command"] = {} + +# Active-Responses +default["ossec"]["active-response"]["host-deny"]["enabled"] = true +default["ossec"]["active-response"]["host-deny"]["location"] = 'local' +default["ossec"]["active-response"]["host-deny"]["level"] = '10' +default["ossec"]["active-response"]["host-deny"]["timeout"] = '600' + +default["ossec"]["active-response"]["firewall-stop"]["enabled"] = true +default["ossec"]["active-response"]["firewall-stop"]["location"] = 'local' +default["ossec"]["active-response"]["firewall-stop"]["level"] = '10' +default["ossec"]["active-response"]["firewall-stop"]["timeout"] = '600' + +default["ossec"]["active-response"]["disable-account"]["enabled"] = false +default["ossec"]["active-response"]["disable-account"]["location"] = 'local' +default["ossec"]["active-response"]["disable-account"]["level"] = '10' +default["ossec"]["active-response"]["disable-account"]["timeout"] = '600' + + +# internal options, you probably don't want to touch that +default["ossec"]["internal"]["analysisd"]["default_timeframe"] = "360" +default["ossec"]["internal"]["analysisd"]["stats_maxdiff"] = "25000" +default["ossec"]["internal"]["analysisd"]["stats_mindiff"] = "250" +default["ossec"]["internal"]["analysisd"]["stats_percent_diff"] = "30" +default["ossec"]["internal"]["analysisd"]["fts_list_size"] = "32" +default["ossec"]["internal"]["analysisd"]["fts_min_size_for_str"] = "14" +default["ossec"]["internal"]["analysisd"]["log_fw"] = "1" +default["ossec"]["internal"]["logcollector"]["loop_timeout"] = "2" +default["ossec"]["internal"]["logcollector"]["open_attempts"] = "8" +default["ossec"]["internal"]["logcollector"]["remote_commands"] = 1 +default["ossec"]["internal"]["remoted"]["recv_counter_flush"] = "128" +default["ossec"]["internal"]["remoted"]["comp_average_printout"] = "19999" +default["ossec"]["internal"]["remoted"]["verify_msg_id"] = "1" +default["ossec"]["internal"]["maild"]["strict_checking"] = "1" +default["ossec"]["internal"]["maild"]["groupping"] = "0" +default["ossec"]["internal"]["maild"]["full_subject"] = "1" +default["ossec"]["internal"]["maild"]["geoip"] = "1" +default["ossec"]["internal"]["monitord"]["compress"] = "1" +default["ossec"]["internal"]["monitord"]["sign"] = "1" +default["ossec"]["internal"]["monitord"]["monitor_agents"] = "1" +default["ossec"]["internal"]["syscheck"]["sleep"] = "2" +default["ossec"]["internal"]["syscheck"]["sleep_after"] = "15" +default["ossec"]["internal"]["dbd"]["reconnect_attempts"] = "10" +default["ossec"]["internal"]["window"]["debug"] = "0" +default["ossec"]["internal"]["syscheck"]["debug"] = "0" +default["ossec"]["internal"]["remoted"]["debug"] = "0" +default["ossec"]["internal"]["analysisd"]["debug"] = "0" +default["ossec"]["internal"]["logcollector"]["debug"] = "0" +default["ossec"]["internal"]["agent"]["debug"] = "0" + +# What OSSEC fules files to load +default["ossec"]["load_rules"] = { + 'rules_config.xml' => true, + 'pam_rules.xml' => true, + 'sshd_rules.xml' => true, + 'telnetd_rules.xml' => false, + 'syslog_rules.xml' => true, + 'arpwatch_rules.xml' => true, + 'symantec-av_rules.xml' => false, + 'symantec-ws_rules.xml' => false, + 'pix_rules.xml' => false, + 'named_rules.xml' => true, + 'smbd_rules.xml' => true, + 'vsftpd_rules.xml' => false, + 'pure-ftpd_rules.xml' => false, + 'proftpd_rules.xml' => false, + 'ms_ftpd_rules.xml' => false, + 'ftpd_rules.xml' => false, + 'hordeimp_rules.xml' => false, + 'roundcube_rules.xml' => false, + 'wordpress_rules.xml' => false, + 'cimserver_rules.xml' => false, + 'vpopmail_rules.xml' => false, + 'vmpop3d_rules.xml' => false, + 'courier_rules.xml' => false, + 'web_rules.xml' => true, + 'web_appsec_rules.xml' => true, + 'apache_rules.xml' => true, + 'nginx_rules.xml' => true, + 'php_rules.xml' => true, + 'mysql_rules.xml' => true, + 'postgresql_rules.xml' => true, + 'ids_rules.xml' => true, + 'squid_rules.xml' => false, + 'firewall_rules.xml' => true, + 'cisco-ios_rules.xml' => false, + 'netscreenfw_rules.xml' => false, + 'sonicwall_rules.xml' => false, + 'postfix_rules.xml' => true, + 'sendmail_rules.xml' => false, + 'imapd_rules.xml' => false, + 'mailscanner_rules.xml' => false, + 'dovecot_rules.xml' => false, + 'ms-exchange_rules.xml' => false, + 'racoon_rules.xml' => false, + 'vpn_concentrator_rules.xml' => false, + 'spamd_rules.xml' => false, + 'msauth_rules.xml' => false, + 'clam_av_rules.xml' => true, + 'mcafee_av_rules.xml' => false, + 'trend-osce_rules.xml' => false, + 'ms-se_rules.xml' => false, + 'zeus_rules.xml' => false, + 'solaris_bsm_rules.xml' => false, + 'vmware_rules.xml' => false, + 'ms_dhcp_rules.xml' => false, + 'asterisk_rules.xml' => false, + 'ossec_rules.xml' => true, + 'attack_rules.xml' => true, + 'local_rules.xml' => true, +} diff --git a/libraries/core.rb b/libraries/core.rb new file mode 100644 index 0000000..c3b6865 --- /dev/null +++ b/libraries/core.rb @@ -0,0 +1,266 @@ +# Core function for OSSEC +# Used by the server and agent recipes + +module OssecCore + + def ossec_hostname_search() + # resolve the hostname_search of a rule to a list of hosts + node["ossec"]["rules"].each do |id,params| + if not params.nil? + params[:body].each do |key, value| + if key.eql?('hostname_search') + hosts_list = search(:node, + "(#{value}) AND roles:ossec-agent "\ + " AND chef_environment:#{node.chef_environment}" + ).map {|n| n.hostname} + if hosts_list.empty? + # search didn't return anything + # store a dummy value in the attributes + Chef::Log.info("OSSEC: Hostname search returned empty result. " + + "'#{value}'") + params[:body][:hostname] = "invalid-search-returned-empty-result" + else + # store in the node params but discard the last char + params[:body][:hostname] = hosts_list.join('|') + end + end + end + end + end + end + + + def ossec_event_location_search() + # resolve the location search of an email_alert block to a hostname + node["ossec"]["email_alerts"].each do|recipient,params| + if params.has_key?('event_location_search') + if Chef::Config[:solo] + Chef::Log.warn('This recipe uses search. Chef Solo does not support search.') + else + dest = search(:node, + "(#{params[:event_location_search]}) " \ + "AND chef_environment:#{node.chef_environment}" + ).map {|n| n.hostname} + node.default["ossec"]["email_alerts"][recipient]["resolved_search"] = dest + end + end + end + end + + def ossec_set_syscheck_flags!(*args) + # go through the list of command/active-response and check the ones + # that apply to this node + args.each do |config| + unless node["ossec"]["syscheck"][config].nil? + node["ossec"]["syscheck"][config].each do |item, params| + unless params[:apply_to].nil? + locations = search(:node, + "(#{params[:apply_to]}) " \ + "AND chef_environment:#{node.chef_environment}" + ).map {|n| n.ipaddress} + if locations.include?(node["ipaddress"]) + node.default["ossec"]["syscheck"][config][item]["use_here"] = true + else + node.default["ossec"]["syscheck"][config][item]["use_here"] = false + end + else + node.default["ossec"]["syscheck"][config][item]["use_here"] = true + end + end + end + end + end + + def ossec_set_filtered_flags!(*args) + # go through the list of command/active-response and check the ones + # that apply to this node + args.each do |config| + unless node["ossec"][config].nil? + node["ossec"][config].each do |item, params| + unless params[:apply_to].nil? + locations = search(:node, + "(#{params[:apply_to]}) " \ + "AND chef_environment:#{node.chef_environment}" + ).map {|n| n.ipaddress} + if locations.include?(node["ipaddress"]) + node.default["ossec"][config][item]["use_here"] = true + else + node.default["ossec"][config][item]["use_here"] = false + end + else + node.default["ossec"][config][item]["use_here"] = true + end + end + end + end + end + + + def ossec_agent_create_parameters(agent, server) + # Returns a hash with the identiers for this agent + agent_hash = {} + # IP is defined by lanip, if available (ohai plugin network_addr) + # or by the default ipaddress otherwise + agent_hash[:ip] = agent[:network][:lanip] || agent.ipaddress + + # Ossec limits the agents name length to 32 characters, so to avoid + # names collisions, we concatenate the agent_ip with the first characters + # of the hostname + name = agent_hash[:ip] + "_" + agent[:hostname] + agent_hash[:name] = name[0,31] + + # ossec agent id is an integer used to identify an agent. we force that ID + # to be the IP address without the dots (10.1.2.3 becomes 10123) + agent_hash[:id] = agent_hash[:ip].gsub(".", "") + + agent_hash[:key] = "undef" + if server["ossec"]["agents"].has_key?(agent_hash[:id]) + if server["ossec"]["agents"][agent_hash[:id]].has_key?('key') + agent_hash[:key] = server["ossec"]["agents"][agent_hash[:id]]["key"] + end + end + agent_hash[:rid] = "none" + + return agent_hash + end + + + def ossec_generate_agent_key(agent_hash) + # Returns a 64 characters double md5 hash, used as a symetric key + seed1 = rand(100000000000).to_s + seed2 = rand(100000000000).to_s + str1 = Digest::MD5.hexdigest(seed1 + \ + agent_hash[:id] + \ + agent_hash[:ip] + \ + seed2) + str2 = Digest::MD5.hexdigest(seed2 + \ + agent_hash[:name] + \ + seed1 + \ + agent_hash[:ip] + \ + agent_hash[:id]) + key = str1 + str2 + return key + end + + + def ossec_agent_has_valid_key?(agent_hash, server) + # Does the server have a valid key for this agent ? + if server["ossec"]["agents"].key?(agent_hash[:id]) + if server["ossec"]["agents"][agent_hash[:id]].key?('key') + if server["ossec"]["agents"][agent_hash[:id]]["key"].length == 64 + return true + end + end + end + return false + end + + + def ossec_agent_knows_key?(agent_hash, agent) + # Does the agent have a key that matches the server ? + if agent.key?('ossec') + if agent["ossec"].key?('agents') + if agent["ossec"]["agents"].key?(agent_hash[:id]) + if agent["ossec"]["agents"][agent_hash[:id]].key?('key') + if agent["ossec"]["agents"][agent_hash[:id]]["key"] == agent_hash[:key] + return true + end + end + end + end + end + return false + end + + + def ossec_verify_agent(agent_hash, server) + # check if this agent (id, name, ip) is defined on the server + if server["ossec"]["agents"].key?(agent_hash[:id]) + agent_srv_data = server["ossec"]["agents"][agent_hash[:id]] + if agent_srv_data[:name].eql?(agent_hash[:name]) + if agent_srv_data[:ip].eql?(agent_hash[:ip]) + return true + else + Chef::Log.info("OSSEC: agent ip mismatch. " + + "server has '#{agent_srv_data[:ip]}' " + + "agent has '#{agent_hash[:ip]}'") + end + else + Chef::Log.info("OSSEC: agent name mismatch. " + + "server has '#{agent_srv_data[:name]}' " + + "agent has '#{agent_hash[:name]}'") + end + else + Chef::Log.info("OSSEC: agent name '#{agent_hash[:name]}' " + + " ip '#{agent_hash[:ip]}'" + + " configuration not found on server.") + end + return false + end + + + def ossec_agent_is_active?(id) + if File.exists?("/var/ossec/bin/agent_control") + #cmd = Chef::ShellOut.new("/var/ossec/bin/agent_control -s -i #{id}") + cmd = Mixlib::ShellOut.new("/var/ossec/bin/agent_control -s -i #{id}") + cmd_ret = cmd.run_command + status = cmd.stdout.split(",") + if status[3] && status[3].eql?("Active") + return true + end + end + return false + end + + + def ossec_agent_is_zombie?(id) + if File.exists?("/var/ossec/bin/agent_control") + #cmd = Chef::ShellOut.new("/var/ossec/bin/agent_control -s -i #{id}") + cmd = Mixlib::ShellOut.new("/var/ossec/bin/agent_control -s -i #{id}") + cmd_ret = cmd.run_command + status = cmd.stdout.split(",") + if not status[6] || status[3] =~ /(Never connected|)/ + return true + elsif status[6] !~ /Unknown/ + last_keep_alive = Time.parse(status[6]) + three_days_ago = (Time.now - (24*60*60*3)) + if three_days_ago > last_keep_alive + return true + end + end + end + return false + end + + + def ossec_agent_should_be_removed?(id) + if File.exists?("/var/ossec/bin/agent_control") + #cmd = Chef::ShellOut.new("/var/ossec/bin/agent_control -s -i #{id}") + cmd = Mixlib::ShellOut.new("/var/ossec/bin/agent_control -s -i #{id}") + cmd_ret = cmd.run_command + status = cmd.stdout.split(",") + if not status[6] or status[6] =~ /Unknown/ + return true + else + last_keep_alive = Time.parse(status[6]) + seven_days_ago = (Time.now - (24*60*60*7)) + if seven_days_ago > last_keep_alive + return true + end + end + end + return false + end + + + def ossec_agent_needs_rid?(id, agent) + # Check if the agent queue needs to be removed, either because the server + # said so, or because the agent asked for it + if agent["ossec"]["agents"][id]["rid"].eql?("todo") \ + or node["ossec"]["agents"][id]["rid"].eql?("todo") + return true + else + return false + end + end +end diff --git a/metadata.rb b/metadata.rb new file mode 100644 index 0000000..73146b9 --- /dev/null +++ b/metadata.rb @@ -0,0 +1,33 @@ +name "ossec-ng" +maintainer "Eric Renfro" +maintainer_email "psi-jack@linux-help.org" +license "GPLv2" +description "Installs/Configures ossec" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version "1.2.0" +issues_url "http://git.linux-help.org/Linux-Help/ossec-ng/issues" +source_url "http://git.linux-help.org/Linux-Help/ossec-ng" + +recipe 'ossec-ng::default', 'Installs the neccessary repositories to install OSSEC' +recipe 'ossec-ng::server', 'Installs OSSEC for use as a server' +recipe 'ossec-ng::agent', 'Installs OSSEC for use as an agent' + +%w( + debian + ubuntu + centos + redhat + scientific + amazon + oracle +).each do |os| + supports os +end + +depends 'yum-epel' +depends 'yum-atomic', '~> 0.1.2' +depends 'apt-atomic', '~> 0.1.2' + +suggests 'postfix' +suggests 'selinux_policy' + diff --git a/recipes/agent.rb b/recipes/agent.rb new file mode 100644 index 0000000..ec4feae --- /dev/null +++ b/recipes/agent.rb @@ -0,0 +1,116 @@ +# Ossec Agent provisioning recipe +# install the ossec-hids-client package, push the global +# and role specific configuration for the node +# get a key from the ossec-server if there's one + +#if not node['lsb']['codename'].eql?('lucid') +# return true +#end + +include_recipe "yum-atomic" + +class Chef::Recipe + include OssecCore +end + + +# Run this recipe if the node is an agent. Since the ossec::agent recipe is +# added to the base role, ossec-servers will run it as well, making this check +# necessary +if not node["ossec"]["agent"]["enable"] + # return will exit this recipe + # and continue the chef provisioning + Chef::Log.info("OSSEC: agent is not enabled on this node") + return true +end + +# Search for the ossec server, and do nothing if there's none +ossec_server = search(:node, + "role:ossec-server " \ + "AND chef_environment:#{node.chef_environment}" + ).first +if ossec_server.nil? + Chef::Log.info("OSSEC: No ossec server available. Agent will not be provisionned") + return true +end + +# install the agent package +package "ossec-hids-client" + +# define the agent parameters +agent_hash = ossec_agent_create_parameters(node, ossec_server) + +# check for the agent configuration on the server. if the server has none, do +# not continue the provisioning. If the server has a configuration for this +# agent, store the parameters on the node and continue +if ossec_verify_agent(agent_hash, ossec_server) + node.normal["ossec"]["agents"][agent_hash[:id]] = ossec_server["ossec"]["agents"][agent_hash[:id]].to_hash +else + Chef::Log.info("OSSEC: this agent is unknown on the ossec server") + return true +end + +# Make sure that the server prepared a key for us +unless ossec_agent_has_valid_key?(agent_hash, ossec_server) + Chef::Log.info("OSSEC: Server doesn't have a valid key for agent.") + return true +end + +service "ossec-agent" do + #provider Chef::Provider::Service::Init + service_name node["ossec"]["client"]["service_name"] + supports :start => true, :stop => true, :restart => true, :status => true + action [ :start ] + only_if "test -e /var/ossec/etc/ossec.conf && test -e /var/ossec/etc/client.keys" +end + +# Get the IP of the ossec server +ossec_server_ip = ossec_server[:network][:lanip] || ossec_server.ipaddress + +# Expand the local flags from node attributes +ossec_set_filtered_flags!("command", "active-response", "syslog_files") +ossec_set_syscheck_flags!("ignore") + +template "/var/ossec/etc/ossec.conf" do + source "ossec-agent.conf.erb" + owner "ossec" + group "ossec" + variables("ossec_server_ip" => ossec_server_ip ) + manage_symlink_source true + notifies :restart, "service[ossec-agent]" +end + +# If client.keys is modified, ask for a queue rid on the server +template "/var/ossec/etc/client.keys" do + mode 0440 + owner "root" + group "ossec" + notifies :create, "ruby_block[set-rid-flag]" + notifies :restart, "service[ossec-agent]" +end + +# "set-rid-flag" is not run by default, but called when the agent's key +# is modified (or created) +ruby_block "set-rid-flag" do + block do + # if the server side rid flag is not set to "done", + # request a queue rid by setting the agent side flag to "todo" + if ossec_server["ossec"]["agents"][agent_hash[:id]]["rid"].eql?("none") + node.normal["ossec"]["agents"][agent_hash[:id]]["rid"] = "todo" + Chef::Log.info "Setting Queue Rid Flag on" + end + end + action :nothing +end + +# unset rid flag if necessary, check that at every run +if node["ossec"]["agents"][agent_hash[:id]]["rid"].eql?("todo") \ + and ossec_server["ossec"]["agents"][agent_hash[:id]]["rid"].eql?("done") + ruby_block "unset rid flag" do + block do + node.normal["ossec"]["agents"][agent_hash[:id]]["rid"] = "none" + Chef::Log.info "Setting Queue Rid Flag off" + end + notifies :restart, "service[ossec-agent]" + end +end diff --git a/recipes/default.rb b/recipes/default.rb new file mode 100644 index 0000000..8a8dae2 --- /dev/null +++ b/recipes/default.rb @@ -0,0 +1,6 @@ +# +# Cookbook Name:: ossec +# Recipe:: default +# +include_recipe "ossec::agent" + diff --git a/recipes/server.rb b/recipes/server.rb new file mode 100644 index 0000000..d671ce8 --- /dev/null +++ b/recipes/server.rb @@ -0,0 +1,181 @@ +# Ossec server provisioning recipe +# install the ossec-hids-server package and push the +# default configuration from the templates + +include_recipe "yum-atomic" + +class Chef::Recipe + include OssecCore +end + + +package "ossec-hids-server" + +# Get all the agents at once, more efficient +if Chef::Config[:solo] + Chef::Log.warn('This recipe uses search. Chef Solo does not support search') +else + ossec_agents = search(:node, + "roles:ossec-agent "\ + "AND chef_environment:#{node.chef_environment}") + + # set local command/active-response flags + ossec_set_filtered_flags!("command", "active-response", "syslog_files") + ossec_set_syscheck_flags!("ignore") + + # resolve searches in server rules + ossec_hostname_search() + + # resolve email_alerts location searches + ossec_event_location_search() + + # initialize the agent hash on the first run + if node["ossec"]["agents"].nil? + node.normal["ossec"]["agents"] = {} + end + + ossec_agents.each do |agent| + # don't process thy self + if agent["ipaddress"] == node["ipaddress"] + next + end + + agent_hash = ossec_agent_create_parameters(agent, node) + + # this agent is running fine, go to the next one + if ossec_agent_is_active?(agent_hash[:id]) + node.normal["ossec"]["agents"][agent_hash[:id]]["status"] = "active" + next + end + + # check that the agent ID still point to the same IP and hostname + # otherwise, delete the record from the ossec server + if not ossec_verify_agent(agent_hash, node) + Chef::Log.info("OSSEC: deleting server record for agent '#{agent_hash[:id]}'") + node.normal["ossec"]["agents"].delete(agent_hash[:id]) + end + + # if this agent doesn't have a valid key, generate one + if not ossec_agent_has_valid_key?(agent_hash, node) + Chef::Log.info("OSSEC: agent '#{agent_hash[:id]}' needs a key. Generating one.") + agent_hash[:key] = ossec_generate_agent_key(agent_hash) + agent_hash[:rid] = "todo" + agent_hash[:status] = "key_exists" + end + + # save agent parameters + agent_hash.each do |k,v| + node.normal["ossec"]["agents"][agent_hash[:id]][k] = v + end + + # Don't continue if the agent has a valid key but doesn't know it yet + if not ossec_agent_knows_key?(agent_hash, agent) + Chef::Log.info("OSSEC: agent '#{agent_hash[:id]}' didn't pick up its key yet.") + next + end + + # Check if it needs a queue cleanup + if ossec_agent_needs_rid?(agent_hash[:id], agent) + ruby_block "ossec queue rid" do + block do + if File.exists?("/var/ossec/queue/rids/#{agent_hash[:id]}") + File.delete("/var/ossec/queue/rids/#{agent_hash[:id]}") + Chef::Log.info("OSSEC: deleted queue for agent '#{agent_hash[:id]}'") + else + Chef::Log.info("OSSEC: No queue for agent '#{agent_hash[:id]}.'") + end + node.normal["ossec"]["agents"][agent_hash[:id]]["rid"] = "done" + end + notifies :restart, "service[ossec-server]", :delayed + end + # done with this agent, go to the next one + next + end + + # If after all that, the agent is still not active, mark it as so + if not ossec_agent_is_active?(agent_hash[:id]) + if ossec_agent_is_zombie?(agent_hash[:id]) + node.normal["ossec"]["agents"][agent_hash[:id]]["status"] = "zombie" + # if the agent is a zombie, perform a rid of its queue on the next run + Chef::Log.info("OSSEC: agent #{agent_hash[:id]} is a zombie. " + + "Request queue deletion") + node.normal["ossec"]["agents"][agent_hash[:id]]["rid"] = "todo" + else + node.normal["ossec"]["agents"][agent_hash[:id]]["status"] = "disconnected" + Chef::Log.info("OSSEC: agent #{agent_hash[:id]} connection failed. " + + "Performing restart") + #cmd = Chef::ShellOut.new("/var/ossec/bin/agent_control -R #{agent_hash[:id]}") + cmd = Mixlib::ShellOut.new("/var/ossec/bin/agent_control -R #{agent_hash[:id]}") + cmd_ret = cmd.run_command + end + end + end + + # Remove the attributes of an agent from the ossec server if the agent doesn't + # exist on Chef and the last keep_alive is more than 7 days old + node["ossec"]["agents"].each do |agent_id, params| + if params[:status].eql?('key_exists') + next + end + + agent = ossec_agents.select{ |n| (n[:ossec][:agents].has_key?(agent_id) \ + && n[:ossec][:agent][:enable]) + }.first + + if not agent.nil? + next + end + if ossec_agent_should_be_removed?(agent_id) + Chef::Log.info("OSSEC: Removing old agent '#{agent_id}' - '#{params[:name]}'") + node["ossec"]["agents"].delete(agent_id) + else + Chef::Log.info("OSSEC: agent '#{agent_id}' - '#{params[:name]}' is candidate for removal") + node.normal["ossec"]["agents"][agent_id]["status"] = 'candidate_for_removal' + end + end +end + +template "/var/ossec/etc/client.keys" do + mode '0440' + owner "root" + group "ossec" +end + +template "/var/ossec/rules/local_rules.xml" do + owner "root" + group "root" + notifies :restart, "service[ossec-server]", :delayed +end + +template "/var/ossec/etc/local_decoder.xml" do + owner "root" + group "root" + notifies :restart, "service[ossec-server]", :delayed +end + +template "/var/ossec/etc/ossec.conf" do + source "ossec-server.conf.erb" + owner "ossec" + group "ossec" + manage_symlink_source true + variables( + :ossec_agents => ossec_agents + ) + notifies :restart, "service[ossec-server]", :delayed +end + +template "/var/ossec/etc/internal_options.conf" do + mode "0444" + owner "root" + group "root" + notifies :restart, "service[ossec-server]", :delayed +end + +service "ossec-server" do + #provider Chef::Provider::Service::Init + service_name node["ossec"]["server"]["service_name"] + supports :start => true, :stop => true, :restart => true, :status => true + action [ :start ] + only_if { File.exist?("/var/ossec/etc/ossec.conf") } +end + diff --git a/recipes/supervisor_logs.rb b/recipes/supervisor_logs.rb new file mode 100644 index 0000000..8e5286d --- /dev/null +++ b/recipes/supervisor_logs.rb @@ -0,0 +1,36 @@ +# This recipe will list the log file created by supervisor +# and add the necessary attributes to have those monitored +# by the local ossec-agent + +# cleanup, before recreation +node["ossec"]["local_syslog_files"].each do |logfile, params| + if logfile =~ /supervisor/ + node["ossec"]["local_syslog_files"].delete(logfile) + end +end + +# each program run by supervisor has a set of logfiles +node["supervisor"]["programs"].each do |program_name, config| + total_procs = config["numprocs"] || 1 + # each process of a program has its own log file + 0.upto(total_procs.to_i - 1) do |numproc| + logfile = "#{node["supervisor"]["log_path"]}/#{program_name}_#{numproc}_stdout.log" + # add the stdout supervisor log file + node.normal["ossec"]["local_syslog_files"][logfile] = { + 'apply_to' => "fqdn:#{node['fqdn']}", + 'log_format' => 'syslog', + 'use_here' => 'true' + } + log("Ossec::Supervisor: Adding '#{logfile}' to monitored log files") + + logfile = "#{node["supervisor"]["log_path"]}/#{program_name}_#{numproc}_stderr.log" + # add the stderr supervisor log file + node.normal["ossec"]["local_syslog_files"][logfile] = { + 'apply_to' => "fqdn:#{node['fqdn']}", + 'log_format' => 'syslog', + 'use_here' => 'true' + } + log("Ossec::Supervisor: Adding '#{logfile}' to monitored log files") + + end +end diff --git a/templates/default/client.keys.erb b/templates/default/client.keys.erb new file mode 100644 index 0000000..c610d40 --- /dev/null +++ b/templates/default/client.keys.erb @@ -0,0 +1,7 @@ +<% if not node["ossec"]["agents"].nil? + node["ossec"]["agents"].sort.each do |ip,fields| + if not fields[:key].nil? %> +<%= fields[:id] %> <%= fields[:name] %> <%= fields[:ip] %> <%= fields[:key] %> +<% end + end + end -%> diff --git a/templates/default/internal_options.conf.erb b/templates/default/internal_options.conf.erb new file mode 100644 index 0000000..2e5e138 --- /dev/null +++ b/templates/default/internal_options.conf.erb @@ -0,0 +1,115 @@ +# internal_options.conf, Daniel B. Cid (dcid @ ossec.net). +# +# DO NOT TOUCH THIS FILE. The default configuration +# is at ossec.conf. More information at: +# http://www.ossec.net/en/manual.html +# +# This file should be handled with care. It contain +# run time modifications that can affect the use +# of ossec. Only change it if you know what you +# are doing. Again, look first at ossec.conf +# for most of the things you want to change. + + +# Analysisd default rule timeframe. +analysisd.default_timeframe=<%= node["ossec"]["internal"]["analysisd"]["default_timeframe"] %> +# Analysisd stats maximum diff. +analysisd.stats_maxdiff=<%= node["ossec"]["internal"]["analysisd"]["stats_maxdiff"] %> +# Analysisd stats minimum diff. +analysisd.stats_mindiff=<%= node["ossec"]["internal"]["analysisd"]["stats_mindiff"] %> +# Analysisd stats percentage (how much to differ from average) +analysisd.stats_percent_diff=<%= node["ossec"]["internal"]["analysisd"]["stats_percent_diff"] %> +# Analysisd FTS list size. +analysisd.fts_list_size=<%= node["ossec"]["internal"]["analysisd"]["fts_list_size"] %> +# Analysisd FTS minimum string size. +analysisd.fts_min_size_for_str=<%= node["ossec"]["internal"]["analysisd"]["fts_min_size_for_str"] %> +# Analysisd Enable the firewall log (at logs/firewall/firewall.log) +# 1 to enable, 0 to disable. +analysisd.log_fw=<%= node["ossec"]["internal"]["analysisd"]["log_fw"] %> + + +# Logcollector file loop timeout (check every 2 seconds for file changes) +logcollector.loop_timeout=<%= node["ossec"]["internal"]["logcollector"]["loop_timeout"] %> + +# Logcollector number of attempts to open a log file. +logcollector.open_attempts=<%= node["ossec"]["internal"]["logcollector"]["open_attempts"] %> + +# Logcollector: Allow the agents to run commands as defined in agent.conf +logcollector.remote_commands=<%= node["ossec"]["internal"]["logcollector"]["remote_commands"] %> + + +# Remoted counter io flush. +remoted.recv_counter_flush=<%= node["ossec"]["internal"]["remoted"]["recv_counter_flush"] %> + +# Remoted compression averages printout. +remoted.comp_average_printout=<%= node["ossec"]["internal"]["remoted"]["comp_average_printout"] %> + +# Verify msg id (set to 0 to disable it) +remoted.verify_msg_id=<%= node["ossec"]["internal"]["remoted"]["verify_msg_id"] %> + + +# Maild strict checking (0=disabled, 1=enabled) +maild.strict_checking=<%= node["ossec"]["internal"]["maild"]["strict_checking"] %> + +# Maild grouping (0=disabled, 1=enabled) +# Groups alerts within the same e-mail. +maild.groupping=<%= node["ossec"]["internal"]["maild"]["groupping"] %> + +# Maild full subject (0=disabled, 1=enabled) +maild.full_subject=<%= node["ossec"]["internal"]["maild"]["full_subject"] %> + +# Maild GeoIP support +maild.geoip=<%= node["ossec"]["internal"]["maild"]["geoip"] %> + + +# Monitord day_wait. Ammount of seconds to wait before compressing/signing +# the files. +monitord.day_wait=10<%= node["ossec"]["internal"]["monitord"]["day_wait"] %> + +# Monitord compress. (0=do not compress, 1=compress) +monitord.compress=<%= node["ossec"]["internal"]["monitord"]["compress"] %> + +# Monitord sign. (0=do not sign, 1=sign) +monitord.sign=<%= node["ossec"]["internal"]["monitord"]["sign"] %> + +# Monitord monitor_agents. (0=do not monitor, 1=monitor) +monitord.monitor_agents=<%= node["ossec"]["internal"]["monitord"]["monitor_agents"] %> + + +# Syscheck checking/usage speed. To avoid large cpu/memory +# usage, you can specify how much to sleep after generating +# the checksum of X files. The default is to sleep 2 seconds +# after reading 15 files. +syscheck.sleep=<%= node["ossec"]["internal"]["syscheck"]["sleep"] %> +syscheck.sleep_after=<%= node["ossec"]["internal"]["syscheck"]["sleep_after"] %> + + +# Database - maximum number of reconnect attempts +dbd.reconnect_attempts=<%= node["ossec"]["internal"]["dbd"]["reconnect_attempts"] %> + + +# Debug options. +# Debug 0 -> no debug +# Debug 1 -> first level of debug +# Debug 2 -> full debugging + +# Windows debug (used by the windows agent) +windows.debug=<%= node["ossec"]["internal"]["window"]["debug"] %> + +# Syscheck (local, server and unix agent) +syscheck.debug=<%= node["ossec"]["internal"]["syscheck"]["debug"] %> + +# Remoted (server debug) +remoted.debug=<%= node["ossec"]["internal"]["remoted"]["debug"] %> + +# Analysisd (server or local) +analysisd.debug=<%= node["ossec"]["internal"]["analysisd"]["debug"] %> + +# Log collector (server, local or unix agent) +logcollector.debug=<%= node["ossec"]["internal"]["logcollector"]["debug"] %> + +# Unix agentd +agent.debug=<%= node["ossec"]["internal"]["agent"]["debug"] %> + + +# EOF diff --git a/templates/default/local_decoder.xml.erb b/templates/default/local_decoder.xml.erb new file mode 100644 index 0000000..3f902b8 --- /dev/null +++ b/templates/default/local_decoder.xml.erb @@ -0,0 +1,21 @@ +<% node["ossec"]["decoders"].sort_by{|k,v| k.to_i}.each do |id,params| + if not params.nil? -%> + +<% params.sort.each do |key, value| + unless key.eql?('name') + if params[key].is_a?(Hash) + if key.eql?('prematch') or key.eql?('regex') + if params[key].has_key?('offset') -%> + <<%= key %> offset="<%= params[key][:offset] %>"><%= params[key][:parser] %>> +<% else -%> + <<%= key %>><%= params[key][:parser] %>> +<% end + end + else -%> + <<%= key %>><%= value %>> + <% end + end + end -%> + +<% end + end -%> diff --git a/templates/default/local_rules.xml.erb b/templates/default/local_rules.xml.erb new file mode 100644 index 0000000..177133c --- /dev/null +++ b/templates/default/local_rules.xml.erb @@ -0,0 +1,30 @@ + +<% node["ossec"]["rules"].sort_by{|k,v| k.to_i}.each do |id,params| + headers = "" + if params.has_key?('head') + params[:head].sort.each do |key, value| + headers += " " + key + "=\"" + value + "\"" + end + end -%> + > +<% if params.has_key?('body') + params[:body].sort.each do |key, value| + if not key.eql?('hostname_search') -%> + <<%= key %>><%= value %>> +<% + end + end + end + if params.has_key?('tags') + params[:tags].sort.each do |tag| -%> + <<%= tag %> /> +<% end + end + if params.has_key?('info') + params[:info].sort.each do |key, value| -%> + <%= value %> +<% end + end -%> + +<% end -%> + diff --git a/templates/default/ossec-agent.conf.erb b/templates/default/ossec-agent.conf.erb new file mode 100644 index 0000000..0be2d94 --- /dev/null +++ b/templates/default/ossec-agent.conf.erb @@ -0,0 +1,91 @@ + + + + <%= @ossec_server_ip %> + + + + + <%= node["ossec"]["syscheck"]["frequency"] %> + + +<% node["ossec"]["syscheck"]["directories"].sort_by {|k,v| k}.each do |directory,params| -%> + <%= directory %> +<% end -%> + + <%= node["ossec"]["syscheck"]["alert_new_files"] %> + <%= node["ossec"]["syscheck"]["auto_ignore"] %> + + +<% unless node["ossec"]["syscheck"]["ignore"].nil? + node["ossec"]["syscheck"]["ignore"].sort_by {|k,v|}.each do |path,params| + if params["use_here"] == true + type = params["type"] || "simple" + if type == "simple" -%> + <%= path %> +<% else -%> + <%= path %> +<% end + end + end + end -%> + + + + /var/ossec/etc/shared/rootkit_files.txt + /var/ossec/etc/shared/rootkit_trojans.txt + + + +<% node["ossec"]["command"].each_pair do |command, params| + if params["enabled"] == true && \ + params["use_here"] == true -%> + + <%= command %> +<% params.each_pair do |param, value| + unless (param == 'enabled' || \ + param == 'apply_to' || \ + param == 'use_here') -%> + <<%= param %>><%= value %>> +<% end + end -%> + +<% end + end %> + +<% node["ossec"]["active-response"].each_pair do |command, params| + if params["enabled"] == true && \ + params["use_here"] == true && \ + (node["ossec"]["command"][command]["enabled"] == true && + node["ossec"]["command"][command]["use_here"] == true) -%> + + <%= command %> +<% params.each_pair do |param, value| + unless (param == 'enabled' || \ + param == 'apply_to' || \ + param == 'use_here') -%> + <<%= param %>><%= value %>> +<% end + end -%> + +<% end + end -%> + + +<% node["ossec"]["syslog_files"].sort_by {|k,v| k}.each do |logfile,params| + if params["use_here"] == true + log_format = params["log_format"] || "syslog" -%> + + <%= log_format %> + <%= logfile %> +<% params.each_pair do |param,value| + unless(param == 'log_format' || \ + param == 'apply_to' || + param == 'use_here') -%> + <<%= param %>><%= value %>> +<% end + end -%> + +<% end + end -%> + diff --git a/templates/default/ossec-server.conf.erb b/templates/default/ossec-server.conf.erb new file mode 100644 index 0000000..ff5c1f9 --- /dev/null +++ b/templates/default/ossec-server.conf.erb @@ -0,0 +1,200 @@ + + + + <%= node["ossec"]["email_notification"] %> + <% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%> + <%= recipient %> + <% end -%> + <%= node["ossec"]["smtp_server"] %> + <%= node["ossec"]["email_from"]%> + <%=node["ossec"]["email_maxperhour"]%> + <%=node["ossec"]["memory_size"]%> + <% node["ossec"]["white_list"].sort_by {|k| k}.each do |ip| -%> + <%= ip %> + <% end -%> + + + +<% node["ossec"]["load_rules"].each_pair do |name, value| + if value -%> + <%= name %> +<% end + end -%> + + + + <%= node["ossec"]["remote"]["connection"] %> + <%= node.ipaddress %> + + + + <%= node["ossec"]["syslog_output"]["ip"] %> + <%= node["ossec"]["syslog_output"]["port"] %> + <%= node["ossec"]["syslog_output"]["min_level"] %> + + + + <%= node["ossec"]["log_alert_level"] %> + <%= node["ossec"]["email_alert_level"] %> + + + + authentication_success + srcip + Daily report: Successful logins + <% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%> + <%= recipient %> + <% end -%> + + + + web + Daily report: Web + <% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%> + <%= recipient %> + <% end -%> + + + + Daily report: Level 7 + 7 + level + <% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%> + <%= recipient %> + <% end -%> + + + + Daily report: Level 12 + 12 + level + <% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%> + <%= recipient %> + <% end -%> + + + + syscheck + Daily report: File changes + filename + <% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%> + <%= recipient %> + <% end -%> + + +<% node["ossec"]["email_alerts"].sort_by {|k,v| k}.each do |recipient,params| + locations = [] + if params.has_key?('event_location_tag') + locations = @ossec_agents.select{ + |n| n[:tags].include?( + params[:event_location_tag] + ) + }.map {|n2| n2.network.lanip || '172.172.172.172'} + elsif params.has_key?('resolved_search') + locations = params[:resolved_search] + end + locations.sort_by {|k| k}.each do |location| -%> + + <%= recipient %> + <%= location %> +<% params.sort_by {|k,v| k}.each do |key, value| + unless key =~ /event_location_tag|event_location_search|resolved_search/ + if key.eql?('tags') + value.sort_by {|k| k}.each do |tag| -%> + <<%= tag %> /> +<% end + else -%> + <<%= key %>><%= value %>> +<% end + end + end -%> + +<% end + end -%> + + + + <%= node["ossec"]["syscheck"]["frequency"] %> + + +<% node["ossec"]["syscheck"]["directories"].sort_by {|k,v| k}.each do |directory,params| -%> + <%= directory %> +<% end -%> + + <%= node["ossec"]["syscheck"]["alert_new_files"] %> + <%= node["ossec"]["syscheck"]["auto_ignore"] %> + + +<% unless node["ossec"]["syscheck"]["ignore"].nil? + node["ossec"]["syscheck"]["ignore"].sort_by {|k,v|}.each do |path,params| + if params["use_here"] == true + type = params["type"] || "simple" + if type == "simple" -%> + <%= path %> +<% else -%> + <%= path %> +<% end + end + end + end -%> + + + + + /var/ossec/etc/shared/rootkit_files.txt + /var/ossec/etc/shared/rootkit_trojans.txt + + + +<% node["ossec"]["command"].each_pair do |command, params| + if params["enabled"] == true && \ + params["use_here"] == true -%> + + <%= command %> +<% params.each_pair do |param, value| + unless (param == 'enabled' || \ + param == 'apply_to' || \ + param == 'use_here') -%> + <<%= param %>><%= value %>> +<% end + end -%> + +<% end + end %> + +<% node["ossec"]["active-response"].each_pair do |command, params| + if params["enabled"] == true && \ + params["use_here"] == true && \ + (node["ossec"]["command"][command]["enabled"] == true && + node["ossec"]["command"][command]["use_here"] == true) -%> + + <%= command %> +<% params.each_pair do |param, value| + unless (param == 'enabled' || \ + param == 'apply_to' || \ + param == 'use_here') -%> + <<%= param %>><%= value %>> +<% end + end -%> + +<% end + end -%> + + +<% node["ossec"]["syslog_files"].sort_by {|k,v| k}.each do |logfile,params| + if params["use_here"] == true + log_format = params["log_format"] || "syslog" -%> + + <%= log_format %> + <%= logfile %> +<% params.each_pair do |param,value| + unless(param == 'log_format' || \ + param == 'apply_to' || + param == 'use_here') -%> + <<%= param %>><%= value %>> +<% end + end -%> + +<% end + end -%> +