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.