diff --git a/.github/workflows/fablab-db-creation.yml b/.github/workflows/fablab-db-creation.yml index 639cffa83..189421ed6 100644 --- a/.github/workflows/fablab-db-creation.yml +++ b/.github/workflows/fablab-db-creation.yml @@ -20,6 +20,7 @@ jobs: build: name: Build and Run runs-on: ubuntu-latest + if: github.repository_owner == 'openziti' steps: - name: Checkout ziti uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-images.yml b/.github/workflows/publish-docker-images.yml index 1daeaa059..105e13ccd 100644 --- a/.github/workflows/publish-docker-images.yml +++ b/.github/workflows/publish-docker-images.yml @@ -96,12 +96,14 @@ jobs: uses: docker/build-push-action@v5 with: builder: ${{ steps.buildx.outputs.name }} - context: ${{ github.workspace }}/dist/docker-images/ziti-controller/ + context: ${{ github.workspace }}/ + file: ${{ github.workspace }}/dist/docker-images/ziti-controller/Dockerfile platforms: linux/amd64,linux/arm64 tags: ${{ steps.tagprep_ctrl.outputs.DOCKER_TAGS }} build-args: | ZITI_CLI_TAG=${{ env.ZITI_CLI_TAG }} ZITI_CLI_IMAGE=${{ env.ZITI_CLI_IMAGE }} + DOCKER_BUILD_DIR=./dist/docker-images/ziti-controller push: true - name: Set Up Container Image Tags for Router Container @@ -116,19 +118,20 @@ jobs: if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then DOCKER_TAGS+=",${IMAGE_REPO}:latest" fi - echo "DEBUG: DOCKER_TAGS=${DOCKER_TAGS}" - echo DOCKER_TAGS="${DOCKER_TAGS}" >> $GITHUB_OUTPUT + echo DOCKER_TAGS="${DOCKER_TAGS}" | tee -a $GITHUB_OUTPUT - name: Build & Push Multi-Platform Router Container Image to Hub uses: docker/build-push-action@v5 with: builder: ${{ steps.buildx.outputs.name }} - context: ${{ github.workspace }}/dist/docker-images/ziti-router/ + context: ${{ github.workspace }}/ + file: ${{ github.workspace }}/dist/docker-images/ziti-router/Dockerfile platforms: linux/amd64,linux/arm64 tags: ${{ steps.tagprep_router.outputs.DOCKER_TAGS }} build-args: | ZITI_CLI_TAG=${{ env.ZITI_CLI_TAG }} ZITI_CLI_IMAGE=${{ env.ZITI_CLI_IMAGE }} + DOCKER_BUILD_DIR=./dist/docker-images/ziti-router push: true - name: Set Up Container Image Tags for Go Tunneler Container diff --git a/.github/workflows/publish-linux-packages.yml b/.github/workflows/publish-linux-packages.yml index dcd402b7b..eb70f59f9 100644 --- a/.github/workflows/publish-linux-packages.yml +++ b/.github/workflows/publish-linux-packages.yml @@ -16,6 +16,8 @@ jobs: matrix: package_name: - openziti + - openziti-controller + - openziti-router arch: - goreleaser: amd64 gox: amd64 diff --git a/CHANGELOG.md b/CHANGELOG.md index b12e8338e..8fe6cdc8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# Release 0.34.2 + +## What's New + +* Deployments Alpha + * Linux packages provide systemd services for controller and router. Both depend on existing package `openziti` which provides the `ziti` command line tool. + * `openziti-controller` provides `ziti-controller.service` + * `openziti-router` provides `ziti-router.service` + * Container images for controller and router now share the bootstrapping logic with the packages, so they + support the same configuration options. + # Release 0.34.1 ## What's New diff --git a/dist/cloudfront/get.openziti.io/routes.yml b/dist/cloudfront/get.openziti.io/routes.yml index b6e5a1d75..e893bb4e5 100644 --- a/dist/cloudfront/get.openziti.io/routes.yml +++ b/dist/cloudfront/get.openziti.io/routes.yml @@ -38,3 +38,7 @@ - get: /zdew/ raw: /openziti/desktop-edge-win/main/release-streams/ file: latest.json + +- get: /dist/ + raw: /openziti/ziti/{{GITHUB_SHA}}/dist/ + file: /docker-images/ziti-router/compose.yml diff --git a/dist/dist-packages/linux/nfpm-openziti-controller.yaml b/dist/dist-packages/linux/nfpm-openziti-controller.yaml new file mode 100644 index 000000000..75a7d2382 --- /dev/null +++ b/dist/dist-packages/linux/nfpm-openziti-controller.yaml @@ -0,0 +1,39 @@ +# nfpm configuration file +# +# check https://nfpm.goreleaser.com/configuration for detailed usage +# +name: openziti-controller +arch: ${GOARCH} +platform: linux +version: ${ZITI_VERSION} +prerelease: ${ZITI_REV} +maintainer: ${ZITI_MAINTAINER} +description: > + Provides a system service for running an OpenZiti Controller +vendor: ${ZITI_VENDOR} +homepage: ${ZITI_HOMEPAGE} +license: Apache-2.0 +# Contents to add to the package. +contents: + - dst: /lib/systemd/system/ + src: ./dist/dist-packages/linux/openziti-controller/ziti-controller.service + + - dst: /opt/openziti/etc/controller + type: dir + file_info: + mode: 0755 + + - dst: /opt/openziti/etc/controller/ + src: ./dist/dist-packages/linux/openziti-controller/env + type: config|noreplace + + - dst: /opt/openziti/etc/controller/ + src: ./dist/dist-packages/linux/openziti-controller/bootstrap.bash + + - dst: /opt/openziti/etc/controller/ + src: ./dist/dist-packages/linux/openziti-controller/entrypoint.bash +depends: + - openziti # ziti CLI + +scripts: + postinstall: ./dist/dist-packages/linux/openziti-controller/postinstall.bash \ No newline at end of file diff --git a/dist/dist-packages/linux/nfpm-openziti-router.yaml b/dist/dist-packages/linux/nfpm-openziti-router.yaml new file mode 100644 index 000000000..877dac65b --- /dev/null +++ b/dist/dist-packages/linux/nfpm-openziti-router.yaml @@ -0,0 +1,41 @@ +# nfpm configuration file +# +# check https://nfpm.goreleaser.com/configuration for detailed usage +# +name: openziti-router +arch: ${GOARCH} +platform: linux +version: ${ZITI_VERSION} +prerelease: ${ZITI_REV} +maintainer: ${ZITI_MAINTAINER} +description: > + Provides a system service for running an OpenZiti Router +vendor: ${ZITI_VENDOR} +homepage: ${ZITI_HOMEPAGE} +license: Apache-2.0 +# Contents to add to the package. +contents: + - dst: /lib/systemd/system/ + src: ./dist/dist-packages/linux/openziti-router/ziti-router.service + + - dst: /opt/openziti/etc/router + type: dir + file_info: + mode: 0755 + + - dst: /opt/openziti/etc/router/ + src: ./dist/dist-packages/linux/openziti-router/env + type: config|noreplace + + - dst: /opt/openziti/etc/router/ + src: ./dist/dist-packages/linux/openziti-router/bootstrap.bash + + - dst: /opt/openziti/etc/router/ + src: ./dist/dist-packages/linux/openziti-router/entrypoint.bash + +scripts: + postinstall: ./dist/dist-packages/linux/openziti-router/postinstall.bash + preremove: ./dist/dist-packages/linux/openziti-router/preremove.bash + +depends: + - openziti # ziti CLI diff --git a/dist/dist-packages/linux/nfpm-openziti.yaml b/dist/dist-packages/linux/nfpm-openziti.yaml index f7dd43be0..49d11a761 100644 --- a/dist/dist-packages/linux/nfpm-openziti.yaml +++ b/dist/dist-packages/linux/nfpm-openziti.yaml @@ -6,6 +6,7 @@ name: openziti arch: ${GOARCH} platform: linux version: ${ZITI_VERSION} +prerelease: ${ZITI_REV} maintainer: ${ZITI_MAINTAINER} description: > The openziti package provides the ziti executable binary as a command line @@ -23,10 +24,3 @@ contents: type: symlink replaces: - ziti-cli - -# packager-neutral scripts may be overridden by packager-specific scripts -# scripts: - # preinstall: ./scripts/preinstall.sh - # postinstall: ./scripts/postinstall.sh - # preremove: ./scripts/preremove.sh - # postremove: ./scripts/postremove.sh diff --git a/dist/dist-packages/linux/openziti-controller/bootstrap.bash b/dist/dist-packages/linux/openziti-controller/bootstrap.bash new file mode 100755 index 000000000..867e124b6 --- /dev/null +++ b/dist/dist-packages/linux/openziti-controller/bootstrap.bash @@ -0,0 +1,208 @@ +# +# bootstrap the OpenZiti Controller with PKI, config file, and database +# + + +# +# defaults +# + +function makePki() { + # + # create root and intermediate CA + # + + # used by "ziti pki create server" as DNS SAN + if [ -z "${ZITI_CTRL_ADVERTISED_ADDRESS:-}" ]; then + echo "ERROR: ZITI_CTRL_ADVERTISED_ADDRESS must be set, i.e., the FQDN by which all devices will reach the"\ + "controller and verify the server certificate" >&2 + return 1 + fi + + if [ "$ZITI_CA_FILE" == "$ZITI_INTERMEDIATE_FILE" ]; then + echo "ERROR: ZITI_CA_FILE and ZITI_INTERMEDIATE_FILE must be different" >&2 + return 1 + fi + + ZITI_CA_CERT="${ZITI_PKI_ROOT}/${ZITI_CA_FILE}/certs/${ZITI_CA_FILE}.cert" + if [ ! -s "${ZITI_CA_CERT}" ]; then + ziti pki create ca \ + --pki-root "${ZITI_PKI_ROOT}" \ + --ca-file "${ZITI_CA_FILE}" + fi + + ZITI_PKI_SIGNER_CERT="${ZITI_PKI_ROOT}/${ZITI_INTERMEDIATE_FILE}/certs/${ZITI_INTERMEDIATE_FILE}.cert" + ZITI_PKI_SIGNER_KEY="${ZITI_PKI_ROOT}/${ZITI_INTERMEDIATE_FILE}/keys/${ZITI_INTERMEDIATE_FILE}.key" + if [[ ! -s "$ZITI_PKI_SIGNER_CERT" && ! -s "$ZITI_PKI_SIGNER_KEY" ]]; then + ziti pki create intermediate \ + --pki-root "${ZITI_PKI_ROOT}" \ + --ca-name "${ZITI_CA_FILE}" \ + --intermediate-file "${ZITI_INTERMEDIATE_FILE}" + elif [[ ! -s "$ZITI_PKI_SIGNER_CERT" || ! -s "$ZITI_PKI_SIGNER_KEY" ]]; then + echo "ERROR: $ZITI_PKI_SIGNER_CERT and $ZITI_PKI_SIGNER_KEY must both exist or neither exist as non-empty files" >&2 + return 1 + fi + + # + # create server and client keys + # + + if [ "$ZITI_SERVER_FILE" == "$ZITI_CLIENT_FILE" ]; then + echo "ERROR: ZITI_SERVER_FILE and ZITI_CLIENT_FILE must be different" >&2 + return 1 + fi + + ZITI_PKI_CTRL_KEY="${ZITI_PKI_ROOT}/${ZITI_INTERMEDIATE_FILE}/keys/${ZITI_SERVER_FILE}.key" + if ! [ -s "$ZITI_PKI_CTRL_KEY" ]; then + ziti pki create key \ + --pki-root "${ZITI_PKI_ROOT}" \ + --ca-name "${ZITI_INTERMEDIATE_FILE}" \ + --key-file "${ZITI_SERVER_FILE}" + fi + + # use the server key for both client and server certs until "ziti create config controller" supports separate keys for + # each + # CLIENT_KEY_FILE="${ZITI_PKI_ROOT}/${ZITI_INTERMEDIATE_FILE}/keys/${ZITI_CLIENT_FILE}.key" + # if ! [ -s "$CLIENT_KEY_FILE" ]; then + # ziti pki create key \ + # --pki-root "${ZITI_PKI_ROOT}" \ + # --ca-name "${ZITI_INTERMEDIATE_FILE}" \ + # --key-file "${ZITI_CLIENT_FILE}" + # fi + + # + # create server and client certs + # + + # server cert + ZITI_PKI_CTRL_SERVER_CERT="${ZITI_PKI_ROOT}/${ZITI_INTERMEDIATE_FILE}/certs/${ZITI_SERVER_FILE}.chain.pem" + if [[ "${ZITI_AUTO_RENEW_CERTS}" == true || ! -s "$ZITI_PKI_CTRL_SERVER_CERT" ]]; then + ziti pki create server \ + --pki-root "${ZITI_PKI_ROOT}" \ + --ca-name "${ZITI_INTERMEDIATE_FILE}" \ + --key-file "${ZITI_SERVER_FILE}" \ + --server-file "${ZITI_SERVER_FILE}" \ + --dns "${ZITI_CTRL_ADVERTISED_ADDRESS}" \ + --allow-overwrite + fi + + # client cert + # use the server key for both client and server certs until "ziti create config controller" supports separate keys for + # each + ZITI_PKI_CTRL_CERT="${ZITI_PKI_ROOT}/${ZITI_INTERMEDIATE_FILE}/certs/${ZITI_CLIENT_FILE}.cert" + if [[ "${ZITI_AUTO_RENEW_CERTS}" == true || ! -s "$ZITI_PKI_CTRL_CERT" ]]; then + ziti pki create client \ + --pki-root "${ZITI_PKI_ROOT}" \ + --ca-name "${ZITI_INTERMEDIATE_FILE}" \ + --key-file "${ZITI_SERVER_FILE}" \ + --client-file "${ZITI_CLIENT_FILE}" \ + --allow-overwrite + fi + +} + +function makeConfig() { + # + # create config file + # + + # enforce first argument is a non-empty string that does not begin with "--" (long option prefix) + if [[ -n "${1:-}" && ! "${1}" =~ ^-- ]]; then + local ZITI_CTRL_CONFIG_FILE="${1}" + shift + else + echo "ERROR: no config file path provided" >&2 + return 1 + fi + shopt -u nocasematch # toggle on case-sensitive comparison + + # used by "ziti create config controller" as advertised address + if [ -z "${ZITI_CTRL_ADVERTISED_ADDRESS:-}" ]; then + echo "ERROR: ZITI_CTRL_ADVERTISED_ADDRESS must be set, i.e., the FQDN by which all devices will reach the"\ + " controller and verify the server certificate" >&2 + return 1 + fi + + # set the path to the root CA cert + export ZITI_PKI_CTRL_CA="${ZITI_PKI_ROOT}/${ZITI_CA_FILE}/certs/${ZITI_CA_FILE}.cert" + + # set the URI of the edge-client API (uses same TCP port); e.g., ztAPI: ziti.example.com:1280 + export ZITI_CTRL_EDGE_ADVERTISED_ADDRESS="${ZITI_CTRL_ADVERTISED_ADDRESS}" \ + ZITI_CTRL_EDGE_ADVERTISED_PORT="${ZITI_CTRL_ADVERTISED_PORT}" + + # export the vars that were assigned inside this script to set the path to the server and client certs and their common + # private key, and the intermediate (signer) CA cert and key + export ZITI_PKI_CTRL_SERVER_CERT \ + ZITI_PKI_CTRL_CERT \ + ZITI_PKI_CTRL_KEY \ + ZITI_PKI_SIGNER_CERT \ + ZITI_PKI_SIGNER_KEY \ + ZITI_CTRL_ADVERTISED_ADDRESS \ + ZITI_CTRL_ADVERTISED_PORT \ + ZITI_CTRL_BIND_ADDRESS \ + ZITI_CTRL_EDGE_BIND_ADDRESS + + if [[ ! -s "${ZITI_CTRL_CONFIG_FILE}" || "${1:-}" == --force ]]; then + ziti create config controller \ + --output "${ZITI_CTRL_CONFIG_FILE}" + fi + +} + +function makeDatabase() { + + # + # create default admin in database + # + + if [ -s "${ZITI_CTRL_DATABASE_FILE}" ]; then + return 0 + fi + + # if the database file is in a subdirectory, create the directory so that "ziti controller edge init" can load the + # controller config.yml which contains a check to ensure the directory exists + DB_DIR="$(dirname "${ZITI_CTRL_DATABASE_FILE}")" + if ! [ "$DB_DIR" == "." ]; then + mkdir -p "$DB_DIR" + fi + + : "${ZITI_PWD:=$(< "/run/credentials/${UNIT_NAME:-ziti-controller.service}/ZITI_PWD")}" + if [ -n "${ZITI_PWD}" ]; then + ziti controller edge init "${ZITI_CTRL_CONFIG_FILE}" \ + --username "${ZITI_USER}" \ + --password "${ZITI_PWD}" + else + echo "ERROR: need admin password; use LoadCredential in"\ + " /lib/systemd/system/ziti-controller.service or set env var ZITI_PWD with at least 5 characters" >&2 + return 1 + fi + +} + +function bootstrap() { + + if [ -n "${1:-}" ]; then + local ZITI_CTRL_CONFIG_FILE="${1}" + echo "DEBUG: using config file path: $(realpath "${ZITI_CTRL_CONFIG_FILE}")" >&2 + else + echo "ERROR: no config file path provided" >&2 + return 1 + fi + + # make PKI unless it exists + if [ "${ZITI_BOOTSTRAP_PKI}" == true ]; then + makePki + fi + + # make config file unless it exists, set force to overwrite every startup + if [ "${ZITI_BOOTSTRAP_CONFIG}" == true ]; then + makeConfig "${ZITI_CTRL_CONFIG_FILE}" + elif [ "${ZITI_BOOTSTRAP_CONFIG}" == force ]; then + makeConfig "${ZITI_CTRL_CONFIG_FILE}" --force + fi + + # make database unless it exists + if [ "${ZITI_BOOTSTRAP_DATABASE}" == true ]; then + makeDatabase + fi +} diff --git a/dist/dist-packages/linux/openziti-controller/entrypoint.bash b/dist/dist-packages/linux/openziti-controller/entrypoint.bash new file mode 100755 index 000000000..684089664 --- /dev/null +++ b/dist/dist-packages/linux/openziti-controller/entrypoint.bash @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# +# this thin wrapper script for the OpenZiti Controller uses variable assignments from the systemd env file +# + +set -o errexit +set -o nounset +set -o pipefail +# set -o xtrace # debug startup + +if ! (( $# )); then + # if no args, run the controller with the default config file + set -- run config.yml +elif [[ "${1}" == run && -z "${2:-}" ]]; then + # if first arg is "run" and second arg is empty, run the controller with the default config file + set -- run config.yml +fi + +# shellcheck disable=SC1090 # default path is set by the systemd service +source "${ZITI_CTRL_BOOTSTRAP_BASH:-/opt/openziti/etc/controller/bootstrap.bash}" + +# if first arg is "run", bootstrap the controller with the config file +if [ "${1}" == run ]; then + bootstrap "${2}" +fi + +# shellcheck disable=SC2068 +exec ziti controller ${@} diff --git a/dist/dist-packages/linux/openziti-controller/env b/dist/dist-packages/linux/openziti-controller/env new file mode 100644 index 000000000..38d680a9b --- /dev/null +++ b/dist/dist-packages/linux/openziti-controller/env @@ -0,0 +1,42 @@ +# +# this is a systemd env file allowing simple assignments for ziti-controller.service environment +# + +# +# for "ziti pki" and "ziti create config controller" commands in bootstrap.bash +# + +ZITI_CTRL_ADVERTISED_ADDRESS= +# the advertised and listening port of the controller (default: 1280) +ZITI_CTRL_ADVERTISED_PORT= +# the interface address on which to listen (default: 0.0.0.0) +ZITI_CTRL_BIND_ADDRESS=0.0.0.0 + +# +# for "ziti pki" commands in bootstrap.bash +# + +# relative to systemd service WorkingDirectory; e.g., /var/lib/ziti-controller/pki +ZITI_PKI_ROOT=pki +# relative to ZITI_PKI_ROOT; root CA dir; e.g., /var/lib/ziti-controller/pki/root +ZITI_CA_FILE=root +# relative to ZITI_PKI_ROOT; intermediate CA dir; e.g., /var/lib/ziti-controller/pki/intermediate +ZITI_INTERMEDIATE_FILE=intermediate +# relative to intermediate CA "keys" and "certs" dirs +ZITI_SERVER_FILE=server +# relative to intermediate CA "keys" and "certs" dirs +ZITI_CLIENT_FILE=client +# basename of identity files +ZITI_NETWORK_NAME=ctrl + +# +# for "ziti controller edge init" command in bootstrap.bash +# +# path to BoltDB relative to working directory /var/lib/ziti-controller +ZITI_CTRL_DATABASE_FILE=bbolt.db +# must be 4 < 100 characters +ZITI_USER=admin +# for better security, leave this assignment empty and create a file readable only by root containing the +# password and set "LoadCredential=ZITI_PWD:/opt/openziti/etc/controller/.pwd" in +# /lib/systemd/system/ziti-controller.service +ZITI_PWD= diff --git a/dist/dist-packages/linux/openziti-controller/postinstall.bash b/dist/dist-packages/linux/openziti-controller/postinstall.bash new file mode 100755 index 000000000..39c32f815 --- /dev/null +++ b/dist/dist-packages/linux/openziti-controller/postinstall.bash @@ -0,0 +1,231 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +install() { + checkSystemdVersion "${MINIMUM_SYSTEMD_VERSION}" + commonActions + +} + +upgrade() { + # Step 2(upgrade), do what you need + commonActions + +} + +commonActions() { + makeEmptyRestrictedFile "${ZITI_PWD_FILE}" + loadEnvStdin + loadEnvFile + promptBootstrap + promptCtrlAdvertisedAddress + promptCtrlPort + promptPwd +} + +checkSystemdVersion() { + # Step 2 (clean install), enable the service in the proper way for this platform + if ! command -V systemctl &>/dev/null; then + echo "ERROR: required command 'systemctl' is missing" >&2 + return 1 + else + systemd_version=$(systemctl --version | awk '/^systemd/ {print $2}') + fi + + if [ "${systemd_version}" -lt "$1" ]; then + printf "\033[31m systemd version %s is less than %d , aborting \033[0m\n" "${systemd_version}" "$1" + return 1 + fi +} + +makeEmptyRestrictedFile() { + if ! [ -s "$1" ]; then + umask 0177 + touch "$1" + fi +} + +prompt() { + # return true if interactive and response is not empty + if [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && -t 0 ]]; then + read -r -p "$1" response + if [ -n "${response:-}" ]; then + echo "${response}" + else + return 1 + fi + else + echo "DEBUG: non-interactive, unable to prompt for answer: '$1'" >&3 + return 1 + fi +} + +loadEnvStdin() { + local key value + # if not a tty (stdin is redirected), then slurp answers from stdin, e.g., env + # assignments like ZITI_PWD=..., one per line + if [[ ! -t 0 ]]; then + while read -r line; do + key=$(awk -F= '{print $1}' <<< "${line}") + value=$(awk -F= '{print $2}' <<< "${line}") + if [[ -n "${key}" && -n "${value}" ]]; then + if grep -qE "^${key}=" "${ZITI_CTRL_ENV_FILE}"; then + sed -Ei "s/^(${key})=.*/\1=${value}/" "${ZITI_CTRL_ENV_FILE}" + else + echo "${key}=${value}" >> "${ZITI_CTRL_ENV_FILE}" + fi + fi + done + fi +} + +loadEnvFile() { + # shellcheck disable=SC1090 + source "${ZITI_CTRL_ENV_FILE}" +} + +promptCtrlAdvertisedAddress() { + if [ -z "${ZITI_CTRL_ADVERTISED_ADDRESS:-}" ]; then + if ZITI_CTRL_ADVERTISED_ADDRESS="$(prompt "Enter the advertised address for the controller (FQDN) [$DEFAULT_ADDR]: " || echo "$DEFAULT_ADDR")"; then + if [ -n "${ZITI_CTRL_ADVERTISED_ADDRESS:-}" ]; then + sed -Ei "s/^(ZITI_CTRL_ADVERTISED_ADDRESS)=.*/\1=${ZITI_CTRL_ADVERTISED_ADDRESS}/" "${ZITI_CTRL_ENV_FILE}" + fi + else + echo "WARN: missing ZITI_CTRL_ADVERTISED_ADDRESS in ${ZITI_CTRL_ENV_FILE}" >&2 + fi + fi +} + +promptPwd() { + # make ziti vars available in "ziti create config environment" + exportZitiVars + # shellcheck disable=SC1090 # compute the path to the identity file + source <(ZITI_HOME=/var/lib/ziti-controller ziti create config environment) + # do nothing if identity file has stuff in it + if [ -s "${ZITI_CTRL_DATABASE_FILE}" ]; then + echo "INFO: database exists in ${ZITI_CTRL_DATABASE_FILE}" + # prompt for password token if interactive, unless already answered + else + ZITI_BOOTSTRAP_DATABASE=$(awk -F= '/^Environment=ZITI_BOOTSTRAP_DATABASE=/ {print $3}' "${ZITI_CTRL_SVC_FILE}") + if ! [[ "${ZITI_BOOTSTRAP_DATABASE:-}" == true ]]; then + echo "INFO: ZITI_BOOTSTRAP_DATABASE is not true in ${ZITI_CTRL_SVC_FILE}" >&2 + # do nothing if enrollment token is already defined in env file + elif [[ -n "${ZITI_PWD:-}" ]]; then + echo "INFO: ZITI_PWD is defined in ${ZITI_CTRL_ENV_FILE} and will be used to init db during"\ + "next startup" + elif grep -qE "^LoadCredential=ZITI_PWD:${ZITI_PWD_FILE}" "${ZITI_CTRL_SVC_FILE}" \ + && [[ -s "${ZITI_PWD_FILE}" ]]; then + echo "INFO: ZITI_PWD is defined in ${ZITI_PWD_FILE} and will be used to"\ + "init db during next startup " + else + if ZITI_PWD="$(prompt "Enter the admin password: ")"; then + if [ -n "${ZITI_PWD:-}" ]; then + echo "$ZITI_PWD" >| "${ZITI_PWD_FILE}" + fi + else + echo "WARN: missing ZITI_PWD; use LoadCredential in"\ + "${ZITI_CTRL_SVC_FILE} or set in ${ZITI_CTRL_ENV_FILE}" >&2 + fi + fi + fi +} + +promptBootstrap() { + # if undefined, check previous answer in service unit or prompt for bootstrap, preserving default if no answer + if [[ -z "${ZITI_BOOTSTRAP:-}" ]]; then + ZITI_BOOTSTRAP="$(awk -F= '/^Environment=ZITI_BOOTSTRAP=/ {print $3}' "${ZITI_CTRL_SVC_FILE}")" + if [[ -z "${ZITI_BOOTSTRAP}" ]]; then + if ZITI_BOOTSTRAP="$(prompt 'Bootstrap the controller config [Y/n]: ' || echo 'true')"; then + if [[ "${ZITI_BOOTSTRAP}" =~ ^[yY]([eE][sS])?$ ]]; then + ZITI_BOOTSTRAP=true + elif [[ "${ZITI_BOOTSTRAP}" =~ ^[nN][oO]?$ ]]; then + ZITI_BOOTSTRAP=false + fi + sed -Ei "s/^(ZITI_BOOTSTRAP)=.*/\1=${ZITI_BOOTSTRAP}/" "${ZITI_CTRL_ENV_FILE}" + fi + sed -Ei 's/^(Environment=ZITI_BOOTSTRAP=).*/\1'"${ZITI_BOOTSTRAP}"'/' "${ZITI_CTRL_SVC_FILE}" + systemctl daemon-reload + fi + fi + if [[ "${ZITI_BOOTSTRAP}" != true ]]; then + exit 0 + fi +} + +promptCtrlPort() { + # if undefined or default value in env file, prompt for router port, preserving default if no answer + if [[ -z "${ZITI_CTRL_ADVERTISED_PORT:-}" ]]; then + if ZITI_CTRL_ADVERTISED_PORT="$(prompt 'Enter the controller port [1280]: ' || echo '1280')"; then + sed -Ei "s/^(ZITI_CTRL_ADVERTISED_PORT)=.*/\1=${ZITI_CTRL_ADVERTISED_PORT}/" "${ZITI_CTRL_ENV_FILE}" + fi + fi + if [[ "${ZITI_CTRL_ADVERTISED_PORT}" -le 1024 ]]; then + grantNetBindService + fi +} + +grantNetBindService() { + # grant binding privileged low ports unless already granted + if ! grep -qE '^AmbientCapabilities=CAP_NET_BIND_SERVICE' "${ZITI_CTRL_SVC_FILE}"; then + # uncomment the line + sed -Ei 's/.*AmbientCapabilities=CAP_NET_BIND_SERVICE/AmbientCapabilities=CAP_NET_BIND_SERVICE/' "${ZITI_CTRL_SVC_FILE}" + fi + systemctl daemon-reload +} + +exportZitiVars() { + # make ziti vars available in forks like "ziti create config environment" + for line in $(set | grep -e "^ZITI_" | sort); do + # shellcheck disable=SC2013 + for var in $(awk -F= '{print $1}' <<< "$line"); do + # shellcheck disable=SC2163 + export "$var" + done + done +} + +: "${MINIMUM_SYSTEMD_VERSION:=232}" +DEFAULT_ADDR=localhost +ZITI_PWD_FILE=/opt/openziti/etc/controller/.pwd +ZITI_CTRL_ENV_FILE=/opt/openziti/etc/controller/env +ZITI_CTRL_SVC_FILE=/lib/systemd/system/ziti-controller.service + +# initialize a file descriptor for debug output +: "${DEBUG:=0}" +if (( DEBUG )); then + exec 3>&1 + set -o xtrace +else + exec 3>/dev/null +fi + +# Step 1, check if this is a clean install or an upgrade +if (( $# )); then + if [[ $1 == 1 || ($1 == configure && -z ${2:-}) ]]; then + # deb passes $1=configure, rpm passes $1=1 + action=install + elif [[ $1 == 2 || ($1 == configure && -n ${2:-}) ]]; then + # deb passes $1=configure $2=, rpm passes $1=2 + action=upgrade + else + echo "ERROR: unexpected action '$1'" >&2 + exit 1 + fi +else + echo "ERROR: missing action" >&2 + exit 1 +fi + +case "$action" in + "install") + printf "\033[32m Post Install of an clean install\033[0m\n" + install + ;; + "upgrade") + printf "\033[32m Post Install of an upgrade\033[0m\n" + upgrade + ;; +esac diff --git a/dist/dist-packages/linux/openziti-controller/ziti-controller.service b/dist/dist-packages/linux/openziti-controller/ziti-controller.service new file mode 100644 index 000000000..5ffa2c151 --- /dev/null +++ b/dist/dist-packages/linux/openziti-controller/ziti-controller.service @@ -0,0 +1,61 @@ +[Unit] +Description=OpenZiti Controller +After=network-online.target + +[Service] + +# you must set the admin password to bootstrap the database; the bootstrapping script will initialize the default admin +# username and password by loading the password from a file or env var; for security, set permissions to allow read only +# by root only and remove the file after the bootstrapping that occurs on the first run or set ZITI_PWD in +# /opt/openziti/etc/controller/env +# +LoadCredential=ZITI_PWD:/opt/openziti/etc/controller/.pwd + +# +## Optional Permissions +# + +# allow binding low ports, e.g., 443/tcp; NOTE: use TLS passthrough if fronting with a reverse proxy, i.e., "raw" TCP +# proxy +# AmbientCapabilities=CAP_NET_BIND_SERVICE + +# +## +# + +# manage the user and permissions for the service automatically +DynamicUser=yes +EnvironmentFile=/opt/openziti/etc/controller/env +# used by bootstrap.bash to look up /run/credentials/$UNIT_NAME/$CREDENTIAL_NAME +Environment=UNIT_NAME=ziti-controller.service +# BASH script that defines function bootstrap() +Environment=ZITI_CTRL_BOOTSTRAP_BASH=/opt/openziti/etc/controller/bootstrap.bash +# disable JSON logging during bootstrapping +Environment=PFXLOG_NO_JSON=true +# set "false" to disable bootstrapping +Environment=ZITI_BOOTSTRAP= +# create a new PKI unless it exists +Environment=ZITI_BOOTSTRAP_PKI=true +# create a config file unless it exists if "true", set "force" to overwrite (changing the advertised URI will break +# existing enrollments who will be unable to connect to the controller) +Environment=ZITI_BOOTSTRAP_CONFIG=true +# create a new database unless it exists +Environment=ZITI_BOOTSTRAP_DATABASE=true +# renew server and client certificates every startup +Environment=ZITI_AUTO_RENEW_CERTS=true +# create a new config file relative to working directory unless it exists +# absolute path where service will be run +ExecStart=/opt/openziti/etc/controller/entrypoint.bash run config.yml +LimitNOFILE=65535 +Restart=always +RestartSec=3 +# relative to /var/lib +StateDirectory=ziti-controller +#:/opt/openziti/etc/controller/state +# "ziti controller run" is the main process managed by this service and replaces entrypoint.bash +Type=simple +UMask=0007 +WorkingDirectory=/var/lib/ziti-controller + +[Install] +WantedBy=multi-user.target diff --git a/dist/dist-packages/linux/openziti-router/bootstrap.bash b/dist/dist-packages/linux/openziti-router/bootstrap.bash new file mode 100755 index 000000000..a275d9541 --- /dev/null +++ b/dist/dist-packages/linux/openziti-router/bootstrap.bash @@ -0,0 +1,77 @@ +# +# bootstrap the OpenZiti Router with a config file and identity +# + +function makeConfig() { + # + # create config file + # + + if [[ ! -s "${ZITI_ROUTER_CONFIG_FILE}" || "${1:-}" == --force ]]; then + ziti create config router "${ZITI_ROUTER_TYPE}" \ + --tunnelerMode "${ZITI_ROUTER_MODE}" \ + --routerName "${ZITI_ROUTER_NAME}" \ + --output "${ZITI_ROUTER_CONFIG_FILE}" + fi + +} + +function enroll() { + + # shellcheck disable=SC1090 # find the identity file path + source <(ziti create config environment | grep ZITI_ROUTER) + + if [[ ! -s "${ZITI_ROUTER_IDENTITY_CERT}" || "${1:-}" == --force ]]; then + if [ -n "${ZITI_ENROLL_TOKEN:-}" ]; then + # shellcheck disable=SC2188 + ziti router enroll "${ZITI_ROUTER_CONFIG_FILE}" \ + --jwt <(echo "${ZITI_ENROLL_TOKEN}") + elif [ -s "/run/credentials/${UNIT_NAME:=ziti-router.service}/ZITI_ENROLL_TOKEN" ]; then + ziti router enroll "${ZITI_ROUTER_CONFIG_FILE}" \ + --jwt "/run/credentials/${UNIT_NAME}/ZITI_ENROLL_TOKEN" + else + echo "ERROR: use LoadCredential in"\ + " /lib/systemd/system/ziti-router.service or set env var ZITI_ENROLL_TOKEN" >&2 + fi + fi + +} + +function bootstrap() { + + if [ -n "${1:-}" ]; then + ZITI_ROUTER_CONFIG_FILE="${1}" + else + echo "ERROR: no config file path provided" >&2 + return 1 + fi + + # make config file unless it exists if true, set force to overwrite + if [ "${ZITI_BOOTSTRAP_CONFIG}" == true ]; then + makeConfig + elif [ "${ZITI_BOOTSTRAP_CONFIG}" == force ]; then + makeConfig --force + fi + + # enroll unless certificate exists, set "force" to overwrite key and cert (requires new enrollment token) + if [ "${ZITI_BOOTSTRAP_ENROLLMENT}" == true ]; then + enroll + elif [ "${ZITI_BOOTSTRAP_ENROLLMENT}" == force ]; then + enroll --force + fi +} + +# +# defaults +# + +# used by "ziti create config router" and "ziti create config environment" +: "${ZITI_ROUTER_ADVERTISED_ADDRESS:=localhost}" +: "${ZITI_ROUTER_NAME:=router}" +: "${ZITI_CTRL_ADVERTISED_PORT:=1280}" +: "${ZITI_ROUTER_MODE:=none}" +export ZITI_ROUTER_NAME \ + ZITI_ROUTER_ADVERTISED_ADDRESS \ + ZITI_CTRL_ADVERTISED_PORT \ + ZITI_ROUTER_PORT \ + ZITI_ROUTER_LISTENER_BIND_PORT="${ZITI_ROUTER_PORT}" diff --git a/dist/dist-packages/linux/openziti-router/entrypoint.bash b/dist/dist-packages/linux/openziti-router/entrypoint.bash new file mode 100755 index 000000000..537f8961c --- /dev/null +++ b/dist/dist-packages/linux/openziti-router/entrypoint.bash @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# +# this thin wrapper script for the OpenZiti Router uses variable assignments from the systemd env file +# + +set -o errexit +set -o nounset +set -o pipefail +# set -o xtrace # debug startup + +if ! (( $# )); then + # if no args, run the router with the default config file + set -- run config.yml +elif [[ "${1}" == run && -z "${2:-}" ]]; then + # if first arg is "run" and second arg is empty, run the router with the default config file + set -- run config.yml +fi + +# shellcheck disable=SC1090 # default path is assigned in env file +source "${ZITI_ROUTER_BOOTSTRAP_BASH:-/opt/openziti/etc/router/bootstrap.bash}" + +# if first arg is "run", bootstrap the router with the config file +if [ "${1}" == run ]; then + bootstrap "${2}" +fi + +# optionally renew certs at startup +if [ "${ZITI_AUTO_RENEW_CERTS:-}" == true ]; then + # shellcheck disable=SC2068 + set -- ${@} --extend +fi + +# shellcheck disable=SC2068 +exec ziti router ${@} diff --git a/dist/dist-packages/linux/openziti-router/env b/dist/dist-packages/linux/openziti-router/env new file mode 100644 index 000000000..e049a1233 --- /dev/null +++ b/dist/dist-packages/linux/openziti-router/env @@ -0,0 +1,45 @@ +# +# this is a systemd env file allowing simple assignments for ziti-controller.service environment and serves as an answer +# file for first run prompts and unattended installations; only variables that are consumed by the ziti binary are +# expected here to preserve the separation between the service unit and the binary +# + +# +# for "ziti create config router edge" commands in bootstrap.bash +# + +# address of the controller (required) +ZITI_CTRL_ADVERTISED_ADDRESS= +# tcp port of the controller (default: 1280) +ZITI_CTRL_ADVERTISED_PORT= + +# for better security, leave this assignment empty and create a file readable only by root containing the +# token and set "LoadCredential=ZITI_ENROLL_TOKEN:/opt/openziti/etc/router/.token" in +# /lib/systemd/system/ziti-router.service +ZITI_ENROLL_TOKEN= + +# the router's address must be resolvable by other routers and edge identities (default: qualified hostname) +ZITI_ROUTER_ADVERTISED_ADDRESS= +# the advertised and listening port of the router, if <= 1024, then grant the NET_BIND_SERVICE ambient capability in +# /lib/systemd/system/ziti-router.service (default: 3022) +ZITI_ROUTER_PORT= + +# the mode of the router; the router must be administratively created with the --tunneler-enabled flag; if "tproxy" mode +# then grant ambient capbility NET_ADMIN in /lib/systemd/system/ziti-router.service and set the host's DNS resolvers to +# have this router's nameserver as the primary in additional to a secondary, recursive resolver (host, tproxy, proxy; +# default: host) +ZITI_ROUTER_MODE= +# where to listen for DNS requests in tproxy mode (default: udp://127.0.0.1:53) +ZITI_ROUTER_TPROXY_RESOLVER= +# CIDR range of IP addresses to assign to DNS clients in tproxy mode (default: 100.64.0.1/10) +ZITI_ROUTER_DNS_IP_RANGE= + +# the interface address on which to listen (default: 0.0.0.0) +ZITI_ROUTER_BIND_ADDRESS= + +# set identity filenames (default: router) +ZITI_ROUTER_NAME=router + +# type of router (default: edge, options: edge, fabric) +ZITI_ROUTER_TYPE=edge + diff --git a/dist/dist-packages/linux/openziti-router/postinstall.bash b/dist/dist-packages/linux/openziti-router/postinstall.bash new file mode 100755 index 000000000..db15f00f1 --- /dev/null +++ b/dist/dist-packages/linux/openziti-router/postinstall.bash @@ -0,0 +1,280 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail +# set -o xtrace + +install() { + checkSystemdVersion $MINIMUM_SYSTEMD_VERSION + commonActions + +} + +upgrade() { + # Step 2(upgrade), do what you need + commonActions + +} + +commonActions() { + makeEmptyRestrictedFile "${ZITI_ENROLL_TOKEN_FILE}" + loadEnvStdin + loadEnvFile + promptBootstrap + promptCtrlAdvertisedAddress + promptCtrlPort + promptRouterAdvertisedAddress + promptRouterPort + promptEnrollToken + promptRouterMode +} + +checkSystemdVersion() { + # Step 2 (clean install), enable the service in the proper way for this platform + if ! command -V systemctl &>/dev/null; then + echo "ERROR: required command 'systemctl' is missing" >&2 + return 1 + else + systemd_version=$(systemctl --version | awk '/^systemd/ {print $2}') + fi + + if [ "${systemd_version}" -lt "$1" ]; then + printf "\033[31m systemd version %s is less than %d , aborting \033[0m\n" "${systemd_version}" "$1" + return 1 + fi +} + +makeEmptyRestrictedFile() { + if ! [ -s "$1" ]; then + umask 0177 + touch "$1" + fi +} + +prompt() { + # return true if interactive and response is not empty + if [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && -t 0 ]]; then + read -r -p "$1" response + if [ -n "${response:-}" ]; then + echo "${response}" + else + return 1 + fi + else + echo "DEBUG: non-interactive, unable to prompt for answer: '$1'" >&3 + return 1 + fi +} + +loadEnvStdin() { + local key value + # if not a tty (stdin is redirected), then slurp answers from stdin, e.g., env + # assignments like ZITI_ENROLL_TOKEN=..., one per line + if [[ ! -t 0 ]]; then + while read -r line; do + key=$(awk -F= '{print $1}' <<< "${line}") + value=$(awk -F= '{print $2}' <<< "${line}") + if [[ -n "${key}" && -n "${value}" ]]; then + if grep -qE "^${key}=" "${ZITI_CTRL_ENV_FILE}"; then + sed -Ei "s/^(${key})=.*/\1=${value}/" "${ZITI_CTRL_ENV_FILE}" + else + echo "${key}=${value}" >> "${ZITI_CTRL_ENV_FILE}" + fi + fi + done + fi +} + +loadEnvFile() { + # shellcheck disable=SC1090 + source "${ZITI_ROUTER_ENV_FILE}" +} + +promptCtrlAdvertisedAddress() { + if [ -z "${ZITI_CTRL_ADVERTISED_ADDRESS:-}" ]; then + if ZITI_CTRL_ADVERTISED_ADDRESS="$(prompt "Enter the advertised address for the controller (FQDN) [$DEFAULT_ADDR]: " || echo "$DEFAULT_ADDR")"; then + if [ -n "${ZITI_CTRL_ADVERTISED_ADDRESS:-}" ]; then + sed -Ei "s/^(ZITI_CTRL_ADVERTISED_ADDRESS)=.*/\1=${ZITI_CTRL_ADVERTISED_ADDRESS}/" "${ZITI_ROUTER_ENV_FILE}" + fi + else + echo "WARN: missing ZITI_CTRL_ADVERTISED_ADDRESS in ${ZITI_ROUTER_ENV_FILE}" >&2 + fi + fi +} + +promptCtrlPort() { + # if undefined or default value in env file, prompt for router port, preserving default if no answer + if [[ -z "${ZITI_CTRL_ADVERTISED_PORT:-}" ]]; then + if ZITI_CTRL_ADVERTISED_PORT="$(prompt 'Enter the controller port [1280]: ' || echo '1280')"; then + sed -Ei "s/^(ZITI_CTRL_ADVERTISED_PORT)=.*/\1=${ZITI_CTRL_ADVERTISED_PORT}/" "${ZITI_ROUTER_ENV_FILE}" + fi + fi +} + +promptRouterAdvertisedAddress() { + if [ -z "${ZITI_ROUTER_ADVERTISED_ADDRESS:-}" ]; then + if ZITI_ROUTER_ADVERTISED_ADDRESS="$(prompt "Enter the advertised address for this router (FQDN) [$DEFAULT_ADDR]: " || echo "$DEFAULT_ADDR")"; then + sed -Ei "s/^(ZITI_ROUTER_ADVERTISED_ADDRESS)=.*/\1=${ZITI_ROUTER_ADVERTISED_ADDRESS}/" "${ZITI_ROUTER_ENV_FILE}" + fi + fi +} + +promptEnrollToken() { + # make ziti vars available in "ziti create config environment" + exportZitiVars + # shellcheck disable=SC1090 # compute the path to the identity file + source <(ZITI_HOME=/var/lib/ziti-router ziti create config environment) + # do nothing if identity file has stuff in it + if [ -s "${ZITI_ROUTER_IDENTITY_CERT}" ]; then + echo "INFO: enrolled identity exists in ${ZITI_ROUTER_IDENTITY_CERT}" + # prompt for enrollment token if interactive, unless already answered + else + ZITI_BOOTSTRAP_ENROLLMENT=$(awk -F= '/^Environment=ZITI_BOOTSTRAP_ENROLLMENT=/ {print $3}' "${ZITI_ROUTER_SVC_FILE}") + if ! [[ "${ZITI_BOOTSTRAP_ENROLLMENT:-}" == true ]]; then + echo "INFO: ZITI_BOOTSTRAP_ENROLLMENT is not true in ${ZITI_ROUTER_SVC_FILE}" >&2 + # do nothing if enrollment token is already defined in env file + elif [[ -n "${ZITI_ENROLL_TOKEN:-}" ]]; then + echo "INFO: ZITI_ENROLL_TOKEN is defined in ${ZITI_ROUTER_ENV_FILE} and will be used to enroll during"\ + "next startup" + elif grep -qE "^LoadCredential=ZITI_ENROLL_TOKEN:${ZITI_ENROLL_TOKEN_FILE}" \ + "${ZITI_ROUTER_SVC_FILE}" \ + && [[ -s "${ZITI_ENROLL_TOKEN_FILE}" ]]; then + echo "INFO: ZITI_ENROLL_TOKEN is defined in ${ZITI_ENROLL_TOKEN_FILE} and will be used to"\ + "enroll during next startup " + else + if ZITI_ENROLL_TOKEN=$(prompt "Enter the enrollment token: "); then + if [ -n "${ZITI_ENROLL_TOKEN:-}" ]; then + echo "$ZITI_ENROLL_TOKEN" >| "${ZITI_ENROLL_TOKEN_FILE}" + fi + else + echo "WARN: missing ZITI_ENROLL_TOKEN; use LoadCredential in"\ + "${ZITI_ROUTER_SVC_FILE} or set in ${ZITI_ROUTER_ENV_FILE}" >&2 + fi + fi + fi +} + +promptRouterMode() { + # if undefined or default value in env file, prompt for router mode, preserving default if no answer + if [[ -z "${ZITI_ROUTER_MODE:-}" ]]; then + if ZITI_ROUTER_MODE="$(prompt 'Enter the router mode (eg. host, tproxy, proxy) [none]: ' || echo 'none')"; then + sed -Ei "s/^(ZITI_ROUTER_MODE)=.*/\1=${ZITI_ROUTER_MODE}/" "${ZITI_ROUTER_ENV_FILE}" + fi + fi + # grant kernel capability NET_ADMIN if tproxy mode + if [[ "${ZITI_ROUTER_MODE}" == tproxy ]]; then + grantNetAdmin + # also grant NET_BIND_SERVICE if resolver port is default 53 or defined <= 1024 + RESOLVER_PORT="${ZITI_ROUTER_TPROXY_RESOLVER##*:}" + if [[ -z "${RESOLVER_PORT}" || "${RESOLVER_PORT}" -le 1024 ]]; then + grantNetBindService + fi + fi +} + +promptBootstrap() { + # if undefined, check previous answer in service unit or prompt for bootstrap, preserving default if no answer + if [[ -z "${ZITI_BOOTSTRAP:-}" ]]; then + ZITI_BOOTSTRAP="$(awk -F= '/^Environment=ZITI_BOOTSTRAP=/ {print $3}' "${ZITI_ROUTER_SVC_FILE}")" + if [[ -z "${ZITI_BOOTSTRAP}" ]]; then + if ZITI_BOOTSTRAP="$(prompt 'Bootstrap the router config [Y/n]: ' || echo 'true')"; then + if [[ "${ZITI_BOOTSTRAP}" =~ ^[yY]([eE][sS])?$ ]]; then + ZITI_BOOTSTRAP=true + elif [[ "${ZITI_BOOTSTRAP}" =~ ^[nN][oO]?$ ]]; then + ZITI_BOOTSTRAP=false + fi + sed -Ei "s/^(ZITI_BOOTSTRAP)=.*/\1=${ZITI_BOOTSTRAP}/" "${ZITI_ROUTER_ENV_FILE}" + fi + sed -Ei 's/^(Environment=ZITI_BOOTSTRAP=).*/\1'"${ZITI_BOOTSTRAP}"'/' "${ZITI_ROUTER_SVC_FILE}" + systemctl daemon-reload + fi + fi + if [[ "${ZITI_BOOTSTRAP}" != true ]]; then + exit 0 + fi +} + +grantNetAdmin() { + # grant ambient capabilities to the router process if not already granted + if ! grep -qE '^AmbientCapabilities=CAP_NET_ADMIN' "${ZITI_ROUTER_SVC_FILE}"; then + # uncomment the line + sed -Ei 's/.*AmbientCapabilities=CAP_NET_ADMIN/AmbientCapabilities=CAP_NET_ADMIN/' "${ZITI_ROUTER_SVC_FILE}" + fi + systemctl daemon-reload +} + +promptRouterPort() { + # if undefined or default value in env file, prompt for router port, preserving default if no answer + if [[ -z "${ZITI_ROUTER_PORT:-}" ]]; then + if ZITI_ROUTER_PORT="$(prompt 'Enter the router port [3022]: ' || echo '3022')"; then + sed -Ei "s/^(ZITI_ROUTER_PORT)=.*/\1=${ZITI_ROUTER_PORT}/" "${ZITI_ROUTER_ENV_FILE}" + fi + fi + if [[ "${ZITI_ROUTER_PORT}" -le 1024 ]]; then + grantNetBindService + fi +} + +grantNetBindService() { + # grant binding privileged low ports unless already granted + if ! grep -qE '^AmbientCapabilities=CAP_NET_BIND_SERVICE' "${ZITI_ROUTER_SVC_FILE}"; then + # uncomment the line + sed -Ei 's/.*AmbientCapabilities=CAP_NET_BIND_SERVICE/AmbientCapabilities=CAP_NET_BIND_SERVICE/' "${ZITI_ROUTER_SVC_FILE}" + fi + systemctl daemon-reload +} + +exportZitiVars() { + # make ziti vars available in forks like "ziti create config environment" + for line in $(set | grep -e "^ZITI_" | sort); do + # shellcheck disable=SC2013 + for var in $(awk -F= '{print $1}' <<< "$line"); do + # shellcheck disable=SC2163 + export "$var" + done + done +} + +: "${MINIMUM_SYSTEMD_VERSION:=232}" +DEFAULT_ADDR=localhost +ZITI_ENROLL_TOKEN_FILE=/opt/openziti/etc/router/.token +ZITI_ROUTER_ENV_FILE=/opt/openziti/etc/router/env +ZITI_ROUTER_SVC_FILE=/lib/systemd/system/ziti-router.service + +# initialize a file descriptor for debug output +: "${DEBUG:=0}" +if (( DEBUG )); then + exec 3>&1 + set -o xtrace +else + exec 3>/dev/null +fi + +# Step 1, check if this is a clean install or an upgrade +if (( $# )); then + if [[ $1 == 1 || ($1 == configure && -z ${2:-}) ]]; then + # deb passes $1=configure, rpm passes $1=1 + action=install + elif [[ $1 == 2 || ($1 == configure && -n ${2:-}) ]]; then + # deb passes $1=configure $2=, rpm passes $1=2 + action=upgrade + else + echo "ERROR: unexpected action '$1'" >&2 + exit 1 + fi +else + echo "ERROR: missing action" >&2 + exit 1 +fi + +case "$action" in + "install") + printf "\033[32m Post Install of an clean install\033[0m\n" + install + ;; + "upgrade") + printf "\033[32m Post Install of an upgrade\033[0m\n" + upgrade + ;; +esac diff --git a/dist/dist-packages/linux/openziti-router/preremove.bash b/dist/dist-packages/linux/openziti-router/preremove.bash new file mode 100755 index 000000000..e7d272675 --- /dev/null +++ b/dist/dist-packages/linux/openziti-router/preremove.bash @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail +# set -o xtrace + +# if it exists and is still empty, clean up the enrollment token file that was created by postinstall.bash, allowing the +# package manager to remove the empty directory +ZITI_ENROLL_TOKEN_FILE=/opt/openziti/etc/router/.token +if [ -e "${ZITI_ENROLL_TOKEN_FILE}" ]; then + if ! [ -s "${ZITI_ENROLL_TOKEN_FILE}" ]; then + rm -f "${ZITI_ENROLL_TOKEN_FILE}" + fi +fi diff --git a/dist/dist-packages/linux/openziti-router/ziti-router.service b/dist/dist-packages/linux/openziti-router/ziti-router.service new file mode 100644 index 000000000..e2c2358b1 --- /dev/null +++ b/dist/dist-packages/linux/openziti-router/ziti-router.service @@ -0,0 +1,59 @@ +[Unit] +Description=OpenZiti Router +After=network-online.target + +[Service] + +# you must provide an enrollment token to enroll the router at first startup +LoadCredential=ZITI_ENROLL_TOKEN:/opt/openziti/etc/router/.token + +# +## extra permissions +# + +# allow binding low ports, e.g., 443/tcp; required when ZITI_ROUTER_MODE=tproxy or ZITI_ROUTER_PORT <= 1024 +# AmbientCapabilities=CAP_NET_BIND_SERVICE +# allow adding IP routes and iptables rules; required when ZITI_ROUTER_MODE=tproxy +# AmbientCapabilities=CAP_NET_ADMIN + +# +## options +# + +# additional environment variables used by ziti commands in bootstrap.bash +EnvironmentFile=/opt/openziti/etc/router/env +# used by bootstrap.bash to look up /run/credentials/$UNIT_NAME/$CREDENTIAL_NAME +Environment=UNIT_NAME=ziti-router.service +# disable JSON logging +Environment=PFXLOG_NO_JSON=true +# set "false" to disable bootstrapping +Environment=ZITI_BOOTSTRAP= +# create a config file unless it exists if "true", set "force" to overwrite +Environment=ZITI_BOOTSTRAP_CONFIG=true +# enroll unless already enrolled if "true", set "force" to overwrite key and cert (requires new enrollment token) +Environment=ZITI_BOOTSTRAP_ENROLLMENT=true +# set the bootstrap function definitions for entrypoint.bash to source +Environment=ZITI_ROUTER_BOOTSTRAP_BASH=/opt/openziti/etc/router/bootstrap.bash +# renew server and client certificates every startup +Environment=ZITI_AUTO_RENEW_CERTS=true + +# +## misc +# + +# manage the user and permissions for the service automatically +DynamicUser=yes +# relative to /var/lib +StateDirectory=ziti-router +# absolute path where service will be run +WorkingDirectory=/var/lib/ziti-router +# "ziti router run" is the main process managed by this service and replaces entrypoint.bash +Type=simple +UMask=0007 +Restart=always +RestartSec=3 +LimitNOFILE=65535 +ExecStart=/opt/openziti/etc/router/entrypoint.bash run config.yml + +[Install] +WantedBy=multi-user.target diff --git a/dist/docker-images/ziti-cli/Dockerfile b/dist/docker-images/ziti-cli/Dockerfile index c6fbbb3f7..85ec12fa1 100644 --- a/dist/docker-images/ziti-cli/Dockerfile +++ b/dist/docker-images/ziti-cli/Dockerfile @@ -31,7 +31,7 @@ LABEL name="openziti/ziti-cli" \ USER root ### install packages -RUN INSTALL_PKGS="python3.11 python3.11-pip tar bash-completion vim-minimal less shadow-utils jq findutils" && \ +RUN INSTALL_PKGS="python3.11 python3.11-pip tar bash-completion vim-minimal less shadow-utils jq findutils hostname" && \ microdnf -y update --setopt=install_weak_deps=0 --setopt=tsflags=nodocs && \ microdnf -y install --setopt=install_weak_deps=0 --setopt=tsflags=nodocs ${INSTALL_PKGS} && \ microdnf clean all @@ -58,9 +58,6 @@ RUN chmod 0755 /usr/local/bin/ziti RUN /usr/local/bin/ziti completion bash > /etc/bash_completion.d/ziti_cli -COPY ${DOCKER_BUILD_DIR}/entrypoint.sh / -RUN chmod +x /entrypoint.sh USER ziggy COPY ${DOCKER_BUILD_DIR}/bashrc /home/ziggy/.bashrc -ENTRYPOINT [ "/entrypoint.sh" ] -CMD [ "ziti" ] +ENTRYPOINT [ "ziti" ] diff --git a/dist/docker-images/ziti-cli/entrypoint.sh b/dist/docker-images/ziti-cli/entrypoint.sh deleted file mode 100644 index e6d33f04c..000000000 --- a/dist/docker-images/ziti-cli/entrypoint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -exec ziti "${@}" diff --git a/dist/docker-images/ziti-controller/Dockerfile b/dist/docker-images/ziti-controller/Dockerfile index 25688bcaf..3718f9116 100644 --- a/dist/docker-images/ziti-controller/Dockerfile +++ b/dist/docker-images/ziti-controller/Dockerfile @@ -3,6 +3,9 @@ ARG ZITI_CLI_IMAGE="docker.io/openziti/ziti-cli" # this builds docker.io/openziti/ziti-controller FROM ${ZITI_CLI_IMAGE}:${ZITI_CLI_TAG} +ARG CONTROLLER_PACKAGE=./dist/dist-packages/linux/openziti-controller +ARG DOCKER_BUILD_DIR=. + # This build stage grabs artifacts that are copied into the final image. # It uses the same base as the final image to maximize docker cache hits. @@ -13,9 +16,40 @@ LABEL name="openziti/ziti-controller" \ summary="Run the OpenZiti Controller" \ description="Run the OpenZiti Controller" +# set up image as root, then drop privs to ziggy USER root -COPY ./entrypoint.sh / -RUN chmod +x /entrypoint.sh + +# selectively toggle bootstrapping steps +ENV ZITI_BOOTSTRAP_PKI=true +ENV ZITI_BOOTSTRAP_CONFIG=true +ENV ZITI_BOOTSTRAP_DATABASE=true + +# defaults for bootstrapping PKI +ENV ZITI_PKI_ROOT=pki +ENV ZITI_CA_FILE=root +ENV ZITI_INTERMEDIATE_FILE=intermediate +ENV ZITI_SERVER_FILE=server +ENV ZITI_CLIENT_FILE=client + +# defaults for bootstrapping config +ENV ZITI_CTRL_ADVERTISED_PORT=1280 + +# defaults for bootstrapping database +ENV ZITI_CTRL_DATABASE_FILE=bbolt.db +ENV ZITI_USER=admin + +# emit human-friendly text logs +ENV PFXLOG_NO_JSON=true +# used by entrypoint.bash to source the bootstrapping script in this image +ENV ZITI_CTRL_BOOTSTRAP_BASH=/bootstrap.bash +# used by "ziti create config controller" as filename for the controller's identity files +ENV ZITI_NETWORK_NAME=ctrl +# used by ziti to format timestamps in output +ENV ZITI_TIME_FORMAT=utc + +COPY ${CONTROLLER_PACKAGE}/bootstrap.bash ${ZITI_CTRL_BOOTSTRAP_BASH} +COPY ${CONTROLLER_PACKAGE}/entrypoint.bash / + +# run as ziggy (2171:2171) by default USER ziggy -ENTRYPOINT [ "/entrypoint.sh" ] -CMD [ "run" ] +ENTRYPOINT [ "/entrypoint.bash" ] diff --git a/dist/docker-images/ziti-controller/README.md b/dist/docker-images/ziti-controller/README.md new file mode 100644 index 000000000..7a9cbd94d --- /dev/null +++ b/dist/docker-images/ziti-controller/README.md @@ -0,0 +1,41 @@ + +# Run Ziti Controller in Docker + +You can use this container image to run a Ziti Controller in a Docker container. + +## Container Image + +The `openziti/ziti-controller` image is thin and is based on the `openziti/ziti-cli` image, which only provides the +`ziti` CLI. The `ziti-controller` image uses the same bootstrapping defaults and option variables as the Linux package. + +## Docker Compose + +The included `compose.yml` demonstrates how to bootstrap a controller container. + +### Example + +At a minimum, you must set the address and password options in the parent env or set every recurrence in the compose file. + +```bash +ZITI_PWD="mypass" \ +ZITI_CTRL_ADVERTISED_ADDRESS=ctrl.127.0.0.1.sslip.io \ + docker compose up +``` + +After a few seconds, `docker compose ps` will show a "healthy" status for the controller. + +Then, you may log in to the controller using the `ziti` CLI. + +```bash +ziti edge login ctrl.127.0.0.1.sslip.io:1280 -u admin -p mypass +``` + +It's not always necessary to publish ports on every one of the Docker host's interfaces. You can instead publish the +controller port only on a particular interface address by setting `ZITI_INTERFACE`. + +```bash +ZITI_PWD="mypass" \ +ZITI_INTERFACE=127.21.71.0 \ +ZITI_CTRL_ADVERTISED_ADDRESS=ctrl.127.21.71.0.sslip.io \ + docker compose up +``` diff --git a/dist/docker-images/ziti-controller/compose.yml b/dist/docker-images/ziti-controller/compose.yml new file mode 100644 index 000000000..dd314eb9c --- /dev/null +++ b/dist/docker-images/ziti-controller/compose.yml @@ -0,0 +1,54 @@ + +volumes: + ziti-controller: + driver: local + +services: + chown-controller: + image: busybox + command: chown -R ${ZIGGY_UID:-2171} /mnt + volumes: + - ziti-controller:/mnt + + ziti-controller: + image: ${ZITI_CONTROLLER_IMAGE:-openziti/ziti-controller} + depends_on: + chown-volume: + condition: service_completed_successfully + volumes: + - ziti-controller:/mnt + working_dir: /mnt + # assign override vars in an .env file or export from parent env to ensure consistency throughout the compose + # project + environment: + # *** these are the important vars to set to bootstrap the configuration during first run*** + ZITI_CTRL_ADVERTISED_ADDRESS: ${ZITI_CTRL_ADVERTISED_ADDRESS:-ziti-controller} # FQDN of the controller + ZITI_CTRL_ADVERTISED_PORT: ${ZITI_CTRL_ADVERTISED_PORT:-1280} # TCP port of the controller + ZITI_PWD: ${ZITI_PWD:-} # password for the default admin user + + # *** less relevant vars below *** + ZITI_BOOTSTRAP: true # bootstrap the controller if "true" + ZITI_BOOTSTRAP_PKI: true # make the default PKI if "true"; requires ZITI_BOOTSTRAP=true + ZITI_BOOTSTRAP_CONFIG: true # make config file from env vars and defaults if "true," overwrite if "force"; requires ZITI_BOOTSTRAP=true + ZITI_BOOTSTRAP_DATABASE: true # make the default admin user if "true"; requires ZITI_BOOTSTRAP=true + ZITI_AUTO_RENEW_CERTS: true # renew certs automatically every startup; requires ZITI_BOOTSTRAP_PKI=true + ZITI_NETWORK_NAME: ctrl # identity files basename + PFXLOG_NO_JSON: true # disable JSON logging + ZITI_TIME_FORMAT: utc # time format for logs + command: run config.yml + ports: + # ensure this port matches the value of ZITI_CTRL_PORT in the container + - ${ZITI_INTERFACE:-0.0.0.0}:${ZITI_CTRL_ADVERTISED_PORT:-1280}:${ZITI_CTRL_ADVERTISED_PORT:-1280} + expose: + - ${ZITI_CTRL_ADVERTISED_PORT:-1280} + restart: unless-stopped + healthcheck: + test: + - CMD + - ziti + - agent + - stats + interval: 3s + timeout: 3s + retries: 5 + start_period: 15s diff --git a/dist/docker-images/ziti-controller/entrypoint.sh b/dist/docker-images/ziti-controller/entrypoint.sh deleted file mode 100644 index cb055cc80..000000000 --- a/dist/docker-images/ziti-controller/entrypoint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -exec ziti controller "${@}" diff --git a/dist/docker-images/ziti-router/Dockerfile b/dist/docker-images/ziti-router/Dockerfile index 1a3a82134..b42ce7ad5 100644 --- a/dist/docker-images/ziti-router/Dockerfile +++ b/dist/docker-images/ziti-router/Dockerfile @@ -3,19 +3,37 @@ ARG ZITI_CLI_IMAGE="docker.io/openziti/ziti-cli" # this builds docker.io/openziti/ziti-router FROM ${ZITI_CLI_IMAGE}:${ZITI_CLI_TAG} -# This build stage grabs artifacts that are copied into the final image. -# It uses the same base as the final image to maximize docker cache hits. +ARG ROUTER_PACKAGE=./dist/dist-packages/linux/openziti-router +ARG DOCKER_BUILD_DIR=. -### Required OpenShift Labels +### Required OpenShift Labels LABEL name="openziti/ziti-router" \ maintainer="developers@openziti.org" \ vendor="NetFoundry" \ summary="Run the OpenZiti Router" \ description="Run the OpenZiti Router" +# set up image as root, then drop privs to ziggy USER root -COPY ./entrypoint.sh / -RUN chmod +x /entrypoint.sh + +RUN INSTALL_PKGS="util-linux iptables" && \ + microdnf -y update --setopt=install_weak_deps=0 --setopt=tsflags=nodocs && \ + microdnf -y install --setopt=install_weak_deps=0 --setopt=tsflags=nodocs ${INSTALL_PKGS} + +# set a var for entrypoint.bash to find this script +ENV ZITI_ROUTER_BOOTSTRAP_BASH=/bootstrap.bash +ENV ZITI_ROUTER_TYPE=edge +ENV ZITI_ROUTER_PORT=3022 +ENV ZITI_BOOTSTRAP_CONFIG=true +ENV ZITI_BOOTSTRAP_ENROLLMENT=true +ENV PFXLOG_NO_JSON=true +COPY ${ROUTER_PACKAGE}/bootstrap.bash ${ZITI_ROUTER_BOOTSTRAP_BASH} +COPY ${ROUTER_PACKAGE}/entrypoint.bash / + +WORKDIR /ziti-router +RUN chown -R ziggy:ziggy /ziti-router + +# run as ziggy (2171:2171) by default, override run-as user with root when DOCKER_ROUTER_MODE=tproxy USER ziggy -ENTRYPOINT [ "/entrypoint.sh" ] -CMD [ "run" ] + +ENTRYPOINT [ "/entrypoint.bash" ] diff --git a/dist/docker-images/ziti-router/README.md b/dist/docker-images/ziti-router/README.md new file mode 100644 index 000000000..834c25942 --- /dev/null +++ b/dist/docker-images/ziti-router/README.md @@ -0,0 +1,92 @@ + +# Run Ziti Router in Docker + +You can use this container image to run Ziti Router in a Docker container. + +## Container Image + +The `openziti/ziti-router` image is thin and is based on the `openziti/ziti-cli` image, which only provides the `ziti` +CLI. The `ziti-router` image simply adds the `ziti router` subcommand to prefix the args you supply. + +## Docker Compose + +The included `compose.yml` demonstrates how to bootstrap a router and assumes you have the enrollment token and know the +address of the controller, i.e., the `ctrl.endpoint` of the control plane listener provided by the OpenZiti controller. + +### TPROXY Example + +This demonstrates how to use the `openziti/ziti-router` image to run a Ziti Router in a Docker container to configure +the network namespace of another container to use the Ziti network. + +```bash +# fetch the compose file for the ziti-router image +wget -O ./compose.router.yml https://get.openziti.io/dist/docker-images/ziti-router/compose.yml + +# run the quickstart network in the background to provide the ctrl.endpoint at quickstart:1280 +wget -O ./compose.quickstart.yml https://get.openziti.io/dock/all-in-one/compose.yml + +# patch the Compose project to use the quickstart network and provide a web server to test the hello service +cat <./compose.tproxy.yml +services: + # add a hello web server to use for a Ziti service target + hello: + image: openziti/hello-world + expose: + - 8000 + networks: + - quickstart + + # add a web client that waits for a healthy tproxy router + tproxy-demo-client: + image: busybox + network_mode: service:ziti-router + depends_on: + ziti-router: + condition: service_healthy + command: wget --output-document=- http://hello.internal/ + + # link the router to the quickstart network so it can reach the Ziti controller + ziti-router: + networks: + - quickstart +EOF +export COMPOSE_FILE=compose.router.yml:compose.quickstart.yml:compose.tproxy.yml + +# run the Ziti controller in the background with the all-in-one quickstart container +docker compose up quickstart-check + +# start the hello web server listening on 8000 +docker compose up hello --detach + +# log in to the Ziti controller +ziti edge login 127.0.0.1:1280 -y -u admin -p admin + +# create a Ziti service for the hello web server +ziti edge secure hello tcp:hello:8000 \ + --interceptAddress=hello.internal + +# grant the quickstart router permission to bind (provide) the hello service +ziti edge update identity quickstart-router \ + --role-attributes=hello.servers + +# create a second Ziti router to use as a tproxy client +ziti edge create edge-router "tproxy-router" \ + --jwt-output-file=./tproxy-router.jwt \ + --tunneler-enabled + +# grant the tproxy client permission to dial (consume) the hello service +ziti edge update identity tproxy-router \ + --role-attributes=hello.clients + +# simulate policies to check for authorization problems +ziti edge policy-advisor services -q + +# run the demo client which triggers the run of the tproxy router because it is a dependency +ZITI_ROUTER_JWT="$(<./tproxyRouter.jwt)" \ +ZITI_ROUTER_MODE=tproxy \ +ZITI_CTRL_ADVERTISED_ADDRESS=quickstart \ +ZITI_CTRL_ADVERTISED_PORT=1280 \ +ZITI_ROUTER_PORT=3023 \ +ZITI_ROUTER_ADVERTISED_ADDRESS=ziti-router \ + docker compose up tproxy-demo-client +``` diff --git a/dist/docker-images/ziti-router/compose.yml b/dist/docker-images/ziti-router/compose.yml new file mode 100644 index 000000000..123bf82da --- /dev/null +++ b/dist/docker-images/ziti-router/compose.yml @@ -0,0 +1,67 @@ + +volumes: + ziti-router: + driver: local + +services: + chown-router: + image: busybox + command: chown -R ${ZIGGY_UID:-2171} /mnt + volumes: + - ziti-router:/mnt + + ziti-router: + image: ${ZITI_ROUTER_IMAGE:-openziti/ziti-router} + depends_on: + chown-volume: + condition: service_completed_successfully + volumes: + - ziti-router:/mnt + working_dir: /mnt + # these declared vars pass through to container and should be assigned in an .env file or exported from parent env + # to ensure consistency throughout the compose project + environment: + # *** these are the important vars to set *** + ZITI_CTRL_ADVERTISED_ADDRESS: ${ZITI_CTRL_ADVERTISED_ADDRESS:-ziti-controller} # domain name of the controller (required) + ZITI_CTRL_ADVERTISED_PORT: ${ZITI_CTRL_ADVERTISED_PORT:-1280} # exposed port of the controller + ZITI_ENROLL_TOKEN: ${ZITI_ENROLL_TOKEN:-} # enrollment token for this router (required) + ZITI_ROUTER_ADVERTISED_ADDRESS: ${ZITI_ROUTER_ADVERTISED_ADDRESS:-ziti-router} # domain name for this router (default: the container ID [hostname -f]) + ZITI_ROUTER_PORT: ${ZITI_ROUTER_PORT-3022} # exposed port for this router + ZITI_ROUTER_MODE: ${ZITI_ROUTER_MODE:-none} # none, host, tproxy, tproxy (default: none, tproxy requires additional config below) + + # *** less relevant vars below *** + ZITI_BOOTSTRAP: true # bootstrap the router if "true" + ZITI_BOOTSTRAP_CONFIG: true # make config file from env vars and defaults if "true," overwrite if "force"; requires ZITI_BOOTSTRAP=true + ZITI_BOOTSTRAP_ENROLLMENT: true # enroll with controller if "true," overwrite if "force"; requires ZITI_BOOTSTRAP=true + ZITI_ROUTER_TYPE: edge # edge, fabric + ZITI_ROUTER_NAME: router # identity files basename + PFXLOG_NO_JSON: true # disable JSON logging + ZITI_TIME_FORMAT: utc # time format for logs + command: run config.yml + ports: + # ensure this port matches the value of ZITI_ROUTER_PORT in the container + - ${ZITI_INTERFACE:-0.0.0.0}:${ZITI_ROUTER_PORT:-3022}:${ZITI_ROUTER_PORT:-3022} + expose: + - ${ZITI_ROUTER_PORT:-3022} + restart: unless-stopped + healthcheck: + test: + - CMD + - ziti + - agent + - stats + interval: 3s + timeout: 3s + retries: 5 + start_period: 15s + + # Additional config for other containers using this router as a transparent intercepting proxy sidecar and default + # nameserver - dns, user, cap_add are required when ZITI_ROUTER_MODE=tproxy (see adjacent README.md for TPROXY + # example) + # + # dns: + # - 127.0.0.1 # this router's Ziti resolver + # - 1.1.1.1 # any recursive resolver + # user: root # required to create TPROXY routes in a container? + # cap_add: + # - NET_ADMIN # required to create TPROXY rules diff --git a/dist/docker-images/ziti-router/entrypoint.sh b/dist/docker-images/ziti-router/entrypoint.sh deleted file mode 100644 index 8bb695e7a..000000000 --- a/dist/docker-images/ziti-router/entrypoint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -exec ziti router "${@}" diff --git a/ziti/cmd/create/config_templates/controller.yml b/ziti/cmd/create/config_templates/controller.yml index 49f40f11f..83f79bee6 100644 --- a/ziti/cmd/create/config_templates/controller.yml +++ b/ziti/cmd/create/config_templates/controller.yml @@ -13,7 +13,7 @@ raft: dataDir: "{{ .ZitiHome }}/raft" minClusterSize: {{ .Controller.Ctrl.MinClusterSize }} {{ else }} -db: "{{ .ZitiHome }}/db/ctrl.db" +db: "{{ .Controller.Database.DatabaseFile }}" # uncomment and configure to enable HA # raft: # dataDir: "{{ .ZitiHome }}/raft" diff --git a/ziti/cmd/create/config_templates/router.yml b/ziti/cmd/create/config_templates/router.yml index c383e1c4f..2a71d482c 100644 --- a/ziti/cmd/create/config_templates/router.yml +++ b/ziti/cmd/create/config_templates/router.yml @@ -40,8 +40,13 @@ link: {{ if or .Router.IsFabric (eq .Router.TunnelerMode "none") }}#{{ end }} - binding: tunnel {{ if or .Router.IsFabric (eq .Router.TunnelerMode "none") }}#{{ end }} options: {{ if or .Router.IsFabric (eq .Router.TunnelerMode "none") }}# mode: host #tproxy|host{{ else }} mode: {{ .Router.TunnelerMode }} #tproxy|host{{ end }} -{{ if and (not .Router.IsFabric) (eq .Router.TunnelerMode "tproxy") }} resolver: udp://{{ .Router.Edge.AdvertisedHost }}:53{{ end }} -{{ if and (not .Router.IsFabric) (eq .Router.TunnelerMode "tproxy") }} lanIf: {{ .Router.Edge.LanInterface }}{{ end }} +{{ if and (not .Router.IsFabric) (eq .Router.TunnelerMode "tproxy") }} resolver: {{ .Router.Edge.Resolver }}{{ end }} +{{- if and (not .Router.IsFabric) (eq .Router.TunnelerMode "tproxy") (.Router.Edge.LanInterface) }} + lanIf: {{ .Router.Edge.LanInterface }} +{{- end }} +{{- if and (not .Router.IsFabric) (eq .Router.TunnelerMode "tproxy") (.Router.Edge.DnsSvcIpRange ) }} + dnsSvcIpRange: {{ .Router.Edge.DnsSvcIpRange }} +{{- end }} {{ if .Router.IsFabric -}} csr: country: US @@ -53,7 +58,7 @@ csr: dns: - localhost {{ if .Router.Edge.CsrSans }} - {{ .Router.Edge.CsrSans }}{{ end }} -{{ if ne .Router.Edge.CsrSans .Hostname }} - {{ .Hostname }}{{ end }} +{{ if ne .Router.Edge.CsrSans .HostnameOrNetworkName }} - {{ .HostnameOrNetworkName }}{{ end }} ip: - "127.0.0.1" {{ if .Router.Edge.IPOverride }} - "{{ .Router.Edge.IPOverride }}"{{ end }} @@ -69,7 +74,7 @@ edge: dns: - localhost {{ if .Router.Edge.CsrSans }} - {{ .Router.Edge.CsrSans }}{{ end }} -{{ if ne .Router.Edge.CsrSans .Hostname }} - {{ .Hostname }}{{ end }} +{{ if ne .Router.Edge.CsrSans .HostnameOrNetworkName }} - {{ .HostnameOrNetworkName }}{{ end }} ip: - "127.0.0.1" {{ if .Router.Edge.IPOverride }} - "{{ .Router.Edge.IPOverride }}"{{ end }} diff --git a/ziti/cmd/create/create_config.go b/ziti/cmd/create/create_config.go index c29653af4..0a688a24a 100644 --- a/ziti/cmd/create/create_config.go +++ b/ziti/cmd/create/create_config.go @@ -52,7 +52,7 @@ type CreateConfigOptions struct { type ConfigTemplateValues struct { ZitiHome string - Hostname string + HostnameOrNetworkName string Controller ControllerTemplateValues Router RouterTemplateValues @@ -121,6 +121,10 @@ type IdentityValues struct { AltCertsEnabled bool } +type DatabaseValues struct { + DatabaseFile string +} + type WebOptionsValues struct { IdleTimeout time.Duration ReadTimeout time.Duration @@ -131,6 +135,7 @@ type WebOptionsValues struct { type ControllerTemplateValues struct { Identity IdentityValues + Database DatabaseValues Ctrl CtrlValues HealthChecks HealthChecksValues EdgeApi EdgeApiValues @@ -162,6 +167,8 @@ type EdgeRouterTemplateValues struct { IPOverride string AdvertisedHost string LanInterface string + Resolver string + DnsSvcIpRange string ListenerBindPort string CsrC string CsrST string @@ -225,7 +232,7 @@ func (options *CreateConfigOptions) addCreateFlags(cmd *cobra.Command) { func (data *ConfigTemplateValues) PopulateConfigValues() { // Get and add hostname to the params - data.Hostname = cmdHelper.HostnameOrNetworkName() + data.HostnameOrNetworkName = cmdHelper.HostnameOrNetworkName() // Get and add ziti home to the params zitiHome := cmdHelper.GetZitiHome() @@ -247,6 +254,7 @@ func (data *ConfigTemplateValues) PopulateConfigValues() { data.Controller.Ctrl.AltAdvertisedAddress = cmdHelper.GetCtrlEdgeAltAdvertisedAddress() data.Controller.Ctrl.BindAddress = cmdHelper.GetCtrlBindAddress() data.Controller.Ctrl.AdvertisedPort = cmdHelper.GetCtrlAdvertisedPort() + data.Controller.Database.DatabaseFile = cmdHelper.GetCtrlDatabaseFile() // healthChecks: data.Controller.HealthChecks.Interval = fabCtrl.DefaultHealthChecksBoltCheckInterval data.Controller.HealthChecks.Timeout = fabCtrl.DefaultHealthChecksBoltCheckTimeout @@ -276,6 +284,8 @@ func (data *ConfigTemplateValues) PopulateConfigValues() { // ************* Router Values ************ data.Router.Edge.Port = cmdHelper.GetZitiEdgeRouterPort() data.Router.Edge.ListenerBindPort = cmdHelper.GetZitiEdgeRouterListenerBindPort() + data.Router.Edge.Resolver = cmdHelper.GetZitiEdgeRouterResolver() + data.Router.Edge.DnsSvcIpRange = cmdHelper.GetZitiEdgeRouterDnsSvcIpRange() data.Router.Edge.CsrC = cmdHelper.GetZitiEdgeRouterC() data.Router.Edge.CsrST = cmdHelper.GetZitiEdgeRouterST() data.Router.Edge.CsrL = cmdHelper.GetZitiEdgeRouterL() diff --git a/ziti/cmd/create/create_config_controller.go b/ziti/cmd/create/create_config_controller.go index bcea029c3..5d1f9fcc5 100644 --- a/ziti/cmd/create/create_config_controller.go +++ b/ziti/cmd/create/create_config_controller.go @@ -34,7 +34,6 @@ import ( const ( optionCtrlPort = "ctrlPort" - optionDatabaseFile = "databaseFile" optionEdgeIdentityEnrollmentDuration = "identityEnrollmentDuration" optionEdgeRouterEnrollmentDuration = "routerEnrollmentDuration" optionMinCluster = "minCluster" @@ -150,7 +149,6 @@ func NewCmdCreateConfigController() *CreateControllerConfigCmd { func (options *CreateConfigControllerOptions) addFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&options.CtrlPort, optionCtrlPort, constants.DefaultCtrlAdvertisedPort, "port used for the router to controller communication") - cmd.Flags().StringVar(&options.DatabaseFile, optionDatabaseFile, "ctrl.db", "location of the database file") cmd.Flags().DurationVar(&options.EdgeIdentityEnrollmentDuration, optionEdgeIdentityEnrollmentDuration, edge.DefaultEdgeEnrollmentDuration, "the edge identity enrollment duration, use 0h0m0s format") cmd.Flags().DurationVar(&options.EdgeRouterEnrollmentDuration, optionEdgeRouterEnrollmentDuration, edge.DefaultEdgeEnrollmentDuration, "the edge router enrollment duration, use 0h0m0s format") cmd.Flags().IntVar(&options.MinCluster, optionMinCluster, 0, "minimum cluster size. Enables HA mode if > 0") diff --git a/ziti/cmd/create/create_config_controller_test.go b/ziti/cmd/create/create_config_controller_test.go index 5665505b3..8a8ac2ee9 100644 --- a/ziti/cmd/create/create_config_controller_test.go +++ b/ziti/cmd/create/create_config_controller_test.go @@ -3,10 +3,12 @@ package create import ( "fmt" cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers" + "github.com/openziti/ziti/ziti/constants" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" "os" + "runtime" "strings" "syscall" "testing" @@ -138,6 +140,7 @@ func TestCreateConfigControllerTemplateValues(t *testing.T) { ".Controller.Ctrl.BindAddress", ".Controller.Ctrl.AdvertisedAddress", ".Controller.Ctrl.AdvertisedPort", + ".Controller.Database.DatabaseFile", ".Controller.EdgeApi.Address", ".Controller.EdgeApi.Port", ".Controller.EdgeEnrollment.SigningCert", @@ -162,6 +165,7 @@ func TestCreateConfigControllerTemplateValues(t *testing.T) { &data.Controller.Ctrl.BindAddress, &data.Controller.Ctrl.AdvertisedAddress, &data.Controller.Ctrl.AdvertisedPort, + &data.Controller.Database.DatabaseFile, &data.Controller.EdgeApi.Address, &data.Controller.EdgeApi.Port, &data.Controller.EdgeEnrollment.SigningCert, @@ -275,6 +279,19 @@ func TestCtrlConfigDefaultsWhenUnset(t *testing.T) { assert.Equal(t, expectedValue, ctrlConfig.Identity.Ca) }) + t.Run("TestDatabaseFileEnv", func(t *testing.T) { + expectedValue := cmdhelper.GetZitiHome() + "/" + constants.DefaultCtrlDatabaseFile + + assert.Equal(t, expectedValue, data.Controller.Database.DatabaseFile) + }) + + // db: + t.Run("TestDatabaseFileConfig", func(t *testing.T) { + expectedValue := cmdhelper.GetZitiHome() + "/" + constants.DefaultCtrlDatabaseFile + + assert.Equal(t, expectedValue, ctrlConfig.Db) + }) + // ctrl: t.Run("TestBindAddress", func(t *testing.T) { expectedValue := testDefaultCtrlBindAddress @@ -440,10 +457,11 @@ func TestCtrlConfigDefaultsWhenUnset(t *testing.T) { }) } -func TestCtrlConfigDefaultsWhenBlank(t *testing.T) { +func TestCtrlConfigDefaultsWhenEmpty(t *testing.T) { keys := map[string]string{ "ZITI_PKI_CTRL_CERT": "", "ZITI_CTRL_EDGE_ADVERTISED_ADDRESS": "", + "ZITI_CTRL_DATABASE_FILE": "", "ZITI_HOME": "", } // run the config @@ -475,6 +493,19 @@ func TestCtrlConfigDefaultsWhenBlank(t *testing.T) { assert.Equal(t, expectedValue, ctrlConfig.Identity.Ca) }) + // db: + t.Run("TestDatabaseFileEnv", func(t *testing.T) { + expectedValue := cmdhelper.GetZitiHome() + "/" + constants.DefaultCtrlDatabaseFile + + assert.Equal(t, expectedValue, data.Controller.Database.DatabaseFile) + }) + + t.Run("TestDatabaseFileConfig", func(t *testing.T) { + expectedValue := cmdhelper.GetZitiHome() + "/" + constants.DefaultCtrlDatabaseFile + + assert.Equal(t, expectedValue, ctrlConfig.Db) + }) + // ctrl: t.Run("TestBindAddress", func(t *testing.T) { expectedValue := testDefaultCtrlBindAddress @@ -640,6 +671,30 @@ func TestCtrlConfigDefaultsWhenBlank(t *testing.T) { }) } +// ensure that a custom database file path is normalized with forward slashes +func TestDatabaseFileNormalization(t *testing.T) { + var customValue string + if runtime.GOOS == "windows" { + customValue = "C:\\custom\\path\\file.db" + } else { + customValue = "/custom\\path/file.db" + } + + keys := map[string]string{ + "ZITI_CTRL_DATABASE_FILE": customValue, + } + + ctrlConfig, data := execCreateConfigControllerCommand(nil, keys) + + if runtime.GOOS == "windows" { + assert.Equal(t, "C:/custom/path/file.db", data.Controller.Database.DatabaseFile) + assert.Equal(t, "C:/custom/path/file.db", ctrlConfig.Db) + } else { + assert.Equal(t, "/custom/path/file.db", data.Controller.Database.DatabaseFile) + assert.Equal(t, "/custom/path/file.db", ctrlConfig.Db) + } +} + func TestZitiCtrlIdentitySection(t *testing.T) { certPath := "/var/test/custom/path/file.cert" serverCertPath := "/var/test/custom/path/file.chain.pem" diff --git a/ziti/cmd/create/create_config_environment.go b/ziti/cmd/create/create_config_environment.go index a378c759b..86213583b 100644 --- a/ziti/cmd/create/create_config_environment.go +++ b/ziti/cmd/create/create_config_environment.go @@ -92,7 +92,7 @@ func NewCmdCreateConfigEnvironment() *cobra.Command { PreRun: func(cmd *cobra.Command, args []string) { data.PopulateConfigValues() // Set router identities - SetZitiRouterIdentity(&data.Router, validateRouterName("")) + SetZitiRouterIdentity(&data.Router, validateRouterName(os.Getenv(constants.ZitiEdgeRouterNameVarName))) // Set up other identity info SetControllerIdentity(&data.Controller) SetEdgeConfig(&data.Controller) @@ -100,10 +100,12 @@ func NewCmdCreateConfigEnvironment() *cobra.Command { environmentOptions.EnvVars = []EnvVar{ {constants.ZitiHomeVarName, constants.ZitiHomeVarDescription, data.ZitiHome}, + {constants.ZitiNetworkNameVarName, constants.ZitiNetworkNameVarDescription, data.HostnameOrNetworkName}, {constants.PkiCtrlCertVarName, constants.PkiCtrlCertVarDescription, data.Controller.Identity.Cert}, {constants.PkiCtrlServerCertVarName, constants.PkiCtrlServerCertVarDescription, data.Controller.Identity.ServerCert}, {constants.PkiCtrlKeyVarName, constants.PkiCtrlKeyVarDescription, data.Controller.Identity.Key}, {constants.PkiCtrlCAVarName, constants.PkiCtrlCAVarDescription, data.Controller.Identity.Ca}, + {constants.CtrlDatabaseFileVarName, constants.CtrlDatabaseFileVarDescription, data.Controller.Database.DatabaseFile}, {constants.CtrlBindAddressVarName, constants.CtrlBindAddressVarDescription, data.Controller.Ctrl.BindAddress}, {constants.CtrlAdvertisedAddressVarName, constants.CtrlAdvertisedAddressVarDescription, data.Controller.Ctrl.AdvertisedAddress}, {constants.CtrlAdvertisedPortVarName, constants.CtrlAdvertisedPortVarDescription, data.Controller.Ctrl.AdvertisedPort}, @@ -132,6 +134,8 @@ func NewCmdCreateConfigEnvironment() *cobra.Command { {constants.ZitiRouterIdentityCAVarName, constants.ZitiRouterIdentityCAVarDescription, data.Router.IdentityCA}, {constants.ZitiEdgeRouterIPOverrideVarName, constants.ZitiEdgeRouterIPOverrideVarDescription, data.Router.Edge.IPOverride}, {constants.ZitiEdgeRouterAdvertisedAddressVarName, constants.ZitiEdgeRouterAdvertisedAddressVarDescription, data.Router.Edge.AdvertisedHost}, + {constants.ZitiEdgeRouterResolverVarName, constants.ZitiEdgeRouterResolverVarDescription, data.Router.Edge.Resolver}, + {constants.ZitiEdgeRouterDnsSvcIpRangeVarName, constants.ZitiEdgeRouterDnsSvcIpRangeVarDescription, data.Router.Edge.DnsSvcIpRange}, {constants.ZitiEdgeRouterCsrCVarName, constants.ZitiEdgeRouterCsrCVarDescription, data.Router.Edge.CsrC}, {constants.ZitiEdgeRouterCsrSTVarName, constants.ZitiEdgeRouterCsrSTVarDescription, data.Router.Edge.CsrST}, {constants.ZitiEdgeRouterCsrLVarName, constants.ZitiEdgeRouterCsrLVarDescription, data.Router.Edge.CsrL}, @@ -184,10 +188,12 @@ func NewCmdCreateConfigEnvironment() *cobra.Command { "the config output.\n\nThe following environment variables can be set to override config values " + "(current value is displayed):\n") sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.ZitiHomeVarName, constants.ZitiHomeVarDescription, data.ZitiHome)) + sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.ZitiNetworkNameVarName, constants.ZitiNetworkNameVarDescription, data.HostnameOrNetworkName)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.PkiCtrlCertVarName, constants.PkiCtrlCertVarDescription, data.Controller.Identity.Cert)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.PkiCtrlServerCertVarName, constants.PkiCtrlServerCertVarDescription, data.Controller.Identity.ServerCert)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.PkiCtrlKeyVarName, constants.PkiCtrlKeyVarDescription, data.Controller.Identity.Key)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.PkiCtrlCAVarName, constants.PkiCtrlCAVarDescription, data.Controller.Identity.Ca)) + sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.CtrlDatabaseFileVarName, constants.CtrlDatabaseFileVarDescription, data.Controller.Database.DatabaseFile)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.CtrlBindAddressVarName, constants.CtrlBindAddressVarDescription, data.Controller.Ctrl.BindAddress)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.CtrlAdvertisedAddressVarName, constants.CtrlAdvertisedAddressVarDescription, data.Controller.Ctrl.AdvertisedAddress)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.CtrlEdgeAltAdvertisedAddressVarName, constants.CtrlEdgeAltAdvertisedAddressVarDescription, data.Controller.Ctrl.AdvertisedAddress)) @@ -214,6 +220,8 @@ func NewCmdCreateConfigEnvironment() *cobra.Command { sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.ZitiRouterIdentityCAVarName, constants.ZitiRouterIdentityCAVarDescription, data.Router.IdentityCA)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.ZitiEdgeRouterIPOverrideVarName, constants.ZitiEdgeRouterIPOverrideVarDescription, data.Router.Edge.IPOverride)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.ZitiEdgeRouterAdvertisedAddressVarName, constants.ZitiEdgeRouterAdvertisedAddressVarDescription, data.Router.Edge.AdvertisedHost)) + sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.ZitiEdgeRouterResolverVarName, constants.ZitiEdgeRouterResolverVarDescription, data.Router.Edge.Resolver)) + sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.ZitiEdgeRouterDnsSvcIpRangeVarName, constants.ZitiEdgeRouterDnsSvcIpRangeVarDescription, data.Router.Edge.DnsSvcIpRange)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.ZitiEdgeRouterCsrCVarName, constants.ZitiEdgeRouterCsrCVarDescription, data.Router.Edge.CsrC)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.ZitiEdgeRouterCsrSTVarName, constants.ZitiEdgeRouterCsrSTVarDescription, data.Router.Edge.CsrST)) sb.WriteString(fmt.Sprintf("%-40s %-50s %s\n", constants.ZitiEdgeRouterCsrLVarName, constants.ZitiEdgeRouterCsrLVarDescription, data.Router.Edge.CsrL)) diff --git a/ziti/cmd/create/create_config_router_edge.go b/ziti/cmd/create/create_config_router_edge.go index 06ad8a4d5..cc54709c8 100644 --- a/ziti/cmd/create/create_config_router_edge.go +++ b/ziti/cmd/create/create_config_router_edge.go @@ -76,6 +76,8 @@ func NewCmdCreateConfigRouterEdge(routerOptions *CreateConfigRouterOptions, data data.Router.IsPrivate = routerOptions.IsPrivate data.Router.TunnelerMode = routerOptions.TunnelerMode data.Router.Edge.LanInterface = routerOptions.LanInterface + data.Router.Edge.Resolver = cmdhelper.GetZitiEdgeRouterResolver() + data.Router.Edge.DnsSvcIpRange = cmdhelper.GetZitiEdgeRouterDnsSvcIpRange() }, Run: func(cmd *cobra.Command, args []string) { routerOptions.Cmd = cmd diff --git a/ziti/cmd/create/create_config_router_edge_test.go b/ziti/cmd/create/create_config_router_edge_test.go index ae642101c..68c5f05f1 100644 --- a/ziti/cmd/create/create_config_router_edge_test.go +++ b/ziti/cmd/create/create_config_router_edge_test.go @@ -238,7 +238,7 @@ func TestExecuteCreateConfigRouterEdgeHasNonBlankTemplateValues(t *testing.T) { _, data := createRouterConfig([]string{"edge", "--routerName", routerName}, routerOptions, nil) expectedNonEmptyStringFields := []string{".Router.Edge.ListenerBindPort", ".ZitiHome", ".Hostname", ".Router.Name", ".Router.IdentityCert", ".Router.IdentityServerCert", ".Router.IdentityKey", ".Router.IdentityCA", ".Router.Edge.Port"} - expectedNonEmptyStringValues := []*string{&data.Router.Edge.ListenerBindPort, &data.ZitiHome, &data.Hostname, &data.Router.Name, &data.Router.IdentityCert, &data.Router.IdentityServerCert, &data.Router.IdentityKey, &data.Router.IdentityCA, &data.Router.Edge.Port} + expectedNonEmptyStringValues := []*string{&data.Router.Edge.ListenerBindPort, &data.ZitiHome, &data.HostnameOrNetworkName, &data.Router.Name, &data.Router.IdentityCert, &data.Router.IdentityServerCert, &data.Router.IdentityKey, &data.Router.IdentityCA, &data.Router.Edge.Port} expectedNonEmptyIntFields := []string{".Router.Listener.OutQueueSize", ".Router.Wss.ReadBufferSize", ".Router.Wss.WriteBufferSize", ".Router.Forwarder.XgressDialQueueLength", ".Router.Forwarder.XgressDialWorkerCount", ".Router.Forwarder.LinkDialQueueLength", ".Router.Forwarder.LinkDialWorkerCount"} expectedNonEmptyIntValues := []*int{&data.Router.Listener.OutQueueSize, &data.Router.Wss.ReadBufferSize, &data.Router.Wss.WriteBufferSize, &data.Router.Forwarder.XgressDialQueueLength, &data.Router.Forwarder.XgressDialWorkerCount, &data.Router.Forwarder.LinkDialQueueLength, &data.Router.Forwarder.LinkDialWorkerCount} expectedNonEmptyTimeFields := []string{".Router.Listener.ConnectTimeout", "Router.Listener.GetSessionTimeout", ".Router.Wss.WriteTimeout", ".Router.Wss.ReadTimeout", ".Router.Wss.IdleTimeout", ".Router.Wss.PongTimeout", ".Router.Wss.PingInterval", ".Router.Wss.HandshakeTimeout"} diff --git a/ziti/cmd/create/create_config_router_fabric_test.go b/ziti/cmd/create/create_config_router_fabric_test.go index 3269d58b1..eaeddbff1 100644 --- a/ziti/cmd/create/create_config_router_fabric_test.go +++ b/ziti/cmd/create/create_config_router_fabric_test.go @@ -20,7 +20,7 @@ func TestExecuteCreateConfigRouterFabricHasNonBlankTemplateValues(t *testing.T) _, data := createRouterConfig([]string{"fabric", "--routerName", routerName}, routerOptions, nil) expectedNonEmptyStringFields := []string{".Router.Listener.BindPort", ".ZitiHome", ".Hostname", ".Router.Name", ".Router.IdentityCert", ".Router.IdentityServerCert", ".Router.IdentityKey", ".Router.IdentityCA", ".Router.Edge.Port"} - expectedNonEmptyStringValues := []*string{&data.Router.Edge.ListenerBindPort, &data.ZitiHome, &data.Hostname, &data.Router.Name, &data.Router.IdentityCert, &data.Router.IdentityServerCert, &data.Router.IdentityKey, &data.Router.IdentityCA, &data.Router.Edge.Port} + expectedNonEmptyStringValues := []*string{&data.Router.Edge.ListenerBindPort, &data.ZitiHome, &data.HostnameOrNetworkName, &data.Router.Name, &data.Router.IdentityCert, &data.Router.IdentityServerCert, &data.Router.IdentityKey, &data.Router.IdentityCA, &data.Router.Edge.Port} expectedNonEmptyIntFields := []string{".Router.Listener.OutQueueSize", ".Router.Wss.ReadBufferSize", ".Router.Wss.WriteBufferSize", ".Router.Forwarder.XgressDialQueueLength", ".Router.Forwarder.XgressDialWorkerCount", ".Router.Forwarder.LinkDialQueueLength", ".Router.Forwarder.LinkDialWorkerCount"} expectedNonEmptyIntValues := []*int{&data.Router.Listener.OutQueueSize, &data.Router.Wss.ReadBufferSize, &data.Router.Wss.WriteBufferSize, &data.Router.Forwarder.XgressDialQueueLength, &data.Router.Forwarder.XgressDialWorkerCount, &data.Router.Forwarder.LinkDialQueueLength, &data.Router.Forwarder.LinkDialWorkerCount} expectedNonEmptyTimeFields := []string{".Router.Listener.ConnectTimeout", "Router.Listener.GetSessionTimeout", ".Router.Wss.WriteTimeout", ".Router.Wss.ReadTimeout", ".Router.Wss.IdleTimeout", ".Router.Wss.PongTimeout", ".Router.Wss.PingInterval", ".Router.Wss.HandshakeTimeout"} diff --git a/ziti/cmd/create/create_config_test.go b/ziti/cmd/create/create_config_test.go index 0c06d231e..c1ef4ae74 100644 --- a/ziti/cmd/create/create_config_test.go +++ b/ziti/cmd/create/create_config_test.go @@ -42,6 +42,7 @@ func getZitiEnvironmentVariables() []string { "ZITI_PKI_CTRL_CA", "ZITI_CTRL_BIND_ADDRESS", "ZITI_CTRL_ADVERTISED_ADDRESS", + "ZITI_CTRL_DATABASE_FILE", "ZITI_CTRL_EDGE_ALT_ADVERTISED_ADDRESS", "ZITI_CTRL_ADVERTISED_PORT", "ZITI_PKI_SIGNER_CERT", @@ -57,6 +58,9 @@ func getZitiEnvironmentVariables() []string { "ZITI_ROUTER_IDENTITY_KEY", "ZITI_ROUTER_IDENTITY_CA", "ZITI_ROUTER_IP_OVERRIDE", + "ZITI_ROUTER_TPROXY_RESOLVER", + "ZITI_ROUTER_DNS_IP_RANGE", + "ZITI_NETWORK_NAME", "ZITI_EDGE_IDENTITY_ENROLLMENT_DURATION", "ZITI_ROUTER_ENROLLMENT_DURATION", "ZITI_ROUTER_ADVERTISED_ADDRESS", diff --git a/ziti/cmd/helpers/env_helpers.go b/ziti/cmd/helpers/env_helpers.go index 78c90105d..40ae01110 100644 --- a/ziti/cmd/helpers/env_helpers.go +++ b/ziti/cmd/helpers/env_helpers.go @@ -17,10 +17,12 @@ package helpers import ( + "github.com/openziti/ziti/router/xgress_edge_tunnel" edge "github.com/openziti/ziti/controller/config" "github.com/openziti/ziti/ziti/constants" "github.com/pkg/errors" "os" + "path/filepath" "strconv" "strings" "time" @@ -104,6 +106,14 @@ func GetCtrlAdvertisedPort() string { return getFromEnv(constants.CtrlAdvertisedPortVarName, defaultValue(constants.DefaultCtrlAdvertisedPort)) } +func GetCtrlDatabaseFile() string { + path := getFromEnv(constants.CtrlDatabaseFileVarName, defaultValue(constants.DefaultCtrlDatabaseFile)) + if !filepath.IsAbs(path) { + path = filepath.Join(GetZitiHome(), path) + } + return NormalizePath(path) +} + func GetCtrlEdgeBindAddress() string { return getFromEnv(constants.CtrlEdgeBindAddressVarName, defaultValue(constants.DefaultCtrlEdgeBindAddress)) } @@ -193,6 +203,12 @@ func NormalizePath(input string) string { func GetRouterAdvertisedAddress() string { return getFromEnv(constants.ZitiEdgeRouterAdvertisedAddressVarName, HostnameOrNetworkName) } +func GetZitiEdgeRouterResolver() string { + return getFromEnv(constants.ZitiEdgeRouterResolverVarName, defaultValue(xgress_edge_tunnel.DefaultDnsResolver)) +} +func GetZitiEdgeRouterDnsSvcIpRange() string { + return getFromEnv(constants.ZitiEdgeRouterDnsSvcIpRangeVarName, defaultValue(xgress_edge_tunnel.DefaultDnsServiceIpRange)) +} func GetRouterSans() string { return getFromEnv(constants.ZitiRouterCsrSansDnsVarName, GetRouterAdvertisedAddress) } diff --git a/ziti/cmd/pki/pki_create_client.go b/ziti/cmd/pki/pki_create_client.go index 2e19c25d8..3d62b0124 100644 --- a/ziti/cmd/pki/pki_create_client.go +++ b/ziti/cmd/pki/pki_create_client.go @@ -79,6 +79,7 @@ func (o *PKICreateClientOptions) addPKICreateClientFlags(cmd *cobra.Command) { cmd.Flags().IntVarP(&o.Flags.CAPrivateKeySize, "private-key-size", "", 4096, "Size of the RSA private key, ignored if -curve is set") cmd.Flags().StringVarP(&o.Flags.EcCurve, "curve", "", "", "If set an EC private key is generated and -private-key-size is ignored, options: P224, P256, P384, P521") cmd.Flags().StringVar(&o.Flags.SpiffeID, "spiffe-id", "", "Optionally provide the path portion of a SPIFFE id. The trust domain will be taken from the signing certificate.") + cmd.Flags().BoolVar(&o.Flags.AllowOverwrite, "allow-overwrite", false, "Allow overwrite existing certs") } // Run implements this command @@ -158,6 +159,7 @@ func (o *PKICreateClientOptions) Run() error { Template: template, IsClientCertificate: true, PrivateKeyOptions: privateKeyOptions, + AllowOverwrite: o.Flags.AllowOverwrite, } if err := o.Flags.PKI.Sign(signer, req); err != nil { diff --git a/ziti/constants/constants.go b/ziti/constants/constants.go index 8017c662d..ca8ee2aae 100644 --- a/ziti/constants/constants.go +++ b/ziti/constants/constants.go @@ -45,6 +45,8 @@ const ( DefaultCtrlBindAddress = "0.0.0.0" DefaultCtrlAdvertisedPort = "6262" + DefaultCtrlDatabaseFile = "db/ctrl.db" + DefaultCtrlEdgeBindAddress = "0.0.0.0" DefaultCtrlEdgeAdvertisedPort = "1280" @@ -57,69 +59,78 @@ const ( // Env Var Constants const ( - ZitiHomeVarName = "ZITI_HOME" - ZitiHomeVarDescription = "Root home directory for Ziti related files" + ZitiHomeVarName = "ZITI_HOME" + ZitiHomeVarDescription = "base dirname used to construct paths" + + ZitiNetworkNameVarName = "ZITI_NETWORK_NAME" + ZitiNetworkNameVarDescription = "base filename used to construct paths" PkiCtrlCertVarName = "ZITI_PKI_CTRL_CERT" - PkiCtrlCertVarDescription = "Path to Identity Cert for Ziti Controller" + PkiCtrlCertVarDescription = "Path to the controller's default identity client cert" PkiCtrlServerCertVarName = "ZITI_PKI_CTRL_SERVER_CERT" - PkiCtrlServerCertVarDescription = "Path to Identity Server Cert for Ziti Controller" + PkiCtrlServerCertVarDescription = "Path to the controller's default identity server cert, including partial chain" PkiCtrlKeyVarName = "ZITI_PKI_CTRL_KEY" - PkiCtrlKeyVarDescription = "Path to Identity Key for Ziti Controller" + PkiCtrlKeyVarDescription = "Path to the controller's default identity private key" PkiCtrlCAVarName = "ZITI_PKI_CTRL_CA" - PkiCtrlCAVarDescription = "Path to Identity CA for Ziti Controller" + PkiCtrlCAVarDescription = "Path to the controller's bundle of trusted root CAs" CtrlBindAddressVarName = "ZITI_CTRL_BIND_ADDRESS" - CtrlBindAddressVarDescription = "The address on which the controller will listen on for control plane connections" + CtrlBindAddressVarDescription = "The address where the controller will listen for router control plane connections" CtrlAdvertisedAddressVarName = "ZITI_CTRL_ADVERTISED_ADDRESS" - CtrlAdvertisedAddressVarDescription = "The address routers will use to connect to the Ziti Controller" + CtrlAdvertisedAddressVarDescription = "The address routers will use to connect to the controller" CtrlAdvertisedPortVarName = "ZITI_CTRL_ADVERTISED_PORT" - CtrlAdvertisedPortVarDescription = "The port routers will use to connect to the Ziti Controller" + CtrlAdvertisedPortVarDescription = "TCP port routers will use to connect to the controller" CtrlEdgeBindAddressVarName = "ZITI_CTRL_EDGE_BIND_ADDRESS" - CtrlEdgeBindAddressVarDescription = "The address on which the edge controller will listen on for API connections" + CtrlEdgeBindAddressVarDescription = "The address where the controller will listen for edge API connections" CtrlEdgeAdvertisedAddressVarName = "ZITI_CTRL_EDGE_ADVERTISED_ADDRESS" - CtrlEdgeAdvertisedAddressVarDescription = "The publicly addressable controller address value" + CtrlEdgeAdvertisedAddressVarDescription = "The controller's edge API address" CtrlEdgeAltAdvertisedAddressVarName = "ZITI_CTRL_EDGE_ALT_ADVERTISED_ADDRESS" - CtrlEdgeAltAdvertisedAddressVarDescription = "The publicly addressable, alternative controller address value. Overrides ZITI_CTRL_EDGE_ADVERTISED_ADDRESS" + CtrlEdgeAltAdvertisedAddressVarDescription = "The controller's edge API alternative address" CtrlEdgeAdvertisedPortVarName = "ZITI_CTRL_EDGE_ADVERTISED_PORT" - CtrlEdgeAdvertisedPortVarDescription = "The publicly addressable controller port value" + CtrlEdgeAdvertisedPortVarDescription = "TCP port of the controller's edge API" + CtrlDatabaseFileVarName = "ZITI_CTRL_DATABASE_FILE" + CtrlDatabaseFileVarDescription = "Path to the controller's database file" PkiSignerCertVarName = "ZITI_PKI_SIGNER_CERT" - PkiSignerCertVarDescription = "Path to the Ziti Signing Cert" + PkiSignerCertVarDescription = "Path to the controller's edge signer CA cert" PkiSignerKeyVarName = "ZITI_PKI_SIGNER_KEY" - PkiSignerKeyVarDescription = "Path to the Ziti Signing Key" + PkiSignerKeyVarDescription = "Path to the controller's edge signer CA key" CtrlEdgeIdentityEnrollmentDurationVarName = "ZITI_EDGE_IDENTITY_ENROLLMENT_DURATION" CtrlEdgeIdentityEnrollmentDurationVarDescription = "The identity enrollment duration in minutes" CtrlEdgeRouterEnrollmentDurationVarName = "ZITI_ROUTER_ENROLLMENT_DURATION" CtrlEdgeRouterEnrollmentDurationVarDescription = "The router enrollment duration in minutes" CtrlPkiEdgeCertVarName = "ZITI_PKI_EDGE_CERT" - CtrlPkiEdgeCertVarDescription = "Path to Identity Cert for Ziti Edge Controller" + CtrlPkiEdgeCertVarDescription = "Path to the controller's web identity client certificate" CtrlPkiEdgeServerCertVarName = "ZITI_PKI_EDGE_SERVER_CERT" - CtrlPkiEdgeServerCertVarDescription = "Path to Identity Server Cert for Ziti Edge Controller" + CtrlPkiEdgeServerCertVarDescription = "Path to the controller's web identity server certificate, including partial chain" CtrlPkiEdgeKeyVarName = "ZITI_PKI_EDGE_KEY" - CtrlPkiEdgeKeyVarDescription = "Path to Identity Key for Ziti Edge Controller" + CtrlPkiEdgeKeyVarDescription = "Path to the controller's web identity private key" CtrlPkiEdgeCAVarName = "ZITI_PKI_EDGE_CA" - CtrlPkiEdgeCAVarDescription = "Path to Identity CA for Ziti Edge Controller" + CtrlPkiEdgeCAVarDescription = "Path to the controller's web identity root CA cert" PkiAltServerCertVarName = "ZITI_PKI_ALT_SERVER_CERT" - PkiAltServerCertVarDescription = "Alternative server certificate to use. Must be specified with ZITI_PKI_ALT_SERVER_KEY" + PkiAltServerCertVarDescription = "Path to the controller's default identity alternative server certificate; requires ZITI_PKI_ALT_SERVER_KEY" PkiAltServerKeyVarName = "ZITI_PKI_ALT_SERVER_KEY" - PkiAltServerKeyVarDescription = "Key to use with the alternative server cert. Must be specified with ZITI_PKI_ALT_SERVER_CERT" + PkiAltServerKeyVarDescription = "Path to the controller's default identity alternative private key. Requires ZITI_PKI_ALT_SERVER_CERT" ZitiEdgeRouterNameVarName = "ZITI_ROUTER_NAME" - ZitiEdgeRouterNameVarDescription = "The Edge Router Name" + ZitiEdgeRouterNameVarDescription = "A filename prefix for the router's key and certs" ZitiEdgeRouterPortVarName = "ZITI_ROUTER_PORT" - ZitiEdgeRouterPortVarDescription = "Port of the Ziti Edge Router" + ZitiEdgeRouterPortVarDescription = "TCP port where the router listens for edge connections from endpoint identities" ZitiRouterIdentityCertVarName = "ZITI_ROUTER_IDENTITY_CERT" - ZitiRouterIdentityCertVarDescription = "Path to Identity Cert for Ziti Router" + ZitiRouterIdentityCertVarDescription = "Path to the router's client certificate" ZitiRouterIdentityServerCertVarName = "ZITI_ROUTER_IDENTITY_SERVER_CERT" - ZitiRouterIdentityServerCertVarDescription = "Path to Identity Server Cert for Ziti Router" + ZitiRouterIdentityServerCertVarDescription = "Path to the router's server certificate" ZitiRouterIdentityKeyVarName = "ZITI_ROUTER_IDENTITY_KEY" - ZitiRouterIdentityKeyVarDescription = "Path to Identity Key for Ziti Router" + ZitiRouterIdentityKeyVarDescription = "Path to the router's private key" ZitiRouterIdentityCAVarName = "ZITI_ROUTER_IDENTITY_CA" - ZitiRouterIdentityCAVarDescription = "Path to Identity CA for Ziti Router" + ZitiRouterIdentityCAVarDescription = "Path to the router's bundle of trusted root CA certs" ZitiEdgeRouterIPOverrideVarName = "ZITI_ROUTER_IP_OVERRIDE" - ZitiEdgeRouterIPOverrideVarDescription = "Override the default edge router IP with a custom IP, this IP will also be added to the PKI" + ZitiEdgeRouterIPOverrideVarDescription = "Additional IP SAN of the router" ZitiEdgeRouterAdvertisedAddressVarName = "ZITI_ROUTER_ADVERTISED_ADDRESS" - ZitiEdgeRouterAdvertisedAddressVarDescription = "The advertised address of the router" + ZitiEdgeRouterAdvertisedAddressVarDescription = "The router's advertised address and DNS SAN" ZitiEdgeRouterListenerBindPortVarName = "ZITI_ROUTER_LISTENER_BIND_PORT" - ZitiEdgeRouterListenerBindPortVarDescription = "The port a public router will advertise on" + ZitiEdgeRouterListenerBindPortVarDescription = "TCP port where the router will listen for and advertise links to other routers" + ZitiEdgeRouterResolverVarName = "ZITI_ROUTER_TPROXY_RESOLVER" + ZitiEdgeRouterResolverVarDescription = "The bind URI to listen for DNS requests in tproxy mode" + ZitiEdgeRouterDnsSvcIpRangeVarName = "ZITI_ROUTER_DNS_IP_RANGE" + ZitiEdgeRouterDnsSvcIpRangeVarDescription = "The CIDR range to use for Ziti DNS in tproxy mode" ZitiEdgeRouterCsrCVarName = "ZITI_ROUTER_CSR_C" ZitiEdgeRouterCsrCVarDescription = "The country (C) to use for router CSRs" ZitiEdgeRouterCsrSTVarName = "ZITI_ROUTER_CSR_ST" @@ -131,5 +142,5 @@ const ( ZitiEdgeRouterCsrOUVarName = "ZITI_ROUTER_CSR_OU" ZitiEdgeRouterCsrOUVarDescription = "The organization unit to use for router CSRs" ZitiRouterCsrSansDnsVarName = "ZITI_ROUTER_CSR_SANS_DNS" - ZitiRouterCsrSansDnsVarDescription = "The SANS value to use for the CSR in the internal PKI. If not supplied, defaults to ZITI_ROUTER_ADVERTISED_ADDRESS" + ZitiRouterCsrSansDnsVarDescription = "Additional DNS SAN of the router" )