modules: shorewall: Add an mgmt module for managing shorewall

This is not perfect, but it's a good start, and it shows how a module
might be structured.
This commit is contained in:
James Shubin
2024-11-18 15:11:31 -05:00
parent 43bd847bad
commit 3bd6986fde
15 changed files with 964 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
#
# Shorewall -- /etc/shorewall/interfaces
#
# For information about entries in this file, type "man shorewall-interfaces"
#
# The manpage is also online at
# https://shorewall.org/manpages/shorewall-interfaces.html
#
?FORMAT 2
###############################################################################
#ZONE INTERFACE OPTIONS

View File

@@ -0,0 +1,10 @@
{{/*
###############################################################################
#ZONE INTERFACE OPTIONS
*/ -}}
{{ if .comment -}}
#
# {{ .comment }}
#
{{ end -}}
{{ .zone }} {{ .interface }} physical={{ .physical }}{{ if .options }},{{ golang_strings_join .options "," }}{{ end }}

View File

@@ -0,0 +1,10 @@
#
# Shorewall -- /etc/shorewall/policy
#
# For information about entries in this file, type "man shorewall-policy"
#
# The manpage is also online at
# https://shorewall.org/manpages/shorewall-policy.html
#
###############################################################################
#SOURCE DEST POLICY LOGLEVEL RATE CONNLIMIT

View File

@@ -0,0 +1,10 @@
{{/*
###############################################################################
#SOURCE DEST POLICY LOGLEVEL RATE CONNLIMIT
*/ -}}
{{ if .comment -}}
#
# {{ .comment }}
#
{{ end -}}
{{ .source }} {{ .dest }} {{ .policy }}{{ if .log_level }} $LOG_LEVEL{{ end }}

View File

@@ -0,0 +1,17 @@
#
# Shorewall -- /etc/shorewall/rules
#
# For information on the settings in this file, type "man shorewall-rules"
#
# The manpage is also online at
# https://shorewall.org/manpages/shorewall-rules.html
#
##############################################################################################################################################################
#ACTION SOURCE DEST PROTO DPORT SPORT ORIGDEST RATE USER MARK CONNLIMIT TIME HEADERS SWITCH HELPER
?SECTION ALL
?SECTION ESTABLISHED
?SECTION RELATED
?SECTION INVALID
?SECTION UNTRACKED
?SECTION NEW

View File

@@ -0,0 +1,12 @@
{{/*
##############################################################################################################################################################
#ACTION SOURCE DEST PROTO DPORT SPORT ORIGDEST RATE USER MARK CONNLIMIT TIME HEADERS SWITCH HELPER
*/ -}}
{{ if .comment -}}
#
# {{ .comment }}
#
{{ end -}}
{{ if .rule -}}
{{ .rule }}
{{ end -}}

View File

@@ -0,0 +1,300 @@
###############################################################################
#
# Shorewall Version 5 -- /etc/shorewall/shorewall.conf
#
# For information about the settings in this file, type "man shorewall.conf"
#
# Manpage also online at https://shorewall.org/manpages/shorewall.conf.html
###############################################################################
# S T A R T U P E N A B L E D
###############################################################################
STARTUP_ENABLED=Yes
###############################################################################
# V E R B O S I T Y
###############################################################################
VERBOSITY=1
###############################################################################
# P A G E R
###############################################################################
PAGER=
###############################################################################
# F I R E W A L L
###############################################################################
FIREWALL=
###############################################################################
# L O G G I N G
###############################################################################
LOG_LEVEL="info"
BLACKLIST_LOG_LEVEL=
INVALID_LOG_LEVEL=
LOG_BACKEND=
LOG_MARTIANS=Yes
LOG_VERBOSITY=2
LOG_ZONE=Both
LOGALLNEW=
LOGFILE=/var/log/messages
LOGFORMAT="%s %s "
LOGTAGONLY=No
LOGLIMIT="s:1/sec:10"
MACLIST_LOG_LEVEL="$LOG_LEVEL"
RELATED_LOG_LEVEL=
RPFILTER_LOG_LEVEL="$LOG_LEVEL"
SFILTER_LOG_LEVEL="$LOG_LEVEL"
SMURF_LOG_LEVEL="$LOG_LEVEL"
STARTUP_LOG=/var/log/shorewall-init.log
TCP_FLAGS_LOG_LEVEL="$LOG_LEVEL"
UNTRACKED_LOG_LEVEL=
###############################################################################
# L O C A T I O N O F F I L E S A N D D I R E C T O R I E S
###############################################################################
ARPTABLES=
CONFIG_PATH=":${CONFDIR}/shorewall:${SHAREDIR}/shorewall"
GEOIPDIR=/usr/share/xt_geoip/LE
IPTABLES=
IP=
IPSET=
LOCKFILE=
MODULESDIR=
NFACCT=
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:/usr/local/sbin"
PERL=/usr/bin/perl
RESTOREFILE=restore
SHOREWALL_SHELL=/bin/sh
SUBSYSLOCK=/var/lock/subsys/shorewall
TC=
###############################################################################
# D E F A U L T A C T I O N S / M A C R O S
###############################################################################
ACCEPT_DEFAULT="none"
BLACKLIST_DEFAULT="Broadcast(DROP),Multicast(DROP),dropNotSyn:$LOG_LEVEL,dropInvalid:$LOG_LEVEL,DropDNSrep:$LOG_LEVEL"
DROP_DEFAULT="Broadcast(DROP),Multicast(DROP)"
NFQUEUE_DEFAULT="none"
QUEUE_DEFAULT="none"
REJECT_DEFAULT="Broadcast(DROP),Multicast(DROP)"
###############################################################################
# R S H / R C P C O M M A N D S
###############################################################################
RCP_COMMAND='scp ${files} ${root}@${system}:${destination}'
RSH_COMMAND='ssh ${root}@${system} ${command}'
###############################################################################
# F I R E W A L L O P T I O N S
###############################################################################
ACCOUNTING=Yes
ACCOUNTING_TABLE=filter
ADD_IP_ALIASES=No
ADD_SNAT_ALIASES=No
ADMINISABSENTMINDED=Yes
AUTOCOMMENT=Yes
AUTOHELPERS=Yes
AUTOMAKE=Yes
BALANCE_PROVIDERS=No
BASIC_FILTERS=No
BLACKLIST="NEW,INVALID,UNTRACKED"
CLAMPMSS=No
CLEAR_TC=Yes
COMPLETE=No
DEFER_DNS_RESOLUTION=Yes
DELETE_THEN_ADD=Yes
DETECT_DNAT_IPADDRS=No
DISABLE_IPV6=No
DOCKER=No
DOCKER_BRIDGE=docker0
DONT_LOAD=
DYNAMIC_BLACKLIST=Yes
EXPAND_POLICIES=Yes
EXPORTMODULES=Yes
FASTACCEPT=No
FORWARD_CLEAR_MARK=
HELPERS=
IGNOREUNKNOWNVARIABLES=No
IMPLICIT_CONTINUE=No
IPSET_WARNINGS=Yes
IP_FORWARDING=Keep
KEEP_RT_TABLES=No
MACLIST_TABLE=filter
MACLIST_TTL=
MANGLE_ENABLED=Yes
MARK_IN_FORWARD_CHAIN=No
MINIUPNPD=No
MULTICAST=No
MUTEX_TIMEOUT=60
NULL_ROUTE_RFC1918=No
OPTIMIZE=All
OPTIMIZE_ACCOUNTING=No
PERL_HASH_SEED=0
REJECT_ACTION=
RENAME_COMBINED=Yes
REQUIRE_INTERFACE=No
RESTART=restart
RESTORE_DEFAULT_ROUTE=Yes
RESTORE_ROUTEMARKS=Yes
RETAIN_ALIASES=No
ROUTE_FILTER=No
SAVE_ARPTABLES=No
SAVE_IPSETS=No
TC_ENABLED=Internal
TC_EXPERT=No
TC_PRIOMAP="2 3 3 3 2 3 1 1 2 2 2 2 2 2 2 2"
TRACK_PROVIDERS=Yes
TRACK_RULES=No
USE_DEFAULT_RT=Yes
USE_NFLOG_SIZE=No
USE_PHYSICAL_NAMES=No
USE_RT_NAMES=No
VERBOSE_MESSAGES=Yes
WARNOLDCAPVERSION=Yes
WORKAROUNDS=No
ZERO_MARKS=No
ZONE2ZONE=-
###############################################################################
# P A C K E T D I S P O S I T I O N
###############################################################################
BLACKLIST_DISPOSITION=DROP
INVALID_DISPOSITION=CONTINUE
MACLIST_DISPOSITION=REJECT
RELATED_DISPOSITION=ACCEPT
RPFILTER_DISPOSITION=DROP
SMURF_DISPOSITION=DROP
SFILTER_DISPOSITION=DROP
TCP_FLAGS_DISPOSITION=DROP
UNTRACKED_DISPOSITION=CONTINUE
################################################################################
# P A C K E T M A R K L A Y O U T
################################################################################
TC_BITS=
PROVIDER_BITS=
PROVIDER_OFFSET=
MASK_BITS=
ZONE_BITS=0

View File

@@ -0,0 +1,10 @@
#
# Shorewall -- /etc/shorewall/snat
#
# For information about entries in this file, type "man shorewall-snat"
#
# See https://shorewall.org/manpages/shorewall-snat.html for more information
#
?FORMAT 2
###################################################################################################################################################
#ACTION SOURCE DEST PROTO DPORT SPORT IPSEC MARK USER SWITCH ORIGDEST PROBABILITY

View File

@@ -0,0 +1,10 @@
{{/*
###################################################################################################################################################
#ACTION SOURCE DEST PROTO DPORT SPORT IPSEC MARK USER SWITCH ORIGDEST PROBABILITY
*/ -}}
{{ if .comment -}}
#
# {{ .comment }}
#
{{ end -}}
{{ .action }} {{ .source }} {{ .dest }}

View File

@@ -0,0 +1,12 @@
{{/*
###############################################################################
#ACTION SOURCE DEST PROTO DPORT SPORT
*/ -}}
{{ if .comment -}}
#
# {{ .comment }}
#
{{ end -}}
{{ if .rule -}}
{{ .rule }}
{{ end -}}

View File

@@ -0,0 +1,13 @@
#
# Shorewall -- /etc/shorewall/stoppedrules
#
# For information about entries in this file, type "man shorewall-stoppedrules"
#
# The manpage is also online at
# https://shorewall.org/manpages/shorewall-stoppedrules.html
#
# See https://shorewall.org/starting_and_stopping_shorewall.htm for additional
# information.
#
###############################################################################
#ACTION SOURCE DEST PROTO DPORT SPORT

View File

@@ -0,0 +1,10 @@
#
# Shorewall -- /etc/shorewall/zones
#
# For information about this file, type "man shorewall-zones"
#
# The manpage is also online at
# https://shorewall.org/manpages/shorewall-zones.html
#
###############################################################################
#ZONE TYPE OPTIONS IN_OPTIONS OUT_OPTIONS

View File

@@ -0,0 +1,10 @@
{{/*
###############################################################################
#ZONE TYPE OPTIONS IN_OPTIONS OUT_OPTIONS
*/ -}}
{{ if .comment -}}
#
# {{ .comment }}
#
{{ end -}}
{{ .name }} {{ .type }}

529
modules/shorewall/main.mcl Normal file
View File

@@ -0,0 +1,529 @@
# Mgmt
# Copyright (C) 2013-2024+ James Shubin and the project contributors
# Written by James Shubin <james@shubin.ca> and the project contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# Additional permission under GNU GPL version 3 section 7
#
# If you modify this program, or any covered work, by linking or combining it
# with embedded mcl code and modules (and that the embedded mcl code and
# modules which link with this program, contain a copy of their source code in
# the authoritative form) containing parts covered by the terms of any other
# license, the licensors of this program grant you additional permission to
# convey the resulting work. Furthermore, the licensors of this program grant
# the original author, James Shubin, additional permission to update this
# additional permission if he deems it necessary to achieve the goals of this
# additional permission.
import "deploy"
import "fmt"
import "golang"
import "local"
import "golang/strings"
# XXX: The templates need a padding function to line up columns.
class firewall() {
pkg "shorewall" {
state => "installed",
Before => File["/etc/shorewall/"],
}
file "/etc/shorewall/" {
state => $const.res.file.state.exists,
recurse => true,
purge => true,
owner => "root",
group => "root",
mode => "u=rwx,go=", # dir
}
file "/etc/shorewall/shorewall.conf" {
state => $const.res.file.state.exists,
content => golang.template(deploy.readfile("/files/shorewall.conf.tmpl")),
owner => "root",
group => "root",
mode => "u=rw,go=",
Notify => Svc["shorewall"],
}
svc "shorewall" {
state => "running",
startup => "enabled",
}
$vardir = local.vardir("shorewall/")
# Add the default fw zone.
include zone("fw", struct{
type => "firewall",
})
include params_base # TODO: Do we need the base file present?
}
class firewall:zone_base() {
file "${vardir}zones.d/" {
state => $const.res.file.state.exists,
recurse => true,
purge => true,
owner => "root",
group => "root",
mode => "u=rwx,go=", # dir
}
file "${vardir}zones.header" {
state => $const.res.file.state.exists,
content => deploy.readfile("/files/zones"), # static, no template!
owner => "root",
group => "root",
mode => "u=rw,go=",
}
file "/etc/shorewall/zones" {
state => $const.res.file.state.exists,
fragments => [
"${vardir}zones.header", # also pull this one file
"${vardir}zones.d/", # pull from this dir
],
owner => "root",
group => "root",
mode => "u=rw,go=",
Notify => Svc["shorewall"],
}
}
# NOTE: the firewall type is added automatically by this module
class firewall:zone($name, $st) {
print "zone: ${name}" {}
# XXX: document why this is named zone_base instead of base:zone_base or
# change the compiler to use the second version?
include zone_base
$type = $st->type || "ipv4"
#$options = [] # TODO: add option validation?
$comment = $st->comment || ""
# TODO: Test type is valid from:
#$valid_types = [
# "bport",
# "bport4",
# "bport6",
# "firewall",
# "ip",
# "ipsec",
# "ipsec4",
# "ipsec6",
# "ipv4",
# "ipv6",
# "local"
# "loopback"
# "vserver",
#]
$tmpl = struct{
name => "${name}",
type => "${type}",
comment => "${comment}",
}
file "${vardir}zones.d/${name}.zone" {
state => $const.res.file.state.exists,
content => golang.template(deploy.readfile("/files/zones.frag.tmpl"), $tmpl),
owner => "root",
group => "root",
mode => "u=rw,go=",
}
}
class firewall:interface_base() {
file "${vardir}interfaces.d/" {
state => $const.res.file.state.exists,
recurse => true,
purge => true,
owner => "root",
group => "root",
mode => "u=rwx,go=", # dir
}
file "${vardir}interfaces.header" {
state => $const.res.file.state.exists,
content => deploy.readfile("/files/interfaces"), # static, no template!
owner => "root",
group => "root",
mode => "u=rw,go=",
}
file "/etc/shorewall/interfaces" {
state => $const.res.file.state.exists,
fragments => [
"${vardir}interfaces.header", # also pull this one file
"${vardir}interfaces.d/", # pull from this dir
],
owner => "root",
group => "root",
mode => "u=rw,go=",
Notify => Svc["shorewall"],
}
}
class firewall:interface($name, $zone, $st) {
print "interface: ${name}" {}
include interface_base
$interface = $st->interface || (strings.to_upper($zone) + "_IF") # eg: NET_IF
$physical = $st->physical || $name
$options []str = $st->options || [] # TODO: add option validation?
$comment = $st->comment || ""
$tmpl = struct{
zone => "${zone}",
interface => "${interface}",
physical => "${physical}",
options => $options,
comment => "${comment}",
}
file "${vardir}interfaces.d/${name}.interface" {
state => $const.res.file.state.exists,
content => golang.template(deploy.readfile("/files/interfaces.frag.tmpl"), $tmpl),
owner => "root",
group => "root",
mode => "u=rw,go=",
}
}
class firewall:policy_base() {
file "${vardir}policy.d/" {
state => $const.res.file.state.exists,
recurse => true,
purge => true,
owner => "root",
group => "root",
mode => "u=rwx,go=", # dir
}
file "${vardir}policy.header" {
state => $const.res.file.state.exists,
content => deploy.readfile("/files/policy"), # static, no template!
owner => "root",
group => "root",
mode => "u=rw,go=",
}
file "/etc/shorewall/policy" {
state => $const.res.file.state.exists,
fragments => [
"${vardir}policy.header", # also pull this one file
"${vardir}policy.d/", # pull from this dir
],
owner => "root",
group => "root",
mode => "u=rw,go=",
Notify => Svc["shorewall"],
}
}
class firewall:policy($name, $st) {
print "policy: ${name}" {}
include policy_base
$source = $st->source
$dest = $st->dest
$policy = $st->policy
$log = $st->log || false
$comment = $st->comment || ""
$tmpl = struct{
source => "${source}",
dest => "${dest}",
policy => "${policy}",
log_level => $log,
comment => "${comment}",
}
file "${vardir}policy.d/${name}.policy" {
state => $const.res.file.state.exists,
content => golang.template(deploy.readfile("/files/policy.frag.tmpl"), $tmpl),
owner => "root",
group => "root",
mode => "u=rw,go=",
}
}
class firewall:rule_base() {
file "${vardir}rules.d/" {
state => $const.res.file.state.exists,
recurse => true,
purge => true,
owner => "root",
group => "root",
mode => "u=rwx,go=", # dir
}
file "${vardir}rules.header" {
state => $const.res.file.state.exists,
content => deploy.readfile("/files/rules"), # static, no template!
owner => "root",
group => "root",
mode => "u=rw,go=",
}
file "/etc/shorewall/rules" {
state => $const.res.file.state.exists,
fragments => [
"${vardir}rules.header", # also pull this one file
"${vardir}rules.d/", # pull from this dir
],
owner => "root",
group => "root",
mode => "u=rw,go=",
Notify => Svc["shorewall"],
}
}
class firewall:rule($name, $st) {
print "rule: ${name}" {}
include rule_base
$rule = $st->rule || "" # entire rule contents OR use the below values
$action = $st->action # REJECT or SSH(ACCEPT) or Ping(DROP)
$source = $st->source # source zone
$source_ips []str = $st->source_ips || []
$dest = $st->dest || "" # dest zone
$dest_ips []str = $st->dest_ips || []
$proto = $st->proto || "" # protocol
# TODO: port doesn't support ranges atm
$port = $st->port || 0
#$sport = $st->sport || 0 # TODO
#$original = $st->original || [] # TODO
$comment = $st->comment || ""
$source_ips_joined = strings.join($source_ips, ",")
$valid_source = if $source_ips_joined == "" {
"${source}"
} else {
"${source}:${source_ips_joined}"
}
$dest_ips_joined = strings.join($dest_ips, ",")
$valid_dest = if $dest_ips_joined == "" {
"${dest}"
} else {
"${dest}:${dest_ips_joined}"
}
$valid_proto = if $proto == "" {
"-"
} else {
"${proto}"
}
# TODO: type switch here if we ever support doing that
$valid_port = if $port == 0 {
"-"
} else {
fmt.printf("%d", $port)
}
# TODO: tabs for beautifying, replace with a padding function eventually.
$full_rule = if $proto == "" and $port == 0 {
"${action}\t${valid_source}\t\t${valid_dest}"
} else {
"${action}\t${valid_source}\t\t${valid_dest}\t\t${valid_proto}\t${valid_port}"
}
$valid_rule = if $rule == "" {
$full_rule
} else {
$rule
}
$tmpl = struct{
rule => "${valid_rule}",
comment => "${comment}",
}
file "${vardir}rules.d/${name}.rule" {
state => $const.res.file.state.exists,
content => golang.template(deploy.readfile("/files/rules.frag.tmpl"), $tmpl),
owner => "root",
group => "root",
mode => "u=rw,go=",
}
}
class firewall:stoppedrule_base() {
file "${vardir}stoppedrules.d/" {
state => $const.res.file.state.exists,
recurse => true,
purge => true,
owner => "root",
group => "root",
mode => "u=rwx,go=", # dir
}
file "${vardir}stoppedrules.header" {
state => $const.res.file.state.exists,
content => deploy.readfile("/files/stoppedrules"), # static, no template!
owner => "root",
group => "root",
mode => "u=rw,go=",
}
file "/etc/shorewall/stoppedrules" {
state => $const.res.file.state.exists,
fragments => [
"${vardir}stoppedrules.header", # also pull this one file
"${vardir}stoppedrules.d/", # pull from this dir
],
owner => "root",
group => "root",
mode => "u=rw,go=",
Notify => Svc["shorewall"],
}
}
class firewall:stoppedrule($name, $st) {
print "stoppedrule: ${name}" {}
include stoppedrule_base
$rule = $st->rule || "" # entire rule contents OR use the below values
$action = $st->action # REJECT or SSH(ACCEPT) or Ping(DROP)
$source = $st->source # source zone
$dest = $st->dest # dest zone
$comment = $st->comment || ""
# TODO: tabs for beautifying, replace with a padding function eventually.
$valid_rule = if $rule == "" {
"${action}\t${source}\t\t${dest}"
} else {
$rule
}
$tmpl = struct{
rule => "${valid_rule}",
comment => "${comment}",
}
file "${vardir}stoppedrules.d/${name}.stoppedrule" {
state => $const.res.file.state.exists,
content => golang.template(deploy.readfile("/files/stoppedrules.frag.tmpl"), $tmpl),
owner => "root",
group => "root",
mode => "u=rw,go=",
}
}
class firewall:snat_base() {
file "${vardir}snat.d/" {
state => $const.res.file.state.exists,
recurse => true,
purge => true,
owner => "root",
group => "root",
mode => "u=rwx,go=", # dir
}
file "${vardir}snat.header" {
state => $const.res.file.state.exists,
content => deploy.readfile("/files/snat"), # static, no template!
owner => "root",
group => "root",
mode => "u=rw,go=",
}
file "/etc/shorewall/snat" {
state => $const.res.file.state.exists,
fragments => [
"${vardir}snat.header", # also pull this one file
"${vardir}snat.d/", # pull from this dir
],
owner => "root",
group => "root",
mode => "u=rw,go=",
Notify => Svc["shorewall"],
}
}
class firewall:snat($name, $st) {
print "snat: ${name}" {}
include snat_base
$action = $st->action # "MASQUERADE" usually
$source = $st->source # list of ip/cidr
$dest = $st->dest
$comment = $st->comment || ""
$valid_source = strings.join($source, ",")
$tmpl = struct{
action => "${action}",
source => "${valid_source}",
dest => "${dest}",
comment => "${comment}",
}
file "${vardir}snat.d/${name}.snat" {
state => $const.res.file.state.exists,
content => golang.template(deploy.readfile("/files/snat.frag.tmpl"), $tmpl),
owner => "root",
group => "root",
mode => "u=rw,go=",
}
}
class firewall:params_base() {
file "${vardir}params.d/" {
state => $const.res.file.state.exists,
recurse => true,
purge => true,
owner => "root",
group => "root",
mode => "u=rwx,go=", # dir
}
file "${vardir}params.header" {
state => $const.res.file.state.exists,
content => deploy.readfile("/files/params"), # static, no template!
owner => "root",
group => "root",
mode => "u=rw,go=",
}
file "/etc/shorewall/params" {
state => $const.res.file.state.exists,
fragments => [
"${vardir}params.header", # also pull this one file
"${vardir}params.d/", # pull from this dir
],
owner => "root",
group => "root",
mode => "u=rw,go=",
Notify => Svc["shorewall"],
}
}
class firewall:params($name, $st) {
print "params: ${name}" {}
include params_base
# TODO: add params
}

View File