Provision a network and service
This guide walks through automating the full provisioning flow end-to-end using two APIs: the NetFoundry API for infrastructure, and the OpenZiti controller API for Ziti resources. When you're done you'll have a network, a customer-hosted edge router, two identities, and a service that routes traffic through the overlay.
You'll need curl and jq. Set NETFOUNDRY_API_TOKEN before you start—see Authentication if you
don't have a token yet.
NF_API=https://gateway.production.netfoundry.io
Find your network group
Your organization belongs to one or more network groups. You need the network group ID to create a network.
-
List your network groups:
curl -s ${NF_API}/core/v3/network-groups \
--header "Authorization: Bearer ${NETFOUNDRY_API_TOKEN}" \
| jq '._embedded.networkGroupList[] | {name, id}'The response lists your groups by name:
{
"name": "My Company",
"id": "4d15ef35-cfa0-4963-a667-5c86d16ce77e"
} -
Set the ID of the group you want to use:
NF_NETWORK_GROUP=4d15ef35-cfa0-4963-a667-5c86d16ce77e
Create a network
Provision the network. Specify the network group, a cloud provider, a region, and a size. Use small for cost-conscious
testing.
-
Create the network:
NF_CREATE=$(curl -s -X POST ${NF_API}/core/v3/networks \
--header "Authorization: Bearer ${NETFOUNDRY_API_TOKEN}" \
--header "Content-Type: application/json" \
--data '{
"networkGroupId": "'"${NF_NETWORK_GROUP}"'",
"name": "example-network",
"size": "small",
"provider": "AWS",
"region": "us-east-1"
}')
NF_NETWORK=$(echo "${NF_CREATE}" | jq -r .id)
NF_EXECUTION=$(echo "${NF_CREATE}" | jq -r '._links.execution.href') -
Poll the execution link until it returns
SUCCESS. The network reachesPROVISIONEDstatus before it's fully ready to use, so use the execution link—not the network status—as your readiness signal. Provisioning takes a few minutes:curl -s "${NF_EXECUTION}" \
--header "Authorization: Bearer ${NETFOUNDRY_API_TOKEN}" | jq -r .status
To see available cloud providers and region codes, query GET /core/v3/regions. The locationCode value is what you
pass as region:
curl -s "${NF_API}/core/v3/regions?provider=AWS" \
--header "Authorization: Bearer ${NETFOUNDRY_API_TOKEN}" \
| jq '._embedded.regionList[] | .locationCode'
To list available sizes, query GET /core/v3/host-sizes.
Get the controller address
Fetch the controller's domain name using the network-controllers embed—you'll use it for all subsequent controller
API calls. Any _links entry in a NetFoundry API response can be embedded this way by passing its name as the
embed query parameter.
NF_CONTROLLER=$(curl -s "${NF_API}/core/v3/networks/${NF_NETWORK}?embed=network-controllers" \
--header "Authorization: Bearer ${NETFOUNDRY_API_TOKEN}" \
| jq -r '._embedded["network-controllers"][0].domainName')
Authenticate to the controller
Exchange your NetFoundry Bearer token for a session token to authenticate against the controller API.
SESSION=$(curl -s -X POST https://${NF_CONTROLLER}/edge/management/v1/authenticate?method=ext-jwt \
--header "Authorization: Bearer ${NETFOUNDRY_API_TOKEN}" \
--header "Content-Type: application/json" \
| jq -r .data.token)
All controller API calls from this point use --header "zt-session: ${SESSION}" instead of a Bearer token.
Create an edge router
Create the edge router on the controller. The enrollmentJwt in the response is a one-time token used to enroll the
router software—fetch and save it before it expires.
-
Create the edge router entry:
NF_ROUTER=$(curl -s -X POST https://${NF_CONTROLLER}/edge/management/v1/edge-routers \
--header "zt-session: ${SESSION}" \
--header "Content-Type: application/json" \
--data '{
"name": "example-router",
"roleAttributes": ["defaultRouters"],
"enrollment": {"ott": true}
}' | jq -r .data.id) -
Fetch the enrollment JWT and save it to a file:
curl -s https://${NF_CONTROLLER}/edge/management/v1/edge-routers/${NF_ROUTER} \
--header "zt-session: ${SESSION}" \
| jq -r .data.enrollmentJwt > router.jwt
Create an edge router policy
New networks include a default edge router policy that allows all identities to use all edge routers (known as an all/all policy). If you're working with a default network, you can skip this step.
An edge router policy governs which identities can route through which edge routers. This policy allows all
identities to use routers with the defaultRouters attribute.
curl -s -X POST https://${NF_CONTROLLER}/edge/management/v1/edge-router-policies \
--header "zt-session: ${SESSION}" \
--header "Content-Type: application/json" \
--data '{
"name": "example-er-policy",
"edgeRouterRoles": ["#defaultRouters"],
"identityRoles": ["#all"],
"semantic": "AnyOf"
}' | jq -r .data.id
Create identities
You need two identities: one for the client that dials the service, and one for the host that provides egress.
Client identity
The client identity represents the device that dials into the service.
-
Create the identity:
NF_CLIENT=$(curl -s -X POST https://${NF_CONTROLLER}/edge/management/v1/identities \
--header "zt-session: ${SESSION}" \
--header "Content-Type: application/json" \
--data '{
"name": "client1",
"type": "Default",
"isAdmin": false,
"roleAttributes": ["clients"],
"enrollment": {"ott": true}
}' | jq -r .data.id) -
Fetch the enrollment JWT and save it to a file:
curl -s https://${NF_CONTROLLER}/edge/management/v1/identities/${NF_CLIENT} \
--header "zt-session: ${SESSION}" \
| jq -r '.data.enrollment.ott.jwt' > client1.jwt
Host identity
The host identity runs on the machine that egresses to your server.
-
Create the identity:
NF_HOST=$(curl -s -X POST https://${NF_CONTROLLER}/edge/management/v1/identities \
--header "zt-session: ${SESSION}" \
--header "Content-Type: application/json" \
--data '{
"name": "exit1",
"type": "Default",
"isAdmin": false,
"roleAttributes": ["exits"],
"enrollment": {"ott": true}
}' | jq -r .data.id) -
Fetch the enrollment JWT and save it to a file:
curl -s https://${NF_CONTROLLER}/edge/management/v1/identities/${NF_HOST} \
--header "zt-session: ${SESSION}" \
| jq -r '.data.enrollment.ott.jwt' > exit1.jwt
Create the service
Services are defined by a pair of configs: an intercept config (how clients connect in) and a host config (where traffic egresses). Create the configs first, then the service that references them.
-
Look up the built-in config type IDs. These are unique per controller:
NF_INTERCEPT_TYPE=$(curl -s https://${NF_CONTROLLER}/edge/management/v1/config-types \
--header "zt-session: ${SESSION}" \
| jq -r '.data[] | select(.name == "intercept.v1") | .id')
NF_HOST_TYPE=$(curl -s https://${NF_CONTROLLER}/edge/management/v1/config-types \
--header "zt-session: ${SESSION}" \
| jq -r '.data[] | select(.name == "host.v1") | .id') -
Create the intercept config. Clients connect to the hostname and port defined here—the tunneler intercepts traffic to this address and routes it through the overlay:
NF_INTERCEPT_CONFIG=$(curl -s -X POST https://${NF_CONTROLLER}/edge/management/v1/configs \
--header "zt-session: ${SESSION}" \
--header "Content-Type: application/json" \
--data '{
"name": "echo-intercept",
"configTypeId": "'"${NF_INTERCEPT_TYPE}"'",
"data": {
"protocols": ["tcp"],
"addresses": ["echo.ziti"],
"portRanges": [{"low": 80, "high": 80}]
}
}' | jq -r .data.id) -
Create the host config. This defines where the host identity egresses traffic. This example routes to
eth0.me:80—an external IP-checking service useful for verifying the overlay is working:NF_HOST_CONFIG=$(curl -s -X POST https://${NF_CONTROLLER}/edge/management/v1/configs \
--header "zt-session: ${SESSION}" \
--header "Content-Type: application/json" \
--data '{
"name": "echo-host",
"configTypeId": "'"${NF_HOST_TYPE}"'",
"data": {
"protocol": "tcp",
"address": "eth0.me",
"port": 80
}
}' | jq -r .data.id) -
Create the service, referencing both configs:
NF_SERVICE=$(curl -s -X POST https://${NF_CONTROLLER}/edge/management/v1/services \
--header "zt-session: ${SESSION}" \
--header "Content-Type: application/json" \
--data '{
"name": "echo-service",
"encryptionRequired": true,
"roleAttributes": ["publicServices"],
"configs": ["'"${NF_INTERCEPT_CONFIG}"'", "'"${NF_HOST_CONFIG}"'"]
}' | jq -r .data.id)
Create service policies
Service policies authorize identities to dial or bind a service. You need two: one for clients (Dial) and one for the host (Bind).
curl -s -X POST https://${NF_CONTROLLER}/edge/management/v1/service-policies \
--header "zt-session: ${SESSION}" \
--header "Content-Type: application/json" \
--data '{
"name": "echo-dial",
"type": "Dial",
"semantic": "AnyOf",
"serviceRoles": ["#publicServices"],
"identityRoles": ["#clients"]
}' | jq -r .data.id
curl -s -X POST https://${NF_CONTROLLER}/edge/management/v1/service-policies \
--header "zt-session: ${SESSION}" \
--header "Content-Type: application/json" \
--data '{
"name": "echo-bind",
"type": "Bind",
"semantic": "AnyOf",
"serviceRoles": ["#publicServices"],
"identityRoles": ["#exits"]
}' | jq -r .data.id
Enroll
Each enrollment JWT is a one-time token. Run these commands on the target machine before the JWT expires.
Enroll the edge router
Run this on the machine where the router will run. The router.jwt file was saved in the
edge router step above.
ziti router enroll --jwt router.jwt
Enroll the identities
Run these on the respective machines—client1.jwt on the client machine, exit1.jwt on the host machine.
ziti edge enroll --jwt client1.jwt
ziti edge enroll --jwt exit1.jwt
The resulting .json files are the identity files. Load them into a Ziti-compatible tunneler to connect. See the
NetFoundry downloads page for tunneler options, or the
Linux tunneler guide for CLI-based setup.