summaryrefslogtreecommitdiffstats
path: root/roles/common/files/usr/local
diff options
context:
space:
mode:
Diffstat (limited to 'roles/common/files/usr/local')
-rwxr-xr-xroles/common/files/usr/local/bin/gendhparam.sh9
-rwxr-xr-xroles/common/files/usr/local/bin/genkeypair.sh58
-rwxr-xr-xroles/common/files/usr/local/sbin/update-firewall61
-rwxr-xr-xroles/common/files/usr/local/sbin/update-firewall.sh475
4 files changed, 89 insertions, 514 deletions
diff --git a/roles/common/files/usr/local/bin/gendhparam.sh b/roles/common/files/usr/local/bin/gendhparam.sh
index 074986b..a94175a 100755
--- a/roles/common/files/usr/local/bin/gendhparam.sh
+++ b/roles/common/files/usr/local/bin/gendhparam.sh
@@ -1,13 +1,10 @@
#!/bin/sh
set -ue
PATH=/usr/bin:/bin
-privkey="$1"
+out="$1"
bits="${2:-2048}"
-rand=
-mv -f "$(mktemp)" "$privkey"
-chmod og-rwx "$privkey"
-
-openssl dhparam -rand "${rand:-/dev/urandom}" "$bits" >"$privkey"
+install --mode=0644 /dev/null "$out"
+openssl dhparam -rand /dev/urandom "$bits" >"$out"
diff --git a/roles/common/files/usr/local/bin/genkeypair.sh b/roles/common/files/usr/local/bin/genkeypair.sh
index 5bf67f2..72102f4 100755
--- a/roles/common/files/usr/local/bin/genkeypair.sh
+++ b/roles/common/files/usr/local/bin/genkeypair.sh
@@ -4,202 +4,194 @@
# certificates or Certificate Signing Requests, or DKIM private keys.
# Inspired from make-ssl-cert(8) and opendkim-genkey(8).
#
# Copyright © 2014 Guilhem Moulin <guilhem@fripost.org>
#
# 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 <http://www.gnu.org/licenses/>.
set -ue
PATH=/usr/bin:/bin
+export PATH
# Default values
type=rsa
bits=
hash=
force=0
config=
pubkey=pubkey.pem
privkey=privkey.pem
dns=
ou=
cn=
usage=
-chmod=
-chown=
-rand=
+mode=
+owner=
+group=
usage() {
cat >&2 <<- EOF
Usage: $0 command [OPTIONS]
Command:
x509: generate a self-signed X.509 server certificate
csr: generate a Certificate Signing Request
dkim: generate a private key (to use for DKIM signing)
+ keypair: generate a key pair
Options:
-t type: key type (default: rsa)
-b bits: key length or EC curve (default: 2048 for RSA, 1024 for DSA, secp224r1 for ECDSA)
-h digest: digest algorithm
--ou: organizational Unit Name; can be repeated
--cn: common Name (default: \$(hostname --fqdn)
--dns: hostname for AltName; can be repeated
-f: force; can be repeated (0: don't overwrite, default;
1: reuse private key if it exists;
2: overwrite both keys if they exist)
--config: configuration file
--pubkey: public key file (default: pubkey.pem)
- --privkey: private key file (default: privkey.pem; created with og-rwx)
+ --privkey: private key file (default: privkey.pem)
--usage: key usage (default: digitalSignature,keyEncipherment,keyCertSign)
- --chmod: chmod the private key
- --chown: chown the private key
+ --mode: set privkey's permission mode (default: 0600)
+ --owner: set privkey's owner (default: the process' current owner)
+ --group: set privkey's group (default: the process' current group)
Return values:
0 The key pair was successfully generated
1 The public or private key file exists, and -f is not set
2 The key generation failed
EOF
}
-dkiminfo() {
- echo "Add the following TXT record to your DNS zone:"
- echo "${cn:-$(date +%Y%m%d)}._domainkey\tIN\tTXT ( "
- # See https://tools.ietf.org/html/rfc4871#section-3.6.1
- # t=s: the "i=" domain in signature headers MUST NOT be a subdomain of "d="
- # s=email: limit DKIM signing to email
- openssl pkey -pubout <"$privkey" | sed '/^--.*--$/d' \
- | { echo -n "v=DKIM1; k=$type; t=s; s=email; p="; tr -d '\n'; } \
- | fold -w 250 \
- | { sed 's/.*/\t"&"/'; echo ' )'; }
-}
-
[ $# -gt 0 ] || { usage; exit 2; }
cmd="$1"; shift
case "$cmd" in
- x509|csr|dkim) ;;
+ x509|csr|dkim|keypair) ;;
*) echo "Unrecognized command: $cmd" >&2; exit 2
esac
nou=1
while [ $# -gt 0 ]; do
case "$1" in
-t) shift; type="$1";;
-t*) type="${1#-t}";;
-b) shift; bits="$1";;
-b*) bits="${1#-b}";;
-h) shift; hash="$1";;
-h*) hash="${1#-h}";;
--dns=?*) dns="${dns:+$dns, }DNS:${1#--dns=}";;
--cn=?*) cn="${1#--cn=}";;
--ou=?*) ou="${ou:+$ou\n}$nou.organizationalUnitName = ${1#--ou=}"
nou=$(( 1 + $nou ));;
-f) force=$(( 1 + $force ));;
--pubkey=?*) pubkey="${1#--pubkey=}";;
--privkey=?*) privkey="${1#--privkey=}";;
--usage=?*) usage="${usage:+$usage,}${1#--usage=}";;
- --config=?*) dns="${1#--config=}";;
+ --config=?*) config="${1#--config=}";;
- --chmod=?*) chmod="${1#--chmod=}";;
- --chown=?*) chown="${1#--chown=}";;
+ --mode=?*) mode="${1#--mode=}";;
+ --owner=?*) owner="${1#--owner=}";;
+ --group=?*) group="${1#--group=}";;
--help) usage; exit;;
*) echo "Unrecognized argument: $1" >&2; exit 2
esac
shift;
done
case "$type" in
# XXX: genrsa and dsaparam have been deprecated in favor of genpkey.
# genpkey can also create explicit EC parameters, but not named.
- rsa) genkey=genrsa; genkeyargs="-f4 ${bits:-2048}";;
- dsa) genkey=dsaparam; genkeyargs="-noout -genkey ${bits:-1024}";;
+ rsa) genkey=genrsa; genkeyargs="-rand /dev/urandom -f4 ${bits:-2048}";;
+ dsa) genkey=dsaparam; genkeyargs="-rand /dev/urandom -noout -genkey ${bits:-1024}";;
# See 'openssl ecparam -list_curves' for the list of supported
# curves. StrongSwan doesn't support explicit curve parameters
# (however explicit parameters might be required to make exotic
# curves work with some clients.)
ecdsa) genkey=ecparam
- genkeyargs="-noout -name ${bits:-secp224r1} -param_enc named_curve -genkey";;
+ genkeyargs="-rand /dev/urandom -noout -name ${bits:-secp224r1} -param_enc named_curve -genkey";;
+ x25519|x448|ed25519|ed448) genkey=genpkey
+ genkeyargs="-algorithm $type";;
*) echo "Unrecognized key type: $type" >&2; exit 2
esac
if [ "$cmd" = x509 -o "$cmd" = csr ]; then
case "$hash" in
md5|rmd160|sha1|sha224|sha256|sha384|sha512|'') ;;
*) echo "Invalid digest algorithm: $hash" >&2; exit 2;
esac
[ "$cn" ] || cn="$(hostname --fqdn)"
[ ${#cn} -le 64 ] || { echo "CommonName too long: $cn" >&2; exit 2; }
fi
if [ -z "$config" -a \( "$cmd" = x509 -o "$cmd" = csr \) ]; then
config=$(mktemp) || exit 2
trap 'rm -f "$config"' EXIT
# see /usr/share/ssl-cert/ssleay.cnf
cat >"$config" <<- EOF
[ req ]
distinguished_name = req_distinguished_name
prompt = no
policy = policy_anything
req_extensions = v3_req
x509_extensions = v3_req
[ req_distinguished_name ]
organizationName = Fripost
organizationalUnitName = SSLcerts
$(echo "$ou")
- commonName = $cn
+ commonName = ${cn:-/}
[ v3_req ]
subjectAltName = email:admin@fripost.org${dns:+, $dns}
basicConstraints = critical, CA:FALSE
# https://security.stackexchange.com/questions/24106/which-key-usages-are-required-by-each-key-exchange-method
keyUsage = critical, ${usage:-digitalSignature, keyEncipherment, keyCertSign}
subjectKeyIdentifier = hash
EOF
fi
if [ -s "$privkey" -a $force -eq 0 ]; then
echo "Error: private key exists: $privkey" >&2
- [ "$cmd" = dkim ] && dkiminfo
exit 1
elif [ ! -s "$privkey" -o $force -ge 2 ]; then
- # Ensure "$privkey" is created with umask 0077
- mv -f "$(mktemp)" "$privkey" || exit 2
- chmod "${chmod:-og-rwx}" "$privkey" || exit 2
- [ -z "$chown" ] || chown "$chown" "$privkey" || exit 2
- openssl $genkey -rand "${rand:-/dev/urandom}" $genkeyargs >"$privkey" || exit 2
- [ "$cmd" = dkim ] && { dkiminfo; exit; }
+ install --mode="${mode:-0600}" ${owner:+--owner="$owner"} ${group:+--group="$group"} /dev/null "$privkey" || exit 2
+ openssl $genkey $genkeyargs >"$privkey" || exit 2
+ [ "$cmd" = dkim ] && exit
fi
if [ "$cmd" = x509 -a "$pubkey" = "$privkey" ]; then
pubkey=$(mktemp)
openssl req -config "$config" -new -x509 ${hash:+-$hash} -days 3650 -key "$privkey" >"$pubkey" || exit 2
cat "$pubkey" >>"$privkey" || exit 2
rm -f "$pubkey"
elif [ "$cmd" = x509 -o "$cmd" = csr ]; then
if [ -s "$pubkey" -a $force -eq 0 ]; then
echo "Error: public key exists: $pubkey" >&2
exit 1
else
[ "$cmd" = x509 ] && x509=-x509 || x509=
openssl req -config "$config" -new $x509 ${hash:+-$hash} -days 3650 -key "$privkey" >"$pubkey" || exit 2
fi
+elif [ "$cmd" = keypair -a "$pubkey" ]; then
+ openssl pkey -pubout <"$privkey" >"$pubkey"
fi
diff --git a/roles/common/files/usr/local/sbin/update-firewall b/roles/common/files/usr/local/sbin/update-firewall
new file mode 100755
index 0000000..e11e8a9
--- /dev/null
+++ b/roles/common/files/usr/local/sbin/update-firewall
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+set -ue
+PATH=/usr/sbin:/usr/bin:/sbin:/bin
+export PATH
+
+NFTABLES="/etc/nftables.conf"
+
+script="$(mktemp --tmpdir=/dev/shm)"
+oldrules="$(mktemp --tmpdir=/dev/shm)"
+newrules="$(mktemp --tmpdir=/dev/shm)"
+netns=
+cleanup(){
+ rm -f -- "$script" "$oldrules" "$newrules"
+ [ -z "$netns" ] || ip netns del "$netns"
+}
+trap cleanup EXIT INT TERM
+
+echo "flush ruleset" >"$script" # should be included already, but...
+cat <"$NFTABLES" >>"$script"
+
+ip netns add "nft-dryrun"
+netns="nft-dryrun"
+
+declare -a INTERFACES=()
+for iface in /sys/class/net/*; do
+ idx="$(< "$iface/ifindex")"
+ INTERFACES[idx]="${iface#/sys/class/net/}"
+done
+
+# create dummy interfaces so we can use iif/oif in the nft rules
+# (we preserve indices to preserve canonical set representation)
+for idx in "${!INTERFACES[@]}"; do
+ [ "${INTERFACES[idx]}" != "lo" ] || continue
+ ip netns exec "$netns" ip link add "${INTERFACES[idx]}" index "$idx" type dummy
+done
+
+# clear sets in the old rules before diff'ing with the new ones
+nft -sn list ruleset >"$oldrules"
+ip netns exec "$netns" nft -f - <"$oldrules"
+ip netns exec "$netns" nft flush set inet filter fail2ban || true
+ip netns exec "$netns" nft flush set inet filter fail2ban6 || true
+ip netns exec "$netns" nft -sn list ruleset >"$oldrules"
+
+ip netns exec "$netns" nft -f - <"$script"
+ip netns exec "$netns" nft -sn list ruleset >"$newrules"
+ip netns del "$netns"
+netns=
+
+if [ ! -t 0 ] || [ ! -t 1 ]; then
+ diff -q -- "$oldrules" "$newrules" && exit 0 || exit 1
+elif ! diff -u --color=auto --label=a/ruleset --label=b/ruleset \
+ -- "$oldrules" "$newrules" && nft -f - <"$script"; then
+ read -p "Ruleset applied. Revert? [Y/n] " -r -t10 r || r="y"
+ if [ "${r,,[a-z]}" != "n" ]; then
+ echo "Reverting..."
+ echo "flush ruleset" >"$script"
+ cat <"$oldrules" >>"$script"
+ nft -f - <"$script"
+ fi
+fi
diff --git a/roles/common/files/usr/local/sbin/update-firewall.sh b/roles/common/files/usr/local/sbin/update-firewall.sh
deleted file mode 100755
index f25f507..0000000
--- a/roles/common/files/usr/local/sbin/update-firewall.sh
+++ /dev/null
@@ -1,475 +0,0 @@
-#!/bin/bash
-
-# Create iptables (v4 and v6) rules. Unless one of [-f] or [-c] is
-# given, or if the ruleset is unchanged, a confirmation is asked after
-# loading the new rulesets; if the user answers No or doesn't answer,
-# the old ruleset is restored. If the user answer Yes (or if the flag
-# [-f] is given), the new ruleset is made persistent (requires a pre-up
-# hook) by moving it to /etc/iptables/rules.v[46].
-#
-# The [-c] flag switch to dry-run (check) mode. The rulesets are not
-# applied, but merely checked against the existing ones. The return
-# value is 0 iff. they do not differ.
-#
-# This firewall is only targeted towards end-servers, not gateways. In
-# particular, there is no NAT'ing at the moment.
-#
-# Dependencies: netmask(1)
-#
-# Copyright © 2013 Guilhem Moulin <guilhem@fripost.org>
-#
-# 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 <http://www.gnu.org/licenses/>.
-
-set -ue
-PATH=/usr/sbin:/usr/bin:/sbin:/bin
-timeout=10
-
-force=0
-check=0
-verbose=0
-addrfam=
-
-secmark=0xA99 # must match that in /etc/network/if-up.d/ipsec
-secproto=esp # must match /etc/ipsec.conf; ESP is the default (vs AH/IPComp)
-
-fail2ban_re='^(\[[0-9]+:[0-9]+\]\s+)?-A fail2ban-\S'
-IPSec_re=" -m policy --dir (in|out) --pol ipsec --reqid [0-9]+ --proto $secproto -j ACCEPT$"
-declare -A rss=() tables=()
-
-usage() {
- cat >&2 <<- EOF
- Usage: $0 [OPTIONS]
-
- Options:
- -f force: no confirmation asked
- -c check: check (dry-run) mode
- -v verbose: see the difference between old and new ruleset
- -4 IPv4 only
- -6 IPv6 only
- EOF
- exit 1
-}
-
-log() {
- /usr/bin/logger -st firewall -p user.info -- "$@"
-}
-fatal() {
- /usr/bin/logger -st firewall -p user.err -- "$@"
- exit 1
-}
-
-iptables() {
- # Fake iptables/ip6tables(8); use the more efficient
- # iptables-restore(8) instead.
- echo "$@" >> "$new";
-}
-commit() {
- # End a table
- echo COMMIT >> "$new"
-}
-inet46() {
- case "$1" in
- 4) echo "$2";;
- 6) echo "$3";;
- esac
-}
-ipt-chains() {
- # Define new (tables and) chains.
- while [ $# -gt 0 ]; do
- case "$1" in
- ?*:*) echo ":${1%:*} ${1##*:} [0:0]";;
- ?*) echo "*$1";;
- esac
- shift
- done >> "$new"
-}
-
-ipt-trim() {
- # Remove dynamic chain/rules from the input stream, as they are
- # automatically included by third-party servers (such as strongSwan
- # or fail2ban). The output is ready to be made persistent.
- grep -Ev -e '^:fail2ban-\S' \
- -e "$IPSec_re" \
- -e '-j fail2ban-\S+$' \
- -e "$fail2ban_re"
-}
-
-ipt-diff() {
- # Get the difference between two rulesets.
- if [ $verbose -eq 1 ]; then
- /usr/bin/diff -u -I '^#' "$1" "$2"
- else
- /usr/bin/diff -q -I '^#' "$1" "$2" >/dev/null
- fi
-}
-
-ipt-persist() {
- # Make the current ruleset persistent. (Requires a pre-up hook
- # script to load the rules before the network is configured.)
-
- log "Making ruleset persistent... "
- [ -d /etc/iptables ] || mkdir /etc/iptables
-
- local f rs table
- for f in "${!tables[@]}"; do
- ipts=/sbin/$(inet46 $f iptables ip6tables)-save
- rs=/etc/iptables/rules.v$f
-
- for table in ${tables[$f]}; do
- /bin/ip netns exec $netns $ipts -t $table
- done | ipt-trim > "$rs"
- chmod 0600 "$rs"
- done
-}
-
-ipt-revert() {
- [ $check -eq 0 ] || return
- log "Reverting to old ruleset... "
-
- local rs
- for f in "${!rss[@]}"; do
- /sbin/$(inet46 $f iptables ip6tables)-restore -c < "${rss[$f]}"
- rm -f "${rss[$f]}"
- done
- exit 1
-}
-
-run() {
- # Build and apply the firewall for IPv4/6.
- local f="$1"
- local ipt=/sbin/$(inet46 $f iptables ip6tables)
- tables[$f]=filter
-
- # The default interface associated with this address.
- local if=$( /bin/ip -$f route show to default scope global \
- | sed -nr '/^default via \S+ dev (\S+).*/ {s//\1/p;q}' )
-
- # The virtual interface reserved for IPSec.
- local ifsec=$( /bin/ip -o -$f link show \
- | sed -nr "/^[0-9]+:\s+(sec[0-9]+)@$if:\s.*/ {s//\1/p;q}" )
-
- # The (host-scoped) IP reserved for IPSec.
- local ipsec=
- if [ "$ifsec" -a $f = 4 ]; then
- tables[$f]='mangle nat filter'
- ipsec=$( /bin/ip -$f address show dev "$ifsec" scope host \
- | sed -nr '/^\s+inet\s(\S+).*/ {s//\1/p;q}' )
- fi
-
- # Store the old (current) ruleset
- local old=$(mktemp --tmpdir current-rules.v$f.XXXXXX) \
- new=$(mktemp --tmpdir new-rules.v$f.XXXXXX)
- for table in ${tables[$f]}; do
- $ipt-save -ct $table
- done > "$old"
- rss[$f]="$old"
-
- local fail2ban=0
- # XXX: As of Wheezy, fail2ban is IPv4 only. See
- # https://github.com/fail2ban/fail2ban/issues/39 for the current
- # state of the art.
- if [ "$f" = 4 ] && which /usr/bin/fail2ban-server >/dev/null; then
- fail2ban=1
- fi
-
- # The usual chains in filter, along with the desired default policies.
- ipt-chains filter INPUT:DROP FORWARD:DROP OUTPUT:DROP
-
- if [ ! "$if" ]; then
- # If the interface is not configured, we stop here and DROP all
- # packets by default. Thanks to the pre-up hook this tight
- # policy will be activated whenever the interface goes up.
- mv "$new" /etc/iptables/rules.v$f
- return 0
- fi
-
- # Fail2ban-specific chains and traps
- if [ $fail2ban -eq 1 ]; then
- echo ":fail2ban - [0:0]"
- # Don't remove existing rules & traps in the current rulest
- grep -- '^:fail2ban-\S' "$old" || true
- grep -E -- ' -j fail2ban-\S+$' "$old" || true
- grep -E -- "$fail2ban_re" "$old" || true
- fi >> "$new"
-
- if [ "$ipsec" ]; then
- # (Host-to-host) IPSec tunnels come first. TODO: test IPSec with IPv6.
- grep -E -- "$IPSec_re" "$old" >> "$new" || true
-
- # Allow any IPsec $secproto protocol packets to be sent and received.
- iptables -A INPUT -i $if -p $secproto -j ACCEPT
- iptables -A OUTPUT -o $if -p $secproto -j ACCEPT
- fi
-
-
- ########################################################################
- # DROP all RFC1918 addresses, martian networks, multicasts, ...
- # Credits to http://newartisans.com/2007/09/neat-tricks-with-iptables/
- # http://baldric.net/loose-iptables-firewall-for-servers/
-
- local ip
- if [ "$f" = 4 ]; then
- # Private-use networks (RFC 1918) and link local (RFC 3927)
- local MyNetwork=$( /bin/ip -4 address show dev $if scope global \
- | sed -nr 's/^\s+inet\s(\S+).*/\1/p')
- [ "$MyNetwork" ] && \
- for ip in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16; do
- # Don't lock us out if we are behind a NAT ;-)
- [ "$ip" = "$(/usr/bin/netmask -nc $ip $MyNetwork | sed 's/ //g')" ] \
- || iptables -A INPUT -i $if -s "$ip" -j DROP
- done
-
- # Other martian packets: "This" network, multicast, broadcast (RFCs
- # 1122, 3171 and 919).
- for ip in 0.0.0.0/8 224.0.0.0/4 240.0.0.0/4 255.255.255.255/32; do
- iptables -A INPUT -i $if -s "$ip" -j DROP
- iptables -A INPUT -i $if -d "$ip" -j DROP
- done
-
- elif [ "$f" = 6 ]; then
- # Martian IPv6 packets: ULA (RFC 4193) and site local addresses
- # (RFC 3879).
- for ip in fc00::/7 fec0::/10; do
- iptables -A INPUT -i $if -s "$ip" -j DROP
- iptables -A INPUT -i $if -d "$ip" -j DROP
- done
- fi
-
- # DROP INVALID packets immediately.
- iptables -A INPUT -m state --state INVALID -j DROP
- iptables -A OUTPUT -m state --state INVALID -j DROP
-
- # DROP bogus TCP packets.
- iptables -A INPUT -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP
- iptables -A INPUT -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP
- iptables -A INPUT -p tcp \! --syn -m state --state NEW -j DROP
-
- # Allow all input/output to/from the loopback interface.
- local localhost=$(inet46 $f '127.0.0.1/8' '::1/128')
- iptables -A INPUT -i lo -s "$localhost" -d "$localhost" -j ACCEPT
- iptables -A OUTPUT -o lo -s "$localhost" -d "$localhost" -j ACCEPT
-
- if [ "$ipsec" ]; then
- # ACCEPT any, *IPSec* traffic destinating to the non-routable
- # $ipsec. Also ACCEPT all traffic originating from $ipsec, as
- # it is MASQUERADE'd.
- iptables -A INPUT -d "$ipsec" -i $if -m policy --dir in \
- --pol ipsec --proto $secproto -j ACCEPT
- iptables -A OUTPUT -m mark --mark "$secmark" -o $if -j ACCEPT
- fi
-
- # Prepare fail2ban. We make fail2ban insert its rules in a
- # dedicated chain, so that it doesn't mess up the existing rules.
- [ $fail2ban -eq 1 ] && iptables -A INPUT -i $if -j fail2ban
-
- if [ "$f" = 4 ]; then
- # Allow only ICMP of type 0, 3 and 8. The rate-limiting is done
- # directly by the kernel (net.ipv4.icmp_ratelimit and
- # net.ipv4.icmp_ratemask runtime options). See icmp(7).
- local t
- for t in 'echo-reply' 'destination-unreachable' 'echo-request'; do
- iptables -A INPUT -p icmp -m icmp --icmp-type $t -j ACCEPT
- iptables -A OUTPUT -p icmp -m icmp --icmp-type $t -j ACCEPT
- done
- elif [ $f = 6 ]; then
- iptables -A INPUT -p icmpv6 -j ACCEPT
- iptables -A OUTPUT -p icmpv6 -j ACCEPT
- fi
-
-
- ########################################################################
- # ACCEPT new connections to the services we provide, or to those we want
- # to connect to.
-
- sed -re 's/#.*//; /^\s*$/d' -e "s/^(in|out|inout)$f?(\s.*)/\1\2/" \
- /etc/iptables/services | \
- grep -Ev '^(in|out|inout)\S\s' | \
- while read dir proto dport sport; do
- # We add two entries per config line: we need to accept the new
- # connection, and latter the reply.
- local stNew=NEW,ESTABLISHED
- local stEst=ESTABLISHED
-
- # In-Out means full-duplex
- [[ "$dir" =~ ^inout ]] && stEst="$stNew"
-
- local iptNew= iptEst= optsNew= optsEst=
- case "$dport" in
- *,*|*:*) optsNew="--match multiport --dports $dport"
- optsEst="--match multiport --sports $dport";;
- ?*) optsNew="--dport $dport"
- optsEst="--sport $dport";;
- esac
- case "$sport" in
- *,*|*:*) optsNew+=" --match multiport --sports $sport"
- optsEst+=" --match multiport --dports $sport";;
- ?*) optsNew+=" --sport $sport"
- optsEst+=" --dport $sport";;
- esac
- case "$dir" in
- in|inout) iptNew="-A INPUT -i"; iptEst="-A OUTPUT -o";;
- out) iptNew="-A OUTPUT -o"; iptEst="-A INPUT -i";;
- *) fatal "Error: Unknown direction: '$dir'."
- esac
-
- iptables $iptNew $if -p $proto $optsNew -m state --state $stNew -j ACCEPT
- iptables $iptEst $if -p $proto $optsEst -m state --state $stEst -j ACCEPT
- done
-
- ########################################################################
- commit
-
- if [ "$ipsec" ]; then
- # DNAT the IPSec paquets to $ipsec after decapsulation, and SNAT
- # them before encapsulation. We need to do the NAT'ing before
- # packets enter the IPSec stack because they are signed
- # afterwards, and NAT'ing would mess up the signature.
- ipt-chains mangle PREROUTING:ACCEPT INPUT:ACCEPT \
- FORWARD:DROP \
- OUTPUT:ACCEPT POSTROUTING:ACCEPT
-
- # Packets which destination is $ipsec *must* be associated with
- # an IPSec policy.
- iptables -A INPUT -d "$ipsec" -i $if -m policy --dir in \
- --pol ipsec --proto $secproto -j ACCEPT
- iptables -A INPUT -d "$ipsec" -i $if -j DROP
-
- # Packets originating from our (non-routable) $ipsec are marked;
- # if there is no xfrm lookup (i.e., no matching IPSec
- # association), the packet will retain its mark and be null
- # routed later on. Otherwise, the packet is re-queued unmarked.
- iptables -A OUTPUT -o $if -j MARK --set-mark 0x0
- iptables -A OUTPUT -s "$ipsec" -o $if -m policy --dir out \
- --pol none -j MARK --set-mark $secmark
- commit
-
- ipt-chains nat PREROUTING:ACCEPT INPUT:ACCEPT \
- OUTPUT:ACCEPT POSTROUTING:ACCEPT
-
- # DNAT all marked packets after decapsulation.
- iptables -A PREROUTING \! -d "$ipsec" -i $if -m policy --dir in \
- --pol ipsec --proto $secproto -j DNAT --to "${ipsec%/*}"
-
- # Packets originating from our IPSec are SNAT'ed (MASQUERADE).
- # (And null-routed later on unless there is an xfrm
- # association.)
- iptables -A POSTROUTING -m mark --mark $secmark -o $if -j MASQUERADE
- commit
- fi
-
- ########################################################################
-
-
- local rv1=0 rv2=0 persistent=/etc/iptables/rules.v$f
- local oldz=$(mktemp --tmpdir current-rules.v$f.XXXXXX)
-
- # Reset the counters. They are not useful for comparing and/or
- # storing persistent ruleset. (We don't use sed -i because we want
- # to restore the counters when reverting.)
- sed -r -e '/^:/ s/\[[0-9]+:[0-9]+\]$/[0:0]/' \
- -e 's/^\[[0-9]+:[0-9]+\]\s+//' \
- "$old" > "$oldz"
-
- /usr/bin/uniq "$new" | /bin/ip netns exec $netns $ipt-restore || ipt-revert
-
- for table in ${tables[$f]}; do
- /bin/ip netns exec $netns $ipt-save -t $table
- done > "$new"
-
- ipt-diff "$oldz" "$new" || rv1=$?
-
- if ! [ -f "$persistent" -a -x /etc/network/if-pre-up.d/iptables ]; then
- rv2=1
- else
- ipt-trim < "$oldz" | ipt-diff - "$persistent" || rv2=$?
- fi
-
- local update="Please run '${0##*/}'."
- if [ $check -eq 0 ]; then
- /usr/bin/uniq "$new" | $ipt-restore || ipt-revert
- else
- if [ $rv1 -ne 0 ]; then
- log "WARN: The IPv$f firewall is not up to date! $update"
- fi
- if [ $rv2 -ne 0 ]; then
- log "WARN: The current IPv$f firewall is not persistent! $update"
- fi
- fi
-
- rm -f "$oldz" "$new"
- return $(( $rv1 | $rv2 ))
-}
-
-
-# Parse options
-while [ $# -gt 0 ]; do
- case "$1" in
- -?*) for (( k=1; k<${#1}; k++ )); do
- o="${1:$k:1}"
- case "$o" in
- 4|6) addrfam="$o";;
- c) check=1;;
- f) force=1;;
- v) verbose=1;;
- *) usage;;
- esac
- done
- ;;
- *) usage;;
- esac
- shift
-done
-
-# If we are going to apply the ruleset, we should either have a TTY, or
-# use -f.
-if ! /usr/bin/tty -s && [ $force -eq 0 -a $check -eq 0 ]; then
- echo "Error: Not a TTY. Try with -f (at your own risks!)" >&2
- exit 1
-fi
-
-# Create an alternative net namespace in which we apply the ruleset, so
-# we can easily get a normalized version we can compare latter. See
-# http://bugzilla.netfilter.org/show_bug.cgi?id=790
-netns="ipt-firewall-test-$$"
-/bin/ip netns add $netns
-
-trap '/bin/ip netns del $netns 2>/dev/null || true; ipt-revert' SIGINT
-trap '/bin/ip netns del $netns; rm -f "${rss[@]}"' EXIT
-
-rv=0
-for f in ${addrfam:=4 6}; do
- run $f || rv=$(( $rv | $? ))
-done
-
-if [ $force -eq 1 ]; then
- # At the user's own risks...
- ipt-persist
-
-elif [ $check -eq 1 -o $rv -eq 0 ]; then
- # Nothing to do, we're all set.
- exit $rv
-
-else
- echo "Try now to establish NEW connections to the machine."
-
- read -n1 -t$timeout \
- -p "Are you sure you want to use the new ruleset? (y/N) " \
- ret 2>&1 || { [ $? -gt 128 ] && echo -n "Timeout..."; }
- case "${ret:-N}" in
- [yY]*) echo; ipt-persist
- ;;
- *) echo; ipt-revert
- ;;
- esac
-fi