Skip to content
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

Add ferm::rule parameters outerface, to_source and to_destination. #111

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion manifests/rule.pp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
# @param daddr The destination address we want to match
# @param proto_options Optional parameters that will be passed to the protocol (for example to match specific ICMP types)
# @param interface an Optional interface where this rule should be applied
# @param outerface an Optional interface via which a packet is going to be sent
# @param to_source Optional new source address of translated packets when using SNAT
# @param to_destination Optional new destination address of translated packets when using DNAT
# @param ensure Set the rule to present or absent
# @param table Select the target table (filter/raw/mangle/nat)
# Default value: filter
Expand All @@ -65,6 +68,9 @@
Optional[Variant[Array, String[1]]] $daddr = undef,
Optional[String[1]] $proto_options = undef,
Optional[String[1]] $interface = undef,
Optional[String[1]] $outerface = undef,
Optional[String[1]] $to_source = undef,
Optional[String[1]] $to_destination = undef,
Enum['absent','present'] $ensure = 'present',
Ferm::Tables $table = 'filter',
){
Expand All @@ -80,6 +86,34 @@
fail('Exactly one of "action" or the deprecated "policy" param is required.')
}

if $action_temp == 'SNAT' and !($chain in ['POSTROUTING', 'INPUT'] and $table == 'nat') {
fail('"SNAT" is only valid in the "POSTROUTING" and "INPUT" chains of the "nat" table.')
}

if $action_temp == 'DNAT' and !($chain in ['PREROUTING', 'OUTPUT'] and $table == 'nat') {
fail('"DNAT" is only valid in the "POSTROUTING" and "OUTPUT" chains of the "nat" table.')
}

if $outerface and !($chain in ['FORWARD', 'OUTPUT', 'POSTROUTING']) {
fail('Outgoing interface can only be set in the "FORWARD", "OUTPUT" and "POSTROUTING" chains.')
} elsif $outerface {
$outerface_real = "outerface ${outerface}"
} else {
$outerface_real = ''
}

if $to_source and $action_temp != 'SNAT' {
robinelfrink marked this conversation as resolved.
Show resolved Hide resolved
fail('Setting new source address is only valid with the "SNAT" action.')
} elsif $to_source and $action_temp == 'SNAT' {
$action_options = "to @ipfilter((${$to_source}))"
} elsif $to_destination and $action_temp != 'DNAT' {
fail('Setting new destination address is only valid with the "DNAT" action.')
} elsif $to_destination and $action_temp == 'DNAT' {
$action_options = "to-destination @ipfilter((${$to_destination}))"
} else {
$action_options = ''
}

if $action_temp in ['RETURN', 'ACCEPT', 'DROP', 'REJECT', 'NOTRACK', 'LOG',
'MARK', 'DNAT', 'SNAT', 'MASQUERADE', 'REDIRECT'] {
$action_real = $action_temp
Expand Down Expand Up @@ -188,7 +222,7 @@
$filename = "${ferm::configdirectory}/chains/${table}-${chain}.conf"
}

$rule = squeeze("${comment_real} ${proto_real} ${proto_options_real} ${dport_real} ${sport_real} ${daddr_real} ${saddr_real} ${action_real};", ' ')
$rule = regsubst(squeeze("${comment_real} ${proto_real} ${proto_options_real} ${dport_real} ${sport_real} ${daddr_real} ${saddr_real} ${outerface_real} ${action_real} ${action_options}", ' '), '\s?$', ';')
if $ensure == 'present' {
if $interface {
unless defined(Concat::Fragment["${chain}-${interface}-aaa"]) {
Expand Down
85 changes: 85 additions & 0 deletions spec/defines/rule_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,54 @@
it { is_expected.to compile.and_raise_error(%r{Cannot specify both policy and action}) }
end

context 'with outerface on input chain' do
let(:title) { 'filter-ssh' }
let :params do
{
chain: 'INPUT',
action: 'ACCEPT',
proto: 'tcp',
dport: '22',
saddr: '127.0.0.1',
outerface: 'eth1'
}
end

it { is_expected.to compile.and_raise_error(%r{Outgoing interface can only be set in the "FORWARD", "OUTPUT" and "POSTROUTING" chains}) }
end

context 'with to_source when action is not SNAT' do
let(:title) { 'snat-ssh' }
let :params do
{
chain: 'POSTROUTING',
action: 'ACCEPT',
proto: 'tcp',
dport: '22',
saddr: '127.0.0.1',
to_source: '192.168.1.1'
}
end

it { is_expected.to compile.and_raise_error(%r{Setting new source address is only valid with the "SNAT" action}) }
end

context 'with to_destination when action is not DNAT' do
let(:title) { 'dnat-ssh' }
let :params do
{
chain: 'PREROUTING',
action: 'ACCEPT',
proto: 'tcp',
dport: '22',
daddr: '172.16.0.1',
to_destination: '192.168.1.1'
}
end

it { is_expected.to compile.and_raise_error(%r{Setting new destination address is only valid with the "DNAT" action}) }
end

context 'without a specific interface using legacy policy param' do
let(:title) { 'filter-ssh' }
let :params do
Expand Down Expand Up @@ -273,6 +321,43 @@
it { is_expected.to contain_concat__fragment('filter-INPUT-config-include') }
it { is_expected.to contain_concat__fragment('filter-SSH-config-include') }
end

context 'source nat with outerface and to_source' do
let(:title) { 'source-nat' }
let :params do
{
chain: 'POSTROUTING',
action: 'SNAT',
proto: 'all',
saddr: '172.16.0.0/24',
outerface: 'eth1',
to_source: '192.168.1.1',
table: 'nat'
}
end

it { is_expected.to compile.with_all_deps }
it { is_expected.to contain_concat__fragment('POSTROUTING-source-nat').with_content("mod comment comment 'source-nat' proto all saddr @ipfilter((172.16.0.0/24)) outerface eth1 SNAT to @ipfilter((192.168.1.1));\n") }
it { is_expected.to contain_concat__fragment('nat-POSTROUTING-config-include') }
end

context 'destination nat with to_destination' do
let(:title) { 'destination-nat' }
let :params do
{
chain: 'PREROUTING',
action: 'DNAT',
proto: 'tcp',
daddr: '172.16.0.1',
to_destination: '192.168.1.1',
table: 'nat'
}
end

it { is_expected.to compile.with_all_deps }
it { is_expected.to contain_concat__fragment('PREROUTING-destination-nat').with_content("mod comment comment 'destination-nat' proto tcp daddr @ipfilter((172.16.0.1)) DNAT to-destination @ipfilter((192.168.1.1));\n") }
it { is_expected.to contain_concat__fragment('nat-PREROUTING-config-include') }
end
end
end
end