Initial commit as ossec-ng

This commit is contained in:
Eric Renfro 2016-07-24 16:11:12 -04:00
commit 03cb22c10f
16 changed files with 2088 additions and 0 deletions

20
.gitignore vendored Normal file
View file

@ -0,0 +1,20 @@
*~
*#
.#*
\#*#
.*.sw[a-z]
*.un~
pkg/
# Berkshelf
.vagrant
/cookbooks
Berksfile.lock
# Bundler
Gemfile.lock
bin/*
.bundle/*
.kitchen/
.kitchen.local.yml

8
Berksfile Normal file
View file

@ -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"

691
README.md Normal file
View file

@ -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= <rule id="12345" level="12" frequency="45" timeframe="60">
body= <description>Test Rule</description>
body= <match>Big Error</match>
body= <hostname>server1</hostname>
tags= <same_source_ip />
tags= <same_source_port />
info= <info type="link">http://IjustGotHacked.com</info>
</rule>
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 ```<hostname>``` 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

267
attributes/ossec.rb Normal file
View file

@ -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,
}

266
libraries/core.rb Normal file
View file

@ -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

33
metadata.rb Normal file
View file

@ -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'

116
recipes/agent.rb Normal file
View file

@ -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

6
recipes/default.rb Normal file
View file

@ -0,0 +1,6 @@
#
# Cookbook Name:: ossec
# Recipe:: default
#
include_recipe "ossec::agent"

181
recipes/server.rb Normal file
View file

@ -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

View file

@ -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

View file

@ -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 -%>

View file

@ -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

View file

@ -0,0 +1,21 @@
<% node["ossec"]["decoders"].sort_by{|k,v| k.to_i}.each do |id,params|
if not params.nil? -%>
<decoder name="<%= params[:name] %>">
<% 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] %></<%= key %>>
<% else -%>
<<%= key %>><%= params[key][:parser] %></<%= key %>>
<% end
end
else -%>
<<%= key %>><%= value %></<%= key %>>
<% end
end
end -%>
</decoder>
<% end
end -%>

View file

@ -0,0 +1,30 @@
<group name="local,syslog,">
<% 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 -%>
<rule id="<%= id %>" <%= headers %>>
<% if params.has_key?('body')
params[:body].sort.each do |key, value|
if not key.eql?('hostname_search') -%>
<<%= key %>><%= value %></<%= key %>>
<%
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| -%>
<info type="<%= key %>"><%= value %></info>
<% end
end -%>
</rule>
<% end -%>
</group>

View file

@ -0,0 +1,91 @@
<!-- OSSEC Agent configuration from chef ossec cookbook v.<%= node["ossec"]["version"] %> -->
<ossec_config>
<client>
<server-ip><%= @ossec_server_ip %></server-ip>
</client>
<syscheck>
<!-- Frequency that syscheck is executed -- default every 2 hours -->
<frequency><%= node["ossec"]["syscheck"]["frequency"] %></frequency>
<!-- Directories to check (perform all possible verifications) -->
<% node["ossec"]["syscheck"]["directories"].sort_by {|k,v| k}.each do |directory,params| -%>
<directories check_all="yes" report_changes="<%= params[:report_changes] %>" realtime="<%= params[:realtime] %>"><%= directory %></directories>
<% end -%>
<alert_new_files><%= node["ossec"]["syscheck"]["alert_new_files"] %></alert_new_files>
<auto_ignore><%= node["ossec"]["syscheck"]["auto_ignore"] %></auto_ignore>
<!-- Files/directories to 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" -%>
<ignore><%= path %></ignore>
<% else -%>
<ignore type='sregex'><%= path %></ignore>
<% end
end
end
end -%>
</syscheck>
<rootcheck>
<rootkit_files>/var/ossec/etc/shared/rootkit_files.txt</rootkit_files>
<rootkit_trojans>/var/ossec/etc/shared/rootkit_trojans.txt</rootkit_trojans>
</rootcheck>
<!-- Commands and Active-Responses -->
<% node["ossec"]["command"].each_pair do |command, params|
if params["enabled"] == true && \
params["use_here"] == true -%>
<command>
<name><%= command %></name>
<% params.each_pair do |param, value|
unless (param == 'enabled' || \
param == 'apply_to' || \
param == 'use_here') -%>
<<%= param %>><%= value %></<%= param %>>
<% end
end -%>
</command>
<% 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) -%>
<active-response>
<command><%= command %></command>
<% params.each_pair do |param, value|
unless (param == 'enabled' || \
param == 'apply_to' || \
param == 'use_here') -%>
<<%= param %>><%= value %></<%= param %>>
<% end
end -%>
</active-response>
<% end
end -%>
<!-- Files to monitor (localfiles) -->
<% node["ossec"]["syslog_files"].sort_by {|k,v| k}.each do |logfile,params|
if params["use_here"] == true
log_format = params["log_format"] || "syslog" -%>
<localfile>
<log_format><%= log_format %></log_format>
<location><%= logfile %></location>
<% params.each_pair do |param,value|
unless(param == 'log_format' || \
param == 'apply_to' ||
param == 'use_here') -%>
<<%= param %>><%= value %></<%= param %>>
<% end
end -%>
</localfile>
<% end
end -%>
</ossec_config>

View file

@ -0,0 +1,200 @@
<!-- OSSEC Server configuration from chef ossec cookbook v.<%= node["ossec"]["version"] %> -->
<ossec_config>
<global>
<email_notification><%= node["ossec"]["email_notification"] %></email_notification>
<% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%>
<email_to><%= recipient %></email_to>
<% end -%>
<smtp_server><%= node["ossec"]["smtp_server"] %></smtp_server>
<email_from><%= node["ossec"]["email_from"]%></email_from>
<email_maxperhour><%=node["ossec"]["email_maxperhour"]%></email_maxperhour>
<memory_size><%=node["ossec"]["memory_size"]%></memory_size>
<% node["ossec"]["white_list"].sort_by {|k| k}.each do |ip| -%>
<white_list><%= ip %></white_list>
<% end -%>
</global>
<rules>
<% node["ossec"]["load_rules"].each_pair do |name, value|
if value -%>
<include><%= name %></include>
<% end
end -%>
</rules>
<remote>
<connection><%= node["ossec"]["remote"]["connection"] %></connection>
<local_ip><%= node.ipaddress %></local_ip>
</remote>
<syslog_output>
<server><%= node["ossec"]["syslog_output"]["ip"] %></server>
<port><%= node["ossec"]["syslog_output"]["port"] %></port>
<level><%= node["ossec"]["syslog_output"]["min_level"] %></level>
</syslog_output>
<alerts>
<log_alert_level><%= node["ossec"]["log_alert_level"] %></log_alert_level>
<email_alert_level><%= node["ossec"]["email_alert_level"] %></email_alert_level>
</alerts>
<reports>
<category>authentication_success</category>
<user type="relation">srcip</user>
<title>Daily report: Successful logins</title>
<% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%>
<email_to><%= recipient %></email_to>
<% end -%>
</reports>
<reports>
<category>web</category>
<title>Daily report: Web</title>
<% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%>
<email_to><%= recipient %></email_to>
<% end -%>
</reports>
<reports>
<title>Daily report: Level 7</title>
<level>7</level>
<srcip type="relation">level</srcip>
<% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%>
<email_to><%= recipient %></email_to>
<% end -%>
</reports>
<reports>
<title>Daily report: Level 12</title>
<level>12</level>
<srcip type="relation">level</srcip>
<% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%>
<email_to><%= recipient %></email_to>
<% end -%>
</reports>
<reports>
<category>syscheck</category>
<title>Daily report: File changes</title>
<location type="relation">filename</location>
<% node["ossec"]["email_to"].sort_by {|k| k}.each do |recipient| -%>
<email_to><%= recipient %></email_to>
<% end -%>
</reports>
<% 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| -%>
<email_alerts>
<email_to><%= recipient %></email_to>
<event_location><%= location %></event_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 %></<%= key %>>
<% end
end
end -%>
</email_alerts>
<% end
end -%>
<syscheck>
<!-- Frequency that syscheck is executed -- default every 2 hours -->
<frequency><%= node["ossec"]["syscheck"]["frequency"] %></frequency>
<!-- Directories to check (perform all possible verifications) -->
<% node["ossec"]["syscheck"]["directories"].sort_by {|k,v| k}.each do |directory,params| -%>
<directories check_all="yes" report_changes="<%= params[:report_changes] %>" realtime="<%= params[:realtime] %>"><%= directory %></directories>
<% end -%>
<alert_new_files><%= node["ossec"]["syscheck"]["alert_new_files"] %></alert_new_files>
<auto_ignore><%= node["ossec"]["syscheck"]["auto_ignore"] %></auto_ignore>
<!-- Files/directories to 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" -%>
<ignore><%= path %></ignore>
<% else -%>
<ignore type='sregex'><%= path %></ignore>
<% end
end
end
end -%>
</syscheck>
<rootcheck>
<rootkit_files>/var/ossec/etc/shared/rootkit_files.txt</rootkit_files>
<rootkit_trojans>/var/ossec/etc/shared/rootkit_trojans.txt</rootkit_trojans>
</rootcheck>
<!-- Commands and Active-Responses -->
<% node["ossec"]["command"].each_pair do |command, params|
if params["enabled"] == true && \
params["use_here"] == true -%>
<command>
<name><%= command %></name>
<% params.each_pair do |param, value|
unless (param == 'enabled' || \
param == 'apply_to' || \
param == 'use_here') -%>
<<%= param %>><%= value %></<%= param %>>
<% end
end -%>
</command>
<% 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) -%>
<active-response>
<command><%= command %></command>
<% params.each_pair do |param, value|
unless (param == 'enabled' || \
param == 'apply_to' || \
param == 'use_here') -%>
<<%= param %>><%= value %></<%= param %>>
<% end
end -%>
</active-response>
<% end
end -%>
<!-- Files to monitor (localfiles) -->
<% node["ossec"]["syslog_files"].sort_by {|k,v| k}.each do |logfile,params|
if params["use_here"] == true
log_format = params["log_format"] || "syslog" -%>
<localfile>
<log_format><%= log_format %></log_format>
<location><%= logfile %></location>
<% params.each_pair do |param,value|
unless(param == 'log_format' || \
param == 'apply_to' ||
param == 'use_here') -%>
<<%= param %>><%= value %></<%= param %>>
<% end
end -%>
</localfile>
<% end
end -%>
</ossec_config>