Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,23 @@ jobs:
docker build -t kiwitcms/postgres:${{ matrix.version }} .

docker images

test:
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@v7

- name: Install packages used during container testing
run: |
# used in test scripts
sudo apt-get install git make sscg

# install beakerlib from source it doesn't ship DEB packages
if [ ! -f "/usr/share/beakerlib/beakerlib.sh" ]; then
git clone https://github.com/beakerlib/beakerlib.git
sudo make -C beakerlib/ install
fi

- name: Run tests
run: |
./testing/runner.sh
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
testing/*.crt
testing/*.key
14 changes: 1 addition & 13 deletions 17/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
FROM postgres:17@sha256:e38411452a464af89e5adadb8d223bf53b898d47d6ef918b2d58c08707350449

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y --no-install-recommends ca-certificates curl gnupg \
&& curl -sSL https://keys.datadoghq.com/DATADOG_APT_KEY_CURRENT.public | gpg --dearmor --output /usr/share/keyrings/datadog-archive-keyring.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/datadog-archive-keyring.gpg] https://apt.datadoghq.com/ stable 7" > /etc/apt/sources.list.d/datadog.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends datadog-agent \
&& apt-get clean

# Entrypoint: start agent, then your app
#CMD service datadog-agent start && exec ./start-my-app.sh
COPY ./docker-entrypoint-initdb.d/00_enable_ssl.sh /docker-entrypoint-initdb.d/
43 changes: 43 additions & 0 deletions 17/docker-entrypoint-initdb.d/00_enable_ssl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash

# Copyright (c) 2026 Alexander Todorov <atodorov@otb.bg>
#
# Licensed under GNU Affero General Public License v3 or later (AGPLv3+)
# https://www.gnu.org/licenses/agpl-3.0.html

set -eu

# these must be the actual content of the files
[ -z "$POSTGRES_SSL_CERT" ] && exit 1
[ -z "$POSTGRES_SSL_KEY" ] && exit 1

# IMPORTANT: to refresh these certificates copy them
# directly under $PGDATA once the server has been initialized
# WARNING: chown postgres:postgres
# WARNING: chmod 0600
echo " **** Configure SSL certificate and key"
echo "$POSTGRES_SSL_CERT" > "$PGDATA/server.crt"
chown postgres:postgres "$PGDATA/server.crt"
chmod 0600 "$PGDATA/server.crt"

echo "$POSTGRES_SSL_KEY" > "$PGDATA/server.key"
chown postgres:postgres "$PGDATA/server.key"
chmod 0600 "$PGDATA/server.key"

echo " **** Turn SSL mode ON"
psql --set ON_ERROR_STOP=1 \
--username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
ALTER SYSTEM SET ssl TO 'ON';
EOSQL

echo " **** Require SSL connections from clients"
sed -i "s/host all all all scram-sha-256/hostssl all all all scram-sha-256/" "$PGDATA/pg_hba.conf"

echo "# Explicitly allow replication over SSL" >> "$PGDATA/pg_hba.conf"
echo "hostssl replication all all scram-sha-256" >> "$PGDATA/pg_hba.conf"

echo "# Explicitly reject non-SSL connections" >> "$PGDATA/pg_hba.conf"
echo "hostnossl all all all reject" >> "$PGDATA/pg_hba.conf"
echo "hostnossl replication all all reject" >> "$PGDATA/pg_hba.conf"

cat "$PGDATA/pg_hba.conf"
11 changes: 1 addition & 10 deletions 18/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
FROM postgres:18@sha256:06cad38a5d9f5d24b4d83d86def30795d5e4b757fedbf5281172b576dedcd941

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y --no-install-recommends ca-certificates curl gnupg \
&& curl -sSL https://keys.datadoghq.com/DATADOG_APT_KEY_CURRENT.public | gpg --dearmor --output /usr/share/keyrings/datadog-archive-keyring.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/datadog-archive-keyring.gpg] https://apt.datadoghq.com/ stable 7" > /etc/apt/sources.list.d/datadog.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends datadog-agent \
&& apt-get clean
COPY ./docker-entrypoint-initdb.d/00_enable_ssl.sh /docker-entrypoint-initdb.d/
43 changes: 43 additions & 0 deletions 18/docker-entrypoint-initdb.d/00_enable_ssl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash

# Copyright (c) 2026 Alexander Todorov <atodorov@otb.bg>
#
# Licensed under GNU Affero General Public License v3 or later (AGPLv3+)
# https://www.gnu.org/licenses/agpl-3.0.html

set -eu

# these must be the actual content of the files
[ -z "$POSTGRES_SSL_CERT" ] && exit 1
[ -z "$POSTGRES_SSL_KEY" ] && exit 1

# IMPORTANT: to refresh these certificates copy them
# directly under $PGDATA once the server has been initialized
# WARNING: chown postgres:postgres
# WARNING: chmod 0600
echo " **** Configure SSL certificate and key"
echo "$POSTGRES_SSL_CERT" > "$PGDATA/server.crt"
chown postgres:postgres "$PGDATA/server.crt"
chmod 0600 "$PGDATA/server.crt"

echo "$POSTGRES_SSL_KEY" > "$PGDATA/server.key"
chown postgres:postgres "$PGDATA/server.key"
chmod 0600 "$PGDATA/server.key"

echo " **** Turn SSL mode ON"
psql --set ON_ERROR_STOP=1 \
--username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
ALTER SYSTEM SET ssl TO 'ON';
EOSQL

echo " **** Require SSL connections from clients"
sed -i "s/host all all all scram-sha-256/hostssl all all all scram-sha-256/" "$PGDATA/pg_hba.conf"

echo "# Explicitly allow replication over SSL" >> "$PGDATA/pg_hba.conf"
echo "hostssl replication all all scram-sha-256" >> "$PGDATA/pg_hba.conf"

echo "# Explicitly reject non-SSL connections" >> "$PGDATA/pg_hba.conf"
echo "hostnossl all all all reject" >> "$PGDATA/pg_hba.conf"
echo "hostnossl replication all all reject" >> "$PGDATA/pg_hba.conf"

cat "$PGDATA/pg_hba.conf"
101 changes: 101 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
services:
postgres_17:
container_name: postgres_17
build:
context: 17/
dockerfile: Dockerfile
restart: always
volumes:
- db17_data:/var/lib/postgresql
environment:
POSTGRES_DB: kiwitcms
POSTGRES_USER: kiwitcms
POSTGRES_PASSWORD: kiwitcms
POSTGRES_SSL_CERT: |-
-----BEGIN CERTIFICATE-----
MIIDyDCCArCgAwIBAgIIB0Yovk/q2RswDQYJKoZIhvcNAQELBQAwZTELMAkGA1UE
BhMCQkcxDjAMBgNVBAcMBVNvZmlhMRIwEAYDVQQKDAlLaXdpIFRDTVMxHzAdBgNV
BAsMFmNhLTM5NzYyODA1NjE1MDg4MTU3OTgxETAPBgNVBAMMCGRhdGFiYXNlMB4X
DTI2MDgyNjE0MzIwMVoXDTI3MDkyODE0MzIwMVowYjELMAkGA1UEBhMCQkcxDjAM
BgNVBAcMBVNvZmlhMRIwEAYDVQQKDAlLaXdpIFRDTVMxHDAaBgNVBAsME1F1YWxp
dHkgRW5naW5lZXJpbmcxETAPBgNVBAMMCGRhdGFiYXNlMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAnS44wxnA/lYCpQEI9ZzYuDBQXls0h0WTs0fGZJJM
BxaMO0+nAIuYaJ3IiW6/6dbHObuxam4icTzCQb0d4rw4DJUyWWT3YzETJ6JkKmuk
MuJxOrOY1Zya5kMYtFcuaMxt3v/uGV5ABYIDWtC1QihxgQU/UKNAm4wsLqCHmOcP
PqZ8bDKuW2ob1QLAVYCcOkBM70v5L+zB8ygjvLr+ihl1Iayp3OWTlppreg8VQ9K+
bv18L1O/a0d9f4o2S0YU0qylPtJwIrujRcFyocsFuIP5ZIqIupmpk61YCTuI8Ota
xoz3u4+WgB3DYmrasihRmFXPe+A9Vm8bL5kQK+pzjU/JDQIDAQABo38wfTAOBgNV
HQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCQYDVR0TBAIwADAqBgNV
HREEIzAhgghkYXRhYmFzZYIVZGF0YWJhc2UuZXhhbXBsZS5raXdpMB8GA1UdIwQY
MBaAFM8yzwSSNg1mJXUk9+3Om1/SNIYFMA0GCSqGSIb3DQEBCwUAA4IBAQB1wGmo
kbN4esvvtl15U3c/gtwdCdERxNgTlh1UkY9NIrlsdG2CnafciMwAw9Y+W4Pv2gIm
C4lcV709rVlTvMbnHRv8CacOnIxzJvjitMJbZLuG0/mtAFvuUX4h+pFI57ZSZl4K
HA2jQtifTAiNri/qbeLlp7E2lzeSbty+/lWdbNr42QkpNAqcMVKNXQRG3r+vrjU+
+TBqhhcRdXN9m+uCiASvMrp7M0FiAoztnEl/2WrfPby2lN/Vwre2jKwFNa57Z1uI
rjH5iA54wqeKU3PmvfjFkvNZ2QNmSRIupSzRUi96Xid6qrn2UsRF/AsT/kDGCcbw
wYR0HWwTLAEw6ynz
-----END CERTIFICATE-----
POSTGRES_SSL_KEY: |-
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCdLjjDGcD+VgKl
AQj1nNi4MFBeWzSHRZOzR8ZkkkwHFow7T6cAi5honciJbr/p1sc5u7FqbiJxPMJB
vR3ivDgMlTJZZPdjMRMnomQqa6Qy4nE6s5jVnJrmQxi0Vy5ozG3e/+4ZXkAFggNa
0LVCKHGBBT9Qo0CbjCwuoIeY5w8+pnxsMq5bahvVAsBVgJw6QEzvS/kv7MHzKCO8
uv6KGXUhrKnc5ZOWmmt6DxVD0r5u/XwvU79rR31/ijZLRhTSrKU+0nAiu6NFwXKh
ywW4g/lkioi6mamTrVgJO4jw61rGjPe7j5aAHcNiatqyKFGYVc974D1WbxsvmRAr
6nONT8kNAgMBAAECggEASv05YYOQ+wbH/jcPMjchgkayoUG8Puln/Tm6IlNV6G/O
eaGX2H/BN/BHM6Jd909b/2RX0aI31nSZbUmvz3Pi/cXUnqe4C1/ndppYVZDDZu93
+/yhZxBvKnpKeAe8AY2+wYyy6PF5c+dCeHpob+bhuB9rxKoOceefUKOP7dgXAtkl
ZzRGeM1j7GmZjGlTwt4sqJa/mYdh+vpMjyFPKovepFBcgOsh0FKM3CJ7+2Hzm0M5
1vPFn3dZ4lqpwO5VSaZGErFiG8lNVlD+Yg36xNFHrOnjb/bvBZhgvTHXKX1mDmQp
wNXf3Y1U0B2PEZDXpbO3sFrcKWezXtezl+ySYVM0tQKBgQDV+wUBQFXnt5OpjVRR
+csFao2ctoZ7wAeOzzCHctKG+9FsIo36DSjYbRj+f3NQnLGQfgURPBXgtIxLNZFg
mWjG91UBOdLe8mJIw1TKEvQh8UGBygbjP6qi7qhreiWocsb3I3LgH3Nd475dvthk
vSyStZaKMNu1JnFJo+dRwkuBywKBgQC8C9G5ef11+BHj9jRULGpgn5yeahjdb+aw
/92dkL0wjCCEd0wffVbn9UOCaOsd5q0+xSpSr8NW+NI+u2m0TCph+ejD8btoKV3u
AFKT4rTBVY05+2s2bX7BE9b6jsgfsr0u4vc7dnFgGo/F2fCSQaZZHG7I/0aLPmPk
obx0YbQlhwKBgQCwrx6I0/ia5mgU3k+7iIhspnjb55LP59XVw5qMnPDc6F0Z7sZQ
rEEHw72Zlt8+tsOfTY8dlgFyk2IsT8UEgGeVsnAMHRTTZkxlTHRcjp3q0OyIIKrj
RZS+CFYkuK0TlAJZ2nlmOlHmOsDbY1QGinWdhjcRUBp6T43HgoDkb4wtVQKBgCU2
TY1ft6mlqpyq9xYFKBPw4gRzSHJhl4xFRezNXLMDt9ee6AB/RDhoz7A55PUo1fXK
/KIaSBSo7qodWkCkpep3kvlbl9N4BC95/t1nCpALxwPHXiMKLgA1VeCFatLjAw8J
ORWaTbtP5k2n4DSBK1MNDltWMPaTJR90Zh+9yfF3AoGAbm5ad+1VlrzeGLeRJJlw
e973HTPwcbKYJUvdQXVKw7Xv1ax39hHDdT0ClSG4RT2CMbQokJ8H7s6Xu4j1QPjs
lndQ0AFTkC/60tVceBZv6i3Mw0ZjfcMhwChnBjVHIJuV7iNGFYLgCGeMSPSDaWV5
5TscwBqnHlbks8ZSsk2u0zQ=
-----END PRIVATE KEY-----

postgres_18:
extends: postgres_17
container_name: postgres_18
build: !override
context: 18/
dockerfile: Dockerfile
volumes:
- db18_data:/var/lib/postgresql

web:
container_name: web
image: pub.kiwitcms.eu/kiwitcms/kiwi:latest
depends_on:
- postgres_17
- postgres_18
restart: always
ports:
- 80:8080
- 443:8443
volumes:
- ./testing/test_settings.py:/venv/lib64/python3.12/site-packages/tcms_settings_dir/test_settings.py:z,ro
environment:
KIWI_DB_ENGINE: django.db.backends.postgresql
KIWI_DB_HOST: postgres_17
KIWI_DB_PORT: 5432
KIWI_DB_NAME: kiwitcms
KIWI_DB_USER: kiwitcms
KIWI_DB_PASSWORD: kiwitcms
KIWI_DB_SSL_MODE: require


volumes:
db17_data:
db18_data:
37 changes: 37 additions & 0 deletions testing/runner.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash

# Copyright (c) 2026 Alexander Todorov <atodorov@otb.bg>
#
# Licensed under GNU Affero General Public License v3 or later (AGPLv3+)
# https://www.gnu.org/licenses/agpl-3.0.html

# Note: execute this file from the project root directory

# setup
rm -rf /var/tmp/beakerlib-*/
export BEAKERLIB_JOURNAL=0

# Generate SSL certificates for Postgres
# WARNING: 644 permissions so that initdb.d/ can read them
# these files are ignored afterwards
/usr/bin/sscg --verbose --force \
--country BG --locality Sofia \
--organization "Kiwi TCMS" \
--organizational-unit "Quality Engineering" \
--hostname database.example.kiwi \
--ca-file ./testing/ca.crt \
--cert-file ./testing/database.crt \
--cert-mode=0644 \
--cert-key-file ./testing/database.key \
--cert-key-mode=0644

# execute test scripts
./testing/test_docker.sh


# look for failures
cat /var/tmp/beakerlib-*/TestResults || exit 11
grep RESULT_STRING /var/tmp/beakerlib-*/TestResults | grep -v PASS && exit 22

# explicit return code
exit 0
68 changes: 68 additions & 0 deletions testing/test_docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/bin/bash

. /usr/share/beakerlib/beakerlib.sh

assert_up_and_running() {
sleep 10
# print health status for db and web containers
rlRun -t -c "docker inspect -f '{{.State.Health.Status}}' web"

# HTTP redirects; HTTPS displays the login page
rlRun -t -c "curl -o- http://localhost/ | grep '301 Moved Permanently'"
rlRun -t -c "curl -k -L -o- https://localhost/ | grep 'Welcome to Kiwi TCMS'"
}

rlJournalStart
rlPhaseStartTest "Container up"
rlRun -t -c "docker compose up -d"
sleep 5
rlPhaseEnd

rlPhaseStartTest "Sanity test - DB allows SSL connections - Kiwi TCMS initial_setup"
# need to monkey-patch createsuperuser.py b/c it rejects input when not using a TTY
rlRun -t -c 'docker exec -i web sed -i "s/raise NotRunningInTTYException/pass/" /venv/lib64/python3.12/site-packages/django/contrib/auth/management/commands/createsuperuser.py'
rlRun -t -c 'docker exec -i web sed -i "s/getpass.getpass/input/" /venv/lib64/python3.12/site-packages/django/contrib/auth/management/commands/createsuperuser.py'

rlRun -t -c 'echo -e "super-root\nroot@example.com\nsecret-2a9a34cd-e51d-4039-b709-b45f629a5595\nsecret-2a9a34cd-e51d-4039-b709-b45f629a5595\n" | docker exec -i web /Kiwi/manage.py initial_setup --database postgres_17'
rlRun -t -c 'echo -e "super-root\nroot@example.com\nsecret-2a9a34cd-e51d-4039-b709-b45f629a5595\nsecret-2a9a34cd-e51d-4039-b709-b45f629a5595\n" | docker exec -i web /Kiwi/manage.py initial_setup --database postgres_18'

# assert only after initial configuration has been applied
assert_up_and_running
rlPhaseEnd

rlPhaseStartTest "Sanity test - DB rejects plain/text connections"
rlRun -t -s "docker exec -i web /Kiwi/manage.py showmigrations --database plain_text" 1

rlAssertGrep "django.db.utils.OperationalError: connection failed:" "$rlRun_LOG"
rlAssertGrep "pg_hba.conf rejects connection .* no encryption" "$rlRun_LOG"
rlPhaseEnd

rlPhaseStartTest "Container restart"
rlRun -t -c "docker compose restart"
assert_up_and_running
rlPhaseEnd

rlPhaseStartTest "Container stop & start"
rlRun -t -c "docker compose stop"
sleep 5
rlRun -t -c "docker compose start"
assert_up_and_running
rlPhaseEnd

rlPhaseStartTest "Container kill & start"
rlRun -t -c "docker compose kill"
sleep 35
rlRun -t -c "docker compose start"
assert_up_and_running
rlPhaseEnd

rlPhaseStartCleanup
rlRun -t -c "docker compose down"
if [ -n "$ImageOS" ]; then
rlRun -t -c "docker volume rm postgres_db17_data"
rlRun -t -c "docker volume rm postgres_db18_data"
fi
rlPhaseEnd
rlJournalEnd

rlJournalPrintText
22 changes: 22 additions & 0 deletions testing/test_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright (c) 2026 Alexander Todorov <atodorov@otb.bg>
#
# Licensed under GNU Affero General Public License v3 or later (AGPLv3+)
# https://www.gnu.org/licenses/agpl-3.0.html

# pylint: disable=undefined-variable

import copy
from tcms.utils.secrets import get_secret


DATABASES["default"]["OPTIONS"]["sslmode"] = get_secret("KIWI_DB_SSL_MODE", "require")

DATABASES["postgres_17"] = copy.deepcopy(DATABASES["default"])
DATABASES["postgres_17"]["HOST"] = "postgres_17"

DATABASES["postgres_18"] = copy.deepcopy(DATABASES["default"])
DATABASES["postgres_18"]["HOST"] = "postgres_18"

# will be used for testing that plan/text DB connections are refused
DATABASES["plain_text"] = copy.deepcopy(DATABASES["default"])
DATABASES["plain_text"]["OPTIONS"]["sslmode"] = "disable"
Loading