diff --git a/README.md b/README.md
index 56930283eb..417482ee24 100644
--- a/README.md
+++ b/README.md
@@ -35,8 +35,10 @@ In order to render and preview the site locally (without docker) you will need a
1) You will need to install python and pip
2) After python is installed, you'll need the following python dependencies:
-`pip install mkdocs`
-`pip install mkdocs-material==8.2.1`
+* `pip install mkdocs`
+* `pip install mkdocs-material==8.2.1`
+* `pip install mkdocs-swagger-ui-tag`
+* 'pip install mkdocs-macros-plugin'
3) Once you have all the pre-reqs installed. You can simply run `mkdocs serve` and view the rendered content locally and makes changes to your documentation and preview them in realtime with a browser at http://0.0.0.0:8001/edgex-docs.
diff --git a/docs_src/security/edgex-openziti.drawio b/docs_src/security/edgex-openziti.drawio
new file mode 100644
index 0000000000..b1f99f45f1
--- /dev/null
+++ b/docs_src/security/edgex-openziti.drawio
@@ -0,0 +1,453 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs_src/security/edgex-openziti.png b/docs_src/security/edgex-openziti.png
new file mode 100644
index 0000000000..8593097fa0
Binary files /dev/null and b/docs_src/security/edgex-openziti.png differ
diff --git a/docs_src/security/external-openziti.png b/docs_src/security/external-openziti.png
new file mode 100644
index 0000000000..428b44dcd5
Binary files /dev/null and b/docs_src/security/external-openziti.png differ
diff --git a/docs_src/security/zero-trust.md b/docs_src/security/zero-trust.md
new file mode 100644
index 0000000000..21d8d5d50e
--- /dev/null
+++ b/docs_src/security/zero-trust.md
@@ -0,0 +1,597 @@
+# Zero Trust
+
+## Introduction
+
+Zero Trust is a set of security strategies centered around the fundamental principle that no network is safe. There are
+numerous vendors, all using the "zero trust" adjective to describe their security solutions. At the core of zero trust
+are a few fundamental principles:
+
+* Explicit Authorization: connections should be authenticated before traffic can be sent
+* Cryptographically Verifiable Identities: connections should be authenticated using strong identities
+* Least Privilege Access: identities should have access only the minimum set of services they require
+* Continual Authentication: authorized connections should be able to be revoked immediately and monitored continuously
+
+With version 3.2+, EdgeX Foundry can now secure its core services with a zero trust configuration. EdgeX Foundry has
+integrated with [OpenZiti](https://openziti.io) to provide secure, zero trust connectivity among the EdgeX provided
+services based on go. This includes the core services, support services, application services and numerous device
+services.
+
+OpenZiti also offers a novel approach of embedding these zero trust concepts directly into services through SDKs. By
+adopting OpenZiti, EdgeX Foundry core services **no longer have ports exposed to the IP-based underlay network at all**.
+After enabling zero trust support, the EdgeX Foundry services will not be discoverable nor attackable by standard
+IP-based tooling whatsoever. Any attacks will first need to be authenticated into the OpenZiti overlay network and then
+also authorized to make connections to the relevant EdgeX Foundry service.
+
+## About OpenZiti
+
+OpenZiti is an open source project focused on bringing zero trust networking principles directly into any application.
+It accomplishes this by providing [numerous SDKs](https://openziti.io/docs/reference/developer/sdk/) that can be
+integrated into any application. EdgeX Foundry has integrated the [golang sdk](https://github.com/openziti/sdk-golang/)
+from the OpenZiti project, enabling secure connectivity through an OpenZiti overlay mesh network. OpenZiti also provides
+clients for all major desktop and mobile operating systems that allow applications which are not OpenZiti-enabled to
+access the overlay network called tunnelers. If an OpenZiti SDK cannot be integrated into an application that needs to
+connect to services secured via OpenZiti, such as the core EdgeX Foundry services, these clients can be used to provide
+access. These applications are known as tunnelers.
+
+An OpenZiti overlay network consists of a controller and edge routers. The controller, as it sounds, is responsible for
+decisions surrounding the overlay network such as authentication, authorization, management of the overlay, etc. Edge
+Routers are responsible for creating the overlay mesh network. One or more can link together to form a fully redundant
+mesh and routers may be assigned specific roles as needed. The full scope of what OpenZiti is and how to use it is
+impossible to document succinctly here. To learn how OpenZiti works or for additional information, please visit the
+official docs site at https://openziti.io/docs and for help, ask in the official support forum:
+https://openziti.discourse.group/.
+
+## Integrating EdgeX Foundry With OpenZiti
+
+EdgeX has adopted and integrated OpenZiti into it's microservice architecture and can now be enabled through
+standard EdgeX configuration mechanisms. In order to build a zero trust overlay network usable by EdgeX, an OpenZiti
+overlay network will need to be available and configured. The EdgeX project provides a docker compose file that deploys
+a full EdgeX Foundry install and can be optionally enabled with an OpenZiti, zero trust overlay network. If you are
+already familiar with deploying EdgeX Foundry through the existing docker compose mechanism you will be able to
+enable this additional security mechanism in the same way.
+
+#### Overview of EdgeX Foundry integrated with OpenZiti
+![Overview of EdgeX Foundry integrated with OpenZiti](edgex-openziti.png)
+
+## Securing EdgeX Services With OpenZiti
+
+When running in secure mode, EdgeX Foundry creates and uses strong identities in the form of JWTs for services.
+When enabling zero trust access, those strong identities are used to authenticate to the OpenZiti overlay network by
+leveraging a feature of OpenZiti called "external JWT signers". OpenZiti can be configured to trust other
+authorities, such as the token provider EdgeX Foundry uses (Vault as of July 12, soon to be OpenBao). Once the
+appropriate trust is configured, OpenZiti can use the strong identities created by another authority for
+authentication to the overlay network. This is how "external JWT signers" operate. In order for OpenZiti to be able
+to verify authentication tokens from the external provider, the JWT provider must be addressable by the OpenZiti
+controller as it will use the JWKS endpoint provided by token provider in order to verify the token.
+
+The OpenZiti external JWT signer must be configured with an expected claim which will be contained within the JWT.
+As part of the authentication process with OpenZiti, after the JWT is verified as authentic, the configured claim
+will be inspected and a corresponding OpenZiti must exist with an "external id" set that matches the value of this
+claim.
+
+### Example JWT Verification With OpenZiti
+
+Let's look at an example verification process to understand better how the JWT from the token provider is used to
+authenticate to the OpenZiti overlay.
+
+1. After the EdgeX Foundry security bootstrapper completes, a token from the token provider for the target service
+ will be available to the service.
+2. The token is read into memory.
+3. The token is exchanged with the token provider for a JWT. Here is a representative payload section of a JWT
+ delivered to the core-command service:
+
+ {
+ "aud": "edgex",
+ "exp": 1720809046,
+ "iat": 1720808146,
+ "iss": "/v1/identity/oidc",
+ "name": "core-command",
+ "namespace": "root",
+ "sub": "790fd597-f773-21a6-158f-ee1158875115"
+ }
+
+ Notice the fields contained within the JWT payload, both the issuer (`iss`) field `name` field are important and
+ are used in the OpenZiti `ext-jwt-signer` configuration. The `iss` field must match a configured `ext-jwt-signer`.
+4. The JWT is sent to OpenZiti for verification.
+5. OpenZiti makes a request to the token provider's JWKS endpoint (as needed) to obtain the necessary key material
+ to verify the authenticity of the JWT.
+6. Once the JWT is verified as authentic, the configured field (in this case `name`) read from the JWT.
+7. OpenZiti scans all identities for one with an `--externalId` set to the value from the JWT (in this case
+ `core-command`)
+8. If an identity is found with an associated auth policy utilizing the expected value (`core-command`), the
+ identity is considered authenticated.
+
+### Authorizing Access to EdgeX Foundry Services
+
+OpenZiti is also now able to authorize connections to EdgeX Foundry services and when configured to operate in with
+the zero trust security model, this is how service authorization works. With zero trust enabled, traffic for any
+particular service must be delivered, authenticated, and authorized by the OpenZiti overlay network. It is no longer
+necessary to also provide a bearer token to the service.
+
+This allows other, non-EdgeX Foundry services or clients to access services without passing a bearer token to the
+service. Instead, those clients will be required to have a strong identity and be authenticated and authorized by
+the zero trust overlay network. OpenZiti does not prevent services from implementing other, additional
+authentication mechanisms. If a service provides an HTTP server and also requires ancillary authentication in the
+form of username/password, bearer token, etc., these additional mechanisms maybe be applied by service authors. The
+EdgeX Foundry services will not require additional authentication when operating in the zero trust security model.
+
+## Listing of EdgeX Foundry ←→ OpenZiti Services
+| EdgeX Foundry Service | OpenZiti Service Name | OpenZiti Configured Intercept Address | Port | Service Attribute |
+|-----------------------|-|---------------------------------------|------|-------------------|
+| core-command | edgex.core-command | core-command.edgex.ziti | 80 | core.svc |
+| core-data | edgex.core-data | core-data.edgex.ziti | 80 | core.svc |
+| core-metadata | edgex.core-metadata | core-metadata.edgex.ziti | 80 | core.svc |
+| ui | edgex.ui | ui.edgex.ziti | 80 | core.svc |
+| rules-engine | edgex.rules-engine | rules-engine.edgex.ziti | 80 | support.svc |
+| support-notifications | edgex.support-notifications | support-notifications.edgex.ziti | 80 | support.svc |
+| support-scheduler | edgex.support-scheduler | support-scheduler.edgex.ziti | 80 | support.svc |
+| device-bacnet-ip | edgex.device-bacnet-ip | device-bacnet-ip.edgex.ziti | 80 | device.svc |
+| device-coap | edgex.device-coap | device-coap.edgex.ziti | 80 | device.svc |
+| device-gpio | edgex.device-gpio | device-gpio.edgex.ziti | 80 | device.svc |
+| device-modbus | edgex.device-modbus | device-modbus.edgex.ziti | 80 | device.svc |
+| device-mqtt | edgex.device-mqtt | device-mqtt.edgex.ziti | 80 | device.svc |
+| device-onvif-camera | edgex.device-onvif-camera | device-onvif-camera.edgex.ziti | 80 | device.svc |
+| device-rest | edgex.device-rest | device-rest.edgex.ziti | 80 | device.svc |
+| device-rfid-llrp | edgex.device-rfid-llrp | device-rfid-llrp.edgex.ziti | 80 | device.svc |
+| device-snmp | edgex.device-snmp | device-snmp.edgex.ziti | 80 | device.svc |
+| device-uart | edgex.device-uart | device-uart.edgex.ziti | 80 | device.svc |
+| device-usb-camera | edgex.device-usb-camera | device-usb-camera.edgex.ziti | 80 | device.svc |
+| device-virtual | edgex.device-virtual | device-virtual.edgex.ziti | 80 | device.svc |
+| app-external-mqtt-trigger | edgex.app-external-mqtt-trigger | app-external-mqtt-trigger.edgex.ziti | 80 | application.svc |
+| app-http-export | edgex.app-http-export | app-http-export.edgex.ziti | 80 | application.svc |
+| app-metrics-influxdb | edgex.app-metrics-influxdb | app-metrics-influxdb.edgex.ziti | 80 | application.svc |
+| app-mqtt-export | edgex.app-mqtt-export | app-mqtt-export.edgex.ziti | 80 | application.svc |
+| app-rfid-llrp-inventory | edgex.app-rfid-llrp-inventory | app-rfid-llrp-inventory.edgex.ziti | 80 | application.svc |
+| app-rules-engine | edgex.app-rules-engine | app-rules-engine.edgex.ziti | 80 | application.svc |
+| app-record-replay | edgex.app-record-replay | app-record-replay.edgex.ziti | 80 | application.svc |
+| app-sample | edgex.app-sample | app-sample.edgex.ziti | 80 | application.svc |
+
+## Granting Access To Services
+
+As shown in the section above, there are essentially four groupings of services:
+* core services, using the `core.svc` attribute
+* support services, using the `support.svc` attribute
+* device services, using the `device.svc` attribute
+* application services, using the `application.svc` attribute
+
+The initialization script will also create OpenZiti service-policy entries authorizing identities to bind services
+if the service is configured as a server and also authorizing identities which need to access identities acting as a
+server. Policies authorizing bind should all named with a `.bind` suffix as in: `edgex.core-metadata.bind`.
+Similarly, policies will be generated ending with `.dial` to authorize identities to dial services.
+
+Generally there are likely to be fewer identities needing to bind services. Instead, it's more common to authorize
+identities to dial services. It's worthwhile to take note that the dial policies leverage attributes, allowing
+identities to be authorized to dial services easily. When creating an identity, assign the appropriate attribute to
+and it will be automatically authorized to dial the related services.
+
+## Controlling Access Through Policies
+
+OpenZiti has a flexible authorization model based on policies. In OpenZiti, these are known as
+[Service Policies](https://openziti.io/docs/learn/core-concepts/security/authorization/policies/overview/). Service
+policies allow the operator of the overlay network to authorize individual identities or groups of identities to
+access services, also by name or by grouping.
+
+When EdgeX Foundry is configured using the `openziti-init-entrypoint.sh` script, it will precreate all the services for
+the default EdgeX Foundry services, regardless whether you use these services or not. That way, should you add an
+optional service later on, the OpenZiti overlay network will likely have an existing service already.
+
+When initializing the OpenZiti overlay for EdgeX Foundry, an initial set of services will be configured in the
+OpenZiti overlay network. Below is an incomplete listing of those services For the most complete table, refer to the
+`openziti-init-entrypoint.sh` script itself.
+
+### Example Authorizing Dial
+To better understand how authorizing an identity to dial a service works, imagine a new device service is being
+developed. Looking at the `edgex.device.id.dial` service policy with the `ziti` CLI, we can see that device services
+(identities with the `#device.id` attribute) are authorized to access `core-metadata`:
+```
+$ ziti edge list service-policies 'name contains "device.id" sort by name limit none'
+╭────────────────────────┬──────────────────────┬──────────┬──────────────────────┬────────────────┬─────────────────────╮
+│ ID │ NAME │ SEMANTIC │ SERVICE ROLES │ IDENTITY ROLES │ POSTURE CHECK ROLES │
+├────────────────────────┼──────────────────────┼──────────┼──────────────────────┼────────────────┼─────────────────────┤
+│ 4lj78qsu9ffrPxsLgs2LEq │ edgex.device.id.dial │ AnyOf │ @edgex.core-metadata │ #device.id │ │
+╰────────────────────────┴──────────────────────┴──────────┴──────────────────────┴────────────────┴─────────────────────╯
+results: 1-1 of 1
+```
+
+When developing a new device service, when creating the identity we should add the `#device.id` attribute to
+authorize this service to connect to `core-metadata`.
+
+## Accessing EdgeX Services OpenZiti From The Network
+
+As described above, EdgeX Foundry services protected by OpenZiti have no listening ports. These services are not
+discoverable by IP-based underlay probes and are not accessible by IP-based tooling without an adapting layer.
+OpenZiti provides software called ["tunnelers"](https://openziti.io/docs/reference/tunnelers/) that adapt classic,
+IP-based, underlay traffic to and from the OpenZiti overlay network. These tunnelers are provided for all major
+desktop and mobile operating systems. These tunnelers are purpose-built applications leveraging the suite of
+[OpenZiti SDKs](https://openziti.io/docs/reference/developer/sdk/) mainly to do one thing: convert IP-based underlay
+traffic to OpenZiti overlay traffic and OpenZiti overlay traffic to IP-based underlay traffic.
+
+When using OpenZiti to add zero trust principles to EdgeX Foundry services, in order to access those services using
+tools that are not integrated with OpenZiti (classic, IP-based tools), one will need to have a tunneler installed to
+adapt the IP traffic to OpenZiti traffic. Accordingly, the tunneler requires a valid identity exists, authorizing
+the tunneler to access the desired services. To add an identity to a tunneler, read about
+[enrolling an identity](https://openziti.io/docs/learn/core-concepts/identities/enrolling/#enrolling-an-identity).
+
+### Intercepting Services
+With the tunneler deployed and authenticated with an OpenZiti overlay network, traffic local to that operating
+system will be able to be sent to the target service. Accessing services with a tunneler, requires additional
+"intercept" configurations. These intercept configurations are used to instruct the tunneler to adapt the host
+operating system in a way that send the IP-based underlay traffic to the tunneler, where it is converted to OpenZiti
+overlay traffic and sent to the target service.
+
+The `openziti-init-entrypoint.sh` script configures each service that acts as a server with an `intercept` config,
+making it easy for an identity with authorization to access the service. The configs all follow the
+pattern of `edgex.${service_name}.intercept` and have a configured intercept of `${service_name}.edgex.ziti`.
+Should [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) configuration be needed, it's important
+to have a predictable top-level domain name, so the intercepts all end with `.edgex.ziti`. Configs can be seen by
+reading the script or by enumerating them with the `ziti` CLI tool.
+
+### Summary of Configured Intercepts
+
+| Service Name | Intercept Name | Intercept Address |
+|--------------|----------------|-------------------|
+| app-external-mqtt-trigger | edgex.app-external-mqtt-trigger.intercept | app-external-mqtt-trigger.edgex.ziti |
+| app-http-export | edgex.app-http-export.intercept | app-http-export.edgex.ziti |
+| app-metrics-influxdb | edgex.app-metrics-influxdb.intercept | app-metrics-influxdb.edgex.ziti |
+| app-mqtt-export | edgex.app-mqtt-export.intercept | app-mqtt-export.edgex.ziti |
+| app-record-replay | edgex.app-record-replay.intercept | app-record-replay.edgex.ziti |
+| app-rfid-llrp-inventory | edgex.app-rfid-llrp-inventory.intercept | app-rfid-llrp-inventory.edgex.ziti |
+| app-rules-engine | edgex.app-rules-engine.intercept | app-rules-engine.edgex.ziti |
+| app-sample | edgex.app-sample.intercept | app-sample.edgex.ziti |
+| core-command | edgex.core-command.intercept | core-command.edgex.ziti |
+| core-data | edgex.core-data.intercept | core-data.edgex.ziti |
+| core-metadata | edgex.core-metadata.intercept | core-metadata.edgex.ziti |
+| device-bacnet-ip | edgex.device-bacnet-ip.intercept | device-bacnet-ip.edgex.ziti |
+| device-coap | edgex.device-coap.intercept | device-coap.edgex.ziti |
+| device-gpio | edgex.device-gpio.intercept | device-gpio.edgex.ziti |
+| device-modbus | edgex.device-modbus.intercept | device-modbus.edgex.ziti |
+| device-mqtt | edgex.device-mqtt.intercept | device-mqtt.edgex.ziti |
+| device-onvif-camera | edgex.device-onvif-camera.intercept | device-onvif-camera.edgex.ziti |
+| device-rest | edgex.device-rest.intercept | device-rest.edgex.ziti |
+| device-rfid-llrp | edgex.device-rfid-llrp.intercept | device-rfid-llrp.edgex.ziti |
+| device-snmp | edgex.device-snmp.intercept | device-snmp.edgex.ziti |
+| device-uart | edgex.device-uart.intercept | device-uart.edgex.ziti |
+| device-usb-camera | edgex.device-usb-camera.intercept | device-usb-camera.edgex.ziti |
+| device-virtual | edgex.device-virtual.intercept | device-virtual.edgex.ziti |
+| rules-engine | edgex.rules-engine.intercept | rules-engine.edgex.ziti |
+| support-notifications | edgex.support-notifications.intercept | support-notifications.edgex.ziti |
+| support-scheduler | edgex.support-scheduler.intercept | support-scheduler.edgex.ziti |
+| ui | edgex.ui.intercept | ui.edgex.ziti |
+
+## Healthcheck Proxy
+
+A great example of an IP-based underlay service that needs access to EdgeX Foundry services is consul. Consul is
+configured to check the health of services but consul is not OpenZiti-enabled and requires IP-based underlay access
+in order to perform health checks.
+
+When running in zero trust mode, each service provides the intercept value for the service as input to the consul
+configuration. When a new service is registered in consul and the healthcheck starts, it will only understand how to
+make a classic, IP-based connection to the endpoint which will not succeed.
+
+To allow consul health checks to succeed another container needs to be deployed. A new container is deployed when
+running in zero trust mode found from ghcr.io/openziti-test-kitchen/healthcheck-proxy/healthcheck-proxy:latest. Read
+the [README.md](https://github.com/openziti-test-kitchen/healthcheck-proxy) to learn more about how to configure it.
+This container starts up and waits for an identity file to be provided to it (which is done during the
+initialization phase by the `openziti-init-entrypoint.sh` script), authorizing the container to make requests to
+EdgeX Foundry services. This identity is created with the `#edgex-healthchecker` role which authorizes the identity
+to connect to all services. The proxy is then configured to only allow specific requests through. By default, the
+proxy will only allow `GET` requests to urls ending with `/ping` and only hostnames ending with `.edgex.ziti` on
+port 80.
+
+In order to capture these requests, the container leverages the docker network it's part of and has an alias
+assigned for each intercept (all preconfigured).
+
+## Operating an External OpenZiti Overlay Network
+
+It's common to expose an OpenZiti overlay network to the internet. An OpenZiti overlay deployed on the internet allows
+identities to connect to the OpenZiti overlay from anywhere. Once connected to the overlay, identities authorized to
+access EdgeX Foundry services are then able to access those services securely from anywhere. EdgeX Foundry supports
+operating an externally managed OpenZiti overlay network, but it does require additional setup steps. Most notably,
+the OpenZiti overlay network will need to be able to communicate to the token provider in order to verify the JWT
+tokens provided during authentication to the network.
+
+![Visual Representation of an External OpenZiti Overlay](external-openziti.png)
+
+Here is an overview of the additional steps required to integrate an EdgeX Foundry deployment with an external
+OpenZiti overlay network:
+* Set up the OpenZiti overlay network
+* Configure the OpenZiti overlay network to tunnel requests to token provider. You could alternatively expose the
+ token provider directly to the internet, but if you have an OpenZiti overlay network available that seems like a
+ bad decision
+* Configure the OpenZiti overlay for EdgeX Foundry support
+* Enable zero trust mode on EdgeX Foundry services
+
+### Set Up the OpenZiti Overlay Network
+Setting up and maintaining an OpenZiti overlay is too big a topic to include here. Refer to the OpenZiti
+documentation for installation and maintenance. Optionally, if available find a vendor to setup and maintain the
+OpenZiti overlay network on your behalf. Currently, NetFoundry can host an OpenZiti overlay for use with EdgeX Foundry
+see: https://nfconsole.io/pricing
+
+## Configure the OpenZiti Overlay for the Token Provider
+Assuming the token provider will be running in your own private networking space, the OpenZiti overlay will need
+to be able to communicate to it to verify JWTs. This will require a tunneler to exist on or near the OpenZiti
+controller to provide the necessary access. An easy way to accomplish this is to deploy a router near the controller
+and enable it for `tproxy` mode. This will allow the router to adapt IP-based underlay traffic from the controller
+back into the overlay network, eventually sending traffic to the token provider.
+
+These steps below will expect this topology. The edge router near/colocated with the controller will be configured
+with `tproxy` support, and it will tunnel traffic to the token provider.
+
+### Set Some Variables In Your Shell
+Correctly set the following variables for your OpenZiti overlay in your shell for use with the commands below.
+Either set the variables directly, create and source a file with these answers, or simply replace them in the
+commands as you see fit. However, if you choose to set these variables, you should be able to copy/paste the other
+commands easier
+
+**Variables to set:**
+```
+ZITI_USER=
+ZITI_PWD=
+OPENZITI_ADVERTISED_ADDRESS="your.openziti.ctrl.example.com"
+OPENZITI_ADVERTISED_PORT=1280
+OPENZITI_CONTROL_PORT=6262
+OPENZITI_PERSISTENCE_PATH="/edgex_openziti"
+OPENZITI_EDGEX_ROUTER_NAME="edgex.router"
+OPENZITI_CONTROLLER_ROUTER_NAME="ip-172-31-47-200-edge-router"
+EDGEX_TOKEN_PROVIDER_HOST=edgex-vault
+```
+
+**Variable Explanations:**
+
+* **ZITI_USER:** The user that has administrative permission to the OpenZiti overlay. In order to create
+ configuration, you will need to be administrator.
+* **ZITI_PWD:** The password for the user with admin priviliges to the OpenZiti overlay.
+* **OPENZITI_ADVERTISED_ADDRESS:** The externally addressable endpoint of the OpenZiti controller management API.
+* **OPENZITI_ADVERTISED_PORT:** The port assigned to the OpenZiti management API.
+* **OPENZITI_CONTROL_PORT:** The control plane port assigned to the OpenZiti control plane. This is the port routers
+ will use to connect to the controller.
+* **OPENZITI_EDGEX_ROUTER_NAME:** The name of the router to create within the docker network the rest of EdgeX
+ Foundry dependencies and services reside. This router will offload requests from the OpenZiti controller to the
+ token provider.
+* **OPENZITI_CONTROLLER_ROUTER_NAME:** The name of the router to create near the controller. This router will be
+ responsible for tunneling token provider requests to the token provider.
+* **OPENZITI_PERSISTENCE_PATH:** The path within docker where the healthcheck proxy is expected to be mounted. An
+ identity will be provisioned and copied into docker at this location, allowing the healthcheck-proxy to proxy
+ healthchecks from consul. Generally speaking this will be `/edgex_openziti`
+* **EDGEX_TOKEN_PROVIDER_HOST:** The address, relative to the router near the EdgeX Foundry services where the token
+ provider endpoints can be reached
+
+### Create or Adapt the Router for the OpenZiti Controller
+
+Configure a router adjacent to the OpenZiti controller with the ability to tunnel and operating in `tproxy` mode.
+Ensure the router was created with tunneling enabled. This complex looking command will use the `ziti` CLI to query
+the OpenZiti overlay for the router near the controller, send the output as json to `jq` and find the
+`isTunnelerEnabled` value.
+
+**Example illustrating the router is tunneler-enabled:**
+```
+ziti edge list ers 'name = "'"${OPENZITI_CONTROLLER_ROUTER_NAME}"'"' -j | jq -r '.data[].isTunnelerEnabled'
+true
+```
+
+After confirming the router is enabled for tunneling, locate the config file and verify a listener is declared with
+the `tunnel` binding and with the `mode` `option` set to `tproxy` as shown:
+```
+listeners:
+...
+ - binding: tunnel
+ options:
+ mode: tproxy
+```
+
+If editing the file was needed, restart the router to pick up the configuration change.
+
+### Create a Router Near EdgeX Foundry
+
+Connections and data will need to be tunneled from the public OpenZiti overlay to the private networking space where
+the token provider is deployed - generally within docker. Add an OpenZiti router to your docker compose project. It
+should look something like this:
+```
+ openziti-router:
+ container_name: edgex-openziti-router
+ image: openziti/ziti-cli:1.1.4
+ env_file:
+ - .env
+ environment:
+ PFXLOG_NO_JSON: "${PFXLOG_NO_JSON:-true}"
+ entrypoint: /openziti-router-entrypoint.sh
+ command: run "\${HOME}/\${OPENZITI_EDGEX_ROUTER_NAME}.yml"
+ volumes:
+ - edgex_openziti:/edgex_openziti
+ - $PWD/openziti-router-entrypoint.sh:/openziti-router-entrypoint.sh
+# ports:
+# - ${ZITI_INTERFACE:-0.0.0.0}:${ZITI_ROUTER_PORT:-3022}:${ZITI_ROUTER_PORT:-3022}
+ restart: unless-stopped
+ networks:
+ edgex-network:
+```
+
+Reviewing the docker-compose service shown above there are some things to take note of.
+* the container uses an entrypoint script found in the `edge-compose` project. Clone the repo or download it from
+ GitHub [here](https://raw.githubusercontent.com/edgexfoundry/edgex-compose/main/openziti-router-entrypoint.sh)
+* The router is set to use 'human legible' logging by setting `PFXLOG_NO_JSON: "${PFXLOG_NO_JSON:-true}"`. Remove
+ this to use json-based logging, which is better for log-shipping
+* The router service mounts a volume named `edgex_openziti` as the path `/edgex_openziti`. This is where configuration
+ files will be stored
+* The router service mounts the entrypoint script from your current directory. Set this accordingly or ensure you
+ start the router from a folder with the `openziti-router-entrypoint.sh` within it
+* the `ports` section is commented out. If you would like to use this router for local zero trust connections, you
+ would uncomment this block and set the exposed port accordingly
+* The container uses an `env_file`. The `env_file` is expected to have the following values set properly:
+ ```
+ ZITI_USER=
+ ZITI_PWD=
+
+ OPENZITI_ADVERTISED_ADDRESS="your.openziti.ctrl.example.com"
+ OPENZITI_ADVERTISED_PORT=1280
+ OPENZITI_CONTROL_PORT=6262
+ OPENZITI_ADVERTISED_ADDRESS_PORT=${OPENZITI_ADVERTISED_ADDRESS}:${OPENZITI_ADVERTISED_PORT}
+
+ OPENZITI_EDGEX_ROUTER_NAME="edgex.router"
+ OPENZITI_CONTROLLER_ROUTER_NAME="ip-172-31-47-200-edge-router"
+
+ OPENZITI_PERSISTENCE_PATH=/edgex_openziti
+ EDGEX_TOKEN_PROVIDER_HOST="edgex-vault"
+ ```
+
+Start the OpenZiti router service using docker. When it first starts, the entrypoint script will wait for an
+enrollment token to be mounted into the container at `/home/ziggy/edgex.router.jwt`. You will see this message
+repeated until the token is put in place:
+```
+waiting for router enrollment... please mount or copy the router's jwt to: /home/ziggy/edgex.router.jwt
+waiting for router enrollment... please mount or copy the router's jwt to: /home/ziggy/edgex.router.jwt
+waiting for router enrollment... please mount or copy the router's jwt to: /home/ziggy/edgex.router.jwt
+```
+
+Once the token is in place, the entrypoint script will enroll the router with the OpenZiti overlay network and then
+start the process router process.
+
+To create the OpenZiti router for use with EdgeX Foundry, perform the following steps as the OpenZiti administrator:
+
+```
+ziti edge login "${OPENZITI_ADVERTISED_ADDRESS}:${OPENZITI_ADVERTISED_PORT}" -u "${ZITI_USER}" -p "${ZITI_PWD}" -y
+ziti edge create edge-router ${OPENZITI_EDGEX_ROUTER_NAME} -t -o ${OPENZITI_EDGEX_ROUTER_NAME}.jwt
+```
+
+With the router created in the OpenZiti overlay, copy the enrollment token into the container at the specified
+location and ensure the `ziggy` user/group owns the token:
+```
+docker compose \
+ -f docker-compose-zero-trust-just-deps.yml \
+ cp ${OPENZITI_EDGEX_ROUTER_NAME}.jwt \
+ openziti-router:/home/ziggy/${OPENZITI_EDGEX_ROUTER_NAME}.jwt
+
+docker compose exec --user root openziti-router chown ziggy:ziggy /home/ziggy/${OPENZITI_EDGEX_ROUTER_NAME}.jwt
+```
+
+Finally, remove the enrollment token from your local filesystem. While only good for one use, it's useful to clean
+it up.
+```
+rm ${OPENZITI_EDGEX_ROUTER_NAME}.jwt
+```
+
+### Configure OpenZiti to Allow Token Provider Tunneling
+
+With the OpenZiti topology in place, the OpenZiti overlay can now be configured to allow the controller to tunnel
+token provider requests. This is a standard OpenZiti configuration task as the overlay network is deployed. Here,
+you will:
+* Create an "intercept" config, allowing the controller to send IP-based underlay traffic to a predefined url
+ that represents the token provider via the ${OPENZITI_CONTROLLER_ROUTER_NAME} router. Notice the `addresses` field
+ and `portRanges`. Whatever values are entered here must match the same values used to initialize the OpenZiti
+ overlay network. This example uses the values from the `openziti-init-entrypoint.sh`
+
+ ziti edge create config "edgex.token-provider.intercept" intercept.v1 \
+ '{"protocols":["tcp"],"addresses":["token-provider.edgex.ziti"], "portRanges":[{"low":8200, "high":8200}]}'
+
+* Create a "host" config, allowing the ${OPENZITI_EDGEX_ROUTER_NAME} router to offload traffic from the OpenZiti
+ overlay network back to the IP-based underlay network. Notice the address and port here must be accurate relative
+ to where the router near the EdgeX Foundry services are deployed. The
+ [container name](https://github.com/edgexfoundry/edgex-compose/blob/main/docker-compose.yml#L1305) or the
+ containers `network alias` should be used for
+
+ ziti edge create config "edgex.token-provider.host" host.v1 \
+ '{"protocol":"tcp", "address":"edgex-vault","port":8200}'
+
+* Create an OpenZiti service representing the path from OpenZiti controller to token provider
+
+ ziti edge create service edgex.token-provider \
+ --configs edgex.token-provider.intercept,edgex.token-provider.host \
+ -a 'edgex.token-provider'
+
+* Authorize the router near the OpenZiti controller to connect to the token provider by creating a `Dial`
+ `service-policy`. Notice this is done using the attribute of `#edgex.token-provider.dialers`.
+ Any identity with this attribute will be authorized to dial the `@edgex.token-provider` service
+
+ ziti edge create service-policy edgex.token-provider.dial Dial \
+ --identity-roles '#edgex.token-provider.dialers' \
+ --service-roles @edgex.token-provider \
+ --semantic "AnyOf"
+
+* Authorize the router near the EdgeX Foundry services to offload traffic towards the IP-based token provider
+ service by creating a `Bial` `service-policy`. Notice this is done using the attribute of `#edgex.token-provider.binders`.
+ Any identity with this attribute will be authorized to bind the `@edgex.token-provider` service
+
+ ziti edge create service-policy edgex.token-provider.bind Bind \
+ --identity-roles '#edgex.token-provider.binders' \
+ --service-roles @edgex.token-provider \
+ --semantic "AnyOf"
+
+* It will likely be necessary to update the router identities, authorizing them to bind and dial the service. Notice
+ here the router near the OpenZiti Controller is marked as `public` as well as `edgex.token-provider.dialers`. This is
+ because it's likely the router near the controller is being used as a public router for any edge connections.
+
+ ziti edge update identity ${OPENZITI_CONTROLLER_ROUTER_NAME} -a 'public,edgex.token-provider.dialers'
+ ziti edge update identity ${OPENZITI_EDGEX_ROUTER_NAME} -a 'edgex.token-provider.binders'
+
+* Wait for the OpenZiti components to recognize the change and confirm a new OpenZiti terminator exists for the
+ token-provider service. This indicates the router near the EdgeX Foundry services is ready to relay traffic from
+ the OpenZiti controller to the token provider. You can check for this terminator by running the following `ziti`
+ CLI command:
+
+ ziti edge list terminators 'service.name = "edgex.token-provider"'
+
+* At this point, you should be able to ssh to the OpenZiti Controller and execute a `curl` (or `wget`) to the
+ configured intercept address and reach your token provider! Getting a `Temporary Redirect` result indicates you
+ have successfully tunneled from the OpenZiti controller to the token provider.
+
+ curl http://token-provider.edgex.ziti:8200
+ Temporary Redirect.
+
+ Alternatively you can issue a request to the JWKS endpoint and receive a response. Using the values configured
+ with the `openziti-init-entrypoint.sh` script here's what that looks like
+
+ curl -s http://token-provider.edgex.ziti:8200/v1/identity/oidc/.well-known/keys | jq .
+ {
+ "keys": [
+ {
+ "use": "sig",
+ "kty": "EC",
+ "kid": "b536965c-316d-3ed2-f830-b4d2e4e7ae3b",
+ "crv": "P-384",
+ "alg": "ES384",
+ "x": "wMnzI5rCNaQLml3kK36sLGDY6en8OVU_564ADhHb3okJYoHo_PCQEsTyFMdbLxW0",
+ "y": "bxxGM2r7_rcJtWsH4vhCohlDBdwOyFXZq_RmY0TO64VKE0lGF1CEmOGHE9nEXZgG"
+ },
+ {
+ "use": "sig",
+ "kty": "EC",
+ "kid": "969cef78-7b05-c0d2-388c-12a3a9c1fad1",
+ "crv": "P-384",
+ "alg": "ES384",
+ "x": "h8m37IqZ_6u-NnRx1OF_olJtnJQJMAR0bQa6rmRjLMgBrJ8CR3DTucM9SYfUN27-",
+ "y": "FT9M-MOkUf7DbCdOr6D1LWtRINLeri1mYkkEc-CB6koFB1gRtydEAwqF0SX5UaR1"
+ }
+ ]
+ }
+
+### Configure the OpenZiti Overlay for EdgeX Foundry
+
+With OpenZiti being able to issue requests to the JWKS endpoint from the token provider, it can now be configured to
+work with EdgeX Foundry services. EdgeX Foundry provides a convenient script that will configure the OpenZiti
+overlay for use with EdgeX Foundry. It is not necessary to run this script within a docker container, but it does
+make it more convenient as the `ziti` CLI will be installed and on the path by using the `openziti/ziti-cli` image.
+
+ docker run -it --rm \
+ --env-file .env \
+ -e ZITI_ADMIN \
+ -e ZITI_PWD \
+ -e OPENZITI_OIDC_URL="http://token-provider.edgex.ziti:8200" \
+ -e OPENZITI_PERSISTENCE_PATH="/edgex_openziti" \
+ -v edgex_edgex_openziti:/edgex_openziti \
+ -v ${PWD}/openziti-init-entrypoint.sh:/openziti-init-entrypoint.sh \
+ --entrypoint "/bin/sh" \
+ --user root \
+ openziti/ziti-cli \
+ -c 'chown -Rc 2002:2001 "${OPENZITI_PERSISTENCE_PATH}" && ./openziti-init-entrypoint.sh'
+
+Some things to note. This container runs as root in order to write the enrollment token for the
+healthcheck container. It passes the `ZITI_ADMIN` and `ZITI_PWD` variables into the container to be used by the
+entrypoint script to automate the configuration of the OpenZiti overlay.
+
+Remember that once configured for zero trust, the docker containers no longer need to export any port as there are
+no listening ports.
+
+### Start EdgeX Foundry's External Dependencies
+
+When the script completes, the OpenZiti overlay will be configured and ready to use. Start the EdgeX Foundry
+services.
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
index 7b705f699f..97cf65b71d 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -335,6 +335,7 @@ nav:
- security/Ch-CORS-Settings.md
- security/Ch-DelayedStartServices.md
- security/Ch-RemoteDeviceServices.md
+ - security/zero-trust.md
- V3 Migration: security/V3Migration.md
- Threat Models:
- threat-models/secret-store/README.md