From 5ea1fe0666b7da77e557fcfa45f77683d969d043 Mon Sep 17 00:00:00 2001 From: Kenneth Bingham Date: Tue, 5 Mar 2024 11:24:27 -0500 Subject: [PATCH] add Linux package openziti-controller --- .github/workflows/fablab-db-creation.yml | 1 + .github/workflows/publish-linux-packages.yml | 1 + .../linux/nfpm-openziti-controller.yaml | 35 +++ dist/dist-packages/linux/nfpm-openziti.yaml | 7 - .../linux/openziti-controller/bootstrap.bash | 215 ++++++++++++++++++ .../linux/openziti-controller/entrypoint.bash | 19 ++ .../linux/openziti-controller/env | 42 ++++ .../ziti-controller.service | 71 ++++++ .../create/config_templates/controller.yml | 2 +- ziti/cmd/create/create_config.go | 6 + ziti/cmd/create/create_config_controller.go | 2 - .../create/create_config_controller_test.go | 57 ++++- ziti/cmd/create/create_config_environment.go | 2 + ziti/cmd/create/create_config_test.go | 1 + ziti/cmd/helpers/env_helpers.go | 9 + ziti/cmd/pki/pki_create_client.go | 2 + ziti/constants/constants.go | 50 ++-- 17 files changed, 488 insertions(+), 34 deletions(-) create mode 100644 dist/dist-packages/linux/nfpm-openziti-controller.yaml create mode 100755 dist/dist-packages/linux/openziti-controller/bootstrap.bash create mode 100755 dist/dist-packages/linux/openziti-controller/entrypoint.bash create mode 100644 dist/dist-packages/linux/openziti-controller/env create mode 100644 dist/dist-packages/linux/openziti-controller/ziti-controller.service diff --git a/.github/workflows/fablab-db-creation.yml b/.github/workflows/fablab-db-creation.yml index 0b32738a6..8499e7232 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-linux-packages.yml b/.github/workflows/publish-linux-packages.yml index 548774307..303b7f5a3 100644 --- a/.github/workflows/publish-linux-packages.yml +++ b/.github/workflows/publish-linux-packages.yml @@ -16,6 +16,7 @@ jobs: matrix: package_name: - openziti + - openziti-controller arch: - goreleaser: amd64 gox: amd64 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..4e887a3fd --- /dev/null +++ b/dist/dist-packages/linux/nfpm-openziti-controller.yaml @@ -0,0 +1,35 @@ +# nfpm configuration file +# +# check https://nfpm.goreleaser.com/configuration for detailed usage +# +name: openziti-controller +arch: ${GOARCH} +platform: linux +version: ${ZITI_VERSION} +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 diff --git a/dist/dist-packages/linux/nfpm-openziti.yaml b/dist/dist-packages/linux/nfpm-openziti.yaml index f7dd43be0..20be50d4b 100644 --- a/dist/dist-packages/linux/nfpm-openziti.yaml +++ b/dist/dist-packages/linux/nfpm-openziti.yaml @@ -23,10 +23,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..146608303 --- /dev/null +++ b/dist/dist-packages/linux/openziti-controller/bootstrap.bash @@ -0,0 +1,215 @@ +#!/usr/bin/env bash +# +# bootstrap the OpenZiti Controller with PKI, config file, and database +# + +set -o errexit +set -o nounset +set -o pipefail + +# use the ziti executable that the 'openziti' package installed +PATH=/opt/openziti/bin:$PATH + +# +# 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 or SetCredential 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..158a837b9 --- /dev/null +++ b/dist/dist-packages/linux/openziti-controller/entrypoint.bash @@ -0,0 +1,19 @@ +#!/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 + +# shellcheck disable=SC1090 # default path is set by the systemd service +source "${ZITI_CTRL_BOOTSTRAP_BASH:-/opt/openziti/etc/controller/bootstrap.bash}" +# if no args or first arg is "run", bootstrap the router with the config file path as next arg, or default "config.yml" +if [ "${1:-run}" == run ]; then + bootstrap "${2:-config.yml}" +fi + +# shellcheck disable=SC2068 +exec ziti controller ${@:-run config.yml} diff --git a/dist/dist-packages/linux/openziti-controller/env b/dist/dist-packages/linux/openziti-controller/env new file mode 100644 index 000000000..5f7ec43a5 --- /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 +# + +# the advertised address of the controller is a fully-qualified domain name; if defined this overrides any value set in +# /lib/systemd/system/ziti-controller.service +#ZITI_CTRL_ADVERTISED_ADDRESS= +# the advertised and listening port of the controller (default: 1280) +ZITI_CTRL_ADVERTISED_PORT=1280 +# 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 + +# +# 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/ziti-controller.service b/dist/dist-packages/linux/openziti-controller/ziti-controller.service new file mode 100644 index 000000000..4f6feef5d --- /dev/null +++ b/dist/dist-packages/linux/openziti-controller/ziti-controller.service @@ -0,0 +1,71 @@ +[Unit] +Description=OpenZiti Controller +After=network-online.target + +[Service] + +# +## Required Configuration +# + +# 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 +# LoadCredential=ZITI_PWD:/opt/openziti/etc/controller/.pwd +# alternatively to the .pwd file, temporarily set ZITI_PWD in /opt/openziti/etc/controller/env or this file with +# SetCredential and exec "systemctl daemon-reload", e.g.: +# SetCredential=ZITI_PWD: + +# you must set the advertised address (FQDN) of the controller; you may specify the domain name as a literal string here +# and exec "systemctl daemon-reload" or in /opt/openziti/etc/controller/env (no daemon-reload required) +Environment=ZITI_CTRL_ADVERTISED_ADDRESS= +# Environment=ZITI_CTRL_ADVERTISED_ADDRESS=ziti.example.com + +# +## 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 +# 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 +# basename of identity files to generate in state dir +Environment=ZITI_NETWORK_NAME=ctrl +# 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/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/create_config.go b/ziti/cmd/create/create_config.go index c29653af4..ba4da861a 100644 --- a/ziti/cmd/create/create_config.go +++ b/ziti/cmd/create/create_config.go @@ -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 @@ -247,6 +252,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 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..74275d1e8 100644 --- a/ziti/cmd/create/create_config_environment.go +++ b/ziti/cmd/create/create_config_environment.go @@ -104,6 +104,7 @@ func NewCmdCreateConfigEnvironment() *cobra.Command { {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}, @@ -188,6 +189,7 @@ func NewCmdCreateConfigEnvironment() *cobra.Command { 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)) diff --git a/ziti/cmd/create/create_config_test.go b/ziti/cmd/create/create_config_test.go index 0c06d231e..41baa7298 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", diff --git a/ziti/cmd/helpers/env_helpers.go b/ziti/cmd/helpers/env_helpers.go index 78c90105d..424d48553 100644 --- a/ziti/cmd/helpers/env_helpers.go +++ b/ziti/cmd/helpers/env_helpers.go @@ -21,6 +21,7 @@ import ( "github.com/openziti/ziti/ziti/constants" "github.com/pkg/errors" "os" + "path/filepath" "strconv" "strings" "time" @@ -104,6 +105,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)) } 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..2351aadf5 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" @@ -58,62 +60,64 @@ const ( // Env Var Constants const ( ZitiHomeVarName = "ZITI_HOME" - ZitiHomeVarDescription = "Root home directory for Ziti related files" + ZitiHomeVarDescription = "Root home directory for Ziti-related files" 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 on which the controller will listen on 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 = "The 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 on which the controller will listen on for API connections" CtrlEdgeAdvertisedAddressVarName = "ZITI_CTRL_EDGE_ADVERTISED_ADDRESS" CtrlEdgeAdvertisedAddressVarDescription = "The publicly addressable controller address value" CtrlEdgeAltAdvertisedAddressVarName = "ZITI_CTRL_EDGE_ALT_ADVERTISED_ADDRESS" CtrlEdgeAltAdvertisedAddressVarDescription = "The publicly addressable, alternative controller address value. Overrides ZITI_CTRL_EDGE_ADVERTISED_ADDRESS" CtrlEdgeAdvertisedPortVarName = "ZITI_CTRL_EDGE_ADVERTISED_PORT" CtrlEdgeAdvertisedPortVarDescription = "The publicly addressable controller port value" + CtrlDatabaseFileVarName = "ZITI_CTRL_DATABASE_FILE" + CtrlDatabaseFileVarDescription = "Path to the controller 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 root 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 root 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 on which the router will listen for edge connections" ZitiRouterIdentityCertVarName = "ZITI_ROUTER_IDENTITY_CERT" - ZitiRouterIdentityCertVarDescription = "Path to Identity Cert for Ziti Router" + ZitiRouterIdentityCertVarDescription = "Path in which to write the router's client certificate during enrollment" ZitiRouterIdentityServerCertVarName = "ZITI_ROUTER_IDENTITY_SERVER_CERT" - ZitiRouterIdentityServerCertVarDescription = "Path to Identity Server Cert for Ziti Router" + ZitiRouterIdentityServerCertVarDescription = "Path in which to write the router's server certificate during enrollment" ZitiRouterIdentityKeyVarName = "ZITI_ROUTER_IDENTITY_KEY" - ZitiRouterIdentityKeyVarDescription = "Path to Identity Key for Ziti Router" + ZitiRouterIdentityKeyVarDescription = "Path to generate the router's private key unless it exists" ZitiRouterIdentityCAVarName = "ZITI_ROUTER_IDENTITY_CA" - ZitiRouterIdentityCAVarDescription = "Path to Identity CA for Ziti Router" + ZitiRouterIdentityCAVarDescription = "Path to write the router's bundle of trusted root CA certs during enrollment" 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" ZitiEdgeRouterAdvertisedAddressVarName = "ZITI_ROUTER_ADVERTISED_ADDRESS"