Public Key Infrastructure (PKI)
All networks leverage Public Key Infrastructure (PKI) to provide secure network connections. This page is not intended to be a comprehensive guide. What it is, is a set of rules that must be followed to properly configure a network. If there are issues when connecting any portion of a network to another - this page should serve as a starting point of understanding.
This article is about managing your own PKI. There's a guide for using public CA certificates.
The network allows the operator to declare any trust anchors as valid. This means Ziti does not need to be configured with a full chain of certificates which link fully back to a root CA. A configuration using a full chain back to a root CA is of course supported but it is not explicitly required. This allows the operator to configure a Ziti Network using one or more chains of trust back to the provided trust anchors. The sections below will describe where these trust anchors can be configured.
Ziti Network components are required to present a certificate to other network components during the connection establishment. This certificate will need to be valid per the configured trust anchor store being connected to.
Ziti Controller
The controller has three distinct sections related to PKI: identity
, edge.api.identity
,
edge.enrollment.signingCert
. The edge.api.identity
configuration section is optional and is provided to allow the external
REST endpoint to present a certificate that is different than the one configured in the identity section.
Connections to the controller are considered valid if the certificate presented during connection is signed by a
trust anchor declared within the identity.ca configuration or if the certificate presented is signed by the certificate
specified in the edge.enrollment.signingCert
.
PKI Configuration
The identity section of the controller configuration is used by the controller when connections are
established to or from other components of a network. There are four sections in the identity block:
cert
, server_cert
, key
, ca
.
ca: A file representing a group of certificates with one or more certificate chains terminating at a trust anchor. When a network component connects to the controller and offers a certificate for validation the incoming connection is checked to see if it signed by a trust anchor specified in this file.
key: Also referred to as the private key. It is generated
first and used to produce the certificates specified in the cert
and server_cert
fields of the controller
configuration file.
cert: The certificate presented to other network components during connection establishment.
server_cert: The certificate returned by the controller when other network components attempt to
communicate to the controller over the IP and port specified in the ctrl.listener
or mgmt.listener
fields of the controller
configuration file. If an edge section is present in the configuration file and no edge.api.identity section exists this
certificate is also returned to incoming connections to the edge.api.advertise
endpoint.
Edge Router
An edge router has one section related to PKI: identity
. It is important to note that an edge router will
manage its own PKI. Allowing the edge router to manage its own PKI is almost certainly desired. The
only setting that an operator may wish to provide is the key
field of the identity. This field is treated differently
than the other values specified. If the key
specified does not exist a new key will be generated. If the key
provided exists the edge router will use it and the other fields will be regenerated and overwritten as necessary.
The certificate generated will be signed by the controller using the certificate specified in edge.enrollment.signingCert
.
PKI Configuration
The identity
section of the edge router configuration is used by the edge router when connections are
established to or from the other components of a network. There are four sections in the identity block:
cert
, server_cert
, key
, ca
.
ca: A file representing a group of certificates with one or more certificate chains terminating at a trust anchor. When a network component connects to the edge router and offers a certificate for validation the incoming connection is checked to see if it signed by a trust anchor specified in this file.
key: Also referred to as the private key. It is generated
first and used to produce the certificates specified in the cert
and server_cert
fields of the Edge Router
configuration file.
cert: The certificate presented to other network components during connection establishment.
server_cert: The certificate returned by the edge router when other network components attempt to
communicate to the edge router over the IP and port specified in the ctrl.listener
or mgmt.listener
fields of the Edge Router
configuration file.
Third Party CA (optional)
A third party CA is one which is maintained and managed entirely outside of the network. This is an important feature for organizations wishing to administer and maintain the certificates used by the different pieces of the Ziti Network. A network is capable of using third party PKIs as the trust mechanism for enrollment and authentication of clients for a network.
With the PKI being managed externally a network is never in possession of the private key. This means the Ziti Network cannot maintain nor distribute certificates necessary for creating secure connections. The network is only capable of verifying if the certificate presented was signed by the externally managed PKI.
Maintaining a PKI outside of the network is a more complex configuration. If a PKI is already established and maintained externally setting up a network with a third party CA may be desired.
Registering the CA
A network will not trust any third party CA implicitly. Before a third party CA can be used for enrollment and authentication of clients in a network it must be registered with the controller to ensure certificates signed by the third party CA can be trusted.
Registering a third party CA is done by using the REST endpoint /cas
from the controller. To register a third
party CA the following information is required to be posted to the endpoint:
- name: the desired name of the CA
- isEnrollmentEnabled: a boolean value indicating if the CA can be used for enrollment. Defaults to true. Set to false to prevent further enrollments using this CA
- isAuthEnabled: a boolean value indicating if the CA can be used for authentication. Defaults to true. Set to false to prevent all authentication from endpoints signed by this certificate
Assuming the create request was well formed and successful, the response from this invocation will contain a field
representing the id
of the third party CA at data.id
. The id of the third party CA will be needed when validating
the third party CA.
Validating the CA
After being submitted to the controller, the third party CA will have the isCsrValidated field set to false indicating it is not yet ready for use. A second step is needed to ensure the third party CA is setup properly as a CA. This step ensures the third party CA provided is capable of fulfilling CSR requests. Clients attempting to connect to a Ziti Network using the third party CA will be rejected.
To validate the third party CA a CSR must be generated and fulfilled by the third party CA to generate a certificate
with the common name (CN) field set to a value assigned by the controller. The controller /cas
REST endpoint can be interrogated to retrieve the details for a specific third party CA. The field necessary to validate
the third party CA is data.verificationToken
and is obtained at this endpoint. A certificate is then created and
signed by the third party CA with the common name field set to the verificationToken.
To finish verifying the third party CA, the certificate created with the verificationToken is posted back to the Ziti
Controller at /cas/${id}/verify
. The id
is also obtained during the creation process. After posting the certificate
with the verificationToken
as the common name the third party CA will change from isVerified=false
to isVerified=true
.
Configuring a network's PKI can be confusing. Validating a single side of a mutual TLS connection is straightforward it becomes tedious to ensure all the certificates and cas in use are valid when you have a fully configured network. It's the goal of this page to make diagnosing PKI issues easier.
Prerequisites
The following steps are bash-based functions and use the openssl, jq and ruby commands. If you don't have bash, openssl and ruby - this page is not for you! Do your best to follow along with the scripts and guidance herein or just make sure bash, openssl, ruby, and jq are installed. All of which are widely available on linux/MacOS/Windows.
The ruby
and jq
commands are not strictly required. They are used to make it easy for you to copy/paste these
commands. The ruby
command is used to translate yaml into json while the jq
command is used to pull the specific
values out of the given files. You can certainly do the same manually (without ruby
and jq
) if you choose.
Define the verifyCertAgainstPool Function
In your bash prompt copy and paste these two functions:
function yaml2json()
{
ruby -ryaml -rjson -e 'puts JSON.pretty_generate(YAML.load(ARGF))' $*
}
function verifyCertAgainstPool()
{
if [[ "" == "$1" ]]
then
echo "Usage: verifyCertAgainstPool [cert to test] [ca pool to use]"
return 1
fi
if [[ "" == "$2" ]]
then
echo "Usage: verifyCertAgainstPool [cert to test] [ca pool to use]"
return 1
fi
echo " Verifying that this certificate:"
echo " - $1"
echo " is valid for this ca pool:"
echo " - $2"
echo ""
openssl verify -partial_chain -CAfile "$2" "$1"
if [ $? -eq 0 ]; then
echo ""
echo "============ SUCCESS! ============"
else
echo ""
echo "============ FAILED TO VALIDATE ============"
fi
}
Validating the PKI
Every connection in a network is mutually authenticated via X509 certificates. It is easiest to first identify the two components trying to communicate to isolate and minimize the configuration and files that need to be inspected. Below you will find sections relevant to each of the pieces of Ziti which connect.
Each section will refer to a bash variable that is expected to be established before running the command. This variable is intended to make it easier for you to simply copy/paste the command and determine if the configuration is valid or not.
Using the provided bash function above - you will see one of two results:
Success
verifyCertAgainstPool /path/to/cert-to-test.cert /path/to/capool.pem
Verifying that this certificate:
- /path/to/cert-to-test.cert
is valid for this ca pool:
- /path/to/capool.pem
/path/to/cert-to-test.cert: OK
============ SUCCESS! ============
Failure
verifyCertAgainstPool /path/to/cert-to-test.cert /path/to/capool.pem
Verifying that this certificate:
- /path/to/cert-to-test.cert
is valid for this ca pool:
- /path/to/capool.pem
C = US, ST = NC, L = Charlotte, O = NetFoundry, OU = Ziti, CN = 87f8cee9-b288-49f1-ab90-b664af29e17a
error 20 at 0 depth lookup: unable to get local issuer certificate
error /path/to/cert-to-test.cert: verification failed
============ FAILED TO VALIDATE ============
Ziti Edge Router to Controller
Variables to Establish Manually
These two variables represent the edge router configuration file and the Controller configuration file.
controller_config_file=~/.config/ziti/ziti-controller/ziti_controller.yml
edge_router_config_file=~/.config/ziti/ziti-router/ziti_router.yml
Variables - Copy/Paste
These commands extract the files specified in the configuration and store them into the assigned variables.
edge_router_cert=$(yaml2json $edge_router_config_file | jq -j .identity.cert)
signing_cert=$(yaml2json $controller_config_file | jq -j .edge.enrollment.signingCert.cert)
controller_cert=$(yaml2json $controller_config_file | jq -j .identity.cert)
edge_router_ca=$(yaml2json $edge_router_config_file | jq -j .identity.ca)
Commands to Verify PKI Configuration
Both of these commands should report SUCCESS.
verifyCertAgainstPool $edge_router_cert $signing_cert
verifyCertAgainstPool $controller_cert $edge_router_ca
Ziti Client to Controller - API
Variables to Establish Manually
These two variables represent the identity file in json for a Ziti client and the Controller configuration file.
identity_file=/path/to/enrolled-identity.json
controller_config_file=~/.config/ziti/ziti-controller/ziti_controller.yml
Variables - Copy/Paste
These commands will extract the cert and ca from the enrolled identity file and put it into a file in the /tmp folder
jq -j .id.cert $identity_file | cut -d ":" -f2 > /tmp/identity.cert
jq -j .id.ca $identity_file | cut -d ":" -f2 > /tmp/identity.ca
These commands extract the files specified in the configuration and store them into the assigned variables.
controller_cert=$(yaml2json $controller_config_file | jq -j .identity.cert)
signing_cert=$(yaml2json $controller_config_file | jq -j .edge.enrollment.signingCert.cert)
controller_api_server_cert=$(yaml2json $controller_config_file | jq -j .edge.api.identity.server_cert)
if [[ "null" == "$controller_api_server_cert" ]]; then controller_api_server_cert=$(yaml2json $controller_config_file | jq -j .identity.server_cert); fi
Commands to Verify PKI Configuration
Both of these commands should report SUCCESS.
verifyCertAgainstPool /tmp/identity.cert $signing_cert
verifyCertAgainstPool $controller_api_server_cert /tmp/identity.ca
Ziti Client to Ziti Edge Router - Data
Variables to Establish Manually
These two variables represent the identity file in json for a Ziti client and the Controller configuration file.
identity_file=/path/to/enrolled-identity.json
edge_router_config_file=~/.config/ziti/ziti-router/ziti_router.yml
Variables - Copy/Paste
This command will extract the ca from the enrolled identity file and put it into a file in the /tmp folder
jq -j .id.ca $identity_file | cut -d ":" -f2 > /tmp/identity.ca
This command extracts the file specified in the configuration and stores it into the assigned variable.
edge_router_cert=$(yaml2json $edge_router_config_file | jq -j .identity.cert)
Commands to Verify PKI Configuration
The following command should report SUCCESS.
verifyCertAgainstPool $edge_router_cert /tmp/identity.ca