diff --git a/lib/linux_admin.rb b/lib/linux_admin.rb index 3f1a5ac..012265c 100644 --- a/lib/linux_admin.rb +++ b/lib/linux_admin.rb @@ -27,6 +27,7 @@ require 'linux_admin/logical_volume' require 'linux_admin/physical_volume' require 'linux_admin/volume_group' +require 'linux_admin/security' module LinuxAdmin extend Common diff --git a/lib/linux_admin/security.rb b/lib/linux_admin/security.rb new file mode 100644 index 0000000..6d12e0b --- /dev/null +++ b/lib/linux_admin/security.rb @@ -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 } diff --git a/lib/linux_admin/security/audit_rules.rb b/lib/linux_admin/security/audit_rules.rb new file mode 100644 index 0000000..4d43790 --- /dev/null +++ b/lib/linux_admin/security/audit_rules.rb @@ -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 + + 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 diff --git a/lib/linux_admin/security/common.rb b/lib/linux_admin/security/common.rb new file mode 100644 index 0000000..19f03c8 --- /dev/null +++ b/lib/linux_admin/security/common.rb @@ -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) + return new_text if new_text + file_text << new_line + end + end + end +end diff --git a/lib/linux_admin/security/limits_conf.rb b/lib/linux_admin/security/limits_conf.rb new file mode 100644 index 0000000..4c84478 --- /dev/null +++ b/lib/linux_admin/security/limits_conf.rb @@ -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 diff --git a/lib/linux_admin/security/login_defs.rb b/lib/linux_admin/security/login_defs.rb new file mode 100644 index 0000000..5cc3ace --- /dev/null +++ b/lib/linux_admin/security/login_defs.rb @@ -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 diff --git a/lib/linux_admin/security/modprobe.rb b/lib/linux_admin/security/modprobe.rb new file mode 100644 index 0000000..046267b --- /dev/null +++ b/lib/linux_admin/security/modprobe.rb @@ -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 + end + + new_text = replace_config_line("", /^install #{mod_name}.*\n/, config_text) + File.write(filename, new_text) + end + end + end +end diff --git a/lib/linux_admin/security/securetty.rb b/lib/linux_admin/security/securetty.rb new file mode 100644 index 0000000..752ece5 --- /dev/null +++ b/lib/linux_admin/security/securetty.rb @@ -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 diff --git a/lib/linux_admin/security/service.rb b/lib/linux_admin/security/service.rb new file mode 100644 index 0000000..47c4443 --- /dev/null +++ b/lib/linux_admin/security/service.rb @@ -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 diff --git a/lib/linux_admin/security/sshd_config.rb b/lib/linux_admin/security/sshd_config.rb new file mode 100644 index 0000000..326a9e0 --- /dev/null +++ b/lib/linux_admin/security/sshd_config.rb @@ -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 diff --git a/lib/linux_admin/security/sysctl_conf.rb b/lib/linux_admin/security/sysctl_conf.rb new file mode 100644 index 0000000..cefdd99 --- /dev/null +++ b/lib/linux_admin/security/sysctl_conf.rb @@ -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 + File.write(filename, config_text) + end + end + end +end diff --git a/lib/linux_admin/security/useradd.rb b/lib/linux_admin/security/useradd.rb new file mode 100644 index 0000000..3fa79ca --- /dev/null +++ b/lib/linux_admin/security/useradd.rb @@ -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 diff --git a/spec/data/security/audit.rules b/spec/data/security/audit.rules new file mode 100644 index 0000000..915f7b4 --- /dev/null +++ b/spec/data/security/audit.rules @@ -0,0 +1 @@ +-b 320 diff --git a/spec/data/security/common b/spec/data/security/common new file mode 100644 index 0000000..3831a06 --- /dev/null +++ b/spec/data/security/common @@ -0,0 +1,2 @@ +#CommentedNoOption no +NoOption no diff --git a/spec/data/security/limits.conf b/spec/data/security/limits.conf new file mode 100644 index 0000000..4c41144 --- /dev/null +++ b/spec/data/security/limits.conf @@ -0,0 +1 @@ +* soft core 100 diff --git a/spec/data/security/login.defs b/spec/data/security/login.defs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/spec/data/security/login.defs @@ -0,0 +1 @@ + diff --git a/spec/data/security/modprobe_lockdown b/spec/data/security/modprobe_lockdown new file mode 100644 index 0000000..dc66b4c --- /dev/null +++ b/spec/data/security/modprobe_lockdown @@ -0,0 +1 @@ +install good_module /bin/true diff --git a/spec/data/security/securetty b/spec/data/security/securetty new file mode 100644 index 0000000..3d81e73 --- /dev/null +++ b/spec/data/security/securetty @@ -0,0 +1,4 @@ +vc/1 +vc/10 +tty1 +hvc1 diff --git a/spec/data/security/sshd_config b/spec/data/security/sshd_config new file mode 100644 index 0000000..e4d0744 --- /dev/null +++ b/spec/data/security/sshd_config @@ -0,0 +1,4 @@ +#PermitUserEnvironment no +#PermitEmptyPasswords yes +ClientAliveInterval 100000 +ClientAliveCountMax 0 diff --git a/spec/data/security/sysctl.conf b/spec/data/security/sysctl.conf new file mode 100644 index 0000000..8c0ad13 --- /dev/null +++ b/spec/data/security/sysctl.conf @@ -0,0 +1,3 @@ +#data.security.commented.zero = 0 +;data.security.commented.semi.one = 1 +data.security.one = 1 diff --git a/spec/data/security/useradd b/spec/data/security/useradd new file mode 100644 index 0000000..6eb9c48 --- /dev/null +++ b/spec/data/security/useradd @@ -0,0 +1,2 @@ +ZERO=0 +#COMMENT_ZERO=0 diff --git a/spec/security/audit_rules_spec.rb b/spec/security/audit_rules_spec.rb new file mode 100644 index 0000000..b1a346e --- /dev/null +++ b/spec/security/audit_rules_spec.rb @@ -0,0 +1,169 @@ +describe LinuxAdmin::Security::AuditRules do + def test_file_name + File.join(data_file_path("security"), "audit.rules") + end + + def test_file_contents + File.read(test_file_name) + end + + around(:each) do |example| + text = test_file_contents + example.run + File.write(test_file_name, text) + end + + describe "#apply_scap_settings" do + it "sets the buffer size" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^-b 16384\n/) + end + + it "sets filesystem audit_time_rules rules" do + described_class.new.apply_scap_settings(test_file_name) + + pat = %r{^-w /etc/localtime -p wa -k audit_time_rules\n} + expect(test_file_contents).to match(pat) + end + + it "sets filesystem audit_account_changes rules" do + described_class.new.apply_scap_settings(test_file_name) + + pat = %r{^-w /etc/group -p wa -k audit_account_changes\n} + expect(test_file_contents).to match(pat) + pat = %r{^-w /etc/passwd -p wa -k audit_account_changes\n} + expect(test_file_contents).to match(pat) + pat = %r{^-w /etc/gshadow -p wa -k audit_account_changes\n} + expect(test_file_contents).to match(pat) + pat = %r{^-w /etc/shadow -p wa -k audit_account_changes\n} + expect(test_file_contents).to match(pat) + pat = %r{^-w /etc/security/opasswd -p wa -k audit_account_changes\n} + expect(test_file_contents).to match(pat) + end + + it "sets filesystem MAC-policy rules" do + described_class.new.apply_scap_settings(test_file_name) + + pat = %r{^-w /etc/selinux/ -p wa -k MAC-policy\n} + expect(test_file_contents).to match(pat) + end + + it "sets filesystem actions rules" do + described_class.new.apply_scap_settings(test_file_name) + + pat = %r{^-w /etc/sudoers -p wa -k actions\n} + expect(test_file_contents).to match(pat) + end + + it "sets filesystem modules rules" do + described_class.new.apply_scap_settings(test_file_name) + + pat = %r{^-w /sbin/insmod -p x -k modules\n} + expect(test_file_contents).to match(pat) + pat = %r{^-w /sbin/rmmod -p x -k modules\n} + expect(test_file_contents).to match(pat) + pat = %r{^-w /sbin/modprobe -p x -k modules\n} + expect(test_file_contents).to match(pat) + end + + it "sets filesystem audit_network_modifications rules" do + described_class.new.apply_scap_settings(test_file_name) + + pat = %r{^-w /etc/issue -p wa -k audit_network_modifications\n} + expect(test_file_contents).to match(pat) + pat = %r{^-w /etc/issue.net -p wa -k audit_network_modifications\n} + expect(test_file_contents).to match(pat) + pat = %r{^-w /etc/hosts -p wa -k audit_network_modifications\n} + expect(test_file_contents).to match(pat) + pat = %r{^-w /etc/sysconfig/network -p wa -k audit_network_modifications\n} + expect(test_file_contents).to match(pat) + end + + it "sets system call audit_network_modifications rules" do + described_class.new.apply_scap_settings(test_file_name) + + pat = /^-a always,exit -F arch=b64 -S sethostname -S setdomainname -k audit_network_modifications\n/ + expect(test_file_contents).to match(pat) + end + + it "sets system call modules rules" do + described_class.new.apply_scap_settings(test_file_name) + + pat = /^-a always,exit -F arch=b64 -S init_module -S delete_module -k modules\n/ + expect(test_file_contents).to match(pat) + end + + it "sets system call audit_time_rules rules" do + described_class.new.apply_scap_settings(test_file_name) + + pat = /^-a always,exit -F arch=b64 -S settimeofday -S clock_settime -k audit_time_rules\n/ + expect(test_file_contents).to match(pat) + pat = /^-a always,exit -F arch=b64 -S adjtimex -k audit_time_rules\n/ + expect(test_file_contents).to match(pat) + end + + it "sets system call access rules" do + described_class.new.apply_scap_settings(test_file_name) + + pat = /^-a\salways,exit\s-F\sarch=b64\s-F\sexit=-EACCES\s-F\sauid>=500\s + -F\sauid!=4294967295\s-S\screat\s-S\sopen\s-S\sopenat\s-S\s + truncate\s-S\sftruncate\s-k\saccess\n/x + expect(test_file_contents).to match(pat) + pat = /^-a\salways,exit\s-F\sarch=b64\s-F\sexit=-EPERM\s-F\sauid>=500\s + -F\sauid!=4294967295\s-S\screat\s-S\sopen\s-S\sopenat\s-S\s + truncate\s-S\sftruncate\s-k\saccess\n/x + expect(test_file_contents).to match(pat) + end + end + + describe "#filesystem_rule" do + it "creates a correctly formated rule" do + args = ["/etc/localtime", "wa", "audit_time_rules"] + rule_text = "-w /etc/localtime -p wa -k audit_time_rules\n" + rule = described_class.new.filesystem_rule(*args) + expect(rule).to eq(rule_text) + end + + it "creates a rule without a key" do + args = ["/etc/localtime", "wa", nil] + rule = described_class.new.filesystem_rule(*args) + expect(rule).to eq("-w /etc/localtime -p wa\n") + end + end + + describe "#system_call_rule" do + it "creates a correctly formated rule" do + args = [ + "always", + "exit", + ["adjtimex"], + {"arch" => "b64"}, + "audit_time_rules" + ] + rule_text = "-a always,exit -F arch=b64 -S adjtimex -k audit_time_rules\n" + rule = described_class.new.system_call_rule(*args) + expect(rule).to eq(rule_text) + end + + it "creates a rule without a key" do + args = [ + "always", + "exit", + ["adjtimex"], + {"arch" => "b64"}, + nil + ] + rule_text = "-a always,exit -F arch=b64 -S adjtimex\n" + rule = described_class.new.system_call_rule(*args) + expect(rule).to eq(rule_text) + end + end + + describe "#set_buffer_size" do + it "replaces an existing value" do + expect(test_file_contents).to match(/^-b \d+\n/) + described_class.new.set_buffer_size(16_384, test_file_name) + expect(test_file_contents).to match(/^-b 16384\n/) + end + end +end diff --git a/spec/security/common_spec.rb b/spec/security/common_spec.rb new file mode 100644 index 0000000..5eaaa11 --- /dev/null +++ b/spec/security/common_spec.rb @@ -0,0 +1,45 @@ +describe LinuxAdmin::Security::Common do + subject { Class.new { include LinuxAdmin::Security::Common }.new } + + def test_file_name + File.join(data_file_path("security"), "common") + end + + def test_file_contents + File.read(test_file_name) + end + + around(:each) do |example| + text = test_file_contents + example.run + File.write(test_file_name, text) + end + + describe ".replace_config_line" do + it "replaces a matching value" do + matches = /^NoOption no/.match(test_file_contents) + expect(matches.size).to eq(1) + + matches = /^NoOption yes/.match(test_file_contents) + expect(matches).to be_nil + + new_contents = subject.replace_config_line("NoOption yes\n", /^NoOpt.*\n/, test_file_contents) + + matches = /^NoOption yes/.match(new_contents) + expect(matches.size).to eq(1) + + matches = /^NoOption no/.match(new_contents) + expect(matches).to be_nil + end + + it "adds a new line if no value matches" do + matches = /^#*NotHereYet.*/.match(test_file_contents) + expect(matches).to be_nil + + new_contents = subject.replace_config_line("NotHereYet yes", /^NoMatch/, test_file_contents) + + matches = /^NotHereYet yes/.match(new_contents) + expect(matches.size).to eq(1) + end + end +end diff --git a/spec/security/limits_conf_spec.rb b/spec/security/limits_conf_spec.rb new file mode 100644 index 0000000..823ad3a --- /dev/null +++ b/spec/security/limits_conf_spec.rb @@ -0,0 +1,27 @@ +describe LinuxAdmin::Security::LimitsConf do + def test_file_name + File.join(data_file_path("security"), "limits.conf") + end + + def test_file_contents + File.read(test_file_name) + end + + around(:each) do |example| + text = test_file_contents + example.run + File.write(test_file_name, text) + end + + describe "#apply_scap_settings" do + it "prevents process core dumps" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^\* +hard +core +0\n/) + end + + it "sets max logins to 10" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^\* +hard +maxlogins +10\n/) + end + end +end diff --git a/spec/security/login_defs_spec.rb b/spec/security/login_defs_spec.rb new file mode 100644 index 0000000..73e1b26 --- /dev/null +++ b/spec/security/login_defs_spec.rb @@ -0,0 +1,22 @@ +describe LinuxAdmin::Security::LoginDefs do + def test_file_name + File.join(data_file_path("security"), "login.defs") + end + + def test_file_contents + File.read(test_file_name) + end + + around(:each) do |example| + text = test_file_contents + example.run + File.write(test_file_name, text) + end + + describe "#apply_scap_settings" do + it "sets PASS_MIN_DAYS to 1" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^PASS_MIN_DAYS +1\n/) + end + end +end diff --git a/spec/security/modprobe_spec.rb b/spec/security/modprobe_spec.rb new file mode 100644 index 0000000..271c3bf --- /dev/null +++ b/spec/security/modprobe_spec.rb @@ -0,0 +1,79 @@ +describe LinuxAdmin::Security::Modprobe do + def test_file_name + File.join(data_file_path("security"), "modprobe_lockdown") + end + + def new_file_name + File.join(data_file_path("security"), "modprobe_new") + end + + def test_file_contents + File.read(test_file_name) + end + + def new_file_contents + File.read(new_file_name) + end + + around(:each) do |example| + text = test_file_contents + example.run + File.write(test_file_name, text) + end + + describe "#apply_scap_settings" do + it "disables the dccp protocol" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(%r{^install dccp /bin/true\n}) + end + + it "disables the sctp protocol" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(%r{^install sctp /bin/true\n}) + end + + it "disables the rds protocol" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(%r{^install rds /bin/true\n}) + end + + it "disables the tipc protocol" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(%r{^install tipc /bin/true\n}) + end + end + + describe "#disable_module" do + it "adds a module to the file" do + described_class.new.disable_module("test_module", test_file_name) + expect(test_file_contents).to match(%r{^install test_module /bin/true\n}) + end + + context "creates a file" do + after do + FileUtils.rm_f(new_file_name) + end + it "if the given one doesn't exist" do + expect(File.exist?(new_file_name)).to be false + described_class.new.disable_module("test_module", new_file_name) + expect(File.exist?(new_file_name)).to be true + expect(new_file_contents).to match(%r{^install test_module /bin/true\n}) + end + end + end + + describe "#enable_module" do + it "removes a module from the file" do + pat = %r{^install good_module /bin/true\n} + expect(test_file_contents).to match(pat) + described_class.new.enable_module("good_module", test_file_name) + expect(test_file_contents).not_to match(pat) + end + + it "succeeds if the given file doesn't exist" do + expect(File.exist?(new_file_name)).to be false + described_class.new.enable_module("test_module", new_file_name) + expect(File.exist?(new_file_name)).to be false + end + end +end diff --git a/spec/security/securetty_spec.rb b/spec/security/securetty_spec.rb new file mode 100644 index 0000000..d854933 --- /dev/null +++ b/spec/security/securetty_spec.rb @@ -0,0 +1,35 @@ +describe LinuxAdmin::Security::Securetty do + def test_file_name + File.join(data_file_path("security"), "securetty") + end + + def test_file_contents + File.read(test_file_name) + end + + around(:each) do |example| + text = test_file_contents + example.run + File.write(test_file_name, text) + end + + describe "#apply_scap_settings" do + it "removes all vc entries" do + expect(test_file_contents).to match(%r{^vc/\d+}) + + described_class.new.apply_scap_settings(test_file_name) + + expect(test_file_contents).not_to match(%r{^vc/\d+}) + end + + it "does not remove other entries" do + expect(test_file_contents).to match(/^tty\d+/) + expect(test_file_contents).to match(/^hvc\d+/) + + described_class.new.apply_scap_settings(test_file_name) + + expect(test_file_contents).to match(/^tty\d+/) + expect(test_file_contents).to match(/^hvc\d+/) + end + end +end diff --git a/spec/security/sshd_config_spec.rb b/spec/security/sshd_config_spec.rb new file mode 100644 index 0000000..37192f4 --- /dev/null +++ b/spec/security/sshd_config_spec.rb @@ -0,0 +1,49 @@ +describe LinuxAdmin::Security::SshdConfig do + def test_file_name + File.join(data_file_path("security"), "sshd_config") + end + + def test_file_contents + File.read(test_file_name) + end + + around(:each) do |example| + text = test_file_contents + example.run + File.write(test_file_name, text) + end + + describe "#apply_scap_settings" do + it "sets PermitUserEnvironment to no" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^PermitUserEnvironment *no\n/) + end + + it "sets PermitEmptyPasswords to no" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^PermitEmptyPasswords *no\n/) + end + + it "sets Ciphers to strong ciphers" do + described_class.new.apply_scap_settings(test_file_name) + strong_ciphers = ["aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-cbc", + "3des-cbc", "aes192-cbc", "aes256-cbc"] + /Ciphers *([\w,]*)\n/.match(test_file_contents) do |m| + actual_ciphers = m[1].split(",") + actual_ciphers.each do |c| + expect(strong_ciphers).to include(c) + end + end + end + + it "sets ClientAliveInterval to 900" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^ClientAliveInterval *900\n/) + end + + it "sets ClientAliveCountMax to 0" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^ClientAliveCountMax *0\n/) + end + end +end diff --git a/spec/security/sysctl_conf_spec.rb b/spec/security/sysctl_conf_spec.rb new file mode 100644 index 0000000..9bcd971 --- /dev/null +++ b/spec/security/sysctl_conf_spec.rb @@ -0,0 +1,72 @@ +describe LinuxAdmin::Security::SysctlConf do + def test_file_name + File.join(data_file_path("security"), "sysctl.conf") + end + + def test_file_contents + File.read(test_file_name) + end + + around(:each) do |example| + text = test_file_contents + example.run + File.write(test_file_name, text) + end + + describe "#apply_scap_settings" do + it "unsets net.ipv4.conf.all.accept_redirects" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^net.ipv4.conf.all.accept_redirects = 0\n/) + end + + it "unsets net.ipv4.conf.all.secure_redirects" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^net.ipv4.conf.all.secure_redirects = 0\n/) + end + + it "sets net.ipv4.conf.all.log_martians" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^net.ipv4.conf.all.log_martians = 1\n/) + end + + it "unsets net.ipv4.conf.default.secure_redirects" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^net.ipv4.conf.default.secure_redirects = 0\n/) + end + + it "unsets net.ipv4.conf.default.accept_redirects" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^net.ipv4.conf.default.accept_redirects = 0\n/) + end + + it "sets net.ipv4.icmp_echo_ignore_broadcasts" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^net.ipv4.icmp_echo_ignore_broadcasts = 1\n/) + end + + it "sets net.ipv4.icmp_ignore_bogus_error_responses" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^net.ipv4.icmp_ignore_bogus_error_responses = 1\n/) + end + + it "sets net.ipv4.conf.all.rp_filter" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^net.ipv4.conf.all.rp_filter = 1\n/) + end + + it "unsets net.ipv6.conf.default.accept_redirects" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^net.ipv6.conf.default.accept_redirects = 0\n/) + end + + it "unsets net.ipv4.conf.default.send_redirects" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^net.ipv4.conf.default.send_redirects = 0\n/) + end + + it "unsets net.ipv4.conf.all.send_redirects" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^net.ipv4.conf.all.send_redirects = 0\n/) + end + end +end diff --git a/spec/security/useradd_spec.rb b/spec/security/useradd_spec.rb new file mode 100644 index 0000000..980a4be --- /dev/null +++ b/spec/security/useradd_spec.rb @@ -0,0 +1,22 @@ +describe LinuxAdmin::Security::Useradd do + def test_file_name + File.join(data_file_path("security"), "useradd") + end + + def test_file_contents + File.read(test_file_name) + end + + around(:each) do |example| + text = test_file_contents + example.run + File.write(test_file_name, text) + end + + describe "#apply_scap_settings" do + it "sets INACTIVE to 35 days" do + described_class.new.apply_scap_settings(test_file_name) + expect(test_file_contents).to match(/^INACTIVE=35\n/) + end + end +end