diff --git a/docs-chef-io/content/inspec/resources/ipfilter.md b/docs-chef-io/content/inspec/resources/ipfilter.md new file mode 100644 index 000000000..491892817 --- /dev/null +++ b/docs-chef-io/content/inspec/resources/ipfilter.md @@ -0,0 +1,75 @@ ++++ +title = "ipfilter resource" +draft = false +gh_repo = "inspec" +platform = "bsd" +platform = "solaris" + +[menu] + [menu.inspec] + title = "ipfilter" + identifier = "inspec/resources/os/ipfilter.md ipfilter resource" + parent = "inspec/resources/os" ++++ + +Use the `ipfilter` Chef InSpec audit resource to test rules that are defined for `ipfilter`. The `ipfstat` utility is used to report on packet filter statistics and filter list. `ipfstat -io` is used to view the active filtering rule set for the input and the output side of the kernel IP processing. The rule match is done against the output rules of `ipfstat -io`. + +## Availability + +### Installation + +This resource is distributed along with Chef InSpec itself. You can use it automatically. + +## Syntax + +An `ipfilter` resource block declares tests for rules in IP tables: + + describe ipfilter do + it { should have_rule("RULE") } + end + +where + +- `have_rule('RULE')` tests the active rule for ipfilter. This must match the entire line taken from `'ipfstat -io'`. + +## Examples + +The following examples show how to use this Chef InSpec audit resource. + +### Test if there are no restrictions on loopback interface + + describe ipfilter do + it { should have_rule("pass in quick on lo0 all") } + it { should have_rule("pass out quick on lo0 all") } + end + +### Test if there are no restrictions on inside LAN interface named xl0 for private network + + describe ipfilter do + it { should have_rule("pass in quick on xl0 all") } + it { should have_rule("pass out quick on xl0 all") } + end + +### Test if there is a rule allowing FTP traffic on the public interface named dc0 + + describe iptables do + it { should have_rule("pass out quick on dc0 proto tcp from any to any port = ftp flags S/FSRPAU keep state") } + end + +### Test if there is a rule allowing SSH on the public interface named dc0 + + describe iptables do + it { should have_rule("pass out quick on dc0 proto tcp from any to any port = ssh flags S/FSRPAU keep state") } + end + +Note that the rule specification must exactly match what's in the output of `'ipfstat -io'`, which will depend on how you've built your rules. + +## Matchers + +For a full list of available matchers, please visit our [matchers page](/inspec/matchers/). + +### have_rule + +The `have_rule` matcher tests the named rule against the information in the output rule of `'ipftstat -io'`: + + it { should have_rule("RULE") } diff --git a/lib/inspec/resources/ipfilter.rb b/lib/inspec/resources/ipfilter.rb new file mode 100644 index 000000000..1b8e14d43 --- /dev/null +++ b/lib/inspec/resources/ipfilter.rb @@ -0,0 +1,58 @@ +require "inspec/resources/command" +module Inspec::Resources + class IpFilter < Inspec.resource(1) + name "ipfilter" + supports platform: "freebsd" + supports platform: "solaris" + desc "Use the ipfilter InSpec audit resource to test rules that are defined for ipfilter, which maintains the IP rule set" + example <<~EXAMPLE + describe ipfilter do + it { should have_rule("pass in quick on lo0 all") } + end + EXAMPLE + + def initialize + # checks if the instance is either bsd or solaris + return if inspec.os.bsd? || inspec.os.solaris? + + # ensures, all calls are aborted for non-supported os + @ipfilter_cache = [] + skip_resource "The `ipfilter` resource is not supported on your OS yet." + end + + def has_rule?(rule = nil) + # checks if the rule is part of the ruleset + retrieve_rules.any? { |line| line.casecmp(rule) == 0 } + end + + def retrieve_rules + # this would be true if the OS family was not bsd/solaris when checked in initliaze + return @ipfilter_cache if defined?(@ipfilter_cache) + + # construct ipfstat command to read all rules + bin = find_ipfstat_or_error + ipfstat_cmd = "#{bin} -io" + cmd = inspec.command(ipfstat_cmd) + + # Return empty array when command is not executed successfully + # or there is no output since no rules are active + return [] if cmd.exit_status.to_i != 0 || cmd.stdout == "" + + # split rules, returns array or rules + @iptables_cache = cmd.stdout.split("\n").map(&:strip) + end + + def to_s + format("Ipfilter").strip + end + + private + def find_ipfstat_or_error + %w{/usr/sbin/ipfstat /sbin/ipfstat ipfstat}.each do |cmd| + return cmd if inspec.command(cmd).exist? + end + + raise Inspec::Exceptions::ResourceFailed, "Could not find `ipfstat`" + end + end +end diff --git a/test/fixtures/cmd/ipfstat-io b/test/fixtures/cmd/ipfstat-io new file mode 100644 index 000000000..e50befa20 --- /dev/null +++ b/test/fixtures/cmd/ipfstat-io @@ -0,0 +1,35 @@ +pass out quick on lo0 all +pass out quick on xl0 all +pass out quick on dc0 proto tcp from any to any port = http flags S/FSRPAU keep state +pass out quick on dc0 proto tcp from any to any port = https flags S/FSRPAU keep state +pass out quick on dc0 proto tcp from any to any port = pop3 flags S/FSRPAU keep state +pass out quick on dc0 proto tcp from any to any port = smtp flags S/FSRPAU keep state +pass out quick on dc0 proto tcp from any to any port = time flags S/FSRPAU keep state +pass out quick on d0c0 proto tcp from any to any port = ftp flags S/FSRPAU keep state +pass out quick on dc0 proto tcp from any to any port = ssh flags S/FSRPAU keep state +pass out quick on dc0 inet proto icmp from any to any icmp-type echo keep state +block out log first quick on dc0 all +pass in quick on lo0 all +pass in quick on xl0 all +block in quick on dc0 inet from 192.168.0.0/16 to any +block in quick on dc0 inet from 172.16.0.0/12 to any +block in quick on dc0 inet from 10.0.0.0/8 to any +block in quick on dc0 inet from 127.0.0.0/8 to any +block in quick on dc0 inet from 0.0.0.0/8 to any +block in quick on dc0 inet from 169.254.0.0/16 to any +block in quick on dc0 inet from 192.0.2.0/24 to any +block in quick on dc0 inet from 204.152.64.0/23 to any +block in quick on dc0 inet from 224.0.0.0/3 to any +block in quick on dc0 from any to any with frag +block in quick on dc0 proto tcp from any to any with short +block in quick on dc0 inet from any to any with opt lsrr +block in quick on dc0 inet from any to any with opt ssrr +block in log first quick on dc0 proto tcp from any to any flags FPU/FSRPAU +block in quick on dc0 from any to any with ipopts +block in quick on dc0 inet proto icmp from any to any icmp-type echo +block in quick on dc0 proto tcp from any to any port = auth +block in log first quick on dc0 proto tcp/udp from any to any port = netbios-ns +block in log first quick on dc0 proto tcp/udp from any to any port = netbios-dgm +block in log first quick on dc0 proto tcp/udp from any to any port = netbios-ssn +block in log first quick on dc0 proto tcp/udp from any to any port = 81 +block in log first quick on dc0 all \ No newline at end of file diff --git a/test/helpers/mock_loader.rb b/test/helpers/mock_loader.rb index 9604c1db0..9131cf89c 100644 --- a/test/helpers/mock_loader.rb +++ b/test/helpers/mock_loader.rb @@ -372,6 +372,9 @@ class MockLoader # ipnat "/usr/sbin/ipnat -l" => cmd.call("ipnat-l"), %{type "/usr/sbin/ipnat"} => empty.call, + # ipfilter + "/usr/sbin/ipfstat -io" => cmd.call("ipfstat-io"), + %{type "/usr/sbin/ipfstat"} => empty.call, # apache_conf "sh -c 'find /etc/apache2/ports.conf -type f -maxdepth 1'" => cmd.call("find-apache2-ports-conf"), "sh -c 'find /etc/httpd/conf.d/*.conf -type f -maxdepth 1'" => cmd.call("find-httpd-ssl-conf"), diff --git a/test/unit/resources/ipfilter_test.rb b/test/unit/resources/ipfilter_test.rb new file mode 100644 index 000000000..bfc816e7d --- /dev/null +++ b/test/unit/resources/ipfilter_test.rb @@ -0,0 +1,32 @@ +require "helper" +require "inspec/resource" +require "inspec/resources/ipfilter" + +describe "Inspec::Resources::Ipfilter" do + # freebsd11 + it "verify ipfilter on freebsd11" do + resource = MockLoader.new(:freebsd11).load_resource("ipfilter") + _(resource.has_rule?("pass out quick on lo0 all")).must_equal true + _(resource.has_rule?("pass in quick on lo0 all")).must_equal true + _(resource.has_rule?("block in quick on dc0 inet from 10.0.0.0/8 to any")).must_equal true + _(resource.has_rule?(nil)).must_equal false + end + + # solaris11 + it "verify ipfilter on solaris11" do + resource = MockLoader.new(:solaris11).load_resource("ipfilter") + _(resource.has_rule?("pass out quick on lo0 all")).must_equal true + _(resource.has_rule?("pass in quick on lo0 all")).must_equal true + _(resource.has_rule?("block in quick on dc0 inet from 10.0.0.0/8 to any")).must_equal true + _(resource.has_rule?(nil)).must_equal false + end + + # undefined + it "verify ipfilter on unsupported os" do + resource = MockLoader.new(:undefined).load_resource("ipfilter") + _(resource.has_rule?("pass out quick on lo0 all")).must_equal false + _(resource.has_rule?("pass in quick on lo0 all")).must_equal false + _(resource.has_rule?("block in quick on dc0 inet from 10.0.0.0/8 to any")).must_equal false + end + +end