Andrew Kovalenko
Andrew Kovalenko

Reputation: 6710

Allow traffic from localhost to docker container

I'm trying to host my backend services on a Ubuntu 16.04 server with docker. There is an nginx handling all HTTP requests and proxy-passing them to backend services.

With iptables INPUT and OUTPUT ACCEPT - everything works perfectly, however if I try to restrict any access except HTTP/HTTPS to nginx - communication between localhost and docker containers breaks.

This is my iptables:

-P INPUT DROP
-P FORWARD DROP
-P OUTPUT DROP
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER

# Drop empty flag packets and sync-flood packets
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A INPUT -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m state --state NEW -j DROP
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,PSH,ACK,URG -j DROP

# Allow HTTP/HTTPS
-A INPUT -p tcp -m tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 8080 -m state --state NEW,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

-A OUTPUT -p tcp -m tcp --sport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -p tcp -m tcp --sport 8080 -m state --state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -p tcp -m tcp --sport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT


# Allow DNS
-A INPUT -p udp -m udp --sport 53 -j ACCEPT
-A INPUT -p tcp -m tcp --sport 53 -j ACCEPT
-A OUTPUT -p udp -m udp --dport 53 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 53 -j ACCEPT

# Block ping
-A INPUT -p icmp -m state --state NEW -m icmp --icmp-type 8 -j DROP

# Allow any loopback
-A INPUT -i lo -j ACCEPT
-A OUTPUT -o lo -j ACCEPT

# Allow forwarding from/to localhost to/from docker
-A FORWARD -i docker0 -o lo -j ACCEPT
-A FORWARD -i lo -o docker0 -j ACCEPT

# Docker-generated rules    
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A FORWARD -o br-30c18a0778b5 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o br-30c18a0778b5 -j DOCKER
-A FORWARD -i br-30c18a0778b5 ! -o br-30c18a0778b5 -j ACCEPT
-A FORWARD -i br-30c18a0778b5 -o br-30c18a0778b5 -j ACCEPT
-A DOCKER -d 172.18.0.2/32 ! -i br-30c18a0778b5 -o br-30c18a0778b5 -p tcp -m tcp --dport 27017 -j ACCEPT
-A DOCKER -d 172.18.0.3/32 ! -i br-30c18a0778b5 -o br-30c18a0778b5 -p tcp -m tcp --dport 4000 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -i br-30c18a0778b5 ! -o br-30c18a0778b5 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o br-30c18a0778b5 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN

The container I proxy-pass to is running on port 4000 mapped to 3003 in docker-compose.yml:

 webapi:
    build: .
    depends_on:
      - mongo
    deploy:
      replicas: 1
      resources:
        limits:
          cpus: "0.1"
          memory: 256M
      restart_policy:
        condition: on-failure
    ports:
      - "3003:4000"
    networks:
      - webnet

But if I run curl http://localhost:3003/api/healthcheck - I get curl: (56) Recv failure: Connection reset by peer which is confusing to me since I don't have any restrictions for loopback or forwarding to docker0.

The only idea I have is: forwarding from container's port 4000 to localhost 3003 is blocked, but I can't come up with how to allow it.

Upvotes: 3

Views: 6702

Answers (2)

Crazy Chenz
Crazy Chenz

Reputation: 13188

I too ran into an issue where I created a docker network and only wanted to expose the DNS server and Nginx services. When doing this, everything worked fine external to the system but within the host I didn't have access as long as the INPUT policy was DROP. Adding localhost and the exact IP addresses and ports to the INPUT chain had no effect.

The solution I found was to ACCEPT the docker network bridge interface in the INPUT chain. Docker will by default name this something like br- (e.g. br-22153f050ac4). You can add this nasty label to the iptables config or you can name your docker network bridge interface and add the more deterministic interface name.

Assuming you created your network with:

docker network create -d bridge -o com.docker.network.bridge.name=webnet webnet

You should be able to allow localhost -> docker container with something like:

sudo iptables -A INPUT -i webnet -j ACCEPT

Assuming everything was successful, you should now be able to access the container via a localhost address (i.e. 127.0.0.1) and the docker container address (e.g. 172.X.Y.Z).

Upvotes: 4

BMitch
BMitch

Reputation: 263637

The easy answer to this is to only publish the port on the loopback interface so you don't need to firewall them:

  port:
    - "127.0.0.1:3003:4000"

For debugging the firewall rules, I'd put some logging in there before dropping the packets. Docker uses a lot more than just the docker-0 bridge, and there are various nat/mangle rules, and proxy processes, involved in publishing ports.

Upvotes: 0

Related Questions