My C++ application runs on an Ubuntu Focal server and communicates with another service on a different server via secure web-socket. I test the functionality with a set of local python3 scripts and that all works fine. Now I want to create a docker-image for my service and expose the socket. The container I built runs fine and communicates with a MariaDB container over the docker-network.
My Dockerfile is this:
FROM ubuntu:focal
ENV DEBIAN_FRONTEND noninteractive
ENV TZ=Europe/London
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ \> /etc/timezone
RUN mkdir -p /app
# Updating/upgrading the base image
RUN apt-get -y update
RUN apt-get -y upgrade
# Installing some useful tools
RUN apt-get install -y net-tools
RUN apt-get install -y iproute2
RUN apt-get install -y iputils-ping
RUN apt-get install -y dbus
RUN apt-get install -y firewalld
RUN apt-get install -y openssh-server
RUN apt-get install -y rsync
RUN apt-get install -y mariadb-client
RUN apt-get install -y libmariadb-dev
# Setting up an SSH daemon
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed 's@session\\s*required\\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" \>\> /etc/profile && echo "Port 1022" \>\> /etc/ssh/sshd_config
RUN ssh-keygen -b 1024 -t rsa -f /etc/ssh/ssh_host_key -q -N ""
RUN ssh-keygen -b 1024 -t dsa -f /etc/ssh/ssh_host_dsa_key -q -N ""
# Set shell to bash
SHELL \["/bin/bash", "--login", "-c"\]
# Create directories in this container
RUN mkdir -p /app/deploy/bin
COPY content2copy/deploy/bin/myserv /app/deploy/bin
COPY content2copy/run_AES.sh /app/deploy
EXPOSE 5000
WORKDIR /app/deploy
My docker-compose.yml is the following:
version: "3.3"
services:
  myserv_run:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        progress: plain
    depends_on:
      - mariadb
    command: bash -c /app/deploy/run_MY.sh
    stdin_open: true
    tty: true
    hostname: myserv_servicehost
    image: myserv_run_ubuntu20:latest
    networks:
      - myserv_network
    ports:
      - ${WEBAPI_PORT}:${WEBAPI_PORT}
    volumes:
      - ${MY_LOG_DIR}:/app/log:rw
      - ${MY_CONFIG_DIR}:/app/deploy/config:rw
    cap_add:
      - ALL
  mariadb:
    image: mariadb:10.7
    ports:
      - 3306:${MARIADB_PORT}
    hostname: myserv_dbhost
    command: --init-file /app/deploy/create_myserv_db.sql
    volumes:
      - ${MARIADB_DIR}:/var/lib/mysql:rw
      - ${APPLICATION_FILES_DIR}/create_myserv__db.sql:/app/deploy/create_myserv_db.sql:rw
    networks:
      - myserv_network
    environment:
      MYSQL_ROOT_PASSWORD: somesecret
      MYSQL_DATABASE: myserv_db
      MYSQL_USER: ${MY_DB_USER}
      MYSQL_PASSWORD: ${MY_DB_PASSWORD}
networks:
  myserv_network: {}
When I issue the docker-compose up command, both mariadb and myserv stay up and myserv is writing to the database.
However, a python3 test program that sends json strings via web-port wss://localhost:5000 fails with error
websocket version=0.59.0
[20221230-10:36:27.085823] ERROR: TLS/SSL connection has been closed (EOF) (_ssl.c:997)[20221230-10:36:27.086067] closed ()
However, port 5000 is being listened on
[vm.ubuntu: ~]> ss -tunlp4 | tr -s ' ' ' ' | grep 5000
tcp LISTEN 0 4096 0.0.0.0:5000 0.0.0.0:*
Odd thing is when I try to investigate the port with lsof I only can see it when using root privileges:
[vm.ubuntu: ~]> lsof -i :5000
## no output!
[vm.ubuntu: ~]> sudo lsof -i :5000
COMMAND      PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 193674 root    4u  IPv4 687684      0t0  TCP *:5000 (LISTEN)
docker-pr 193686 root    4u  IPv6 686599      0t0  TCP *:5000 (LISTEN)
When I run the service on a virtual machine (where it works) I use the command:
firewall-cmd --add-port 5000
to open the port through the firewall. I've done the same inside the container (and outside) and a combination of those but the result is the same.
netstat -a -l -n | grep 5000
tcp        0      0 0.0.0.0:5000            0.0.0.0:*               LISTEN      
tcp6       0      0 :::5000                 :::*                    LISTEN
docker ps also shows that port 5000 is exposed by the container.
I also tried to run the python3 scripts in interactive mode within the running container but still I had no luck.
So question really is: Am I missing some docker security feature here? Do I need to make extra configuration changes for web-ports to work with docker?
EDIT:
Reproduce behaviour:
file websockserver:
#!/usr/bin/env python3
# WSS (WS over TLS) server example, with a self-signed certificate
import asyncio
import ssl
import websockets
async def hello(websocket):
    name = await websocket.recv()
    print(f"< {name}")
    greeting = f"Hello {name}!"
    await websocket.send(greeting)
    print(f"> {greeting}")
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain("./rootCA.crt", "./rootCA.key")
start_server = websockets.serve(
    hello, '127.0.0.1', 5000, ssl=ssl_context)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
file websockclient:
#!/usr/bin/env python3
# WSS (WS over TLS) client example, with a self-signed certificate
import asyncio
import ssl
import websockets
#ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context = ssl._create_unverified_context()
ssl_context.load_cert_chain("./rootCA.crt", "./rootCA.key")
async def hello():
    async with websockets.connect(
            'wss://localhost:5000', ssl=ssl_context) as websocket:
        name = input("What's your name? ")
        await websocket.send(name)
        print(f"> {name}")
        greeting = await websocket.recv()
        print(f"< {greeting}")
asyncio.get_event_loop().run_until_complete(hello())
file docker-compose:
version: "3.3"
services:
  py_server:
    build:
      context: .
      dockerfile: Dockerfile.server
      args:
        progress: plain
    command: bash -c /usr/src/app/websockserver
    stdin_open: true
    tty: true
    hostname: server_host
    image: py_server:latest
    networks:
      - py_network
    ports:
       # HOST:CONTAINER)
      - "5000:5000"
networks:
  py_network: {}
file Dockerfile.server: NOTE: Commented out part used to create certificates
FROM python:3
WORKDIR /usr/src/app
# RUN openssl genpkey -algorithm RSA -out rootCA.key -pkeyopt rsa_keygen_bits:4096 -pkeyopt rsa_keygen_pubexp:3
# RUN openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 5000 -out rootCA.crt -config rootCA.cnf
# RUN openssl req -new -sha256 -nodes -out webapi.csr -newkey rsa:4096 -keyout webapi.key -config webapi.cnf -extensions v3_req
# RUN openssl x509 -req -in webapi.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -days 5000 -extfile webapi.cnf -extensions v3_req -out webapi.crt
COPY ./rootCA.crt /usr/src/app
COPY ./webapi.crt /usr/src/app
COPY ./rootCA.key /usr/src/app
COPY ./webapi.key /usr/src/app
RUN cp /usr/src/app/rootCA.crt /usr/local/share/ca-certificates/PRMrootCA.crt
RUN cp /usr/src/app/webapi.crt /usr/local/share/ca-certificates/webapi.crt
RUN update-ca-certificates
RUN pip3 install websockets
RUN pip3 install websocket
WORKDIR /usr/src/app
COPY ./websockserver /usr/src/app
When starting websockserver in one terminal and websockclient in another then server and client communicate. When the server is dockerised then the client cannot connect.
lsof -i :5000 only shows a result when run with sudo privileges.