diff --git a/.github/workflows/job-lint.yml b/.github/workflows/job-lint.yml index f7d4d5cf22..bbbae077d6 100644 --- a/.github/workflows/job-lint.yml +++ b/.github/workflows/job-lint.yml @@ -61,32 +61,18 @@ jobs: sudo apt-get update -qq sudo apt-get install -y --no-install-recommends nginx gettext-base # Préparer un répertoire de test isolé avec la config substituée - mkdir -p /tmp/nginx-test/server_blocks /tmp/nginx-test/logs + mkdir -p /tmp/nginx-test/conf.d /tmp/nginx-test/logs envsubst '${LEGACY_UPSTREAM} ${NESTJS_UPSTREAM}' \ < apps/nginx-strangler/conf.d/routing.conf \ - > /tmp/nginx-test/server_blocks/routing.conf - # Créer un nginx.conf minimal pour la validation - cat > /tmp/nginx-test/nginx.conf <<'EOF' - user www-data; - worker_processes auto; - pid /tmp/nginx-test/nginx.pid; - error_log /tmp/nginx-test/logs/error.log notice; - events { - worker_connections 1024; - } - http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for" ' - 'upstream=$upstream_addr rt=$request_time'; - access_log /tmp/nginx-test/logs/access.log main; - sendfile on; - keepalive_timeout 65; - include /tmp/nginx-test/server_blocks/*.conf; - } - EOF + > /tmp/nginx-test/conf.d/routing.conf + # Adapter nginx.conf pour l'environnement CI (user www-data, paths accessibles) + sed \ + -e 's|^user .*|user www-data;|' \ + -e 's|pid .*|pid /tmp/nginx-test/nginx.pid;|' \ + -e 's|error_log .*|error_log /tmp/nginx-test/logs/error.log notice;|' \ + -e 's|access_log .*|access_log /tmp/nginx-test/logs/access.log main;|' \ + -e 's|include /etc/nginx/conf\.d/\*\.conf|include /tmp/nginx-test/conf.d/*.conf|' \ + apps/nginx-strangler/nginx.conf > /tmp/nginx-test/nginx.conf nginx -t -c /tmp/nginx-test/nginx.conf env: LEGACY_UPSTREAM: "127.0.0.1:8080" diff --git a/apps/client/Dockerfile b/apps/client/Dockerfile index 3cacb20855..1b48a7578d 100644 --- a/apps/client/Dockerfile +++ b/apps/client/Dockerfile @@ -37,24 +37,12 @@ RUN pnpm --filter @cpn-console/client run build # Prod stage ----------------------------------------------------------------------- -FROM nginx:1.27-alpine AS prod - -COPY --from=build /app/apps/client/dist /etc/nginx/html/ -COPY ./apps/client/nginx/default.conf /etc/nginx/conf.d/default.conf -COPY ./apps/client/nginx/entrypoint.sh /entrypoint.sh - -RUN chown -R nginx:nginx \ - /entrypoint.sh \ - /etc/nginx/nginx.conf \ - /etc/nginx/conf.d \ - /etc/nginx/mime.types \ - /etc/nginx/html \ - /var/cache/nginx \ - /var/log/nginx \ - && touch /var/run/nginx.pid \ - && chown nginx:nginx /var/run/nginx.pid - -USER nginx -ENTRYPOINT [ "/entrypoint.sh" ] +FROM nginxinc/nginx-unprivileged:1.31-alpine AS prod + +COPY --chown=nginx:root --from=build /app/apps/client/dist /etc/nginx/html/ +COPY --chown=nginx:root --chmod=660 ./apps/client/nginx/default.conf /etc/nginx/conf.d/default.conf +COPY --chown=nginx:root --chmod=750 ./apps/client/nginx/entrypoint.sh /docker-entrypoint.d/99-envsubst-js-assets.sh + +RUN chmod -R g=u /etc/nginx/html EXPOSE 8080 diff --git a/apps/client/nginx/entrypoint.sh b/apps/client/nginx/entrypoint.sh index fad0aa9ed0..98dfe3bcd9 100755 --- a/apps/client/nginx/entrypoint.sh +++ b/apps/client/nginx/entrypoint.sh @@ -1,26 +1,34 @@ #!/bin/sh -ROOT_DIR=/etc/nginx/html +ASSETS_DIR=/etc/nginx/html/assets -populate () { - KEY=$(echo "dso-$1" | tr '[:upper:]' '[:lower:]' | sed 's/_/-/g') - VALUE=$(eval "echo \${$1}") - sed -i 's|'${KEY}'|'${VALUE}'|g' $2 +make_envsubst_vars() { + fmt= + for name do + fmt="${fmt}${fmt:+ }\${$name}" + done + + printf '%s\n' "$fmt" } +ENV_VARS=$(make_envsubst_vars \ + SERVER_HOST \ + SERVER_PORT \ + OPENCDS_ENABLED \ + KEYCLOAK_PROTOCOL \ + KEYCLOAK_DOMAIN \ + KEYCLOAK_REALM \ + KEYCLOAK_CLIENT_ID \ + KEYCLOAK_REDIRECT_URI \ + CONTACT_EMAIL \ +) -echo "Replacing env constants in JS" -for file in $ROOT_DIR/assets/*.js; do +echo "Replacing env variables in JavaScript asset files..." +for file in $ASSETS_DIR/*.js; do echo "Processing $file ..."; - - populate SERVER_HOST $file - populate SERVER_PORT $file - populate OPENCDS_ENABLED $file - populate KEYCLOAK_PROTOCOL $file - populate KEYCLOAK_DOMAIN $file - populate KEYCLOAK_REALM $file - populate KEYCLOAK_CLIENT_ID $file - populate KEYCLOAK_REDIRECT_URI $file + envsubst "${ENV_VARS}" \ + < $file \ + > $file-out && \ + mv $file-out $file done - -nginx -g 'daemon off;' +echo "Done !" diff --git a/apps/client/src/utils/env.ts b/apps/client/src/utils/env.ts index a6f6ffd166..189eb7143e 100644 --- a/apps/client/src/utils/env.ts +++ b/apps/client/src/utils/env.ts @@ -1,19 +1,20 @@ -export const serverHost: string = process.env.SERVER_HOST ?? 'dso-server-host' +/* eslint-disable no-template-curly-in-string */ +export const serverHost: string = process.env.SERVER_HOST ?? '${SERVER_HOST}' -export const serverPort: string = process.env.SERVER_PORT ?? 'dso-server-port' +export const serverPort: string = process.env.SERVER_PORT ?? '${SERVER_PORT}' -export const clientPort: string = process.env.CLIENT_PORT ?? 'dso-client-port' +export const clientPort: string = process.env.CLIENT_PORT ?? '${CLIENT_PORT}' -export const keycloakProtocol: string = process.env.KEYCLOAK_PROTOCOL ?? 'dso-keycloak-protocol' +export const keycloakProtocol: string = process.env.KEYCLOAK_PROTOCOL ?? '${KEYCLOAK_PROTOCOL}' -export const keycloakDomain: string = process.env.KEYCLOAK_DOMAIN ?? 'dso-keycloak-domain' +export const keycloakDomain: string = process.env.KEYCLOAK_DOMAIN ?? '${KEYCLOAK_DOMAIN}' -export const keycloakRealm: string = process.env.KEYCLOAK_REALM ?? 'dso-keycloak-realm' +export const keycloakRealm: string = process.env.KEYCLOAK_REALM ?? '${KEYCLOAK_REALM}' -export const keycloakClientId: string = process.env.KEYCLOAK_CLIENT_ID ?? 'dso-keycloak-client-id' +export const keycloakClientId: string = process.env.KEYCLOAK_CLIENT_ID ?? '${KEYCLOAK_CLIENT_ID}' -export const keycloakRedirectUri: string = process.env.KEYCLOAK_REDIRECT_URI ?? 'dso-keycloak-redirect-uri' +export const keycloakRedirectUri: string = process.env.KEYCLOAK_REDIRECT_URI ?? '${KEYCLOAK_REDIRECT_URI}' -export const contactEmail: string = process.env.CONTACT_EMAIL ?? 'cloudpinative-relations@interieur.gouv.fr' +export const contactEmail: string = process.env.CONTACT_EMAIL ?? '${CONTACT_EMAIL}' -export const openCDSEnabled: string = process.env.OPENCDS_ENABLED ?? 'dso-opencds-enabled' +export const openCDSEnabled: string = process.env.OPENCDS_ENABLED ?? '${OPENCDS_ENABLED}' diff --git a/apps/nginx-strangler/Dockerfile b/apps/nginx-strangler/Dockerfile index 66a1c1fd51..ec2c6753fd 100644 --- a/apps/nginx-strangler/Dockerfile +++ b/apps/nginx-strangler/Dockerfile @@ -1,13 +1,32 @@ -FROM docker.io/bitnamilegacy/nginx:1.29.1 AS prod +FROM nginxinc/nginx-unprivileged:1.31-alpine AS prod -USER 0 +USER root + +# On supprime la config par défaut +RUN rm /etc/nginx/conf.d/default.conf + +# Config principale +COPY apps/nginx-strangler/nginx.conf /etc/nginx/nginx.conf # Template de routing (sera substitué au démarrage) -COPY --chown=1001:0 --chmod=660 apps/nginx-strangler/conf.d/routing.conf /opt/bitnami/nginx/conf/server_blocks/routing.conf.template +COPY apps/nginx-strangler/conf.d/routing.conf /etc/nginx/templates/routing.conf.template + +# Donner à l'utilisateur nginx et au groupe root les droits d'écriture sur conf.d/ (pour subst au démarrage) +# et sur les répertoires de logs/pid nécessaires en mode non-root +# OpenShift exécute les conteneurs avec un UID arbitraire appartenant au groupe root. +RUN chown -R nginx:root \ + /etc/nginx/nginx.conf \ + /etc/nginx/conf.d \ + /etc/nginx/templates \ + /etc/nginx/mime.types \ + /var/cache/nginx \ + /var/log/nginx \ + && touch /var/run/nginx.pid \ + && chown nginx:root /var/run/nginx.pid && chmod g+w /var/run/nginx.pid -# Script d'entrypoint pour substitution des variables -COPY --chown=1001:0 --chmod=770 apps/nginx-strangler/entrypoint.sh /docker-entrypoint-initdb.d/load-routing.sh +USER nginx -USER 1001 +HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ + CMD wget -qO- http://127.0.0.1:8080/health || exit 1 EXPOSE 8080 diff --git a/apps/nginx-strangler/entrypoint.sh b/apps/nginx-strangler/entrypoint.sh deleted file mode 100644 index 19a234e2e6..0000000000 --- a/apps/nginx-strangler/entrypoint.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# Substitue les variables d'environnement dans le template de routing -# Les variables substituées : LEGACY_UPSTREAM, NESTJS_UPSTREAM -envsubst '${LEGACY_UPSTREAM} ${NESTJS_UPSTREAM}' \ - < /opt/bitnami/nginx/conf/server_blocks/routing.conf.template \ - > /opt/bitnami/nginx/conf/server_blocks/routing.conf - -echo "Routing configuration generated with:" -echo " LEGACY_UPSTREAM=${LEGACY_UPSTREAM}" -echo " NESTJS_UPSTREAM=${NESTJS_UPSTREAM}" diff --git a/apps/nginx-strangler/nginx.conf b/apps/nginx-strangler/nginx.conf new file mode 100644 index 0000000000..657e2100dd --- /dev/null +++ b/apps/nginx-strangler/nginx.conf @@ -0,0 +1,28 @@ +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + 'upstream=$upstream_addr rt=$request_time'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + keepalive_timeout 65; + + # Taille des headers (nécessaire pour les tokens Keycloak) + large_client_header_buffers 4 32k; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/package.json b/package.json index 22baf0d8ba..53a102c99e 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,10 @@ "docker:integ:build": "export $(cat apps/server/.env.integ | grep -v '#' | xargs) && export COMPOSE_FILE=./docker/docker-compose.integ.yml && cd $(dirname $COMPOSE_FILE) && docker buildx bake --allow=fs.read=.. --file $(basename $COMPOSE_FILE) --load && cd - > /dev/null", "docker:integ:clean": "docker compose --env-file apps/server/.env.integ -f ./docker/docker-compose.integ.yml down --remove-orphans", "docker:integ:delete": "docker compose --env-file apps/server/.env.integ -f ./docker/docker-compose.integ.yml down -v --remove-orphans", + "docker:ci": "docker compose --env-file apps/server/.env.integ -f ./docker/docker-compose.ci.yml up --menu", + "docker:ci:build": "export $(cat apps/server/.env.integ | grep -v '#' | xargs) && export COMPOSE_FILE=./docker/docker-compose.ci.yml && cd $(dirname $COMPOSE_FILE) && docker buildx bake --allow=fs.read=.. --file $(basename $COMPOSE_FILE) --load && cd - > /dev/null", + "docker:ci:clean": "docker compose --env-file apps/server/.env.integ -f ./docker/docker-compose.ci.yml down --remove-orphans", + "docker:ci:delete": "docker rmi -f $(docker images --filter=reference='dso-console/*:ci' -q) ; true", "format": "pnpm -r run format", "format:root": "eslint . --fix", "format:style": "pnpm -r run format:style",