From f9af11c2112ad329349bcf3a0cc61c019df98b0c Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 08:51:48 -0700 Subject: [PATCH 01/49] feat: add cvmanager_nginx_proxy service to docker-compose --- docker-compose.yml | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index bd06227bc..d1e5d9b68 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,31 @@ include: - docker-compose-obu-ota-server.yml services: + cvmanager_nginx_proxy: + build: + context: ../nginx_proxy_resources + image: cvmanager_nginx_proxy:latest + restart: unless-stopped + ports: + - 80:80 + - 443:443 + environment: + NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx + SERVER_HOST: ${WEBAPP_DOMAIN} + volumes: + - /nginx-ssl.conf:/etc/nginx/templates/nginx.conf.template + - /gen_dhparam.sh:/docker-entrypoint.d/gen_dhparam.sh + - /ssl/server.crt:/etc/ssl/certs/server.crt + - /ssl/server.key:/etc/ssl/private/server.key + depends_on: + - cvmanager_webapp + - cvmanager_keycloak + - cvmanager_api + logging: + options: + max-size: '10m' + max-file: '5' + cvmanager_api: profiles: - basic @@ -15,10 +40,7 @@ services: image: jpo_cvmanager_api:latest restart: always extra_hosts: - ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} - ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} - ports: - - '8081:5000' + : ${WEBAPP_HOST_IP} environment: PG_DB_HOST: ${PG_DB_HOST} PG_DB_USER: ${PG_DB_USER} @@ -75,6 +97,8 @@ services: LOGGING_LEVEL: ${API_LOGGING_LEVEL} volumes: - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json + - /ssl/server.crt:/etc/ssl/certs/server.crt + - /ssl/server.key:/etc/ssl/private/server.key logging: options: max-size: '10m' @@ -118,10 +142,7 @@ services: condition: service_healthy required: false extra_hosts: - ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} - ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} - ports: - - '80:80' + : ${WEBAPP_HOST_IP} logging: options: max-size: '10m' @@ -166,9 +187,6 @@ services: extra_hosts: ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} - ports: - - '8084:8080' - - '9000:9000' environment: KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} @@ -190,6 +208,7 @@ services: command: - start-dev - --import-realm + - --proxy edge logging: options: max-size: '10m' From 13304680e0a1fb6081ec18cc9c12ba73c0d40c0e Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 08:53:26 -0700 Subject: [PATCH 02/49] fix: set default values for STORAGE_TYPE and ISS_KEY_TABLE_NAME in docker-compose --- docker-compose-addons.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose-addons.yml b/docker-compose-addons.yml index ef59efd39..843f85a37 100644 --- a/docker-compose-addons.yml +++ b/docker-compose-addons.yml @@ -87,14 +87,14 @@ services: condition: service_healthy required: false environment: - STORAGE_TYPE: ${STORAGE_TYPE} + STORAGE_TYPE: ${STORAGE_TYPE:-POSTGRES} ISS_API_KEY: ${ISS_API_KEY} ISS_API_KEY_NAME: ${ISS_API_KEY_NAME} ISS_PROJECT_ID: ${ISS_PROJECT_ID} ISS_SCMS_TOKEN_REST_ENDPOINT: ${ISS_SCMS_TOKEN_REST_ENDPOINT} ISS_SCMS_VEHICLE_REST_ENDPOINT: ${ISS_SCMS_VEHICLE_REST_ENDPOINT} - ISS_KEY_TABLE_NAME: ${ISS_KEY_TABLE_NAME} + ISS_KEY_TABLE_NAME: ${ISS_KEY_TABLE_NAME:-iss_keys} PG_DB_HOST: ${PG_DB_HOST} PG_DB_NAME: ${PG_DB_NAME} From b332bf6d0d16c15e0446861824fb078e1d825ad4 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 08:53:51 -0700 Subject: [PATCH 03/49] chore: comment out firmware manager services in docker-compose-addons.yml --- docker-compose-addons.yml | 128 +++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/docker-compose-addons.yml b/docker-compose-addons.yml index 843f85a37..3b0c081ce 100644 --- a/docker-compose-addons.yml +++ b/docker-compose-addons.yml @@ -112,67 +112,67 @@ services: max-size: '10m' max-file: '5' - firmware_manager_upgrade_scheduler: - profiles: - - addons - - firmware_manager_upgrade_scheduler - build: - context: services - dockerfile: Dockerfile.fmus - image: jpo_firmware_manager_upgrade_scheduler:latest - restart: on-failure:3 - - ports: - - '8089:8080' - environment: - PG_DB_HOST: ${PG_DB_HOST} - PG_DB_NAME: postgres - PG_DB_USER: ${PG_DB_USER} - PG_DB_PASS: ${PG_DB_PASS} - - UPGRADE_RUNNER_ENDPOINT: ${FIRMWARE_MANAGER_UPGRADE_RUNNER_ENDPOINT} - - LOGGING_LEVEL: ${FIRMWARE_MANAGER_LOGGING_LEVEL} - volumes: - - ${HOST_BLOB_STORAGE_DIRECTORY}:/mnt/blob_storage - logging: - options: - max-size: '10m' - max-file: '5' - - firmware_manager_upgrade_runner: - profiles: - - addons - - firmware_manager_upgrade_runner - build: - context: services - dockerfile: Dockerfile.fmur - image: jpo_firmware_manager_upgrade_runner:latest - restart: on-failure:3 - - ports: - - '8090:8080' - environment: - BLOB_STORAGE_PROVIDER: ${BLOB_STORAGE_PROVIDER} - BLOB_STORAGE_BUCKET: ${BLOB_STORAGE_BUCKET} - - FW_UPGRADE_MAX_RETRY_LIMIT: ${FW_UPGRADE_MAX_RETRY_LIMIT} - - GCP_PROJECT: ${GCP_PROJECT_ID} - GOOGLE_APPLICATION_CREDENTIALS: /home/gcp_key.json - - SMTP_SERVER_IP: ${SMTP_SERVER_IP} - SMTP_EMAIL: ${SMTP_EMAIL} - SMTP_USERNAME: ${SMTP_USERNAME} - SMTP_PASSWORD: ${SMTP_PASSWORD} - - UPGRADE_SCHEDULER_ENDPOINT: ${FIRMWARE_MANAGER_UPGRADE_SCHEDULER_ENDPOINT} - - LOGGING_LEVEL: ${FIRMWARE_MANAGER_LOGGING_LEVEL} - volumes: - - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json - - ${HOST_BLOB_STORAGE_DIRECTORY}:/mnt/blob_storage - logging: - options: - max-size: '10m' - max-file: '5' +# firmware_manager_upgrade_scheduler: +# profiles: +# - addons +# - firmware_manager_upgrade_scheduler +# build: +# context: services +# dockerfile: Dockerfile.fmus +# image: jpo_firmware_manager_upgrade_scheduler:latest +# restart: on-failure:3 +# +# ports: +# - '8089:8080' +# environment: +# PG_DB_HOST: ${PG_DB_HOST} +# PG_DB_NAME: postgres +# PG_DB_USER: ${PG_DB_USER} +# PG_DB_PASS: ${PG_DB_PASS} +# +# UPGRADE_RUNNER_ENDPOINT: ${FIRMWARE_MANAGER_UPGRADE_RUNNER_ENDPOINT} +# +# LOGGING_LEVEL: ${FIRMWARE_MANAGER_LOGGING_LEVEL} +# volumes: +# - ${HOST_BLOB_STORAGE_DIRECTORY}:/mnt/blob_storage +# logging: +# options: +# max-size: '10m' +# max-file: '5' +# +# firmware_manager_upgrade_runner: +# profiles: +# - addons +# - firmware_manager_upgrade_runner +# build: +# context: services +# dockerfile: Dockerfile.fmur +# image: jpo_firmware_manager_upgrade_runner:latest +# restart: on-failure:3 +# +# ports: +# - '8090:8080' +# environment: +# BLOB_STORAGE_PROVIDER: ${BLOB_STORAGE_PROVIDER} +# BLOB_STORAGE_BUCKET: ${BLOB_STORAGE_BUCKET} +# +# FW_UPGRADE_MAX_RETRY_LIMIT: ${FW_UPGRADE_MAX_RETRY_LIMIT} +# +# GCP_PROJECT: ${GCP_PROJECT_ID} +# GOOGLE_APPLICATION_CREDENTIALS: /home/gcp_key.json +# +# SMTP_SERVER_IP: ${SMTP_SERVER_IP} +# SMTP_EMAIL: ${SMTP_EMAIL} +# SMTP_USERNAME: ${SMTP_USERNAME} +# SMTP_PASSWORD: ${SMTP_PASSWORD} +# +# UPGRADE_SCHEDULER_ENDPOINT: ${FIRMWARE_MANAGER_UPGRADE_SCHEDULER_ENDPOINT} +# +# LOGGING_LEVEL: ${FIRMWARE_MANAGER_LOGGING_LEVEL} +# volumes: +# - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json +# - ${HOST_BLOB_STORAGE_DIRECTORY}:/mnt/blob_storage +# logging: +# options: +# max-size: '10m' +# max-file: '5' From ede5011639d1579cb0272f12e7ffd97d21320ce3 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:01:45 -0700 Subject: [PATCH 04/49] chore: update SSL certificate paths in docker-compose for cvmanager_api service --- docker-compose.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d1e5d9b68..3d170b997 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -97,8 +97,7 @@ services: LOGGING_LEVEL: ${API_LOGGING_LEVEL} volumes: - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json - - /ssl/server.crt:/etc/ssl/certs/server.crt - - /ssl/server.key:/etc/ssl/private/server.key + - .crt:/etc/ssl/ca/ca.crt logging: options: max-size: '10m' From 8db9144a756bac6058d46c5e2ee85a1be0d71996 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:02:23 -0700 Subject: [PATCH 05/49] chore: comment out cvmanager_postgres service in docker-compose.yml --- docker-compose.yml | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3d170b997..909d1a138 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -146,24 +146,24 @@ services: options: max-size: '10m' - cvmanager_postgres: - profiles: - - basic - - cvmanager_postgres - - intersection_no_api - image: postgis/postgis:15-master - restart: always - ports: - - '5432:5432' - environment: - POSTGRES_USER: ${PG_DB_USER} - POSTGRES_PASSWORD: ${PG_DB_PASS} - volumes: - - pgdb:/var/lib/postgresql/data - - ./resources/sql_scripts:/docker-entrypoint-initdb.d - logging: - options: - max-size: '10m' +# cvmanager_postgres: +# profiles: +# - basic +# - cvmanager_postgres +# - intersection_no_api +# image: postgis/postgis:15-master +# restart: always +# ports: +# - '5432:5432' +# environment: +# POSTGRES_USER: ${PG_DB_USER} +# POSTGRES_PASSWORD: ${PG_DB_PASS} +# volumes: +# - pgdb:/var/lib/postgresql/data +# - ./resources/sql_scripts:/docker-entrypoint-initdb.d +# logging: +# options: +# max-size: '10m' cvmanager_keycloak: profiles: @@ -178,10 +178,10 @@ services: KEYCLOAK_LOGIN_THEME_NAME: ${KEYCLOAK_LOGIN_THEME_NAME}.jar image: jpo_cvmanager_keycloak:latest restart: always - depends_on: - cvmanager_postgres: - required: false - condition: service_started +# depends_on: +# cvmanager_postgres: +# required: false +# condition: service_started extra_hosts: ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} From 300d2f012483ca294a339fff00ad9981b81fe6de Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:03:36 -0700 Subject: [PATCH 06/49] feat: add SSL support for database connection in pgquery.py --- services/common/pgquery.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/common/pgquery.py b/services/common/pgquery.py index 40c9e02f6..a166e237b 100644 --- a/services/common/pgquery.py +++ b/services/common/pgquery.py @@ -1,4 +1,5 @@ import os +import ssl import sqlalchemy import logging @@ -23,6 +24,8 @@ def init_tcp_connection_engine(db_user, db_pass, db_name, db_hostname, db_port): logging.info("Creating DB pool") logging.debug(f"{db_user},{db_name},{db_hostname},{db_port}") + ssl_context = ssl.create_default_context() + ssl_context.load_verify_locations("/etc/ssl/ca/ca.crt") pool = sqlalchemy.create_engine( # Equivalent URL: # postgresql+pg8000://:@:/ @@ -34,6 +37,7 @@ def init_tcp_connection_engine(db_user, db_pass, db_name, db_hostname, db_port): port=db_port, # e.g. 5432 database=db_name, # e.g. "my-database-name" ), + connect_args={"ssl_context": ssl_context}, **db_config, ) # pool.dialect.description_encoding = None From 97a48124db7e89efc8e3a8c0af3a215028dc5dc6 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:04:56 -0700 Subject: [PATCH 07/49] chore: comment out file type validation in util.py --- services/common/util.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/common/util.py b/services/common/util.py index 92405210f..a6152779d 100644 --- a/services/common/util.py +++ b/services/common/util.py @@ -52,9 +52,9 @@ def validate_file_type(file_name, extension=".tar"): file_name (str): The name of the file to be downloaded. extension (str): The file extension to validate against. """ - if not file_name.endswith(extension): - logging.error( - f'Unsupported file type for storage object {file_name}. Only "{extension}" files are supported.' - ) - return False + # if not file_name.endswith(extension): + # logging.error( + # f'Unsupported file type for storage object {file_name}. Only "{extension}" files are supported.' + # ) + # return False return True From 24e973ab3d569b22b99399ef0120f34843a64c21 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:05:35 -0700 Subject: [PATCH 08/49] chore: update default active upgrade limit to 4 in upgrade_scheduler.py --- .../firmware_manager/upgrade_scheduler/upgrade_scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/addons/images/firmware_manager/upgrade_scheduler/upgrade_scheduler.py b/services/addons/images/firmware_manager/upgrade_scheduler/upgrade_scheduler.py index 3b965c5c9..379bed5fc 100644 --- a/services/addons/images/firmware_manager/upgrade_scheduler/upgrade_scheduler.py +++ b/services/addons/images/firmware_manager/upgrade_scheduler/upgrade_scheduler.py @@ -33,7 +33,7 @@ # Changed from a constant to a function to help with unit testing def get_upgrade_limit() -> int: try: - upgrade_limit = int(os.environ.get("ACTIVE_UPGRADE_LIMIT", "1")) + upgrade_limit = int(os.environ.get("ACTIVE_UPGRADE_LIMIT", "4")) return upgrade_limit except ValueError: raise ValueError( From 36353646942d3f8c5171f30ebbf58355574f731d Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:06:47 -0700 Subject: [PATCH 09/49] chore: add commented-out SSL certificate path in docker-compose-addons.yml --- docker-compose-addons.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose-addons.yml b/docker-compose-addons.yml index 3b0c081ce..b2b5da602 100644 --- a/docker-compose-addons.yml +++ b/docker-compose-addons.yml @@ -172,6 +172,7 @@ services: # volumes: # - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json # - ${HOST_BLOB_STORAGE_DIRECTORY}:/mnt/blob_storage +# - .crt:/etc/ssl/ca/ca.crt # logging: # options: # max-size: '10m' From bcebd9f8736fbb6e1056a7ae36265c7bf4488183 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:07:10 -0700 Subject: [PATCH 10/49] chore: update nginx proxy build context in docker-compose.yml --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 909d1a138..a1c9ca897 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ include: services: cvmanager_nginx_proxy: build: - context: ../nginx_proxy_resources + context: image: cvmanager_nginx_proxy:latest restart: unless-stopped ports: From 280e4b7ca99900b17439284ccda01c6fe4a0f320 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:14:00 -0700 Subject: [PATCH 11/49] chore: update resource paths and placeholders for nginx proxy and SSL in docker-compose files --- docker-compose-addons.yml | 2 +- docker-compose.yml | 16 ++++++++-------- sample.env | 3 +++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docker-compose-addons.yml b/docker-compose-addons.yml index b2b5da602..f6beb0d73 100644 --- a/docker-compose-addons.yml +++ b/docker-compose-addons.yml @@ -172,7 +172,7 @@ services: # volumes: # - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json # - ${HOST_BLOB_STORAGE_DIRECTORY}:/mnt/blob_storage -# - .crt:/etc/ssl/ca/ca.crt +# - ${CA_CERT_PATH:-./resources/certs/ca.crt}:/etc/ssl/ca/ca.crt # logging: # options: # max-size: '10m' diff --git a/docker-compose.yml b/docker-compose.yml index a1c9ca897..1b51a3664 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ include: services: cvmanager_nginx_proxy: build: - context: + context: ${NGINX_PROXY_RESOURCES:-./resources/nginx} image: cvmanager_nginx_proxy:latest restart: unless-stopped ports: @@ -17,10 +17,10 @@ services: NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx SERVER_HOST: ${WEBAPP_DOMAIN} volumes: - - /nginx-ssl.conf:/etc/nginx/templates/nginx.conf.template - - /gen_dhparam.sh:/docker-entrypoint.d/gen_dhparam.sh - - /ssl/server.crt:/etc/ssl/certs/server.crt - - /ssl/server.key:/etc/ssl/private/server.key + - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/nginx-ssl.conf:/etc/nginx/templates/nginx.conf.template + - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/gen_dhparam.sh:/docker-entrypoint.d/gen_dhparam.sh + - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/ssl/server.crt:/etc/ssl/certs/server.crt + - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/ssl/server.key:/etc/ssl/private/server.key depends_on: - cvmanager_webapp - cvmanager_keycloak @@ -40,7 +40,7 @@ services: image: jpo_cvmanager_api:latest restart: always extra_hosts: - : ${WEBAPP_HOST_IP} + ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} environment: PG_DB_HOST: ${PG_DB_HOST} PG_DB_USER: ${PG_DB_USER} @@ -97,7 +97,7 @@ services: LOGGING_LEVEL: ${API_LOGGING_LEVEL} volumes: - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json - - .crt:/etc/ssl/ca/ca.crt + - ${CA_CERT_PATH:-./resources/certs/ca.crt}:/etc/ssl/ca/ca.crt logging: options: max-size: '10m' @@ -141,7 +141,7 @@ services: condition: service_healthy required: false extra_hosts: - : ${WEBAPP_HOST_IP} + ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} logging: options: max-size: '10m' diff --git a/sample.env b/sample.env index 1361ca207..72a3658f7 100644 --- a/sample.env +++ b/sample.env @@ -455,3 +455,6 @@ NGINX_ENCRYPTION="plain" # SSL file name in path /docker/nginx/ssl/ SERVER_CERT_FILE="ota_server.crt" SERVER_KEY_FILE="ota_server.key" + +NGINX_PROXY_RESOURCES=./resources/nginx +CA_CERT_PATH=./resources/certs/ca.crt \ No newline at end of file From 588e05b0be40797d037301ecdc36ee10b8c5f764 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:19:11 -0700 Subject: [PATCH 12/49] chore: add nginx SSL configuration for webapp, API, and Keycloak --- resources/nginx/nginx-ssl.conf | 53 ++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 resources/nginx/nginx-ssl.conf diff --git a/resources/nginx/nginx-ssl.conf b/resources/nginx/nginx-ssl.conf new file mode 100644 index 000000000..d5878e93f --- /dev/null +++ b/resources/nginx/nginx-ssl.conf @@ -0,0 +1,53 @@ +upstream webapp { + server cvmanager_webapp:80; +} + +upstream api { + server cvmanager_api:8000; +} + +upstream keycloak { + server cvmanager_keycloak:8080; +} + +server { + listen 80; + server_name ${SERVER_HOST}; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name ${SERVER_HOST}; + + ssl_certificate /etc/ssl/certs/server.crt; + ssl_certificate_key /etc/ssl/private/server.key; + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # API routes + location /api/ { + proxy_pass http://api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Keycloak routes + location /auth/ { + proxy_pass http://keycloak/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Webapp routes (default) + location / { + proxy_pass http://webapp/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file From 4a25a24627c208c4571165aea0cb289ddfa359a9 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:19:30 -0700 Subject: [PATCH 13/49] chore: add script to generate Diffie-Hellman parameters if not present --- resources/nginx/gen_dhparam.sh | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 resources/nginx/gen_dhparam.sh diff --git a/resources/nginx/gen_dhparam.sh b/resources/nginx/gen_dhparam.sh new file mode 100644 index 000000000..924f6142b --- /dev/null +++ b/resources/nginx/gen_dhparam.sh @@ -0,0 +1,4 @@ +#!/bin/sh +if [ ! -f /etc/ssl/certs/dhparam.pem ]; then + openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 +fi \ No newline at end of file From 0f809d8c951823b6ddecc4acf111abd171ee47e5 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:19:42 -0700 Subject: [PATCH 14/49] chore: add SSL certificate and key files to .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index cdf645256..3f0ad9861 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ htmlcov .pytest_cache local_blob_storage services/coverage.xml + +resources/nginx/ssl/*.crt +resources/nginx/ssl/*.key +resources/nginx/ssl/*.pem \ No newline at end of file From 2525793937b7f002dcfc53e57f9c6b3b49289460 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:24:59 -0700 Subject: [PATCH 15/49] chore: enhance nginx SSL configuration with rate limiting, security headers, and buffering settings --- resources/nginx/nginx-ssl.conf | 81 ++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/resources/nginx/nginx-ssl.conf b/resources/nginx/nginx-ssl.conf index d5878e93f..525d2d826 100644 --- a/resources/nginx/nginx-ssl.conf +++ b/resources/nginx/nginx-ssl.conf @@ -10,6 +10,16 @@ upstream keycloak { server cvmanager_keycloak:8080; } +# Rate limiting zones +limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s; +limit_req_zone $binary_remote_addr zone=general_limit:10m rate=30r/s; + +# WebSocket connection upgrade map +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + server { listen 80; server_name ${SERVER_HOST}; @@ -20,34 +30,105 @@ server { listen 443 ssl http2; server_name ${SERVER_HOST}; + # SSL Configuration ssl_certificate /etc/ssl/certs/server.crt; ssl_certificate_key /etc/ssl/private/server.key; ssl_dhparam /etc/ssl/certs/dhparam.pem; + # SSL Protocols and Ciphers + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; + ssl_prefer_server_ciphers off; + + # SSL Session Configuration + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + ssl_session_tickets off; + + # Security Headers + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # Client body size limit (adjust based on your needs) + client_max_body_size 10M; + + # Gzip Compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; + + # Proxy Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + # API routes location /api/ { + limit_req zone=api_limit burst=20 nodelay; + proxy_pass http://api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Buffering settings + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + proxy_busy_buffers_size 8k; } # Keycloak routes location /auth/ { + limit_req zone=general_limit burst=30 nodelay; + proxy_pass http://keycloak/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Larger buffers for Keycloak authentication flows + proxy_buffering on; + proxy_buffer_size 8k; + proxy_buffers 8 8k; + proxy_busy_buffers_size 16k; } # Webapp routes (default) location / { + limit_req zone=general_limit burst=30 nodelay; + proxy_pass http://webapp/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support (for potential real-time features) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Buffering settings + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + proxy_busy_buffers_size 8k; } } \ No newline at end of file From 1828ccf3047cea6a43cf211f9cede2d26d000985 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:43:39 -0700 Subject: [PATCH 16/49] chore: update docker-compose.yml to include cvmanager_postgres service and adjust dependencies --- docker-compose.yml | 50 ++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1b51a3664..e472ab69e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,8 +6,11 @@ include: services: cvmanager_nginx_proxy: + profiles: + - nginx_proxy build: context: ${NGINX_PROXY_RESOURCES:-./resources/nginx} + dockerfile: Dockerfile image: cvmanager_nginx_proxy:latest restart: unless-stopped ports: @@ -25,6 +28,7 @@ services: - cvmanager_webapp - cvmanager_keycloak - cvmanager_api + - cvmanager_postgres logging: options: max-size: '10m' @@ -95,6 +99,8 @@ services: TIMEZONE: ${TIMEZONE} LOGGING_LEVEL: ${API_LOGGING_LEVEL} + depends_on: + - cvmanager_postgres volumes: - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json - ${CA_CERT_PATH:-./resources/certs/ca.crt}:/etc/ssl/ca/ca.crt @@ -146,24 +152,24 @@ services: options: max-size: '10m' -# cvmanager_postgres: -# profiles: -# - basic -# - cvmanager_postgres -# - intersection_no_api -# image: postgis/postgis:15-master -# restart: always -# ports: -# - '5432:5432' -# environment: -# POSTGRES_USER: ${PG_DB_USER} -# POSTGRES_PASSWORD: ${PG_DB_PASS} -# volumes: -# - pgdb:/var/lib/postgresql/data -# - ./resources/sql_scripts:/docker-entrypoint-initdb.d -# logging: -# options: -# max-size: '10m' + cvmanager_postgres: + profiles: + - basic + - cvmanager_postgres + - intersection_no_api + image: postgis/postgis:15-master + restart: always + ports: + - '5432:5432' + environment: + POSTGRES_USER: ${PG_DB_USER} + POSTGRES_PASSWORD: ${PG_DB_PASS} + volumes: + - pgdb:/var/lib/postgresql/data + - ./resources/sql_scripts:/docker-entrypoint-initdb.d + logging: + options: + max-size: '10m' cvmanager_keycloak: profiles: @@ -178,10 +184,10 @@ services: KEYCLOAK_LOGIN_THEME_NAME: ${KEYCLOAK_LOGIN_THEME_NAME}.jar image: jpo_cvmanager_keycloak:latest restart: always -# depends_on: -# cvmanager_postgres: -# required: false -# condition: service_started + depends_on: + cvmanager_postgres: + required: false + condition: service_started extra_hosts: ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} From a3d021d1153935e06afa96e6cd6cd4330c62b017 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:53:58 -0700 Subject: [PATCH 17/49] chore: refactor nginx SSL configuration for improved structure and readability --- resources/nginx/nginx-ssl.conf | 251 +++++++++++++++++---------------- 1 file changed, 129 insertions(+), 122 deletions(-) diff --git a/resources/nginx/nginx-ssl.conf b/resources/nginx/nginx-ssl.conf index 525d2d826..dcb9a9744 100644 --- a/resources/nginx/nginx-ssl.conf +++ b/resources/nginx/nginx-ssl.conf @@ -1,134 +1,141 @@ -upstream webapp { - server cvmanager_webapp:80; +events { + worker_connections 1024; } -upstream api { - server cvmanager_api:8000; -} - -upstream keycloak { - server cvmanager_keycloak:8080; -} +http { + upstream webapp { + server cvmanager_webapp:80; + } -# Rate limiting zones -limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s; -limit_req_zone $binary_remote_addr zone=general_limit:10m rate=30r/s; + upstream api { + server cvmanager_api:8000; + } -# WebSocket connection upgrade map -map $http_upgrade $connection_upgrade { - default upgrade; - '' close; -} + upstream keycloak { + server cvmanager_keycloak:8080; + } -server { - listen 80; - server_name ${SERVER_HOST}; - return 301 https://$server_name$request_uri; -} + # Rate limiting zones + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s; + limit_req_zone $binary_remote_addr zone=general_limit:10m rate=30r/s; -server { - listen 443 ssl http2; - server_name ${SERVER_HOST}; - - # SSL Configuration - ssl_certificate /etc/ssl/certs/server.crt; - ssl_certificate_key /etc/ssl/private/server.key; - ssl_dhparam /etc/ssl/certs/dhparam.pem; - - # SSL Protocols and Ciphers - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; - ssl_prefer_server_ciphers off; - - # SSL Session Configuration - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - ssl_session_tickets off; - - # Security Headers - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header Referrer-Policy "strict-origin-when-cross-origin" always; - - # Client body size limit (adjust based on your needs) - client_max_body_size 10M; - - # Gzip Compression - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; - - # Proxy Timeouts - proxy_connect_timeout 60s; - proxy_send_timeout 60s; - proxy_read_timeout 60s; - - # API routes - location /api/ { - limit_req zone=api_limit burst=20 nodelay; - - proxy_pass http://api/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # WebSocket support - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - # Buffering settings - proxy_buffering on; - proxy_buffer_size 4k; - proxy_buffers 8 4k; - proxy_busy_buffers_size 8k; + # WebSocket connection upgrade map + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; } - # Keycloak routes - location /auth/ { - limit_req zone=general_limit burst=30 nodelay; - - proxy_pass http://keycloak/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # WebSocket support - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - # Larger buffers for Keycloak authentication flows - proxy_buffering on; - proxy_buffer_size 8k; - proxy_buffers 8 8k; - proxy_busy_buffers_size 16k; + server { + listen 80; + server_name ${SERVER_HOST}; + return 301 https://$server_name$request_uri; } - # Webapp routes (default) - location / { - limit_req zone=general_limit burst=30 nodelay; - - proxy_pass http://webapp/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # WebSocket support (for potential real-time features) - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - # Buffering settings - proxy_buffering on; - proxy_buffer_size 4k; - proxy_buffers 8 4k; - proxy_busy_buffers_size 8k; + server { + listen 443 ssl; + http2 on; + server_name ${SERVER_HOST}; + + # SSL Configuration + ssl_certificate /etc/ssl/certs/server.crt; + ssl_certificate_key /etc/ssl/private/server.key; + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # SSL Protocols and Ciphers + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; + ssl_prefer_server_ciphers off; + + # SSL Session Configuration + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + ssl_session_tickets off; + + # Security Headers + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # Client body size limit (adjust based on your needs) + client_max_body_size 10M; + + # Gzip Compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; + + # Proxy Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + + # API routes + location /api/ { + limit_req zone=api_limit burst=20 nodelay; + + proxy_pass http://api; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Buffering settings + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + proxy_busy_buffers_size 8k; + } + + # Keycloak routes + location /auth/ { + limit_req zone=general_limit burst=30 nodelay; + + proxy_pass http://keycloak; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Larger buffers for Keycloak authentication flows + proxy_buffering on; + proxy_buffer_size 8k; + proxy_buffers 8 8k; + proxy_busy_buffers_size 16k; + } + + # Webapp routes (default) + location / { + limit_req zone=general_limit burst=30 nodelay; + + proxy_pass http://webapp; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support (for potential real-time features) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Buffering settings + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + proxy_busy_buffers_size 8k; + } } } \ No newline at end of file From 913cc8c26fad6f97bbde05955587c2b4aa5e9c41 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 09:54:18 -0700 Subject: [PATCH 18/49] chore: update realm.json to include HTTPS origins for redirectUris and webOrigins --- resources/keycloak/realm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/keycloak/realm.json b/resources/keycloak/realm.json index 36fe2dfdd..976624c8e 100644 --- a/resources/keycloak/realm.json +++ b/resources/keycloak/realm.json @@ -688,8 +688,8 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": ["http://localhost:3000/*", "http://localhost/*", "http://cvmanager.local.com/*"], - "webOrigins": ["http://localhost:3000", "http://localhost", "http://cvmanager.local.com"], + "redirectUris": ["http://localhost:3000/*", "http://localhost/*", "http://cvmanager.local.com/*", "https://cvmanager.local.com/*"], + "webOrigins": ["http://localhost:3000", "http://localhost", "http://cvmanager.local.com", "https://cvmanager.local.com"], "notBefore": 0, "bearerOnly": false, "consentRequired": false, From 22be9b2489e66f7355a24b198765e375950a763e Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 10:19:48 -0700 Subject: [PATCH 19/49] chore: update cvmanager API port in nginx SSL configuration --- resources/nginx/nginx-ssl.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/nginx/nginx-ssl.conf b/resources/nginx/nginx-ssl.conf index dcb9a9744..8e1db5565 100644 --- a/resources/nginx/nginx-ssl.conf +++ b/resources/nginx/nginx-ssl.conf @@ -8,7 +8,7 @@ http { } upstream api { - server cvmanager_api:8000; + server cvmanager_api:5000; } upstream keycloak { From d437b14801acdd946e3a76511d9ca9f168962b8f Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 10:20:08 -0700 Subject: [PATCH 20/49] chore: update docker-compose.yml to remove unused gen_dhparam.sh volume and adjust Keycloak proxy settings --- docker-compose.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e472ab69e..0a7b84bee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,6 @@ services: SERVER_HOST: ${WEBAPP_DOMAIN} volumes: - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/nginx-ssl.conf:/etc/nginx/templates/nginx.conf.template - - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/gen_dhparam.sh:/docker-entrypoint.d/gen_dhparam.sh - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/ssl/server.crt:/etc/ssl/certs/server.crt - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/ssl/server.key:/etc/ssl/private/server.key depends_on: @@ -210,10 +209,12 @@ services: GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET} KC_HOSTNAME_STRICT_HTTPS: 'false' KC_HTTP_ENABLED: 'true' + KC_PROXY_HEADERS: xforwarded command: - start-dev - --import-realm - - --proxy edge + - --http-relative-path + - /auth logging: options: max-size: '10m' From 969abaf8fb3442e2d3b11dd5bea694ef545190f1 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 10:20:56 -0700 Subject: [PATCH 21/49] chore: update sample.env to change Keycloak and API endpoints to use HTTPS --- sample.env | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sample.env b/sample.env index 72a3658f7..5d053943d 100644 --- a/sample.env +++ b/sample.env @@ -11,15 +11,14 @@ COMPOSE_PROFILES=basic,webapp,mongo_full,kafka_full,intersection,kafka_connect_s DOCKER_HOST_IP= -KEYCLOAK_DOMAIN=cvmanager.auth.com +KEYCLOAK_DOMAIN=cvmanager.local.com KC_HOST_IP=${DOCKER_HOST_IP} -KEYCLOAK_ENDPOINT=http://${KEYCLOAK_DOMAIN}:8084 - +KEYCLOAK_ENDPOINT=https://${WEBAPP_DOMAIN}/auth WEBAPP_DOMAIN=cvmanager.local.com WEBAPP_HOST_IP=${DOCKER_HOST_IP} -WEBAPP_ENDPOINT=http://${WEBAPP_DOMAIN} +WEBAPP_ENDPOINT=https://${WEBAPP_DOMAIN} -API_ENDPOINT=http://cvmanager.local.com:8081 +API_ENDPOINT=https://${WEBAPP_DOMAIN}/api # Remote dockerhub variables for ODE and ConflictMonitor images DOCKERHUB_HOST=usdotjpoode From 58fdbe58f9e1ee965c93e301b94945626772f74f Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 10:59:33 -0700 Subject: [PATCH 22/49] chore: update nginx SSL configuration to include trailing slash in proxy_pass for API --- resources/nginx/nginx-ssl.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/nginx/nginx-ssl.conf b/resources/nginx/nginx-ssl.conf index 8e1db5565..c038fa7bb 100644 --- a/resources/nginx/nginx-ssl.conf +++ b/resources/nginx/nginx-ssl.conf @@ -76,7 +76,7 @@ http { location /api/ { limit_req zone=api_limit burst=20 nodelay; - proxy_pass http://api; + proxy_pass http://api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; From 9c55fc45738dbb87efd96272a324d10c260a9fed Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 10:59:55 -0700 Subject: [PATCH 23/49] chore: update Keycloak endpoint in sample.env to include trailing slash and set PG_SSL_REQUIRED to False --- sample.env | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sample.env b/sample.env index 5d053943d..c6b0dfd89 100644 --- a/sample.env +++ b/sample.env @@ -13,7 +13,7 @@ DOCKER_HOST_IP= KEYCLOAK_DOMAIN=cvmanager.local.com KC_HOST_IP=${DOCKER_HOST_IP} -KEYCLOAK_ENDPOINT=https://${WEBAPP_DOMAIN}/auth +KEYCLOAK_ENDPOINT=https://${WEBAPP_DOMAIN}/auth/ WEBAPP_DOMAIN=cvmanager.local.com WEBAPP_HOST_IP=${DOCKER_HOST_IP} WEBAPP_ENDPOINT=https://${WEBAPP_DOMAIN} @@ -79,6 +79,8 @@ PG_DB_NAME=postgres PG_DB_USER=postgres # If the PG_DB_PASS variable has special characters, make sure to wrap it in single quotes PG_DB_PASS=postgres +# Set to 'False' to disable SSL when connecting to the Postgres database +PG_SSL_REQUIRED=False #### ---- cvmanager_api @@ -455,5 +457,4 @@ NGINX_ENCRYPTION="plain" SERVER_CERT_FILE="ota_server.crt" SERVER_KEY_FILE="ota_server.key" -NGINX_PROXY_RESOURCES=./resources/nginx -CA_CERT_PATH=./resources/certs/ca.crt \ No newline at end of file +NGINX_PROXY_RESOURCES=./resources/nginx \ No newline at end of file From 656165a31cbffb301d3b3502c5c995c4fb2a5ad1 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 11:00:09 -0700 Subject: [PATCH 24/49] chore: enhance SSL connection handling for PostgreSQL based on PG_SSL_REQUIRED environment variable --- services/common/pgquery.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/services/common/pgquery.py b/services/common/pgquery.py index a166e237b..4c1675d8f 100644 --- a/services/common/pgquery.py +++ b/services/common/pgquery.py @@ -24,8 +24,25 @@ def init_tcp_connection_engine(db_user, db_pass, db_name, db_hostname, db_port): logging.info("Creating DB pool") logging.debug(f"{db_user},{db_name},{db_hostname},{db_port}") - ssl_context = ssl.create_default_context() - ssl_context.load_verify_locations("/etc/ssl/ca/ca.crt") + + pg_ssl_required = os.getenv("PG_SSL_REQUIRED", "True").lower() == "true" + connect_args = {} + + if pg_ssl_required: + ssl_context = ssl.create_default_context() + ca_cert_path = "/etc/ssl/ca/ca.crt" + if os.path.exists(ca_cert_path): + ssl_context.load_verify_locations(ca_cert_path) + else: + logging.warning( + f"CA certificate not found at {ca_cert_path}. Skipping SSL verification for Postgres." + ) + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + connect_args["ssl_context"] = ssl_context + else: + logging.info("PG_SSL_REQUIRED is False. Connecting to Postgres without SSL.") + pool = sqlalchemy.create_engine( # Equivalent URL: # postgresql+pg8000://:@:/ @@ -37,7 +54,7 @@ def init_tcp_connection_engine(db_user, db_pass, db_name, db_hostname, db_port): port=db_port, # e.g. 5432 database=db_name, # e.g. "my-database-name" ), - connect_args={"ssl_context": ssl_context}, + connect_args=connect_args, **db_config, ) # pool.dialect.description_encoding = None From 13d643277ddf895aa8ba560b80a4eff6e31cf55a Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 11:00:26 -0700 Subject: [PATCH 25/49] chore: add configurable Keycloak verification based on KEYCLOAK_VERIFY environment variable --- services/api/src/middleware.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/api/src/middleware.py b/services/api/src/middleware.py index 0ca717659..41c81249b 100644 --- a/services/api/src/middleware.py +++ b/services/api/src/middleware.py @@ -38,11 +38,18 @@ class FEATURE_KEYS_LITERAL(Enum): def get_user_role(token) -> UserInfo | None: # TODO: Consider using pythjon-jose or PyJWT to locally validate the token, instead of calling the Keycloak server + keycloak_verify = os.getenv("KEYCLOAK_VERIFY", "True") + if keycloak_verify.lower() == "true": + keycloak_verify = True + elif keycloak_verify.lower() == "false": + keycloak_verify = False + keycloak_openid = KeycloakOpenID( server_url=os.getenv("KEYCLOAK_ENDPOINT"), realm_name=os.getenv("KEYCLOAK_REALM"), client_id=os.getenv("KEYCLOAK_API_CLIENT_ID"), client_secret_key=os.getenv("KEYCLOAK_API_CLIENT_SECRET_KEY"), + verify=keycloak_verify, ) introspect = keycloak_openid.introspect(token) data = None From 7173d32bef519b5ad6fc45b7bb48d3b23c5040b1 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 11:00:40 -0700 Subject: [PATCH 26/49] chore: update docker-compose files to include PG_SSL_REQUIRED variable and adjust Keycloak healthcheck URL --- docker-compose-addons.yml | 8 +++++++- docker-compose.yml | 6 ++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docker-compose-addons.yml b/docker-compose-addons.yml index f6beb0d73..90e50b9c9 100644 --- a/docker-compose-addons.yml +++ b/docker-compose-addons.yml @@ -27,6 +27,8 @@ services: DESTINATION_DB: ${COUNT_DESTINATION_DB} + PG_SSL_REQUIRED: ${PG_SSL_REQUIRED:-True} + MONGO_DB_URI: ${MONGO_DB_URI} MONGO_DB_NAME: ${CM_DATABASE_NAME} INPUT_COUNTS_MONGO_COLLECTION_NAME: ${INPUT_COUNTS_MONGO_COLLECTION_NAME} @@ -67,6 +69,8 @@ services: PG_DB_USER: ${PG_DB_USER} PG_DB_PASS: ${PG_DB_PASS} + PG_SSL_REQUIRED: ${PG_SSL_REQUIRED:-True} + LOGGING_LEVEL: ${RSU_STATUS_LOGGING_LEVEL} logging: options: @@ -101,6 +105,8 @@ services: PG_DB_USER: ${PG_DB_USER} PG_DB_PASS: ${PG_DB_PASS} + PG_SSL_REQUIRED: ${PG_SSL_REQUIRED:-True} + PROJECT_ID: ${GCP_PROJECT_ID} GOOGLE_APPLICATION_CREDENTIALS: /home/gcp_key.json @@ -172,7 +178,7 @@ services: # volumes: # - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json # - ${HOST_BLOB_STORAGE_DIRECTORY}:/mnt/blob_storage -# - ${CA_CERT_PATH:-./resources/certs/ca.crt}:/etc/ssl/ca/ca.crt +# - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/ssl/server.crt:/etc/ssl/certs/server.crt # logging: # options: # max-size: '10m' diff --git a/docker-compose.yml b/docker-compose.yml index 0a7b84bee..f60238243 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -98,11 +98,13 @@ services: TIMEZONE: ${TIMEZONE} LOGGING_LEVEL: ${API_LOGGING_LEVEL} + KEYCLOAK_VERIFY: /etc/ssl/certs/server.crt + PG_SSL_REQUIRED: ${PG_SSL_REQUIRED:-True} depends_on: - cvmanager_postgres volumes: - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json - - ${CA_CERT_PATH:-./resources/certs/ca.crt}:/etc/ssl/ca/ca.crt + - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/ssl/server.crt:/etc/ssl/certs/server.crt logging: options: max-size: '10m' @@ -219,7 +221,7 @@ services: options: max-size: '10m' healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:9000/health'] + test: ['CMD', 'curl', '-f', 'http://localhost:8080/auth/realms/master'] interval: 30s timeout: 10s retries: 3 From 849361fd57e6128e6dc465926fa9405d48b42db0 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 11:01:10 -0700 Subject: [PATCH 27/49] chore: add Dockerfile for nginx with OpenSSL and dhparam generation script --- resources/nginx/Dockerfile | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 resources/nginx/Dockerfile diff --git a/resources/nginx/Dockerfile b/resources/nginx/Dockerfile new file mode 100644 index 000000000..b02794b5b --- /dev/null +++ b/resources/nginx/Dockerfile @@ -0,0 +1,14 @@ +FROM nginx:alpine + +# The official nginx image supports environment variable substitution in templates +# placed in /etc/nginx/templates/*.template. +# We mount the config in docker-compose, but we can also copy it here as a default. + +# Ensure we have openssl for dhparam generation if needed by the script +RUN apk add --no-network --no-cache openssl || apk add --no-cache openssl + +# Ensure gen_dhparam.sh is executable when mounted +COPY gen_dhparam.sh /docker-entrypoint.d/gen_dhparam.sh +RUN chmod +x /docker-entrypoint.d/gen_dhparam.sh + +# The gen_dhparam.sh script will be executed by the entrypoint because it's in /docker-entrypoint.d/ From f86ef5e5c17b0e9cd890f80245ed3aa68e07c6ba Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 11:37:33 -0700 Subject: [PATCH 28/49] chore: update realm configuration to require SSL for all connections and add local testing documentation for NGINX SSL proxy --- resources/keycloak/realm.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/keycloak/realm.json b/resources/keycloak/realm.json index 976624c8e..f6e7198ea 100644 --- a/resources/keycloak/realm.json +++ b/resources/keycloak/realm.json @@ -27,7 +27,7 @@ "oauth2DeviceCodeLifespan": 600, "oauth2DevicePollingInterval": 5, "enabled": true, - "sslRequired": "external", + "sslRequired": "all", "registrationAllowed": false, "registrationEmailAsUsername": false, "rememberMe": false, @@ -688,8 +688,8 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": ["http://localhost:3000/*", "http://localhost/*", "http://cvmanager.local.com/*", "https://cvmanager.local.com/*"], - "webOrigins": ["http://localhost:3000", "http://localhost", "http://cvmanager.local.com", "https://cvmanager.local.com"], + "redirectUris": ["http://localhost:3000/*", "http://localhost/*", "https://cvmanager.local.com/*"], + "webOrigins": ["http://localhost:3000", "http://localhost", "https://cvmanager.local.com"], "notBefore": 0, "bearerOnly": false, "consentRequired": false, From 4c66d534b570e969e35ffd42a2ffe9302f25c8d5 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 11:37:44 -0700 Subject: [PATCH 29/49] chore: update nginx configuration to enhance rate limiting and buffer settings for Keycloak and webapp routes --- resources/nginx/nginx-ssl.conf | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/resources/nginx/nginx-ssl.conf b/resources/nginx/nginx-ssl.conf index c038fa7bb..89b91c616 100644 --- a/resources/nginx/nginx-ssl.conf +++ b/resources/nginx/nginx-ssl.conf @@ -3,6 +3,9 @@ events { } http { + include mime.types; + default_type application/octet-stream; + upstream webapp { server cvmanager_webapp:80; } @@ -16,8 +19,8 @@ http { } # Rate limiting zones - limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s; - limit_req_zone $binary_remote_addr zone=general_limit:10m rate=30r/s; + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=50r/s; + limit_req_zone $binary_remote_addr zone=general_limit:10m rate=100r/s; # WebSocket connection upgrade map map $http_upgrade $connection_upgrade { @@ -96,13 +99,17 @@ http { # Keycloak routes location /auth/ { - limit_req zone=general_limit burst=30 nodelay; + gzip off; + limit_req zone=general_limit burst=100 nodelay; proxy_pass http://keycloak; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Ssl on; # WebSocket support proxy_http_version 1.1; @@ -111,14 +118,15 @@ http { # Larger buffers for Keycloak authentication flows proxy_buffering on; - proxy_buffer_size 8k; - proxy_buffers 8 8k; - proxy_busy_buffers_size 16k; + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + proxy_redirect off; } # Webapp routes (default) location / { - limit_req zone=general_limit burst=30 nodelay; + limit_req zone=general_limit burst=100 nodelay; proxy_pass http://webapp; proxy_set_header Host $host; From 27789821bd0b7622afde36f460b04700c301fa34 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 11:38:02 -0700 Subject: [PATCH 30/49] chore: update Keycloak configuration in docker-compose to enforce strict HTTPS and set default log level --- docker-compose.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index f60238243..76acf485d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -200,18 +200,21 @@ services: KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN} KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} KC_HEALTH_ENABLED: 'true' - KC_LOG_LEVEL: ${KC_LOGGING_LEVEL} + KC_LOG_LEVEL: ${KC_LOGGING_LEVEL:-INFO} KC_DB: postgres KC_DB_URL: jdbc:postgresql://${PG_DB_HOST}/postgres?currentSchema=keycloak KC_DB_USERNAME: ${PG_DB_USER} KC_DB_PASSWORD: ${PG_DB_PASS} KC_HOSTNAME: ${KEYCLOAK_DOMAIN} + KC_HOSTNAME_PORT: 443 + KC_HOSTNAME_STRICT_HTTPS: 'true' + KC_HOSTNAME_STRICT: 'false' KEYCLOAK_API_CLIENT_SECRET_KEY: ${KEYCLOAK_API_CLIENT_SECRET_KEY} GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID} GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET} - KC_HOSTNAME_STRICT_HTTPS: 'false' KC_HTTP_ENABLED: 'true' KC_PROXY_HEADERS: xforwarded + KC_HOSTNAME_STRICT_BACKCHANNEL: 'false' command: - start-dev - --import-realm From 0cbde2276c7289231c44ebdc538a3ecd4102a394 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 11:38:13 -0700 Subject: [PATCH 31/49] chore: add detailed local testing documentation for NGINX SSL proxy setup --- resources/nginx/README-testing.md | 107 ++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 resources/nginx/README-testing.md diff --git a/resources/nginx/README-testing.md b/resources/nginx/README-testing.md new file mode 100644 index 000000000..c4d4245d4 --- /dev/null +++ b/resources/nginx/README-testing.md @@ -0,0 +1,107 @@ +### Local Testing Documentation for NGINX SSL Proxy + +This documentation provides instructions for setting up and testing the NGINX SSL proxy locally. The NGINX proxy serves as the entry point for the JPO CV Manager, handling SSL termination and routing requests to the webapp, API, and Keycloak services. + +#### Prerequisites + +- **Docker and Docker Compose**: Ensure you have Docker installed and running on your machine. +- **OpenSSL**: Required for generating self-signed certificates. +- **Root/Administrator Access**: Needed to modify the local `hosts` file. + +#### 1. Generate Self-Signed Certificates + +The NGINX configuration expects SSL certificates in the `resources/nginx/ssl` directory. You can generate these for local testing using the following commands: + +```bash +# Navigate to the nginx resources directory +cd resources/nginx + +# Create the ssl directory if it doesn't exist +mkdir -p ssl + +# Generate a self-signed certificate and private key with SAN +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout ssl/server.key -out ssl/server.crt \ + -subj "/C=US/ST=State/L=City/O=Organization/OU=Unit/CN=cvmanager.local.com" \ + -addext "subjectAltName = DNS:cvmanager.local.com" +``` + +*Note: Ensure the `CN` (Common Name) and `subjectAltName` match the `WEBAPP_DOMAIN` defined in your `.env` file. Modern browsers and libraries (like Python's `requests`) require the `subjectAltName` extension for proper hostname verification.* + +#### 2. Configure Local Domain + +To access the services using the configured domain name (e.g., `cvmanager.local.com`), you need to update your local `hosts` file. + +**For Linux/macOS:** +Add the following line to `/etc/hosts`: +``` + cvmanager.local.com +``` + +**For Windows:** +Add the following line to `C:\Windows\System32\drivers\etc\hosts` (run Notepad as Administrator): +``` + cvmanager.local.com +``` + +*Note: Replace `` with your actual WSL2 IP address. You can find this by running `hostname -I` in your WSL terminal.* + +#### 3. Environment Configuration + +Ensure your `.env` file is properly configured. You can use `sample.env` as a template. Key variables for the NGINX setup include: + +```env +DOCKER_HOST_IP= +WEBAPP_DOMAIN=cvmanager.local.com +WEBAPP_HOST_IP=${DOCKER_HOST_IP} +NGINX_PROXY_RESOURCES=./resources/nginx +PG_SSL_REQUIRED=False +``` + +*Note: `PG_SSL_REQUIRED=False` is required when connecting to the local Postgres container, as it does not have SSL enabled.* + +#### 4. Run the Setup + +To test the NGINX proxy, you only need to start the NGINX service along with its dependencies (WebApp, API, Keycloak, and Postgres). Use the following command to start the necessary services: + +```bash +# From the project root +docker compose --profile nginx_proxy --profile basic --profile webapp up --build +``` + +*Note: The `basic` profile includes `cvmanager_api`, `cvmanager_keycloak`, and `cvmanager_postgres`.* + +Alternatively, if you want to run everything: +```bash +docker compose up --build +``` + +The `cvmanager_nginx_proxy` service will: +1. Generate `dhparam.pem` using the `gen_dhparam.sh` script if it's missing. +2. Substitute environment variables in the `nginx-ssl.conf` template. +3. Start NGINX on ports 80 and 443. + +#### 5. Verification + +Once the services are up and running, you can verify the setup: + +- **HTTP Redirection**: Navigate to `http://cvmanager.local.com`. It should automatically redirect you to `https://cvmanager.local.com`. +- **HTTPS Access**: Navigate to `https://cvmanager.local.com`. Since you are using a self-signed certificate, your browser will show a security warning. You can proceed past this warning to access the webapp. +- **API Routes**: Verify that `https://cvmanager.local.com/api/` routes to the CV Manager API. +- **Auth Routes**: Verify that `https://cvmanager.local.com/auth/` routes to the Keycloak instance. + +#### Important Note on Service Endpoints + +When using the NGINX proxy, all services are accessed through the same domain and port (443). Ensure your `.env` file reflects this. Specifically, the `KEYCLOAK_ENDPOINT` **must include a trailing slash** to ensure correct routing by the proxy and proper URL construction by the API: + +```env +KEYCLOAK_ENDPOINT=https://cvmanager.local.com/auth/ +WEBAPP_ENDPOINT=https://cvmanager.local.com +API_ENDPOINT=https://cvmanager.local.com/api +``` + +#### Troubleshooting + +- **Certificate Errors**: If NGINX fails to start, check the logs (`docker compose logs cvmanager_nginx_proxy`) to ensure `server.crt` and `server.key` are correctly mounted in `/etc/ssl/certs/` and `/etc/ssl/private/` respectively. +- **Domain Resolution**: If you cannot reach the domain, ensure your `hosts` file was saved correctly and that you are not behind a VPN/proxy that interferes with local resolution. +- **Port Conflicts**: Ensure ports 80 and 443 are not being used by other services on your host machine. From aa1aee46c365f504400f340cd176352ca97d2be7 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 11:49:50 -0700 Subject: [PATCH 32/49] chore: update COMPOSE_PROFILES in sample.env --- sample.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample.env b/sample.env index c6b0dfd89..a28e3d25f 100644 --- a/sample.env +++ b/sample.env @@ -5,7 +5,7 @@ # Additionally, each individual service in this project can be started by specifying its service name as a profile. # The currently available profile groups are listed below. # basic, webapp, mongo_full, kafka_full, intersection, intersection_no_api, conflictmonitor, addons, obu_ota, kafka_connect_standalone -COMPOSE_PROFILES=basic,webapp,mongo_full,kafka_full,intersection,kafka_connect_standalone +COMPOSE_PROFILES=nginx_proxy,basic,webapp,addons ######## -------- General Variables - Apply to All Profiles From 14310925f2cccc76c847805a21a80f2d93043a2c Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 11:49:59 -0700 Subject: [PATCH 33/49] chore: remove unnecessary `depends_on` condition from iss_health_check service in docker-compose-addons --- docker-compose-addons.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docker-compose-addons.yml b/docker-compose-addons.yml index 90e50b9c9..c83c40787 100644 --- a/docker-compose-addons.yml +++ b/docker-compose-addons.yml @@ -86,10 +86,6 @@ services: dockerfile: Dockerfile.iss_health_check image: iss_health_check:latest restart: on-failure:3 - depends_on: - cvmanager_postgres: - condition: service_healthy - required: false environment: STORAGE_TYPE: ${STORAGE_TYPE:-POSTGRES} From 233eb0bb013b7ad93e6d876f7bbf0873faa5f3a4 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 12:19:37 -0700 Subject: [PATCH 34/49] chore: add deployment documentation for NGINX SSL proxy with external PostgreSQL setup --- resources/nginx/README-deployment.md | 102 +++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 resources/nginx/README-deployment.md diff --git a/resources/nginx/README-deployment.md b/resources/nginx/README-deployment.md new file mode 100644 index 000000000..447d603d3 --- /dev/null +++ b/resources/nginx/README-deployment.md @@ -0,0 +1,102 @@ +### Deployment Documentation for JPO CV Manager + +This documentation provides instructions for deploying the JPO CV Manager to a VM with a specific domain name and an external PostgreSQL database. This setup uses NGINX as an SSL proxy for routing and SSL termination. + +#### Prerequisites + +- **Docker and Docker Compose**: Ensure you have Docker installed and running on your VM. +- **External PostgreSQL Database**: A PostgreSQL instance (version 15+ recommended) accessible from the VM. +- **SSL Certificates**: You must provide valid SSL certificates for your domain (e.g., from a corporate CA or other provider). +- **DNS Record**: A DNS A record or CNAME pointing your domain name to the VM's public IP address. + +#### 1. Configure SSL Certificates + +The NGINX configuration expects the provided SSL certificates in the `resources/nginx/ssl` directory. + +1. Navigate to the NGINX resources directory: + ```bash + cd resources/nginx + mkdir -p ssl + ``` +2. Place your provided `server.crt` and `server.key` files in the `ssl` directory. + - `resources/nginx/ssl/server.crt` + - `resources/nginx/ssl/server.key` + +#### 2. Environment Configuration + +Create or update your `.env` file in the project root. You can use `sample.env` as a template. Configure the following key variables for deployment: + +##### General and Domain Settings +```env +# The domain name of your deployment +WEBAPP_DOMAIN=cvmanager.example.com +KEYCLOAK_DOMAIN=cvmanager.example.com + +# The public IP or internal IP of the VM where Docker is running +DOCKER_HOST_IP= +WEBAPP_HOST_IP=${DOCKER_HOST_IP} + +# NGINX Resources path +NGINX_PROXY_RESOURCES=./resources/nginx +``` + +##### External PostgreSQL Settings +Configure these variables to point to your external database instead of the included container: +```env +# Connection details for the external Postgres instance +PG_DB_HOST=: +PG_DB_NAME= +PG_DB_USER= +PG_DB_PASS='' + +# Enable SSL for the connection to the external database +PG_SSL_REQUIRED=True +``` + +##### Service Endpoints +Ensure the endpoints use the configured domain: +```env +KEYCLOAK_ENDPOINT=https://${WEBAPP_DOMAIN}/auth/ +WEBAPP_ENDPOINT=https://${WEBAPP_DOMAIN} +API_ENDPOINT=https://${WEBAPP_DOMAIN}/api +``` + +#### 3. Update Docker Profiles + +In your `.env` file, update the `COMPOSE_PROFILES` to include the necessary services but exclude the local postgres if you don't want it to run. However, since many services depend on the `basic` profile which includes `cvmanager_api` and `cvmanager_keycloak`, it is recommended to use specific service profiles or be aware that the `cvmanager_postgres` container may still start if the `basic` profile is used. + +To run only the necessary services for an external DB deployment, you can use: +```env +COMPOSE_PROFILES=nginx_proxy,cvmanager_api,cvmanager_keycloak,webapp,addons +``` + +#### 4. Run the Deployment + +Once the `.env` file and SSL certificates are in place, start the services using Docker Compose: + +```bash +# From the project root +docker compose up -d --build +``` + +The `cvmanager_nginx_proxy` service will: +1. Generate `dhparam.pem` if it's missing. +2. Substitute environment variables in the `nginx-ssl.conf` template. +3. Start NGINX on ports 80 and 443. + +#### 5. Verification + +1. **Browser Access**: Navigate to `https://cvmanager.example.com`. You should see the CV Manager login page. +2. **SSL**: Verify that the SSL certificate is valid and issued to your domain. +3. **Database Connection**: Check the logs for `cvmanager_api` and `cvmanager_keycloak` to ensure they successfully connected to the external database: + ```bash + docker compose logs cvmanager_api + docker compose logs cvmanager_keycloak + ``` +4. **Keycloak**: Ensure you can log in. Keycloak will automatically create its schema in the external database on the first run (using the `keycloak` schema if configured in the JDBC URL). + +#### Troubleshooting + +- **Database Connectivity**: Ensure the VM's IP is whitelisted in your external PostgreSQL's `pg_hba.conf` and that any firewalls allow traffic on the Postgres port (usually 5432). +- **SSL Certificate Mounts**: If NGINX fails to start, verify that the files exist at `resources/nginx/ssl/server.crt` and `resources/nginx/ssl/server.key`. +- **Domain Resolution**: Ensure your DNS is correctly configured. You can test this using `ping cvmanager.example.com` from a remote machine. From dc682464b8a07875727aed19dc07cc4050ef683d Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 12:33:32 -0700 Subject: [PATCH 35/49] chore: re-enable file extension validation in `is_valid_file_extension` function --- services/common/util.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/common/util.py b/services/common/util.py index a6152779d..92405210f 100644 --- a/services/common/util.py +++ b/services/common/util.py @@ -52,9 +52,9 @@ def validate_file_type(file_name, extension=".tar"): file_name (str): The name of the file to be downloaded. extension (str): The file extension to validate against. """ - # if not file_name.endswith(extension): - # logging.error( - # f'Unsupported file type for storage object {file_name}. Only "{extension}" files are supported.' - # ) - # return False + if not file_name.endswith(extension): + logging.error( + f'Unsupported file type for storage object {file_name}. Only "{extension}" files are supported.' + ) + return False return True From 64b5fbd048d40ea9352a886c0908853c10c82b07 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 12:33:58 -0700 Subject: [PATCH 36/49] chore: update `test_pgquery` to mock `PG_SSL_REQUIRED` and adjust `create_engine` call --- services/common/tests/test_pgquery.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/common/tests/test_pgquery.py b/services/common/tests/test_pgquery.py index ab3c40f05..8c98dc204 100644 --- a/services/common/tests/test_pgquery.py +++ b/services/common/tests/test_pgquery.py @@ -5,6 +5,7 @@ # test that init_tcp_connection_engine is calling sqlalchemy.create_engine with expected arguments +@patch.dict(os.environ, {"PG_SSL_REQUIRED": "False"}) @patch( "common.pgquery.db_config", new={"pool_size": 5, "max_overflow": 2, "pool_timeout": 30, "pool_recycle": 1800}, @@ -44,8 +45,9 @@ def test_init_tcp_connection_engine(): "pool_timeout": 30, "pool_recycle": 1800, } - sqlalchemy.create_engine.assert_called_once_with("my_url", **my_db_config) - + sqlalchemy.create_engine.assert_called_once_with( + "my_url", connect_args={}, **my_db_config + ) # test that init_socket_connection_engine is calling sqlalchemy.create_engine with expected arguments @patch( From aced1d63abdc29714e3f1d2da152dea362d4f7db Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 31 Dec 2025 12:35:18 -0700 Subject: [PATCH 37/49] chore: update `test_get_upgrade_limit_no_env` assertion to reflect new upgrade limit --- .../upgrade_scheduler/test_upgrade_scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/addons/tests/firmware_manager/upgrade_scheduler/test_upgrade_scheduler.py b/services/addons/tests/firmware_manager/upgrade_scheduler/test_upgrade_scheduler.py index 4a10f58f2..06e47882f 100644 --- a/services/addons/tests/firmware_manager/upgrade_scheduler/test_upgrade_scheduler.py +++ b/services/addons/tests/firmware_manager/upgrade_scheduler/test_upgrade_scheduler.py @@ -1101,7 +1101,7 @@ def test_init_background_task(mock_bgscheduler): def test_get_upgrade_limit_no_env(): limit = upgrade_scheduler.get_upgrade_limit() - assert limit == 1 + assert limit == 4 @patch.dict("os.environ", {"ACTIVE_UPGRADE_LIMIT": "5"}) From 685e102f616b47e7c0f0ffe7f69a9e30f1778f7e Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 6 Jan 2026 14:58:35 -0700 Subject: [PATCH 38/49] chore: update NGINX configuration for improved SSL settings and proxy handling --- resources/nginx/nginx-ssl.conf | 541 +++++++++++++++++++++++++++------ 1 file changed, 447 insertions(+), 94 deletions(-) diff --git a/resources/nginx/nginx-ssl.conf b/resources/nginx/nginx-ssl.conf index 89b91c616..69f83c772 100644 --- a/resources/nginx/nginx-ssl.conf +++ b/resources/nginx/nginx-ssl.conf @@ -1,14 +1,8 @@ events { - worker_connections 1024; + worker_connections 768; } http { - include mime.types; - default_type application/octet-stream; - - upstream webapp { - server cvmanager_webapp:80; - } upstream api { server cvmanager_api:5000; @@ -18,132 +12,491 @@ http { server cvmanager_keycloak:8080; } - # Rate limiting zones - limit_req_zone $binary_remote_addr zone=api_limit:10m rate=50r/s; - limit_req_zone $binary_remote_addr zone=general_limit:10m rate=100r/s; - - # WebSocket connection upgrade map - map $http_upgrade $connection_upgrade { - default upgrade; - '' close; + upstream webapp { + server cvmanager_webapp:80; } - server { - listen 80; - server_name ${SERVER_HOST}; - return 301 https://$server_name$request_uri; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + ## + # SSL Settings + ## + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_dhparam "/etc/nginx/dhparam.pem"; + add_header Strict-Transport-Security "max-age=31557600; includeSubDomains"; + ssl_stapling on; + ssl_stapling_verify on; + + ## + # General Logging Settings + ## + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/access.log; + + # HTTP forwards everything to HTTPS + # + server { + listen 80; + + server_name $SERVER_HOST; + + location / { + return 301 https://$host$request_uri; + } + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + location ~ /\.ht { + deny all; + } } server { - listen 443 ssl; - http2 on; - server_name ${SERVER_HOST}; + listen 443 default_server ssl; + server_name $SERVER_HOST; - # SSL Configuration - ssl_certificate /etc/ssl/certs/server.crt; + ssl_certificate /etc/ssl/certs/server.crt; ssl_certificate_key /etc/ssl/private/server.key; - ssl_dhparam /etc/ssl/certs/dhparam.pem; - # SSL Protocols and Ciphers - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; - ssl_prefer_server_ciphers off; + client_max_body_size 300M; + + location ~ /\.ht { + deny all; + } - # SSL Session Configuration - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - ssl_session_tickets off; + # webapp endpoints + location / { + proxy_pass http://webapp/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; - # Security Headers - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header Referrer-Policy "strict-origin-when-cross-origin" always; + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; - # Client body size limit (adjust based on your needs) - client_max_body_size 10M; + access_log /var/log/nginx/webapp-access.log; + } - # Gzip Compression - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; + location /dashboard { + proxy_pass http://webapp/dashboard; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; - # Proxy Timeouts - proxy_connect_timeout 60s; - proxy_send_timeout 60s; - proxy_read_timeout 60s; + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; - # API routes - location /api/ { - limit_req zone=api_limit burst=20 nodelay; + access_log /var/log/nginx/webapp-access.log; + } - proxy_pass http://api/; - proxy_set_header Host $host; + # keycloak endpoints + location /admin { + proxy_pass http://keycloak/admin; + proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; - # WebSocket support proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; - # Buffering settings - proxy_buffering on; - proxy_buffer_size 4k; - proxy_buffers 8 4k; - proxy_busy_buffers_size 8k; + access_log /var/log/nginx/access.log; } - # Keycloak routes - location /auth/ { - gzip off; - limit_req zone=general_limit burst=100 nodelay; + location /realms { + proxy_pass http://keycloak/realms; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } - proxy_pass http://keycloak; - proxy_set_header Host $host; + location /resources { + proxy_pass http://keycloak/resources; + proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Forwarded-Ssl on; + proxy_set_header Range $http_range; - # WebSocket support proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; - # Larger buffers for Keycloak authentication flows - proxy_buffering on; - proxy_buffer_size 128k; - proxy_buffers 4 256k; - proxy_busy_buffers_size 256k; - proxy_redirect off; + access_log /var/log/nginx/access.log; } - # Webapp routes (default) - location / { - limit_req zone=general_limit burst=100 nodelay; + location /user-auth { + proxy_pass http://api/user-auth; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; - proxy_pass http://webapp; - proxy_set_header Host $host; + access_log /var/log/nginx/access.log; + } + + location /rsuinfo { + proxy_pass http://api/rsuinfo; + proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; - # WebSocket support (for potential real-time features) proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; - # Buffering settings - proxy_buffering on; - proxy_buffer_size 4k; - proxy_buffers 8 4k; - proxy_busy_buffers_size 8k; + access_log /var/log/nginx/access.log; } + + location /rsu-online-status { + proxy_pass http://api/rsu-online-status; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /rsucounts { + proxy_pass http://api/rsucounts; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /rsu-msgfwd-query { + proxy_pass http://api/rsu-msgfwd-query; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /rsu-command { + proxy_pass http://api/rsu-command; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /rsu-map-info { + proxy_pass http://api/rsu-map-info; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /rsu-geo-query { + proxy_pass http://api/rsu-geo-query; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /wzdx-feed { + proxy_pass http://api/wzdx-feed; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /rsu-geo-msg-data { + proxy_pass http://api/rsu-geo-msg-data; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /iss-scms-status { + proxy_pass http://api/iss-scms-status; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /rsu-ssm-srm-data { + proxy_pass http://api/rsu-ssm-srm-data; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /admin-new-rsu { + proxy_pass http://api/admin-new-rsu; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /admin-rsu { + proxy_pass http://api/admin-rsu; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /admin-new-user { + proxy_pass http://api/admin-new-user; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /admin-user { + proxy_pass http://api/admin-user; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /admin-new-org { + proxy_pass http://api/admin-new-org; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /admin-org { + proxy_pass http://api/admin-org; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /contact-support { + proxy_pass http://api/contact-support; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /unsubscribe-error-emails { + proxy_pass http://api/unsubscribe-error-emails; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + } } \ No newline at end of file From 42cc272e39106445eaf79cf5fad1288f2e6c981c Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 6 Jan 2026 15:01:25 -0700 Subject: [PATCH 39/49] chore: update NGINX configuration with new endpoints for geo-query and notification proxies --- resources/nginx/nginx-ssl.conf | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/resources/nginx/nginx-ssl.conf b/resources/nginx/nginx-ssl.conf index 69f83c772..c0a262734 100644 --- a/resources/nginx/nginx-ssl.conf +++ b/resources/nginx/nginx-ssl.conf @@ -260,8 +260,8 @@ http { access_log /var/log/nginx/access.log; } - location /rsu-map-info { - proxy_pass http://api/rsu-map-info; + location /rsu-config-geo-query { + proxy_pass http://api/rsu-config-geo-query; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -277,8 +277,8 @@ http { access_log /var/log/nginx/access.log; } - location /rsu-geo-query { - proxy_pass http://api/rsu-geo-query; + location /rsu-error-summary { + proxy_pass http://api/rsu-error-summary; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -481,8 +481,25 @@ http { access_log /var/log/nginx/access.log; } - location /unsubscribe-error-emails { - proxy_pass http://api/unsubscribe-error-emails; + location /admin-new-notification { + proxy_pass http://api/admin-new-notification; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + + location /admin-notification { + proxy_pass http://api/admin-notification; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; From 97df42f9ba15902b656864eeb604941c7cdc6bb0 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 6 Jan 2026 15:16:07 -0700 Subject: [PATCH 40/49] chore: add support for custom CA certificates in API service and improve related documentation --- .gitignore | 5 ++++- docker-compose.yml | 5 +++-- resources/nginx/README-deployment.md | 26 ++++++++++++++++++++++++-- resources/nginx/README-testing.md | 2 ++ sample.env | 12 +++++++++--- services/Dockerfile.api | 11 +++++++---- 6 files changed, 49 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 3f0ad9861..0abb22182 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,7 @@ services/coverage.xml resources/nginx/ssl/*.crt resources/nginx/ssl/*.key -resources/nginx/ssl/*.pem \ No newline at end of file +resources/nginx/ssl/*.pem +services/resources/ca/*.crt +services/resources/ca/*.key +services/resources/ca/*.pem \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 76acf485d..c8e7bd1c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -98,13 +98,14 @@ services: TIMEZONE: ${TIMEZONE} LOGGING_LEVEL: ${API_LOGGING_LEVEL} - KEYCLOAK_VERIFY: /etc/ssl/certs/server.crt + KEYCLOAK_VERIFY: ${KEYCLOAK_VERIFY:-/usr/local/share/ca-certificates/server.crt} PG_SSL_REQUIRED: ${PG_SSL_REQUIRED:-True} + REQUESTS_CA_BUNDLE: ${REQUESTS_CA_BUNDLE:-/etc/ssl/certs/ca-certificates.crt} depends_on: - cvmanager_postgres volumes: - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json - - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/ssl/server.crt:/etc/ssl/certs/server.crt + - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/ssl/server.crt:/usr/local/share/ca-certificates/server.crt logging: options: max-size: '10m' diff --git a/resources/nginx/README-deployment.md b/resources/nginx/README-deployment.md index 447d603d3..50b5b497f 100644 --- a/resources/nginx/README-deployment.md +++ b/resources/nginx/README-deployment.md @@ -9,7 +9,7 @@ This documentation provides instructions for deploying the JPO CV Manager to a V - **SSL Certificates**: You must provide valid SSL certificates for your domain (e.g., from a corporate CA or other provider). - **DNS Record**: A DNS A record or CNAME pointing your domain name to the VM's public IP address. -#### 1. Configure SSL Certificates +#### 2. Configure SSL and CA Certificates The NGINX configuration expects the provided SSL certificates in the `resources/nginx/ssl` directory. @@ -22,7 +22,29 @@ The NGINX configuration expects the provided SSL certificates in the `resources/ - `resources/nginx/ssl/server.crt` - `resources/nginx/ssl/server.key` -#### 2. Environment Configuration +##### Adding a Custom CA Certificate (Optional) + +If your SSL certificate is issued by a private or corporate CA that is not in the standard trusted root list, you may need to add the CA certificate to the API service's truststore. This ensures the API can securely communicate with Keycloak. + +1. Create the CA resources directory: + ```bash + mkdir -p services/resources/ca + ``` +2. Place your CA certificate (in `.crt` format) in this directory: + - `services/resources/ca/your-corporate-ca.crt` +3. Rebuild the API service: + ```bash + docker compose build cvmanager_api + ``` + +Alternatively, you can mount the CA certificate at runtime by updating the `volumes` section of the `cvmanager_api` service in `docker-compose.yml`: + +```yaml + volumes: + - ./path/to/your-ca.crt:/usr/local/share/ca-certificates/your-ca.crt +``` + +#### 3. Environment Configuration Create or update your `.env` file in the project root. You can use `sample.env` as a template. Configure the following key variables for deployment: diff --git a/resources/nginx/README-testing.md b/resources/nginx/README-testing.md index c4d4245d4..4164c07f2 100644 --- a/resources/nginx/README-testing.md +++ b/resources/nginx/README-testing.md @@ -81,6 +81,8 @@ The `cvmanager_nginx_proxy` service will: 2. Substitute environment variables in the `nginx-ssl.conf` template. 3. Start NGINX on ports 80 and 443. +The `cvmanager_api` service is also configured to trust the generated `server.crt` certificate. This is necessary because the API communicates with Keycloak over HTTPS using the local domain name. Since the certificate is self-signed, it must be added to the API's truststore to avoid SSL verification errors. + #### 5. Verification Once the services are up and running, you can verify the setup: diff --git a/sample.env b/sample.env index a28e3d25f..f94ad7b77 100644 --- a/sample.env +++ b/sample.env @@ -20,6 +20,9 @@ WEBAPP_ENDPOINT=https://${WEBAPP_DOMAIN} API_ENDPOINT=https://${WEBAPP_DOMAIN}/api +# NGINX Resources path +NGINX_PROXY_RESOURCES=./resources/nginx + # Remote dockerhub variables for ODE and ConflictMonitor images DOCKERHUB_HOST=usdotjpoode DOCKERHUB_RELEASE=2025-q2 @@ -82,6 +85,11 @@ PG_DB_PASS=postgres # Set to 'False' to disable SSL when connecting to the Postgres database PG_SSL_REQUIRED=False +# CA Certificate truststore path for the API service +# This is used by Python's requests library and Keycloak verification +REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt +KEYCLOAK_VERIFY=/usr/local/share/ca-certificates/server.crt + #### ---- cvmanager_api # Allowed CORS domain for accessing the CV Manager API from (set to the web application hostname) @@ -455,6 +463,4 @@ NGINX_ENCRYPTION="plain" # SSL file name in path /docker/nginx/ssl/ SERVER_CERT_FILE="ota_server.crt" -SERVER_KEY_FILE="ota_server.key" - -NGINX_PROXY_RESOURCES=./resources/nginx \ No newline at end of file +SERVER_KEY_FILE="ota_server.key" \ No newline at end of file diff --git a/services/Dockerfile.api b/services/Dockerfile.api index 9237a9aaf..bcbdb2c49 100644 --- a/services/Dockerfile.api +++ b/services/Dockerfile.api @@ -1,8 +1,11 @@ FROM python:3.12.2-slim -# Prepare the SNMP functionality for the image -RUN apt-get update && apt-get install -y snmpd && apt-get install -y snmp -COPY resources/mibs/* /usr/share/snmp/mibs/ +# Prepare the SNMP and CA certificate functionality for the image +RUN apt-get update && \ + apt-get install -y snmpd snmp ca-certificates +COPY resources/mibs/ /usr/share/snmp/mibs/ +COPY resources/ca/ /usr/local/share/ca-certificates/ +RUN update-ca-certificates # Allow statements and log messages to immediately appear in the Knative logs ENV PYTHONUNBUFFERED=True @@ -23,4 +26,4 @@ RUN pip install --no-cache-dir -r requirements.txt # For environments with multiple CPU cores, increase the number of workers # to be equal to the cores available. # Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run to handle instance scaling. -CMD exec gunicorn --bind :5000 --workers 1 --threads 8 --timeout 0 main:app \ No newline at end of file +CMD update-ca-certificates && exec gunicorn --bind :5000 --workers 1 --threads 8 --timeout 0 main:app \ No newline at end of file From b41480c53b0fe4cc07a5041339bbd6c3afa746c6 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 6 Jan 2026 15:28:18 -0700 Subject: [PATCH 41/49] chore: update dhparam generation script to use correct NGINX path --- resources/nginx/gen_dhparam.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/nginx/gen_dhparam.sh b/resources/nginx/gen_dhparam.sh index 924f6142b..f3e402291 100644 --- a/resources/nginx/gen_dhparam.sh +++ b/resources/nginx/gen_dhparam.sh @@ -1,4 +1,4 @@ #!/bin/sh -if [ ! -f /etc/ssl/certs/dhparam.pem ]; then - openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 +if [ ! -f /etc/nginx/dhparam.pem ]; then + openssl dhparam -out /etc/nginx/dhparam.pem 2048 fi \ No newline at end of file From 3e420854371d24b8f944d2fe6af5f83196b87d0a Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 6 Jan 2026 15:51:53 -0700 Subject: [PATCH 42/49] chore: update Keycloak endpoint in docker-compose and sample.env for new path structure --- docker-compose.yml | 4 +--- sample.env | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index c8e7bd1c4..f2d222bec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -219,13 +219,11 @@ services: command: - start-dev - --import-realm - - --http-relative-path - - /auth logging: options: max-size: '10m' healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:8080/auth/realms/master'] + test: ['CMD', 'curl', '-f', 'http://localhost:8080/realms/master'] interval: 30s timeout: 10s retries: 3 diff --git a/sample.env b/sample.env index f94ad7b77..812635184 100644 --- a/sample.env +++ b/sample.env @@ -13,7 +13,7 @@ DOCKER_HOST_IP= KEYCLOAK_DOMAIN=cvmanager.local.com KC_HOST_IP=${DOCKER_HOST_IP} -KEYCLOAK_ENDPOINT=https://${WEBAPP_DOMAIN}/auth/ +KEYCLOAK_ENDPOINT=https://${WEBAPP_DOMAIN} WEBAPP_DOMAIN=cvmanager.local.com WEBAPP_HOST_IP=${DOCKER_HOST_IP} WEBAPP_ENDPOINT=https://${WEBAPP_DOMAIN} From 14ee52d727036b0a4396a16e7e674f7492e2d392 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 6 Jan 2026 15:56:04 -0700 Subject: [PATCH 43/49] chore: update endpoint references in documentation and sample.env for simplified path structure --- resources/nginx/README-deployment.md | 4 ++-- resources/nginx/README-testing.md | 10 +++++----- sample.env | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/nginx/README-deployment.md b/resources/nginx/README-deployment.md index 50b5b497f..95f66bc2f 100644 --- a/resources/nginx/README-deployment.md +++ b/resources/nginx/README-deployment.md @@ -78,9 +78,9 @@ PG_SSL_REQUIRED=True ##### Service Endpoints Ensure the endpoints use the configured domain: ```env -KEYCLOAK_ENDPOINT=https://${WEBAPP_DOMAIN}/auth/ +KEYCLOAK_ENDPOINT=https://${WEBAPP_DOMAIN} WEBAPP_ENDPOINT=https://${WEBAPP_DOMAIN} -API_ENDPOINT=https://${WEBAPP_DOMAIN}/api +API_ENDPOINT=https://${WEBAPP_DOMAIN} ``` #### 3. Update Docker Profiles diff --git a/resources/nginx/README-testing.md b/resources/nginx/README-testing.md index 4164c07f2..601d004bc 100644 --- a/resources/nginx/README-testing.md +++ b/resources/nginx/README-testing.md @@ -89,17 +89,17 @@ Once the services are up and running, you can verify the setup: - **HTTP Redirection**: Navigate to `http://cvmanager.local.com`. It should automatically redirect you to `https://cvmanager.local.com`. - **HTTPS Access**: Navigate to `https://cvmanager.local.com`. Since you are using a self-signed certificate, your browser will show a security warning. You can proceed past this warning to access the webapp. -- **API Routes**: Verify that `https://cvmanager.local.com/api/` routes to the CV Manager API. -- **Auth Routes**: Verify that `https://cvmanager.local.com/auth/` routes to the Keycloak instance. +- **API Routes**: Verify that API endpoints like `https://cvmanager.local.com/user-auth` route to the CV Manager API. +- **Auth Routes**: Verify that Keycloak endpoints like `https://cvmanager.local.com/realms/cvmanager/.well-known/openid-configuration` route to the Keycloak instance. #### Important Note on Service Endpoints -When using the NGINX proxy, all services are accessed through the same domain and port (443). Ensure your `.env` file reflects this. Specifically, the `KEYCLOAK_ENDPOINT` **must include a trailing slash** to ensure correct routing by the proxy and proper URL construction by the API: +When using the NGINX proxy, all services are accessed through the same domain and port (443). Ensure your `.env` file reflects this. Specifically, the `KEYCLOAK_ENDPOINT` should point to the base domain: ```env -KEYCLOAK_ENDPOINT=https://cvmanager.local.com/auth/ +KEYCLOAK_ENDPOINT=https://cvmanager.local.com WEBAPP_ENDPOINT=https://cvmanager.local.com -API_ENDPOINT=https://cvmanager.local.com/api +API_ENDPOINT=https://cvmanager.local.com ``` #### Troubleshooting diff --git a/sample.env b/sample.env index 812635184..e720a5ad3 100644 --- a/sample.env +++ b/sample.env @@ -18,7 +18,7 @@ WEBAPP_DOMAIN=cvmanager.local.com WEBAPP_HOST_IP=${DOCKER_HOST_IP} WEBAPP_ENDPOINT=https://${WEBAPP_DOMAIN} -API_ENDPOINT=https://${WEBAPP_DOMAIN}/api +API_ENDPOINT=https://${WEBAPP_DOMAIN} # NGINX Resources path NGINX_PROXY_RESOURCES=./resources/nginx From dad4b3d9bb3016b3590b51994f45cd222a3f9176 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Tue, 6 Jan 2026 15:58:36 -0700 Subject: [PATCH 44/49] chore: update KEYCLOAK_ENDPOINT to use KEYCLOAK_DOMAIN in sample.env --- sample.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample.env b/sample.env index e720a5ad3..fd5e803c4 100644 --- a/sample.env +++ b/sample.env @@ -13,7 +13,7 @@ DOCKER_HOST_IP= KEYCLOAK_DOMAIN=cvmanager.local.com KC_HOST_IP=${DOCKER_HOST_IP} -KEYCLOAK_ENDPOINT=https://${WEBAPP_DOMAIN} +KEYCLOAK_ENDPOINT=https://${KEYCLOAK_DOMAIN} WEBAPP_DOMAIN=cvmanager.local.com WEBAPP_HOST_IP=${DOCKER_HOST_IP} WEBAPP_ENDPOINT=https://${WEBAPP_DOMAIN} From d24e70db8cf2be029f967df2fd247b17c30bd84f Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 7 Jan 2026 15:10:35 -0700 Subject: [PATCH 45/49] chore: update docker-compose to remove dynamic NGINX_PROXY_RESOURCES and improve CA certificate handling --- docker-compose-addons.yml | 5 +++++ docker-compose.yml | 17 +++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docker-compose-addons.yml b/docker-compose-addons.yml index c83c40787..4f7c560de 100644 --- a/docker-compose-addons.yml +++ b/docker-compose-addons.yml @@ -37,6 +37,8 @@ services: KAFKA_BIGQUERY_TABLENAME: ${KAFKA_BIGQUERY_TABLENAME} LOGGING_LEVEL: ${COUNTS_LOGGING_LEVEL} + volumes: + - ./services/resources/ca:/etc/ssl/ca logging: options: max-size: '10m' @@ -72,6 +74,8 @@ services: PG_SSL_REQUIRED: ${PG_SSL_REQUIRED:-True} LOGGING_LEVEL: ${RSU_STATUS_LOGGING_LEVEL} + volumes: + - ./services/resources/ca:/etc/ssl/ca logging: options: max-size: '10m' @@ -109,6 +113,7 @@ services: LOGGING_LEVEL: ${ISS_LOGGING_LEVEL} volumes: - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json + - ./services/resources/ca:/etc/ssl/ca logging: options: max-size: '10m' diff --git a/docker-compose.yml b/docker-compose.yml index f2d222bec..7b1bdd775 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: profiles: - nginx_proxy build: - context: ${NGINX_PROXY_RESOURCES:-./resources/nginx} + context: ./resources/nginx dockerfile: Dockerfile image: cvmanager_nginx_proxy:latest restart: unless-stopped @@ -20,9 +20,8 @@ services: NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx SERVER_HOST: ${WEBAPP_DOMAIN} volumes: - - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/nginx-ssl.conf:/etc/nginx/templates/nginx.conf.template - - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/ssl/server.crt:/etc/ssl/certs/server.crt - - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/ssl/server.key:/etc/ssl/private/server.key + - ./resources/nginx/nginx-ssl.conf:/etc/nginx/templates/nginx.conf.template + - ./resources/nginx/ssl:/etc/ssl depends_on: - cvmanager_webapp - cvmanager_keycloak @@ -48,7 +47,7 @@ services: PG_DB_HOST: ${PG_DB_HOST} PG_DB_USER: ${PG_DB_USER} PG_DB_PASS: ${PG_DB_PASS} - PG_DB_NAME: postgres + PG_DB_NAME: ${PG_DB_NAME} INSTANCE_CONNECTION_NAME: ${INSTANCE_CONNECTION_NAME} MONGO_DB_URI: ${MONGO_DB_URI} @@ -98,14 +97,14 @@ services: TIMEZONE: ${TIMEZONE} LOGGING_LEVEL: ${API_LOGGING_LEVEL} - KEYCLOAK_VERIFY: ${KEYCLOAK_VERIFY:-/usr/local/share/ca-certificates/server.crt} + KEYCLOAK_VERIFY: ${KEYCLOAK_VERIFY:-/etc/ssl/ca/ca.crt} PG_SSL_REQUIRED: ${PG_SSL_REQUIRED:-True} - REQUESTS_CA_BUNDLE: ${REQUESTS_CA_BUNDLE:-/etc/ssl/certs/ca-certificates.crt} + REQUESTS_CA_BUNDLE: /etc/ssl/ca/ca.crt depends_on: - cvmanager_postgres volumes: - ./resources/google/${GOOGLE_ACCESS_KEY_NAME}:/home/gcp_key.json - - ${NGINX_PROXY_RESOURCES:-./resources/nginx}/ssl/server.crt:/usr/local/share/ca-certificates/server.crt + - ./services/resources/ca:/etc/ssl/ca logging: options: max-size: '10m' @@ -190,10 +189,8 @@ services: cvmanager_postgres: required: false condition: service_started - extra_hosts: ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} - ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} environment: KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} From 0e356bc5b870bd1e46e4efa91a7050b6f80e44ff Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 7 Jan 2026 15:10:45 -0700 Subject: [PATCH 46/49] chore: refine CA certificate handling in Dockerfile.api to copy specific files --- services/Dockerfile.api | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/Dockerfile.api b/services/Dockerfile.api index bcbdb2c49..2a9647be1 100644 --- a/services/Dockerfile.api +++ b/services/Dockerfile.api @@ -4,7 +4,8 @@ FROM python:3.12.2-slim RUN apt-get update && \ apt-get install -y snmpd snmp ca-certificates COPY resources/mibs/ /usr/share/snmp/mibs/ -COPY resources/ca/ /usr/local/share/ca-certificates/ + +COPY resources/ca/ca.crt /usr/local/share/ca-certificates/ca.crt RUN update-ca-certificates # Allow statements and log messages to immediately appear in the Knative logs From c08a0afa4ffaf64032dc887f060f4e6cf34d44a8 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 7 Jan 2026 15:14:37 -0700 Subject: [PATCH 47/49] chore: refine CA certificate path references in NGINX deployment documentation --- resources/nginx/README-deployment.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/nginx/README-deployment.md b/resources/nginx/README-deployment.md index 95f66bc2f..bba9f62f4 100644 --- a/resources/nginx/README-deployment.md +++ b/resources/nginx/README-deployment.md @@ -30,8 +30,8 @@ If your SSL certificate is issued by a private or corporate CA that is not in th ```bash mkdir -p services/resources/ca ``` -2. Place your CA certificate (in `.crt` format) in this directory: - - `services/resources/ca/your-corporate-ca.crt` +2. Place your CA certificate (in `.crt` format) in this directory as `ca.crt`: + - `services/resources/ca/ca.crt` 3. Rebuild the API service: ```bash docker compose build cvmanager_api @@ -41,7 +41,7 @@ Alternatively, you can mount the CA certificate at runtime by updating the `volu ```yaml volumes: - - ./path/to/your-ca.crt:/usr/local/share/ca-certificates/your-ca.crt + - ./path/to/your-ca.crt:/etc/ssl/ca/ca.crt ``` #### 3. Environment Configuration From 21ea2cfaff55a381c6c5285e3e47412707baccc0 Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 7 Jan 2026 15:17:23 -0700 Subject: [PATCH 48/49] chore: update CA certificate paths in sample.env for API service --- sample.env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sample.env b/sample.env index fd5e803c4..3d314e173 100644 --- a/sample.env +++ b/sample.env @@ -87,8 +87,8 @@ PG_SSL_REQUIRED=False # CA Certificate truststore path for the API service # This is used by Python's requests library and Keycloak verification -REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt -KEYCLOAK_VERIFY=/usr/local/share/ca-certificates/server.crt +REQUESTS_CA_BUNDLE=/etc/ssl/ca/ca.crt +KEYCLOAK_VERIFY=/etc/ssl/ca/ca.crt #### ---- cvmanager_api From d8e7901e3fd53672b6681059cd109a3bf877826a Mon Sep 17 00:00:00 2001 From: dmccoystephenson Date: Wed, 7 Jan 2026 15:17:34 -0700 Subject: [PATCH 49/49] chore: update README-testing.md to improve SSL certificate generation and handling steps --- resources/nginx/README-testing.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/resources/nginx/README-testing.md b/resources/nginx/README-testing.md index 601d004bc..fef06b36d 100644 --- a/resources/nginx/README-testing.md +++ b/resources/nginx/README-testing.md @@ -19,11 +19,20 @@ cd resources/nginx # Create the ssl directory if it doesn't exist mkdir -p ssl -# Generate a self-signed certificate and private key with SAN +# Generate a self-signed certificate and private key with SAN for the web domain openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout ssl/server.key -out ssl/server.crt \ -subj "/C=US/ST=State/L=City/O=Organization/OU=Unit/CN=cvmanager.local.com" \ -addext "subjectAltName = DNS:cvmanager.local.com" + +# Move certificate and key files to correct locations +mkdir -p ssl/certs ssl/private +mv ssl/server.crt ssl/certs/server.crt +mv ssl/server.key ssl/private/server.key + +# Copy the server certificate to the API resources CA directory to be trusted by the API +mkdir -p ../../services/resources/ca +cp ssl/server.crt ../../services/resources/ca/ca.crt ``` *Note: Ensure the `CN` (Common Name) and `subjectAltName` match the `WEBAPP_DOMAIN` defined in your `.env` file. Modern browsers and libraries (like Python's `requests`) require the `subjectAltName` extension for proper hostname verification.* @@ -58,8 +67,6 @@ NGINX_PROXY_RESOURCES=./resources/nginx PG_SSL_REQUIRED=False ``` -*Note: `PG_SSL_REQUIRED=False` is required when connecting to the local Postgres container, as it does not have SSL enabled.* - #### 4. Run the Setup To test the NGINX proxy, you only need to start the NGINX service along with its dependencies (WebApp, API, Keycloak, and Postgres). Use the following command to start the necessary services: