Iptables rules with dockerized web application - can't block incoming traffic - docker

i'm hosting a dockerized web application binded with port 8081 in my remote server.
I want to block that web application for external ips, as I already did wit port 8080 hosting a plain jenkins server.
Here's what i've tried:
iptables -A INPUT -d <my-server-ip> -p tcp --dport 8081 -j DROP
As I did with port 8080.
Here is
iptables -nv -L INPUT
output:
Chain INPUT (policy ACCEPT 2836 packets, 590K bytes)
pkts bytes target prot opt in out source destination
495 23676 DROP tcp -- * * 0.0.0.0/0 <my-ip-addr> tcp dpt:8080
0 0 DROP tcp -- * * 0.0.0.0/0 <my-ip-addr> tcp dpt:8081
Has it possibily something to do with DOCKER chain in iptables ?
Chain DOCKER (1 references)
pkts bytes target prot opt in out source destination
9 568 ACCEPT tcp -- !docker0 docker0 0.0.0.0/0 <container-eth1-addr> tcp dpt:8080
There are more specific rules i need to add ?
Isn't my server INPUT rules supposed to be applied before those listed in the DOCKER chain?
UPDATE - SOLVED
Thanks to larsks's comments I found the solution.
The goal here was to block tcp traffic on port 8081 binded with docker docker container but being able to use ssh tunneling as "poor man" VPN (so non publish the port was not an option).
Just had to add this rule:
iptables -I DOCKER-USER 1 -d <container-eth-ip> ! -s 127.0.0.1 -p tcp --dport 8080 -j DROP

Related

Cannot access Grafana (Docker image) using port 3000 from non-local server

I'm working on a Ubuntu server with Docker, in which I ran the Grafana Docker Image using the following commands (based on documentation: https://grafana.com/docs/grafana/latest/installation/docker):
docker volume create grafana-storage
docker run -d -p 3000:3000 --name=grafana -v grafana-storage:/var/lib/grafana grafana/grafana-oss
I can access to my Grafana instance successfully using localhost:3000, but the issue is that I cannot access from any external server x.x.x.x:3000. Not even locally unless I use localhost.
Nevertheless, I can access from an external server to x.x.x.x (port 80, which is being used by another process) from an external device.
I proceeded to check port configuration details:
netstat -an | grep "3000"
tcp 0 0 yy.yy.yy.yy:53596 yy.yy.yy.yy:3000 ESTABLISHED
tcp 0 0 yy.yy.yy.yy:53716 yy.yy.yy.yy:3000 ESTABLISHED
tcp 0 0 yy.yy.yy.yy:53700 yy.yy.yy.yy:3000 ESTABLISHED
tcp6 0 0 :::3000 :::* LISTEN
tcp6 0 0 ::1:3000 ::1:50602 ESTABLISHED
tcp6 0 0 ::1:50706 ::1:3000 ESTABLISHED
tcp6 0 0 ::1:3000 ::1:50706 ESTABLISHED
tcp6 0 0 ::1:50602 ::1:3000 ESTABLISHED
iptables -S | grep "3000"
-A INPUT -p tcp -m tcp --dport 3000 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 3000 -j ACCEPT
-A OUTPUT -p tcp -m tcp --sport 3000 -j ACCEPT
-A DOCKER -d yy.yy.yy.yy/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 3000 -j ACCEPT
iptables -L INPUT -nvx
Chain INPUT (policy ACCEPT 659 packets, 98721 bytes)
pkts bytes target prot opt in out source destination
136 8160 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3000
0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3000
And noted that firewall was inactive, then I enable it and put a rule:
sudo ufw enable
sudo ufw allow 3000/tcp comment 'grafana-port'
sudo ufw reload
sudo ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] 3000/tcp ALLOW IN Anywhere # grafana-port
[ 2] 3000/tcp (v6) ALLOW IN Anywhere (v6) # grafana-port
Applying this change did not make x.x.x.x:3000 work. However, port 80 is still working normally as I mentioned. Maybe I am missing something else.
More info: I tried using domain = x.x.x.x and serve_from_subpath = true in grafana.ini, defaults.ini and custom.ini files. It did not work.
Have you any advice or contribution in order to solve this issue? Thank you in advance!
Have you tried the following?
docker run -p x.x.x.x:3000:3000 ...
Issue was solved letting the firewall inactive, and connecting to the server's router at tplinkwifi.net. There I put this router configuration on advanced > NAT forwarding > Port forwarding:
Device IP Address: 192.168.x.x
External Port: 3000
Internal Port: 3000
Protocol: TCP
Then restarted the docker container and got access from non-local server.
Thank you for your suggestions.

iptables with docker port mapping

Iptables rules are notoriously difficult to set up when Docker is running on the host, and I thought I had a definitive solution in this fantastic blog post: https://unrouted.io/2017/08/15/docker-firewall/
The configuration described in this blog post has served me well for a long time, but I'm now facing a problem I never had before.
I am running a docker container that exposes a service on port 465 on the host. Port 465 maps to port 25 in the container. Here's how to simulate such a service:
$ docker run --rm -it -p 465:25 python:3.6 python3 -m http.server 25
My problem is that I cannot access port 465 on my server from the outside:
$ curl mydomain.com:465
curl: (7) Failed to connect to mydomain.com port 465: No route to host
However, and here comes the interesting part, I do manage to access the service if the port on the host maps to the same port in the container. In other words, when I run on the host:
$ docker run --rm -it -p 465:465 python:3.6 python3 -m http.server 465
then I can access the service from the outside:
$ curl mydomain.com:465
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org...
This whole problem is due to my iptables definition: I know that because when I flush the iptables rules, I do manage to access the service from outside, whatever the port mapping.
Here are my iptable rules:
*filter
# Source: https://unrouted.io/2017/08/15/docker-firewall/
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:FILTERS - [0:0]
:DOCKER-USER - [0:0]
-F INPUT
-F DOCKER-USER
-F FILTERS
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -s 127.0.0.0/8 -j REJECT
-A INPUT -p icmp --icmp-type any -j ACCEPT
-A INPUT -j FILTERS
-A DOCKER-USER -i eth0 -j FILTERS
-A FILTERS -m state --state ESTABLISHED,RELATED -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 465 -j ACCEPT
-A FILTERS -j REJECT --reject-with icmp-host-prohibited
COMMIT
How should I modify my iptables so that I can access my container from the outside, whatever the port mapping?
EDIT:
Here are the complete iptables rules in the failing scenario (465:25 mapping):
$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
REJECT all -- loopback/8 anywhere reject-with icmp-port-unreachable
ACCEPT icmp -- anywhere anywhere icmp any
FILTERS all -- anywhere anywhere
Chain FORWARD (policy DROP)
target prot opt source destination
DOCKER-USER all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-1 all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain DOCKER (3 references)
target prot opt source destination
ACCEPT tcp -- anywhere 172.19.0.4 tcp dpt:3000
ACCEPT tcp -- anywhere 172.17.0.3 tcp dpt:smtp
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target prot opt source destination
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
RETURN all -- anywhere anywhere
Chain DOCKER-ISOLATION-STAGE-2 (3 references)
target prot opt source destination
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
RETURN all -- anywhere anywhere
Chain DOCKER-USER (1 references)
target prot opt source destination
FILTERS all -- anywhere anywhere
Chain FILTERS (2 references)
target prot opt source destination
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:ssh
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:http
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:https
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:urd
REJECT all -- anywhere anywhere reject-with icmp-host-prohibited
Thanks for reaching out to me on Twitter. I've actually looked into this issue before without someone else who noticed it and I think I know whats happening. In your example:
docker run --rm -it -p 465:25 python:3.6 python3 -m http.server 25
If you look at your full firewall config with iptables-save you'll see a bunch of NAT rules. You'll probably see something that looks like this in the *nat section:
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
... snip ...
-A DOCKER ! -i br-abbaabbaabba -p tcp -m tcp --dport 465 -j DNAT --to-destination 172.18.0.10:25
So this rule is executed in the PREROUTING phase and rewrites the incoming packet to look like it was always for port 25 and not port 465. And this happens before the filter tables INPUT chain runs.
So I reckon if you allowed traffic to port 25, then actually you would be able to access port 465 too. Obviously you don't want to allow access to all port 25 because that includes your host's port 25.
All the usual tricks you'd do at this point are made that much more difficult because of Docker.
Option 1
You could go down the explicit is better than implicit route and split the host vs docker rules:
*filter
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:FILTERS - [0:0]
:DOCKER-USER - [0:0]
-F INPUT
-F DOCKER-USER
-F FILTERS
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -s 127.0.0.0/8 -j REJECT
-A INPUT -p icmp --icmp-type any -j ACCEPT
# Rules for services running on the host:
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
# Rules for services running in containers:
-A DOCKER-USER -i eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT
# This says dport 25, but is actually 465. Yay for prerouting + NAT.
# Service on real host port 25 should still be inaccessible because DOCKER-USER
# is only accessible via `FORWARD` and not `INPUT`...
-A DOCKER-USER -i eth0 -m state --state NEW -m tcp -p tcp --dport 25 -j ACCEPT
-A DOCKER-USER -j REJECT --reject-with icmp-host-prohibited
COMMIT
It's still unsatisfying that you are allowing traffic to port 25..
Option 2
I believe right now Docker doesn't put anything in *raw or *mangle so its safe to add your own rules there. Obviously there are limitations with these tables (raw is before connection tracking, mangle is only for marking connections) so thats not great either.
Option 3
Finally, the only other thing I can think the conntrack iptables module might have the answer with --ctorigdstport, but i've never tried it myself. Looking at this you could try:
iptables -A FILTERS -p tcp --dport 25 -m conntrack --ctstate NEW --ctorigdstport 465 -j ACCEPT
A bit ugly to look at, but explicit about what is happening. If you try this one and it works let me know and i'll see about writing it up / updating that blog post.

How to connect youtrack, upsource and teamcity to hub all running as docker containers on the same machine

Since we are a small team we want to put JetBrains' Hub, Youtrack, Upsource and Teamcity as docker containers (all on the same machine for now). Docker is running on Photon OS 2.0 running on ESXi 6.7. Nginx in another container acts as a DNS proxy so all services are reachable with their own domain names on port 80 for now...
I got all 5 services running and and can access them in a browser. However connecting Youtrack, Upsource and Teamcity to Hub is a challenge. Youtrack, Upsource and Teamcity ask for the Hub URL to confirm it and ask for permission to access Hub.
The Problem:
Hub URL: http://hub.teamtools.mydomain.com -> the container can not access it under that address and verification fails with timeout
Hub URL: http://172.18.0.3:8080 -> the container can access Hub on the internal docker net and then shows a pop up which is trying to show a confirmation page by redirecting to Hub on that internal IP which of course fails in the browser (I tried to copy the URL from the popup into a new window and adjust it there as a hack but that does not work.)
Questions:
How can I link Youtrack, Upsource and Teamcity to the Hub? In order for the confimation process to work the docker containers need to be able to access each other with an external IP/domain name.
Does anything speak against having all four Teamtools on the same machine to get started and separate them later as demand grows?
Configuration so far:
The containers have been turned into services like so:
/etc/systemd/system/docker.nginx.servcie
[Unit]
Description=Nginx DNS proxy
After=docker.service
Requires=docker.service
[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=/usr/bin/docker network create --subnet=172.18.0.0/16 dockerNet
ExecStartPre=-/usr/bin/docker exec %n stop
ExecStartPre=-/usr/bin/docker rm %n
ExecStartPre=/usr/bin/docker pull jwilder/nginx-proxy
ExecStart=/usr/bin/docker run --rm --name %n \
-v /var/run/docker.sock:/tmp/docker.sock:ro \
--net dockerNet --ip 172.18.0.2 \
-p 80:80 \
jwilder/nginx-proxy
[Install]
WantedBy=multi-user.target
/etc/systemd/system/docker.hub.service
[Unit]
Description=JetBrains Hub Service
After=docker.nginx-proxy.service
Requires=docker.nginx-proxy.service
[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker exec %n stop
ExecStartPre=-/usr/bin/docker rm %n
ExecStartPre=/usr/bin/docker pull jetbrains/hub:2018.2.9635
ExecStart=/usr/bin/docker run --rm --name %n \
-v /opt/hub/data:/opt/hub/data \
-v /opt/hub/conf:/opt/hub/conf \
-v /opt/hub/logs:/opt/hub/logs \
-v /opt/hub/backups:/opt/hub/backups \
--net dockerNet --ip 172.18.0.3 \
-p 8010:8080 \
--expose 8080 \
-e VIRTUAL_PORT=8080 \
-e VIRTUAL_HOST=hub,teamtools.mydomain.com,hub.teamtools.mydomain.com \
jetbrains/hub:2018.2.9635
[Install]
WantedBy=multi-user.target
... and so on. Since I'm, still trying things out, the ports are mapped on the host and exposed so nginx-proxy can pick them up. I also added static IPs to the containers hoping this would help with my problem.
Running those services results in:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7ba8ed89b832 jetbrains/teamcity-server "/run-services.sh" 12 hours ago Up 12 hours 0.0.0.0:8111->8111/tcp docker.teamcity.service
5c819c48cbcc jetbrains/upsource:2018.1.357 "/bin/bash /run.sh" 12 hours ago Up 12 hours 0.0.0.0:8030->8080/tcp docker.upsource.service
cf9dcd1b534c jetbrains/youtrack:2018.2.42223 "/bin/bash /run.sh" 14 hours ago Up 14 hours 0.0.0.0:8020->8080/tcp docker.youtrack.service
de86c3e1f2e2 jetbrains/hub:2018.2.9635 "/bin/bash /run.sh" 14 hours ago Up 14 hours 0.0.0.0:8010->8080/tcp docker.hub.service
9df9cb44e485 jwilder/nginx-proxy "/app/docker-entry..." 14 hours ago Up 14 hours 0.0.0.0:80->80/tcp docker.nginx-proxy.service
Additional Info:
I did consider this could be a firewall issue and this post seems to suggest the same thing:
https://forums.docker.com/t/access-docker-container-from-inside-of-the-container-via-external-url/33271
After some discussion with the provider of the virtual server it
turned out, that conflicting firewall rules between plesk firewall and
iptables caused this problem. After the conflict had been fixed by the
provider the container could be accessed.
Firewall on Photon with rules auto added by docker:
Chain INPUT (policy DROP 2 packets, 203 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0
258 19408 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
6 360 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
186 13066 DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
186 13066 DOCKER-ISOLATION all -- * * 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
103 7224 ACCEPT all -- * br-83f08846fc2e 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
9 524 DOCKER all -- * br-83f08846fc2e 0.0.0.0/0 0.0.0.0/0
74 5318 ACCEPT all -- br-83f08846fc2e !br-83f08846fc2e 0.0.0.0/0 0.0.0.0/0
1 52 ACCEPT all -- br-83f08846fc2e br-83f08846fc2e 0.0.0.0/0 0.0.0.0/0
Chain OUTPUT (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
300 78566 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
8 472 ACCEPT tcp -- !br-83f08846fc2e br-83f08846fc2e 0.0.0.0/0 172.18.0.2 tcp dpt:80
0 0 ACCEPT tcp -- !br-83f08846fc2e br-83f08846fc2e 0.0.0.0/0 172.18.0.3 tcp dpt:8080
0 0 ACCEPT tcp -- !br-83f08846fc2e br-83f08846fc2e 0.0.0.0/0 172.18.0.5 tcp dpt:8080
0 0 ACCEPT tcp -- !br-83f08846fc2e br-83f08846fc2e 0.0.0.0/0 172.18.0.4 tcp dpt:8080
0 0 ACCEPT tcp -- !br-83f08846fc2e br-83f08846fc2e 0.0.0.0/0 172.18.0.6 tcp dpt:8111
Chain DOCKER-ISOLATION (1 references)
pkts bytes target prot opt in out source destination
0 0 DROP all -- br-83f08846fc2e docker0 0.0.0.0/0 0.0.0.0/0
0 0 DROP all -- docker0 br-83f08846fc2e 0.0.0.0/0 0.0.0.0/0
186 13066 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-USER (1 references)
pkts bytes target prot opt in out source destination
186 13066 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
It turned out to be a firewall issue.
I resolved it using this https://unrouted.io/2017/08/15/docker-firewall/ as a starting point.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:FILTERS - [0:0]
:DOCKER-USER - [0:0]
-F INPUT
-F DOCKER-USER
-F FILTERS
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp --icmp-type any -j ACCEPT
-A INPUT -j FILTERS
-A DOCKER-USER -i eth0 -j FILTERS
-A FILTERS -m state --state ESTABLISHED,RELATED -j ACCEPT
# full access from my workstation
-A FILTERS -m state --state NEW -s 192.168.0.10/32
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport 3306 -j ACCEPT
-A FILTERS -j REJECT --reject-with icmp-host-prohibited
COMMIT

Proxying Docker container for outgoing connections

My application need access webservice requires white list IP.
Since I have many app servers, i end up a solution like this
iptables -t nat -A OUTPUT -p tcp --dport 80 -d a.webservices.dimension.abc.com -j DNAT --to 52.1.2.3:9999
that means outgoing connection to a.webservices.dimension.abc.com will be routed to 52.1.2.3:9999 ( a proxy server )
It works.
And now I move my application server ( ec2 vm) to Docker container. But doing this, the iptables rule doesn't work.
here is the final rules
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCALChain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- anywhere !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
DNAT tcp -- anywhere 1.2.3.4 tcp dpt:http to:52.1.2.3:9999
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- ip-172-17-0-0.us-west-2.compute.internal/16 anywhere
MASQUERADE tcp -- ip-172-17-0-2.us-west-2.compute.internal ip-172-17-0-2.us-west-2.compute.internal tcp dpt:http
Chain DOCKER (2 references)
target prot opt source destination
LOG all -- anywhere anywhere limit: avg 2/min burst 5 LOG level warning prefix "DOCKER CHAIN "
RETURN all -- anywhere anywhere
DNAT tcp -- anywhere anywhere tcp dpt:9090 to:172.17.0.2:80
As a result, the following command d
docker exec -it some-nginx curl -iL 1.2.3.4
doesn't work
BUT
curl -iL 1.2.3.4
works
I don't know why.
Proxying outgoing connection from Docker container should be common but I haven't found out any existing solution.
Please help.
UPDATE
The following link
https://serverfault.com/questions/734526/whitelisting-outgoing-traffic-from-docker-containers
shed me some lights.
use this rule to make it work
iptables -t nat -A PREROUTING -i docker0 -p tcp a.webservices.dimension.abc.com -j DNAT --to 52.1.2.3:9999

Docker ignores iptable rules when using "-p <port>:<port>"

Just realized a few days ago that Docker seems to bypass my iptable rules. I am not incredible experienced with Docker nor iptables. Tried a lot of different things the last days. Also saw that there was big change in recent docker versions with a special DOCKER-chain that should allow me to do that. However not sure what I am doing wrong but it never does what I expect it to do.
So what I want is quite simple. I want that it behaves like expected. That if I have an ACCEPT-Rule to go through and if not it gets blocked.
My iptable looked originally like that (so before my many unsuccessful attempts):
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [779:162776]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -s 1.2.3.4 -p tcp -m tcp --dport 123 -j ACCEPT
-A INPUT -j DROP
COMMIT
Hoped that it does exactly what I want. Just allow access to ports 22 and 80 and also allow port 123 from the ip 1.2.3.4. However If I create a container with "-p 123:123" everybody can access it. Can anybody help me and tell me how I have to change the above file?
Thanks!
Docker-Version: 1.6.2
Edit:
Left initially my different tries out to not overcomplicate the question. However adding at least one of them could maybe be helpful.
*nat
:PREROUTING ACCEPT [319:17164]
:INPUT ACCEPT [8:436]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [16:960]
:DOCKER - [0:0]
COMMIT
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [779:162776]
:DOCKER - [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A DOCKER -s 1.2.3.4 -p tcp -m tcp --dport 123 -j ACCEPT
-A DOCKER -j DROP
-A INPUT -j DROP
COMMIT
The above kind of works. However get then a lot of other problems. For example do I get problems with container linking, DNS does not work anymore, and so on. So then end up adding a lot of additional rules to fix that issues but I get never to a state where it runs properly. So I guess there most be better and easier solution out there.
Solution:
Ended up doing more or less exactly what larsks said. Just did not add it to the FORWARD chain, I added it to the DOCKER chain instead. The problem with the FORWARD chain is that Docker adds its stuff in there when it restarts in first position. Which results in having my rules getting pushed down and not having any effect. However for the DOCKER chain it seems Docker appends only additional rules so mine stay in effect. So when I save my rules and then restart the server everything still works fine.
So now it looks more or less like that:
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [779:162776]
:DOCKER - [0:0]
# That I can access from IP 1.2.3.4
-A DOCKER -s 1.2.3.4/32 -p tcp -m tcp --dport 123 -j ACCEPT
# That I can access from other Docker containers
-A DOCKER -o docker0 -p tcp -m tcp --dport 123 -j ACCEPT
# Does not allow it for anything else
-A DOCKER -p tcp --dport 123 -j DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -j DROP
COMMIT
I'm not an expert on iptables but I know that if you run the container with -p 127.0.0.1:123:123 then the port won't be exposed on all interfaces, just on the loopback.
Your iptables configuration looks a little broken right now, as if you cleared it out at some point without restarting Docker. For example, you have a DOCKER chain available in both the filter and nat tables, but no rules that reference it, so rules placed in that chain will have no affect.
In general, if you want to implement iptables rules that affect your Docker containers they need to go in the FORWARD chain of the filter table. Each container has it's own ip address, which means that your host is simply accepting packets and then FORWARDing them to the container address.
Rules in the INPUT chain are only for packets with a final destination of an address on an interface in the host's global network namespace.
However, I'm not sure that iptables is actually your problem.
If you are trying to expose services in containers such that they are available to other systems, you need to publish those ports using the -p flag to docker run. You can read more about that
in this section of the documentation.
If you want to update your question with a specific example of what you are trying to accomplish I can provide a more targeted answer.
Update
It's true that when you publish a container port using -p it will generally be available to any source ip address. In order to restrict access to a published port you would need to add a new rule to your FORWARD chain. For example, if I start a web server:
docker run --name web -p 80:8080 larsks/mini-httpd
The web server in the container is now available on port 8080 on my host. If I want to block access to this port, I need to insert a rule into the FORWARD chain that blocks access to port 80 on the container ip. So first I need the container ip address:
$ web_ip=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' web)
$ echo $web_ip
172.17.0.5
The rule I create in the FORWARD chain needs to come before the rules that docker creates, so I will need to specify an explicit position:
iptables -I FORWARD 1 -d $web_ip -p tcp --dport 80 \! -s 192.168.1.10 -j DROP
This would block all traffic from hosts other than 192.168.1.10.
If you want a rule to apply to all containers, rather than a specific container, you can bind it to the docker0 interface rather than a specific ip address:
-A FORWARD -o docker0 -p tcp --dport 80 \! -s 192.168.1.10 -j DROP
This would prohibit access to port 80 on any container.
Ended up doing more or less exactly what larsks said. Just did not add
it to the FORWARD chain, I added it to the DOCKER chain instead.
I've found the same in the docs: https://docs.docker.com/v1.5/articles/networking/#the-world
Docker will not delete or modify any pre-existing rules from the
DOCKER filter chain. This allows the user to create in advance any
rules required to further restrict access to the containers.
Docker's forward rules permit all external source IPs by default. To
allow only a specific IP or network to access the containers, insert a
negated rule at the top of the DOCKER filter chain. For example, to
restrict external access such that only source IP 8.8.8.8 can access
the containers, the following rule could be added:
$ iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j DROP
To use iptables on published ports from docker containers, you need a combination of things:
DOCKER-USER table: docker uses this table for iptables rules that affect containers and is reserved specifically for user provided rules that won't be overwritten by the docker engine when it restarts.
conntrack: port forwarding can publish on one port and forward to another in the container. You can have multiple containers all listening on port 80 with different published ports on the host.
To use these, the resulting iptables rule looks like:
iptables -I DOCKER-USER -i eth0 -s 10.0.0.0/24 -p tcp \
-m conntrack --ctorigdstport 8080 -j ACCEPT
iptables -I DOCKER-USER -i eth0 ! -s 10.0.0.0/24 -p tcp \
-m conntrack --ctorigdstport 8080 -j DROP
This handles requests to the published port 8080/tcp (that's on the host, the container could be listening on 80 or any other port), and only accepts the requests from the 10.0.0.0/24 subnet. Everything outside of that subnet is dropped.
Note that the DOCKER-USER table has a default rule to immediately return, so all changes should be inserted before that default rule in the table.
Given: Debian stretch, docker 18.06, and a docker process created via
docker run ... -p 5678:1234 ...
Required: Access to the docker container restricted to multiple external subnetworks.
Solution: (using some example subnetworks)
iptables -I DOCKER-USER -p tcp --dport 1234 -j REJECT
iptables -I DOCKER-USER -s 18.204.0.0/16 -p tcp --dport 1234 -j RETURN
iptables -I DOCKER-USER -s 34.192.0.0/16 -p tcp --dport 1234 -j RETURN
iptables -I DOCKER-USER -s 35.153.0.0/16 -p tcp --dport 1234 -j RETURN
iptables -I DOCKER-USER -s 13.56.63.0/24 -p tcp --dport 1234 -j RETURN
Persist the changed rules using iptables-save:
iptables-save > /etc/iptables/rules.v4
Result:
iptables -L DOCKER-USER -n -v --line-numbers
Chain DOCKER-USER (1 references)
pkts bytes target prot opt in out source destination
0 0 RETURN tcp -- * * 13.56.63.0/24 0.0.0.0/0 tcp dpt:1234
0 0 RETURN tcp -- * * 35.153.0.0/16 0.0.0.0/0 tcp dpt:1234
0 0 RETURN tcp -- * * 34.192.0.0/16 0.0.0.0/0 tcp dpt:1234
0 0 RETURN tcp -- * * 18.204.0.0/16 0.0.0.0/0 tcp dpt:1234
0 0 REJECT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1234 reject-with icmp-port-unreachable
Background 1: Rules
All rules are added to the chain DOCKER-USER as recommended by the current docker documentation.
The first rule targets REJECT and will end up as the last rule, since the other rules are added on top (option -I without a position number corresponds to adding a rule at position 1). All packages with destination port 1234 reaching this rule will be rejected.
The other rules target RETURN, i.e. a package with destination port 1234 and source IP from one of the given subnetworks will be returned to the calling chain, which is the FORWARD chain.
iptables -L FORWARD -n -v
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
16471 4568K DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
16413 4565K DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
7173 2060K ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
45 2340 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
From the FORWARD chain, it will be processed by the DOCKER chain, where it is forwarded as desired to the docker container:
iptables -L DOCKER -n -v
Chain DOCKER (1 references)
pkts bytes target prot opt in out source destination
45 2340 ACCEPT tcp -- !docker0 docker0 0.0.0.0/0 172.17.0.2 tcp dpt:1234
Background 2: Port value
We don't use the external port value 5678 in the forward rule, because the destination port is changed via a rule automatically created by docker and applied before the forward chain is executed. See top section of /etc/iptables/rules.v4:
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
...
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 5678 -j DNAT --to-destination 172.17.0.2:1234

Resources