7

Just set up an ElasticSearch container to use with company's Laravel app. Creating docker-compose.yml and running it is flawless and straight-forward but the issue occurs when I want to firewall this thing so that it's only accessible from one, specific IP of the mentioned Laravel app.

While researching I noticed that plenty of people are having these issues where traffic to port forwarded by Docker gets completely exposed to the public and that they're unable to firewall it properly.

I found several solutions in last 6 hours of which none were working. I assume this has something to do with the way Docker processes/forwards the inbound traffic and my iptables knowledge isn't so vast so that I could understand what's happening on my own.

This is my docker-compose.yml (for what it's worth):

version: '3.4'

services:

  elasticsearch:
    image: khezen/elasticsearch:6.1.1
    container_name: elasticsearch

    environment:
      NETWORK_HOST: 0.0.0.0
      HOSTS: 127.0.0.1
      CLUSTER_NAME: fd-es
      NODE_NAME: node-1
      HEAP_SIZE: 31g
      HTTP_SSL: "true"
      ELASTIC_PWD: "somepasswd"
      HTTP_CORS_ENABLE: "true"
      HTTP_CORS_ALLOW_ORIGIN: /https?:\/\/localhost(:[0-9]+)?/

    ulimits:
      memlock:
       soft: -1
       hard: -1

    volumes:
      - ./config/elasticsearch.yml:/elasticsearch/config/elasticsearch.yml
      - ./data:/elasticsearch/data
      - ./logs:/elasticsearch/logs

    ports:
      - 9200:9200

    networks:
      - es-network

    restart: always

networks:
  es-network:
    driver: bridge

These are my currently used iptables rules which are to some degree what I want but all traffic to port 9200 from any client is still let through instead of being accessible only from my app:

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [779:162776]
:DOCKER - [0:0]
-A DOCKER -s xxx.xxx.xxx.xxx -p tcp -m tcp --dport 9200 -j ACCEPT
-A DOCKER -o docker0 -p tcp -m tcp --dport 9200 -j ACCEPT
-A DOCKER -p tcp --dport 9200 -j DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -p icmp -m icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -p udp -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -p tcp -m tcp --dport 44344 -j ACCEPT
-A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
-A INPUT -p tcp -j REJECT --reject-with tcp-reset
-A INPUT -j REJECT --reject-with icmp-proto-unreachable
-A INPUT -j DROP

I tried disabling Docker's iptables support and disabling bridge networking and tweaked iptables rules couple of dozen of times but to no avail.

I'd appreciate any suggestion and help to make this happen because I'm out of ideas and search results for this problem.

Thanks in advance!

4 Answers 4

3

For everyone looking for solution to this problem, the answer is this:

*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 -p icmp --icmp-type any -j ACCEPT
-A INPUT -j FILTERS

-A DOCKER-USER -i ens33 -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 -j REJECT --reject-with icmp-host-prohibited

COMMIT

Found it here: https://unrouted.io/2017/08/15/docker-firewall/

Works perfectly.

1
  • This solution is very close to what I have too. Now I also found out that this rule: -A INPUT -m conntrack --ctstate INVALID -j DROP blocked the Docker. I wonder why that is. Any idea? Commented May 23, 2020 at 3:41
2

I'll explain my scenario where I've tested what you want to achieve.

I've launched a docker container where port 9010 is forwarded to port 8080:

docker run -p 9010:8080 swaggerapi/swagger-editor

Docker creates a DNAT rule for the PREROUTING chain that forwards traffic from port 9010 to port 8080:

DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:9010 to:172.17.0.5:8080

Docker also creates a rule in the DOCKER chain allowing all traffic sent to the container's IP address and port 8080.

ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.5           tcp dpt:8080

The DOCKER chain is used in the FORWARD chain, where all traffic sent to the docker0 bridge is processed by rules in the DOCKER chain.

DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0

Now I want to filter traffic sent to port 8080 (traffic to port 9010 has been processed by the PREROUTING and now it's traffic sent to port 8080) blocking all IP addresses while allowing traffic from IP 192.168.1.142. The IP address of the container could be added to those rules for increased granularity of course.

I add the following rules at the beginning of the FORWARD chain. Alternatively you could replace FORWARD with DOCKER.

iptables -I FORWARD -p tcp --dport 8080 -j DROP
iptables -I FORWARD -p tcp -s 192.168.1.142 --dport 8080 -j ACCEPT

Thanks to those rules only IP 192.168.1.142 can reach the 8080 port used by the container.

For those visiting this answer if you only want to allow one specific IP address to access the containers use the iptables command suggested in the Docker docs.

2
  • I tried a bunch of stuff, don't remember if this was one of them but sometime last night I found this article which helped a lot and the thing works as I wanted. Now I have this set of iptables rules which are working but would like an opinion on if it can be locked down even further. I'm asking that solely based on rules simplicity, it looks unbelievably simple and effective for this on the first sight complex thing.
    – dzhi
    Commented Jan 30, 2018 at 9:27
  • I see, glad you found another alternative. I tried to answer your initial question adding two rules to the FORWARD chain in a default Docker installation in Linux which has helped me to filter traffic as you requested. As you're asking an opinion about your new configuration and if it can be locked down, I'd say I'm not an iptables expert and I'd prefer not to break the opinion-based culture of stack overflow/serverfault :) Commented Jan 30, 2018 at 10:01
0

I think adding this line would have fixed your issue :

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [779:162776]
:DOCKER - [0:0]

**-A INPUT -p tcp -m tcp --dport 9200 -j DOCKER**

-A DOCKER -s xxx.xxx.xxx.xxx -p tcp -m tcp --dport 9200 -j ACCEPT
0

The slight issue I was having with the accepted answer was that it was allowing access to the container ports without any regard to the published port. It would work in an ideal scenario where you would do: -p 80:80 or -p 8080:8080.

In case of -p 8080:80, you have to follow this comment here: https://github.com/moby/moby/issues/22054#issuecomment-466663033

Basically the NAT happens before the filtering, so --dport wouldn't work, just like this person asked: https://github.com/moby/moby/issues/22054#issuecomment-426655503

So I have modified the accepted answer as follows and following this example:

*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

# BASIC Allow
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp --icmp-type any -j ACCEPT

# Chain to FILTERS
-A INPUT -j FILTERS
-A DOCKER-USER -i eth0 -j FILTERS

# ALLOW outgoing traffic
-A FILTERS -m state --state ESTABLISHED,RELATED -j ACCEPT

# COMMON FIREWALL RULES

# ALLOW SSH ON THE HOST
-A FILTERS -p tcp -m multiport --dports 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

# ALLOW the following ports on the containers as well.
-A FILTERS -p tcp -m conntrack --ctstate NEW,ESTABLISHED --ctorigdstport 80 -j ACCEPT
-A FILTERS -p tcp -m conntrack --ctstate NEW,ESTABLISHED --ctorigdstport 443 -j ACCEPT

# DENY something
#-A FILTERS -p icmp --icmp-type echo-request -j REJECT

# FINAL REJECT
-A FILTERS -j REJECT

COMMIT

Caveats

(because why not)

If you allow a port on the host level, as in my example above where I allowed SSH.

Please note that this will allow any containers running on the same port, regardless of the published port, to be accessible to everyone.

For Example:

If I allow port 8080, you can test by running an ncat container, docker run --rm -p 8081:8080 subfuzion/netcat -l 8080 and then using ncat from another machine, ncat <docker container host public ip>:8081 and you will see the connection goes through.

A work around could be (that I haven't tested) to only allow ports using --ctorigdstport but I am not sure if it would lock you out of your machine, like so (this is not tested):

-A FILTERS -p tcp -m conntrack --ctstate NEW,ESTABLISHED --ctorigdstport 22 -j ACCEPT

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .