aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThore Bödecker <me@foxxx0.de>2020-07-02 15:34:54 +0200
committerGitHub <noreply@github.com>2020-07-02 15:34:54 +0200
commit5929f907fd3e74150f1b84640a80d6fd5472419a (patch)
tree99d83798d646d6eabb901ad72e1e0d6d731cc9b2
parent8d967c7b915fec97846b1d6b567489646b3096a3 (diff)
parent3d1a935519f01a7c5ad23d54d0b997876967dbf3 (diff)
downloadpuppet-ferm-5929f907fd3e74150f1b84640a80d6fd5472419a.tar.gz
puppet-ferm-5929f907fd3e74150f1b84640a80d6fd5472419a.tar.bz2
Merge pull request #115 from voxpupuli/release-v5.0.0
Release v5.0.0
-rw-r--r--CHANGELOG.md27
-rw-r--r--README.md34
-rw-r--r--REFERENCE.md48
-rw-r--r--manifests/rule.pp70
-rw-r--r--metadata.json2
-rw-r--r--spec/acceptance/ferm_spec.rb4
-rw-r--r--spec/defines/rule_spec.rb99
-rw-r--r--spec/type_aliases/actions_spec.rb46
-rw-r--r--spec/type_aliases/policies_spec.rb39
-rw-r--r--spec/type_aliases/port_spec.rb43
-rw-r--r--spec/type_aliases/protocols_spec.rb46
-rw-r--r--spec/type_aliases/tables_spec.rb39
-rw-r--r--types/port.pp13
13 files changed, 445 insertions, 65 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8261ea2..ec974b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file.
Each new release typically also includes the latest modulesync defaults.
These should not affect the functionality of the module.
+## [v5.0.0](https://github.com/voxpupuli/puppet-ferm/tree/v5.0.0) (2020-07-02)
+
+[Full Changelog](https://github.com/voxpupuli/puppet-ferm/compare/v4.0.0...v5.0.0)
+
+**Implemented enhancements:**
+
+- implement proper sport/dport types, validate port ranges, fix some minor regressions [\#114](https://github.com/voxpupuli/puppet-ferm/pull/114) ([foxxx0](https://github.com/foxxx0))
+
+## [v4.0.0](https://github.com/voxpupuli/puppet-ferm/tree/v4.0.0) (2020-06-22)
+
+[Full Changelog](https://github.com/voxpupuli/puppet-ferm/compare/v3.0.1...v4.0.0)
+
+**Breaking changes:**
+
+- implement multiport support for dport/sport / drop string support for sport/dport [\#112](https://github.com/voxpupuli/puppet-ferm/pull/112) ([foxxx0](https://github.com/foxxx0))
+
+**Implemented enhancements:**
+
+- Add Ubuntu 20.04 support [\#109](https://github.com/voxpupuli/puppet-ferm/pull/109) ([bastelfreak](https://github.com/bastelfreak))
+- Allow adding custom ferm dsl for subchains. This is important for usi… [\#105](https://github.com/voxpupuli/puppet-ferm/pull/105) ([rehanone](https://github.com/rehanone))
+- add install\_method parameter [\#104](https://github.com/voxpupuli/puppet-ferm/pull/104) ([Dan33l](https://github.com/Dan33l))
+
+**Closed issues:**
+
+- Unrecognized keyword: @preserve with ubuntu1604 [\#103](https://github.com/voxpupuli/puppet-ferm/issues/103)
+- feature: include custom ferm files? [\#76](https://github.com/voxpupuli/puppet-ferm/issues/76)
+
## [v3.0.1](https://github.com/voxpupuli/puppet-ferm/tree/v3.0.1) (2020-05-06)
[Full Changelog](https://github.com/voxpupuli/puppet-ferm/compare/v3.0.0...v3.0.1)
diff --git a/README.md b/README.md
index 64a9f84..262fe9d 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ You can easily define rules in Puppet (they don't need to be exported resources)
chain => 'INPUT',
policy => 'ACCEPT',
proto => 'tcp',
- dport => '(9092 9093)',
+ dport => [9092, 9093],
saddr => "(${facts['networking']['ip6']}/128 ${facts['networking']['ip']}/32)",
tag => 'allow_kafka_server2server',
}
@@ -97,7 +97,9 @@ ferm::rules:
chain: 'INPUT'
policy: 'ACCEPT'
proto: 'tcp'
- dport: '(80 443)'
+ dport:
+ - 80
+ - 443
saddr: "%{alias('subnets')}"
```
@@ -157,6 +159,34 @@ The second rule will disable connection tracking for all other traffic coming in
This will prevent your conntrack table from overflowing, tracking only the relevant connections and allowing you to use a stateful ruleset.
+#### create a custom chain, e.g. for managing custom FORWARD chain rule for OpenVPN using custom ferm DSL.
+
+```puppet
+$my_rules = @(EOT)
+chain OPENVPN_FORWORD_RULES {
+ proto udp {
+ interface tun0 {
+ outerface enp4s0 {
+ mod conntrack ctstate (NEW) saddr @ipfilter((10.8.0.0/24)) ACCEPT;
+ }
+ }
+ }
+}
+| EOT
+
+ferm::chain{'OPENVPN_FORWORD_RULES':
+ chain => 'OPENVPN_FORWORD_RULES',
+ content => $my_rules,
+}
+
+ferm::rule { "OpenVPN - FORWORD all udp traffic from network 10.8.0.0/24 to subchain OPENVPN_FORWORD_RULES":
+ chain => 'FORWARD',
+ action => 'OPENVPN_FORWORD_RULES',
+ saddr => '10.8.0.0/24',
+ proto => 'udp',
+}
+```
+
## Reference
All parameters are documented within the classes. We generate markdown
diff --git a/REFERENCE.md b/REFERENCE.md
index eef0dc5..821136b 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -267,34 +267,6 @@ ferm::chain{'check-ssh':
}
```
-##### create a custom chain, e.g. for managing custom FORWARD chain rule for OpenVPN using custom ferm DSL.
-
-```puppet
-$my_rules = @(EOT)
-chain OPENVPN_FORWORD_RULES {
- proto udp {
- interface tun0 {
- outerface enp4s0 {
- mod conntrack ctstate (NEW) saddr @ipfilter((10.8.0.0/24)) ACCEPT;
- }
- }
- }
-}
-| EOT
-
-ferm::chain{'OPENVPN_FORWORD_RULES':
- chain => 'OPENVPN_FORWORD_RULES',
- content => $my_rules,
-}
-
-ferm::rule { "OpenVPN - FORWORD all udp traffic from network 10.8.0.0/24 to subchain OPENVPN_FORWORD_RULES":
- chain => 'FORWARD',
- action => 'OPENVPN_FORWORD_RULES',
- saddr => '10.8.0.0/24',
- proto => 'udp',
-}
-```
-
#### Parameters
The following parameters are available in the `ferm::chain` defined type.
@@ -352,7 +324,7 @@ Default value: 'filter'
##### `ip_versions`
-Data type: `Array[Enum['ip','ip6']]`
+Data type: `Array[Enum['ip', 'ip6']]`
Set list of versions of ip we want ot use.
@@ -360,11 +332,11 @@ Default value: $ferm::ip_versions
##### `content`
-Data type: `Optional[String]`
+Data type: `Optional[String[1]]`
-Can only be used for custom chains. It allows you to provide your own ferm rules for this chain. Sets the contents of this custom chain to provided value.
-Default value: undef
+
+Default value: `undef`
### ferm::ipset
@@ -454,7 +426,7 @@ ferm::rule{'incoming-ssh':
chain => 'INPUT',
action => 'SSH',
proto => 'tcp',
- dport => '22',
+ dport => 22,
}
```
@@ -465,7 +437,7 @@ ferm::rule{'allow-ssh-localhost':
chain => 'SSH',
action => 'ACCEPT',
proto => 'tcp',
- dport => '22',
+ dport => 22,
saddr => '127.0.0.1',
}
```
@@ -538,17 +510,17 @@ Default value: `undef`
##### `dport`
-Data type: `Optional[Variant[Stdlib::Port,String[1]]]`
+Data type: `Optional[Variant[Stdlib::Port,Array[Stdlib::Port]]]`
-The destination port, can be a range as string or a single port number as integer
+The destination port, can be a single port number as integer or an Array of integers (which will then use the multiport matcher)
Default value: `undef`
##### `sport`
-Data type: `Optional[Variant[Stdlib::Port,String[1]]]`
+Data type: `Optional[Variant[Stdlib::Port,Array[Stdlib::Port]]]`
-The source port, can be a range as string or a single port number as integer
+The source port, can be a single port number as integer or an Array of integers (which will then use the multiport matcher)
Default value: `undef`
diff --git a/manifests/rule.pp b/manifests/rule.pp
index 1acbfd1..f239402 100644
--- a/manifests/rule.pp
+++ b/manifests/rule.pp
@@ -5,7 +5,7 @@
# chain => 'INPUT',
# action => 'SSH',
# proto => 'tcp',
-# dport => '22',
+# dport => 22,
# }
#
# @example Create a rule in the 'SSH' chain to allow connections from localhost
@@ -13,7 +13,7 @@
# chain => 'SSH',
# action => 'ACCEPT',
# proto => 'tcp',
-# dport => '22',
+# dport => 22,
# saddr => '127.0.0.1',
# }
#
@@ -43,8 +43,8 @@
# @param policy Configure what we want to do with the packet (drop/accept/reject, can also be a target chain name) [DEPRECATED]
# Default value: undef
# Allowed values: (RETURN|ACCEPT|DROP|REJECT|NOTRACK|LOG|MARK|DNAT|SNAT|MASQUERADE|REDIRECT|String[1])
-# @param dport The destination port, can be a range as string or a single port number as integer
-# @param sport The source port, can be a range as string or a single port number as integer
+# @param dport The destination port, can be a single port number as integer or an Array of integers (which will then use the multiport matcher)
+# @param sport The source port, can be a single port number as integer or an Array of integers (which will then use the multiport matcher)
# @param saddr The source address we want to match
# @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)
@@ -59,8 +59,8 @@ define ferm::rule (
String $comment = $name,
Optional[Ferm::Actions] $action = undef,
Optional[Ferm::Policies] $policy = undef,
- Optional[Variant[Stdlib::Port,String[1]]] $dport = undef,
- Optional[Variant[Stdlib::Port,String[1]]] $sport = undef,
+ Optional[Ferm::Port] $dport = undef,
+ Optional[Ferm::Port] $sport = undef,
Optional[Variant[Array, String[1]]] $saddr = undef,
Optional[Variant[Array, String[1]]] $daddr = undef,
Optional[String[1]] $proto_options = undef,
@@ -95,14 +95,60 @@ define ferm::rule (
String => "proto ${proto}",
}
- $dport_real = $dport ? {
- undef => '',
- default => "dport ${dport}",
+
+ if $dport =~ Array {
+ $dports = join($dport, ' ')
+ $dport_real = "mod multiport destination-ports (${dports})"
+ } elsif $dport =~ Integer {
+ $dport_real = "dport ${dport}"
+ } elsif String($dport) =~ /^\d*:\d+$/ {
+ $portrange = split($dport, /:/)
+ $lower = $portrange[0] ? {
+ '' => 0,
+ default => Integer($portrange[0]),
+ }
+ $upper = Integer($portrange[1])
+ assert_type(Tuple[Stdlib::Port, Stdlib::Port], [$lower, $upper]) |$expected, $actual| {
+ fail("The data type should be \'${expected}\', not \'${actual}\'. The data is [${lower}, ${upper}])}.")
+ ''
+ }
+ if $lower > $upper {
+ fail("Lower port number of the port range is larger than upper. ${lower}:${upper}")
+ }
+ $dport_real = "dport ${lower}:${upper}"
+ } elsif String($dport) == '' {
+ $dport_real = ''
+ } else {
+ fail("invalid destination-port: ${dport}")
}
- $sport_real = $sport ? {
- undef => '',
- default => "sport ${sport}",
+
+ if $sport =~ Array {
+ $sports = join($sport, ' ')
+ $sport_real = "mod multiport source-ports (${sports})"
+ } elsif $sport =~ Integer {
+ $sport_real = "sport ${sport}"
+ } elsif String($sport) =~ /^\d*:\d+$/ {
+ $portrange = split($sport, /:/)
+ $lower = $portrange[0] ? {
+ '' => 0,
+ default => Integer($portrange[0]),
+ }
+ $upper = Integer($portrange[1])
+ assert_type(Tuple[Stdlib::Port, Stdlib::Port], [$lower, $upper]) |$expected, $actual| {
+ fail("The data type should be \'${expected}\', not \'${actual}\'. The data is [${lower}, ${upper}])}.")
+ ''
+ }
+ if $lower > $upper {
+ fail("Lower port number of the port range is larger than upper. ${lower}:${upper}")
+ }
+ $sport_real = "sport ${lower}:${upper}"
+ } elsif String($sport) == '' {
+ $sport_real = ''
+ } else {
+ fail("invalid source-port: ${sport}")
}
+
+
if $saddr =~ Array {
assert_type(Array[Stdlib::IP::Address], flatten($saddr)) |$expected, $actual| {
fail( "The data type should be \'${expected}\', not \'${actual}\'. The data is ${flatten($saddr)}." )
diff --git a/metadata.json b/metadata.json
index 6a114b2..ff194cd 100644
--- a/metadata.json
+++ b/metadata.json
@@ -1,6 +1,6 @@
{
"name": "puppet-ferm",
- "version": "3.0.2-rc0",
+ "version": "5.0.0",
"author": "Vox Pupuli",
"summary": "deploy and manage ferm",
"license": "AGPL-3.0",
diff --git a/spec/acceptance/ferm_spec.rb b/spec/acceptance/ferm_spec.rb
index f8f0ef4..eee01fa 100644
--- a/spec/acceptance/ferm_spec.rb
+++ b/spec/acceptance/ferm_spec.rb
@@ -126,14 +126,14 @@ describe 'ferm' do
chain => 'INPUT',
action => 'HTTP',
proto => 'tcp',
- dport => '80',
+ dport => 80,
require => Ferm::Chain['check-http'],
}
ferm::rule { 'allow_http_localhost':
chain => 'HTTP',
action => 'ACCEPT',
proto => 'tcp',
- dport => '80',
+ dport => 80,
saddr => '127.0.0.1',
require => Ferm::Chain['check-http'],
}
diff --git a/spec/defines/rule_spec.rb b/spec/defines/rule_spec.rb
index 5e4ad69..f2601c6 100644
--- a/spec/defines/rule_spec.rb
+++ b/spec/defines/rule_spec.rb
@@ -17,7 +17,7 @@ describe 'ferm::rule', type: :define do
{
chain: 'INPUT',
proto: 'tcp',
- dport: '22',
+ dport: 22,
saddr: '127.0.0.1'
}
end
@@ -33,7 +33,7 @@ describe 'ferm::rule', type: :define do
policy: 'ACCEPT',
action: 'ACCEPT',
proto: 'tcp',
- dport: '22',
+ dport: 22,
saddr: '127.0.0.1'
}
end
@@ -48,7 +48,7 @@ describe 'ferm::rule', type: :define do
chain: 'INPUT',
policy: 'ACCEPT',
proto: 'tcp',
- dport: '22',
+ dport: 22,
saddr: '127.0.0.1'
}
end
@@ -64,7 +64,7 @@ describe 'ferm::rule', type: :define do
chain: 'INPUT',
action: 'ACCEPT',
proto: 'tcp',
- dport: '22',
+ dport: 22,
saddr: '127.0.0.1'
}
end
@@ -83,7 +83,7 @@ describe 'ferm::rule', type: :define do
chain: 'INPUT',
action: 'ACCEPT',
proto: 'tcp',
- dport: '22',
+ dport: 22,
saddr: '127.0.0.1',
interface: 'eth0'
}
@@ -102,7 +102,7 @@ describe 'ferm::rule', type: :define do
chain: 'INPUT',
action: 'ACCEPT',
proto: 'tcp',
- dport: '22',
+ dport: 22,
daddr: ['127.0.0.1', '123.123.123.123', ['10.0.0.1', '10.0.0.2']],
interface: 'eth0'
}
@@ -121,18 +121,97 @@ describe 'ferm::rule', type: :define do
chain: 'INPUT',
action: 'ACCEPT',
proto: %w[tcp udp],
- dport: '(8301 8302)',
+ dport: [8301, 8302],
saddr: '127.0.0.1'
}
end
it { is_expected.to compile.with_all_deps }
- it { is_expected.to contain_concat__fragment('INPUT-filter-consul').with_content("mod comment comment 'filter-consul' proto (tcp udp) dport (8301 8302) saddr @ipfilter((127.0.0.1)) ACCEPT;\n") }
+ it { is_expected.to contain_concat__fragment('INPUT-filter-consul').with_content("mod comment comment 'filter-consul' proto (tcp udp) mod multiport destination-ports (8301 8302) saddr @ipfilter((127.0.0.1)) ACCEPT;\n") }
it { is_expected.to contain_concat__fragment('filter-INPUT-config-include') }
it { is_expected.to contain_concat__fragment('filter-FORWARD-config-include') }
it { is_expected.to contain_concat__fragment('filter-OUTPUT-config-include') }
end
+ context 'with a valid destination-port range' do
+ let(:title) { 'filter-portrange' }
+ let :params do
+ {
+ chain: 'INPUT',
+ action: 'ACCEPT',
+ proto: 'tcp',
+ dport: '20000:25000',
+ saddr: '127.0.0.1'
+ }
+ end
+
+ it { is_expected.to compile.with_all_deps }
+ it { is_expected.to contain_concat__fragment('INPUT-filter-portrange').with_content("mod comment comment 'filter-portrange' proto tcp dport 20000:25000 saddr @ipfilter((127.0.0.1)) ACCEPT;\n") }
+ it { is_expected.to contain_concat__fragment('filter-INPUT-config-include') }
+ it { is_expected.to contain_concat__fragment('filter-FORWARD-config-include') }
+ it { is_expected.to contain_concat__fragment('filter-OUTPUT-config-include') }
+ end
+
+ context 'with a malformed source-port range' do
+ let(:title) { 'filter-malformed-portrange' }
+ let :params do
+ {
+ chain: 'INPUT',
+ action: 'ACCEPT',
+ proto: 'tcp',
+ sport: '25000:20000',
+ saddr: '127.0.0.1'
+ }
+ end
+
+ it { is_expected.to compile.and_raise_error(%r{Lower port number of the port range is larger than upper. 25000:20000}) }
+ end
+
+ context 'with an invalid destination-port range' do
+ let(:title) { 'filter-invalid-portrange' }
+ let :params do
+ {
+ chain: 'INPUT',
+ action: 'ACCEPT',
+ proto: 'tcp',
+ dport: '50000:65538',
+ saddr: '127.0.0.1'
+ }
+ end
+
+ it { is_expected.to compile.and_raise_error(%r{The data type should be 'Tuple\[Stdlib::Port, Stdlib::Port\]', not 'Tuple\[Integer\[50000, 50000\], Integer\[65538, 65538\]\]'. The data is \[50000, 65538\]}) }
+ end
+
+ context 'with an invalid destination-port string' do
+ let(:title) { 'filter-invalid-portnumber' }
+ let :params do
+ {
+ chain: 'INPUT',
+ action: 'ACCEPT',
+ proto: 'tcp',
+ dport: '65538',
+ saddr: '127.0.0.1'
+ }
+ end
+
+ it { is_expected.to compile.and_raise_error(%r{parameter 'dport' expects a Ferm::Port .* value, got String}) }
+ end
+
+ context 'with an invalid source-port number' do
+ let(:title) { 'filter-invalid-portnumber' }
+ let :params do
+ {
+ chain: 'INPUT',
+ action: 'ACCEPT',
+ proto: 'tcp',
+ sport: 65_538,
+ saddr: '127.0.0.1'
+ }
+ end
+
+ it { is_expected.to compile.and_raise_error(%r{parameter 'sport' expects a Ferm::Port .* value, got Integer}) }
+ end
+
context 'with jumping to custom chains' do
# create custom chain
let(:pre_condition) do
@@ -149,7 +228,7 @@ describe 'ferm::rule', type: :define do
chain: 'INPUT',
action: 'SSH',
proto: 'tcp',
- dport: '22'
+ dport: 22
}
end
@@ -184,7 +263,7 @@ describe 'ferm::rule', type: :define do
chain: 'SSH',
action: 'ACCEPT',
proto: 'tcp',
- dport: '22',
+ dport: 22,
saddr: '127.0.0.1'
}
end
diff --git a/spec/type_aliases/actions_spec.rb b/spec/type_aliases/actions_spec.rb
new file mode 100644
index 0000000..9c42e12
--- /dev/null
+++ b/spec/type_aliases/actions_spec.rb
@@ -0,0 +1,46 @@
+# rubocop:disable Style/WordArray, Style/TrailingCommaInLiteral
+require 'spec_helper'
+
+describe 'Ferm::Actions' do
+ describe 'valid values' do
+ [
+ 'RETURN',
+ 'ACCEPT',
+ 'DROP',
+ 'REJECT',
+ 'NOTRACK',
+ 'LOG',
+ 'MARK',
+ 'DNAT',
+ 'SNAT',
+ 'MASQUERADE',
+ 'REDIRECT',
+ 'MYFANCYCUSTOMCHAINNAMEISALSOVALID',
+ ].each do |value|
+ describe value.inspect do
+ it { is_expected.to allow_value(value) }
+ end
+ end
+ end
+
+ describe 'invalid values' do
+ context 'with garbage inputs' do
+ [
+ # :symbol, # this should not match but seems liks String[1] allows it?
+ # nil, # this should not match but seems liks String[1] allows it?
+ '',
+ true,
+ false,
+ ['meep', 'meep'],
+ 65_538,
+ [95_000, 67_000],
+ {},
+ { 'foo' => 'bar' },
+ ].each do |value|
+ describe value.inspect do
+ it { is_expected.not_to allow_value(value) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/type_aliases/policies_spec.rb b/spec/type_aliases/policies_spec.rb
new file mode 100644
index 0000000..bc45423
--- /dev/null
+++ b/spec/type_aliases/policies_spec.rb
@@ -0,0 +1,39 @@
+# rubocop:disable Style/WordArray, Style/TrailingCommaInLiteral
+require 'spec_helper'
+
+describe 'Ferm::Policies' do
+ describe 'valid values' do
+ [
+ 'ACCEPT',
+ 'DROP',
+ ].each do |value|
+ describe value.inspect do
+ it { is_expected.to allow_value(value) }
+ end
+ end
+ end
+
+ describe 'invalid values' do
+ context 'with garbage inputs' do
+ [
+ 'RETURN',
+ 'REJECT',
+ 'foobar',
+ :symbol,
+ nil,
+ '',
+ true,
+ false,
+ ['meep', 'meep'],
+ 65_538,
+ [95_000, 67_000],
+ {},
+ { 'foo' => 'bar' },
+ ].each do |value|
+ describe value.inspect do
+ it { is_expected.not_to allow_value(value) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/type_aliases/port_spec.rb b/spec/type_aliases/port_spec.rb
new file mode 100644
index 0000000..e2b0d43
--- /dev/null
+++ b/spec/type_aliases/port_spec.rb
@@ -0,0 +1,43 @@
+# rubocop:disable Style/WordArray, Style/TrailingCommaInLiteral
+require 'spec_helper'
+
+describe 'Ferm::Port' do
+ describe 'valid values' do
+ [
+ 17,
+ 65_535,
+ '25:30',
+ ':22',
+ [80, 443, 8080, 8443],
+ ].each do |value|
+ describe value.inspect do
+ it { is_expected.to allow_value(value) }
+ end
+ end
+ end
+
+ describe 'invalid values' do
+ context 'with garbage inputs' do
+ [
+ 'asdf',
+ true,
+ false,
+ :symbol,
+ ['meep', 'meep'],
+ 65_538,
+ [95_000, 67_000],
+ '12345',
+ '20:22:23',
+ '1024:',
+ 'ネット',
+ nil,
+ {},
+ { 'foo' => 'bar' },
+ ].each do |value|
+ describe value.inspect do
+ it { is_expected.not_to allow_value(value) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/type_aliases/protocols_spec.rb b/spec/type_aliases/protocols_spec.rb
new file mode 100644
index 0000000..a067b69
--- /dev/null
+++ b/spec/type_aliases/protocols_spec.rb
@@ -0,0 +1,46 @@
+# rubocop:disable Style/WordArray, Style/TrailingCommaInLiteral
+require 'spec_helper'
+
+describe 'Ferm::Protocols' do
+ describe 'valid values' do
+ [
+ 'icmp',
+ 'tcp',
+ 'udp',
+ 'udplite',
+ 'icmpv6',
+ 'esp',
+ 'ah',
+ 'sctp',
+ 'mh',
+ 'all',
+ ['icmp', 'tcp', 'udp'],
+ ].each do |value|
+ describe value.inspect do
+ it { is_expected.to allow_value(value) }
+ end
+ end
+ end
+
+ describe 'invalid values' do
+ context 'with garbage inputs' do
+ [
+ :symbol,
+ nil,
+ 'foobar',
+ '',
+ true,
+ false,
+ ['meep', 'meep'],
+ 65_538,
+ [95_000, 67_000],
+ {},
+ { 'foo' => 'bar' },
+ ].each do |value|
+ describe value.inspect do
+ it { is_expected.not_to allow_value(value) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/type_aliases/tables_spec.rb b/spec/type_aliases/tables_spec.rb
new file mode 100644
index 0000000..eb02877
--- /dev/null
+++ b/spec/type_aliases/tables_spec.rb
@@ -0,0 +1,39 @@
+# rubocop:disable Style/WordArray, Style/TrailingCommaInLiteral
+require 'spec_helper'
+
+describe 'Ferm::Tables' do
+ describe 'valid values' do
+ [
+ 'raw',
+ 'mangle',
+ 'nat',
+ 'filter',
+ ].each do |value|
+ describe value.inspect do
+ it { is_expected.to allow_value(value) }
+ end
+ end
+ end
+
+ describe 'invalid values' do
+ context 'with garbage inputs' do
+ [
+ :symbol,
+ nil,
+ 'foobar',
+ '',
+ true,
+ false,
+ ['meep', 'meep'],
+ 65_538,
+ [95_000, 67_000],
+ {},
+ { 'foo' => 'bar' },
+ ].each do |value|
+ describe value.inspect do
+ it { is_expected.not_to allow_value(value) }
+ end
+ end
+ end
+ end
+end
diff --git a/types/port.pp b/types/port.pp
new file mode 100644
index 0000000..dc2b7e1
--- /dev/null
+++ b/types/port.pp
@@ -0,0 +1,13 @@
+# @summary ferm port-spec
+#
+# allowed variants:
+# -----------------
+# + single Integer port
+# + Array of Integers (creates a multiport matcher)
+# + ferm range port-spec (pair of colon-separated integer, assumes 0 if first is omitted)
+
+type Ferm::Port = Variant[
+ Stdlib::Port,
+ Array[Stdlib::Port],
+ Pattern['^\d*:\d+$'],
+]