0

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.

DKyb
  • 1
  • 1
  • Can you [edit] the question to include a [mcve]? On the one hand, it'd be helpful to see your Python application code, since there's at least [one common configuration error](/q/30323224) that has a similar symptom; on the other, you should be able to dramatically trim down the Dockerfile and Compose setup, deleting the unused "useful tools" and sshd setup, and some unneeded Compose options like `hostname:` or `tty:`. You're also overriding the image's `CMD` to run a script and it would help to know what that script does. – David Maze Jan 05 '23 at 10:52
  • Thanks David, the start script only starts the process that is listening on the port. It is difficult to produce a minimal problem, as that would mean to get program a new similar C++ service which would need to be programmed from scratch. In a way this example is minimal - for debugging purposes etc I wand to be able to listen to posts, ping etc. – DKyb Jan 05 '23 at 15:47
  • One thing that might be relevant is the following: on my virtual machines I create a certification authority and install a certificate for the web-api. I think this process does not quite work within the container. So it might be that I just need to find a way to import the certificate from the host, or something like that – DKyb Jan 05 '23 at 15:47

0 Answers0