Skip to content

Commit 4b3d586

Browse files
committed
feat(iptables): First draft of firewall-based clocking
1 parent 380747f commit 4b3d586

File tree

11 files changed

+208
-25
lines changed

11 files changed

+208
-25
lines changed

defaults/main.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,51 @@
11
---
2+
# Global config
3+
iptables_supported_distributions: [ Debian ]
4+
iptables_packages: [ iptables-persistent ]
5+
iptables_template_header:
6+
- "WARNING: This file is generated automatically with Ansible, do not edit directly!!"
7+
- "---"
8+
9+
# Network config
10+
iptables_v4_enabled: true
11+
iptables_v6_enabled: true
12+
13+
# Begining of the firewall rules (top of the rules file)
14+
iptables_v4_rules_header:
15+
- name: "Create the BEFORE_DOCKER chain"
16+
rules:
17+
- "*filter"
18+
- ":INPUT ACCEPT [0:0]"
19+
- ":FORWARD ACCEPT [0:0]"
20+
- ":OUTPUT ACCEPT [0:0]"
21+
- ":BEFORE_DOCKER - [0:0]"
22+
- name: "Default action"
23+
rules:
24+
- "-A BEFORE_DOCKER -j DROP"
25+
- name: "Allow Docker internal"
26+
rules:
27+
- "-A BEFORE_DOCKER -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT"
28+
- "-A BEFORE_DOCKER -i docker0 -o docker0 -j ACCEPT"
29+
- name: "Allow local connections"
30+
rules:
31+
- "-A BEFORE_DOCKER -i lo -j ACCEPT"
32+
- "-A FORWARD -o docker0 -j BEFORE_DOCKER"
33+
iptables_v6_rules_header:
34+
- name: "Default policy"
35+
rules:
36+
- "*filter"
37+
- ":INPUT ACCEPT [0:0]"
38+
- ":FORWARD ACCEPT [0:0]"
39+
- ":OUTPUT ACCEPT [0:0]"
40+
41+
# End of the firewall rules (bottom of the rules file)
42+
iptables_v4_rules_footer:
43+
- name: "Finally insert the BEFORE_DOCKER table before the DOCKER table in the FORWARD chain."
44+
rules:
45+
- "-I BEFORE_DOCKER -i lo -j ACCEPT"
46+
- "-I FORWARD -o docker0 -j BEFORE_DOCKER"
47+
iptables_v6_rules_footer: []
48+
49+
50+
# Misc
51+
TEST_ENV: "{{ 'molecule_' in ansible_hostname }}"

molecule/default/Dockerfile.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ FROM {{ item.registry.url }}/{{ item.image }}
66
FROM {{ item.image }}
77
{% endif %}
88

9-
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates cron gpg nginx systemd systemd-sysv vim && apt-get clean; \
9+
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates cron gpg nginx systemd systemd-sysv curl vim && apt-get clean; \
1010
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python*-dnf bash && dnf clean all; \
1111
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
1212
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \

molecule/default/molecule.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ platforms:
1515
command: /sbin/init
1616
capabilities:
1717
- SYS_ADMIN
18+
exposed_ports:
19+
- 80/udp
20+
- 80/tcp
21+
published_ports:
22+
- 0.0.0.0:8888:80/udp
23+
- 0.0.0.0:8888:80/tcp
1824
groups:
1925
- debian
2026
provisioner:
@@ -27,6 +33,20 @@ provisioner:
2733
log: true
2834
lint:
2935
name: ansible-lint
36+
scenario:
37+
name: default
38+
test_sequence:
39+
- lint
40+
- destroy
41+
- dependency
42+
- syntax
43+
- create
44+
- prepare
45+
- converge
46+
# - idempotence
47+
- side_effect
48+
- verify
49+
- destroy
3050
verifier:
3151
name: testinfra
3252
lint:

molecule/default/prepare.yml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@
1111
- include_role:
1212
name: geerlingguy.docker
1313

14-
- name: Install setuptools
14+
- name: Install Python tools
1515
apt:
16-
name: python-setuptools
17-
state: present
18-
19-
- name: Install pip
20-
apt:
21-
name: python-pip
16+
name: "{{ item.name }}"
2217
state: present
18+
with_items:
19+
- name: python-setuptools
20+
- name: python-pip
2321

2422
- name: Ensure pip_install_packages are installed.
2523
pip:

molecule/default/tests/test_default.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,5 @@
99
def test_docker(host):
1010

1111
daemon = host.service("docker")
12-
assert host.file("/etc/docker/daemon.json").exists
1312
assert daemon.is_running
1413
assert daemon.is_enabled

molecule/default/tests/test_security.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import os
2+
import requests
3+
from requests import ReadTimeout
24

35
import testinfra.utils.ansible_runner
46

@@ -12,6 +14,9 @@ def test_webserver_connected_via_localhost(host):
1214
assert scks.is_listening
1315

1416
def test_not_webserver_connected_via_public_network(host):
15-
16-
scks = host.socket("tcp://0.0.0.0:80")
17-
assert not scks.is_listening
17+
try:
18+
response = requests.get('http://localhost:8888', verify=False, timeout=10)
19+
# should be never reached
20+
assert not response.status_code == 200
21+
except ReadTimeout:
22+
pass

tasks/main.yml

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,38 @@
11
---
22

3-
- name: Create docker config folder
4-
file:
5-
path: /etc/docker/
6-
state: directory
7-
tags:
8-
- docker
9-
- security
3+
- name: Installing needed packages
4+
become: true
5+
package:
6+
name: "{{ iptables_packages }}"
7+
state: present
8+
update_cache: true
9+
10+
- name: Copy firewall script into place.
11+
template:
12+
src: docker-hardening.bash.j2
13+
dest: /etc/docker-hardening.bash
14+
owner: root
15+
group: root
16+
mode: 0744
17+
18+
- name: Copy firewall init script into place.
19+
template:
20+
src: docker-hardening.init.j2
21+
dest: /etc/init.d/docker-hardening
22+
owner: root
23+
group: root
24+
mode: 0755
25+
when: "ansible_service_mgr != 'systemd'"
26+
27+
- name: Copy firewall systemd unit file into place (for systemd systems).
28+
template:
29+
src: docker-hardening.unit.j2
30+
dest: /etc/systemd/system/docker-hardening.service
31+
owner: root
32+
group: root
33+
mode: 0644
34+
when: "ansible_service_mgr == 'systemd'"
35+
36+
- name: Activate Rules
37+
become: true
38+
shell: /etc/docker-hardening.bash

templates/docker-hardening.bash.j2

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env bash
2+
3+
# Check if the PRE_DOCKER chain exists, if it does there's an existing reference to it.
4+
iptables -C FORWARD -o docker0 -j BEFORE_DOCKER
5+
6+
if [ $? -eq 0 ]; then
7+
# Remove reference (will be re-added again later in this script)
8+
iptables -D FORWARD -o docker0 -j BEFORE_DOCKER
9+
# Flush all existing rules
10+
iptables -F BEFORE_DOCKER
11+
else
12+
# Create the PRE_DOCKER chain
13+
iptables -N BEFORE_DOCKER
14+
fi
15+
16+
# Default action
17+
iptables -I BEFORE_DOCKER -j DROP
18+
# Docker internal use
19+
iptables -I BEFORE_DOCKER -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
20+
iptables -I BEFORE_DOCKER -i docker0 -o docker0 -j ACCEPT
21+
# allow connections from local interface
22+
iptables -I BEFORE_DOCKER -i lo -j ACCEPT
23+
# Finally insert the BEFORE_DOCKER table before the DOCKER table in the FORWARD chain.
24+
iptables -I FORWARD -o docker0 -j BEFORE_DOCKER

templates/docker-hardening.init.j2

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#! /bin/sh
2+
# /etc/init.d/docker-hardening
3+
#
4+
# Firewall init script, to be used with /etc/firewall.bash by Jeff Geerling.
5+
#
6+
# @author Jeff Geerling
7+
8+
### BEGIN INIT INFO
9+
# Provides: docker-hardening
10+
# Required-Start: $remote_fs $syslog
11+
# Required-Stop: $remote_fs $syslog
12+
# Default-Start: 2 3 4 5
13+
# Default-Stop: 0 1 6
14+
# Short-Description: Start docker-hardening at boot time.
15+
# Description: Enable the docker-hardening.
16+
### END INIT INFO
17+
18+
# Carry out specific functions when asked to by the system
19+
case "$1" in
20+
start)
21+
echo "Starting firewall."
22+
/etc/docker-hardening.bash
23+
;;
24+
stop)
25+
echo "Stopping firewall."
26+
iptables -F
27+
if [ -x "$(which ip6tables 2>/dev/null)" ]; then
28+
ip6tables -F
29+
fi
30+
;;
31+
restart)
32+
echo "Restarting firewall."
33+
/etc/docker-hardening.bash
34+
;;
35+
status)
36+
echo -e "`iptables -L -n`"
37+
EXIT=4 # program or service status is unknown
38+
NUMBER_OF_RULES=$(iptables-save | grep '^\-' | wc -l)
39+
if [ 0 -eq $NUMBER_OF_RULES ]; then
40+
EXIT=3 # program is not running
41+
else
42+
EXIT=0 # program is running or service is OK
43+
fi
44+
exit $EXIT
45+
;;
46+
*)
47+
echo "Usage: /etc/init.d/firewall {start|stop|status|restart}"
48+
exit 1
49+
;;
50+
esac
51+
52+
exit 0

templates/docker-hardening.unit.j2

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[Unit]
2+
Description=docker-hardening
3+
After=syslog.target network.target
4+
5+
[Service]
6+
Type=oneshot
7+
ExecStart=/etc/docker-hardening.bash
8+
ExecStop=/sbin/iptables -F
9+
RemainAfterExit=yes
10+
11+
[Install]
12+
WantedBy=multi-user.target

0 commit comments

Comments
 (0)