Skip to content

feat: trust internal CAs in browser monitors#1149

Draft
shahzad31 wants to merge 2 commits into
mainfrom
browser-monitor-internal-ca
Draft

feat: trust internal CAs in browser monitors#1149
shahzad31 wants to merge 2 commits into
mainfrom
browser-monitor-internal-ca

Conversation

@shahzad31

@shahzad31 shahzad31 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Browser journeys run on headless Chromium, which on Linux trusts its own NSS store rather than the system CA store. Sites served by an internal/private CA therefore fail with ERR_CERT_AUTHORITY_INVALID, and the only workaround was either rebuilding the elastic-agent-complete image to inject the CA into the NSS DB, or using ignoreHTTPSErrors (which disables TLS validation for every endpoint).

This PR adds first-class support for trusting internal CAs in browser monitors:

  • New certificateAuthorities option, available via the Synthetics config and the --certificate-authorities CLI flag. Each entry can be inline PEM content or a path to a PEM file.
  • The agent computes the SHA-256 fingerprint of each CA's SubjectPublicKeyInfo and launches Chromium with --ignore-certificate-errors-spki-list=<fingerprints>. Only certificates issued by the provided CAs are trusted; normal validation still applies to all other endpoints (unlike ignoreHTTPSErrors).
  • Inline PEM keeps this usable on Elastic-managed/global locations without rebuilding the agent image.

Closes #717
Closes #170

Why SPKI pinning instead of ignoreHTTPSErrors

--ignore-certificate-errors-spki-list scopes the trust to the user's own CA public key, so a real MITM presenting a different certificate still fails the monitor. ignoreHTTPSErrors blanket-disables validation, which most enterprises can't accept.

Usage

Config (synthetics.config.ts) — each entry is a path to a PEM file, inline PEM, a Buffer, or an array of any of these:

import type { SyntheticsConfig } from '@elastic/synthetics';

export default (): SyntheticsConfig => ({
  certificateAuthorities: ['./certs/internal-ca.crt'],
  // inline also works:
  // certificateAuthorities: [readFileSync('./certs/internal-ca.crt', 'utf-8')],
});

CLI (variadic — pass more than one):

npx @elastic/synthetics . --certificate-authorities ./certs/internal-ca.crt

A runnable example (config + journey + README) lives in examples/internal-ca.

Try it locally

No internal infra needed — spin up a throwaway CA + HTTPS server and watch the journey flip from failing to passing:

# 1. private CA + a localhost server cert signed by it
mkdir -p certs && cd certs
openssl req -x509 -newkey rsa:2048 -nodes -keyout internal-ca.key \
  -out internal-ca.crt -days 3650 -subj "/CN=Example Internal CA"
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/CN=localhost"
openssl x509 -req -in server.csr -CA internal-ca.crt -CAkey internal-ca.key \
  -CAcreateserial -out server.crt -days 825 \
  -extfile <(printf "subjectAltName=DNS:localhost,IP:127.0.0.1")
cd ..

# 2. serve HTTPS with that cert (leave running)
node -e "require('https').createServer({key:require('fs').readFileSync('certs/server.key'),cert:require('fs').readFileSync('certs/server.crt')},(_,res)=>res.end('ok')).listen(8443,()=>console.log('up'))" &

# 3. WITHOUT the CA -> step fails with net::ERR_CERT_AUTHORITY_INVALID
npx @elastic/synthetics examples/internal-ca --params '{"url":"https://localhost:8443/"}'

# 4. WITH the CA -> journey succeeds
npx @elastic/synthetics examples/internal-ca \
  --certificate-authorities ./certs/internal-ca.crt \
  --params '{"url":"https://localhost:8443/"}'

Confirm the flag actually reached Chromium:

ps -ef | grep -- '--ignore-certificate-errors-spki-list'

Test plan

  • Unit tests for SPKI fingerprinting / PEM parsing (__tests__/core/certs.test.ts)
  • Option normalization (inline PEM + file path) in __tests__/options.test.ts
  • End-to-end CLI test: a journey against a self-signed TLS server succeeds with --certificate-authorities and still fails without it (__tests__/cli.test.ts)
  • tsc, eslint, prettier --check, full unit suite green

Notes

  • The --ignore-certificate-errors-spki-list switch ignores all certificate errors (including expiry/hostname) for the pinned key, by design.
  • Applies to the chromium.launch path; the remote wsEndpoint/connect path is unaffected.

Made with Cursor

Browser journeys run on headless Chromium, which trusts its own NSS
store rather than the system CA store on Linux. As a result, sites
served by an internal/private CA fail with ERR_CERT_AUTHORITY_INVALID
and the only escape hatch was `ignoreHTTPSErrors`, which disables TLS
validation for every endpoint.

Add a `certificateAuthorities` option (Synthetics config and the
`--certificate-authorities` CLI flag, accepting inline PEM or file
paths). The agent computes the SHA-256 SPKI fingerprint of each CA and
launches Chromium with `--ignore-certificate-errors-spki-list`, so only
certificates issued by the provided CAs are trusted while normal
validation still applies to all other endpoints. Inline PEM keeps this
usable on Elastic-managed locations without rebuilding the agent image.

Co-authored-by: Cursor <cursoragent@cursor.com>
Demonstrates the new `certificateAuthorities` option for trusting an
internal CA from a browser monitor, with end-to-end local testing
instructions (throwaway CA + HTTPS server).

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Private certs no longer function on the new Elastic Agent Complete Image for Journeys Support custom cert store

1 participant