From 12fc5c6fd83f9929e5242eda5033ffcc27bd7534 Mon Sep 17 00:00:00 2001 From: Mario Calderon - Systemhaus Westfalia Date: Fri, 19 Jul 2024 14:38:29 -0600 Subject: [PATCH 1/3] Implement Letsencrypt Certificates for HTTPS connections --- docker-compose/.env | 6 +- .../17d-ui_gateway_service_standard.yml | 13 +++- docker-compose/docker-compose-standard.yml | 12 +++- docker-compose/env_template.env | 6 +- docker-compose/nginx/gateway/api_gateway.conf | 66 ++++++++++++++++--- 5 files changed, 88 insertions(+), 15 deletions(-) diff --git a/docker-compose/.env b/docker-compose/.env index d5697a58..5ce70fb9 100644 --- a/docker-compose/.env +++ b/docker-compose/.env @@ -250,8 +250,10 @@ ENVOY_GRPC_PROXY_REPORT_PORT=5557 NGINX_UI_GATEWAY_IMAGE="nginx:1.26.0-alpine3.19" NGINX_UI_GATEWAY_CONTAINER_NAME="${COMPOSE_PROJECT_NAME}.nginx.ui.gateway" NGINX_UI_GATEWAY_HOSTNAME="${CLIENT_NAME}.nginx" -NGINX_UI_GATEWAY_EXTERNAL_PORT=80 -NGINX_UI_GATEWAY_INTERNAL_PORT=80 +NGINX_UI_GATEWAY_HTTP_EXTERNAL_PORT=80 +NGINX_UI_GATEWAY_HTTP_INTERNAL_PORT=80 +NGINX_UI_GATEWAY_HTTPS_EXTERNAL_PORT=443 +NGINX_UI_GATEWAY_HTTPS_INTERNAL_PORT=443 NGINX_UI_GATEWAY_VOLUME="${COMPOSE_PROJECT_NAME}.volume_nginx" # Not used yet in in docker-compose.yml. diff --git a/docker-compose/17d-ui_gateway_service_standard.yml b/docker-compose/17d-ui_gateway_service_standard.yml index f19963a7..cb19f099 100644 --- a/docker-compose/17d-ui_gateway_service_standard.yml +++ b/docker-compose/17d-ui_gateway_service_standard.yml @@ -14,13 +14,24 @@ adempiere.site: condition: service_started ports: - - ${NGINX_UI_GATEWAY_EXTERNAL_PORT}:${NGINX_UI_GATEWAY_INTERNAL_PORT} + - ${NGINX_UI_GATEWAY_HTTP_EXTERNAL_PORT}:${NGINX_UI_GATEWAY_HTTP_INTERNAL_PORT} + # Remember to open port 443 on host if you want to use HTTPS protocol + - ${NGINX_UI_GATEWAY_HTTPS_EXTERNAL_PORT}:${NGINX_UI_GATEWAY_HTTPS_INTERNAL_PORT} volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf - ./nginx/upstreams/:/etc/nginx/api_upstreams_conf.d - ./nginx/api/:/etc/nginx/api_conf.d - ./nginx/gateway/api_gateway.conf:/etc/nginx/api_gateway.conf - ./nginx/api_json_errors.conf:/etc/nginx/api_json_errors.conf + + # Certificate generated by Letsencypt on the Host. Only fullchain.pem and privkey.pem are needed. + # Replace YOUR_DOMAINNAME with the DNS value you have acquired the Certificate for on the Host + - /etc/letsencrypt/live/YOUR_DOMAINNAME/fullchain.pem:/etc/letsencrypt/live/YOUR_DOMAINNAME/fullchain.pem + - /etc/letsencrypt/live/YOUR_DOMAINNAME/privkey.pem:/etc/letsencrypt/live/YOUR_DOMAINNAME/privkey.pem + # Diffie-Hellman to secure Certificate transport. It must also be generated on the Host. + # Command to generate: openssl dhparam -out /etc/nginx/dhparam.pem 4096 + - /etc/nginx/dhparam.pem:/etc/nginx/dhparam.pem + #- ./keys/api_keys.conf:/etc/nginx/api_keys.conf # time zone - ${TIMEZONE_PATH_ON_HOST}:${TIMEZONE_PATH_ON_CONTAINER}:${TIMEZONE_OPTIONS} # Map the Timezone of the host to the Timezone of the container diff --git a/docker-compose/docker-compose-standard.yml b/docker-compose/docker-compose-standard.yml index cf20e21b..7bcac27e 100644 --- a/docker-compose/docker-compose-standard.yml +++ b/docker-compose/docker-compose-standard.yml @@ -469,12 +469,22 @@ services: - ./nginx/api/:/etc/nginx/api_conf.d - ./nginx/gateway/api_gateway.conf:/etc/nginx/api_gateway.conf - ./nginx/api_json_errors.conf:/etc/nginx/api_json_errors.conf + + # Certificate generated by Letsencypt on the Host. Only fullchain.pem and privkey.pem are needed. + # Replace YOUR_DOMAINNAME with the DNS value you have acquired the Certificate for on the Host + - /etc/letsencrypt/live/YOUR_DOMAINNAME/fullchain.pem:/etc/letsencrypt/live/YOUR_DOMAINNAME/fullchain.pem + - /etc/letsencrypt/live/YOUR_DOMAINNAME/privkey.pem:/etc/letsencrypt/live/YOUR_DOMAINNAME/privkey.pem + # Diffie-Hellman to secure Certificate transport. It must also be generated on the Host. + # Command to generate: openssl dhparam -out /etc/nginx/dhparam.pem 4096 + - /etc/nginx/dhparam.pem:/etc/nginx/dhparam.pem + #- ./keys/api_keys.conf:/etc/nginx/api_keys.conf # time zone - ${TIMEZONE_PATH_ON_HOST}:${TIMEZONE_PATH_ON_CONTAINER}:${TIMEZONE_OPTIONS} # Map the Timezone of the host to the Timezone of the container - ${LOCALTIME_PATH_ON_HOST}:${LOCALTIME_PATH_ON_CONTAINER}:${LOCALTIME_OPTIONS} # Map the Localtime of the host to the Timezone of the container ports: - - ${NGINX_UI_GATEWAY_EXTERNAL_PORT}:${NGINX_UI_GATEWAY_INTERNAL_PORT} + - ${NGINX_UI_GATEWAY_HTTP_EXTERNAL_PORT}:${NGINX_UI_GATEWAY_HTTP_INTERNAL_PORT} + - ${NGINX_UI_GATEWAY_HTTPS_EXTERNAL_PORT}:${NGINX_UI_GATEWAY_HTTPS_INTERNAL_PORT} networks: - adempiere_network diff --git a/docker-compose/env_template.env b/docker-compose/env_template.env index d5697a58..5ce70fb9 100644 --- a/docker-compose/env_template.env +++ b/docker-compose/env_template.env @@ -250,8 +250,10 @@ ENVOY_GRPC_PROXY_REPORT_PORT=5557 NGINX_UI_GATEWAY_IMAGE="nginx:1.26.0-alpine3.19" NGINX_UI_GATEWAY_CONTAINER_NAME="${COMPOSE_PROJECT_NAME}.nginx.ui.gateway" NGINX_UI_GATEWAY_HOSTNAME="${CLIENT_NAME}.nginx" -NGINX_UI_GATEWAY_EXTERNAL_PORT=80 -NGINX_UI_GATEWAY_INTERNAL_PORT=80 +NGINX_UI_GATEWAY_HTTP_EXTERNAL_PORT=80 +NGINX_UI_GATEWAY_HTTP_INTERNAL_PORT=80 +NGINX_UI_GATEWAY_HTTPS_EXTERNAL_PORT=443 +NGINX_UI_GATEWAY_HTTPS_INTERNAL_PORT=443 NGINX_UI_GATEWAY_VOLUME="${COMPOSE_PROJECT_NAME}.volume_nginx" # Not used yet in in docker-compose.yml. diff --git a/docker-compose/nginx/gateway/api_gateway.conf b/docker-compose/nginx/gateway/api_gateway.conf index ab6deeba..ca2dc022 100644 --- a/docker-compose/nginx/gateway/api_gateway.conf +++ b/docker-compose/nginx/gateway/api_gateway.conf @@ -11,15 +11,63 @@ server { # listen 127.0.0.1:80; listen localhost:80; - #listen 443 ssl; - # server_name api.adempiere.io; - # TLS config - #ssl_certificate /etc/ssl/certs/api.example.com.crt; - #ssl_certificate_key /etc/ssl/private/api.example.com.key; - #ssl_session_cache shared:SSL:10m; - #ssl_session_timeout 5m; - #ssl_ciphers HIGH:!aNULL:!MD5; - #ssl_protocols TLSv1.2 TLSv1.3; + # API definitions, one per file + include api_conf.d/*.conf; + include api_conf.d/**/*.conf; + + # Error responses + error_page 404 = @400; # Treat invalid paths as bad requests + proxy_intercept_errors off; # Do not send backend errors to client + include api_json_errors.conf; # API client-friendly JSON errors + default_type application/json; # If no content-type, assume JSON +} + +server { + # Context for HTTPS (port 443) + # You must first generate a certificate in the Host, for example with Letsencrypt. + # Then, you must replace the text "YOUR_DOMAINNAME" with the name the certificate was issued for and uncomment the corresponding lines below. + # The directives server_name, ssl_certificate and ssl_certificate_key must be uncommented and have the correct values for the HTTPS connection to work correctly. + # Otherwise, there will be a connection error. + # Do no forget to also open port 443 or HTTP protocol on the Host. + + access_log /var/log/nginx/api_access.log main; # Each API may also log to a separate file + + # Begin of Certificate suggestions by https://cipherlist.eu/ + listen 443 ssl; + listen [::]:443 ssl; + listen localhost:443; + + # Uncomment the following line and replace "YOUR_DOMAINNAME" with the DNS value of the HOST + # server_name YOUR_DOMAINNAME; # Here: the (sub)domain this server is referred as + + # Uncomment the following line and replace "YOUR_DOMAINNAME" with the DNS value of the HOST + # ssl_certificate /etc/letsencrypt/live/YOUR_DOMAINNAME/fullchain.pem; # Generated for (sub)domain on Host and mounted here + + # Uncomment the following line and replace "YOUR_DOMAINNAME" with the DNS value of the HOST + # ssl_certificate_key /etc/letsencrypt/live/YOUR_DOMAINNAME/privkey.pem; # Generated for (sub)domain on Host and mounted here + + ssl_protocols TLSV1.2 TLSv1.3;# Requires nginx >= 1.13.0 else use TLSv1.2 + ssl_prefer_server_ciphers on; + + # Uncomment the following line once you have generated a Diffie-Hellman key + # ssl_dhparam /etc/nginx/dhparam.pem; # Diffie-Hellman generated on Host and mounted here: openssl dhparam -out /etc/nginx/dhparam.pem 4096 + + ssl_ciphers EECDH+CHACHA20:EECDH+AESGCM:EDH+AESGCM; + ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256; + ssl_conf_command Options PrioritizeChaCha; + ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0 + ssl_session_timeout 10m; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; # Requires nginx >= 1.5.9 + ssl_stapling on; # Requires nginx >= 1.3.7 + ssl_stapling_verify on; # Requires nginx => 1.3.7 + resolver 8.8.8.8 8.8.4.4 valid=300s; # Google. Choose as you please. + resolver_timeout 5s; + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + # End of Certificate suggestions by https://cipherlist.eu/ # API definitions, one per file include api_conf.d/*.conf; From 04be9d79d68cd3c76212e6e0ee45881a9fc9c1fb Mon Sep 17 00:00:00 2001 From: Mario Calderon - Systemhaus Westfalia Date: Sat, 20 Jul 2024 11:20:09 -0600 Subject: [PATCH 2/3] Update README.md --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5c493ef9..6e81be0d 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,7 @@ There are several docker compose files that start different services, according ### User's perspective From a user's point of view, the application consists of the following. Take note that the ports are defined in file *env_template.env* as external ports and can be changed if needed or desired. -- A home web site accesible via port **80** - From which all applications can be called +- A home web site accesible via port **80**, or port **443** (assuming Letsencrypt certificates were generated and configured for the Host it is running on). - An ADempiere ZK UI accesible via path **/webui** - An ADempiere Vue UI accesible via port **/vue** - A Postgres database accesible e.g. by PGAdmin via port **55432** @@ -55,14 +54,14 @@ Take note that the ports are defined in file *env_template.env* as external port ### Application Stack The application stack consists of the following services defined in the *docker-compose files* (and retrieved on the console with **docker compose ls**); these services will eventually run as containers: -- **adempiere-site**: defines the landing page (web site) for this application -- **postgresql.service**: defines the Postgres database -- **adempiere-zk**: defines the Jetty server and the ADempiere ZK UI -- **adempiere-grpc-server**: Defines a grpc server as the backend server for Vue +- **adempiere-site**: the landing page (web site) for this application +- **postgresql.service**: the Postgres database +- **adempiere-zk**: the Jetty server and the ADempiere ZK UI +- **adempiere-grpc-server**: a grpc server as the backend server for Vue - **adempiere.processor**: for processes that are executed outside Adempiere - **dkron.scheduler**: a scheduler - **grpc.proxy**: an envoy server -- **vue-ui**: defines ADempiere Vue UI +- **vue-ui**: ADempiere Vue UI - **opensearch.node**: stores the Application Dictionary definitions - **opensearch.setup**: configure the service *opensearch.node* - **zookeeper**: controller for *kafka* service @@ -70,18 +69,18 @@ The application stack consists of the following services defined in the *docker- - **kafdrop**: a Kafka Cluster Queues Overview, Monitor and Administrator - **dictionary.rs**: - **keycloak**: user management on service *postgresql.service* -- **ui.gateway**: +- **ui.gateway**: manages all incoming requests and distributes them to the corresponding containers. - **s3.storage**: for attachments - **s3.client**: configuration of "s3-storage" service - **s3.gateway.rs**: - **opensearch.dashboards**: display and monitor of e.g. exported menus, smart browsers, forms, windows, processes. Additional objects defined in the *docker-compose files*: -- `adempiere_network`: defines the subnet used in the involved Docker containers (e.g. **192.168.100.0/24**) -- `volume_postgres`: defines the mounting point of the Postgres database (typically directory **/var/lib/postgresql/data**) to a local directory on the host where the Docker container runs. This implement a persistent database. -- `volume_backups`: defines the mounting point for a backup (or restore) directory on the Docker container to a local directrory on the host where the Docker container has access. +- `adempiere_network`: the subnet used in the involved Docker containers (e.g. **192.168.100.0/24**) +- `volume_postgres`: the mounting point of the Postgres database (typically directory **/var/lib/postgresql/data**) to a local directory on the host where the Docker container runs. This implement a persistent database. +- `volume_backups`: the mounting point for a backup (or restore) directory on the Docker container to a local directrory on the host where the Docker container has access. - `volume_persistent_files`: mounting point for the ZK container -- `volume_scheduler`: defines the mounting point for the scheduler (`TO BE IMPLEMENTED YET`) +- `volume_scheduler`: the mounting point for the scheduler (`TO BE IMPLEMENTED YET`) ### Architecture - The application `vue` stack as graphic: @@ -465,6 +464,39 @@ cd adempiere-ui-gateway/docker-compose Connect to database via port **55432** with a DB connector, e.g. PGAdmin. Or to the port the variable `POSTGRES_EXTERNAL_PORT` points in file `env_template.env`. +## Using Certificates +### Get Letsencrypt Certificate +Steps to use Letsencrypt certificates for a secure communication: +- Letsencrypt issues Certificates only to a server which owns a Domainename. +- On your registrar, make sure the Domainname you want to use points to the to the IP of the Host server. +- Install the program `certbot` on your Host to get certificates. +- The port 80 must be enabled on the Host so certbot can generate the certificates. +- Generate with certbot on the Host the `Letsencrypt certificates`. The option `--standalone` suits best when doing it manually. +- Example: `sudo certbot certonly --standalone --preferred-challenges http -d YOUR_DOMAINNAME` +- If the generation runs successsfully four files will be created: privkey.pem, fullchain.pem, chain.pem and cert.pem. +- On files `docker-compose/docker-compose-standard.yml` and `docker-compose/17d-ui_gateway_service_standard.yml`, replace YOUR_DOMAINNAME with the DNS value you have acquired the Certificate for on the Host. +- Still on files `docker-compose/docker-compose-standard.yml` and `docker-compose/17d-ui_gateway_service_standard.yml`, make sure that the files `fullchain.pem` and `privkey.pem` are mounted to the files where Letsencrypt generated the certificates. +- If you want to apply Certificates to other application stacks, you must apply the changes on the other files where the service ui.gateway is defined. +- On file `docker-compose/nginx/gateway/api_gateway.conf`, uncomment the directives concerning the certificate and replace the text YOUR_DOMAINNAME with the Domain Name reserved for the Host. +- Still on file `docker-compose/nginx/gateway/api_gateway.conf`, make sure that the variables `ssl_certificate` and `ssl_certificate_key` are mounted to the files where Letsencrypt generated the certificates. +- **Important** + - the Letsencrypt certificate generation includes cron jobs to renew the certificate validation. + - the certificate validation process runs twice a day and needs the port 80 to be unused while validating. + - this means, the Nginx server must be down for the renewal to be effective. + - so you must either synchronize the certificate validation process to be on time with a programmed downtime of the Nginx server, or you disable the certificate validation process and stop the Nginx server and run the certificate validation process manually at least one month before expiration. + - another solution would be to open another port but 80 to access Nginx, but it is up to you. +### Make Certificate Transmission Secure +Diffie-Hellman script makes the certificate transmission more secure. +- Generate on the Host a `Diffie-Hollman` key to secure the Certificate transmission. +- Usually, you generate it with the following command: `openssl dhparam -out /etc/nginx/dhparam.pem 4096`. +- It may take long to generate the key. +- On files `docker-compose/docker-compose-standard.yml` and `docker-compose/17d-ui_gateway_service_standard.yml`, make sure the Diffie-Hellman key is mounted orrectly. +- On file `docker-compose/nginx/gateway/api_gateway.conf`, make sure that the variable `ssl_dhparam` is mounted to the file where the Diffie-Hollman key was generated. +### Misc Certificates +- Enable access of Port 443 on the Host. +- You can still use HTTP protocoll, because the context for port 80 is left untouched. +- Remember that Letsencrypt certificates are issued for 90 days. + ## Useful Commands This application uses **Docker Compose** and as such, all docker and docker compose commands can be called to work wit it. ### Container Management From b9a048b50809870022ed5d1d3f516d4ed57ec74b Mon Sep 17 00:00:00 2001 From: Mario Calderon - Systemhaus Westfalia Date: Sat, 20 Jul 2024 11:29:30 -0600 Subject: [PATCH 3/3] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6e81be0d..396895bb 100644 --- a/README.md +++ b/README.md @@ -465,6 +465,9 @@ Connect to database via port **55432** with a DB connector, e.g. PGAdmin. Or to the port the variable `POSTGRES_EXTERNAL_PORT` points in file `env_template.env`. ## Using Certificates +You can use whatever cerfiticate you may choose. +Here, instructions to implement Letsencrypt are documented. +If you decide to implement with other Certificate Authority (CA), you can follow the instructions, only changing Letsencrypt with the CA selected. ### Get Letsencrypt Certificate Steps to use Letsencrypt certificates for a secure communication: - Letsencrypt issues Certificates only to a server which owns a Domainename.