diff --git a/.github/workflows/test-quickstart.yml b/.github/workflows/test-quickstart.yml index f2deeaedf..5b7363c7c 100644 --- a/.github/workflows/test-quickstart.yml +++ b/.github/workflows/test-quickstart.yml @@ -84,3 +84,25 @@ jobs: ls -lAn ${GOCACHE:-${HOME}/.cache/go-build}/ ${GOPATH:-${HOME}/go}/pkg/mod/ docker compose --profile test logs exit 0 + + haQuickstartTest: + name: Test HA Quickstart + runs-on: ubuntu-latest + steps: + - name: Shallow checkout + uses: actions/checkout@v4 + + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + + - name: Build ziti executable + shell: bash + run: | + mkdir -pv /tmp/build + go build -o /tmp/build ${GITHUB_WORKSPACE}/... + + - name: Build and run a three quickstart in HA mode + shell: bash + run: ./quickstart/test/ha-test.sh \ No newline at end of file diff --git a/quickstart/test/ha-test.sh b/quickstart/test/ha-test.sh new file mode 100755 index 000000000..f8334608f --- /dev/null +++ b/quickstart/test/ha-test.sh @@ -0,0 +1,123 @@ +BUILD_DIR=/tmp/build + +ctrl_port=2001 +router_port=3001 +rm -rf "/tmp/quickstart-ha-test" +ziti_home="/tmp/quickstart-ha-test" + +function _wait_for_controller { + local advertised_host_port="127.0.0.1:${1}" + local timeout=60 + local elapsed=0 + + while [[ "$(curl -w "%{http_code}" -m 1 -s -k -o /dev/null https://${advertised_host_port}/edge/client/v1/version)" != "200" ]]; do + if (( elapsed >= timeout )); then + echo "Timeout waiting for https://${advertised_host_port}" >&2 + exit 1 + fi + echo "waiting for https://${advertised_host_port}" + sleep 3 + (( elapsed += 3 )) + done + echo "CONTROLLER ONLINE AT: https://${advertised_host_port}" +} + +function _stop_instances { + echo "killing...." + kill "$@" 2>/dev/null + + for pid in "$@"; do + while kill -0 "$pid" 2>/dev/null; do + echo "Waiting for process $pid to stop..." + sleep 1 + done + echo "Process $pid has stopped." + done +} + +trap 'kill $inst001pid $inst002pid $inst003pid 2>/dev/null' EXIT + +"${BUILD_DIR}/ziti" edge quickstart ha \ + --home "${ziti_home}" \ + --trust-domain="quickstart-ha-test" \ + --instance-id inst001 \ + --ctrl-port "${ctrl_port}" \ + --router-port "${router_port}" \ + & +inst001pid=$! + +_wait_for_controller "${ctrl_port}" +sleep 5 +echo "controller online" + +"${BUILD_DIR}/ziti" edge quickstart join \ + --home "${ziti_home}" \ + --trust-domain="quickstart-ha-test" \ + --ctrl-port 2002 \ + --router-port 3002 \ + --instance-id "inst002" \ + --member-pid "${inst001pid}" & +inst002pid=$! + +"${BUILD_DIR}/ziti" edge quickstart join \ + --home "${ziti_home}" \ + --trust-domain="quickstart-ha-test" \ + --ctrl-port 2003 \ + --router-port 3003 \ + --instance-id "inst003" \ + --member-pid "${inst001pid}" & +inst003pid=$! + +count=0 +timeout=60 # Timeout in seconds +elapsed=0 + +while [[ $count -lt 3 ]]; do + results=$("${BUILD_DIR}/ziti" fabric list links -j | jq -r '.data[].state') + connected_count=$(echo "$results" | grep -c "Connected") + + if [[ $connected_count -eq 3 ]]; then + echo "All three are connected." + break + else + echo "Waiting for three router links before continuing..." + sleep 3 + ((elapsed+=3)) + + if [[ $elapsed -ge $timeout ]]; then + echo "Timeout reached; not all connections are 'Connected'." + exit 1 + fi + fi +done + +# three links == things are ready -- tests start below +output=$("${BUILD_DIR}/ziti" agent cluster list --pid $inst001pid) + +echo "" +echo "$output" +echo "" + +# Extract the columns for LEADER and CONNECTED +leaders=$(echo "$output" | grep inst | awk -F '│' '{print $5}') +connected=$(echo "$output" | grep inst | awk -F '/│' '{print $6}') + +# Check there is only one leader +leader_count=$(echo "$leaders" | grep -c "true") +if [[ $leader_count -ne 1 ]]; then + echo "Test failed: Expected 1 leader, found $leader_count" + _stop_instances $inst001pid $inst002pid $inst003pid + exit 1 +fi + +# Check all are connected +disconnected_count=$(echo "$connected" | grep -c "false") +if [[ $disconnected_count -ne 0 ]]; then + echo "Test failed: Some instances are not connected" + _stop_instances $inst001pid $inst002pid $inst003pid + exit 1 +fi + +echo "Test passed: One leader found and all instances are connected" +_stop_instances $inst001pid $inst002pid $inst003pid + diff --git a/ziti/cmd/create/config_templates/controller.yml b/ziti/cmd/create/config_templates/controller.yml index 41035a93e..e71470788 100644 --- a/ziti/cmd/create/config_templates/controller.yml +++ b/ziti/cmd/create/config_templates/controller.yml @@ -233,6 +233,8 @@ web: options: { } - binding: fabric options: { } + - binding: edge-oidc + options: { } {{ if not .Controller.Web.BindPoints.Console.Enabled }}#{{- end }}- binding: zac {{ if not .Controller.Web.BindPoints.Console.Enabled }}#{{- end }} options: {{ if not .Controller.Web.BindPoints.Console.Enabled }}#{{- end }} location: {{ .Controller.Web.BindPoints.Console.Location }} diff --git a/ziti/cmd/create/config_templates/router.yml b/ziti/cmd/create/config_templates/router.yml index a137f9a41..5e5cb22b4 100644 --- a/ziti/cmd/create/config_templates/router.yml +++ b/ziti/cmd/create/config_templates/router.yml @@ -16,6 +16,9 @@ identity: {{ if not .Router.AltCertsEnabled }}#{{ end }} - server_cert: "{{ .Router.AltServerCert }}" {{ if not .Router.AltCertsEnabled }}#{{ end }} server_key: "{{ .Router.AltServerKey }}" +ha: + enabled: {{ .Router.IsHA }} + ctrl: endpoint: tls:{{ .Controller.Ctrl.AdvertisedAddress }}:{{ .Controller.Ctrl.AdvertisedPort }} diff --git a/ziti/cmd/create/create_config.go b/ziti/cmd/create/create_config.go index b3867ea7c..4536beca9 100644 --- a/ziti/cmd/create/create_config.go +++ b/ziti/cmd/create/create_config.go @@ -72,6 +72,7 @@ type CtrlValues struct { BindAddress string AltAdvertisedAddress string MinClusterSize int + InstanceId string } type HealthChecksValues struct { @@ -112,8 +113,8 @@ type BindPointsValues struct { } type ConsoleValues struct { - Enabled bool - Location string + Enabled bool + Location string } type IdentityValues struct { @@ -165,6 +166,7 @@ type RouterTemplateValues struct { Wss WSSRouterTemplateValues Forwarder RouterForwarderTemplateValues Listener RouterListenerTemplateValues + IsHA bool } type EdgeRouterTemplateValues struct { diff --git a/ziti/cmd/create/create_config_router.go b/ziti/cmd/create/create_config_router.go index 2537dbf4e..202443b6c 100644 --- a/ziti/cmd/create/create_config_router.go +++ b/ziti/cmd/create/create_config_router.go @@ -38,6 +38,7 @@ type CreateConfigRouterOptions struct { IsPrivate bool TunnelerMode string LanInterface string + IsHA bool } type NewCreateConfigRouterCmd struct { diff --git a/ziti/cmd/create/create_config_router_edge.go b/ziti/cmd/create/create_config_router_edge.go index e9e255667..53f5fcf53 100644 --- a/ziti/cmd/create/create_config_router_edge.go +++ b/ziti/cmd/create/create_config_router_edge.go @@ -78,6 +78,7 @@ func NewCmdCreateConfigRouterEdge(routerOptions *CreateConfigRouterOptions, data data.Router.Edge.LanInterface = routerOptions.LanInterface data.Router.Edge.Resolver = cmdhelper.GetZitiEdgeRouterResolver() data.Router.Edge.DnsSvcIpRange = cmdhelper.GetZitiEdgeRouterDnsSvcIpRange() + data.Router.IsHA = routerOptions.IsHA }, Run: func(cmd *cobra.Command, args []string) { routerOptions.Cmd = cmd diff --git a/ziti/cmd/edge/quickstart.go b/ziti/cmd/edge/quickstart.go index d0ed9b16a..ef611c973 100644 --- a/ziti/cmd/edge/quickstart.go +++ b/ziti/cmd/edge/quickstart.go @@ -20,25 +20,36 @@ import ( "context" "crypto/tls" "fmt" - "github.com/openziti/ziti/common/version" - edgeSubCmd "github.com/openziti/ziti/controller/subcmd" - "github.com/openziti/ziti/ziti/cmd/create" - "github.com/openziti/ziti/ziti/cmd/helpers" - "github.com/openziti/ziti/ziti/cmd/pki" - "github.com/openziti/ziti/ziti/constants" - controller2 "github.com/openziti/ziti/ziti/controller" - "github.com/openziti/ziti/ziti/router" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" "io" + "net" "net/http" "os" "os/signal" "os/user" + "path" + "regexp" "strconv" "strings" "syscall" "time" + + "github.com/google/uuid" + "github.com/michaelquigley/pfxlog" + "github.com/openziti/ziti/common/version" + "github.com/openziti/ziti/controller/rest_client/raft" + edgeSubCmd "github.com/openziti/ziti/controller/subcmd" + "github.com/openziti/ziti/ziti/cmd/agentcli" + "github.com/openziti/ziti/ziti/cmd/api" + "github.com/openziti/ziti/ziti/cmd/common" + "github.com/openziti/ziti/ziti/cmd/create" + "github.com/openziti/ziti/ziti/cmd/helpers" + "github.com/openziti/ziti/ziti/cmd/pki" + "github.com/openziti/ziti/ziti/constants" + ctrlcmd "github.com/openziti/ziti/ziti/controller" + "github.com/openziti/ziti/ziti/router" + "github.com/openziti/ziti/ziti/util" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" ) type QuickstartOpts struct { @@ -53,16 +64,45 @@ type QuickstartOpts struct { out io.Writer errOut io.Writer cleanOnExit bool + TrustDomain string + isHA bool + InstanceID string + MemberPID int + joinCommand bool + verbose bool + nonVoter bool + routerless bool } -// NewQuickStartCmd creates a command object for the "create" command -func NewQuickStartCmd(out io.Writer, errOut io.Writer, context context.Context) *cobra.Command { +func addCommonQuickstartFlags(cmd *cobra.Command, options *QuickstartOpts) { currentCtrlAddy := helpers.GetCtrlEdgeAdvertisedAddress() currentCtrlPort := helpers.GetCtrlEdgeAdvertisedPort() currentRouterAddy := helpers.GetRouterAdvertisedAddress() currentRouterPort := helpers.GetZitiEdgeRouterPort() - defautlCtrlPort, _ := strconv.ParseInt(constants.DefaultCtrlEdgeAdvertisedPort, 10, 16) - defautlRouterPort, _ := strconv.ParseInt(constants.DefaultZitiEdgeRouterPort, 10, 16) + defaultCtrlPort, _ := strconv.ParseInt(constants.DefaultCtrlEdgeAdvertisedPort, 10, 16) + defaultRouterPort, _ := strconv.ParseInt(constants.DefaultZitiEdgeRouterPort, 10, 16) + + cmd.Flags().StringVarP(&options.Username, "username", "u", "", "admin username, default: admin") + cmd.Flags().StringVarP(&options.Password, "password", "p", "", "admin password, default: admin") + + cmd.Flags().StringVar(&options.Home, "home", "", "permanent directory") + + cmd.Flags().StringVar(&options.ControllerAddress, "ctrl-address", "", "sets the advertised address for the control plane and API. current: "+currentCtrlAddy) + cmd.Flags().Int16Var(&options.ControllerPort, "ctrl-port", int16(defaultCtrlPort), "sets the port to use for the control plane and API. current: "+currentCtrlPort) + cmd.Flags().StringVar(&options.RouterAddress, "router-address", "", "sets the advertised address for the integrated router. current: "+currentRouterAddy) + cmd.Flags().Int16Var(&options.RouterPort, "router-port", int16(defaultRouterPort), "sets the port to use for the integrated router. current: "+currentRouterPort) + cmd.Flags().BoolVar(&options.routerless, "no-router", false, "specifies the quickstart should not start a router") + + cmd.Flags().BoolVar(&options.verbose, "verbose", false, "Show additional output.") +} + +func addQuickstartHaFlags(cmd *cobra.Command, options *QuickstartOpts) { + cmd.Flags().StringVar(&options.TrustDomain, "trust-domain", "", "the specified trust domain to be used in SPIFFE ids.") + cmd.Flags().StringVar(&options.InstanceID, "instance-id", "", "specifies a unique instance id for use in ha mode.") +} + +// NewQuickStartCmd creates a command object for the "create" command +func NewQuickStartCmd(out io.Writer, errOut io.Writer, context context.Context) *cobra.Command { options := &QuickstartOpts{} cmd := &cobra.Command{ Use: "quickstart", @@ -71,18 +111,57 @@ func NewQuickStartCmd(out io.Writer, errOut io.Writer, context context.Context) Run: func(cmd *cobra.Command, args []string) { options.out = out options.errOut = errOut + options.TrustDomain = "quickstart" + options.InstanceID = "quickstart" options.run(context) }, } - cmd.Flags().StringVarP(&options.Username, "username", "u", "", "Admin username, default: admin") - cmd.Flags().StringVarP(&options.Password, "password", "p", "", "Admin password, default: admin") + addCommonQuickstartFlags(cmd, options) + cmd.AddCommand(NewQuickStartJoinClusterCmd(out, errOut, context)) + cmd.AddCommand(NewQuickStartHaCmd(out, errOut, context)) + return cmd +} - cmd.Flags().StringVar(&options.Home, "home", "", "permanent directory") +func NewQuickStartHaCmd(out io.Writer, errOut io.Writer, context context.Context) *cobra.Command { + options := &QuickstartOpts{} + cmd := &cobra.Command{ + Use: "ha", + Short: "runs a Controller and Router in quickstart HA mode and creates the first cluster member", + Long: "runs a Controller and Router in quickstart HA mode and creates the first cluster member with a temporary directory; suitable for testing and development", + Run: func(cmd *cobra.Command, args []string) { + options.out = out + options.errOut = errOut + options.isHA = true + if options.TrustDomain == "" { + options.TrustDomain = uuid.New().String() + fmt.Println("Trust domain was not supplied. Using a random trust domain: " + options.TrustDomain) + } + options.run(context) + }, + } + addCommonQuickstartFlags(cmd, options) + addQuickstartHaFlags(cmd, options) + cmd.Hidden = true + return cmd +} - cmd.Flags().StringVar(&options.ControllerAddress, "ctrl-address", "", "Sets the advertised address for the control plane and API. current: "+currentCtrlAddy) - cmd.Flags().Int16Var(&options.ControllerPort, "ctrl-port", int16(defautlCtrlPort), "Sets the port to use for the control plane and API. current: "+currentCtrlPort) - cmd.Flags().StringVar(&options.RouterAddress, "router-address", "", "Sets the advertised address for the integrated router. current: "+currentRouterAddy) - cmd.Flags().Int16Var(&options.RouterPort, "router-port", int16(defautlRouterPort), "Sets the port to use for the integrated router. current: "+currentRouterPort) +func NewQuickStartJoinClusterCmd(out io.Writer, errOut io.Writer, context context.Context) *cobra.Command { + options := &QuickstartOpts{} + cmd := &cobra.Command{ + Use: "join", + Short: "runs a Controller and Router in quickstart mode and joins an existing cluster", + Long: "runs a Controller and Router in quickstart mode and joins an existing cluster with a temporary directory; suitable for testing and development", + Run: func(cmd *cobra.Command, args []string) { + options.out = out + options.errOut = errOut + options.join(context) + }, + } + addCommonQuickstartFlags(cmd, options) + addQuickstartHaFlags(cmd, options) + cmd.Flags().IntVarP(&options.MemberPID, "member-pid", "m", 0, "the pid of a cluster member. required") + cmd.Flags().BoolVar(&options.nonVoter, "non-voting", false, "used with ha mode. specifies the member is a non-voting member") + cmd.Hidden = true return cmd } @@ -95,7 +174,27 @@ func (o *QuickstartOpts) cleanupHome() { } } +func (o *QuickstartOpts) join(ctx context.Context) { + if strings.TrimSpace(o.InstanceID) == "" { + logrus.Fatalf("the instance-id is required when joining a cluster") + } + if strings.TrimSpace(o.Home) == "" { + logrus.Fatalf("the home directory must be specified when joining an existing cluster. the root-ca is used to create the server's pki") + } + + if o.MemberPID == 0 { + logrus.Fatalf("--member-pid is required") + } + o.isHA = true + o.joinCommand = true + o.run(ctx) +} + func (o *QuickstartOpts) run(ctx context.Context) { + if o.verbose { + pfxlog.GlobalInit(logrus.DebugLevel, pfxlog.DefaultOptions().Color()) + } + //set env vars if o.Home == "" { tmpDir, _ := os.MkdirTemp("", "quickstart") @@ -137,52 +236,73 @@ func (o *QuickstartOpts) run(ctx context.Context) { o.Password = "admin" } - ctrlYaml := o.Home + "/ctrl.yaml" + if o.InstanceID == "" { + o.InstanceID = uuid.New().String() + } + + ctrlYaml := path.Join(o.instHome(), "ctrl.yaml") + routerName := "router-" + o.InstanceID //ZITI_HOME=/tmp ziti create config controller | grep -v "#" | sed -E 's/^ *$//g' | sed '/^$/d' _ = os.Setenv("ZITI_HOME", o.Home) - _ = os.Setenv("ZITI_PKI_CTRL_CA", o.Home+"/pki/root-ca/certs/root-ca.cert") - _ = os.Setenv("ZITI_PKI_CTRL_KEY", o.Home+"/pki/intermediate-ca/keys/server.key") - _ = os.Setenv("ZITI_PKI_CTRL_CERT", o.Home+"/pki/intermediate-ca/certs/client.chain.pem") - _ = os.Setenv("ZITI_PKI_SIGNER_CERT", o.Home+"/pki/intermediate-ca/certs/intermediate-ca.cert") - _ = os.Setenv("ZITI_PKI_SIGNER_KEY", o.Home+"/pki/intermediate-ca/keys/intermediate-ca.key") - _ = os.Setenv("ZITI_PKI_CTRL_SERVER_CERT", o.Home+"/pki/intermediate-ca/certs/server.chain.pem") - - routerName := "quickstart-router" + pkiLoc := path.Join(o.Home, "pki") + rootLoc := path.Join(pkiLoc, "root-ca") + pkiIntermediateName := o.scopedName("intermediate-ca") + pkiServerName := o.scopedNameOff("server") + pkiClientName := o.scopedNameOff("client") + intermediateLoc := path.Join(pkiLoc, pkiIntermediateName) + _ = os.Setenv("ZITI_PKI_CTRL_CA", path.Join(rootLoc, "certs", "root-ca.cert")) + _ = os.Setenv("ZITI_PKI_CTRL_KEY", path.Join(intermediateLoc, "keys", pkiServerName+".key")) + _ = os.Setenv("ZITI_PKI_CTRL_SERVER_CERT", path.Join(intermediateLoc, "certs", pkiServerName+".chain.pem")) + _ = os.Setenv("ZITI_PKI_CTRL_CERT", path.Join(intermediateLoc, "certs", pkiClientName+".chain.pem")) + _ = os.Setenv("ZITI_PKI_SIGNER_CERT", path.Join(intermediateLoc, "certs", pkiIntermediateName+".cert")) + _ = os.Setenv("ZITI_PKI_SIGNER_KEY", path.Join(intermediateLoc, "keys", pkiIntermediateName+".key")) + routerNameFromEnv := os.Getenv(constants.ZitiEdgeRouterNameVarName) if routerNameFromEnv != "" { routerName = routerNameFromEnv } - dbDir := o.Home + "/db" + dbDir := path.Join(o.instHome(), "db") if _, err := os.Stat(dbDir); !os.IsNotExist(err) { o.AlreadyInitialized = true } else { - _ = os.MkdirAll(dbDir, 0o777) + _ = os.MkdirAll(dbDir, 0o700) logrus.Debugf("made directory '%s'", dbDir) o.createMinimalPki() + _ = os.Setenv("ZITI_HOME", o.instHome()) ctrl := create.NewCmdCreateConfigController() - ctrl.SetArgs([]string{ + args := []string{ fmt.Sprintf("--output=%s", ctrlYaml), - }) - _ = ctrl.Execute() + } + if o.isHA { + args = append(args, fmt.Sprintf("--minCluster=%d", 1)) + } + ctrl.SetArgs(args) + err = ctrl.Execute() + if err != nil { + logrus.Fatal(err) + } - initCmd := edgeSubCmd.NewEdgeInitializeCmd(version.GetCmdBuildInfo()) - initCmd.SetArgs([]string{ - fmt.Sprintf("--username=%s", o.Username), - fmt.Sprintf("--password=%s", o.Password), - ctrlYaml, - }) - initErr := initCmd.Execute() - if initErr != nil { - logrus.Fatal(initErr) + if !o.isHA { + initCmd := edgeSubCmd.NewEdgeInitializeCmd(version.GetCmdBuildInfo()) + initCmd.SetArgs([]string{ + fmt.Sprintf("--username=%s", o.Username), + fmt.Sprintf("--password=%s", o.Password), + ctrlYaml, + }) + initErr := initCmd.Execute() + if initErr != nil { + logrus.Fatal(initErr) + } } } + fmt.Println("Starting controller...") go func() { - runCtrl := controller2.NewRunCmd() + runCtrl := ctrlcmd.NewRunCmd() runCtrl.SetArgs([]string{ ctrlYaml, }) @@ -191,15 +311,13 @@ func (o *QuickstartOpts) run(ctx context.Context) { logrus.Fatal(runCtrlErr) } }() - - fmt.Println("Controller running... Configuring and starting Router...") + fmt.Println("Controller running...") ctrlAddy := helpers.GetCtrlEdgeAdvertisedAddress() ctrlPort := helpers.GetCtrlEdgeAdvertisedPort() ctrlUrl := fmt.Sprintf("https://%s:%s", ctrlAddy, ctrlPort) c := make(chan struct{}) - defer close(c) timeout, _ := time.ParseDuration("30s") go waitForController(ctrlUrl, c) select { @@ -212,7 +330,130 @@ func (o *QuickstartOpts) run(ctx context.Context) { return } - erYaml := o.Home + "/" + routerName + ".yaml" + if o.isHA { + p := common.NewOptionsProvider(o.out, o.errOut) + if !o.joinCommand { + fmt.Println("waiting three seconds for controller to become ready...") + time.Sleep(3 * time.Second) + agentInitCmd := agentcli.NewAgentCtrlInit(p) + pid := os.Getpid() + args := []string{ + o.Username, + o.Password, + o.Username, + fmt.Sprintf("--pid=%d", pid), + } + agentInitCmd.SetArgs(args) + + agentInitErr := agentInitCmd.Execute() + if agentInitErr != nil { + logrus.Fatal(agentInitErr) + } + } else { + agentJoinCmd := agentcli.NewAgentClusterAdd(p) + + args := []string{ + fmt.Sprintf("tls:%s:%s", helpers.GetCtrlAdvertisedAddress(), helpers.GetCtrlAdvertisedPort()), + fmt.Sprintf("--pid=%d", o.MemberPID), + fmt.Sprintf("--voter=%t", !o.nonVoter), + } + agentJoinCmd.SetArgs(args) + + fmt.Println("waiting three seconds for controller to become ready...") + time.Sleep(3 * time.Second) + + addChan := make(chan struct{}) + addTimeout := time.Second * 30 + go func() { + o.waitForLeader() + agentJoinErr := agentJoinCmd.Execute() + if agentJoinErr != nil { + logrus.Fatal(agentJoinErr) + } + close(addChan) + }() + + select { + case <-addChan: + //completed normally + logrus.Info("Add command successful. continuing...") + case <-time.After(addTimeout): + fmt.Println("timed out adding to cluster") + o.cleanupHome() + return + } + } + } + + erConfigFile := path.Join(o.instHome(), routerName+".yaml") + o.configureRouter(routerName, erConfigFile, ctrlUrl) + o.runRouter(erConfigFile) + + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGTERM) + + if !o.routerless { + r := make(chan struct{}) + timeout, _ = time.ParseDuration("30s") + go waitForRouter(o.RouterAddress, o.RouterPort, r) + select { + case <-r: + //completed normally + case <-time.After(timeout): + fmt.Println("timed out waiting for router:", ctrlUrl) + o.cleanupHome() + return + } + } + + if o.isHA { + go func() { + time.Sleep(3 * time.Second) // output this after a bit... + nextInstId := incrementStringSuffix(o.InstanceID) + fmt.Println() + o.printDetails() + fmt.Println("=======================================================================================") + fmt.Println("Quickly add another member to this cluster using: ") + fmt.Printf(" ziti edge quickstart join \\\n") + fmt.Printf(" --ctrl-port %d \\\n", o.ControllerPort+1) + fmt.Printf(" --router-port %d \\\n", o.RouterPort+1) + fmt.Printf(" --home \"%s\" \\\n", o.Home) + fmt.Printf(" --trust-domain=\"%s\" \\\n", o.TrustDomain) + fmt.Printf(" --member-pid %d\\ \n", os.Getpid()) + fmt.Printf(" --instance-id \"%s\"\n", nextInstId) + fmt.Println("=======================================================================================") + fmt.Println() + }() + } else { + fmt.Println() + o.printDetails() + fmt.Println("=======================================================================================") + } + + select { + case <-ch: + fmt.Println("Signal to shutdown received") + case <-ctx.Done(): + fmt.Println("Cancellation request received") + } + o.cleanupHome() +} + +func (o *QuickstartOpts) printDetails() { + fmt.Println("=======================================================================================") + fmt.Println("controller and router started.") + fmt.Println(" controller located at : " + helpers.GetCtrlAdvertisedAddress() + ":" + strconv.Itoa(int(o.ControllerPort))) + fmt.Println(" router located at : " + helpers.GetRouterAdvertisedAddress() + ":" + strconv.Itoa(int(o.RouterPort))) + fmt.Println(" config dir located at : " + o.Home) + fmt.Println(" configured trust domain: " + o.TrustDomain) + fmt.Printf(" instance pid : %d\n", os.Getpid()) +} + +func (o *QuickstartOpts) configureRouter(routerName string, configFile string, ctrlUrl string) { + if o.routerless { + return + } + if !o.AlreadyInitialized { loginCmd := NewLoginCmd(o.out, o.errOut) loginCmd.SetArgs([]string{ @@ -221,47 +462,33 @@ func (o *QuickstartOpts) run(ctx context.Context) { fmt.Sprintf("--password=%s", o.Password), "-y", }) + if o.joinCommand { + o.waitForLeader() + } loginErr := loginCmd.Execute() if loginErr != nil { logrus.Fatal(loginErr) } - // Allow all identities to use any edge router with the "public" attribute - // ziti edge create edge-router-policy all-endpoints-public-routers --edge-router-roles "#public" --identity-roles "#all" - erpCmd := NewCreateEdgeRouterPolicyCmd(o.out, o.errOut) - erpCmd.SetArgs([]string{ - "all-endpoints-public-routers", - fmt.Sprintf("--edge-router-roles=%s", "#public"), - fmt.Sprintf("--identity-roles=%s", "#all"), - }) - erpCmdErr := erpCmd.Execute() - if erpCmdErr != nil { - logrus.Fatal(erpCmdErr) - } + o.configureOverlay() - // # Allow all edge-routers to access all services - // ziti edge create service-edge-router-policy all-routers-all-services --edge-router-roles "#all" --service-roles "#all" - serpCmd := NewCreateServiceEdgeRouterPolicyCmd(o.out, o.errOut) - serpCmd.SetArgs([]string{ - "all-routers-all-services", - fmt.Sprintf("--edge-router-roles=%s", "#all"), - fmt.Sprintf("--service-roles=%s", "#all"), - }) - serpCmdErr := serpCmd.Execute() - if serpCmdErr != nil { - logrus.Fatal(serpCmdErr) - } + time.Sleep(1 * time.Second) + + var erJwt string // ziti edge create edge-router ${ZITI_HOSTNAME}-edge-router -o ${ZITI_HOME}/${ZITI_HOSTNAME}-edge-router.jwt -t -a public createErCmd := NewCreateEdgeRouterCmd(o.out, o.errOut) - erJwt := o.Home + "/" + routerName + ".jwt" + erJwt = path.Join(o.Home, routerName+".jwt") createErCmd.SetArgs([]string{ routerName, fmt.Sprintf("--jwt-output-file=%s", erJwt), "--tunneler-enabled", fmt.Sprintf("--role-attributes=%s", "public"), }) + + o.waitForLeader() //wait for a leader before doing anything createErErr := createErCmd.Execute() + if createErErr != nil { logrus.Fatal(createErErr) } @@ -271,12 +498,15 @@ func (o *QuickstartOpts) run(ctx context.Context) { data := &create.ConfigTemplateValues{} data.PopulateConfigValues() + opts.IsHA = o.isHA create.SetZitiRouterIdentity(&data.Router, routerName) erCfg := create.NewCmdCreateConfigRouterEdge(opts, data) erCfg.SetArgs([]string{ fmt.Sprintf("--routerName=%s", routerName), - fmt.Sprintf("--output=%s", erYaml), + fmt.Sprintf("--output=%s", configFile), }) + + o.waitForLeader() erCfgErr := erCfg.Execute() if erCfgErr != nil { logrus.Fatal(erCfgErr) @@ -285,62 +515,81 @@ func (o *QuickstartOpts) run(ctx context.Context) { // ziti router enroll ${ZITI_HOME}/${ZITI_HOSTNAME}-edge-router.yaml --jwt ${ZITI_HOME}/${ZITI_HOSTNAME}-edge-router.jwt erEnroll := router.NewEnrollGwCmd() erEnroll.SetArgs([]string{ - erYaml, + configFile, fmt.Sprintf("--jwt=%s", erJwt), }) + + o.waitForLeader() //needed? erEnrollErr := erEnroll.Execute() if erEnrollErr != nil { logrus.Fatal(erEnrollErr) } } +} +func (o *QuickstartOpts) runRouter(configFile string) { + if o.routerless { + return + } go func() { // ziti router run ${ZITI_HOME}/${ZITI_HOSTNAME}-edge-router.yaml &> ${ZITI_HOME}/${ZITI_HOSTNAME}-edge-router.log & erRunCmd := router.NewRunCmd() erRunCmd.SetArgs([]string{ - erYaml, + configFile, }) + + o.waitForLeader() //needed? erRunCmdErr := erRunCmd.Execute() if erRunCmdErr != nil { logrus.Fatal(erRunCmdErr) } }() - - ch := make(chan os.Signal, 1) - signal.Notify(ch, os.Interrupt, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGTERM) - - select { - case <-ch: - fmt.Println("Signal to shutdown received") - case <-ctx.Done(): - fmt.Println("Cancellation request received") - } - o.cleanupHome() } func (o *QuickstartOpts) createMinimalPki() { - where := o.Home + "/pki" + where := path.Join(o.Home, "pki") fmt.Println("emitting a minimal PKI") - //ziti pki create ca --pki-root="$pkiDir" --ca-file="root-ca" --ca-name="root-ca" - ca := pki.NewCmdPKICreateCA(o.out, o.errOut) - ca.SetArgs([]string{ - fmt.Sprintf("--pki-root=%s", where), - fmt.Sprintf("--ca-file=%s", "root-ca"), - fmt.Sprintf("--ca-name=%s", "root-ca"), - }) - pkiErr := ca.Execute() - if pkiErr != nil { - logrus.Fatal(pkiErr) + sid := fmt.Sprintf("spiffe://%s/controller/%s", o.TrustDomain, o.InstanceID) + + //ziti pki create ca --pki-root="$pkiDir" --ca-file="root-ca" --ca-name="root-ca" --spiffe-id="whatever" + if o.joinCommand { + // indicates we are joining a cluster. don't emit a root-ca, expect it'll be there or error + } else { + rootCaPath := path.Join(where, "root-ca", "certs", "root-ca.cert") + rootCa, statErr := os.Stat(rootCaPath) + if statErr != nil { + if !os.IsNotExist(statErr) { + logrus.Warnf("could not check for root-ca: %v", statErr) + } + } + if rootCa == nil { + ca := pki.NewCmdPKICreateCA(o.out, o.errOut) + rootCaArgs := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-file=%s", "root-ca"), + fmt.Sprintf("--ca-name=%s", "root-ca"), + fmt.Sprintf("--trust-domain=%s", o.TrustDomain), + } + + ca.SetArgs(rootCaArgs) + pkiErr := ca.Execute() + if pkiErr != nil { + logrus.Fatal(pkiErr) + } + + } else { + logrus.Infof("%s exists and will be reused", rootCaPath) + } } - //ziti pki create intermediate --pki-root "$pkiDir" --ca-name "root-ca" --intermediate-name "intermediate-ca" --intermediate-file "intermediate-ca" --max-path-len "1" + //ziti pki create intermediate --pki-root "$pkiDir" --ca-name "root-ca" --intermediate-name "intermediate-ca" --intermediate-file "intermediate-ca" --max-path-len "1" --spiffe-id="whatever" intermediate := pki.NewCmdPKICreateIntermediate(o.out, o.errOut) intermediate.SetArgs([]string{ fmt.Sprintf("--pki-root=%s", where), fmt.Sprintf("--ca-name=%s", "root-ca"), - fmt.Sprintf("--intermediate-name=%s", "intermediate-ca"), - fmt.Sprintf("--intermediate-file=%s", "intermediate-ca"), + fmt.Sprintf("--intermediate-name=%s", o.scopedName("intermediate-ca")), + fmt.Sprintf("--intermediate-file=%s", o.scopedName("intermediate-ca")), "--max-path-len=1", }) intErr := intermediate.Execute() @@ -348,34 +597,38 @@ func (o *QuickstartOpts) createMinimalPki() { logrus.Fatal(intErr) } - //ziti pki create server --pki-root="${ZITI_HOME}/pki" --ca-name "intermediate-ca" --server-name "server" --server-file "server" --dns "localhost,${ZITI_HOSTNAME}" + //ziti pki create server --pki-root="${ZITI_HOME}/pki" --ca-name "intermediate-ca" --server-name "server" --server-file "server" --dns "localhost,${ZITI_HOSTNAME}" --spiffe-id="whatever" svr := pki.NewCmdPKICreateServer(o.out, o.errOut) var ips = "127.0.0.1,::1" - ip_override := os.Getenv("ZITI_CTRL_EDGE_IP_OVERRIDE") - if ip_override != "" { - ips = ips + "," + ip_override + ipOverride := os.Getenv("ZITI_CTRL_EDGE_IP_OVERRIDE") + if ipOverride != "" { + ips = ips + "," + ipOverride } - svr.SetArgs([]string{ + args := []string{ fmt.Sprintf("--pki-root=%s", where), - fmt.Sprintf("--ca-name=%s", "intermediate-ca"), - fmt.Sprintf("--server-name=%s", "server"), - fmt.Sprintf("--server-file=%s", "server"), + fmt.Sprintf("--ca-name=%s", o.scopedName("intermediate-ca")), + fmt.Sprintf("--server-name=%s", o.InstanceID), + fmt.Sprintf("--server-file=%s", o.scopedNameOff("server")), fmt.Sprintf("--dns=%s,%s", "localhost", helpers.GetCtrlAdvertisedAddress()), fmt.Sprintf("--ip=%s", ips), - }) + fmt.Sprintf("--spiffe-id=%s", sid), + } + + svr.SetArgs(args) svrErr := svr.Execute() if svrErr != nil { logrus.Fatal(svrErr) } - //ziti pki create client --pki-root="${ZITI_HOME}/pki" --ca-name "intermediate-ca" --client-name "client" --client-file "client" --key-file "server" + //ziti pki create client --pki-root="${ZITI_HOME}/pki" --ca-name "intermediate-ca" --client-name "client" --client-file "client" --key-file "server" --spiffe-id="whatever" client := pki.NewCmdPKICreateClient(o.out, o.errOut) client.SetArgs([]string{ fmt.Sprintf("--pki-root=%s", where), - fmt.Sprintf("--ca-name=%s", "intermediate-ca"), - fmt.Sprintf("--client-name=%s", "client"), - fmt.Sprintf("--client-file=%s", "client"), - fmt.Sprintf("--key-file=%s", "server"), + fmt.Sprintf("--ca-name=%s", o.scopedName("intermediate-ca")), + fmt.Sprintf("--client-name=%s", o.InstanceID), + fmt.Sprintf("--client-file=%s", o.scopedNameOff("client")), + fmt.Sprintf("--key-file=%s", o.scopedNameOff("server")), + fmt.Sprintf("--spiffe-id=%s", sid), }) clientErr := client.Execute() if clientErr != nil { @@ -396,3 +649,119 @@ func waitForController(ctrlUrl string, done chan struct{}) { } done <- struct{}{} } + +func waitForRouter(address string, port int16, done chan struct{}) { + for { + addr := fmt.Sprintf("%s:%d", address, port) + conn, err := net.DialTimeout("tcp", addr, 2*time.Second) + if err == nil { + _ = conn.Close() + fmt.Printf("Router is available on %s:%d\n", address, port) + close(done) + return + } + time.Sleep(25 * time.Millisecond) + } +} + +func (o *QuickstartOpts) scopedNameOff(name string) string { + return name +} +func (o *QuickstartOpts) scopedName(name string) string { + if o.InstanceID != "" { + return name + "-" + o.InstanceID + } else { + return name + } +} + +func (o *QuickstartOpts) instHome() string { + if o.isHA { + return path.Join(o.Home, o.InstanceID) + } + return o.Home +} + +func (o *QuickstartOpts) configureOverlay() { + if o.joinCommand { + return + } + + // Allow all identities to use any edge router with the "public" attribute + // ziti edge create edge-router-policy all-endpoints-public-routers --edge-router-roles "#public" --identity-roles "#all" + erpCmd := NewCreateEdgeRouterPolicyCmd(o.out, o.errOut) + erpCmd.SetArgs([]string{ + "all-endpoints-public-routers", + fmt.Sprintf("--edge-router-roles=%s", "#public"), + fmt.Sprintf("--identity-roles=%s", "#all"), + }) + erpCmdErr := erpCmd.Execute() + if erpCmdErr != nil { + logrus.Fatal(erpCmdErr) + } + + // # Allow all edge-routers to access all services + // ziti edge create service-edge-router-policy all-routers-all-services --edge-router-roles "#all" --service-roles "#all" + serpCmd := NewCreateServiceEdgeRouterPolicyCmd(o.out, o.errOut) + serpCmd.SetArgs([]string{ + "all-routers-all-services", + fmt.Sprintf("--edge-router-roles=%s", "#all"), + fmt.Sprintf("--service-roles=%s", "#all"), + }) + o.waitForLeader() + serpCmdErr := serpCmd.Execute() + if serpCmdErr != nil { + logrus.Fatal(serpCmdErr) + } +} + +type raftListMembersAction struct { + api.Options +} + +func (o *QuickstartOpts) waitForLeader() bool { + for { + p := common.NewOptionsProvider(o.out, o.errOut) + action := &raftListMembersAction{ + Options: api.Options{CommonOptions: p()}, + } + + client, err := util.NewFabricManagementClient(action) + if err != nil { + return false + } + members, err := client.Raft.RaftListMembers(&raft.RaftListMembersParams{ + Context: context.Background(), + }) + + if err != nil { + return false + } + for _, m := range members.Payload.Data { + if m.Leader != nil && *m.Leader { + time.Sleep(500 * time.Millisecond) // this just gives time for the leader to 'settle' -- shouldn't be necessary + return true + } + } + time.Sleep(50 * time.Millisecond) + } +} + +func incrementStringSuffix(input string) string { + // Regular expression to capture the numeric suffix + re := regexp.MustCompile(`(\d+)$`) + match := re.FindStringSubmatch(input) + + if len(match) == 0 { + return uuid.New().String() + } + + numStr := match[1] + numLength := len(numStr) + num, _ := strconv.Atoi(numStr) + num++ + + incremented := fmt.Sprintf("%0*d", numLength, num) + + return strings.TrimSuffix(input, numStr) + incremented +} diff --git a/ziti/cmd/edge/quickstart_shared_test.go b/ziti/cmd/edge/quickstart_shared_test.go index d2dbecb6a..e73c06b8d 100644 --- a/ziti/cmd/edge/quickstart_shared_test.go +++ b/ziti/cmd/edge/quickstart_shared_test.go @@ -148,13 +148,23 @@ func getIdentityByName(client *rest_management_api_client.ZitiEdgeManagement, na Context: context.Background(), } params.SetTimeout(30 * time.Second) - resp, err := client.Identity.ListIdentities(params, nil) - if err != nil { - log.Fatalf("Could not obtain an ID for the identity named %s", name) - fmt.Println(err) - } - return resp.GetPayload().Data[0] + timeout := time.Now().Add(3 * time.Second) + + for { + resp, err := client.Identity.ListIdentities(params, nil) + if err == nil && len(resp.GetPayload().Data) > 0 { + return resp.GetPayload().Data[0] + } + + if time.Now().After(timeout) { + log.Fatalf("Could not obtain an ID for the identity named %s after retries", name) + return nil + } + + fmt.Printf("Retrying to fetch identity %s...\n", name) + time.Sleep(100 * time.Millisecond) + } } func getServiceByName(client *rest_management_api_client.ZitiEdgeManagement, name string) *rest_model.ServiceDetail { diff --git a/ziti/cmd/pki/common_test.go b/ziti/cmd/pki/common_test.go new file mode 100644 index 000000000..6c6207e7b --- /dev/null +++ b/ziti/cmd/pki/common_test.go @@ -0,0 +1,193 @@ +/* +Copyright NetFoundry Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package pki + +import ( + "bytes" + "fmt" + "github.com/openziti/ziti/ziti/pki/pki" + "github.com/openziti/ziti/ziti/pki/store" + "github.com/sirupsen/logrus" + "net" + "net/url" + "os" + "testing" +) + +var where = "/tmp/pki-test" +var trustDomain = "pki-test-domain" +var testPki *pki.ZitiPKI + +var rootCaWithSpiffeIdName = "root-ca-with-spiffe-id" +var rootCaWithoutSpiffeIdName = "root-ca-without-spiffe-id" +var intCaNameWithSpiffeIdName = "intermediate-ca-with-spiffe-id" +var intCaNameWithoutSpiffeIdName = "intermediate-ca-without-spiffe-id" + +func streams() (*bytes.Buffer, *bytes.Buffer) { + return new(bytes.Buffer), new(bytes.Buffer) +} + +type URLSlice []*url.URL + +func (u URLSlice) Paths() []string { + paths := make([]string, len(u)) + for i, uri := range u { + paths[i] = uri.Path + } + return paths +} +func (u URLSlice) Hosts() []string { + hosts := make([]string, len(u)) + for i, uri := range u { + hosts[i] = uri.Host + } + return hosts +} + +func urisAsStrings(uris []*url.URL) []string { + urisAsStrings := make([]string, len(uris)) + for i, uri := range uris { + urisAsStrings[i] = uri.String() + } + return urisAsStrings +} + +func ipsAsStrings(ips []net.IP) []string { + ipsAsStrings := make([]string, len(ips)) + for i, ip := range ips { + ipsAsStrings[i] = ip.String() + } + return ipsAsStrings +} + +func TestMain(m *testing.M) { + var code int + if setup() { + // Run tests + code = m.Run() + } + teardown() + + // Exit with the code from the test run + os.Exit(code) +} + +func setup() bool { + where, _ = os.MkdirTemp("", "pki-test") + testPki = &pki.ZitiPKI{Store: &store.Local{}} + local := testPki.Store.(*store.Local) + local.Root = where + if !createTestCaWithSpiffeId() { + return false + } + if !createTestCaWithoutSpiffeId() { + return false + } + if !createTestIntermediateWithSpiffeId() { + return false + } + if !createTestIntermediateWithoutSpiffeId() { + return false + } + + return true +} + +func createTestCaWithSpiffeId() bool { + out, errOut := streams() + ca := NewCmdPKICreateCA(out, errOut) + + rootCaArgs := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-file=%s", rootCaWithSpiffeIdName), + fmt.Sprintf("--ca-name=%s", rootCaWithSpiffeIdName), + fmt.Sprintf("--trust-domain=%s", "spiffe://"+rootCaWithSpiffeIdName), + } + + ca.SetArgs(rootCaArgs) + pkiErr := ca.Execute() + if pkiErr != nil { + logrus.Error(pkiErr) + return false + } + return true +} +func createTestCaWithoutSpiffeId() bool { + out, errOut := streams() + ca := NewCmdPKICreateCA(out, errOut) + + rootCaArgs := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-file=%s", rootCaWithoutSpiffeIdName), + fmt.Sprintf("--ca-name=%s", rootCaWithoutSpiffeIdName), + } + + ca.SetArgs(rootCaArgs) + pkiErr := ca.Execute() + if pkiErr != nil { + logrus.Error(pkiErr) + return false + } + return true +} +func createTestIntermediateWithSpiffeId() bool { + out, errOut := streams() + intermediateCmd := NewCmdPKICreateIntermediate(out, errOut) + intermediateArgs := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-name=%s", rootCaWithSpiffeIdName), + fmt.Sprintf("--intermediate-name=%s", intCaNameWithSpiffeIdName), + fmt.Sprintf("--intermediate-file=%s", intCaNameWithSpiffeIdName), + "--max-path-len=1", + } + + intermediateCmd.SetArgs(intermediateArgs) + pkiErr := intermediateCmd.Execute() + if pkiErr != nil { + logrus.Error(pkiErr) + return false + } + return true +} +func createTestIntermediateWithoutSpiffeId() bool { + out, errOut := streams() + intermediateCmd := NewCmdPKICreateIntermediate(out, errOut) + intermediateArgs := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-name=%s", rootCaWithoutSpiffeIdName), + fmt.Sprintf("--intermediate-name=%s", intCaNameWithoutSpiffeIdName), + fmt.Sprintf("--intermediate-file=%s", intCaNameWithoutSpiffeIdName), + "--max-path-len=1", + } + + intermediateCmd.SetArgs(intermediateArgs) + pkiErr := intermediateCmd.Execute() + if pkiErr != nil { + logrus.Error(pkiErr) + return false + } + return true +} + +func teardown() { + fmt.Printf("removing temp directory: %s\n", where) + _ = os.RemoveAll(where) +} + +func addSpiffeArg(id string, args []string) []string { + args = append(args, "--spiffe-id="+id) + return args +} diff --git a/ziti/cmd/pki/pki_create_ca.go b/ziti/cmd/pki/pki_create_ca.go index 4b92e4295..56dcb93b4 100644 --- a/ziti/cmd/pki/pki_create_ca.go +++ b/ziti/cmd/pki/pki_create_ca.go @@ -114,6 +114,11 @@ func (o *PKICreateCAOptions) Run() error { if err != nil { return errors.Wrapf(err, "unable to parse spiffe id [%v]", o.Flags.SpiffeID) } + + if len(spiffeId.Path) > 0 && strings.TrimSpace(spiffeId.Path) != "/" { + log.Warnf("trust-domain [%v] includes path information and will be ignored", spiffeId.String()) + spiffeId.Path = "" + } template.URIs = append(template.URIs, spiffeId) } diff --git a/ziti/cmd/pki/pki_create_ca_test.go b/ziti/cmd/pki/pki_create_ca_test.go new file mode 100644 index 000000000..01cfb4667 --- /dev/null +++ b/ziti/cmd/pki/pki_create_ca_test.go @@ -0,0 +1,119 @@ +/* +Copyright NetFoundry Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package pki + +import ( + "fmt" + "github.com/google/uuid" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTrustDomain(t *testing.T) { + out, errOut := streams() + ca := NewCmdPKICreateCA(out, errOut) + name := uuid.New().String() + rootCaArgs := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-file=%s", name), + fmt.Sprintf("--ca-name=%s", name), + fmt.Sprintf("--trust-domain=%s", "spiffe://"+trustDomain), + } + + ca.SetArgs(rootCaArgs) + pkiErr := ca.Execute() + if pkiErr != nil { + t.Fatal(pkiErr) + } + + bundle, e := testPki.GetCA(name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + + assert.Contains(t, urisAsStrings(bundle.Cert.URIs), "spiffe://"+trustDomain) +} + +func TestNoTrustDomain(t *testing.T) { + out, errOut := streams() + ca := NewCmdPKICreateCA(out, errOut) + name := uuid.New().String() + rootCaArgs := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-file=%s", name), + fmt.Sprintf("--ca-name=%s", name), + } + + ca.SetArgs(rootCaArgs) + pkiErr := ca.Execute() + if pkiErr != nil { + t.Fatal(pkiErr) + } + + bundle, e := testPki.GetCA(name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + + assert.Empty(t, bundle.Cert.URIs) +} + +func TestTrustDomainSpiffeAppended(t *testing.T) { + out, errOut := streams() + ca := NewCmdPKICreateCA(out, errOut) + name := uuid.New().String() + rootCaArgs := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-file=%s", name), + fmt.Sprintf("--ca-name=%s", name), + fmt.Sprintf("--trust-domain=%s", trustDomain), + } + + ca.SetArgs(rootCaArgs) + pkiErr := ca.Execute() + if pkiErr != nil { + t.Fatal(pkiErr) + } + + bundle, e := testPki.GetCA(name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + + assert.Contains(t, urisAsStrings(bundle.Cert.URIs), "spiffe://"+trustDomain) +} + +func TestTrustDomainWithPath(t *testing.T) { + out, errOut := streams() + ca := NewCmdPKICreateCA(out, errOut) + name := uuid.New().String() + rootCaArgs := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-file=%s", name), + fmt.Sprintf("--ca-name=%s", name), + fmt.Sprintf("--trust-domain=%s", "spiffe://"+trustDomain+"/path"), + } + + ca.SetArgs(rootCaArgs) + pkiErr := ca.Execute() + if pkiErr != nil { + t.Fatal(pkiErr) + } + + bundle, e := testPki.GetCA(name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + + assert.Contains(t, urisAsStrings(bundle.Cert.URIs), "spiffe://"+trustDomain) +} diff --git a/ziti/cmd/pki/pki_create_client.go b/ziti/cmd/pki/pki_create_client.go index 0f70dd67a..67d76a25a 100644 --- a/ziti/cmd/pki/pki_create_client.go +++ b/ziti/cmd/pki/pki_create_client.go @@ -29,6 +29,7 @@ import ( "github.com/spf13/cobra" "io" "net/url" + "strings" ) // PKICreateClientOptions the options for the create spring command @@ -78,7 +79,7 @@ func (o *PKICreateClientOptions) addPKICreateClientFlags(cmd *cobra.Command) { cmd.Flags().IntVarP(&o.Flags.CAMaxPath, "max-path-len", "", -1, "Intermediate maximum path length") 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().StringVar(&o.Flags.SpiffeID, "spiffe-id", "", "The SPIFFE id to use. If not a complete SPIFFE id, this is treated as the SPIFFE id path and the trust domain will be taken from the signing certificate.") cmd.Flags().BoolVar(&o.Flags.AllowOverwrite, "allow-overwrite", false, "Allow overwrite existing certs") } @@ -128,23 +129,34 @@ func (o *PKICreateClientOptions) Run() error { } if o.Flags.SpiffeID != "" { - var trustDomain *url.URL - for _, uri := range signer.Cert.URIs { - if uri.Scheme == "spiffe" { - if trustDomain != nil { - return errors.New("signing cert contained multiple spiffe ids, which is not allowed") + if !strings.HasPrefix(o.Flags.SpiffeID, "spiffe://") { + var trustDomain *url.URL + for _, uri := range signer.Cert.URIs { + if uri.Scheme == "spiffe" { + if trustDomain != nil { + return errors.New("signing cert contained multiple spiffe ids") + } + trustDomain = uri } - trustDomain = uri } - } - if trustDomain == nil { - return errors.New("signing cert doesn't have a spiffe id. unknown trust domain") + if trustDomain != nil { + spiffeId := *trustDomain + sid, serr := url.Parse(o.Flags.SpiffeID) + if serr != nil { + return serr + } + spiffeId.Path = sid.Path + template.URIs = append(template.URIs, &spiffeId) + } + } else { + // just use whatever spiffe id was provided + sid, serr := url.Parse(o.Flags.SpiffeID) + if serr != nil { + return serr + } + template.URIs = append(template.URIs, sid) } - - spiffId := *trustDomain - spiffId.Path = o.Flags.SpiffeID - template.URIs = append(template.URIs, &spiffId) } privateKeyOptions, err := o.ObtainPrivateKeyOptions() diff --git a/ziti/cmd/pki/pki_create_client_test.go b/ziti/cmd/pki/pki_create_client_test.go new file mode 100644 index 000000000..6e73f02da --- /dev/null +++ b/ziti/cmd/pki/pki_create_client_test.go @@ -0,0 +1,123 @@ +/* +Copyright NetFoundry Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package pki + +import ( + "fmt" + "github.com/google/uuid" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestClientCertNoSpiffeIdFromIntermediate(t *testing.T) { + out, errOut := streams() + svr := NewCmdPKICreateClient(out, errOut) + name := uuid.New().String() + args := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-name=%s", intCaNameWithoutSpiffeIdName), + fmt.Sprintf("--client-name=%s", name), + fmt.Sprintf("--client-file=%s", name), + } + + svr.SetArgs(args) + svrErr := svr.Execute() + if svrErr != nil { + t.Fatal(svrErr) + } + + bundle, e := testPki.GetBundle(intCaNameWithoutSpiffeIdName, name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + + assert.Nil(t, bundle.Cert.URIs) +} + +func TestClientCertSpiffeIdFromIntermediate(t *testing.T) { + out, errOut := streams() + svr := NewCmdPKICreateClient(out, errOut) + name := uuid.New().String() + args := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-name=%s", intCaNameWithSpiffeIdName), + fmt.Sprintf("--client-name=%s", name), + fmt.Sprintf("--client-file=%s", name), + } + + svr.SetArgs(addSpiffeArg("/some/path", args)) + svrErr := svr.Execute() + if svrErr != nil { + t.Fatal(svrErr) + } + + bundle, e := testPki.GetBundle(intCaNameWithSpiffeIdName, name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + urls := URLSlice(bundle.Cert.URIs) + assert.Contains(t, urls.Hosts(), rootCaWithSpiffeIdName) + assert.Contains(t, urls.Paths(), "/some/path") +} + +func TestClientCertNoSpiffeIdFromIntermediateAddSpiffeId(t *testing.T) { + out, errOut := streams() + svr := NewCmdPKICreateClient(out, errOut) + name := uuid.New().String() + args := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-name=%s", intCaNameWithoutSpiffeIdName), + fmt.Sprintf("--client-name=%s", name), + fmt.Sprintf("--client-file=%s", name), + } + + sid := "spiffe://not-from-ca/the-path" + svr.SetArgs(addSpiffeArg(sid, args)) + svrErr := svr.Execute() + if svrErr != nil { + t.Fatal(svrErr) + } + + bundle, e := testPki.GetBundle(intCaNameWithoutSpiffeIdName, name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + + assert.Contains(t, urisAsStrings(bundle.Cert.URIs), sid) +} + +func TestClientCertSpiffeIdFromIntermediateAddSpiffeId(t *testing.T) { + out, errOut := streams() + svr := NewCmdPKICreateClient(out, errOut) + name := uuid.New().String() + args := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-name=%s", intCaNameWithSpiffeIdName), + fmt.Sprintf("--client-name=%s", name), + fmt.Sprintf("--client-file=%s", name), + } + + sid := "spiffe://from-ca/the-path" + svr.SetArgs(addSpiffeArg(sid, args)) + svrErr := svr.Execute() + if svrErr != nil { + t.Fatal(svrErr) + } + + bundle, e := testPki.GetBundle(intCaNameWithSpiffeIdName, name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + + assert.Contains(t, urisAsStrings(bundle.Cert.URIs), sid) +} diff --git a/ziti/cmd/pki/pki_create_intermediate_test.go b/ziti/cmd/pki/pki_create_intermediate_test.go new file mode 100644 index 000000000..a20a106fd --- /dev/null +++ b/ziti/cmd/pki/pki_create_intermediate_test.go @@ -0,0 +1,49 @@ +/* +Copyright NetFoundry Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package pki + +import ( + "fmt" + "github.com/google/uuid" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSpiffedSetFromCa(t *testing.T) { + out, errOut := streams() + intermediateCmd := NewCmdPKICreateIntermediate(out, errOut) + name := uuid.New().String() + intermediateArgs := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-name=%s", rootCaWithSpiffeIdName), + fmt.Sprintf("--intermediate-name=%s", name), + fmt.Sprintf("--intermediate-file=%s", name), + "--max-path-len=1", + } + + intermediateCmd.SetArgs(intermediateArgs) + pkiErr := intermediateCmd.Execute() + if pkiErr != nil { + t.Fatal(pkiErr) + } + + bundle, e := testPki.GetCA(name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + + assert.Contains(t, urisAsStrings(bundle.Cert.URIs), "spiffe://"+rootCaWithSpiffeIdName) +} diff --git a/ziti/cmd/pki/pki_create_server.go b/ziti/cmd/pki/pki_create_server.go index 9530da676..d8ea38fff 100644 --- a/ziti/cmd/pki/pki_create_server.go +++ b/ziti/cmd/pki/pki_create_server.go @@ -29,6 +29,7 @@ import ( "github.com/spf13/cobra" "io" "net/url" + "strings" ) // PKICreateServerOptions the options for the create spring command @@ -79,7 +80,7 @@ func (o *PKICreateServerOptions) addPKICreateServerFlags(cmd *cobra.Command) { cmd.Flags().IntVarP(&o.Flags.CAMaxPath, "max-path-len", "", -1, "Intermediate maximum path length") 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().StringVar(&o.Flags.SpiffeID, "spiffe-id", "", "The SPIFFE id to use. If not a complete SPIFFE id, this is treated as the SPIFFE id path and the trust domain will be taken from the signing certificate.") cmd.Flags().BoolVar(&o.Flags.AllowOverwrite, "allow-overwrite", false, "Allow overwrite existing certs") } @@ -135,23 +136,34 @@ func (o *PKICreateServerOptions) Run() error { } if o.Flags.SpiffeID != "" { - var trustDomain *url.URL - for _, uri := range signer.Cert.URIs { - if uri.Scheme == "spiffe" { - if trustDomain != nil { - return errors.New("signing cert contained multiple spiffe ids, which is not allowed") + if !strings.HasPrefix(o.Flags.SpiffeID, "spiffe://") { + var trustDomain *url.URL + for _, uri := range signer.Cert.URIs { + if uri.Scheme == "spiffe" { + if trustDomain != nil { + return errors.New("signing cert contained multiple spiffe ids") + } + trustDomain = uri } - trustDomain = uri } - } - if trustDomain == nil { - return errors.New("signing cert doesn't have a spiffe id. unknown trust domain") + if trustDomain != nil { + spiffeId := *trustDomain + sid, serr := url.Parse(o.Flags.SpiffeID) + if serr != nil { + return serr + } + spiffeId.Path = sid.Path + template.URIs = append(template.URIs, &spiffeId) + } + } else { + // just use whatever spiffe id was provided + sid, serr := url.Parse(o.Flags.SpiffeID) + if serr != nil { + return serr + } + template.URIs = append(template.URIs, sid) } - - spiffId := *trustDomain - spiffId.Path = o.Flags.SpiffeID - template.URIs = append(template.URIs, &spiffId) } privateKeyOptions, err := o.ObtainPrivateKeyOptions() diff --git a/ziti/cmd/pki/pki_create_server_test.go b/ziti/cmd/pki/pki_create_server_test.go new file mode 100644 index 000000000..4793b534b --- /dev/null +++ b/ziti/cmd/pki/pki_create_server_test.go @@ -0,0 +1,142 @@ +/* +Copyright NetFoundry Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package pki + +import ( + "fmt" + "github.com/google/uuid" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestServerCertNoSpiffeIdFromIntermediate(t *testing.T) { + out, errOut := streams() + svr := NewCmdPKICreateServer(out, errOut) + name := uuid.New().String() + args := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-name=%s", intCaNameWithoutSpiffeIdName), + fmt.Sprintf("--server-name=%s", name), + fmt.Sprintf("--server-file=%s", name), + fmt.Sprintf("--dns=%s", "localhost,dns.entry"), + fmt.Sprintf("--ip=%s", "127.0.0.1,::1"), + } + + svr.SetArgs(args) + svrErr := svr.Execute() + if svrErr != nil { + t.Fatal(svrErr) + } + + bundle, e := testPki.GetBundle(intCaNameWithoutSpiffeIdName, name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + + assert.Contains(t, bundle.Cert.DNSNames, "dns.entry") + assert.Contains(t, bundle.Cert.DNSNames, "localhost") + ips := ipsAsStrings(bundle.Cert.IPAddresses) + assert.Contains(t, ips, "127.0.0.1") + assert.Contains(t, ips, "::1") + assert.Nil(t, bundle.Cert.URIs) +} + +func TestServerCertSpiffeIdFromIntermediate(t *testing.T) { + out, errOut := streams() + svr := NewCmdPKICreateServer(out, errOut) + name := uuid.New().String() + args := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-name=%s", intCaNameWithSpiffeIdName), + fmt.Sprintf("--server-name=%s", name), + fmt.Sprintf("--server-file=%s", name), + fmt.Sprintf("--dns=%s", "localhost,dns.entry"), + fmt.Sprintf("--ip=%s", "127.0.0.1,::1"), + } + + svr.SetArgs(addSpiffeArg("/some/path", args)) + svrErr := svr.Execute() + if svrErr != nil { + t.Fatal(svrErr) + } + + bundle, e := testPki.GetBundle(intCaNameWithSpiffeIdName, name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + urls := URLSlice(bundle.Cert.URIs) + + assert.Contains(t, bundle.Cert.DNSNames, "dns.entry") + assert.Contains(t, bundle.Cert.DNSNames, "localhost") + ips := ipsAsStrings(bundle.Cert.IPAddresses) + assert.Contains(t, ips, "127.0.0.1") + assert.Contains(t, ips, "::1") + assert.Contains(t, urls.Hosts(), rootCaWithSpiffeIdName) + assert.Contains(t, urls.Paths(), "/some/path") +} + +func TestServerCertNoSpiffeIdFromIntermediateAddSpiffeId(t *testing.T) { + out, errOut := streams() + svr := NewCmdPKICreateServer(out, errOut) + name := uuid.New().String() + args := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-name=%s", intCaNameWithoutSpiffeIdName), + fmt.Sprintf("--server-name=%s", name), + fmt.Sprintf("--server-file=%s", name), + fmt.Sprintf("--dns=%s", "localhost,dns.entry"), + fmt.Sprintf("--ip=%s", "127.0.0.1,::1"), + } + + sid := "spiffe://not-from-ca/the-path" + svr.SetArgs(addSpiffeArg(sid, args)) + svrErr := svr.Execute() + if svrErr != nil { + t.Fatal(svrErr) + } + + bundle, e := testPki.GetBundle(intCaNameWithoutSpiffeIdName, name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + + assert.Contains(t, urisAsStrings(bundle.Cert.URIs), sid) +} + +func TestServerCertSpiffeIdFromIntermediateAddSpiffeId(t *testing.T) { + out, errOut := streams() + svr := NewCmdPKICreateServer(out, errOut) + name := uuid.New().String() + args := []string{ + fmt.Sprintf("--pki-root=%s", where), + fmt.Sprintf("--ca-name=%s", intCaNameWithSpiffeIdName), + fmt.Sprintf("--server-name=%s", name), + fmt.Sprintf("--server-file=%s", name), + fmt.Sprintf("--dns=%s", "localhost,dns.entry"), + fmt.Sprintf("--ip=%s", "127.0.0.1,::1"), + } + + sid := "spiffe://from-ca/the-path" + svr.SetArgs(addSpiffeArg(sid, args)) + svrErr := svr.Execute() + if svrErr != nil { + t.Fatal(svrErr) + } + + bundle, e := testPki.GetBundle(intCaNameWithSpiffeIdName, name) + assert.NotNil(t, bundle) + assert.Nil(t, e) + + assert.Contains(t, urisAsStrings(bundle.Cert.URIs), sid) +} diff --git a/ziti/cmd/verify/ops_verify_traffic.go b/ziti/cmd/verify/ops_verify_traffic.go index f1602cee2..b22fd4f4d 100644 --- a/ziti/cmd/verify/ops_verify_traffic.go +++ b/ziti/cmd/verify/ops_verify_traffic.go @@ -1,17 +1,17 @@ /* - Copyright NetFoundry Inc. +Copyright NetFoundry Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - https://www.apache.org/licenses/LICENSE-2.0 +https://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ package verify @@ -41,7 +41,6 @@ import ( "github.com/openziti/ziti/internal/rest/mgmt" ) - type traffic struct { loginOpts edge.LoginOptions prefix string @@ -56,6 +55,7 @@ type traffic struct { clientIdName string bindSPName string dialSPName string + haEnabled bool } func NewVerifyTraffic(out io.Writer, errOut io.Writer) *cobra.Command { @@ -72,7 +72,7 @@ func NewVerifyTraffic(out io.Writer, errOut io.Writer) *cobra.Command { pfxlog.GlobalInit(logLvl, pfxlog.DefaultOptions().Color()) configureLogFormat(logLvl) - + timePrefix := time.Now().Format("2006-01-02-1504") if t.prefix == "" { if t.mode != "both" { @@ -131,7 +131,9 @@ func NewVerifyTraffic(out io.Writer, errOut io.Writer) *cobra.Command { cmd.Flags().StringVarP(&t.mode, "mode", "m", "", "[optional, default 'both'] The mode to perform: server, client, both.") cmd.Flags().BoolVar(&t.cleanup, "cleanup", false, "Whether to perform cleanup.") cmd.Flags().BoolVar(&t.allowMultipleServers, "allow-multiple-servers", false, "Whether to allows the same server multiple times.") - + cmd.Flags().BoolVar(&t.haEnabled, "ha", false, "Enable high availability mode.") + _ = cmd.Flags().MarkHidden("ha") + edge.AddLoginFlags(cmd, &t.loginOpts) t.loginOpts.Out = out t.loginOpts.Err = errOut @@ -139,11 +141,13 @@ func NewVerifyTraffic(out io.Writer, errOut io.Writer) *cobra.Command { return cmd } -func startServer(ctx context.Context, serviceName string, zitiCfg *ziti.Config) error { +func (t *traffic) startServer(ctx context.Context, serviceName string, zitiCfg *ziti.Config) error { + zitiCfg.EnableHa = t.haEnabled c, err := ziti.NewContext(zitiCfg) if err != nil { log.Fatal(err) } + listener, err := c.Listen(serviceName) if err != nil { log.Fatal(err) @@ -200,8 +204,9 @@ func handleConnection(conn net.Conn) { log.Debugf("responding with : %s", strings.TrimSpace(resp)) } -func startClient(client *rest_management_api_client.ZitiEdgeManagement, serviceName string, zitiCfg *ziti.Config) error { +func (t *traffic) startClient(client *rest_management_api_client.ZitiEdgeManagement, serviceName string, zitiCfg *ziti.Config) error { waitForTerminator(client, serviceName, 10*time.Second) + zitiCfg.EnableHa = t.haEnabled c, err := ziti.NewContext(zitiCfg) if err != nil { log.Fatal(err) @@ -279,8 +284,8 @@ func createIdentity(client *rest_management_api_client.ZitiEdgeManagement, name Enrollment: &rest_model.IdentityCreateEnrollment{ Ott: true, }, - IsAdmin: &falseVar, - Name: &name, + IsAdmin: &falseVar, + Name: &name, RoleAttributes: &roleAttributes, Type: &usrType, } @@ -395,7 +400,7 @@ func enrollIdentity(client *rest_management_api_client.ZitiEdgeManagement, id st // Get the identity object params := &identity.DetailIdentityParams{ Context: context.Background(), - ID: id, + ID: id, } params.SetTimeout(5 * time.Second) resp, err := client.Identity.DetailIdentity(params, nil) @@ -513,7 +518,7 @@ func (t *traffic) doServer(ctx context.Context, configureServices bool) { } serverCfg := t.configureServer() defer t.cleanupServer() - if err := startServer(ctx, t.svcName, serverCfg); err != nil { + if err := t.startServer(ctx, t.svcName, serverCfg); err != nil { log.Fatalf("unexpected error: %v", err) } } @@ -521,7 +526,7 @@ func (t *traffic) doServer(ctx context.Context, configureServices bool) { func (t *traffic) doClient(cancel context.CancelFunc) { clientCfg := t.configureClient() defer t.cleanupClient() - if err := startClient(t.client, t.svcName, clientCfg); err != nil { + if err := t.startClient(t.client, t.svcName, clientCfg); err != nil { log.Fatal(err) } @@ -529,4 +534,4 @@ func (t *traffic) doClient(cancel context.CancelFunc) { cancel() //end the server time.Sleep(1 * time.Second) log.Info("client complete") -} \ No newline at end of file +}