0

My app has a Flask backend and an Angular/Electron frontend. The app runs locally on Mac Catalina. Flask, Celery and Redis are in separate docker containers, while the frontend is outside Docker. The Flask container is listening on port 0.0.0.0:5078. I've set CORS policy to allow messages from "127.0.0.1:4200" only, sent by the frontend. There's no need for internet connections. The backend containers will be launched by the frontend by emulating a terminal command. I'll install the app remotely on non-technical users' Catalina MacBooks.

Question: According to Docker might be exposing ports to the world, Beware of exposing ports in Docker and Docker not blocked by macOS firewall, this use of 0.0.0.0:5078 is a security threat. How can I resolve this threat, eg by block ing any external connections to this port?

Here's some python 3.8 code

# imports: waitress, flask_cors, blueprint
cors = CORS(blueprint, resources={r"/*": {"origins":["http://127.0.0.1:4200"]}})
if __name__ == "__main__":
      serve(flask_app, host= '0.0.0.0', port=5078, threads=8)

Here's the Dockerfile:

FROM python:3.8.3-slim-buster
WORKDIR /app
COPY requirements.txt requirements.txt
ENV BUILD_DEPS="build-essential" \
    APP_DEPS="curl libpq-dev"

RUN apt-get update \
  && apt-get install -y ${BUILD_DEPS} ${APP_DEPS} --no-install-recommends \
  && pip install --default-timeout=10000 -r requirements.txt

ARG FLASK_ENV="development"
ENV FLASK_ENV="${FLASK_ENV}" \
    FLASK_APP="back5x.api.app" \
    PYTHONUNBUFFERED="true"\
    FLASK_DEBUG=1
COPY  . .

RUN ["chmod", "+x", "/app/docker-entrypoint.sh"]
ENTRYPOINT ["/app/docker-entrypoint.sh"]
EXPOSE 5078
CMD ["python", "main.py"]

and the docker-compose:

version: "3.8"
services:
  redis:
 #    ...

  web:
    build:
      context: "."
      args:
        - "FLASK_ENV=development"
    depends_on:
      - "redis"
      - "worker"
    env_file:
      - ".env"
    environment:
      FLASK_DEBUG: 1
      FLASK_APP: back5x.api.app.py
    healthcheck:
      test: "${DOCKER_HEALTHCHECK_TEST:-curl localhost:5078/healthy}"
        ...
    ports:
      - "5078:5078"
    restart: "unless-stopped"
    volumes:
       - ".:/app"

  worker: #celery worker
    ...
volumes:
  redis: {}

Tried: The Docker-based solutions I've found use Linux iptables, eg Disallow egress from Docker containers on Docker for Mac and the above references. So I've added these to the Dockerfile:

RUN apt-get install -y iptables  --no-install-recommends   #after pip install
RUN iptables -N DOCKER-USER   #after COPY . .
RUN iptables -I FORWARD -j DOCKER-USER
RUN iptables -A DOCKER-USER -j RETURN
RUN iptables -I DOCKER-USER -i eth0 ! -s 0.0.0.0 -j DROP

Without the middle three lines, I got an error that DOCKER-USER couldn't be found; with them, that I must run as a root. I've tried a privileged mode and app_cap, but as I'm new to Docker, I haven't got this to work.

I've also looked into defining a rule in Mac's PF firewall to block external connections to the port in question. However, this is not ideal for people who'll be using my app. A similar situation is with installing the paid-for "Little Snitch" app.

Before going this route, could there be a code or Docker-based solution? (Or perhaps there is an appropriate command for launching the backend?)

schrödingcöder
  • 565
  • 1
  • 9
  • 18
  • You should be able to set `ports: ['127.0.0.1:5078:5078']` and similar to set Docker to not publish ports externally. `iptables` won't work on a MacOS host, or from a Dockerfile. (Can you rewrite the "backend" parts in native Javascript, and have a single self-contained non-Docker Electron app?) – David Maze Sep 02 '20 at 16:13
  • @DavidMaze got lots of python stats/number-crunching stuff on the backend, so will be hard to do in JS. AFAIK Electron/GUI not a good match to put inside docker. But will setting ports: ['127.0.0.1:5078:5078'] solve the problem? Ie, still get messages from Electron but no internet connections? – schrödingercöder – schrödingcöder Sep 02 '20 at 16:17
  • Using `127.0.0.1:5078` will allow only processes running on your Mac to connect. The egress question linked solves a different problem, outbound traffic coming from containers. – Matt Sep 03 '20 at 01:36

1 Answers1

0

A working solution is based on the David Maze and Matt's comments and on this question. These are the steps:

  1. Open Docker for Mac, Preferences and Docker Engine. Add "ip": "127.0.0.1" to the json config file.
  2. In the docker-compose, set ports to "127.0.0.1:5078:5078" for the web service.
  3. Leave the Dockerfile and the python code as before: ie, the flask host is still 0.0.0.0

As I checked, messages from Electron's localhost 4201 went through. Also, running netstat -anvp tcp | awk 'NR<3 || /LISTEN/ shows that the insecure port 0.0.0.0.5078 is no longer exposed to the outside. `

schrödingcöder
  • 565
  • 1
  • 9
  • 18