#!/bin/bash
#
# kate: tab-indents off; tab-width 4; indent-width 4;
#
# This file implements the following CIS controls
# --------------------------------------------------------------------
#  3.5.1.1*   Ensure default deny firewall policy
#  3.5.1.2    Ensure loopback traffic is configured
#  3.5.1.4    Ensure firewall rules exist for all open ports
#----------------------------------------------------------------------
#  Custom     Block EC2 Metadata access from docker containers
#----------------------------------------------------------------------
#         * Accepted non-compliance  (but faked for AWS Inspector)

#
# NOTE: AWS Inspector is easily confused, which means that we can't use comments
#       in the rules that inspector is looking for, otherwise it will claim non-compliance
#

#################################################################################
#                                *IMPORTANT*
# Due to ECS clobbering any iptables pre-configuration in unpredictable ways
# the preconfigure ruleset hardening is abandoned, and instead ng-encis now
# only provides *tools* to help with runtime iptables hardening
##################################################################################

# We are done here, the config below is for historical reference purposes only!
exit 0


# Make sure the firewall service is enabled and started
systemctl enable --now --no-block iptables.service

augtool --autosave --backup --noautoload --echo <<'EOD'
    transform Iptables.lns incl /etc/sysconfig/iptables
    load

    defvar filter /files/etc/sysconfig/iptables/table[.="filter"]


    ####################################################################
    # Ensure default deny firewall policy (3.5.1.1)
    #  - accepted non-compliance but faked for Inspector reasons
    ####################################################################

    # Delete our default OUTPUT ACCEPT rule if it already exists
    rm $filter/append[.="OUTPUT"][jump="ACCEPT"][comment='"[ng-encis] Allow all egress"']

    # Append our default OUTPUT ACCEPT rule at the end of the chain
    # we set a comment so we know that it's "our" rule
    defnode node $filter/append[last()+1] OUTPUT
    set $node/jump ACCEPT
    set $node/match comment
    set $node/comment '"[ng-encis] Allow all egress"'

    # Set the OUTPUT policy
    set $filter/chain[.="OUTPUT"]/policy DROP

    # Add a new (temporary) all ingress rule just before the 'default' REJECT
    rm $filter/append[.='INPUT'][comment='"[ng-encis] Allow all ingress (temporary)"']
    ins append before $filter/append[.='INPUT'][jump="REJECT"][reject-with="icmp-host-prohibited"]
    defvar rule $filter/append[.='']

    set $rule/jump ACCEPT
    set $rule/match[1] state
    set $rule/state NEW
    set $rule/match[2] comment
    set $rule/comment '"[ng-encis] Allow all ingress (temporary)"'
    set $rule INPUT

    # Set the INPUT policy
    set $filter/chain[.="INPUT"]/policy DROP

    ######################################################################
    # Ensure loopback traffic is configured (3.5.1.2)
    ######################################################################
    rm $filter/append[.="INPUT" ][jump="ACCEPT"][in-interface="lo"]
    rm $filter/append[.="INPUT" ][jump="DROP"  ][source="127.0.0.1/8"]
    rm $filter/append[.="OUTPUT"][jump="ACCEPT"][out-interface="lo"]

    ins append after $filter/chain[last()]
    set $filter/append[1] INPUT
    set $filter/append[1]/jump ACCEPT
    set $filter/append[1]/in-interface lo

    ins append after $filter/append[1]
    set $filter/append[2] INPUT
    set $filter/append[2]/jump DROP
    set $filter/append[2]/source 127.0.0.1/8

    ins append before $filter/append[.="OUTPUT"][position() = 1]
    set $filter/append[.='']/out-interface lo
    set $filter/append[.='']/jump ACCEPT
    set $filter/append[.=''] OUTPUT

    ####################################################################
    # Block docker access to EC2 instance metadata service
    ####################################################################
    rm $filter/append[.="FORWARD"][jump="DROP"][comment='"BLOCK Container Access to AWS Instance Metadata"']
    rm $filter/append[.="FORWARD"][jump="DROP"][comment='"[ng-encis] BLOCK Container Access to AWS Instance Metadata"']

    ins append before $filter/append[.="FORWARD"][position() = 1]
    set $filter/append[.='']/in-interface docker+
    set $filter/append[.='']/jump DROP
    set $filter/append[.='']/destination 169.254.169.254/32
    set $filter/append[.='']/match[1] comment
    set $filter/append[.='']/comment '"[ng-encis] BLOCK Container Access to AWS Instance Metadata"'
    set $filter/append[.=''] FORWARD

    # Allow forwarding of ESTABLISHED and RELATED connections
    rm $filter/append[.='FORWARD'][comment='"[ng-encis] Allow ESTABLISHED connections to be forwarded"']
    ins append before $filter/append[.='FORWARD'][position() = 1]
    defvar rule $filter/append[.='']
    set $rule/match[1] state
    set $rule/state ESTABLISHED,RELATED
    set $rule/jump ACCEPT
    set $rule/match[2] comment
    set $rule/comment '"[ng-encis] Allow ESTABLISHED connections to be forwarded"'
    set $rule FORWARD

    # Add a new (temporary) all forward rule just before the 'default' REJECT
    rm $filter/append[.='FORWARD'][comment='"[ng-encis] Allow all forwarding (temporary)"']
    ins append before $filter/append[.='FORWARD'][jump="REJECT"][reject-with="icmp-host-prohibited"]
    defvar rule $filter/append[.='']

    set $rule FORWARD
    set $rule/jump ACCEPT
    set $rule/match[1] state
    set $rule/state NEW
    set $rule/match[2] comment
    set $rule/comment '"[ng-encis] Allow all forwarding (temporary)"'

    # Set the FORWARD policy
    set $filter/chain[.="FORWARD"]/policy DROP

    ###################################################################
    # 3.5.1.4 - Ensure firewall rules exist for all open ports
    ###################################################################
    # So compliance with this requires compromising security, as you MUST allow NEW dhcp (udp/68) traffic
    # in on all interfaces, fortunantly we can migitate most of this insanity by setting the source address
    # to 127.127.127.127/32 to make the rule mostly ineffective.  Note that the ordering of the matches
    # ie dport *before* state in any given rule does matter for Inspector since it's pretty dumb

    # Set the match order of the default tcp/22 (ssh) rule such that Inspector can actually see it because it's dumb
    set /tmp/append INPUT
    defvar newssh /tmp/append
    set $newssh/protocol tcp
    set $newssh/dport 22
    set $newssh/match[1] state
    set $newssh/state NEW
    set $newssh/jump ACCEPT

    mv $newssh $filter/append[.='INPUT'][protocol='tcp'][dport='22'][state='NEW'][jump='ACCEPT']


    # "Allow" DHCP ingress (not really, this is just to shut inspector up hence the sourceaddress)
    rm $filter/append[.='INPUT'][protocol='udp'][state='NEW'][dport="68"][jump="ACCEPT"]
    ins append before $filter/append[.='INPUT'][protocol='tcp'][state='NEW'][dport="22"][jump="ACCEPT"]
    defvar dhcp $filter/append[.='']
    set $dhcp/source 127.127.127.127/32
    set $dhcp/protocol udp
    set $dhcp/dport 68
    set $dhcp/match[1] state
    set $dhcp/state NEW
    set $dhcp/jump ACCEPT
    set $dhcp INPUT


EOD

# Need to reload to ensure any following changes (eg in user-data) don't clobber our rules
systemctl is-active --quiet iptables.service && systemctl --no-block reload iptables.service
