Use ziti PKI as intermediate / subordanary ca

I've a private CA running and like to setup the openziti pki as intermediate / subordanary ca from my private pki. Also I like to stay with the quickstart docker-compose setup for now.

Is this setup possible?

Probably not, with the quickstart... The quickstart is very opinionated. It's evolved tremendously since it's original inception but it's still not flexible enough to handle this particular request. Once you start to explore outside of the quickstart for PKI, I'd say you are probably at the point where you kinda need to learn how all the config files pieces come together.

I think you can do what you want, but I wouldn't use the quickstart at this point. Instead, I would say you probably should look at the bare-bones containers that just have the ziti cli in them.

I actually think this would be a fun and interesting Ziti TV to do and it's docs that we need to have. Our Ziti TV schedule is packed for the next few weeks but since this sounds like fun I might just make a one-off video illustrating how to do this. Assuming I'm successful, I'll post it back along with the commands and then I'll put an issue in to get it properly turned into doc/a guide.

But for now, I'd say the answer is "probably not"...

ok Thank you.

A session on Ziti TV or a illustrating video would be cool.
Take your time. I still have a lot of other stuff in the pipeline.

btw I'm looking to move to step-ca in future. The have a cool raspherry setup and also a docker-compose setup.

In case you @TheLumberjack like to do the illustrating video based on the external step-ca ore someone like to try step-ca here is how I got the step-ca up and running.

Create a compose vi dockerfile-compose.yml:

version: '3.9'

    image: smallstep/step-ca:latest
    container_name: step-ca
    hostname: step-ca
      - ./step-ca:/home/step
      # Cert Authority (i.e., PrivateCorp CA). Shown on all issued certs.
      # Comma-separated list of hostnames/IP addresses on which the CA accept requests
      # Provisioner.
      # Password for encrypted CA Keys & Provisioner
      - "DOCKER_STEPCA_INIT_PASSWORD=changeMe$$32782265
    restart: unless-stopped
          - ca.ziti

    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
      - ""
      - ""
      - ./caddy/data/:/data/
      - ./caddy/config/:/config/
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile
          - acme.ziti


Create a Caddy File mkdir caddy; vi caddy/Caddyfile

https://acme.ziti {
  # Inbound TLS connection configuration
  tls {
    # Configure Caddy's internal ACME client
    # to get a server certificate from `step-ca`
    issuer acme {
      dir https://ca.ziti:9000/acme/acme/directory
      trusted_roots /config/root_ca.crt
  # Backend TLS connection to step-ca
  reverse_proxy /acme/acme/* https://ca.ziti:9000 {
    transport http {
      # This allows the proxy to complete a trust
      # chain by trusting step-ca's root.
      tls_trusted_ca_certs /config/root_ca.crt

Strat docker containers docker compose up
Verify the Certs and activate ACME inside the container docker exec -ti step-ca bash:

step ca provisioner list
step ca provisioner add acme --type ACME
step ca provisioner update acme --x509-min-dur=20m --x509-max-dur=2160h --x509-default-dur=2160h     # 90 days lifetime of certs, default 24h

step certificate verify certs/intermediate_ca.crt  --roots certs/root_ca.crt   # empty result means ok
step certificate inspect certs/intermediate_ca.crt  --roots certs/root_ca.crt

Create and verify the intermediate cert for openziti inside the container docker exec -ti step-ca bash:

step certificate create --profile intermediate-ca  --ca ~/certs/root_ca.crt --ca-key ~/secrets/root_ca_key "Ziti Corporation" ziti_ca.crt ziti_ca_key
mv ziti_ca.crt ~/certs/
mv ziti_ca_key ~/secrets/
step certificate inspect certs/ziti_ca.crt  --roots certs/root_ca.crt

Copy the newly created root ca cert to caddy and restart caddy. (During docker compose up the cert will be created.)

cp step-ca/certs/root_ca.crt caddy/config/root_ca.crt
docker restart caddy

For production use this might be interesting:

1 Like

If you like to use a private CA on your ziti console ZAC then extend your quickstart
Add Caddy to your OpenZiti Quickstart compose file vi dockerfile-compose.yml:

    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
      - ""
      #- ""    # zac only accessible via ziti network
      - "443:443"
      - ./caddy/data/:/data/
      - ./caddy/config/:/config/
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile
      - ziti

Create a Caddy File mkdir caddy; vi caddy/Caddyfile. Important: change ctrl.ziti to the name of your controller.

  acme_ca https://acme.ziti/acme/acme/directory
  acme_ca_root /config/root_ca.crt

ctrl.ziti {
  reverse_proxy https://ziti-console:8443 {
    transport http {

Copy / Paste the root ca from step-ca to this server vi caddy/config/root_ca.crt

Openziti Rules. Important: Adapt *.ziti to your ziti identities and the Role AcmeServers to your ca and AcmeClients to your clients. Those Rules will only work for certificates whith the same name as your identity.

ziti edge create config host.v1 '{"protocol":"tcp","address":"acme.ziti", "port":443}\'
ziti edge create config AcmeClientSvc.intercept.v1 intercept.v1 '{"protocols":["tcp"],"addresses":["acme.ziti"], "portRanges":[{"low":443, "high":443}]}'
ziti edge create service AcmeClientSvc --configs AcmeClientSvc.intercept.v1,
ziti edge create service-policy AcmeClientSvc.policy.bind Bind --service-roles "@AcmeClientSvc" --identity-roles "#AcmeServers"
ziti edge create service-policy AcmeClientSvc.policy.dial Dial --service-roles "@AcmeClientSvc" --identity-roles "#AcmeClients"

ziti edge create config host.v1 '{"protocol":"tcp","address":"", "port":80, "listenOptions":{"bindUsingEdgeIdentity":true} }'
ziti edge create config AcmeServerHttpSvc.intercept.v1 intercept.v1 '{"protocols":["tcp"],"addresses":["*.ziti"], "portRanges":[{"low":80, "high":80}], "dialOptions":{"identity":"$dst_hostname"}}'
ziti edge create service AcmeServerHttpSvc --configs AcmeServerHttpSvc.intercept.v1,
ziti edge create service-policy AcmeServerHttpSvc.policy.bind Bind --service-roles "@AcmeServerHttpSvc" --identity-roles "#AcmeClients"
ziti edge create service-policy AcmeServerHttpSvc.policy.dial Dial --service-roles "@AcmeServerHttpSvc" --identity-roles "#AcmeServers"

ziti edge create config host.v1 '{"protocol":"tcp","address":"", "port":443, "listenOptions":{"bindUsingEdgeIdentity":true} }'
ziti edge create config AcmeServerAlpnSvc.intercept.v1 intercept.v1 '{"protocols":["tcp"],"addresses":["*.ziti"], "portRanges":[{"low":443, "high":443}], "dialOptions":{"identity":"$dst_hostname"}}'
ziti edge create service AcmeServerAlpnSvc --configs AcmeServerAlpnSvc.intercept.v1,
ziti edge create service-policy AcmeServerAlpnSvc.policy.bind Bind --service-roles "@AcmeServerAlpnSvc" --identity-roles "#AcmeClients"
ziti edge create service-policy AcmeServerAlpnSvc.policy.dial Dial --service-roles "@AcmeServerAlpnSvc" --identity-roles "#AcmeServers"

Add an DNS entry on your CA Server for acme.ziti in your main dns or on the localhost in case you use the linux tunneler and not the docker tunneler. vi /etc/hosts localhost acme.ziti

Add your public certificate to your Client mashine. In case of Fedora:

sudo vim /etc/pki/ca-trust/source/anchors/root_ca.ziti.pem  # copy / paste the cert
sudo update-ca-trust
# restart your browser
1 Like

Hi @Metz, just checking in. I saw you made a bunch of progress on this thread. Do you still need to figure out how to use an independently provisioned CA with your network? I'm happy to produce a super quick video showing you how if you would like it and still need it.


@TheLumberjack, I saw your Video from the other post What does the quickstart do that I need to do myself? - #4 by TheLumberjack
Looks like here you have already answered a lot of my questions.

I was busy until know with other stuff but will for sure test the setup this week or next week.

Ok cool. I thought it would cover much/most of what you needed. The one bit of "magic" you will want is in the script I was using to make sure the instructions work here

The most important lines, to "import" your key/cert to the ZITI_PKI location, would be these:

export ZITI_HOME="/opt/ziti/quickstart"
export ZITI_PKI="${ZITI_HOME}/pki"



Then, when you run your ziti pki commands, you supply THAT as the --ca-name, as shown in that video. For example, generating an intermediate for the network-components/control plane would be something like:

ziti pki create intermediate \
  --pki-root="${ZITI_PKI}" \
  --intermediate-name "${ZITI_CTRL_CA_NAME}" \
  --intermediate-file "${ZITI_CTRL_CA_NAME}" \
  --max-path-len "1"

I think that'll probably give you all you need, but if you want to refer to that script for a 'full example', it's there. Let me know if you need more help! :slight_smile: