-
Notifications
You must be signed in to change notification settings - Fork 30
[WIP] Add scap lockdown #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
36ea797
d53d735
2829aca
34e6e1d
1257438
f51d5b4
7a87983
36e070c
d5b4aa2
e15cd10
18e6fdc
407ce0b
bad9946
b9f5b59
296cfd1
64f62b0
142ce15
25c4f01
21af188
31969ec
9a2e5b7
31c9309
8e3ab08
d0d6def
3e82d70
b121e02
1d523cb
397b473
d641a04
4361161
1b6f4b4
bd55a38
622d6b2
203712e
b1d597a
153d82f
a8948be
c17840d
4d5b5f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module LinuxAdmin | ||
class Security | ||
require 'linux_admin/service' | ||
def scap_lockdown | ||
class_list = [SshdConfig, Service, SysctlConf, LimitsConf, Securetty, LoginDefs, | ||
Useradd, AuditRules, Modprobe] | ||
class_list.each { |c| c.new.public_send(:apply_scap_settings) } | ||
AuditRules.new.reload_rules | ||
LinuxAdmin::Service.new("sshd").restart | ||
end | ||
end | ||
end | ||
require 'linux_admin/security/common' | ||
Dir.glob(File.join(File.dirname(__FILE__), "security", "*.rb")).each { |f| require f } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
module LinuxAdmin | ||
class Security | ||
class AuditRules | ||
include LinuxAdmin::Common | ||
include Security::Common | ||
CONF_FILE = "/etc/audit/rules.d/audit.rules" | ||
|
||
SCAP_FILESYSTEM_RULES = [ | ||
["/etc/localtime", "wa", "audit_time_rules"], | ||
["/etc/group", "wa", "audit_account_changes"], | ||
["/etc/passwd", "wa", "audit_account_changes"], | ||
["/etc/gshadow", "wa", "audit_account_changes"], | ||
["/etc/shadow", "wa", "audit_account_changes"], | ||
["/etc/security/opasswd", "wa", "audit_account_changes"], | ||
["/etc/selinux/", "wa", "MAC-policy"], | ||
["/etc/sudoers", "wa", "actions"], | ||
["/sbin/insmod", "x", "modules"], | ||
["/sbin/rmmod", "x", "modules"], | ||
["/sbin/modprobe", "x", "modules"], | ||
["/etc/issue", "wa", "audit_network_modifications"], | ||
["/etc/issue.net", "wa", "audit_network_modifications"], | ||
["/etc/hosts", "wa", "audit_network_modifications"], | ||
["/etc/sysconfig/network", "wa", "audit_network_modifications"] | ||
] | ||
|
||
SCAP_SYSTEM_CALL_RULES = [ | ||
[ | ||
"always", | ||
"exit", | ||
%w(sethostname setdomainname), | ||
{"arch" => "b64"}, | ||
"audit_network_modifications" | ||
], | ||
[ | ||
"always", | ||
"exit", | ||
%w(init_module delete_module), | ||
{"arch" => "b64"}, | ||
"modules" | ||
], | ||
[ | ||
"always", | ||
"exit", | ||
%w(settimeofday clock_settime), | ||
{"arch" => "b64"}, | ||
"audit_time_rules" | ||
], | ||
[ | ||
"always", | ||
"exit", | ||
["adjtimex"], | ||
{"arch" => "b64"}, | ||
"audit_time_rules" | ||
], | ||
[ | ||
"always", | ||
"exit", | ||
%w(creat open openat truncate ftruncate), | ||
{"arch" => "b64", "exit" => "-EACCES", "auid>" => "500", "auid!" => "4294967295"}, | ||
"access" | ||
], | ||
[ | ||
"always", | ||
"exit", | ||
%w(creat open openat truncate ftruncate), | ||
{"arch" => "b64", "exit" => "-EPERM", "auid>" => "500", "auid!" => "4294967295"}, | ||
"access" | ||
] | ||
] | ||
|
||
def apply_scap_settings(filename = CONF_FILE) | ||
set_buffer_size(16_384, filename) | ||
config_text = File.read(filename) | ||
|
||
SCAP_FILESYSTEM_RULES.each do |r| | ||
rule_text = filesystem_rule(*r) | ||
config_text = replace_config_line(rule_text, rule_text, config_text) | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mostly style..but if you make these one-lined it might be cleaner. |
||
|
||
SCAP_SYSTEM_CALL_RULES.each do |r| | ||
rule_text = system_call_rule(*r) | ||
config_text = replace_config_line(rule_text, rule_text, config_text) | ||
end | ||
File.write(filename, config_text) | ||
end | ||
|
||
def filesystem_rule(path, permissions, key_name) | ||
rule = "-w #{path} -p #{permissions}" | ||
key_name ? rule << " -k #{key_name}\n" : rule << "\n" | ||
end | ||
|
||
def system_call_rule(action, filter, calls, fields, key_name) | ||
rule = "-a #{action},#{filter}" | ||
fields.each { |f, v| rule << " -F #{f}=#{v}" } | ||
calls.each { |c| rule << " -S #{c}" } | ||
key_name ? rule << " -k #{key_name}\n" : rule << "\n" | ||
end | ||
|
||
def set_buffer_size(size, filename = CONF_FILE) | ||
config_text = File.read(filename) | ||
new_line = "-b #{size}\n" | ||
new_text = replace_config_line(new_line, /^-b \d+\n/, config_text) | ||
|
||
File.write(filename, new_text) | ||
end | ||
|
||
def reload_rules(filename = CONF_FILE) | ||
run!(cmd(:auditctl), :params => {"-R" => [filename]}) | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
module LinuxAdmin | ||
class Security | ||
module Common | ||
def replace_config_line(new_line, rep_regex, file_text) | ||
new_text = file_text.gsub!(rep_regex, new_line) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you want the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do in this case, because the other version will just return a copy of the original string rather than nil if no substitutions were made. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, wasn't sure if you were aware that it would modify the original. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could do String.new(file_text).gsub!(rep_regex, new_line) because the caller of this method should expect a return, rather than using the passed in object. I think that will lead to less confusion as I'm only using this version because of the return behavior. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although I would still be returning the same string object in the case where there are no substitutions made ... That might be even more confusing. |
||
return new_text if new_text | ||
file_text << new_line | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
module LinuxAdmin | ||
class Security | ||
class LimitsConf | ||
include Security::Common | ||
CONF_FILE = "/etc/security/limits.conf" | ||
|
||
def apply_scap_settings(filename = CONF_FILE) | ||
config_text = File.read(filename) | ||
|
||
new_line = "* hard core 0\n" | ||
config_text = replace_config_line(new_line, /^[^#\n]* core .*\n/, config_text) | ||
|
||
new_line = "* hard maxlogins 10\n" | ||
config_text = replace_config_line(new_line, /^[^#\n]* maxlogins .*\n/, config_text) | ||
|
||
File.write(filename, config_text) | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
module LinuxAdmin | ||
class Security | ||
class LoginDefs | ||
include Security::Common | ||
CONF_FILE = "/etc/login.defs" | ||
|
||
SCAP_SETTINGS = { | ||
"PASS_MIN_DAYS" => 1 | ||
} | ||
|
||
def apply_scap_settings(filename = CONF_FILE) | ||
text = File.read(filename) | ||
SCAP_SETTINGS.each do |k, v| | ||
text = replace_config_line("#{k} #{v}\n", /^#*#{k}.*\n/, text) | ||
end | ||
File.write(filename, text) | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
module LinuxAdmin | ||
class Security | ||
class Modprobe | ||
include Security::Common | ||
CONF_FILE = "/etc/modprobe.d/scap.conf" | ||
SCAP_MODULES = %w(dccp sctp rds tipc) | ||
|
||
def apply_scap_settings(filename = CONF_FILE) | ||
SCAP_MODULES.each { |m| disable_module(m, filename) } | ||
end | ||
|
||
def disable_module(mod_name, filename) | ||
begin | ||
config_text = File.read(filename) | ||
rescue Errno::ENOENT | ||
# Okay if file doesn't exist we will create it | ||
config_text = "" | ||
end | ||
|
||
new_line = "install #{mod_name} /bin/true\n" | ||
new_text = replace_config_line(new_line, /^install #{mod_name}.*\n/, config_text) | ||
File.write(filename, new_text) | ||
end | ||
|
||
def enable_module(mod_name, filename) | ||
begin | ||
config_text = File.read(filename) | ||
rescue Errno::ENOENT | ||
return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't need the return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I take that return out wont we run lines 32 and 33? If the file doesn't exist, we don't want to do that. |
||
end | ||
|
||
new_text = replace_config_line("", /^install #{mod_name}.*\n/, config_text) | ||
File.write(filename, new_text) | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
module LinuxAdmin | ||
class Security | ||
class Securetty | ||
include Security::Common | ||
CONF_FILE = "/etc/securetty" | ||
|
||
def apply_scap_settings(filename = CONF_FILE) | ||
remove_vcs(filename) | ||
end | ||
|
||
def remove_vcs(filename = CONF_FILE) | ||
config_text = File.read(filename) | ||
new_text = replace_config_line("", %r{^vc/\d+\n}, config_text) | ||
|
||
File.write(filename, new_text) | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
module LinuxAdmin | ||
class Security | ||
class Service | ||
require 'linux_admin/service' | ||
|
||
def apply_scap_settings | ||
disable_service("autofs") | ||
disable_service("atd") | ||
end | ||
|
||
private | ||
|
||
def disable_service(service_name) | ||
serv = LinuxAdmin::Service.new(service_name) | ||
serv.stop if serv.running? | ||
serv.disable | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
module LinuxAdmin | ||
class Security | ||
class SshdConfig | ||
require 'linux_admin/service' | ||
include Security::Common | ||
CONF_FILE = "/etc/ssh/sshd_config" | ||
|
||
SCAP_SETTINGS = { | ||
"PermitUserEnvironment" => "no", | ||
"PermitEmptyPasswords" => "no", | ||
"Ciphers" => "aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc", | ||
"ClientAliveInterval" => "900", | ||
"ClientAliveCountMax" => "0" | ||
} | ||
|
||
def apply_scap_settings(filename = CONF_FILE) | ||
config_text = File.read(filename) | ||
SCAP_SETTINGS.each do |k, v| | ||
new_line = "#{k} #{v}\n" | ||
config_text = replace_config_line(new_line, /^#*#{k}.*\n/, config_text) | ||
end | ||
File.write(filename, config_text) | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
module LinuxAdmin | ||
class Security | ||
class SysctlConf | ||
include Security::Common | ||
CONF_FILE = "/etc/sysctl.conf" | ||
|
||
SCAP_SETTINGS = { | ||
"net.ipv4.conf.all.accept_redirects" => 0, | ||
"net.ipv4.conf.all.secure_redirects" => 0, | ||
"net.ipv4.conf.all.log_martians" => 1, | ||
"net.ipv4.conf.default.secure_redirects" => 0, | ||
"net.ipv4.conf.default.accept_redirects" => 0, | ||
"net.ipv4.icmp_echo_ignore_broadcasts" => 1, | ||
"net.ipv4.icmp_ignore_bogus_error_responses" => 1, | ||
"net.ipv4.conf.all.rp_filter" => 1, | ||
"net.ipv6.conf.default.accept_redirects" => 0, | ||
"net.ipv4.conf.default.send_redirects" => 0, | ||
"net.ipv4.conf.all.send_redirects" => 0 | ||
} | ||
|
||
def apply_scap_settings(filename = CONF_FILE) | ||
config_text = File.read(filename) | ||
SCAP_SETTINGS.each do |k, v| | ||
new_line = "#{k} = #{v}\n" | ||
config_text = replace_config_line(new_line, /^[#;]*#{k}.*\n/, config_text) | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ugh, this file will be written over an over...wonder if we can mitigate that/ |
||
File.write(filename, config_text) | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
module LinuxAdmin | ||
class Security | ||
class Useradd | ||
include Security::Common | ||
CONF_FILE = "/etc/default/useradd" | ||
|
||
SCAP_SETTINGS = { | ||
"INACTIVE" => 35, | ||
} | ||
|
||
def apply_scap_settings(filename = CONF_FILE) | ||
config_text = File.read(filename) | ||
SCAP_SETTINGS.each do |k, v| | ||
new_line = "#{k}=#{v}\n" | ||
config_text = replace_config_line(new_line, /^#*#{k}.*\n/, config_text) | ||
end | ||
File.write(filename, config_text) | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
-b 320 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
#CommentedNoOption no | ||
NoOption no |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* soft core 100 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
install good_module /bin/true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
vc/1 | ||
vc/10 | ||
tty1 | ||
hvc1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#PermitUserEnvironment no | ||
#PermitEmptyPasswords yes | ||
ClientAliveInterval 100000 | ||
ClientAliveCountMax 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#data.security.commented.zero = 0 | ||
;data.security.commented.semi.one = 1 | ||
data.security.one = 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
ZERO=0 | ||
#COMMENT_ZERO=0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, this seems strange to me. I'd prefer to see the
[ ]
syntax instead of the %w here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no preference, I did it this was to appease RuboCop.