Node.js SDK enrollment fails with TLS handshake error — Let's Encrypt + Internal PKI separation

Hi,

I'm deploying OpenZiti with Docker Compose using openziti/ziti-controller and openziti/ziti-router (non-quickstart images). I'm using the Node.js SDK (@openziti/ziti-sdk-nodejs) in an Electron app to enroll a client identity.

Setup:

  • Port 1280 and 8443 open externally

  • Port 6262 is internal Docker only

  • Two DNS names to avoid SAN overlap (as recommended):

    • ziti.certiv.pt → CNAME to server → used for internal PKI/ctrl channel

    • rede.certiv.pt → CNAME to server → used for Let's Encrypt / Edge API

docker-compose.yml:

services:
  ziti-controller:
    image: openziti/ziti-controller:latest
    environment:
      - ZITI_HOME=/ziti-controller
      - ZITI_BOOTSTRAP=true
      - ZITI_BOOTSTRAP_PKI=true
      - ZITI_BOOTSTRAP_CONFIG=true
      - ZITI_BOOTSTRAP_DATABASE=true
    ports:
      - "1280:1280"
      - "6262:6262"
    networks:
      ziti-net:
        aliases:
          - ziti-controller
          - ziti.certiv.pt

  ziti-router:
    image: openziti/ziti-router:latest
    environment:
      - ZITI_CTRL_ADVERTISED_ADDRESS=ziti.certiv.pt
      - ZITI_CTRL_ADVERTISED_PORT=6262
      - ZITI_ROUTER_ADVERTISED_ADDRESS=rede.certiv.pt
      - ZITI_ROUTER_PORT=8443
    ports:
      - "8443:8443"

Relevant config.yml sections:

ctrl:
  options:
    advertiseAddress: tls:ziti.certiv.pt:6262
  listener: tls:0.0.0.0:6262

edge:
  api:
    address: rede.certiv.pt:1280

web:
  - name: client-management
    bindPoints:
      - interface: 0.0.0.0:1280
        address: rede.certiv.pt:1280
    identity:
      alt_server_certs:
        - server_cert: "/etc/letsencrypt/live/rede.certiv.pt/fullchain.pem"
          server_key:  "/etc/letsencrypt/live/rede.certiv.pt/privkey.pem"

Problem:

When I try to enroll using the Node.js SDK, the enrollment fails with a TLS error. The controller logs show:

ERR _context=tls:0.0.0.0:1280 

error=remote error: tls: unknown certificate

msg=handshake failed

The browser also shows NET::ERR_CERT_AUTHORITY_INVALID when accessing https://rede.certiv.pt:1280/zac, which suggests the alt_server_certs with Let's Encrypt is not being picked up correctly.

Questions:

  1. Is the alt_server_certs being ignored because the FQDN rede.certiv.pt overlaps with the PKI somehow?

  2. Should the ctrl.options.advertiseAddress use port 6262 (which is internal only) or 1280?

  3. What does the ctrls field in the enrollment JWT contain for client identities, and does the Node.js SDK actually connect to that address after enrollment?

Thank you!

Hi @franciscoarruda, welcome to the community and to OpenZiti!

Hmmm. To be honest, this makes me think the LE cert IS being served and also makes me think you have shadowed the "external address" of the controller with the LE cert. Overlapping the domain name is a frequent problem and causes non-deterministic behavior. There are multiple discourse posts discussing this problem. First thing to verify is that your controller's "advertised" addresses are always using the private PKI generated by OpenZiti. (assuming you allowed OpenZiti to generate the PKI). You must not shadow the external address. Let's check that first and foremost.

It's totally up to you. 1280 is the "api port" - it's where all the HTTP requests will land by default. 6262 is the "control plane port" and you will need this port exposed if you plan to add another controller or another router. If you want to use SNI, you can set the port to 1280 and then both HTTP and control plane APIs will be served via the same port.

Are you referring to the decoded JWT? That should represent the list of controllers in your OpenZiti overlay network when running in "HA" mode.

Check your advertised address though, this is such a common problem that I expect that's what it is. Oh also make sure you get the latest node sdk, I beleive there have been updates in and around enrollment lately as well. I don't think that's the issue here, but it wouldn't hurt to make sure you're on the latest. hth