mirror of
https://github.com/inspec/inspec
synced 2024-11-27 07:00:39 +00:00
9b4a276e9f
* adding control statement to add rule in front of string as long as it doesn't already contain rule. Correcting resource name in firewalld from etc_hosts_deny adding tests for both branches of the statement created in firewalld Signed-off-by: Vern Burton <me@vernburton.com> * moving to unless with a start_with Signed-off-by: Vern Burton <me@vernburton.com> * adding documentation that states that it is not needed to add `rule` string Signed-off-by: Vern Burton <me@vernburton.com>
144 lines
4.8 KiB
Ruby
144 lines
4.8 KiB
Ruby
# encoding: utf-8
|
|
# author: Matthew
|
|
|
|
module Inspec::Resources
|
|
class FirewallD < Inspec.resource(1)
|
|
###
|
|
# This recourse assumes that the file sudo vim /etc/polkit-1/rules.d/49-nopasswd_global.rules has been
|
|
# set to allow users in group "wheel" to perform any commands without authentication.
|
|
###
|
|
|
|
name 'firewalld'
|
|
desc 'Use the firewalld resource to check and see if firewalld is configured to grand or deny access to specific hosts or services'
|
|
example "
|
|
describe firewalld do
|
|
it { should be_running }
|
|
its('default_zone') { should eq 'public' }
|
|
it { should have_service_enabled_in_zone('ssh', 'public') }
|
|
it { should have_rule_enabled('rule family=ipv4 source address=192.168.0.14 accept', 'public') }
|
|
end
|
|
|
|
describe firewalld.where { zone == 'public' } do
|
|
its('interfaces') { should cmp ['enp0s3', 'eno2'] }
|
|
its('sources') { should cmp ['ssh', 'icmp'] }
|
|
its('services') { should cmp ['192.168.1.0/24', '192.168.1.2'] }
|
|
end
|
|
"
|
|
|
|
attr_reader :params
|
|
|
|
filter = FilterTable.create
|
|
filter.add_accessor(:where)
|
|
.add_accessor(:entries)
|
|
.add(:zone, field: 'zone')
|
|
.add(:interfaces, field: 'interfaces')
|
|
.add(:sources, field: 'sources')
|
|
.add(:services, field: 'services')
|
|
|
|
filter.connect(self, :params)
|
|
|
|
def initialize
|
|
return skip_resource 'The `firewalld` resource is not supported on your OS.' unless inspec.os.linux?
|
|
@params = parse_active_zones(active_zones)
|
|
end
|
|
|
|
def installed?
|
|
inspec.command('firewall-cmd').exist?
|
|
end
|
|
|
|
def has_zone?(query_zone)
|
|
return false unless installed?
|
|
result = firewalld_command('--get-zones').split(' ')
|
|
result.include?(query_zone)
|
|
end
|
|
|
|
def running?
|
|
return false unless installed?
|
|
result = firewalld_command('--state')
|
|
result =~ /^running/ ? true : false
|
|
end
|
|
|
|
def default_zone
|
|
# return: word associated with the name of the default zone
|
|
# example: 'public'
|
|
firewalld_command('--get-default-zone')
|
|
end
|
|
|
|
def has_service_enabled_in_zone?(query_service, query_zone = default_zone)
|
|
firewalld_command("--zone=#{query_zone} --query-service=#{query_service}") == 'yes'
|
|
end
|
|
|
|
def service_ports_enabled_in_zone(query_service, query_zone = default_zone)
|
|
# return: String of ports open
|
|
# example: ['22/tcp', '4722/tcp']
|
|
firewalld_command("--zone=#{query_zone} --service=#{query_service} --get-ports --permanent").split(' ')
|
|
end
|
|
|
|
def service_protocols_enabled_in_zone(query_service, query_zone = default_zone)
|
|
# return: String of protocoals open
|
|
# example: ['icmp', 'ipv4', 'igmp']
|
|
firewalld_command("--zone=#{query_zone} --service=#{query_service} --get-protocols --permanent").split(' ')
|
|
end
|
|
|
|
def has_port_enabled_in_zone?(query_port, query_zone = default_zone)
|
|
firewalld_command("--zone=#{query_zone} --query-port=#{query_port}") == 'yes'
|
|
end
|
|
|
|
def has_rule_enabled?(rule, query_zone = default_zone)
|
|
rule = "rule #{rule}" unless rule.start_with?('rule')
|
|
firewalld_command("--zone=#{query_zone} --query-rich-rule='#{rule}'") == 'yes'
|
|
end
|
|
|
|
private
|
|
|
|
def active_zones
|
|
# return syntax:
|
|
# [default-zone-name]
|
|
# interfaces: [open interfases]
|
|
#
|
|
# example:
|
|
# public
|
|
# interfaces: enp0s3
|
|
firewalld_command('--get-active-zones')
|
|
end
|
|
|
|
def parse_active_zones(content)
|
|
# Split by every second line, which contains the zone and the interfaces.
|
|
content = content.split(/\n/).each_slice(2).map { |slice| slice.join("\n") }
|
|
content.map do |line|
|
|
parse_line(line)
|
|
end.compact
|
|
end
|
|
|
|
def parse_line(line)
|
|
zone = line.split("\n")[0]
|
|
{
|
|
'zone' => zone,
|
|
'interfaces' => line.split(':')[1].split(' '),
|
|
'services' => services_bound(zone),
|
|
'sources' => sources_bound(zone),
|
|
}
|
|
end
|
|
|
|
def sources_bound(query_zone)
|
|
# result: a list containing either an ip address or ip address with a mask, or a ipset or an ipset with the ipset prefix.
|
|
# example: ['192.168.0.4', '192.168.0.0/16', '2111:DB28:ABC:12::', '2111:db89:ab3d:0112::0/64']
|
|
firewalld_command("--zone=#{query_zone} --list-sources").split(' ')
|
|
end
|
|
|
|
def services_bound(query_zone)
|
|
# result: a list of services bound to a zone.
|
|
# example: ['ssh', 'dhcpv6-client']
|
|
firewalld_command("--zone=#{query_zone} --list-services").split(' ')
|
|
end
|
|
|
|
def firewalld_command(command)
|
|
command = "firewall-cmd #{command}"
|
|
result = inspec.command(command)
|
|
if result.stderr != ''
|
|
return "Error on command #{command}: #{result.stderr}"
|
|
end
|
|
result.stdout.strip
|
|
end
|
|
end
|
|
end
|