[[!comment format=mdwn
 username="david"
 avatar="http://cdn.libravatar.org/avatar/22c2d800db6a7699139df604a67cb221"
 subject="A  first attempt"
 date="2019-07-18T23:30:44Z"
 content="""
Here's what I came up with. I don't know if I'm missing some more obvious way. Thanks to Stefan Gronke on github for answering \"what's a simple way to make an iptables systemd service\"

[[!format haskell \"\"\"
module Propellor.Property.SiteSpecific.Tethera.Firewall (
    iptablesRules
  , iptablesUnits
  , saved
  ) where

import Propellor.Base
import Propellor.Property.Firewall
-- import qualified Propellor.Property.Cmd as Cmd
import qualified Propellor.Property.File as File

iptablesRules :: [Port] -> [Port] -> Property DebianLike
iptablesRules tcpPorts udpPorts = propertyList \"IPTables based firewall\" $ props
  & installed
  & rule INPUT Filter DROP (Ctstate [INVALID])
  & rule INPUT Filter ACCEPT (InIFace \"lo\")
  & rule OUTPUT Filter ACCEPT (OutIFace \"lo\")
  & rule INPUT Filter ACCEPT (Ctstate [ESTABLISHED, RELATED])
  & rule INPUT Filter ACCEPT (Proto ICMP)
  & openPorts TCP tcpPorts
  & openPorts UDP udpPorts
  & rule OUTPUT Filter ACCEPT Everything
  & rule INPUT Filter DROP Everything
  & rule FORWARD Filter DROP Everything
  where
    openPorts proto lst = combineProperties \"open TCP ports\" $
                   toProps (map
                             (\p -> (rule INPUT Filter ACCEPT 
                                     ((Proto proto) :- (DPort p)) ))
                             lst)

saved :: Property UnixLike
saved = combineProperties \"iptables rules saved\" $ props
  & cmdProperty \"iptables-save\" [\"-f\", rulesFile ]
        `changesFile` rulesFile
    `requires` File.dirExists rulesDir
  & cmdProperty \"ip6tables-save\" [\"-f\", rules6File ]
        `changesFile` rules6File
    `requires` File.dirExists rulesDir
  where
    rulesDir = \"/etc/iptables\"
    rulesFile = rulesDir ++ \"/iptables.rules\"
    rules6File = rulesDir ++ \"/ip6tables.rules\"

iptablesUnits :: Property UnixLike  
iptablesUnits = combineProperties \"systemd units for iptables\" $ props
                & unitFile \"iptables\"
                & unitFile \"ip6tables\"                
  where
    unitDir = \"/etc/systemd/system\"
    unitFile baseName = combineProperties (\"systemd units for \" ++ baseName) $ props
                & File.hasContent (unitDir ++ \"/\"++baseName++\".service\")
                [
                  \"[Unit]\"
                , \"Description=Packet Filtering Framework\"
                , \"DefaultDependencies=no\"
                , \"After=systemd-sysctl.service\"
                , \"Before=sysinit.target\"
                , \"[Service]\"
                , \"Type=oneshot\"
                , \"ExecStart=/sbin/\"++baseName++\"-restore /etc/iptables/\"++baseName++\".rules\"
                , \"ExecReload=/sbin/\"++baseName++\"-restore /etc/iptables/\"++baseName++\".rules\"
                , \"ExecStop=/usr/local/bin/flush-\"++baseName++\".sh\"
                , \"RemainAfterExit=yes\"
                , \"[Install]\"
                , \"WantedBy=multi-user.target\"
                ]
                & File.hasContent fipSh
                [
                  \"#!/bin/sh\"
                , \"iptables -F\"
                , \"iptables -X\"
                , \"iptables -t nat -F\"
                , \"iptables -t nat -X\"
                , \"iptables -t mangle -F\"
                , \"iptables -t mangle -X\"
                , \"iptables -P INPUT ACCEPT\"
                , \"iptables -P FORWARD ACCEPT\"
                , \"iptables -P OUTPUT ACCEPT\"
                ]
                & File.mode fipSh 0755
      where
            fipSh = \"/usr/local/bin/flush-\"++baseName++\".sh\"
\"\"\"]]
"""]]
