﻿# ServiceControl TLS Configuration


ServiceControl instances can be configured to use HTTPS directly, enabling encrypted connections without relying on a reverse proxy for SSL termination.

## Configuration

ServiceControl instances can be configured via environment variables or App.config. Each instance type uses a different prefix. See the [Hosting Guide](../hosting-guide.md) for example usage of these configuration settings, along with [Authentication](authentication.md) and [Forward Headers](forward-headers.md), in a scenario-based format.

- [Primary Instance](/servicecontrol/servicecontrol-instances/configuration.md#tls)
- [Audit Instance](/servicecontrol/audit-instances/configuration.md#tls)
- [Monitoring Instance](/servicecontrol/monitoring-instances/configuration.md#tls)

## Security Considerations

### Certificate Management

- ServiceControl supports PFX (PKCS#12) certificate files
- Store certificate files securely with appropriate file permissions

### HSTS

The [HTTP Strict-Transport-Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security) response header (often abbreviated as HSTS) informs browsers that the host should only be accessed using HTTPS, and that any future attempts to access it using HTTP should automatically be upgraded to HTTPS. Additionally, on future connections to the host, the browser will not allow the user to bypass secure connection errors, such as an invalid certificate. HSTS identifies a host by its domain name only. Its important to read what each of these settings do before enabling them.

### HTTP to HTTPS Redirect

The `HTTPS_REDIRECTHTTPTOHTTPS` setting is intended for use with a reverse proxy that handles both HTTP and HTTPS traffic. When enabled:

- The redirect uses HTTP 307 (Temporary Redirect) to preserve the request method
- The reverse proxy must forward both HTTP and HTTPS requests to ServiceControl
- ServiceControl will redirect HTTP requests to HTTPS based on the `X-Forwarded-Proto` header
- **Important:** You must also set `HTTPS_PORT` to specify the HTTPS port for the redirect URL

> [!NOTE]
> When running ServiceControl directly without a reverse proxy, the application only listens on a single protocol (HTTP or HTTPS).

## Configuration examples

The following examples show common TLS configurations for different deployment scenarios using the primary ServiceControl instance.

### Direct HTTPS with certificate

When ServiceControl handles TLS directly using a PFX certificate:

```xml
<add key="ServiceControl/Https.Enabled" value="true" />
<add key="ServiceControl/Https.CertificatePath" value="C:\certs\servicecontrol.pfx" />
<add key="ServiceControl/Https.CertificatePassword" value="{certificate-password}" />
```

**Container**:

> [!NOTE]
> The following is a docker compose snippet. For full examples, see the [Platform Container Examples repository](https://github.com/Particular/PlatformContainerExamples).

```bash
servicecontrol:
    image: particular/servicecontrol:latest
    env_file: .env
    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: "..."
      SSL_CERT_FILE: "/etc/ssl/certs/ca-bundle.crt"
      SERVICECONTROL_AUTHENTICATION_ENABLED: "true"
      SERVICECONTROL_AUTHENTICATION_AUTHORITY: "https://login.microsoftonline.com/{tenantId}"
      SERVICECONTROL_AUTHENTICATION_AUDIENCE: "api://{api-id}"
      SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_CLIENTID: "{servicepulse-clientid}"
      SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_AUTHORITY: "https://login.microsoftonline.com/{tenantId}/v2.0"
      SERVICECONTROL_AUTHENTICATION_SERVICEPULSE_APISCOPES: '["api://{api-id}/access_as_user"]'
    command: --setup-and-run
    restart: unless-stopped
    volumes:
      - C:\path\to\servicecontrol\cert.pfx:/usr/share/ParticularSoftware/certificate.pfx
      - C:\path\to\servicecontrol\ca-bundle.crt:/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
```

**SSL_CERT_FILE (CRT)**: This tells SSL/TLS libraries (including .NET's HttpClient, OpenSSL, and libcurl) the path to a CA (Certificate Authority) certificate bundle file. This bundle contains the public certificates of trusted CAs used to verify the authenticity of SSL/TLS certificates presented by remote servers. This certificate is used to verify HTTPS (trust others).

**SERVICEPULSE_HTTPS_CERTIFICATEPATH (PFX)**: This is a binary archive format that bundles together the private key, public certificate, and certificate chain. This certificate is used to serve HTTPS (prove identity).

> [!NOTE]
> When containers communicate with each other over HTTPS, they use the Docker service names (like `servicecontrol`, `servicecontrol-audit`) as hostnames. TLS validation will fail if the certificate doesn't include these names in its Subject Alternative Names (SANs).

### Direct HTTPS with HSTS

When ServiceControl handles TLS directly and you want to enable HSTS:

```xml
<add key="ServiceControl/Https.Enabled" value="true" />
<add key="ServiceControl/Https.CertificatePath" value="C:\certs\servicecontrol.pfx" />
<add key="ServiceControl/Https.CertificatePassword" value="{certificate-password}" />
<add key="ServiceControl/Https.EnableHsts" value="true" />
<add key="ServiceControl/Https.HstsMaxAgeSeconds" value="31536000" />
```

### Reverse proxy with HTTP to HTTPS redirect

When TLS is terminated at a reverse proxy and you want ServiceControl to redirect HTTP requests:

```xml
<add key="ServiceControl/Https.RedirectHttpToHttps" value="true" />
<add key="ServiceControl/Https.Port" value="443" />
```

### Reverse proxy with HSTS

When TLS is terminated at a reverse proxy and you want ServiceControl to add HSTS headers:

```xml
<add key="ServiceControl/Https.EnableHsts" value="true" />
<add key="ServiceControl/Https.HstsMaxAgeSeconds" value="31536000" />
<add key="ServiceControl/Https.HstsIncludeSubDomains" value="true" />
```

## Troubleshooting

### Certificate not found or access denied

**Symptom**: The application fails to start with certificate-related errors when using a PFX certificate file.

**Cause**: The certificate file path is incorrect, the file is not readable, or the password is wrong.

**Solutions**:

- Verify the certificate path is correct and the file exists
- Check that the certificate file has appropriate permissions
- Confirm the certificate password is correct
- For containers, ensure the volume mount is correct (e.g. `-v certificate.pfx:/usr/share/ParticularSoftware/certificate.pfx`)

### HTTPS redirect not working

**Symptom**: HTTP requests are not redirected to HTTPS when using a reverse proxy.

**Cause**: The redirect requires the `X-Forwarded-Proto` header to detect the original protocol, which may not be processed.

**Solutions**:

- Verify `RedirectHttpToHttps` is set to `true`
- Ensure the HTTPS port setting is configured correctly
- Configure [forward headers](forward-headers.md) so the `X-Forwarded-Proto` header is trusted
- Confirm the reverse proxy is sending the `X-Forwarded-Proto` header

### HSTS header not appearing

**Symptom**: The `Strict-Transport-Security` header is not present in responses.

**Cause**: HSTS headers are only sent over HTTPS connections, or the setting is not enabled.

**Solutions**:

- Verify `EnableHsts` is set to `true`
- Confirm you are accessing via HTTPS (HSTS headers are not sent over HTTP)
- When using a reverse proxy, ensure the request is recognized as HTTPS via `X-Forwarded-Proto`

### Browser shows certificate warning

**Symptom**: Browser displays "Your connection is not private" or similar certificate warnings.

**Cause**: The client (browser or another container) doesn't trust the CA that signed the server's certificate.

**Solutions**:

- Use a certificate from a trusted Certificate Authority for production
- Ensure the certificate's Common Name (CN) or Subject Alternative Name (SAN) matches the hostname
- Check that the certificate has not expired
- Ensure the certificate is in the correct Trusted Root Certificate Authorities store. e.g.
  - If running in a Windows Service as `Local System`, the certificate should be in the `Local Computer` store.
  - If running as yourself, the certificate should be in the `Current User` store.
- For internal/development use, add the self-signed certificate to the trusted root store
- For containers: Add the CA certificate to the CA bundle and set `SSL_CERT_FILE`

### The remote certificate is invalid according to the validation procedure

**Symptom**: Service starts but fails when calling other services (e.g. ServiceControl can't reach ServiceControl-Audit) or when validating tokens with Azure AD.

**Cause**: The CA bundle doesn't contain the CA certificate that signed the remote server's certificate.

**Solutions**:

- Ensure the CA bundle includes your local CA certificate (e.g. mkcert's rootCA.pem)
- For Azure AD authentication, append the Mozilla CA bundle: curl https://curl.se/ca/cacert.pem >> ca-bundle.crt
- Verify `SSL_CERT_FILE` environment variable points to the correct path inside the container
- Check the CA bundle is mounted correctly

### The certificate doesn't match the hostname

**Symptom**: Browser or client rejects the certificate with a hostname mismatch error

**Cause**: The certificate's Subject Alternative Names (SANs) don't include the hostname being used to connect.

**Solutions**:

- Regenerate the certificate with all required hostnames
- Include both the Docker service name (for container-to-container) and localhost (for host access)

### Health checks fail with certificate errors

**Symptom**: Container health check reports unhealthy; logs show SSL errors from the health check command.

**Cause**: The health check binary inside the container doesn't trust the certificate.

**Solutions**:

- Ensure `SSL_CERT_FILE` is set so the health check process can find the CA bundle
- Check the health check URL uses the correct protocol: `https://` not `http://`
- Verify the certificate includes localhost in its SANs (health checks run inside the container)
