From eb5772651737cee39d944e33b1a8a68d031a1496 Mon Sep 17 00:00:00 2001 From: Warwick Schroeder Date: Thu, 5 Feb 2026 19:28:01 +0800 Subject: [PATCH 1/6] Add example for a secure compose deployment --- docker-compose/.env-secure | 11 +++ docker-compose/README.md | 60 +++++++++--- docker-compose/compose-secure.yml | 151 ++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 11 deletions(-) create mode 100644 docker-compose/.env-secure create mode 100644 docker-compose/compose-secure.yml diff --git a/docker-compose/.env-secure b/docker-compose/.env-secure new file mode 100644 index 0000000..1bf18f0 --- /dev/null +++ b/docker-compose/.env-secure @@ -0,0 +1,11 @@ +SERVICECONTROL_TAG=latest +SERVICEPULSE_TAG=latest +TRANSPORTTYPE=RabbitMQ.QuorumConventionalRouting +CONNECTIONSTRING="host=rabbitmq;username=guest;password=guest" +PARTICULARSOFTWARE_LICENSE="$PARTICULARSOFTWARE_LICENSE" +CERTIFICATE_PASSWORD="changeit" +SERVICECONTROL_AUTHENTICATION_AUTHORITY="https://login.microsoftonline.com/{tenant-id}" +SERVICECONTROL_AUTHENTICATION_AUDIENCE="api://{client-id}" +SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_CLIENTID="{servicepulse-client-id}" +CERTIFICATE_PATH="./certs/certificate.pfx" +CA_BUNDLE_PATH="./certs/ca-bundle.crt" \ No newline at end of file diff --git a/docker-compose/README.md b/docker-compose/README.md index 55c73e5..e5b47e1 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -7,28 +7,66 @@ Running ServiceControl and ServicePulse locally in containers provides a way to ## Usage **Pull the latest images:** Before running the containers, ensure you're using the latest version of each image by executing the following command: + ```shell docker compose pull ``` - This command checks for any updates to the images specified in the docker-compose.yml file and pulls them if available. + +This command checks for any updates to the images specified in the docker-compose.yml file and pulls them if available. **Start the containers:** After pulling the latest images, modify the [environment file](.env), if necessary, and then start up the containers using: + ```shell docker compose up -d ``` Once composed: -* [ServicePulse](https://docs.particular.net/servicepulse/) can be accessed at http://localhost:9090 -* [ServiceInsight](https://docs.particular.net/serviceinsight/) can be used with a connection URL of http://localhost:33333/api +- [ServicePulse](https://docs.particular.net/servicepulse/) can be accessed at http://localhost:9090 +- [ServiceInsight](https://docs.particular.net/serviceinsight/) can be used with a connection URL of http://localhost:33333/api ## Implementation details -* The ports for all services are exposed to localhost: - * `33333`: ServiceControl API - * `44444`: Audit API - * `33633`: Monitoring API - * `8080`: Database backend - * `9090` ServicePulse UI -* One instance of the [`servicecontrol-ravendb` container](https://docs.particular.net/servicecontrol/ravendb/containers) is used for both the [`servicecontrol`](https://docs.particular.net/servicecontrol/servicecontrol-instances/deployment/containers) and [`servicecontrol-audit`](https://docs.particular.net/servicecontrol/audit-instances/deployment/containers) containers. - * _A single database container should not be shared between multiple ServiceControl instances in production scenarios._ +- The ports for all services are exposed to localhost: + - `33333`: ServiceControl API + - `44444`: Audit API + - `33633`: Monitoring API + - `8080`: Database backend + - `9090` ServicePulse UI +- One instance of the [`servicecontrol-ravendb` container](https://docs.particular.net/servicecontrol/ravendb/containers) is used for both the [`servicecontrol`](https://docs.particular.net/servicecontrol/servicecontrol-instances/deployment/containers) and [`servicecontrol-audit`](https://docs.particular.net/servicecontrol/audit-instances/deployment/containers) containers. + - _A single database container should not be shared between multiple ServiceControl instances in production scenarios._ + +## Running with HTTPS and authentication + +The `compose-secure.yml` file provides a configuration with HTTPS enabled and OAuth2/OIDC authentication using Microsoft Entra ID (Azure AD). + +### Prerequisites + +1. **SSL Certificate**: A PFX certificate file for HTTPS +2. **CA Bundle**: A CA certificate bundle for validating the identity provider's certificates +3. **Microsoft Entra ID App Registration**: Configure an app registration for authentication + +### Configuration + +Copy `.env-secure` and update the following values: + +| Variable | Description | +|-------------------------------------------------------|------------------------------------------------------------------------------------------| +| `CERTIFICATE_PASSWORD` | Password for the PFX certificate | +| `CERTIFICATE_PATH` | Path to the PFX certificate file (default: `./certs/certificate.pfx`) | +| `CA_BUNDLE_PATH` | Path to the CA bundle file (default: `./certs/ca-bundle.crt`) | +| `SERVICECONTROL_AUTHENTICATION_AUTHORITY` | Microsoft Entra ID authority URL (e.g., `https://login.microsoftonline.com/{tenant-id}`) | +| `SERVICECONTROL_AUTHENTICATION_AUDIENCE` | API audience URI (e.g., `api://{client-id}`) | +| `SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_CLIENTID` | ServicePulse app registration client ID | + +### Starting the secure containers + +```shell +docker compose -f compose-secure.yml pull +docker compose -f compose-secure.yml up -d +``` + +Once composed: + +- [ServicePulse](https://docs.particular.net/servicepulse/) can be accessed at `https://localhost:9090` +- [ServiceInsight](https://docs.particular.net/serviceinsight/) can be used with a connection URL of `https://localhost:33333/api` diff --git a/docker-compose/compose-secure.yml b/docker-compose/compose-secure.yml new file mode 100644 index 0000000..569fa9f --- /dev/null +++ b/docker-compose/compose-secure.yml @@ -0,0 +1,151 @@ +name: service-platform + +services: + servicecontrol: + image: particular/servicecontrol:${SERVICECONTROL_TAG} + env_file: .env-secure + ports: + - "33333:33333" + environment: + RAVENDB_CONNECTIONSTRING: http://servicecontrol-db:8080 + REMOTEINSTANCES: '[{"api_uri":"https://servicecontrol-audit:44444/api"}]' + SERVICECONTROL_HTTPS_ENABLED: "true" + SERVICECONTROL_HTTPS_CERTIFICATEPATH: "/usr/share/ParticularSoftware/certificate.pfx" + SERVICECONTROL_HTTPS_CERTIFICATEPASSWORD: "${CERTIFICATE_PASSWORD}" + SSL_CERT_FILE: "/etc/ssl/certs/ca-bundle.crt" + SERVICECONTROL_AUTHENTICATION_ENABLED: "true" + SERVICECONTROL_AUTHENTICATION_AUTHORITY: "${SERVICECONTROL_AUTHENTICATION_AUTHORITY}" + SERVICECONTROL_AUTHENTICATION_AUDIENCE: "${SERVICECONTROL_AUTHENTICATION_AUDIENCE}" + SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_CLIENTID: "${SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_CLIENTID}" + SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_AUTHORITY: "${SERVICECONTROL_AUTHENTICATION_AUTHORITY}/v2.0" + SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_APISCOPES: '["${SERVICECONTROL_AUTHENTICATION_AUDIENCE}/access_as_user"]' + command: --setup-and-run + restart: unless-stopped + volumes: + - ${CERTIFICATE_PATH}:/usr/share/ParticularSoftware/certificate.pfx + - ${CA_BUNDLE_PATH}:/etc/ssl/certs/ca-bundle.crt:ro + healthcheck: + test: ["CMD", "/healthcheck/healthcheck", "https://localhost:33333/api"] + interval: 30s + timeout: 10s + start_period: 60s + retries: 3 + depends_on: + servicecontrol-db: + condition: service_healthy + rabbitmq: + condition: service_healthy + + # WARNING: A single database container should not be shared between multiple ServiceControl instances in production scenarios. + servicecontrol-db: + image: particular/servicecontrol-ravendb:${SERVICECONTROL_TAG} + ports: + - "8080:8080" + volumes: + - raven-config:/var/lib/ravendb/config + - raven-data:/var/lib/ravendb/data + + servicecontrol-audit: + image: particular/servicecontrol-audit:${SERVICECONTROL_TAG} + env_file: .env-secure + ports: + - "44444:44444" + environment: + RAVENDB_CONNECTIONSTRING: http://servicecontrol-db:8080 + SERVICECONTROLQUEUEADDRESS: Particular.ServiceControl + SERVICECONTROL_AUDIT_HTTPS_ENABLED: "true" + SERVICECONTROL_AUDIT_HTTPS_CERTIFICATEPATH: "/usr/share/ParticularSoftware/certificate.pfx" + SERVICECONTROL_AUDIT_HTTPS_CERTIFICATEPASSWORD: "${CERTIFICATE_PASSWORD}" + SSL_CERT_FILE: "/etc/ssl/certs/ca-bundle.crt" + SERVICECONTROL_AUDIT_AUTHENTICATION_ENABLED: "true" + SERVICECONTROL_AUDIT_AUTHENTICATION_AUTHORITY: "${SERVICECONTROL_AUTHENTICATION_AUTHORITY}" + SERVICECONTROL_AUDIT_AUTHENTICATION_AUDIENCE: "${SERVICECONTROL_AUTHENTICATION_AUDIENCE}" + command: --setup-and-run + restart: unless-stopped + volumes: + - ${CERTIFICATE_PATH}:/usr/share/ParticularSoftware/certificate.pfx + - ${CA_BUNDLE_PATH}:/etc/ssl/certs/ca-bundle.crt:ro + healthcheck: + test: ["CMD", "/healthcheck/healthcheck", "https://localhost:44444/api"] + interval: 30s + timeout: 10s + start_period: 60s + retries: 3 + depends_on: + servicecontrol-db: + condition: service_healthy + rabbitmq: + condition: service_healthy + + servicecontrol-monitoring: + image: particular/servicecontrol-monitoring:${SERVICECONTROL_TAG} + env_file: .env-secure + environment: + MONITORING_HTTPS_ENABLED: "true" + MONITORING_HTTPS_CERTIFICATEPATH: "/usr/share/ParticularSoftware/certificate.pfx" + MONITORING_HTTPS_CERTIFICATEPASSWORD: "${CERTIFICATE_PASSWORD}" + SSL_CERT_FILE: "/etc/ssl/certs/ca-bundle.crt" + MONITORING_AUTHENTICATION_ENABLED: "true" + MONITORING_AUTHENTICATION_AUTHORITY: "${SERVICECONTROL_AUTHENTICATION_AUTHORITY}" + MONITORING_AUTHENTICATION_AUDIENCE: "${SERVICECONTROL_AUTHENTICATION_AUDIENCE}" + restart: unless-stopped + command: --setup-and-run + ports: + - "33633:33633" + volumes: + - ${CERTIFICATE_PATH}:/usr/share/ParticularSoftware/certificate.pfx + - ${CA_BUNDLE_PATH}:/etc/ssl/certs/ca-bundle.crt:ro + healthcheck: + test: ["CMD", "/healthcheck/healthcheck", "https://localhost:33633/"] + interval: 30s + timeout: 10s + start_period: 60s + retries: 3 + depends_on: + rabbitmq: + condition: service_healthy + + servicepulse: + image: particular/servicepulse:${SERVICEPULSE_TAG} + ports: + - "9090:9090" + environment: + SERVICECONTROL_URL: https://servicecontrol:33333 + MONITORING_URL: https://servicecontrol-monitoring:33633 + SERVICEPULSE_HTTPS_ENABLED: "true" + SERVICEPULSE_HTTPS_CERTIFICATEPATH: "/usr/share/ParticularSoftware/certificate.pfx" + SERVICEPULSE_HTTPS_CERTIFICATEPASSWORD: "${CERTIFICATE_PASSWORD}" + SSL_CERT_FILE: "/etc/ssl/certs/ca-bundle.crt" + ASPNETCORE_URLS: "https://+:9090" + restart: unless-stopped + volumes: + - ${CERTIFICATE_PATH}:/usr/share/ParticularSoftware/certificate.pfx + - ${CA_BUNDLE_PATH}:/etc/ssl/certs/ca-bundle.crt:ro + depends_on: + servicecontrol: + condition: service_healthy + servicecontrol-monitoring: + condition: service_healthy + rabbitmq: + condition: service_healthy + + rabbitmq: + image: rabbitmq:3-management + ports: + - "5672:5672" + - "15672:15672" + restart: unless-stopped + healthcheck: + test: rabbitmq-diagnostics check_port_connectivity + interval: 30s + timeout: 10s + start_period: 30s + start_interval: 10s + retries: 3 + volumes: + - rabbitmq-data:/var/lib/rabbitmq + +volumes: + rabbitmq-data: + raven-config: + raven-data: From a7a4daf14c72a82e925fb5b01424fb5b653b2dbf Mon Sep 17 00:00:00 2001 From: Warwick Schroeder Date: Fri, 6 Feb 2026 16:08:11 +0800 Subject: [PATCH 2/6] Updates from feedback --- docker-compose/.env-secure | 11 ----- docker-compose/README.md | 82 ++++++++++++++++++++++++++----- docker-compose/compose-secure.yml | 24 ++++----- 3 files changed, 83 insertions(+), 34 deletions(-) delete mode 100644 docker-compose/.env-secure diff --git a/docker-compose/.env-secure b/docker-compose/.env-secure deleted file mode 100644 index 1bf18f0..0000000 --- a/docker-compose/.env-secure +++ /dev/null @@ -1,11 +0,0 @@ -SERVICECONTROL_TAG=latest -SERVICEPULSE_TAG=latest -TRANSPORTTYPE=RabbitMQ.QuorumConventionalRouting -CONNECTIONSTRING="host=rabbitmq;username=guest;password=guest" -PARTICULARSOFTWARE_LICENSE="$PARTICULARSOFTWARE_LICENSE" -CERTIFICATE_PASSWORD="changeit" -SERVICECONTROL_AUTHENTICATION_AUTHORITY="https://login.microsoftonline.com/{tenant-id}" -SERVICECONTROL_AUTHENTICATION_AUDIENCE="api://{client-id}" -SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_CLIENTID="{servicepulse-client-id}" -CERTIFICATE_PATH="./certs/certificate.pfx" -CA_BUNDLE_PATH="./certs/ca-bundle.crt" \ No newline at end of file diff --git a/docker-compose/README.md b/docker-compose/README.md index e5b47e1..680ac56 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -46,27 +46,87 @@ The `compose-secure.yml` file provides a configuration with HTTPS enabled and OA 2. **CA Bundle**: A CA certificate bundle for validating the identity provider's certificates 3. **Microsoft Entra ID App Registration**: Configure an app registration for authentication +> [!NOTE] +> The PFX file contains the private key and certificate for the service to **serve** HTTPS. The CA bundle contains only public CA certificates for the service to **verify** other services' certificates. Both are required when containers communicate over HTTPS. + ### Configuration -Copy `.env-secure` and update the following values: +Update as needed, and copy the following variables into the `.env` file. + +```text +CERTIFICATE_PASSWORD="" +CERTIFICATE_PATH="" +CA_BUNDLE_PATH="" +IDP_AUTHORITY="" +SERVICECONTROL_AUDIENCE="" +SERVICEPULSE_CLIENTID="" +SERVICEPULSE_APISCOPES='' +``` + +| Variable | Description | +|---------------------------|------------------------------------------------------------------------------------------| +| `CERTIFICATE_PASSWORD` | Password for the PFX certificate | +| `CERTIFICATE_PATH` | Path to the PFX certificate file (e.g.: `./certs/certificate.pfx`) | +| `CA_BUNDLE_PATH` | Path to the CA bundle file (default: `./certs/ca-bundle.crt`) | +| `IDP_AUTHORITY` | Microsoft Entra ID authority URL (e.g., `https://login.microsoftonline.com/{tenant-id}`) | +| `SERVICECONTROL_AUDIENCE` | API audience URI (e.g., `api://{client-id}`) | +| `SERVICEPULSE_CLIENTID` | ServicePulse app registration client ID | +| `SERVICEPULSE_APISCOPES` | Array of API scopes. e.g. `["api://{client-id}"]` | + +#### Generate a PFX Certificate for Local Testing Only + +> [!WARNING] +> The certificate generated below is for local testing only. Use a certificate from a trusted Certificate Authority for production deployments. + +The below assume the `mkcert` tool has been installed. + +```cmd +# Install mkcert's root CA (one-time setup) +mkcert -install + +# Navigate to a certs folder. e.g. +cd certs + +# Generate PFX certificate for localhost +mkcert -p12-file localhost.pfx -pkcs12 localhost 127.0.0.1 ::1 servicecontrol servicecontrol-audit servicecontrol-monitor +``` + +#### Generate a CA Bundle for Local Testing Only + +> [!WARNING] +> The CA bundle generated below is for local testing only. Use certificates from a trusted Certificate Authority for production deployments. + +When running ServiceControl in Docker containers, each container needs a CA bundle file to trust certificates presented by other services. The `SSL_CERT_FILE` environment variable tells .NET where to find this bundle. + +#### What is a CA Bundle? + +A CA bundle is a file containing one or more Certificate Authority (CA) certificates. When a container makes an HTTPS request to another service, it uses this bundle to verify the server's certificate chain. Without it, containers would reject connections to services using your mkcert certificates. + +#### Why is it needed? -| Variable | Description | -|-------------------------------------------------------|------------------------------------------------------------------------------------------| -| `CERTIFICATE_PASSWORD` | Password for the PFX certificate | -| `CERTIFICATE_PATH` | Path to the PFX certificate file (default: `./certs/certificate.pfx`) | -| `CA_BUNDLE_PATH` | Path to the CA bundle file (default: `./certs/ca-bundle.crt`) | -| `SERVICECONTROL_AUTHENTICATION_AUTHORITY` | Microsoft Entra ID authority URL (e.g., `https://login.microsoftonline.com/{tenant-id}`) | -| `SERVICECONTROL_AUTHENTICATION_AUDIENCE` | API audience URI (e.g., `api://{client-id}`) | -| `SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_CLIENTID` | ServicePulse app registration client ID | +Unlike your host machine (where `mkcert -install` adds the CA to the system trust store), Docker containers don't share the host's trust store. You must explicitly provide the CA certificates that containers should trust. + +#### Generate the CA bundle + +```cmd +REM Get the mkcert CA root location +for /f "delims=" %i in ('mkcert -CAROOT') do set CA_ROOT=%i + +REM For local development only (just mkcert CA) +copy "%CA_ROOT%\rootCA.pem" certs\ca-bundle.crt + +REM For local development with external HTTPS calls (e.g., Azure AD authentication) +REM Download Mozilla's public CA bundle and combine with mkcert CA +copy "%CA_ROOT%\rootCA.pem" certs\ca-bundle.crt +curl -s https://curl.se/ca/cacert.pem >> certs\ca-bundle.crt +``` ### Starting the secure containers ```shell -docker compose -f compose-secure.yml pull docker compose -f compose-secure.yml up -d ``` Once composed: - [ServicePulse](https://docs.particular.net/servicepulse/) can be accessed at `https://localhost:9090` -- [ServiceInsight](https://docs.particular.net/serviceinsight/) can be used with a connection URL of `https://localhost:33333/api` diff --git a/docker-compose/compose-secure.yml b/docker-compose/compose-secure.yml index 569fa9f..bc9eaf2 100644 --- a/docker-compose/compose-secure.yml +++ b/docker-compose/compose-secure.yml @@ -3,7 +3,7 @@ name: service-platform services: servicecontrol: image: particular/servicecontrol:${SERVICECONTROL_TAG} - env_file: .env-secure + env_file: .env ports: - "33333:33333" environment: @@ -14,11 +14,11 @@ services: SERVICECONTROL_HTTPS_CERTIFICATEPASSWORD: "${CERTIFICATE_PASSWORD}" SSL_CERT_FILE: "/etc/ssl/certs/ca-bundle.crt" SERVICECONTROL_AUTHENTICATION_ENABLED: "true" - SERVICECONTROL_AUTHENTICATION_AUTHORITY: "${SERVICECONTROL_AUTHENTICATION_AUTHORITY}" - SERVICECONTROL_AUTHENTICATION_AUDIENCE: "${SERVICECONTROL_AUTHENTICATION_AUDIENCE}" - SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_CLIENTID: "${SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_CLIENTID}" - SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_AUTHORITY: "${SERVICECONTROL_AUTHENTICATION_AUTHORITY}/v2.0" - SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_APISCOPES: '["${SERVICECONTROL_AUTHENTICATION_AUDIENCE}/access_as_user"]' + SERVICECONTROL_AUTHENTICATION_AUTHORITY: "${IDP_AUTHORITY}" + SERVICECONTROL_AUTHENTICATION_AUDIENCE: "${SERVICECONTROL_AUDIENCE}" + SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_CLIENTID: "${SERVICEPULSE_CLIENTID}" + SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_AUTHORITY: "${IDP_AUTHORITY}/v2.0" + SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_APISCOPES: '${SERVICEPULSE_APISCOPES}' command: --setup-and-run restart: unless-stopped volumes: @@ -47,7 +47,7 @@ services: servicecontrol-audit: image: particular/servicecontrol-audit:${SERVICECONTROL_TAG} - env_file: .env-secure + env_file: .env ports: - "44444:44444" environment: @@ -58,8 +58,8 @@ services: SERVICECONTROL_AUDIT_HTTPS_CERTIFICATEPASSWORD: "${CERTIFICATE_PASSWORD}" SSL_CERT_FILE: "/etc/ssl/certs/ca-bundle.crt" SERVICECONTROL_AUDIT_AUTHENTICATION_ENABLED: "true" - SERVICECONTROL_AUDIT_AUTHENTICATION_AUTHORITY: "${SERVICECONTROL_AUTHENTICATION_AUTHORITY}" - SERVICECONTROL_AUDIT_AUTHENTICATION_AUDIENCE: "${SERVICECONTROL_AUTHENTICATION_AUDIENCE}" + SERVICECONTROL_AUDIT_AUTHENTICATION_AUTHORITY: "${IDP_AUTHORITY}" + SERVICECONTROL_AUDIT_AUTHENTICATION_AUDIENCE: "${SERVICECONTROL_AUDIENCE}" command: --setup-and-run restart: unless-stopped volumes: @@ -79,15 +79,15 @@ services: servicecontrol-monitoring: image: particular/servicecontrol-monitoring:${SERVICECONTROL_TAG} - env_file: .env-secure + env_file: .env environment: MONITORING_HTTPS_ENABLED: "true" MONITORING_HTTPS_CERTIFICATEPATH: "/usr/share/ParticularSoftware/certificate.pfx" MONITORING_HTTPS_CERTIFICATEPASSWORD: "${CERTIFICATE_PASSWORD}" SSL_CERT_FILE: "/etc/ssl/certs/ca-bundle.crt" MONITORING_AUTHENTICATION_ENABLED: "true" - MONITORING_AUTHENTICATION_AUTHORITY: "${SERVICECONTROL_AUTHENTICATION_AUTHORITY}" - MONITORING_AUTHENTICATION_AUDIENCE: "${SERVICECONTROL_AUTHENTICATION_AUDIENCE}" + MONITORING_AUTHENTICATION_AUTHORITY: "${IDP_AUTHORITY}" + MONITORING_AUTHENTICATION_AUDIENCE: "${SERVICECONTROL_AUDIENCE}" restart: unless-stopped command: --setup-and-run ports: From a5c351cbbe5369b27438f6817a20c0323303b0eb Mon Sep 17 00:00:00 2001 From: Warwick Schroeder Date: Sat, 7 Feb 2026 07:37:48 +0800 Subject: [PATCH 3/6] Further refinement of the secure container setups --- docker-compose/README.md | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/docker-compose/README.md b/docker-compose/README.md index 680ac56..e0fd3ab 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -47,11 +47,11 @@ The `compose-secure.yml` file provides a configuration with HTTPS enabled and OA 3. **Microsoft Entra ID App Registration**: Configure an app registration for authentication > [!NOTE] -> The PFX file contains the private key and certificate for the service to **serve** HTTPS. The CA bundle contains only public CA certificates for the service to **verify** other services' certificates. Both are required when containers communicate over HTTPS. +> The [PFX file](#generate-a-pfx-certificate-for-local-testing-only) contains the private key and certificate for the service to **serve** HTTPS. The [CA bundle](#generate-a-ca-bundle-for-local-testing-only) contains only public CA certificates for the service to **verify** other services' certificates. Both are required when containers communicate over HTTPS. ### Configuration -Update as needed, and copy the following variables into the `.env` file. +Update the below variables as needed, and copy into the `.env` file. ```text CERTIFICATE_PASSWORD="" @@ -66,7 +66,7 @@ SERVICEPULSE_APISCOPES='' | Variable | Description | |---------------------------|------------------------------------------------------------------------------------------| | `CERTIFICATE_PASSWORD` | Password for the PFX certificate | -| `CERTIFICATE_PATH` | Path to the PFX certificate file (e.g.: `./certs/certificate.pfx`) | +| `CERTIFICATE_PATH` | Path to the PFX certificate file (e.g.: `./certs/servicecontrol.pfx`) | | `CA_BUNDLE_PATH` | Path to the CA bundle file (default: `./certs/ca-bundle.crt`) | | `IDP_AUTHORITY` | Microsoft Entra ID authority URL (e.g., `https://login.microsoftonline.com/{tenant-id}`) | | `SERVICECONTROL_AUDIENCE` | API audience URI (e.g., `api://{client-id}`) | @@ -80,15 +80,19 @@ SERVICEPULSE_APISCOPES='' The below assume the `mkcert` tool has been installed. -```cmd +> [!IMPORTANT] +> The certificate must include every hostname that will be used to access a service over HTTPS. In a Docker Compose network, containers reach each other using service names as hostnames (e.g., `https://servicecontrol:33333`). During the TLS handshake the client checks that the server's certificate contains a [Subject Alternative Name](https://en.wikipedia.org/wiki/Subject_Alternative_Name) (SAN) matching the hostname it connected to. If the name is missing, the connection is rejected. + +```bash # Install mkcert's root CA (one-time setup) mkcert -install -# Navigate to a certs folder. e.g. +# Navigate/create a folder to store the certificates. e.g. +mkdir certs cd certs -# Generate PFX certificate for localhost -mkcert -p12-file localhost.pfx -pkcs12 localhost 127.0.0.1 ::1 servicecontrol servicecontrol-audit servicecontrol-monitor +# Generate PFX certificate for localhost/servicecontrol instances +mkcert -p12-file servicecontrol.pfx -pkcs12 localhost 127.0.0.1 ::1 servicecontrol servicecontrol-audit servicecontrol-monitoring ``` #### Generate a CA Bundle for Local Testing Only @@ -100,25 +104,19 @@ When running ServiceControl in Docker containers, each container needs a CA bund #### What is a CA Bundle? -A CA bundle is a file containing one or more Certificate Authority (CA) certificates. When a container makes an HTTPS request to another service, it uses this bundle to verify the server's certificate chain. Without it, containers would reject connections to services using your mkcert certificates. - -#### Why is it needed? - -Unlike your host machine (where `mkcert -install` adds the CA to the system trust store), Docker containers don't share the host's trust store. You must explicitly provide the CA certificates that containers should trust. +A CA bundle is a file containing one or more Certificate Authority (CA) certificates. When a container makes an HTTPS request to another service, it uses this bundle to verify the server's certificate chain. Without it, containers would reject connections to services using your mkcert certificates. Unlike your host machine (where `mkcert -install` adds the CA to the system trust store), Docker containers don't share the host's trust store. You must explicitly provide the CA certificates that containers should trust. #### Generate the CA bundle -```cmd -REM Get the mkcert CA root location +```bash +# Get the mkcert CA root location for /f "delims=" %i in ('mkcert -CAROOT') do set CA_ROOT=%i -REM For local development only (just mkcert CA) -copy "%CA_ROOT%\rootCA.pem" certs\ca-bundle.crt +# Navigate to the folder containing the PFX certificate +cd certs -REM For local development with external HTTPS calls (e.g., Azure AD authentication) -REM Download Mozilla's public CA bundle and combine with mkcert CA -copy "%CA_ROOT%\rootCA.pem" certs\ca-bundle.crt -curl -s https://curl.se/ca/cacert.pem >> certs\ca-bundle.crt +# For local development only (just mkcert CA) +copy "%CA_ROOT%\rootCA.pem" ca-bundle.crt ``` ### Starting the secure containers From b733b5120a918753915ae2fb3412b027dd0b3cc3 Mon Sep 17 00:00:00 2001 From: jasontaylordev Date: Mon, 9 Feb 2026 09:57:30 +1000 Subject: [PATCH 4/6] Update README for improved clarity, pwsh compatibility, and update compose-secure.yml for latest image usage --- docker-compose/README.md | 44 +++++++++++++++---------------- docker-compose/compose-secure.yml | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docker-compose/README.md b/docker-compose/README.md index e0fd3ab..19683cf 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -8,7 +8,7 @@ Running ServiceControl and ServicePulse locally in containers provides a way to **Pull the latest images:** Before running the containers, ensure you're using the latest version of each image by executing the following command: - ```shell + ```pwsh docker compose pull ``` @@ -16,7 +16,7 @@ This command checks for any updates to the images specified in the docker-compos **Start the containers:** After pulling the latest images, modify the [environment file](.env), if necessary, and then start up the containers using: -```shell +```pwsh docker compose up -d ``` @@ -51,27 +51,27 @@ The `compose-secure.yml` file provides a configuration with HTTPS enabled and OA ### Configuration -Update the below variables as needed, and copy into the `.env` file. +Add the following variables to your `.env` file and replace the `{placeholder}` values with your actual configuration: ```text -CERTIFICATE_PASSWORD="" -CERTIFICATE_PATH="" -CA_BUNDLE_PATH="" -IDP_AUTHORITY="" -SERVICECONTROL_AUDIENCE="" -SERVICEPULSE_CLIENTID="" -SERVICEPULSE_APISCOPES='' +CERTIFICATE_PASSWORD="{password}" +CERTIFICATE_PATH="./certs/servicecontrol.pfx" +CA_BUNDLE_PATH="./certs/ca-bundle.crt" +IDP_AUTHORITY="https://login.microsoftonline.com/{tenant-id}" +SERVICECONTROL_AUDIENCE="api://{servicecontrol-client-id}" +SERVICEPULSE_CLIENTID="{servicepulse-client-id}" +SERVICEPULSE_APISCOPES=["api://{servicecontrol-client-id}/{scope-name}"] ``` | Variable | Description | |---------------------------|------------------------------------------------------------------------------------------| -| `CERTIFICATE_PASSWORD` | Password for the PFX certificate | -| `CERTIFICATE_PATH` | Path to the PFX certificate file (e.g.: `./certs/servicecontrol.pfx`) | -| `CA_BUNDLE_PATH` | Path to the CA bundle file (default: `./certs/ca-bundle.crt`) | +| `CERTIFICATE_PASSWORD` | Password for the PFX certificate (e.g., the password used when generating with mkcert) | +| `CERTIFICATE_PATH` | Path to the PFX certificate file (e.g., `./certs/servicecontrol.pfx`) | +| `CA_BUNDLE_PATH` | Path to the CA bundle file (e.g., `./certs/ca-bundle.crt`) | | `IDP_AUTHORITY` | Microsoft Entra ID authority URL (e.g., `https://login.microsoftonline.com/{tenant-id}`) | -| `SERVICECONTROL_AUDIENCE` | API audience URI (e.g., `api://{client-id}`) | -| `SERVICEPULSE_CLIENTID` | ServicePulse app registration client ID | -| `SERVICEPULSE_APISCOPES` | Array of API scopes. e.g. `["api://{client-id}"]` | +| `SERVICECONTROL_AUDIENCE` | Application ID URI from ServiceControl app registration (e.g., `api://{servicecontrol-client-id}`) | +| `SERVICEPULSE_CLIENTID` | Application (client) ID from ServicePulse app registration AD | +| `SERVICEPULSE_APISCOPES` | Array of API scopes ServicePulse should request when calling ServiceControl (e.g., ["api://{servicecontrol-client-id}/{scope-name}"]) | #### Generate a PFX Certificate for Local Testing Only @@ -83,7 +83,7 @@ The below assume the `mkcert` tool has been installed. > [!IMPORTANT] > The certificate must include every hostname that will be used to access a service over HTTPS. In a Docker Compose network, containers reach each other using service names as hostnames (e.g., `https://servicecontrol:33333`). During the TLS handshake the client checks that the server's certificate contains a [Subject Alternative Name](https://en.wikipedia.org/wiki/Subject_Alternative_Name) (SAN) matching the hostname it connected to. If the name is missing, the connection is rejected. -```bash +```pwsh # Install mkcert's root CA (one-time setup) mkcert -install @@ -108,23 +108,23 @@ A CA bundle is a file containing one or more Certificate Authority (CA) certific #### Generate the CA bundle -```bash +```pwsh # Get the mkcert CA root location -for /f "delims=" %i in ('mkcert -CAROOT') do set CA_ROOT=%i +$CA_ROOT = mkcert -CAROOT # Navigate to the folder containing the PFX certificate cd certs # For local development only (just mkcert CA) -copy "%CA_ROOT%\rootCA.pem" ca-bundle.crt +copy "$CA_ROOT/rootCA.pem" ca-bundle.crt ``` ### Starting the secure containers -```shell +```pwsh docker compose -f compose-secure.yml up -d ``` Once composed: -- [ServicePulse](https://docs.particular.net/servicepulse/) can be accessed at `https://localhost:9090` +- [ServicePulse](https://docs.particular.net/servicepulse/) can be accessed at https://localhost:9090 diff --git a/docker-compose/compose-secure.yml b/docker-compose/compose-secure.yml index bc9eaf2..2f89511 100644 --- a/docker-compose/compose-secure.yml +++ b/docker-compose/compose-secure.yml @@ -38,7 +38,7 @@ services: # WARNING: A single database container should not be shared between multiple ServiceControl instances in production scenarios. servicecontrol-db: - image: particular/servicecontrol-ravendb:${SERVICECONTROL_TAG} + image: particular/servicecontrol-ravendb:latest ports: - "8080:8080" volumes: From ca37db3b96ccf12990fd25dfa771a253d01de497 Mon Sep 17 00:00:00 2001 From: jasontaylordev Date: Mon, 9 Feb 2026 12:01:52 +1000 Subject: [PATCH 5/6] Update servicecontrol-db image tag to use SERVICECONTROL_TAG variable --- docker-compose/compose-secure.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose/compose-secure.yml b/docker-compose/compose-secure.yml index 2f89511..bc9eaf2 100644 --- a/docker-compose/compose-secure.yml +++ b/docker-compose/compose-secure.yml @@ -38,7 +38,7 @@ services: # WARNING: A single database container should not be shared between multiple ServiceControl instances in production scenarios. servicecontrol-db: - image: particular/servicecontrol-ravendb:latest + image: particular/servicecontrol-ravendb:${SERVICECONTROL_TAG} ports: - "8080:8080" volumes: From f5b2894d6101c154b9680ebf9785d13fdfa00fe9 Mon Sep 17 00:00:00 2001 From: jasontaylordev Date: Mon, 9 Feb 2026 12:26:06 +1000 Subject: [PATCH 6/6] Add instructions to pull the latest images before starting secure containers --- docker-compose/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker-compose/README.md b/docker-compose/README.md index 19683cf..362e512 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -119,6 +119,13 @@ cd certs copy "$CA_ROOT/rootCA.pem" ca-bundle.crt ``` + ### Pull the latest images + Before running the containers, ensure you're using the latest version of each image by executing the following command: + + ```pwsh + docker compose -f compose-secure.yml pull + ``` + ### Starting the secure containers ```pwsh