mirror of
https://github.com/openziti/ziti.git
synced 2026-09-10 00:35:41 +00:00
add support for and identity-driven bindPoints in controller (#3315)
* add support for and identity-driven bindPoints in controller * cannot use ListenOptions as it pulls the go sdk into xweb :( * more generic log message * add ziti cli login tests in prep for continuing adding identity support in controller * updates to tests * updates to tests * rebase with main * allow login testing to external overlay * more changes to allow a zitified ziti cli. add a test for testing login and ensure it works over a zitified connection * refactor bindPoints to a module * rebase with main * no functional changes, just major refactoring based on PR requests. encapsulated all tests state into loginTestState, moved overlay to testutil * rework a couple of util funcs to be cleaner per PR feedback * make the new func more useful * run tests via github action * update changelog and remove unnecssary serveTls for now * update from xweb v2 to v3 * change where factory is added and fix compilation issue of a test * linting changes, move ascode test to cli_tests and activate via cli_tests * use proper go build * forgot to set the bin location * fix timeout on test * different errors on linux, windows and on gh runners * cleanup after self-pr review * use longer name to prevent codespell issues... * additional changelog and add addressable terminator support * fix out of control concatenation in cache file. fix ipv6 checking * updates based on newer sdk and edge api client * ensure oidc sessions auth for both older and newer commands * add better error when url is empty and update changelog * codespell fixes * remove extraneous file * update to 1.3.0 to kick off CI * PR related changes. add interface enforcer and refactor networkIdentity * go tidied * fix golangci-lint and ha quickstart test * keep fixing golanglint-ci... lol * golanglint i was sure i'd fixed * fix login test * should fix ziti ops verify traffic as well * fix verify traffic when all login information is supplied as well * make all the timeouts longer? seems to run fine locally but fail in actions
This commit is contained in:
@@ -169,6 +169,23 @@ jobs:
|
||||
run: |
|
||||
go test ./... --tags apitests
|
||||
|
||||
- name: Run CLI tests and Integration Tests
|
||||
if: ${{ vars.ZITI_SKIP_CLI_TESTS != 'true' }}
|
||||
timeout-minutes: 10
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir cli_tests_bin
|
||||
go build -o cli_tests_bin ./...
|
||||
ZITI_CLI_TEST_ZITI_BIN="$PWD/cli_tests_bin/ziti" go test ./... --tags cli_tests
|
||||
|
||||
- name: Upload CLI test logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cli-test-logs-${{ github.run_id }}
|
||||
path: '**/ctrl-std*log'
|
||||
retention-days: 5
|
||||
|
||||
fablab-smoketest:
|
||||
name: Fablab Smoketest
|
||||
# not applicable to forks. shouldn't run on release build
|
||||
|
||||
+172
-1
@@ -1,3 +1,174 @@
|
||||
# Release 1.8.0
|
||||
|
||||
## What's New
|
||||
|
||||
* controllers can now optionally bind APIs using a OpenZiti identity
|
||||
* `ziti edge login` now supports the `--network-identity` flag to authenticate and establish connections through the Ziti overlay network
|
||||
* `ziti edge login` now supports using a bearer token with `--token` for authentication. The token is expected to be
|
||||
provided as just the JWT, not with the "Bearer " prefix
|
||||
* identity configuration can now be loaded from files or environment variables for flexible deployment scenarios
|
||||
|
||||
## Binding Controller APIs With Identity
|
||||
|
||||
Controller APIs can now be bound to an OpenZiti overlay network identity, allowing secure communication through
|
||||
the Ziti network. This is useful for scenarios where you want to expose controller APIs only through the overlay
|
||||
network rather than on a standard network interface.
|
||||
|
||||
### Configuration Structure
|
||||
|
||||
A standard `bindPoint` configuration looks like this:
|
||||
```text
|
||||
bindPoints:
|
||||
- interface: 127.0.0.1:18441
|
||||
address: 127.0.0.1:18441
|
||||
```
|
||||
|
||||
To bind controller APIs to an OpenZiti identity, add an additional `identity` block to your `bindPoints`. The
|
||||
identity configuration specifies where to load the Ziti identity file and which service to bind it to:
|
||||
|
||||
```text
|
||||
bindPoints:
|
||||
- interface: 127.0.0.1:18441
|
||||
address: 127.0.0.1:18441
|
||||
- identity:
|
||||
file: "c:/temp/ctrl.testing/ctrl.identity.json"
|
||||
service: "mgmt"
|
||||
```
|
||||
|
||||
### Supported Configuration Options
|
||||
|
||||
- `file`: Path to a Ziti identity JSON file containing the controller's identity and enrollment certificate
|
||||
- `env`: Name of an environment variable containing a base64-encoded Ziti identity (alternative to `file`)
|
||||
- `service`: The name of the Ziti service to bind the controller API to
|
||||
|
||||
### Using Environment Variables
|
||||
|
||||
For deployments where storing identity files on disk is not preferred, you can reference a base64-encoded
|
||||
identity file from an environment variable. The environment variable should contain the base64-encoded contents
|
||||
of the identity JSON file.
|
||||
|
||||
For example, if an environment variable named `ZITI_CTRL_IDENTITY` contains a base64-encoded identity file:
|
||||
|
||||
```text
|
||||
bindPoints:
|
||||
- interface: 127.0.0.1:18441
|
||||
address: 127.0.0.1:18441
|
||||
- identity:
|
||||
env: ZITI_CTRL_IDENTITY
|
||||
service: "mgmt"
|
||||
```
|
||||
|
||||
### IPv6 Support
|
||||
|
||||
Both IPv4 and IPv6 addresses are supported for standard bind points. IPv6 addresses should be specified in bracket
|
||||
notation with a port number:
|
||||
|
||||
```text
|
||||
bindPoints:
|
||||
- interface: "[::1]:18441"
|
||||
address: "[::1]:18441"
|
||||
- identity:
|
||||
file: "/path/to/identity.json"
|
||||
service: "mgmt"
|
||||
```
|
||||
|
||||
## CLI Enhancements for Identity-Based Connections
|
||||
|
||||
The `ziti edge login` command and REST client utilities have been enhanced to support identity-based connections
|
||||
through the Ziti overlay network.
|
||||
|
||||
### New `--network-identity` Flag for `ziti edge login`
|
||||
|
||||
The `ziti edge login` command now includes a `--network-identity` flag that allows you to authenticate to a Ziti
|
||||
controller through the overlay network using a Ziti identity:
|
||||
|
||||
```bash
|
||||
ziti edge login https://ziti.mgmt.apis.local:1280 \
|
||||
--username myuser \
|
||||
--password mypass \
|
||||
--network-identity /path/to/identity.json
|
||||
```
|
||||
|
||||
This is useful when the controller is only accessible through the Ziti overlay network or when you want to ensure
|
||||
all communication to the controller flows through the overlay for security purposes.
|
||||
|
||||
### Identity Resolution Order
|
||||
|
||||
When establishing connections, identities are resolved in the following order:
|
||||
|
||||
1. **Command-line flag**: The `--network-identity` flag takes precedence
|
||||
2. **Environment variable**: If `ZITI_CLI_NETWORK_ID` is set and contains a base64-encoded identity, it is used
|
||||
3. **Cached identity file**: If a network identity was saved from a previous login in the Ziti config directory, it may be used
|
||||
|
||||
This layered approach allows for flexibility in deployment scenarios:
|
||||
- Development: Use command-line flags for quick testing
|
||||
- Automation: Use environment variables in CI/CD pipelines
|
||||
- Production: Cache identities securely for repeated access
|
||||
|
||||
#### Dialing Modes When Authenticating
|
||||
|
||||
The CLI supports two dialing modes:
|
||||
|
||||
**Intercept-based Dialing (Default)**
|
||||
By default, URLs are expected to leverage intercepts. Create a service with an appropriate intercept config and use
|
||||
the intercept address when dialing. This is the standard mode for most use cases. For example, given a service with
|
||||
the intercept `ziti.mgmt.apis.local`
|
||||
```bash
|
||||
ziti edge login https://ziti.mgmt.apis.local:1280 \
|
||||
--username myuser \
|
||||
--password mypass \
|
||||
--network-identity /path/to/identity.json
|
||||
```
|
||||
|
||||
**Identity-aware Dialing (Addressable Terminators)**
|
||||
To support addressable terminators-based dialing, specify a user in the URL. This activates dial-by-identity
|
||||
functionality. The URL format should be `identity-to-dial@service-name-to-dial`. For example:
|
||||
```bash
|
||||
ziti edge login https://my-identity@my-service:1280 \
|
||||
--username myuser \
|
||||
--password mypass \
|
||||
--network-identity /path/to/identity.json
|
||||
```
|
||||
|
||||
In this mode, the transport extracts the identity from the URL and uses it to establish a direct connection to
|
||||
the specified service via the addressable terminator.
|
||||
|
||||
## What's New
|
||||
|
||||
* controllers can now optionally bind APIs using a OpenZiti identity
|
||||
|
||||
## Binding Controller APIs With Identity
|
||||
|
||||
It's now possible to bind controller APIs to an OpenZiti overlay network identity. To bind a given controller
|
||||
API to an OpenZiti identity, add a section to the desired `bindPoint` section. For example a common `bindPoint`
|
||||
configuration might look like:
|
||||
```text
|
||||
bindPoints:
|
||||
- interface: 127.0.0.1:18441
|
||||
address: 127.0.0.1:18441
|
||||
```
|
||||
To bind any declared APIs to a given OpenZiti identity add an `identity` block:
|
||||
```text
|
||||
bindPoints:
|
||||
- interface: 127.0.0.1:18441
|
||||
address: 127.0.0.1:18441
|
||||
- identity:
|
||||
file: "c:/temp/ctrl.testing/clint.ctrl.json"
|
||||
service: "mgmt"
|
||||
```
|
||||
It's possible to refer to an environment variable for the identity file if desired. Add an environment variable with
|
||||
the contents of the environment variable the identity file base64 encoded. For example if an environment is defined
|
||||
with the name `ZITI_ID_EXAMPLE` and contains a base64 encoded identity file, the following `bindPoint` block can be used:
|
||||
```text
|
||||
bindPoints:
|
||||
- interface: 127.0.0.1:18441
|
||||
address: 127.0.0.1:18441
|
||||
- identity:
|
||||
env: ZITI_ID_EXAMPLE
|
||||
service: "mgmt"
|
||||
```
|
||||
|
||||
|
||||
# Release 1.7.2
|
||||
|
||||
## What's New
|
||||
@@ -138,7 +309,7 @@ Added support for dynamic service proxies with configurable binding and protocol
|
||||
This allows Edge Routers and Tunnelers to create proxy endpoints that can forward traffic for Ziti services.
|
||||
|
||||
This differs from intercept.v1 in that intercept.v1 will intercept traffic on specified
|
||||
IP ip addresses or DNS entries to forward to a service using tproxy or tun interface,
|
||||
IP addresses or DNS entries to forward to a service using tproxy or tun interface,
|
||||
depending on implementation.
|
||||
|
||||
A proxy on the other hand will just start a regular TCP/UDP listener on the configured port,
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
gosundheit "github.com/AppsFlyer/go-sundheit"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
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 bindpoints
|
||||
|
||||
import (
|
||||
"github.com/openziti/xweb/v3"
|
||||
)
|
||||
|
||||
// BindPointListenerFactory implements the xweb.BindPointListenerFactory.
|
||||
// It provides a factory that generates xweb.BindPoints based on the provided config section
|
||||
type BindPointListenerFactory struct {
|
||||
}
|
||||
|
||||
// New checks to see if this bindPoint is for overlay or underlay then calls the expected func.
|
||||
// As of now there are only two types, OverlayBindPoint and UnderlayBindPoint
|
||||
func (c *BindPointListenerFactory) New(conf map[interface{}]interface{}) (xweb.BindPoint, error) {
|
||||
if conf["identity"] != nil {
|
||||
return newOverlayBindPoint(conf)
|
||||
} else { // only two options right now. underlay and overlay...
|
||||
return newUnderlayBindPoint(conf)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
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 bindpoints
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/identity"
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
"github.com/openziti/xweb/v3"
|
||||
)
|
||||
|
||||
var _ xweb.BindPoint = (*OverlayBindPoint)(nil)
|
||||
|
||||
// OverlayBindPoint represents the BindPointConfig when an identity is supplied as opposed to an address
|
||||
type OverlayBindPoint struct {
|
||||
Identity []byte //an openziti identity
|
||||
Service string //name of the service to bind
|
||||
ClientAuthType tls.ClientAuthType
|
||||
Name string
|
||||
Opts ziti.ListenOptions
|
||||
cfg ziti.Config
|
||||
ctx ziti.Context
|
||||
}
|
||||
|
||||
func (o OverlayBindPoint) BeforeHandler(next http.Handler) http.Handler {
|
||||
return next
|
||||
}
|
||||
func (o OverlayBindPoint) AfterHandler(prev http.Handler) http.Handler {
|
||||
return prev
|
||||
}
|
||||
func (o OverlayBindPoint) ServerAddress() string {
|
||||
return o.Name
|
||||
}
|
||||
func newOverlayBindPoint(conf map[interface{}]interface{}) (OverlayBindPoint, error) {
|
||||
o := OverlayBindPoint{}
|
||||
|
||||
identVal, ok := conf["identity"]
|
||||
if !ok {
|
||||
return o, errors.New("missing identity section")
|
||||
}
|
||||
|
||||
identCfg, ok := identVal.(map[interface{}]interface{})
|
||||
if !ok {
|
||||
return o, errors.New("identity config must be a map")
|
||||
}
|
||||
|
||||
if fileVal, ok := identCfg["file"].(string); ok {
|
||||
data, err := os.ReadFile(fileVal)
|
||||
if err != nil {
|
||||
return o, err
|
||||
}
|
||||
o.Identity = data
|
||||
}
|
||||
|
||||
if envName, ok := identCfg["env"].(string); ok {
|
||||
b64Id := os.Getenv(envName)
|
||||
idReader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(b64Id))
|
||||
data, err := io.ReadAll(idReader)
|
||||
if err != nil {
|
||||
return o, err
|
||||
}
|
||||
o.Identity = data
|
||||
}
|
||||
|
||||
if len(o.Identity) < 1 {
|
||||
return o, errors.New("no identity configured: file or env required")
|
||||
}
|
||||
|
||||
if svc, ok := identCfg["service"].(string); ok {
|
||||
o.Service = svc
|
||||
} else {
|
||||
return o, errors.New("service must be supplied when using an identity binding")
|
||||
}
|
||||
|
||||
if certRequired, ok := identCfg["tlsClientAuthenticationPolicy"].(string); ok {
|
||||
switch strings.ToLower(certRequired) {
|
||||
case "noclientcert":
|
||||
o.ClientAuthType = tls.NoClientCert
|
||||
case "requestclientcert":
|
||||
o.ClientAuthType = tls.RequestClientCert
|
||||
case "requireanyclientcert":
|
||||
o.ClientAuthType = tls.RequireAnyClientCert
|
||||
case "verifyclientcertifgiven":
|
||||
o.ClientAuthType = tls.VerifyClientCertIfGiven
|
||||
case "requireandverifyclientcert":
|
||||
o.ClientAuthType = tls.RequireAndVerifyClientCert
|
||||
default:
|
||||
o.ClientAuthType = tls.VerifyClientCertIfGiven
|
||||
}
|
||||
}
|
||||
if listenOptsCfg, ok := identCfg["listenOptions"]; ok {
|
||||
optsCfg := listenOptsCfg.(map[interface{}]interface{})
|
||||
if asId, ok := optsCfg["bindUsingEdgeIdentity"].(bool); ok {
|
||||
o.Opts.BindUsingEdgeIdentity = asId
|
||||
}
|
||||
}
|
||||
|
||||
o.cfg = ziti.Config{}
|
||||
unMarshallErr := json.Unmarshal(o.Identity, &o.cfg)
|
||||
if unMarshallErr != nil {
|
||||
return o, unMarshallErr
|
||||
}
|
||||
|
||||
if ctx, newCtxErr := ziti.NewContext(&o.cfg); newCtxErr != nil {
|
||||
return o, newCtxErr
|
||||
} else {
|
||||
o.ctx = ctx
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func (o OverlayBindPoint) Listener(_ string, tlsConfig *tls.Config) (net.Listener, error) {
|
||||
var ln net.Listener
|
||||
|
||||
ln, err := o.ctx.ListenWithOptions(o.Service, &o.Opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listening on overlay: %s", err)
|
||||
}
|
||||
|
||||
ln = tls.NewListener(ln, tlsConfig)
|
||||
if tlsConfig.ClientAuth < tls.RequestClientCert {
|
||||
pfxlog.Logger().WithError(err).Warnf("The configured certificate verification method [%d] will not support mutual TLS", tlsConfig.ClientAuth)
|
||||
}
|
||||
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func (o OverlayBindPoint) Validate(_ identity.Identity) error {
|
||||
return nil // much of the validation happens before this func is invoked in newOverlayBindPoint
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
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 bindpoints
|
||||
|
||||
import (
|
||||
gotls "crypto/tls"
|
||||
goerrs "errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/openziti/identity"
|
||||
transporttls "github.com/openziti/transport/v2/tls"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
ZitiCtrlAddressHeader = "ziti-ctrl-address"
|
||||
)
|
||||
|
||||
// UnderlayBindPoint represents the interface:port address of where a http.Server should listen for a ServerConfig and the public
|
||||
// address that should be used to address it.
|
||||
type UnderlayBindPoint struct {
|
||||
InterfaceAddress string //<interface>:<port>
|
||||
Address string //<ip/host>:<port>
|
||||
NewAddress string //<ip/host>:<port> sent out as a header for clients to alternatively swap to (ip -> hostname moves)
|
||||
}
|
||||
|
||||
func (u UnderlayBindPoint) BeforeHandler(next http.Handler) http.Handler {
|
||||
return u.wrapSetCtrlAddressHeader(next)
|
||||
}
|
||||
func (u UnderlayBindPoint) AfterHandler(prev http.Handler) http.Handler {
|
||||
return prev
|
||||
}
|
||||
func (u UnderlayBindPoint) Listener(serverName string, tlsConfig *gotls.Config) (net.Listener, error) {
|
||||
ln, err := transporttls.ListenTLS(u.InterfaceAddress, serverName, tlsConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listening: %s", err)
|
||||
}
|
||||
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func (u UnderlayBindPoint) ServerAddress() string {
|
||||
return u.Address
|
||||
}
|
||||
|
||||
func newUnderlayBindPoint(conf map[interface{}]interface{}) (xweb.BindPoint, error) {
|
||||
u := UnderlayBindPoint{}
|
||||
if v, ok := conf["interface"].(string); ok {
|
||||
u.InterfaceAddress = v
|
||||
}
|
||||
if v, ok := conf["address"].(string); ok {
|
||||
u.Address = v
|
||||
}
|
||||
if v, ok := conf["newAddress"].(string); ok {
|
||||
u.NewAddress = v
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// Validate this configuration object.
|
||||
func (u UnderlayBindPoint) Validate(id identity.Identity) error {
|
||||
var errs []error
|
||||
|
||||
// required
|
||||
if err := validateHostPort(u.InterfaceAddress); err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid interface address [%s]: %v", u.InterfaceAddress, err))
|
||||
}
|
||||
|
||||
// required
|
||||
if err := validateHostPort(u.Address); err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid advertise address [%s]: %v", u.Address, err))
|
||||
}
|
||||
|
||||
//optional
|
||||
if u.NewAddress != "" {
|
||||
if err := validateHostPort(u.NewAddress); err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid new address [%s]: %v", u.NewAddress, err))
|
||||
}
|
||||
}
|
||||
|
||||
if h, _, err := net.SplitHostPort(u.Address); err == nil {
|
||||
if ve := id.ValidFor(normalizeIp(h)); ve != nil {
|
||||
errs = append(errs, fmt.Errorf("address not valid %s: %v", u.Address, ve))
|
||||
}
|
||||
}
|
||||
|
||||
return goerrs.Join(errs...)
|
||||
}
|
||||
|
||||
func normalizeIp(s string) string {
|
||||
s = strings.Trim(s, "[]")
|
||||
var ip net.IP
|
||||
if i := strings.IndexByte(s, '%'); i != -1 {
|
||||
ip = net.ParseIP(s[:i])
|
||||
} else {
|
||||
ip = net.ParseIP(s)
|
||||
}
|
||||
if ip == nil {
|
||||
return s
|
||||
} else {
|
||||
return ip.String()
|
||||
}
|
||||
}
|
||||
|
||||
// wrapSetCtrlAddressHeader will check to see if the bindPoint is configured to advertise a "new address". If so
|
||||
// the value is added to the ZitiCtrlAddressHeader which will be sent out on every response. Clients can check this
|
||||
// header to be notified that the controller is or will be moving from one ip/hostname to another. When the
|
||||
// new address value is set, both the old and new addresses should be valid as the clients will begin using the
|
||||
// new address on their next connect.
|
||||
func (u UnderlayBindPoint) wrapSetCtrlAddressHeader(handler http.Handler) http.Handler {
|
||||
wrappedHandler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
if u.NewAddress != "" {
|
||||
address := "https://" + u.NewAddress
|
||||
writer.Header().Set(ZitiCtrlAddressHeader, address)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(writer, request)
|
||||
})
|
||||
|
||||
return wrappedHandler
|
||||
}
|
||||
|
||||
func validateHostPort(address string) error {
|
||||
address = strings.TrimSpace(address)
|
||||
|
||||
if address == "" {
|
||||
return errors.New("must not be an empty string or unspecified")
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
|
||||
if err != nil {
|
||||
return errors.Errorf("could not split host and port: %v", err)
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
return errors.New("host must be specified")
|
||||
}
|
||||
|
||||
if port == "" {
|
||||
return errors.New("port must be specified")
|
||||
}
|
||||
|
||||
if port, err := strconv.ParseInt(port, 10, 32); err != nil {
|
||||
return errors.New("invalid port, must be a integer")
|
||||
} else if port < 1 || port > 65535 {
|
||||
return errors.New("invalid port, must 1-65535")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -22,11 +22,9 @@ import (
|
||||
cryptoTls "crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
stderr "errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
@@ -41,12 +39,13 @@ import (
|
||||
"github.com/openziti/storage/boltz"
|
||||
"github.com/openziti/transport/v2"
|
||||
"github.com/openziti/transport/v2/tls"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/openziti/ziti/common/capabilities"
|
||||
"github.com/openziti/ziti/common/concurrency"
|
||||
fabricMetrics "github.com/openziti/ziti/common/metrics"
|
||||
"github.com/openziti/ziti/common/pb/ctrl_pb"
|
||||
"github.com/openziti/ziti/common/profiler"
|
||||
"github.com/openziti/ziti/controller/bindpoints"
|
||||
"github.com/openziti/ziti/controller/command"
|
||||
"github.com/openziti/ziti/controller/config"
|
||||
"github.com/openziti/ziti/controller/db"
|
||||
@@ -102,6 +101,10 @@ type Controller struct {
|
||||
healthChecker gosundheit.Health
|
||||
}
|
||||
|
||||
func init() {
|
||||
xweb.BindPointListenerFactoryRegistry = append(xweb.BindPointListenerFactoryRegistry, &bindpoints.BindPointListenerFactory{})
|
||||
}
|
||||
|
||||
func (c *Controller) GetPeerSigners() []*x509.Certificate {
|
||||
if c.raftController == nil || c.raftController.Mesh == nil {
|
||||
return nil
|
||||
@@ -235,17 +238,6 @@ func NewController(cfg *config.Config, versionProvider versions.VersionProvider)
|
||||
xwebInitialized: concurrency.NewInitState(),
|
||||
}
|
||||
xwebInstanceOptions := xweb.InstanceOptions{
|
||||
InstanceValidators: []xweb.InstanceValidator{func(config *xweb.InstanceConfig) error {
|
||||
var errs []error
|
||||
for i, serverConfig := range config.ServerConfigs {
|
||||
for _, bp := range serverConfig.BindPoints {
|
||||
if ve := serverConfig.Identity.ValidFor(strings.Split(bp.Address, ":")[0]); ve != nil {
|
||||
errs = append(errs, fmt.Errorf("could not validate server at %s[%d]: %v", config.Options.DefaultConfigSection, i, ve))
|
||||
}
|
||||
}
|
||||
}
|
||||
return stderr.Join(errs...)
|
||||
}},
|
||||
DefaultIdentity: c.config.Id,
|
||||
DefaultIdentitySection: xweb.DefaultIdentitySection,
|
||||
DefaultConfigSection: xweb.DefaultConfigSection,
|
||||
@@ -844,8 +836,8 @@ func (c *Controller) GetApiAddresses() (map[string][]event.ApiAddress, []byte) {
|
||||
for _, bindPoint := range serverConfig.BindPoints {
|
||||
for _, api := range serverConfig.APIs {
|
||||
apiData[api.Binding()] = append(apiData[api.Binding()], event.ApiAddress{
|
||||
Url: "https://" + bindPoint.Address + getApiPath(api.Binding()), //TODO: temp till xweb support reporting API paths
|
||||
Version: "v1", //TODO: temp till xweb supports reporting versions via api.Version()
|
||||
Url: "https://" + bindPoint.ServerAddress() + getApiPath(api.Binding()), //TODO: temp till xweb support reporting API paths
|
||||
Version: "v1", //TODO: temp till xweb supports reporting versions via api.Version()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -49,7 +49,7 @@ import (
|
||||
"github.com/openziti/metrics"
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
"github.com/openziti/storage/boltz"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/openziti/ziti/common"
|
||||
"github.com/openziti/ziti/common/cert"
|
||||
"github.com/openziti/ziti/common/eid"
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
clientInformational "github.com/openziti/edge-api/rest_client_api_server/operations/informational"
|
||||
managementInformational "github.com/openziti/edge-api/rest_management_api_server/operations/informational"
|
||||
"github.com/openziti/edge-api/rest_model"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/openziti/ziti/common/build"
|
||||
"github.com/openziti/ziti/controller/env"
|
||||
"github.com/openziti/ziti/controller/internal/permissions"
|
||||
@@ -115,7 +115,7 @@ func (ir *VersionRouter) List(ae *env.AppEnv, rc *response.RequestContext) {
|
||||
}
|
||||
|
||||
for _, bindPoint := range webListener.BindPoints {
|
||||
apiBaseUrl := bindPoint.Address + apiBindingToPath(api.Binding())
|
||||
apiBaseUrl := bindPoint.ServerAddress() + apiBindingToPath(api.Binding())
|
||||
apiToBaseUrls[api.Binding()][apiBaseUrl] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"github.com/openziti/edge-api/rest_client_api_client"
|
||||
"github.com/openziti/edge-api/rest_client_api_server"
|
||||
"github.com/openziti/edge-api/rest_management_api_server"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/openziti/ziti/controller/api"
|
||||
"github.com/openziti/ziti/controller/apierror"
|
||||
"github.com/openziti/ziti/controller/env"
|
||||
@@ -61,7 +61,7 @@ func (factory ClientApiFactory) Validate(config *xweb.InstanceConfig) error {
|
||||
|
||||
if !clientApiFound && api.Binding() == ClientApiBinding {
|
||||
for _, bindPoint := range webListener.BindPoints {
|
||||
if bindPoint.Address == edgeConfig.Api.Address {
|
||||
if bindPoint.ServerAddress() == edgeConfig.Api.Address {
|
||||
factory.appEnv.SetClientApiDefaultCertificate(webListener.Identity.ServerCert()[0])
|
||||
clientApiFound = true
|
||||
break
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
gosundheit "github.com/AppsFlyer/go-sundheit"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/openziti/ziti/controller/env"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"github.com/openziti/channel/v4/websockets"
|
||||
"github.com/openziti/foundation/v2/concurrenz"
|
||||
"github.com/openziti/identity"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/openziti/ziti/controller/api_impl"
|
||||
"github.com/openziti/ziti/controller/env"
|
||||
"github.com/openziti/ziti/controller/handler_mgmt"
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
"github.com/openziti/edge-api/rest_management_api_server"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/openziti/ziti/controller/api"
|
||||
"github.com/openziti/ziti/controller/apierror"
|
||||
"github.com/openziti/ziti/controller/env"
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/identity"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/openziti/ziti/controller/api_impl"
|
||||
"github.com/openziti/ziti/controller/network"
|
||||
)
|
||||
|
||||
@@ -21,12 +21,13 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/openziti/identity"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/identity"
|
||||
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/openziti/ziti/controller/api"
|
||||
"github.com/openziti/ziti/controller/env"
|
||||
"github.com/openziti/ziti/controller/oidc_auth"
|
||||
@@ -188,7 +189,7 @@ func NewOidcApiHandler(serverConfig *xweb.ServerConfig, ae *env.AppEnv, options
|
||||
// getPossibleIssuers inspects the API server's identity and bind points for addresses, SAN DNS, and SAN IP entries
|
||||
// that denote valid issuers. It returns a list of hostname:port combinations as a slice. It handles converting
|
||||
// :443 to explicit and implicit ports for clients that may silently remove :443
|
||||
func getPossibleIssuers(id identity.Identity, bindPoints []*xweb.BindPointConfig) []oidc_auth.Issuer {
|
||||
func getPossibleIssuers(id identity.Identity, bindPoints []xweb.BindPoint) []oidc_auth.Issuer {
|
||||
const (
|
||||
DefaultTlsPort = "443"
|
||||
)
|
||||
@@ -200,7 +201,7 @@ func getPossibleIssuers(id identity.Identity, bindPoints []*xweb.BindPointConfig
|
||||
portMap := map[string]struct{}{}
|
||||
|
||||
for _, bindPoint := range bindPoints {
|
||||
host, port, err := net.SplitHostPort(bindPoint.Address)
|
||||
host, port, err := net.SplitHostPort(bindPoint.ServerAddress())
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
@@ -211,7 +212,7 @@ func getPossibleIssuers(id identity.Identity, bindPoints []*xweb.BindPointConfig
|
||||
issuerMap[host] = struct{}{}
|
||||
}
|
||||
|
||||
issuerMap[bindPoint.Address] = struct{}{}
|
||||
issuerMap[bindPoint.ServerAddress()] = struct{}{}
|
||||
}
|
||||
|
||||
var ports []string
|
||||
|
||||
@@ -24,13 +24,15 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/openziti/identity"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"math/big"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/openziti/identity"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/openziti/ziti/controller/bindpoints"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_getPossibleIssuers(t *testing.T) {
|
||||
@@ -132,11 +134,11 @@ func Test_getPossibleIssuers(t *testing.T) {
|
||||
bindPoint2Address = "test2.example.com:443"
|
||||
)
|
||||
|
||||
bindPoints := []*xweb.BindPointConfig{
|
||||
{
|
||||
bindPoints := []xweb.BindPoint{
|
||||
&bindpoints.UnderlayBindPoint{
|
||||
Address: bindPoint1Address,
|
||||
},
|
||||
{
|
||||
&bindpoints.UnderlayBindPoint{
|
||||
Address: bindPoint2Address,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ package webapis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/xweb/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -24,15 +24,15 @@ require (
|
||||
github.com/gaissmai/extnetip v1.2.0
|
||||
github.com/go-acme/lego/v4 v4.25.2
|
||||
github.com/go-jose/go-jose/v4 v4.1.3
|
||||
github.com/go-openapi/errors v0.22.4
|
||||
github.com/go-openapi/errors v0.22.3
|
||||
github.com/go-openapi/jsonpointer v0.22.1
|
||||
github.com/go-openapi/loads v0.23.2
|
||||
github.com/go-openapi/runtime v0.29.2
|
||||
github.com/go-openapi/spec v0.22.1
|
||||
github.com/go-openapi/strfmt v0.25.0
|
||||
github.com/go-openapi/loads v0.23.1
|
||||
github.com/go-openapi/runtime v0.29.0
|
||||
github.com/go-openapi/spec v0.22.0
|
||||
github.com/go-openapi/strfmt v0.24.0
|
||||
github.com/go-openapi/swag v0.25.1
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1
|
||||
github.com/go-openapi/validate v0.25.1
|
||||
github.com/go-openapi/validate v0.25.0
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
@@ -72,7 +72,7 @@ require (
|
||||
github.com/openziti/storage v0.4.31
|
||||
github.com/openziti/transport/v2 v2.0.198
|
||||
github.com/openziti/x509-claims v1.0.3
|
||||
github.com/openziti/xweb/v2 v2.3.4
|
||||
github.com/openziti/xweb/v3 v3.0.1
|
||||
github.com/openziti/ziti-db-explorer v1.1.3
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
@@ -92,13 +92,14 @@ require (
|
||||
go.etcd.io/bbolt v1.4.3
|
||||
go.uber.org/atomic v1.11.0
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5
|
||||
golang.org/x/crypto v0.44.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||
golang.org/x/net v0.47.0
|
||||
golang.org/x/oauth2 v0.33.0
|
||||
golang.org/x/sync v0.18.0
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/text v0.31.0
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/oauth2 v0.32.0
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/sys v0.37.0
|
||||
golang.org/x/term v0.36.0
|
||||
golang.org/x/text v0.30.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8
|
||||
gopkg.in/resty.v1 v1.12.0
|
||||
@@ -116,6 +117,7 @@ require (
|
||||
github.com/antchfx/xpath v1.3.2 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/biogo/store v0.0.0-20200525035639-8c94ae1e7c9c // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
|
||||
github.com/boltdb/bolt v1.3.1 // indirect
|
||||
@@ -133,8 +135,8 @@ require (
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/analysis v0.24.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.3 // indirect
|
||||
github.com/go-openapi/analysis v0.24.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.2 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/fileutils v0.25.1 // indirect
|
||||
@@ -173,6 +175,7 @@ require (
|
||||
github.com/openziti-incubator/cf v0.0.3 // indirect
|
||||
github.com/openziti/dilithium v0.3.5 // indirect
|
||||
github.com/openziti/go-term-markdown v1.0.1 // indirect
|
||||
github.com/openziti/xweb/v2 v2.3.4 // indirect
|
||||
github.com/parallaxsecond/parsec-client-go v0.0.0-20221025095442-f0a77d263cf9 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pion/dtls/v3 v3.0.7 // indirect
|
||||
@@ -191,8 +194,8 @@ require (
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
@@ -200,7 +203,7 @@ require (
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zitadel/logging v0.6.2 // indirect
|
||||
github.com/zitadel/schema v1.3.1 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.6 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.4 // indirect
|
||||
go.mozilla.org/pkcs7 v0.9.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
@@ -208,9 +211,8 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
nhooyr.io/websocket v1.8.17 // indirect
|
||||
)
|
||||
|
||||
@@ -100,6 +100,8 @@ github.com/armon/go-metrics v0.3.8/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4
|
||||
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
|
||||
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
@@ -225,22 +227,22 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM=
|
||||
github.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84=
|
||||
github.com/go-openapi/errors v0.22.4 h1:oi2K9mHTOb5DPW2Zjdzs/NIvwi2N3fARKaTJLdNabaM=
|
||||
github.com/go-openapi/errors v0.22.4/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk=
|
||||
github.com/go-openapi/analysis v0.24.0 h1:vE/VFFkICKyYuTWYnplQ+aVr45vlG6NcZKC7BdIXhsA=
|
||||
github.com/go-openapi/analysis v0.24.0/go.mod h1:GLyoJA+bvmGGaHgpfeDh8ldpGo69fAJg7eeMDMRCIrw=
|
||||
github.com/go-openapi/errors v0.22.3 h1:k6Hxa5Jg1TUyZnOwV2Lh81j8ayNw5VVYLvKrp4zFKFs=
|
||||
github.com/go-openapi/errors v0.22.3/go.mod h1:+WvbaBBULWCOna//9B9TbLNGSFOfF8lY9dw4hGiEiKQ=
|
||||
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
|
||||
github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
|
||||
github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc=
|
||||
github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4=
|
||||
github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJp4=
|
||||
github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY=
|
||||
github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0=
|
||||
github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0=
|
||||
github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k=
|
||||
github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA=
|
||||
github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ=
|
||||
github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8=
|
||||
github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU=
|
||||
github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ=
|
||||
github.com/go-openapi/loads v0.23.1 h1:H8A0dX2KDHxDzc797h0+uiCZ5kwE2+VojaQVaTlXvS0=
|
||||
github.com/go-openapi/loads v0.23.1/go.mod h1:hZSXkyACCWzWPQqizAv/Ye0yhi2zzHwMmoXQ6YQml44=
|
||||
github.com/go-openapi/runtime v0.29.0 h1:Y7iDTFarS9XaFQ+fA+lBLngMwH6nYfqig1G+pHxMRO0=
|
||||
github.com/go-openapi/runtime v0.29.0/go.mod h1:52HOkEmLL/fE4Pg3Kf9nxc9fYQn0UsIWyGjGIJE9dkg=
|
||||
github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0Tw=
|
||||
github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0=
|
||||
github.com/go-openapi/strfmt v0.24.0 h1:dDsopqbI3wrrlIzeXRbqMihRNnjzGC+ez4NQaAAJLuc=
|
||||
github.com/go-openapi/strfmt v0.24.0/go.mod h1:Lnn1Bk9rZjXxU9VMADbEEOo7D7CDyKGLsSKekhFr7s4=
|
||||
github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8=
|
||||
github.com/go-openapi/swag v0.25.1/go.mod h1:bzONdGlT0fkStgGPd3bhZf1MnuPkf2YAys6h+jZipOo=
|
||||
github.com/go-openapi/swag/cmdutils v0.25.1 h1:nDke3nAFDArAa631aitksFGj2omusks88GF1VwdYqPY=
|
||||
@@ -267,12 +269,8 @@ github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3
|
||||
github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
|
||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-openapi/validate v0.25.1 h1:sSACUI6Jcnbo5IWqbYHgjibrhhmt3vR6lCzKZnmAgBw=
|
||||
github.com/go-openapi/validate v0.25.1/go.mod h1:RMVyVFYte0gbSTaZ0N4KmTn6u/kClvAFp+mAVfS/DQc=
|
||||
github.com/go-openapi/validate v0.25.0 h1:JD9eGX81hDTjoY3WOzh6WqxVBVl7xjsLnvDo1GL5WPU=
|
||||
github.com/go-openapi/validate v0.25.0/go.mod h1:SUY7vKrN5FiwK6LyvSwKjDfLNirSfWwHNgxd2l29Mmw=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
@@ -621,6 +619,8 @@ github.com/openziti/x509-claims v1.0.3 h1:HNdQ8Nf1agB3lBs1gahcO6zfkeS4S5xoQ2/PkY
|
||||
github.com/openziti/x509-claims v1.0.3/go.mod h1:Z0WIpBm6c4ecrpRKrou6Gk2wrLWxJO/+tuUwKh8VewE=
|
||||
github.com/openziti/xweb/v2 v2.3.4 h1:QFRyyvxBuBv9dRJVMW04gpD3dXo6L4vNs4PM3nlYtYM=
|
||||
github.com/openziti/xweb/v2 v2.3.4/go.mod h1:z1bKQh1mCFxXL9288jtdKzYeXYVXv1Em452jabM2gIM=
|
||||
github.com/openziti/xweb/v3 v3.0.1 h1:Ey31+21XirlU7ld/Plmpy8YpDVOevSEyFBAH6FzX7wU=
|
||||
github.com/openziti/xweb/v3 v3.0.1/go.mod h1:ljMB+Ne3bI3I5cItZ4H/bLIGuXvsnlsHVZOp1Z7AvqM=
|
||||
github.com/openziti/ziti-db-explorer v1.1.3 h1:9JER16MJzagtYPdGEhgDcw2p/BXNCVbf9IgA/sMB52w=
|
||||
github.com/openziti/ziti-db-explorer v1.1.3/go.mod h1:pMIMNJoTRSTbkO2e7cZWiBokA3jMdeiGAILP3QhU+v8=
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=
|
||||
@@ -791,10 +791,10 @@ github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSW
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125 h1:3SNcvBmEPE1YlB1JpVZouslJpI3GBNoiqW7+wb0Rz7w=
|
||||
github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
@@ -831,8 +831,8 @@ go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
|
||||
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
||||
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI=
|
||||
go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
@@ -883,8 +883,8 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -924,8 +924,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -974,8 +974,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -990,8 +990,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
|
||||
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1005,8 +1005,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1083,13 +1083,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1099,8 +1099,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1162,8 +1162,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package jwtutil
|
||||
|
||||
import "strings"
|
||||
|
||||
func IsJwt(token string) bool {
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) != 3 {
|
||||
return false
|
||||
}
|
||||
for _, p := range parts {
|
||||
if p == "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+36
-13
@@ -26,7 +26,7 @@ function _wait_for_controller {
|
||||
)" != "200" ]]; do
|
||||
if (( elapsed >= timeout )); then
|
||||
echo "Timeout waiting for https://${advertised_host_port}" >&2
|
||||
exit 1
|
||||
return 1
|
||||
fi
|
||||
echo "waiting for https://${advertised_host_port}"
|
||||
sleep 3
|
||||
@@ -40,7 +40,7 @@ function _wait_for_controller {
|
||||
while ! "${BUILD_DIR}/ziti" edge login -u admin -p admin "${advertised_host_port}" -y; do
|
||||
if (( elapsed >= timeout )); then
|
||||
echo "Login failed after $timeout seconds, exiting."
|
||||
exit 1
|
||||
return 1
|
||||
fi
|
||||
echo "Login failed, retrying..."
|
||||
sleep 1
|
||||
@@ -53,18 +53,18 @@ function _wait_for_controller {
|
||||
function _check_command() {
|
||||
if ! command -v "$1" &>/dev/null; then
|
||||
echo "ERROR: this script requires ${BINS[*]}, but '$1' is missing." >&2
|
||||
$1
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function _wait_for_leader() {
|
||||
local timeout=10 # Maximum wait time in seconds
|
||||
local timeout=10
|
||||
local elapsed=0
|
||||
|
||||
while [ "$elapsed" -lt "$timeout" ]; do
|
||||
if "${BUILD_DIR}/ziti" ops cluster list | awk -F'│' 'NR>3 {print $3, $5}' | grep -q "true"; then
|
||||
echo "Leader found"
|
||||
return 0 # Success
|
||||
return 0
|
||||
fi
|
||||
echo "leader not found. waiting for leader..."
|
||||
sleep 1
|
||||
@@ -72,7 +72,7 @@ function _wait_for_leader() {
|
||||
done
|
||||
|
||||
echo "No leader found after $timeout seconds"
|
||||
return 1 # Failure
|
||||
return 1
|
||||
}
|
||||
|
||||
declare -a BINS=(awk grep jq "${BUILD_DIR}/ziti")
|
||||
@@ -91,8 +91,15 @@ done
|
||||
> >(while IFS= read -r line; do echo "inst1: $line"; done) 2>&1 &
|
||||
pid1=$!
|
||||
|
||||
_wait_for_controller "2001"
|
||||
_wait_for_leader
|
||||
if ! _wait_for_controller "2001"; then
|
||||
echo "Controller 2001 failed to start" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! _wait_for_leader; then
|
||||
echo "Leader not found after starting inst1" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${BUILD_DIR}/ziti" edge quickstart join \
|
||||
--ctrl-address="127.0.0.1" \
|
||||
@@ -107,8 +114,16 @@ _wait_for_leader
|
||||
pid2=$!
|
||||
|
||||
sleep 3
|
||||
_wait_for_controller "2002"
|
||||
_wait_for_leader
|
||||
|
||||
if ! _wait_for_controller "2002"; then
|
||||
echo "Controller 2002 failed to start" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! _wait_for_leader; then
|
||||
echo "Leader not found after starting inst2" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${BUILD_DIR}/ziti" edge quickstart join \
|
||||
--ctrl-address="127.0.0.1" \
|
||||
@@ -123,7 +138,11 @@ _wait_for_leader
|
||||
pid3=$!
|
||||
|
||||
sleep 3
|
||||
_wait_for_controller "2003"
|
||||
|
||||
if ! _wait_for_controller "2003"; then
|
||||
echo "Controller 2003 failed to start" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "========================================================="
|
||||
echo "HA Cluster should now be online"
|
||||
@@ -131,8 +150,12 @@ echo "HA Cluster should now be online"
|
||||
echo ""
|
||||
echo "Building and running quickstart test"
|
||||
echo "========================================================="
|
||||
"${BUILD_DIR}/ziti" ops verify traffic -u admin -p admin --controller-url localhost:2001 -y \
|
||||
> >(while IFS= read -r line; do echo "traffic: $line"; done) 2>&1
|
||||
|
||||
if ! "${BUILD_DIR}/ziti" ops verify traffic -u admin -p admin --controller-url localhost:2001 -y \
|
||||
> >(while IFS= read -r line; do echo "traffic: $line"; done) 2>&1; then
|
||||
echo "Traffic verification failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ZITI_CTRL_EDGE_ADVERTISED_ADDRESS=localhost \
|
||||
ZITI_CTRL_EDGE_ADVERTISED_PORT=2001 \
|
||||
|
||||
+18
-10
@@ -48,7 +48,7 @@ import (
|
||||
"github.com/openziti/metrics"
|
||||
"github.com/openziti/sdk-golang/xgress"
|
||||
"github.com/openziti/transport/v2"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/xweb/v3"
|
||||
"github.com/openziti/ziti/common"
|
||||
"github.com/openziti/ziti/common/alert"
|
||||
"github.com/openziti/ziti/common/config"
|
||||
@@ -298,24 +298,32 @@ func (self *Router) createDataPlaneAdapter() xgress.DataPlaneAdapter {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Router) ListenForShutdownSignal() {
|
||||
func (self *Router) ListenForShutdownSignal(ctx context.Context) {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, os.Interrupt, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer signal.Stop(ch)
|
||||
|
||||
s := <-ch
|
||||
pfxlog.Logger().Info("waiting for shutdown signal or context cancel")
|
||||
|
||||
if s == syscall.SIGQUIT {
|
||||
fmt.Println("=== STACK DUMP BEGIN ===")
|
||||
debugz.DumpStack()
|
||||
fmt.Println("=== STACK DUMP CLOSE ===")
|
||||
select {
|
||||
case s := <-ch:
|
||||
pfxlog.Logger().Infof("received signal: %v", s)
|
||||
if s == syscall.SIGQUIT {
|
||||
fmt.Println("=== STACK DUMP BEGIN ===")
|
||||
debugz.DumpStack()
|
||||
fmt.Println("=== STACK DUMP END ===")
|
||||
}
|
||||
case <-ctx.Done():
|
||||
pfxlog.Logger().Info("context cancelled, initiating shutdown")
|
||||
}
|
||||
|
||||
log := pfxlog.Logger()
|
||||
|
||||
log.Info("shutting down ziti router")
|
||||
|
||||
if err := self.Shutdown(); err != nil {
|
||||
log.WithError(err).Info("error encountered during shutdown")
|
||||
log.WithError(err).Error("error encountered during shutdown")
|
||||
} else {
|
||||
log.Info("shutdown complete")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,7 +649,7 @@ func (self *Router) registerComponents() error {
|
||||
var errs []error
|
||||
for i, serverConfig := range config.ServerConfigs {
|
||||
for _, bp := range serverConfig.BindPoints {
|
||||
if ve := serverConfig.Identity.ValidFor(strings.Split(bp.Address, ":")[0]); ve != nil {
|
||||
if ve := serverConfig.Identity.ValidFor(strings.Split(bp.ServerAddress(), ":")[0]); ve != nil {
|
||||
if config.Options.DefaultConfigSection != xweb.DefaultConfigSection {
|
||||
errs = append(errs, fmt.Errorf("could not validate server at %s[%d]: %v", config.Options.DefaultConfigSection, i, ve))
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build cli_tests
|
||||
|
||||
/*
|
||||
Copyright NetFoundry Inc.
|
||||
|
||||
@@ -14,87 +16,97 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
package cli_tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/antchfx/jsonquery"
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/ziti/ziti/cmd/ascode/exporter"
|
||||
"github.com/openziti/ziti/ziti/cmd/ascode/importer"
|
||||
"github.com/openziti/ziti/ziti/run"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/antchfx/jsonquery"
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/ziti/tests/testutil"
|
||||
"github.com/openziti/ziti/ziti/cmd/ascode/exporter"
|
||||
"github.com/openziti/ziti/ziti/cmd/ascode/importer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var log = pfxlog.Logger()
|
||||
|
||||
func TestYamlUploadAndDownload(t *testing.T) {
|
||||
zitiPath := os.Getenv("ZITI_CLI_TEST_ZITI_BIN")
|
||||
if zitiPath == "" {
|
||||
t.Fatalf("ZITI_CLI_TEST_ZITI_BIN not set")
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cmdComplete := make(chan bool)
|
||||
qsCmd := run.NewQuickStartCmd(os.Stdout, os.Stderr, ctx)
|
||||
|
||||
qsCmd.SetArgs([]string{})
|
||||
|
||||
go func() {
|
||||
err := qsCmd.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cmdComplete <- true
|
||||
}()
|
||||
|
||||
c := make(chan struct{})
|
||||
go waitForController("https://127.0.0.1:1280", c)
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
//completed normally
|
||||
log.Info("controller online")
|
||||
case <-time.After(30 * time.Second):
|
||||
cancel()
|
||||
panic("timed out waiting for controller")
|
||||
baseDir := filepath.Join(os.TempDir(), "import-tests")
|
||||
if me := os.MkdirAll(baseDir, 0755); me != nil {
|
||||
t.Fatalf("failed creating baseDir dir: %v", baseDir)
|
||||
}
|
||||
testRunHome, err := os.MkdirTemp(baseDir, "test-run-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed creating temp dir: %v", err)
|
||||
}
|
||||
|
||||
performImport(t)
|
||||
performAllTest(t)
|
||||
performServiceAndConfigTest(t)
|
||||
performIdentitiesTest(t)
|
||||
// set ZITI_CONFIG_DIR so that anything here forth is not corrupting local stuff
|
||||
_ = os.Setenv("ZITI_CONFIG_DIR", filepath.Join(testRunHome, ".config/ziti"))
|
||||
overlay := testutil.CreateOverlay(t, ctx, 60*time.Second, testRunHome, "import", false)
|
||||
targetDone := make(chan error)
|
||||
go overlay.StartExternal(zitiPath, targetDone)
|
||||
|
||||
defer func() {
|
||||
if !t.Failed() {
|
||||
// allow/ensure the processes to exit windows is a pain about rm'ing folders if not
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
go func() { errChan <- overlay.Stop() }()
|
||||
success := true
|
||||
|
||||
if err := <-errChan; err != nil {
|
||||
t.Logf("stop error: %v", err)
|
||||
success = false
|
||||
}
|
||||
|
||||
if !success {
|
||||
t.Logf("manual cleanup may be required at %s", testRunHome)
|
||||
} else {
|
||||
t.Logf("tests passed, removing temp dir at %s", testRunHome)
|
||||
if rerr := os.RemoveAll(testRunHome); rerr != nil {
|
||||
t.Logf("remove %s failed... **sigh**: %v", testRunHome, rerr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.Logf("tests failed, temp dir left intact at %s", testRunHome)
|
||||
}
|
||||
overlay.CleanupPids()
|
||||
}()
|
||||
startErr := overlay.WaitForControllerReady(30 * time.Second)
|
||||
if startErr != nil {
|
||||
log.Fatalf("start controller failed: %v", startErr)
|
||||
}
|
||||
|
||||
performImport(t, overlay.ControllerHostPort())
|
||||
performAllTest(t, overlay.ControllerHostPort())
|
||||
performServiceAndConfigTest(t, overlay.ControllerHostPort())
|
||||
performIdentitiesTest(t, overlay.ControllerHostPort())
|
||||
|
||||
cancel() //terminate the running ctrl/router
|
||||
|
||||
<-cmdComplete
|
||||
fmt.Println("Operation completed")
|
||||
}
|
||||
|
||||
func waitForController(ctrlUrl string, done chan struct{}) {
|
||||
tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||
client := &http.Client{Transport: tr}
|
||||
for {
|
||||
r, e := client.Get(ctrlUrl)
|
||||
if e != nil || r == nil || r.StatusCode != 200 {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
done <- struct{}{}
|
||||
|
||||
}
|
||||
|
||||
func performImport(t *testing.T) {
|
||||
func performImport(t *testing.T, url string) {
|
||||
|
||||
errWriter := strings.Builder{}
|
||||
|
||||
uploadWriter := strings.Builder{}
|
||||
importCmd := importer.NewImportCmd(&uploadWriter, &errWriter)
|
||||
importCmd.SetArgs([]string{"--input-format=yaml", "--yes", "--controller-url=localhost:1280", "--username=admin", "--password=admin", "./test.yaml"})
|
||||
importCmd.SetArgs([]string{"--input-format=yaml", "--yes", "--controller-url=" + url, "--username=admin", "--password=admin", "./test.yaml"})
|
||||
|
||||
err := importCmd.Execute()
|
||||
if err != nil {
|
||||
@@ -103,7 +115,7 @@ func performImport(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func performAllTest(t *testing.T) {
|
||||
func performAllTest(t *testing.T, url string) {
|
||||
|
||||
// Create a temporary file in the default temporary directory
|
||||
tempFile, err := os.CreateTemp("", "ascode-output-*.json")
|
||||
@@ -114,7 +126,7 @@ func performAllTest(t *testing.T) {
|
||||
log.Info("output file: ", tempFile.Name())
|
||||
|
||||
exportCmd := exporter.NewExportCmd(os.Stdout, os.Stderr)
|
||||
exportCmd.SetArgs([]string{"--output-format=json", "--yes", "--controller-url=localhost:1280", "--username=admin", "--password=admin", "--output-file=" + tempFile.Name(), "all"})
|
||||
exportCmd.SetArgs([]string{"--output-format=json", "--yes", "--controller-url=" + url, "--username=admin", "--password=admin", "--output-file=" + tempFile.Name(), "all"})
|
||||
err = exportCmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -271,7 +283,7 @@ func performAllTest(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func performServiceAndConfigTest(t *testing.T) {
|
||||
func performServiceAndConfigTest(t *testing.T, url string) {
|
||||
|
||||
// Create a temporary file in the default temporary directory
|
||||
tempFile, err := os.CreateTemp("", "ascode-output-*.json")
|
||||
@@ -282,7 +294,7 @@ func performServiceAndConfigTest(t *testing.T) {
|
||||
log.Info("output file: ", tempFile.Name())
|
||||
|
||||
exportCmd := exporter.NewExportCmd(os.Stdout, os.Stderr)
|
||||
exportCmd.SetArgs([]string{"--output-format=json", "--yes", "--controller-url=localhost:1280", "--username=admin", "--password=admin", "--output-file=" + tempFile.Name(), "service,config"})
|
||||
exportCmd.SetArgs([]string{"--output-format=json", "--yes", "--controller-url=" + url, "--username=admin", "--password=admin", "--output-file=" + tempFile.Name(), "service,config"})
|
||||
err = exportCmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -336,7 +348,7 @@ func performServiceAndConfigTest(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func performIdentitiesTest(t *testing.T) {
|
||||
func performIdentitiesTest(t *testing.T, url string) {
|
||||
|
||||
// Create a temporary file in the default temporary directory
|
||||
tempFile, err := os.CreateTemp("", "ascode-output-*.json")
|
||||
@@ -347,7 +359,7 @@ func performIdentitiesTest(t *testing.T) {
|
||||
log.Info("output file: ", tempFile.Name())
|
||||
|
||||
exportCmd := exporter.NewExportCmd(os.Stdout, os.Stderr)
|
||||
exportCmd.SetArgs([]string{"--output-format=json", "--yes", "--controller-url=localhost:1280", "--username=admin", "--password=admin", "--output-file=" + tempFile.Name(), "identity"})
|
||||
exportCmd.SetArgs([]string{"--output-format=json", "--yes", "--controller-url=" + url, "--username=admin", "--password=admin", "--output-file=" + tempFile.Name(), "identity"})
|
||||
err = exportCmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -0,0 +1,579 @@
|
||||
//go:build cli_tests
|
||||
|
||||
/*
|
||||
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 cli_tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
gopath "path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
"github.com/openziti/ziti/tests/testutil"
|
||||
"github.com/openziti/ziti/ziti/cmd"
|
||||
"github.com/openziti/ziti/ziti/cmd/api"
|
||||
"github.com/openziti/ziti/ziti/cmd/common"
|
||||
"github.com/openziti/ziti/ziti/cmd/edge"
|
||||
"github.com/openziti/ziti/ziti/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type loginTestState struct {
|
||||
homeDir string
|
||||
zitiContext *ziti.Context
|
||||
zitiTransport *http.Transport
|
||||
commonOpts api.Options
|
||||
externalZiti testutil.Overlay
|
||||
controllerUnderTest testutil.Overlay
|
||||
}
|
||||
|
||||
func (s *loginTestState) removeZitiDir(t *testing.T) {
|
||||
zitiDir, _ := util.ConfigDir()
|
||||
if err := os.RemoveAll(zitiDir); err != nil {
|
||||
t.Errorf("remove %s: %v", zitiDir, err)
|
||||
t.Fail()
|
||||
}
|
||||
t.Logf("Removed ziti dir from: %s", zitiDir)
|
||||
}
|
||||
|
||||
func Test_LoginSuite(t *testing.T) {
|
||||
zitiPath := os.Getenv("ZITI_CLI_TEST_ZITI_BIN")
|
||||
if zitiPath == "" {
|
||||
t.Fatalf("ZITI_CLI_TEST_ZITI_BIN not set")
|
||||
}
|
||||
if _, statErr := os.Stat(zitiPath); statErr != nil {
|
||||
t.Fatalf("ziti binary not found at provided location %s: %v", zitiPath, statErr)
|
||||
}
|
||||
baseDir := filepath.Join(os.TempDir(), "cli-tests")
|
||||
if me := os.MkdirAll(baseDir, 0755); me != nil {
|
||||
t.Fatalf("failed creating baseDir dir: %v", baseDir)
|
||||
}
|
||||
testRunHome, mkdirErr := os.MkdirTemp(baseDir, "test-run-*")
|
||||
if mkdirErr != nil {
|
||||
t.Fatalf("failed creating temp dir: %v", mkdirErr)
|
||||
}
|
||||
// set ZITI_CONFIG_DIR so that anything here forth is not corrupting local stuff
|
||||
cfgDir := filepath.Join(baseDir, ".config/ziti")
|
||||
_ = os.Setenv("ZITI_CONFIG_DIR", cfgDir)
|
||||
_ = os.RemoveAll(cfgDir)
|
||||
externalCtx, externalCancel := context.WithCancel(context.Background())
|
||||
defer externalCancel()
|
||||
ctrlUnderTestCtx, ctrlUnderTestCancel := context.WithCancel(context.Background())
|
||||
defer ctrlUnderTestCancel()
|
||||
|
||||
testState := &loginTestState{
|
||||
homeDir: util.HomeDir(),
|
||||
zitiContext: nil,
|
||||
zitiTransport: nil,
|
||||
externalZiti: testutil.CreateOverlay(t, externalCtx, 600*time.Second, testRunHome, "external", false),
|
||||
controllerUnderTest: testutil.CreateOverlay(t, ctrlUnderTestCtx, 600*time.Second, testRunHome, "target", false),
|
||||
commonOpts: api.Options{
|
||||
CommonOptions: common.CommonOptions{
|
||||
Out: os.Stdout,
|
||||
Err: os.Stderr,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if !t.Failed() {
|
||||
// allow/ensure the processes to exit windows is a pain about rm'ing folders if not
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
go func() { errChan <- testState.externalZiti.Stop() }()
|
||||
go func() { errChan <- testState.controllerUnderTest.Stop() }()
|
||||
success := true
|
||||
for i := 0; i < 2; i++ { // Wait for both
|
||||
if deferErr := <-errChan; deferErr != nil {
|
||||
t.Logf("stop error: %v", deferErr)
|
||||
success = false
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
t.Logf("manual cleanup may be required at %s", testRunHome)
|
||||
} else {
|
||||
t.Logf("tests passed, removing temp dir at %s", testRunHome)
|
||||
if rerr := os.RemoveAll(testRunHome); rerr != nil {
|
||||
t.Logf("remove %s failed... **sigh**: %v", testRunHome, rerr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.Logf("tests failed, temp dir left intact at %s", testRunHome)
|
||||
}
|
||||
testState.externalZiti.CleanupPids()
|
||||
testState.controllerUnderTest.CleanupPids()
|
||||
}()
|
||||
|
||||
extDone := make(chan error)
|
||||
go testState.externalZiti.StartExternal(zitiPath, extDone)
|
||||
targetDone := make(chan error)
|
||||
go testState.controllerUnderTest.StartExternal(zitiPath, targetDone)
|
||||
|
||||
exStartErr := testState.externalZiti.WaitForControllerReady(60 * time.Second)
|
||||
if exStartErr != nil {
|
||||
log.Fatalf("externalZiti start failed: %v", exStartErr)
|
||||
}
|
||||
|
||||
cutStartErr := testState.controllerUnderTest.WaitForControllerReady(60 * time.Second)
|
||||
if cutStartErr != nil {
|
||||
log.Fatalf("controllerUnderTest start failed: %v", cutStartErr)
|
||||
}
|
||||
|
||||
if lo, le := testState.controllerUnderTest.Login(); le != nil {
|
||||
t.Fatalf("unable to login before running tests: %v", le)
|
||||
} else {
|
||||
testState.controllerUnderTest.ApiSession = lo.ApiSession
|
||||
}
|
||||
|
||||
require.NotEmpty(t, testState.controllerUnderTest.ApiSession)
|
||||
require.NotEmpty(t, testState.controllerUnderTest.ApiSession.GetToken())
|
||||
|
||||
now := time.Now().Format("150405")
|
||||
|
||||
if ae := testState.controllerUnderTest.CreateAdminIdentity(t, now, testRunHome); ae != nil {
|
||||
t.Fatalf("unable to create controller admin: %v", ae)
|
||||
}
|
||||
|
||||
t.Log("====================================================================================")
|
||||
t.Log("=========================== overlay ready. tests begin =============================")
|
||||
t.Log("====================================================================================")
|
||||
|
||||
testTimeout := 120 * time.Second
|
||||
testDone := make(chan struct{})
|
||||
testTimer := time.NewTimer(testTimeout)
|
||||
defer testTimer.Stop()
|
||||
|
||||
go func() {
|
||||
defer close(testDone)
|
||||
// Just signal when time is up, don't call t methods
|
||||
<-testTimer.C
|
||||
}()
|
||||
|
||||
if lr, le := testState.controllerUnderTest.Login(); le != nil {
|
||||
t.Fatalf("unable to login before running tests: %v", le)
|
||||
} else {
|
||||
//set the valid token for reuse later:
|
||||
require.NotEmpty(t, lr.ApiSession)
|
||||
testState.controllerUnderTest.ApiSession = lr.ApiSession
|
||||
}
|
||||
|
||||
t.Run("login tests over underlay", testState.runLoginTests)
|
||||
|
||||
t.Log("Cancelling controllerUnderTest to reconfigure for use with ziti")
|
||||
|
||||
ctrlUnderTestCancel()
|
||||
if se := testState.controllerUnderTest.Stop(); se != nil {
|
||||
t.Fatalf("controllerUnderTest didn't stop? %v", se)
|
||||
}
|
||||
t.Log("Cancelling controllerUnderTest complete")
|
||||
testState.loginTestsOverZiti(t, now, zitiPath)
|
||||
externalCancel()
|
||||
|
||||
t.Run("make sure any ziti instances are stopped", testState.externalZiti.EnsureAllPidsStopped)
|
||||
t.Run("make sure any ziti instances are stopped", testState.controllerUnderTest.EnsureAllPidsStopped)
|
||||
}
|
||||
|
||||
func (s *loginTestState) runLoginTests(t *testing.T) {
|
||||
//Authentication Methods
|
||||
t.Run("correct password succeeds", s.testCorrectPasswordSucceeds)
|
||||
t.Run("wrong password fails", s.testWrongPasswordFails)
|
||||
t.Run("token based login", s.testTokenBasedLogin)
|
||||
t.Run("client cert authentication - no ca", s.testClientCertAuthentication)
|
||||
t.Run("identity file authentication", s.testIdentityFileAuthentication)
|
||||
t.Run("external JWT authentication", s.testExternalJWTAuthentication)
|
||||
t.Run("network identity zitified connection", s.testNetworkIdentityZitifiedConnection)
|
||||
|
||||
// Edge Cases
|
||||
t.Run("empty username", s.testEmptyUsername)
|
||||
t.Run("empty password", s.testEmptyPassword)
|
||||
t.Run("invalid controller URL", s.testInvalidControllerURL)
|
||||
t.Run("non-existent username", s.testNonExistentUsername)
|
||||
t.Run("controller unavailable", s.testControllerUnavailable)
|
||||
}
|
||||
|
||||
// Authentication Methods
|
||||
func (s *loginTestState) testCorrectPasswordSucceeds(t *testing.T) {
|
||||
opts := s.controllerUnderTest.NewTestLoginOpts()
|
||||
|
||||
err := opts.Run()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, opts.ApiSession)
|
||||
t.Logf("Login successful, token: %s", opts.Token)
|
||||
|
||||
// Verify we can create a management client
|
||||
client, err := opts.NewManagementClient(false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, client)
|
||||
require.NotEmpty(t, opts.ApiSession)
|
||||
}
|
||||
|
||||
func (s *loginTestState) testWrongPasswordFails(t *testing.T) {
|
||||
opts := &edge.LoginOptions{
|
||||
Options: s.commonOpts,
|
||||
Username: s.controllerUnderTest.Username,
|
||||
Password: "wrong-password",
|
||||
ControllerUrl: s.controllerUnderTest.ControllerHostPort(),
|
||||
Yes: true,
|
||||
IgnoreConfig: true,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile,
|
||||
}
|
||||
|
||||
err := opts.Run()
|
||||
require.Error(t, err, "login with wrong password should fail")
|
||||
}
|
||||
|
||||
func (s *loginTestState) testTokenBasedLogin(t *testing.T) {
|
||||
opts := &edge.LoginOptions{
|
||||
Options: s.commonOpts,
|
||||
ApiSession: s.controllerUnderTest.ApiSession,
|
||||
ControllerUrl: s.controllerUnderTest.ControllerHostPort(),
|
||||
IgnoreConfig: true,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile,
|
||||
}
|
||||
|
||||
err := opts.Run()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, opts.ApiSession)
|
||||
require.NotEmpty(t, opts.ApiSession.GetToken())
|
||||
t.Logf("Login successful, token: %s", opts.ApiSession.GetToken())
|
||||
}
|
||||
|
||||
func (s *loginTestState) testClientCertAuthentication(t *testing.T) {
|
||||
// Setup common options
|
||||
baseOpts := edge.LoginOptions{
|
||||
Options: s.commonOpts,
|
||||
ControllerUrl: s.controllerUnderTest.ControllerHostPort(),
|
||||
Yes: true,
|
||||
IgnoreConfig: true,
|
||||
ClientCert: s.controllerUnderTest.AdminCertFile,
|
||||
ClientKey: s.controllerUnderTest.AdminKeyFile,
|
||||
CaCert: s.controllerUnderTest.AdminCaFile,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile,
|
||||
}
|
||||
|
||||
s.removeZitiDir(t)
|
||||
t.Run("all present", func(t *testing.T) {
|
||||
opts := baseOpts
|
||||
|
||||
err := opts.Run()
|
||||
require.NoError(t, err, "login with cert/key/ca when all present should succeed")
|
||||
require.NotEmpty(t, opts.ApiSession)
|
||||
t.Logf("Login successful, token: %s", opts.Token)
|
||||
})
|
||||
|
||||
s.removeZitiDir(t)
|
||||
t.Run("no cert", func(t *testing.T) {
|
||||
opts := baseOpts
|
||||
opts.ClientCert = ""
|
||||
|
||||
err := opts.Run()
|
||||
require.Error(t, err, "expected error when client cert is missing")
|
||||
require.Contains(t, err.Error(), "username required but not provided")
|
||||
})
|
||||
|
||||
s.removeZitiDir(t)
|
||||
t.Run("no key", func(t *testing.T) {
|
||||
opts := baseOpts
|
||||
opts.ClientKey = ""
|
||||
|
||||
err := opts.Run()
|
||||
require.Error(t, err, "expected error when client key is missing")
|
||||
require.Contains(t, err.Error(), "failed to read key")
|
||||
})
|
||||
|
||||
s.removeZitiDir(t)
|
||||
t.Run("no CA cert with yes flag", func(t *testing.T) {
|
||||
opts := baseOpts
|
||||
opts.CaCert = ""
|
||||
opts.Yes = true
|
||||
|
||||
err := opts.Run()
|
||||
require.NoError(t, err, "expected success when CA cert is missing and IgnoreConfig is enabled and 'Yes' is true")
|
||||
require.NotEmpty(t, opts.ApiSession)
|
||||
t.Logf("Login successful, token: %s", opts.Token)
|
||||
})
|
||||
|
||||
s.removeZitiDir(t)
|
||||
t.Run("no CA cert without yes flag", func(t *testing.T) {
|
||||
opts := baseOpts
|
||||
opts.CaCert = ""
|
||||
opts.Yes = false
|
||||
|
||||
err := opts.Run()
|
||||
require.Error(t, err, "expected error when CA cert is missing")
|
||||
require.Contains(t, err.Error(), "Cannot accept certs - no terminal")
|
||||
})
|
||||
}
|
||||
|
||||
func (s *loginTestState) testIdentityFileAuthentication(t *testing.T) {
|
||||
opts := &edge.LoginOptions{
|
||||
Options: s.commonOpts,
|
||||
ControllerUrl: s.controllerUnderTest.ControllerHostPort(),
|
||||
Yes: true,
|
||||
IgnoreConfig: true,
|
||||
File: s.controllerUnderTest.AdminIdFile,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile,
|
||||
}
|
||||
|
||||
err := opts.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := opts.NewManagementClient(false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, client)
|
||||
require.NotEmpty(t, opts.ApiSession)
|
||||
t.Logf("Login successful, token: %s", opts.Token)
|
||||
}
|
||||
|
||||
func (s *loginTestState) testExternalJWTAuthentication(t *testing.T) {
|
||||
// TODO: Generate valid JWT token
|
||||
t.Skip("External JWT authentication requires JWT setup")
|
||||
}
|
||||
|
||||
func (s *loginTestState) testNetworkIdentityZitifiedConnection(t *testing.T) {
|
||||
// TODO: Create network identity file
|
||||
t.Skip("Network identity requires identity setup")
|
||||
}
|
||||
|
||||
// Edge Cases
|
||||
func (s *loginTestState) testEmptyUsername(t *testing.T) {
|
||||
opts := &edge.LoginOptions{
|
||||
Options: s.commonOpts,
|
||||
Username: "",
|
||||
Password: s.controllerUnderTest.Password,
|
||||
ControllerUrl: s.controllerUnderTest.ControllerHostPort(),
|
||||
Yes: true,
|
||||
IgnoreConfig: true,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile,
|
||||
}
|
||||
|
||||
err := opts.Run()
|
||||
require.Error(t, err, "empty username should fail")
|
||||
require.Contains(t, err.Error(), "username required but not provided")
|
||||
t.Logf("Empty username correctly failed: %v", err)
|
||||
}
|
||||
|
||||
func (s *loginTestState) testEmptyPassword(t *testing.T) {
|
||||
opts := &edge.LoginOptions{
|
||||
Options: s.commonOpts,
|
||||
Username: s.controllerUnderTest.Username,
|
||||
Password: "",
|
||||
ControllerUrl: s.controllerUnderTest.ControllerHostPort(),
|
||||
Yes: true,
|
||||
IgnoreConfig: true,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile,
|
||||
}
|
||||
|
||||
err := opts.Run()
|
||||
require.Error(t, err, "empty password should fail")
|
||||
require.Contains(t, err.Error(), "password required but not provided")
|
||||
t.Logf("Empty password correctly failed: %v", err)
|
||||
}
|
||||
|
||||
func (s *loginTestState) testInvalidControllerURL(t *testing.T) {
|
||||
hostErrors := []string{"i/o timeout", "no such host", "server misbehaving"}
|
||||
t.Run("not-a-url", func(t *testing.T) {
|
||||
opts := &edge.LoginOptions{Options: s.commonOpts, Username: s.controllerUnderTest.Username, Password: s.controllerUnderTest.Password,
|
||||
ControllerUrl: "not-a-url", Yes: true, IgnoreConfig: true,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile}
|
||||
err := opts.Run()
|
||||
require.Error(t, err)
|
||||
require.True(t,
|
||||
strings.Contains(err.Error(), hostErrors[0]) ||
|
||||
strings.Contains(err.Error(), hostErrors[1]) ||
|
||||
strings.Contains(err.Error(), hostErrors[2]) ||
|
||||
strings.Contains(err.Error(), "service 'not-a-url' not found"),
|
||||
"Error %s not contained in host errors array: %v", err.Error(), hostErrors)
|
||||
t.Logf("Invalid URL correctly failed: %v", err)
|
||||
})
|
||||
|
||||
t.Run("http://[invalid", func(t *testing.T) {
|
||||
opts := &edge.LoginOptions{
|
||||
Options: s.commonOpts,
|
||||
Username: s.controllerUnderTest.Username,
|
||||
Password: s.controllerUnderTest.Password,
|
||||
ControllerUrl: "http://[invalid",
|
||||
Yes: true,
|
||||
IgnoreConfig: true,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile,
|
||||
}
|
||||
err := opts.Run()
|
||||
require.Error(t, err)
|
||||
invurlmsg := "invalid controller URL"
|
||||
parsemsg := "unable to parse controller url"
|
||||
require.True(t,
|
||||
strings.Contains(err.Error(), invurlmsg) ||
|
||||
strings.Contains(err.Error(), parsemsg),
|
||||
`Error %s found but expected either: %s or %s`, err.Error(), invurlmsg, parsemsg)
|
||||
t.Logf("Invalid URL correctly failed: %v", err)
|
||||
})
|
||||
|
||||
t.Run("ftp://wrong-scheme.com", func(t *testing.T) {
|
||||
opts := &edge.LoginOptions{Options: s.commonOpts, Username: s.controllerUnderTest.Username, Password: s.controllerUnderTest.Password,
|
||||
ControllerUrl: "ftp://wrong-scheme.com", Yes: true, IgnoreConfig: true,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile}
|
||||
err := opts.Run()
|
||||
require.Error(t, err)
|
||||
require.True(t,
|
||||
strings.Contains(err.Error(), hostErrors[0]) ||
|
||||
strings.Contains(err.Error(), hostErrors[1]) ||
|
||||
strings.Contains(err.Error(), hostErrors[2]) ||
|
||||
strings.Contains(err.Error(), "service 'ftp' not found"),
|
||||
"Error %s not contained in host errors array: %v", err.Error(), hostErrors)
|
||||
t.Logf("Invalid URL correctly failed: %v", err)
|
||||
})
|
||||
|
||||
t.Run("https://non-existent-host-12345.local:9999", func(t *testing.T) {
|
||||
opts := &edge.LoginOptions{Options: s.commonOpts, Username: s.controllerUnderTest.Username, Password: s.controllerUnderTest.Password,
|
||||
ControllerUrl: "https://non-existent-host-12345.local:9999", Yes: true, IgnoreConfig: true,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile}
|
||||
err := opts.Run()
|
||||
require.Error(t, err)
|
||||
require.True(t,
|
||||
strings.Contains(err.Error(), hostErrors[0]) ||
|
||||
strings.Contains(err.Error(), hostErrors[1]) ||
|
||||
strings.Contains(err.Error(), hostErrors[2]) ||
|
||||
strings.Contains(err.Error(), "service 'non-existent-host-12345.local' not found"),
|
||||
"Error %s not contained in host errors array: %v", err.Error(), hostErrors)
|
||||
t.Logf("Invalid URL correctly failed: %v", err)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *loginTestState) testNonExistentUsername(t *testing.T) {
|
||||
opts := &edge.LoginOptions{
|
||||
Options: s.commonOpts,
|
||||
Username: "nonexistent-user-12345",
|
||||
Password: s.controllerUnderTest.Password,
|
||||
ControllerUrl: s.controllerUnderTest.ControllerHostPort(),
|
||||
Yes: true,
|
||||
IgnoreConfig: true,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile,
|
||||
}
|
||||
|
||||
err := opts.Run()
|
||||
require.Error(t, err, "non-existent username should fail")
|
||||
t.Logf("Non-existent username correctly failed: %v", err)
|
||||
}
|
||||
|
||||
func (s *loginTestState) testControllerUnavailable(t *testing.T) {
|
||||
expectedErr := "connection refused"
|
||||
if runtime.GOOS == "windows" { //because of course it's different on linux/windows
|
||||
expectedErr = "the target machine actively refused it"
|
||||
}
|
||||
opts := &edge.LoginOptions{
|
||||
Options: s.commonOpts,
|
||||
Username: s.controllerUnderTest.Username,
|
||||
Password: s.controllerUnderTest.Password,
|
||||
ControllerUrl: "https://127.0.0.1:9999",
|
||||
Yes: true,
|
||||
IgnoreConfig: true,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile,
|
||||
}
|
||||
|
||||
err := opts.Run()
|
||||
require.Error(t, err, "unavailable controller should fail")
|
||||
require.True(t,
|
||||
strings.Contains(err.Error(), expectedErr) ||
|
||||
strings.Contains(err.Error(), "service '127.0.0.1' not found"),
|
||||
"Expected error not found: %v", err.Error())
|
||||
|
||||
t.Logf("Unavailable controller correctly failed: %v", err)
|
||||
}
|
||||
|
||||
func (s *loginTestState) reconfigureTargetForZiti(pkiRoot string) error {
|
||||
v2 := cmd.NewRootCommand(os.Stdin, os.Stdout, os.Stderr)
|
||||
v2.SetArgs(strings.Split("pki create server --key-file server --pki-root "+pkiRoot+" --ip 127.0.0.1,::1 --dns localhost,mgmt,mgmt.ziti --ca-name intermediate-ca-quickstart --server-file mgmt.ziti", " "))
|
||||
if zitiCmdErr := v2.Execute(); zitiCmdErr != nil {
|
||||
return zitiCmdErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *loginTestState) loginTestsOverZiti(t *testing.T, now, zitiPath string) {
|
||||
t.Run("login tests over ziti", func(t *testing.T) {
|
||||
pkiRoot := gopath.Join(s.controllerUnderTest.Home, "pki")
|
||||
if reconfErr := s.reconfigureTargetForZiti(pkiRoot); reconfErr != nil {
|
||||
t.Fatalf("failed to reconfigure target: %v", reconfErr)
|
||||
}
|
||||
|
||||
if ie := s.externalZiti.CreateOverlayIdentities(t, now); ie != nil {
|
||||
t.Fatalf("failed to initialize ziti transport for controllerUnderTest: %v", ie)
|
||||
}
|
||||
s.controllerUnderTest.NetworkDialingIdFile = s.externalZiti.NetworkDialingIdFile
|
||||
s.controllerUnderTest.NetworkBindingIdFile = s.externalZiti.NetworkBindingIdFile
|
||||
|
||||
controllerUnderTestCtx2, controllerUnderTestCancel := context.WithCancel(context.Background())
|
||||
defer controllerUnderTestCancel()
|
||||
s.controllerUnderTest.Ctx = controllerUnderTestCtx2
|
||||
s.controllerUnderTest.ConfigFile = gopath.Join(s.controllerUnderTest.Home, "ctrl.yaml")
|
||||
newServerCertPath := gopath.Join(s.controllerUnderTest.Home, "pki/intermediate-ca-quickstart/certs/mgmt.ziti.chain.pem")
|
||||
if re := s.controllerUnderTest.ReplaceConfig(newServerCertPath); re != nil {
|
||||
t.Fatalf("failed to replace config: %v", re)
|
||||
}
|
||||
|
||||
targetDone := make(chan error)
|
||||
go s.controllerUnderTest.StartExternal(zitiPath, targetDone)
|
||||
cutStartErr := s.controllerUnderTest.WaitForControllerReady(60 * time.Second)
|
||||
if cutStartErr != nil {
|
||||
log.Fatalf("controllerUnderTest start failed: %v", cutStartErr)
|
||||
}
|
||||
s.controllerUnderTest.ControllerAddress = "mgmt.ziti"
|
||||
s.controllerUnderTest.ControllerPort = 443
|
||||
|
||||
s.runLoginTests(t)
|
||||
|
||||
s.testZitiThenNot(t)
|
||||
|
||||
controllerUnderTestCancel()
|
||||
})
|
||||
}
|
||||
|
||||
func (s *loginTestState) testZitiThenNot(t *testing.T) {
|
||||
// this test should make sure that after logging in with a zitified login, a subsequent login to a non-zitified
|
||||
// controller works as expected
|
||||
opts := &edge.LoginOptions{
|
||||
Options: s.commonOpts,
|
||||
Username: s.controllerUnderTest.Username,
|
||||
Password: s.controllerUnderTest.Password,
|
||||
ControllerUrl: s.controllerUnderTest.ControllerHostPort(),
|
||||
Yes: true,
|
||||
IgnoreConfig: false,
|
||||
NetworkId: s.controllerUnderTest.NetworkDialingIdFile,
|
||||
}
|
||||
|
||||
err := opts.Run()
|
||||
require.NoError(t, err, "overlay controller should login successfully")
|
||||
|
||||
opts = &edge.LoginOptions{
|
||||
Options: s.commonOpts,
|
||||
Username: s.externalZiti.Username,
|
||||
Password: s.externalZiti.Password,
|
||||
ControllerUrl: s.externalZiti.ControllerHostPort(),
|
||||
Yes: true,
|
||||
IgnoreConfig: false,
|
||||
NetworkId: "",
|
||||
}
|
||||
|
||||
err = opts.Run()
|
||||
require.NoError(t, err, "underlay controller should login successfully")
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"authPolicies": [],
|
||||
"certificateAuthorities": [],
|
||||
"configTypes": [],
|
||||
"configs": [
|
||||
{
|
||||
"configType": "@intercept.v1",
|
||||
"data": {
|
||||
"addresses": [
|
||||
"mgmt.ziti"
|
||||
],
|
||||
"portRanges": [
|
||||
{
|
||||
"high": 443,
|
||||
"low": 443
|
||||
}
|
||||
],
|
||||
"protocols": [
|
||||
"tcp"
|
||||
]
|
||||
},
|
||||
"name": "mgmt.interceptv1",
|
||||
"tags": {}
|
||||
}
|
||||
],
|
||||
"edgeRouterPolicies": [
|
||||
{
|
||||
"edgeRouterRoles": [
|
||||
"#public"
|
||||
],
|
||||
"identityRoles": [
|
||||
"#all"
|
||||
],
|
||||
"name": "all-endpoints-public-routers",
|
||||
"semantic": "AnyOf",
|
||||
"tags": {}
|
||||
},
|
||||
{
|
||||
"edgeRouterRoles": [
|
||||
"@router-quickstart"
|
||||
],
|
||||
"identityRoles": [
|
||||
"@router-quickstart"
|
||||
],
|
||||
"name": "edge-router-RoZMjWCQAx-system",
|
||||
"semantic": "AnyOf",
|
||||
"tags": {}
|
||||
}
|
||||
],
|
||||
"edgeRouters": [
|
||||
{
|
||||
"appData": {},
|
||||
"disabled": false,
|
||||
"hostname": "sg4",
|
||||
"isTunnelerEnabled": true,
|
||||
"name": "router-quickstart",
|
||||
"noTraversal": false,
|
||||
"roleAttributes": [
|
||||
"public"
|
||||
],
|
||||
"tags": {},
|
||||
"unverifiedCertPem": null,
|
||||
"unverifiedFingerprint": null
|
||||
}
|
||||
],
|
||||
"externalJwtSigners": [],
|
||||
"identities": [
|
||||
],
|
||||
"postureChecks": [],
|
||||
"serviceEdgeRouterPolicies": [
|
||||
{
|
||||
"edgeRouterRoles": [
|
||||
"#all"
|
||||
],
|
||||
"name": "all-routers-all-services",
|
||||
"semantic": "AnyOf",
|
||||
"serviceRoles": [
|
||||
"#all"
|
||||
],
|
||||
"tags": {}
|
||||
}
|
||||
],
|
||||
"servicePolicies": [
|
||||
{
|
||||
"identityRoles": [
|
||||
"#mgmtclients"
|
||||
],
|
||||
"name": "mgmt.dial",
|
||||
"postureCheckRoles": [],
|
||||
"semantic": "AnyOf",
|
||||
"serviceRoles": [
|
||||
"@mgmt"
|
||||
],
|
||||
"tags": {},
|
||||
"type": "Dial"
|
||||
},
|
||||
{
|
||||
"identityRoles": [
|
||||
"#mgmtservers"
|
||||
],
|
||||
"name": "mgmt.bind",
|
||||
"postureCheckRoles": [],
|
||||
"semantic": "AnyOf",
|
||||
"serviceRoles": [
|
||||
"@mgmt"
|
||||
],
|
||||
"tags": {},
|
||||
"type": "Bind"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"configs": [
|
||||
"@mgmt.interceptv1"
|
||||
],
|
||||
"encryptionRequired": true,
|
||||
"name": "mgmt",
|
||||
"roleAttributes": [
|
||||
"mgmtservers"
|
||||
],
|
||||
"tags": {},
|
||||
"terminatorStrategy": "smartrouting"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# raise exceptions
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR" && git rev-parse --show-toplevel)"
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
echo "changed to root dir at: $ROOT_DIR"
|
||||
|
||||
if [ -z "$ZITI_CLI_TEST_ZITI_BIN" ]; then
|
||||
echo "building binary for use in tests"
|
||||
mkdir -p cli_tests_bin
|
||||
go build -o cli_tests_bin ./...
|
||||
export ZITI_CLI_TEST_ZITI_BIN="$ROOT_DIR/cli_tests_bin/ziti"
|
||||
else
|
||||
echo "using pre-defined ZITI_CLI_TEST_ZITI_BIN at: $ZITI_CLI_TEST_ZITI_BIN"
|
||||
fi
|
||||
|
||||
echo "executing cli_tests from $PWD"
|
||||
go test ./tests/cli_tests/... --tags cli_tests ${ZITI_CLI_TESTS_VERBOSE:-}
|
||||
echo "cli_tests complete"
|
||||
@@ -0,0 +1,547 @@
|
||||
//go:build apitests || cli_tests
|
||||
|
||||
/*
|
||||
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 testutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
gopath "path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/openziti/edge-api/rest_util"
|
||||
edge_apis "github.com/openziti/sdk-golang/edge-apis"
|
||||
"github.com/openziti/ziti/ziti/cmd"
|
||||
"github.com/openziti/ziti/ziti/cmd/api"
|
||||
"github.com/openziti/ziti/ziti/cmd/common"
|
||||
"github.com/openziti/ziti/ziti/cmd/edge"
|
||||
"github.com/openziti/ziti/ziti/cmd/ops"
|
||||
"github.com/openziti/ziti/ziti/run"
|
||||
"github.com/openziti/ziti/ziti/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var commonOpts = api.Options{
|
||||
CommonOptions: common.CommonOptions{
|
||||
Out: os.Stdout,
|
||||
Err: os.Stderr,
|
||||
},
|
||||
}
|
||||
|
||||
type loginCreds struct {
|
||||
AdminCertFile string
|
||||
AdminCaFile string
|
||||
AdminKeyFile string
|
||||
AdminIdFile string
|
||||
ApiSession edge_apis.ApiSession
|
||||
}
|
||||
|
||||
type Overlay struct {
|
||||
loginCreds
|
||||
NetworkBindingIdFile string // a ziti identity file to use when starting the controller which will bind a given service over an overlay
|
||||
NetworkDialingIdFile string // a ziti identity file used to dial mgmt services hosted/bound by a controller using a ziti overlay
|
||||
t *testing.T
|
||||
Ctx context.Context
|
||||
StartTimeout time.Duration
|
||||
Name string
|
||||
extCmd *exec.Cmd
|
||||
cmdDone chan error
|
||||
pidsMutex *sync.Mutex
|
||||
activePids []int
|
||||
*run.QuickstartOpts
|
||||
}
|
||||
|
||||
func (o *Overlay) ControllerHostPort() string {
|
||||
return fmt.Sprintf("https://%s:%d", o.ControllerAddress, o.ControllerPort)
|
||||
}
|
||||
func (o *Overlay) RouterHostPort() string {
|
||||
return fmt.Sprintf("https://%s:%d", o.RouterAddress, o.RouterPort)
|
||||
}
|
||||
|
||||
func (o *Overlay) startArgs() []string {
|
||||
args := []string{
|
||||
fmt.Sprintf("--home=%s", o.Home),
|
||||
fmt.Sprintf("--ctrl-address=%s", o.ControllerAddress),
|
||||
fmt.Sprintf("--ctrl-port=%d", o.ControllerPort),
|
||||
fmt.Sprintf("--router-address=%s", o.RouterAddress),
|
||||
fmt.Sprintf("--router-port=%d", o.RouterPort),
|
||||
}
|
||||
if o.Routerless {
|
||||
args = append(args, "--no-router")
|
||||
}
|
||||
if o.ConfigureAndExit {
|
||||
args = append(args, "--configure-and-exit")
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (o *Overlay) ReplaceConfig(newServerCertPath string) error {
|
||||
content, err := os.ReadFile(o.QuickstartOpts.ConfigFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newContent := string(content)
|
||||
|
||||
// remove edge-management section
|
||||
reEdgeMgmt := regexp.MustCompile(`(?m)^ *- binding: edge-management[\s\S]+?options: \{\ }\n`)
|
||||
newContent = reEdgeMgmt.ReplaceAllString(newContent, "")
|
||||
|
||||
// replace "- binding: fabric" block with single "-"
|
||||
reFabric := regexp.MustCompile(`(?m)- binding: fabric[\s\S]+?options: \{\ }\n {6}-`)
|
||||
newContent = reFabric.ReplaceAllString(newContent, "-")
|
||||
|
||||
reServerCert := regexp.MustCompile(`(?m)^( *)(server_cert:.*server.chain.pem")$`)
|
||||
newContent = reServerCert.ReplaceAllString(newContent, "$1#$2\n${1}server_cert: "+newServerCertPath)
|
||||
|
||||
newContent = newContent + `
|
||||
- name: secured-by-ziti-http
|
||||
bindPoints:
|
||||
- identity:
|
||||
file: ` + o.NetworkBindingIdFile + `
|
||||
service: "mgmt"
|
||||
serveTLS: true
|
||||
apis:
|
||||
- binding: edge-management
|
||||
options: { }
|
||||
- binding: fabric
|
||||
options: { }
|
||||
- binding: zac
|
||||
options:
|
||||
location: "/ctrl/zac/ziti-console-v3.12.5"
|
||||
indexFile: index.html`
|
||||
if we := os.WriteFile(o.QuickstartOpts.ConfigFile, []byte(newContent), 0644); we != nil {
|
||||
return fmt.Errorf("failed to write new content to target controller file: %v", we)
|
||||
} else {
|
||||
fmt.Println("CHANGED FILE AT : " + o.QuickstartOpts.ConfigFile)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Overlay) StartExternal(zitiPath string, done chan error) {
|
||||
args := append([]string{"edge", "quickstart"}, o.startArgs()...)
|
||||
fmt.Printf("%s overlay command: %s %s\n", o.Name, zitiPath, strings.Join(args, " "))
|
||||
o.extCmd = exec.CommandContext(
|
||||
o.Ctx,
|
||||
zitiPath,
|
||||
args...,
|
||||
)
|
||||
_ = os.Mkdir(o.Home, 0755)
|
||||
stdoutFile, createErr1 := os.Create(filepath.Join(o.Home, "ctrl-stdout.log"))
|
||||
if createErr1 != nil {
|
||||
done <- createErr1
|
||||
}
|
||||
stderrFile, createErr2 := os.Create(filepath.Join(o.Home, "ctrl-stderr.log"))
|
||||
if createErr2 != nil {
|
||||
done <- createErr2
|
||||
}
|
||||
o.extCmd.Stdout = stdoutFile
|
||||
o.extCmd.Stderr = stderrFile
|
||||
|
||||
fmt.Printf("ctrl logs at: %s\n", filepath.Join(o.Home, "ctrl-stdout.log"))
|
||||
if startErr := o.extCmd.Start(); startErr != nil {
|
||||
done <- startErr
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("started ziti quickstart (pid=%d)\n", o.extCmd.Process.Pid)
|
||||
o.trackPid(o.extCmd.Process.Pid)
|
||||
|
||||
go func() {
|
||||
err := o.extCmd.Wait()
|
||||
if errors.Is(err, context.Canceled) || (err != nil && strings.Contains(err.Error(), "signal killed")) {
|
||||
err = nil
|
||||
}
|
||||
done <- err
|
||||
}()
|
||||
}
|
||||
|
||||
func (o *Overlay) CreateAdminIdentity(t *testing.T, now, baseDir string) error {
|
||||
if lr, le := o.Login(); le != nil {
|
||||
return le
|
||||
} else {
|
||||
//set the valid token for reuse later:
|
||||
require.NotEmpty(t, lr)
|
||||
require.NotEmpty(t, lr.ApiSession)
|
||||
require.NotEmpty(t, lr.ApiSession.GetToken())
|
||||
o.ApiSession = lr.ApiSession
|
||||
}
|
||||
adminIdName := fmt.Sprintf("test-admin-%s", now)
|
||||
adminJwtPath := filepath.Join(baseDir, adminIdName+".jwt")
|
||||
zitiCmd := edge.NewCmdEdge(os.Stdout, os.Stderr, common.NewOptionsProvider(os.Stdout, os.Stderr))
|
||||
zitiCmd.SetArgs(strings.Split("create identity "+adminIdName+" -o "+adminJwtPath+" --admin", " "))
|
||||
if zitiCmdErr := zitiCmd.Execute(); zitiCmdErr != nil {
|
||||
t.Fatalf("unable to create identity: %v", zitiCmdErr)
|
||||
}
|
||||
zitiCmd.SetArgs([]string{"enroll", adminJwtPath})
|
||||
if zitiCmdErr := zitiCmd.Execute(); zitiCmdErr != nil {
|
||||
t.Fatalf("unable to create identity: %v", zitiCmdErr)
|
||||
}
|
||||
o.AdminIdFile = strings.TrimSuffix(adminJwtPath, ".jwt") + ".json"
|
||||
t.Logf("identity file should exist at: %v", o.AdminIdFile)
|
||||
|
||||
unwrapCmd := ops.NewUnwrapIdentityFileCommand(os.Stdout, os.Stderr)
|
||||
unwrapCmd.SetArgs([]string{o.AdminIdFile})
|
||||
unwrapCmdErr := unwrapCmd.Execute()
|
||||
if unwrapCmdErr != nil {
|
||||
t.Fatalf("unable to unwrap identity: %v", unwrapCmdErr)
|
||||
}
|
||||
o.AdminCertFile = strings.TrimSuffix(adminJwtPath, ".jwt") + ".cert"
|
||||
o.AdminCaFile = strings.TrimSuffix(adminJwtPath, ".jwt") + ".ca"
|
||||
o.AdminKeyFile = strings.TrimSuffix(adminJwtPath, ".jwt") + ".key"
|
||||
t.Logf("certfile should exist at: %v", o.AdminCertFile)
|
||||
t.Logf("caFile should exist at: %v", o.AdminCaFile)
|
||||
t.Logf("keyFile should exist at: %v", o.AdminKeyFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Overlay) CreateOverlayIdentities(t *testing.T, now string) error {
|
||||
p := common.NewOptionsProvider(os.Stdout, os.Stderr)
|
||||
if _, le := o.Login(); le != nil {
|
||||
return le
|
||||
}
|
||||
|
||||
controllerIdName := fmt.Sprintf("controller-binder-%s", now)
|
||||
controllerJwtPath := filepath.Join(o.Home, controllerIdName+".jwt")
|
||||
zitiCmd1 := edge.NewCmdEdge(os.Stdout, os.Stderr, p)
|
||||
zitiCmd1.SetArgs(strings.Split("create identity "+controllerIdName+" -o "+controllerJwtPath+" --admin -a mgmtservers", " "))
|
||||
if zitiCmdErr := zitiCmd1.Execute(); zitiCmdErr != nil {
|
||||
t.Fatalf("unable to create identity: %v", zitiCmdErr)
|
||||
}
|
||||
zitiCmd1.SetArgs([]string{"enroll", controllerJwtPath})
|
||||
if zitiCmdErr := zitiCmd1.Execute(); zitiCmdErr != nil {
|
||||
t.Fatalf("unable to create identity: %v", zitiCmdErr)
|
||||
}
|
||||
o.NetworkBindingIdFile = strings.TrimSuffix(controllerJwtPath, ".jwt") + ".json"
|
||||
t.Logf("networkBindingIdFile should exist at: %v", o.NetworkBindingIdFile)
|
||||
|
||||
clientIdName := fmt.Sprintf("controller-client-%s", now)
|
||||
clientJwtPath := filepath.Join(o.Home, clientIdName+".jwt")
|
||||
zitiCmd1 = edge.NewCmdEdge(os.Stdout, os.Stderr, p)
|
||||
zitiCmd1.SetArgs(strings.Split("create identity "+clientIdName+" -o "+clientJwtPath+" -a mgmtclients", " "))
|
||||
if zitiCmdErr := zitiCmd1.Execute(); zitiCmdErr != nil {
|
||||
t.Fatalf("unable to create identity: %v", zitiCmdErr)
|
||||
}
|
||||
zitiCmd1.SetArgs([]string{"enroll", clientJwtPath})
|
||||
if zitiCmdErr := zitiCmd1.Execute(); zitiCmdErr != nil {
|
||||
t.Fatalf("unable to create identity: %v", zitiCmdErr)
|
||||
}
|
||||
o.NetworkDialingIdFile = strings.TrimSuffix(clientJwtPath, ".jwt") + ".json"
|
||||
t.Logf("networkDialingIdFile should exist at: %v", o.NetworkDialingIdFile)
|
||||
|
||||
cwd, _ := os.Getwd()
|
||||
yamlToImport, _ := filepath.Abs(cwd + "/login_test_import.yml")
|
||||
v2 := cmd.NewRootCommand(os.Stdin, os.Stdout, os.Stderr)
|
||||
v2.SetArgs(strings.Split("ops import --username "+o.Username+" --password "+o.Password+" "+yamlToImport, " "))
|
||||
if v2Err := v2.Execute(); v2Err != nil {
|
||||
t.Fatalf("unable to import zitified-login-test.yml: %v", v2Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (o *Overlay) Stop() error {
|
||||
if o.extCmd != nil && o.extCmd.Process != nil {
|
||||
_ = o.extCmd.Process.Kill()
|
||||
|
||||
// Poll until process is gone
|
||||
for i := 0; i < 120; i++ { // 60 seconds total
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// Try to find the process - if it fails, process is gone
|
||||
if proc, err := os.FindProcess(o.extCmd.Process.Pid); err != nil || proc == nil {
|
||||
o.closeFileHandles()
|
||||
return nil
|
||||
}
|
||||
|
||||
// On Windows, FindProcess can succeed, send signal 0 to check
|
||||
if err := o.extCmd.Process.Signal(syscall.Signal(0)); err != nil {
|
||||
o.closeFileHandles()
|
||||
return nil // Process is gone
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("overlay %s did not exit after kill", o.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (o *Overlay) Login() (*edge.LoginOptions, error) {
|
||||
initialLogin := &edge.LoginOptions{
|
||||
Options: commonOpts,
|
||||
Username: o.Username,
|
||||
Password: o.Password,
|
||||
ControllerUrl: o.ControllerHostPort(),
|
||||
Yes: true,
|
||||
NetworkId: o.NetworkDialingIdFile,
|
||||
}
|
||||
ile := initialLogin.Run()
|
||||
if ile == nil {
|
||||
util.ReloadConfig() //every login really needs to call reload to flush/overwrite the cached client
|
||||
}
|
||||
return initialLogin, ile
|
||||
}
|
||||
|
||||
func (o *Overlay) closeFileHandles() {
|
||||
if f, ok := o.extCmd.Stdout.(*os.File); ok {
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println("failed to close stdout")
|
||||
}
|
||||
}
|
||||
if f, ok := o.extCmd.Stderr.(*os.File); ok {
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println("failed to close stderr")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Overlay) NewTestLoginOpts() edge.LoginOptions {
|
||||
return edge.LoginOptions{
|
||||
Options: commonOpts,
|
||||
Username: o.Username,
|
||||
Password: o.Password,
|
||||
ControllerUrl: o.ControllerHostPort(),
|
||||
Yes: true,
|
||||
IgnoreConfig: false,
|
||||
NetworkId: o.NetworkDialingIdFile,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Overlay) trackPid(pid int) {
|
||||
o.pidsMutex.Lock()
|
||||
defer o.pidsMutex.Unlock()
|
||||
o.activePids = append(o.activePids, pid)
|
||||
}
|
||||
|
||||
func (o *Overlay) CleanupPids() {
|
||||
o.pidsMutex.Lock()
|
||||
defer o.pidsMutex.Unlock()
|
||||
|
||||
for _, pid := range o.activePids {
|
||||
if !o.isPidRunning(pid) {
|
||||
fmt.Printf("Process (pid=%d) already exited\n", pid)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("Process (pid=%d) still running, killing...\n", pid)
|
||||
proc, _ := os.FindProcess(pid)
|
||||
if err := proc.Kill(); err != nil {
|
||||
fmt.Printf("Failed to kill process (pid=%d): %v\n", pid, err)
|
||||
} else {
|
||||
fmt.Printf("Successfully killed process (pid=%d)\n", pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Overlay) forceKillPids(pids []int) {
|
||||
if runtime.GOOS != "windows" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, pid := range pids {
|
||||
fmt.Printf("Force killing process (pid=%d)...\n", pid)
|
||||
c := exec.Command("taskkill", "/F", "/PID", fmt.Sprintf("%d", pid))
|
||||
_ = c.Run()
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Overlay) EnsureAllPidsStopped(t *testing.T) {
|
||||
running := o.getRunningPids()
|
||||
if len(running) == 0 {
|
||||
fmt.Printf("All processes stopped\n")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Processes still running: %v, waiting 10o...\n", running)
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
running = o.getRunningPids()
|
||||
if len(running) > 0 {
|
||||
fmt.Printf("Force killing remaining processes: %v\n", running)
|
||||
o.forceKillPids(running)
|
||||
|
||||
// Poll for up to 60s for processes to exit
|
||||
start := time.Now()
|
||||
for time.Since(start) < 60*time.Second {
|
||||
running = o.getRunningPids()
|
||||
if len(running) == 0 {
|
||||
fmt.Printf("All processes stopped after force kill\n")
|
||||
return
|
||||
}
|
||||
fmt.Printf("Processes still running after %.0fs: %v\n", time.Since(start).Seconds(), running)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
running = o.getRunningPids()
|
||||
if len(running) > 0 {
|
||||
t.Fatalf("Processes still running after 60s wait: %v", running)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("All processes stopped\n")
|
||||
}
|
||||
|
||||
func (o *Overlay) isPidRunning(pid int) bool {
|
||||
if pid <= 0 {
|
||||
return false
|
||||
}
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
err = proc.Signal(syscall.Signal(0))
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if err.Error() == "os: process already finished" {
|
||||
return false
|
||||
}
|
||||
errno, ok := err.(syscall.Errno)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
switch errno {
|
||||
case syscall.ESRCH:
|
||||
return false
|
||||
case syscall.EPERM:
|
||||
return true
|
||||
default:
|
||||
// ignored
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *Overlay) getRunningPidsWindows(pids []int) []int {
|
||||
if len(pids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
args := []string{"/NH"}
|
||||
for _, pid := range pids {
|
||||
args = append(args, "/FI", fmt.Sprintf("PID eq %d", pid))
|
||||
}
|
||||
|
||||
c := exec.Command("tasklist", args...)
|
||||
output, err := c.Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var running []int
|
||||
outputStr := string(output)
|
||||
for _, pid := range pids {
|
||||
if strings.Contains(outputStr, fmt.Sprintf("%d", pid)) {
|
||||
running = append(running, pid)
|
||||
}
|
||||
}
|
||||
return running
|
||||
}
|
||||
|
||||
func (o *Overlay) getRunningPids() []int {
|
||||
o.pidsMutex.Lock()
|
||||
defer o.pidsMutex.Unlock()
|
||||
|
||||
if len(o.activePids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
return o.getRunningPidsWindows(o.activePids)
|
||||
}
|
||||
|
||||
var running []int
|
||||
for _, pid := range o.activePids {
|
||||
if o.isPidRunning(pid) {
|
||||
running = append(running, pid)
|
||||
}
|
||||
}
|
||||
return running
|
||||
}
|
||||
|
||||
func (o *Overlay) WaitForControllerReady(timeout time.Duration) error {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
fmt.Println("Waiting for controller to start... " + o.ControllerHostPort())
|
||||
_, err := rest_util.GetControllerWellKnownCas(o.ControllerHostPort())
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
case <-time.After(timeout):
|
||||
return fmt.Errorf("timeout waiting for controller to become ready at %s", o.ControllerHostPort())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Overlay) WaitForRouterReady(timeout time.Duration) error {
|
||||
if !o.Routerless {
|
||||
routerReady := make(chan error)
|
||||
o.WaitForRouter(timeout, routerReady)
|
||||
|
||||
select {
|
||||
case err := <-routerReady:
|
||||
return err
|
||||
case <-time.After(10 * time.Second):
|
||||
return fmt.Errorf("timeout waiting for router to be ready at: %s", o.RouterHostPort())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateOverlay(t *testing.T, ctx context.Context, startTimeout time.Duration, home string, name string, ha bool) Overlay {
|
||||
o := Overlay{
|
||||
Name: name,
|
||||
t: t,
|
||||
Ctx: ctx,
|
||||
StartTimeout: startTimeout,
|
||||
cmdDone: make(chan error, 1),
|
||||
QuickstartOpts: &run.QuickstartOpts{
|
||||
Home: gopath.Join(home, name),
|
||||
ControllerAddress: "localhost", //helpers.GetCtrlAdvertisedAddress(),
|
||||
ControllerPort: findAvailablePort(t),
|
||||
RouterAddress: "localhost", //helpers.GetRouterAdvertisedAddress(),
|
||||
RouterPort: findAvailablePort(t),
|
||||
Routerless: false,
|
||||
TrustDomain: name,
|
||||
InstanceID: name,
|
||||
IsHA: ha,
|
||||
Username: "admin",
|
||||
Password: "admin",
|
||||
ConfigureAndExit: false,
|
||||
},
|
||||
pidsMutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func findAvailablePort(t *testing.T) uint16 {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = listener.Close() }()
|
||||
return (uint16)(listener.Addr().(*net.TCPAddr).Port)
|
||||
}
|
||||
@@ -20,6 +20,11 @@ import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/judedaryl/go-arrayutils"
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
@@ -33,10 +38,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var log = pfxlog.Logger()
|
||||
@@ -75,7 +76,7 @@ func NewExportCmd(out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
Long: "Export all or comma separated list of selected entities.\n" +
|
||||
"Valid entities are: [all|ca/certificate-authority|identity|edge-router|service|config|config-type|service-policy|edge-router-policy|service-edge-router-policy|external-jwt-signer|auth-policy|posture-check] (default all)",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
logLvl := logrus.InfoLevel
|
||||
if loginOpts.Verbose {
|
||||
@@ -97,11 +98,12 @@ func NewExportCmd(out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
log.Fatalf("Invalid output format: %s", outputFormat)
|
||||
}
|
||||
|
||||
client, err := loginOpts.NewMgmtClient()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
mgmtClient, mgmtClientErr := loginOpts.NewManagementClient(true)
|
||||
if mgmtClientErr != nil {
|
||||
log.WithError(mgmtClientErr).Error("Error creating management client")
|
||||
return mgmtClientErr
|
||||
}
|
||||
exporter.Client = client
|
||||
exporter.Client = mgmtClient.BaseClient.API.ZitiEdgeManagement
|
||||
|
||||
var entities []string
|
||||
if len(args) > 0 {
|
||||
@@ -158,7 +160,7 @@ func NewExportCmd(out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
WithField("bytes", bytes).
|
||||
Debug("Wrote data")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
@@ -19,6 +19,12 @@ package importer
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/judedaryl/go-arrayutils"
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
@@ -32,10 +38,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io"
|
||||
"maps"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var log = pfxlog.Logger()
|
||||
@@ -94,7 +96,7 @@ func NewImportCmd(out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
filename: args[0],
|
||||
}.read()
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("error reading file")
|
||||
return fmt.Errorf("error reading file: %v", err)
|
||||
}
|
||||
|
||||
data := map[string][]interface{}{}
|
||||
@@ -111,11 +113,11 @@ func NewImportCmd(out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
}
|
||||
importer.Data = data
|
||||
|
||||
client, err := loginOpts.NewMgmtClient()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
mgmtClient, mgmtClientErr := loginOpts.NewManagementClient(true)
|
||||
if mgmtClientErr != nil {
|
||||
return mgmtClientErr
|
||||
}
|
||||
importer.Client = client
|
||||
importer.Client = mgmtClient.BaseClient.API.ZitiEdgeManagement
|
||||
|
||||
var entities []string
|
||||
if len(args) > 1 {
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
|
||||
"github.com/openziti/channel/v4"
|
||||
foundation "github.com/openziti/transport/v2"
|
||||
fabXweb "github.com/openziti/xweb/v2"
|
||||
fabXweb "github.com/openziti/xweb/v3"
|
||||
edge "github.com/openziti/ziti/controller/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
+506
-182
@@ -17,24 +17,11 @@
|
||||
package edge
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/Jeffail/gabs"
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/edge-api/rest_client_api_client"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
"github.com/openziti/edge-api/rest_util"
|
||||
"github.com/openziti/foundation/v2/term"
|
||||
edge_apis "github.com/openziti/sdk-golang/edge-apis"
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
ziticobra "github.com/openziti/ziti/internal/cobra"
|
||||
"github.com/openziti/ziti/ziti/cmd/api"
|
||||
"github.com/openziti/ziti/ziti/cmd/common"
|
||||
cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers"
|
||||
"github.com/openziti/ziti/ziti/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -42,6 +29,23 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Jeffail/gabs"
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/edge-api/rest_client_api_client"
|
||||
"github.com/openziti/edge-api/rest_util"
|
||||
"github.com/openziti/foundation/v2/term"
|
||||
edge_apis "github.com/openziti/sdk-golang/edge-apis"
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
ziticobra "github.com/openziti/ziti/internal/cobra"
|
||||
"github.com/openziti/ziti/internal/jwtutil"
|
||||
"github.com/openziti/ziti/ziti/cmd/api"
|
||||
"github.com/openziti/ziti/ziti/cmd/common"
|
||||
"github.com/openziti/ziti/ziti/constants"
|
||||
"github.com/openziti/ziti/ziti/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
xterm "golang.org/x/term"
|
||||
)
|
||||
|
||||
// LoginOptions are the flags for login commands
|
||||
@@ -60,8 +64,24 @@ type LoginOptions struct {
|
||||
ExtJwtToken string
|
||||
File string
|
||||
ControllerUrl string
|
||||
|
||||
ServiceName string
|
||||
NetworkId string
|
||||
FileCertCreds *edge_apis.IdentityCredentials
|
||||
ApiSession edge_apis.ApiSession
|
||||
TotpCallback func(strings chan string)
|
||||
|
||||
client http.Client
|
||||
transport *http.Transport
|
||||
caPool *x509.CertPool
|
||||
cachedId *util.RestClientEdgeIdentity
|
||||
mgmtClient *edge_apis.ManagementApiClient
|
||||
}
|
||||
|
||||
func (options *LoginOptions) GetClient() http.Client {
|
||||
return options.client
|
||||
}
|
||||
func (options *LoginOptions) SetClient(c http.Client) {
|
||||
options.client = c
|
||||
}
|
||||
|
||||
const LoginFlagKey = "login"
|
||||
@@ -96,6 +116,10 @@ func AddLoginFlags(cmd *cobra.Command, options *LoginOptions) {
|
||||
addLoginAnnotation(cmd, "ext-jwt")
|
||||
cmd.Flags().StringVarP(&options.File, "file", "f", "", "An identity file to use for authentication")
|
||||
addLoginAnnotation(cmd, "file")
|
||||
cmd.Flags().StringVarP(&options.ServiceName, "service", "s", "", "The service name to use. When set the file will be used to create a zitified connection")
|
||||
addLoginAnnotation(cmd, "service")
|
||||
cmd.Flags().StringVarP(&options.NetworkId, "network-identity", "n", "", "The identity to use to connect to the OpenZiti overlay")
|
||||
addLoginAnnotation(cmd, "network-identity")
|
||||
|
||||
options.AddCommonFlags(cmd)
|
||||
}
|
||||
@@ -113,7 +137,7 @@ func NewLoginCmd(out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
Short: "logs into a Ziti Edge Controller instance",
|
||||
Long: `login allows the ziti command to establish a session with a Ziti Edge Controller, allowing more commands to be run against the controller.`,
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.Cmd = cmd
|
||||
if len(args) > 0 {
|
||||
options.ControllerUrl = args[0]
|
||||
@@ -122,11 +146,11 @@ func NewLoginCmd(out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
if options.extJwtFile != "" {
|
||||
auth, err := os.ReadFile(options.extJwtFile)
|
||||
if err != nil {
|
||||
pfxlog.Logger().Fatal(err)
|
||||
return err
|
||||
}
|
||||
options.ExtJwtToken = string(auth)
|
||||
}
|
||||
cmdhelper.CheckErr(options.Run())
|
||||
return options.Run()
|
||||
},
|
||||
SuggestFor: []string{},
|
||||
}
|
||||
@@ -136,59 +160,39 @@ func NewLoginCmd(out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *LoginOptions) newHttpClient() *http.Client {
|
||||
func (o *LoginOptions) newHttpClient(tryCachedCreds bool) (http.Client, error) {
|
||||
if o.ControllerUrl != "" && o.Args == nil || len(o.Args) < 1 {
|
||||
o.Args = []string{o.ControllerUrl}
|
||||
}
|
||||
|
||||
// any error indicates there are probably no saved credentials. look for login information and use those
|
||||
loginErr := o.Run()
|
||||
if loginErr != nil {
|
||||
pfxlog.Logger().Fatal(loginErr)
|
||||
}
|
||||
|
||||
caPool := x509.NewCertPool()
|
||||
if o.CaCert != "" {
|
||||
if _, cacertErr := os.Stat(o.CaCert); cacertErr == nil {
|
||||
rootPemData, err := os.ReadFile(o.CaCert)
|
||||
if err != nil {
|
||||
pfxlog.Logger().Fatalf("error reading CA cert [%s]", o.CaCert)
|
||||
}
|
||||
caPool.AppendCertsFromPEM(rootPemData)
|
||||
} else {
|
||||
pfxlog.Logger().Warnf("CA cert not found [%s]", o.CaCert)
|
||||
if tryCachedCreds {
|
||||
// any error indicates there are probably no saved credentials. look for login information and use those
|
||||
cached := *o
|
||||
cached.PopulateFromCache()
|
||||
cached.IgnoreConfig = true // don't overwrite when trying to login
|
||||
loginErr := cached.Run()
|
||||
if loginErr != nil {
|
||||
return http.Client{}, loginErr
|
||||
}
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: caPool,
|
||||
},
|
||||
},
|
||||
t, cte := o.createHttpTransport()
|
||||
if cte != nil {
|
||||
return http.Client{}, cte
|
||||
}
|
||||
c := http.Client{
|
||||
Transport: t,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewClientApiClient returns a new management client for use with the controller using the set of login material provided
|
||||
func (o *LoginOptions) NewClientApiClient() (*rest_client_api_client.ZitiEdgeClient, error) {
|
||||
httpClient := o.newHttpClient()
|
||||
|
||||
c, e := rest_util.NewEdgeClientClientWithToken(httpClient, o.ControllerUrl, o.Token)
|
||||
if e != nil {
|
||||
pfxlog.Logger().Fatal(e)
|
||||
nc, newClientErr := o.newHttpClient(true)
|
||||
if newClientErr != nil {
|
||||
return nil, newClientErr
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewMgmtClient returns a new management client for use with the controller using the set of login material provided
|
||||
func (o *LoginOptions) NewMgmtClient() (*rest_management_api_client.ZitiEdgeManagement, error) {
|
||||
httpClient := o.newHttpClient()
|
||||
|
||||
c, e := rest_util.NewEdgeManagementClientWithToken(httpClient, o.ControllerUrl, o.Token)
|
||||
if e != nil {
|
||||
pfxlog.Logger().Fatal(e)
|
||||
}
|
||||
return c, nil
|
||||
return rest_util.NewEdgeClientClientWithToken(&nc, o.ControllerUrl, o.Token)
|
||||
}
|
||||
|
||||
// Run implements this command
|
||||
@@ -200,17 +204,21 @@ func (o *LoginOptions) Run() error {
|
||||
return cfgErr
|
||||
}
|
||||
|
||||
httpClient, newClientErr := o.newHttpClient(false)
|
||||
if newClientErr != nil {
|
||||
return newClientErr
|
||||
}
|
||||
o.client = httpClient
|
||||
|
||||
if o.File != "" {
|
||||
cfg, err := ziti.NewConfigFromFile(o.File)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read file %s: %w", o.File, err)
|
||||
}
|
||||
|
||||
if !o.IgnoreConfig {
|
||||
idCredentials := edge_apis.NewIdentityCredentialsFromConfig(cfg.ID)
|
||||
o.FileCertCreds = idCredentials
|
||||
}
|
||||
idCredentials := edge_apis.NewIdentityCredentialsFromConfig(cfg.ID)
|
||||
o.FileCertCreds = idCredentials
|
||||
|
||||
ztAPI := cfg.ZtAPI
|
||||
|
||||
// override with the first HA client API URL if defined
|
||||
@@ -223,15 +231,19 @@ func (o *LoginOptions) Run() error {
|
||||
return fmt.Errorf("could not parse ztAPI '%s' as a URL", ztAPI)
|
||||
}
|
||||
|
||||
host = parsedZtAPI.Host
|
||||
if o.ControllerUrl == "" {
|
||||
host = parsedZtAPI.Host
|
||||
} else {
|
||||
host = o.ControllerUrl
|
||||
}
|
||||
}
|
||||
|
||||
id := config.GetIdentity()
|
||||
|
||||
if host == "" {
|
||||
if o.ControllerUrl == "" {
|
||||
if defaultId := config.EdgeIdentities[id]; defaultId != nil && !o.IgnoreConfig {
|
||||
host = defaultId.Url
|
||||
if strings.TrimPrefix(o.ControllerUrl, "https://") == "" {
|
||||
if cachedCliConfig := config.EdgeIdentities[id]; cachedCliConfig != nil && !o.IgnoreConfig {
|
||||
host = cachedCliConfig.Url
|
||||
o.Printf("Using controller url: %v from identity '%v' in config file: %v\n", host, id, configFile)
|
||||
} else {
|
||||
var err error
|
||||
@@ -247,16 +259,16 @@ func (o *LoginOptions) Run() error {
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(host, "http") {
|
||||
host = "https://" + host
|
||||
}
|
||||
host = addHttpsIfNeeded(host)
|
||||
|
||||
ctrlUrl, urlParseErr := url.Parse(host)
|
||||
if urlParseErr != nil {
|
||||
return errors.Wrap(urlParseErr, "invalid controller URL")
|
||||
return errors.New("invalid controller URL supplied")
|
||||
}
|
||||
|
||||
host = ctrlUrl.Scheme + "://" + ctrlUrl.Host
|
||||
if ctrlUrl.Host == "" {
|
||||
return errors.New("invalid controller URL supplied")
|
||||
}
|
||||
|
||||
if err := o.ConfigureCerts(host, ctrlUrl); err != nil {
|
||||
return err
|
||||
@@ -270,12 +282,12 @@ func (o *LoginOptions) Run() error {
|
||||
|
||||
if ctrlUrl.Path == "" {
|
||||
if o.FileCertCreds != nil && o.FileCertCreds.CaPool != nil {
|
||||
host = util.EdgeControllerGetManagementApiBasePathWithPool(host, o.FileCertCreds.CaPool)
|
||||
host = util.EdgeControllerGetManagementApiBasePathWithPool(host, o.FileCertCreds.CaPool, &httpClient)
|
||||
} else {
|
||||
host = util.EdgeControllerGetManagementApiBasePath(host, o.CaCert)
|
||||
host = util.EdgeControllerGetManagementApiBasePath(host, o.CaCert, &httpClient)
|
||||
}
|
||||
} else {
|
||||
host = host + ctrlUrl.Path
|
||||
hostUrl, _ := url.Parse(host)
|
||||
o.ControllerUrl = o.ControllerUrl + hostUrl.Path
|
||||
}
|
||||
|
||||
if o.Token != "" && o.Cmd != nil && !o.Cmd.Flag("read-only").Changed {
|
||||
@@ -283,86 +295,115 @@ func (o *LoginOptions) Run() error {
|
||||
o.Println("NOTE: When using --token the saved identity will be marked as read-only unless --read-only=false is provided")
|
||||
}
|
||||
|
||||
body := "{}"
|
||||
if o.Token == "" && o.ClientCert == "" && o.ExtJwtToken == "" && o.FileCertCreds == nil {
|
||||
dontHaveApiSession := !(o.ApiSession != nil && len(o.ApiSession.GetToken()) != 0)
|
||||
if dontHaveApiSession && o.Token == "" && o.ClientCert == "" && o.ExtJwtToken == "" && o.FileCertCreds == nil {
|
||||
for o.Username == "" {
|
||||
var err error
|
||||
if defaultId := config.EdgeIdentities[id]; defaultId != nil && defaultId.Username != "" && !o.IgnoreConfig {
|
||||
o.Username = defaultId.Username
|
||||
o.Printf("Using username: %v from identity '%v' in config file: %v\n", o.Username, id, configFile)
|
||||
} else if o.Username, err = term.Prompt("Enter username: "); err != nil {
|
||||
return err
|
||||
if xterm.IsTerminal(int(os.Stdin.Fd())) {
|
||||
var err error
|
||||
if cachedCliConfig := config.EdgeIdentities[id]; cachedCliConfig != nil && cachedCliConfig.Username != "" && !o.IgnoreConfig {
|
||||
o.Username = cachedCliConfig.Username
|
||||
o.Printf("Using username: %v from identity '%v' in config file: %v\n", o.Username, id, configFile)
|
||||
} else if o.Username, err = term.Prompt("Enter username: "); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("username required but not provided")
|
||||
}
|
||||
}
|
||||
|
||||
if o.Password == "" {
|
||||
var err error
|
||||
if o.Password, err = term.PromptPassword("Enter password: ", false); err != nil {
|
||||
return err
|
||||
if xterm.IsTerminal(int(os.Stdin.Fd())) {
|
||||
if o.Password, err = term.PromptPassword("Enter password: ", false); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("password required but not provided")
|
||||
}
|
||||
}
|
||||
|
||||
container := gabs.New()
|
||||
_, _ = container.SetP(o.Username, "username")
|
||||
_, _ = container.SetP(o.Password, "password")
|
||||
|
||||
body = container.String()
|
||||
}
|
||||
|
||||
if o.Token == "" {
|
||||
jsonParsed, err := login(o, host, body)
|
||||
caPool, caPoolErr := o.GetCaPool()
|
||||
if caPoolErr != nil {
|
||||
return caPoolErr
|
||||
} else {
|
||||
o.caPool = caPool
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !jsonParsed.ExistsP("data.token") {
|
||||
return fmt.Errorf("no session token returned from login request to %v. Received: %v", host, jsonParsed.String())
|
||||
}
|
||||
|
||||
var ok bool
|
||||
o.Token, ok = jsonParsed.Path("data.token").Data().(string)
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("session token returned from login request to %v is not in the expected format. Received: %v", host, jsonParsed.String())
|
||||
}
|
||||
|
||||
if !o.OutputJSONResponse {
|
||||
o.Printf("Token: %v\n", o.Token)
|
||||
t, e := o.createHttpTransport()
|
||||
if e != nil {
|
||||
return e
|
||||
} else {
|
||||
o.transport = t
|
||||
nc, ncErr := o.newHttpClient(false)
|
||||
if ncErr != nil {
|
||||
return ncErr
|
||||
}
|
||||
o.client = nc
|
||||
}
|
||||
|
||||
o.ControllerUrl = host
|
||||
|
||||
if o.Token == "" || dontHaveApiSession {
|
||||
// if no token or api session, need to log in
|
||||
adminClient, newMgmtClientErr := o.NewManagementClient(false)
|
||||
if newMgmtClientErr != nil {
|
||||
return newMgmtClientErr
|
||||
}
|
||||
s, le := o.Login()
|
||||
if le != nil {
|
||||
return le
|
||||
}
|
||||
if s == nil {
|
||||
return fmt.Errorf("failed to login")
|
||||
} else {
|
||||
o.ApiSession = s
|
||||
}
|
||||
o.mgmtClient = adminClient
|
||||
}
|
||||
|
||||
var sess *edge_apis.ApiSessionJsonWrapper
|
||||
if o.ApiSession != nil {
|
||||
sess = &edge_apis.ApiSessionJsonWrapper{
|
||||
ApiSession: o.ApiSession,
|
||||
}
|
||||
}
|
||||
if !o.IgnoreConfig {
|
||||
loginIdentity := &util.RestClientEdgeIdentity{
|
||||
Url: host,
|
||||
Username: o.Username,
|
||||
Token: o.Token,
|
||||
LoginTime: time.Now().Format(time.RFC3339),
|
||||
CaCert: o.CaCert,
|
||||
ReadOnly: o.ReadOnly,
|
||||
Url: o.ControllerUrl,
|
||||
Username: o.Username,
|
||||
Token: "", // --use-api-session--
|
||||
LoginTime: time.Now().Format(time.RFC3339),
|
||||
CaCert: o.CaCert,
|
||||
ReadOnly: o.ReadOnly,
|
||||
NetworkIdFile: o.NetworkId,
|
||||
ApiSession: sess,
|
||||
}
|
||||
o.Printf("Saving identity '%v' to %v\n", id, configFile)
|
||||
config.EdgeIdentities[id] = loginIdentity
|
||||
|
||||
return util.PersistRestClientConfig(config)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LoginOptions) ConfigureCerts(host string, ctrlUrl *url.URL) error {
|
||||
isServerTrusted, err := util.IsServerTrusted(host)
|
||||
httpClient := o.GetClient()
|
||||
isServerTrusted, err := util.IsServerTrusted(host, &httpClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isServerTrusted && o.CaCert == "" {
|
||||
wellKnownCerts, certs, err := util.GetWellKnownCerts(host)
|
||||
wellKnownCerts, certs, err := util.GetWellKnownCerts(host, httpClient)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to retrieve server certificate authority from %v", host)
|
||||
}
|
||||
|
||||
certsTrusted, err := util.AreCertsTrusted(host, wellKnownCerts)
|
||||
certsTrusted, err := util.AreCertsTrusted(host, wellKnownCerts, httpClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -370,7 +411,7 @@ func (o *LoginOptions) ConfigureCerts(host string, ctrlUrl *url.URL) error {
|
||||
return errors.New("server supplied certs not trusted by server, unable to continue")
|
||||
}
|
||||
|
||||
savedCerts, certFile, err := util.ReadCert(ctrlUrl.Hostname())
|
||||
savedCerts, certFile, err := util.ReadCert(ctrlUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -386,7 +427,7 @@ func (o *LoginOptions) ConfigureCerts(host string, ctrlUrl *url.URL) error {
|
||||
}
|
||||
}
|
||||
if replace {
|
||||
_, err = util.WriteCert(o, ctrlUrl.Hostname(), wellKnownCerts)
|
||||
_, err = util.WriteCert(o, ctrlUrl, wellKnownCerts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -403,7 +444,7 @@ func (o *LoginOptions) ConfigureCerts(host string, ctrlUrl *url.URL) error {
|
||||
}
|
||||
}
|
||||
if importCerts {
|
||||
o.CaCert, err = util.WriteCert(o, ctrlUrl.Hostname(), wellKnownCerts)
|
||||
o.CaCert, err = util.WriteCert(o, ctrlUrl, wellKnownCerts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -411,20 +452,15 @@ func (o *LoginOptions) ConfigureCerts(host string, ctrlUrl *url.URL) error {
|
||||
o.Println("WARNING: no certificate authority provided for server, continuing but login will likely fail")
|
||||
}
|
||||
}
|
||||
} else if isServerTrusted && o.CaCert != "" {
|
||||
override, err := o.askYesNo("Server certificate authority is already trusted. Are you sure you want to provide an additional CA [Y/N]: ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !override {
|
||||
o.CaCert = ""
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LoginOptions) askYesNo(prompt string) (bool, error) {
|
||||
if o.Yes {
|
||||
return true, nil
|
||||
}
|
||||
filter := &yesNoFilter{}
|
||||
if _, err := o.ask(prompt, filter.Accept); err != nil {
|
||||
return false, err
|
||||
@@ -433,6 +469,14 @@ func (o *LoginOptions) askYesNo(prompt string) (bool, error) {
|
||||
}
|
||||
|
||||
func (o *LoginOptions) ask(prompt string, f func(string) bool) (string, error) {
|
||||
if o.Yes {
|
||||
return "yes", nil
|
||||
}
|
||||
|
||||
if !xterm.IsTerminal(int(os.Stdin.Fd())) {
|
||||
return "", errors.New("Cannot accept certs - no terminal")
|
||||
}
|
||||
|
||||
for {
|
||||
val, err := term.Prompt(prompt)
|
||||
if err != nil {
|
||||
@@ -464,63 +508,343 @@ func (self *yesNoFilter) Accept(s string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// EdgeControllerLogin will authenticate to the given Edge Controller
|
||||
func login(o *LoginOptions, url string, authentication string) (*gabs.Container, error) {
|
||||
client := util.NewClient()
|
||||
cert := o.CaCert
|
||||
out := o.Out
|
||||
logJSON := o.OutputJSONResponse
|
||||
timeout := o.Timeout
|
||||
verbose := o.Verbose
|
||||
method := "password"
|
||||
if cert != "" {
|
||||
client.SetRootCertificate(cert)
|
||||
func (o *LoginOptions) terminatorId() string {
|
||||
o.ControllerUrl = addHttpsIfNeeded(o.ControllerUrl)
|
||||
curl, curle := url.Parse(o.ControllerUrl)
|
||||
if curle != nil {
|
||||
o.Printf("unable to parse controller url [%s]\n", o.ControllerUrl)
|
||||
return ""
|
||||
}
|
||||
authHeader := ""
|
||||
if o.ExtJwtToken != "" {
|
||||
method = "ext-jwt"
|
||||
authHeader = "Bearer " + strings.TrimSpace(o.ExtJwtToken)
|
||||
client.SetHeader("Authorization", authHeader)
|
||||
return curl.User.Username()
|
||||
}
|
||||
|
||||
func (o *LoginOptions) createHttpTransport() (*http.Transport, error) {
|
||||
// if cli param supplied - use it first
|
||||
if o.NetworkId != "" {
|
||||
t, e := util.NewZitifiedTransportFromFile(o.NetworkId, o.terminatorId())
|
||||
o.transport = t
|
||||
return t, e
|
||||
}
|
||||
|
||||
// if env var set - use it
|
||||
if zt, zte := util.ZitifiedTransportFromEnv(o.terminatorId()); zte != nil {
|
||||
o.Printf("NetworkId found by env var [%s] but failed: %v\n", constants.ZitiCliNetworkIdVarName, zte)
|
||||
return nil, zte
|
||||
} else {
|
||||
if o.ClientCert != "" {
|
||||
clientCert, err := tls.LoadX509KeyPair(o.ClientCert, o.ClientKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load client certificate: %s with key %s: %v", o.ClientCert, o.ClientKey, err)
|
||||
}
|
||||
client.SetCertificates(clientCert)
|
||||
method = "cert"
|
||||
} else if o.FileCertCreds != nil {
|
||||
tlsCert := o.FileCertCreds.TlsCerts()[0]
|
||||
client.SetCertificates(tlsCert)
|
||||
method = "cert"
|
||||
if zt != nil {
|
||||
o.Printf("NetworkId found by env var [%s], zitified transport enabled\n", constants.ZitiCliNetworkIdVarName)
|
||||
o.NetworkId = ""
|
||||
o.transport = zt
|
||||
return zt, nil
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := client.
|
||||
SetTimeout(time.Duration(timeout)*time.Second).
|
||||
SetDebug(verbose).
|
||||
R().
|
||||
SetQueryParam("method", method).
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(authentication).
|
||||
Post(url + "/authenticate")
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to authenticate to %v. Error: %v", url, err)
|
||||
caPool, caErr := o.GetCaPool()
|
||||
if caErr != nil {
|
||||
return nil, caErr
|
||||
}
|
||||
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return nil, fmt.Errorf("unable to authenticate to %v. Status code: %v, Server returned: %v", url, resp.Status(), util.PrettyPrintResponse(resp))
|
||||
}
|
||||
|
||||
if logJSON {
|
||||
util.OutputJson(out, resp.Body())
|
||||
}
|
||||
|
||||
jsonParsed, err := gabs.ParseJSON(resp.Body())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse response from %v. Server returned: %v", url, resp.String())
|
||||
}
|
||||
|
||||
return jsonParsed, nil
|
||||
return &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: caPool,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *LoginOptions) PopulateFromCache() {
|
||||
config, _, cfgErr := util.LoadRestClientConfig()
|
||||
if cfgErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id := config.GetIdentity()
|
||||
cachedCliConfig := config.EdgeIdentities[id]
|
||||
if cachedCliConfig == nil {
|
||||
return
|
||||
}
|
||||
if o.ControllerUrl == "" {
|
||||
o.ControllerUrl = cachedCliConfig.Url
|
||||
}
|
||||
o.ControllerUrl = addHttpsIfNeeded(o.ControllerUrl)
|
||||
if o.Username == "" {
|
||||
o.Username = cachedCliConfig.Username
|
||||
}
|
||||
if o.Token == "" {
|
||||
o.Token = cachedCliConfig.Token
|
||||
}
|
||||
if o.NetworkId == "" {
|
||||
o.NetworkId = cachedCliConfig.NetworkIdFile
|
||||
}
|
||||
if o.CaCert == "" {
|
||||
o.CaCert = cachedCliConfig.CaCert
|
||||
}
|
||||
if o.ApiSession == nil {
|
||||
if cachedCliConfig.ApiSession != nil {
|
||||
o.ApiSession = cachedCliConfig.ApiSession.ApiSession
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewFromCache(out io.Writer, eout io.Writer) LoginOptions {
|
||||
o := LoginOptions{
|
||||
Options: api.Options{
|
||||
CommonOptions: common.CommonOptions{
|
||||
Out: out,
|
||||
Err: eout,
|
||||
},
|
||||
},
|
||||
}
|
||||
o.PopulateFromCache()
|
||||
return o
|
||||
}
|
||||
|
||||
func TryCachedCredsLogin(out io.Writer, eout io.Writer) (LoginOptions, error) {
|
||||
o := NewFromCache(out, eout)
|
||||
err := o.Run()
|
||||
if err != nil {
|
||||
return o, err
|
||||
} else {
|
||||
return o, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LoginOptions) GetCaPool() (*x509.CertPool, error) {
|
||||
caPool := x509.NewCertPool()
|
||||
if o.CaCert != "" {
|
||||
if _, cacertErr := os.Stat(o.CaCert); cacertErr == nil {
|
||||
rootPemData, err := os.ReadFile(o.CaCert)
|
||||
if err != nil {
|
||||
pfxlog.Logger().Fatalf("error reading CA cert [%s]", o.CaCert)
|
||||
}
|
||||
caPool.AppendCertsFromPEM(rootPemData)
|
||||
} else {
|
||||
pfxlog.Logger().Warnf("CA cert not found [%s]", o.CaCert)
|
||||
}
|
||||
}
|
||||
return caPool, nil
|
||||
}
|
||||
|
||||
func (o *LoginOptions) MergeUnsetFrom(cached LoginOptions) {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if o.Username == "" {
|
||||
o.Username = cached.Username
|
||||
}
|
||||
if o.Password == "" {
|
||||
o.Password = cached.Password
|
||||
}
|
||||
if o.Token == "" {
|
||||
o.Token = cached.Token
|
||||
}
|
||||
if o.CaCert == "" {
|
||||
o.CaCert = cached.CaCert
|
||||
}
|
||||
if o.ClientCert == "" {
|
||||
o.ClientCert = cached.ClientCert
|
||||
}
|
||||
if o.ClientKey == "" {
|
||||
o.ClientKey = cached.ClientKey
|
||||
}
|
||||
if o.ServiceName == "" {
|
||||
o.ServiceName = cached.ServiceName
|
||||
}
|
||||
if o.extJwtFile == "" {
|
||||
o.extJwtFile = cached.extJwtFile
|
||||
}
|
||||
if o.ExtJwtToken == "" {
|
||||
o.ExtJwtToken = cached.ExtJwtToken
|
||||
}
|
||||
if o.File == "" {
|
||||
o.File = cached.File
|
||||
}
|
||||
if o.ControllerUrl == "" {
|
||||
o.ControllerUrl = cached.ControllerUrl
|
||||
}
|
||||
if o.NetworkId == "" {
|
||||
o.NetworkId = cached.NetworkId
|
||||
}
|
||||
if o.FileCertCreds == nil {
|
||||
o.FileCertCreds = cached.FileCertCreds
|
||||
}
|
||||
if o.ApiSession == nil {
|
||||
o.ApiSession = cached.ApiSession
|
||||
}
|
||||
if o.transport == nil {
|
||||
o.transport = cached.transport
|
||||
}
|
||||
if o.caPool == nil {
|
||||
o.caPool = cached.caPool
|
||||
}
|
||||
if o.cachedId == nil {
|
||||
o.cachedId = cached.cachedId
|
||||
}
|
||||
if o.mgmtClient == nil {
|
||||
o.mgmtClient = cached.mgmtClient
|
||||
}
|
||||
if o.TotpCallback == nil {
|
||||
o.TotpCallback = cached.TotpCallback
|
||||
}
|
||||
o.client = cached.client
|
||||
}
|
||||
|
||||
func (o *LoginOptions) NewManagementClient(useCachedCreds bool) (*edge_apis.ManagementApiClient, error) {
|
||||
if useCachedCreds {
|
||||
o.PopulateFromCache()
|
||||
// any error indicates there are probably no saved credentials. look for login information and use those
|
||||
cached := *o
|
||||
cached.IgnoreConfig = true // don't overwrite when trying to login
|
||||
loginErr := cached.Run()
|
||||
if loginErr != nil {
|
||||
return nil, loginErr
|
||||
}
|
||||
o.MergeUnsetFrom(cached)
|
||||
_, sessErr := o.mgmtClient.AuthenticateWithPreviousSession(&edge_apis.EmptyCredentials{}, o.ApiSession)
|
||||
if sessErr != nil {
|
||||
return nil, sessErr
|
||||
}
|
||||
return o.mgmtClient, nil
|
||||
}
|
||||
|
||||
o.ControllerUrl = addHttpsIfNeeded(o.ControllerUrl)
|
||||
ctrlUrl, _ := url.Parse(o.ControllerUrl)
|
||||
if ctrlUrl.Path == "" {
|
||||
resolvedUrl := util.EdgeControllerGetManagementApiBasePath(o.ControllerUrl, o.CaCert, &o.client)
|
||||
hostUrl, _ := url.Parse(resolvedUrl)
|
||||
o.ControllerUrl = o.ControllerUrl + hostUrl.Path
|
||||
}
|
||||
transport := &edge_apis.TlsAwareHttpTransport{Transport: o.transport}
|
||||
o.client.Transport = transport
|
||||
|
||||
o.mgmtClient = edge_apis.NewManagementApiClientWithConfig(&edge_apis.ApiClientConfig{
|
||||
ApiUrls: []*url.URL{ctrlUrl},
|
||||
CaPool: o.caPool,
|
||||
TotpCodeProvider: edge_apis.NewTotpCodeProviderFromChStringFunc(o.TotpCallback),
|
||||
Components: &edge_apis.Components{
|
||||
HttpClient: &o.client,
|
||||
TlsAwareTransport: transport,
|
||||
CaPool: o.caPool,
|
||||
},
|
||||
})
|
||||
|
||||
o.mgmtClient.SetAllowOidcDynamicallyEnabled(true)
|
||||
|
||||
return o.mgmtClient, nil
|
||||
}
|
||||
|
||||
func (o *LoginOptions) Login() (edge_apis.ApiSession, error) {
|
||||
var authCreds edge_apis.Credentials
|
||||
if o.Token != "" {
|
||||
if jwtutil.IsJwt(o.Token) {
|
||||
return edge_apis.NewApiSessionOidc(o.Token, ""), nil
|
||||
} else {
|
||||
return edge_apis.NewApiSessionLegacy(o.Token), nil
|
||||
}
|
||||
} else if o.ApiSession != nil {
|
||||
return o.ApiSession, nil
|
||||
} else if o.Username != "" && o.Password != "" {
|
||||
authCreds = edge_apis.NewUpdbCredentials(o.Username, o.Password)
|
||||
} else if o.ClientCert != "" || o.ClientKey != "" {
|
||||
var key crypto.PrivateKey
|
||||
if keyPEM, err := os.ReadFile(o.ClientKey); err != nil {
|
||||
return nil, fmt.Errorf("failed to read key: %w", err)
|
||||
} else {
|
||||
keyBlock, _ := pem.Decode(keyPEM)
|
||||
k, _ := x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||
key = k
|
||||
}
|
||||
|
||||
var cert *x509.Certificate
|
||||
if certPEM, err := os.ReadFile(o.ClientCert); err != nil {
|
||||
return nil, fmt.Errorf("failed to read cert: %w", err)
|
||||
} else {
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("invalid cert pem")
|
||||
}
|
||||
c, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse cert: %w", err)
|
||||
}
|
||||
cert = c
|
||||
}
|
||||
|
||||
authCreds = edge_apis.NewCertCredentials([]*x509.Certificate{cert}, key)
|
||||
} else if o.File != "" {
|
||||
cfg, err := ziti.NewConfigFromFile(o.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authCreds = edge_apis.NewIdentityCredentialsFromConfig(cfg.ID)
|
||||
} else if o.extJwtFile != "" {
|
||||
jwt, jwtErr := os.ReadFile(o.extJwtFile)
|
||||
if jwtErr != nil {
|
||||
return nil, fmt.Errorf("failed to read jwt file: %w", jwtErr)
|
||||
}
|
||||
authCreds = edge_apis.NewJwtCredentials(string(jwt))
|
||||
}
|
||||
|
||||
s, err := o.mgmtClient.Authenticate(authCreds, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("authentication failed for some reason but no error")
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// EffectiveUrl will take all inputs and return the expected url of the controller or prompt the user to enter the url
|
||||
// to use if insufficient information exists in the inputs provided
|
||||
func (o *LoginOptions) EffectiveUrl() (string, error) {
|
||||
// if provided use the url provided
|
||||
if o.ControllerUrl != "" {
|
||||
return addHttpsIfNeeded(o.ControllerUrl), nil
|
||||
}
|
||||
|
||||
// if using file-based auth and --file is provided, look into the file for the url
|
||||
if o.File != "" {
|
||||
return o.UrlFromFile()
|
||||
}
|
||||
|
||||
// if a cached id exists - use the url from that
|
||||
if o.cachedId != nil {
|
||||
return addHttpsIfNeeded(o.cachedId.Url), nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func addHttpsIfNeeded(host string) string {
|
||||
if !strings.HasPrefix(host, "http") {
|
||||
host = "https://" + host
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func (o *LoginOptions) UrlFromFile() (string, error) {
|
||||
cfg, err := ziti.NewConfigFromFile(o.File)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read file %s: %w", o.File, err)
|
||||
}
|
||||
|
||||
if o.FileCertCreds == nil {
|
||||
idCredentials := edge_apis.NewIdentityCredentialsFromConfig(cfg.ID)
|
||||
o.FileCertCreds = idCredentials
|
||||
}
|
||||
|
||||
ztAPI := cfg.ZtAPI
|
||||
|
||||
// override with the first HA client API URL if defined
|
||||
if len(cfg.ZtAPIs) > 0 {
|
||||
ztAPI = cfg.ZtAPIs[0]
|
||||
}
|
||||
|
||||
parsedZtAPI, err := url.Parse(ztAPI)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not parse ztAPI '%s' as a URL", ztAPI)
|
||||
}
|
||||
|
||||
return addHttpsIfNeeded(parsedZtAPI.Host), nil
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package edge
|
||||
@@ -23,15 +23,16 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/openziti/edge-api/rest_model"
|
||||
"github.com/openziti/ziti/ziti/cmd/api"
|
||||
"github.com/openziti/ziti/ziti/cmd/common"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/openziti/edge-api/rest_model"
|
||||
"github.com/openziti/ziti/ziti/cmd/api"
|
||||
"github.com/openziti/ziti/ziti/cmd/common"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/securecookie"
|
||||
@@ -446,7 +447,7 @@ func NewOidcVerificationCmd(out io.Writer, errOut io.Writer, initialContext cont
|
||||
cmd.Flags().BoolVar(&opts.showRefreshToken, "refresh-token", false, "Display the full Refresh Token to the screen. Use caution.")
|
||||
cmd.Flags().BoolVar(&opts.showAccessToken, "access-token", false, "Display the full Access Token to the screen. Use caution.")
|
||||
cmd.Flags().StringVar(&opts.ControllerUrl, "controller-url", "", "The url of the controller")
|
||||
cmd.Flags().StringSliceVarP(&opts.additionalScopes, "additional-scopes", "s", []string{}, "List of additional scopes to add")
|
||||
cmd.Flags().StringSliceVarP(&opts.additionalScopes, "additional-scopes", "a", []string{}, "List of additional scopes to add")
|
||||
cmd.Flags().BoolVar(&opts.attemptAuth, "authenticate", false, "Also attempt to authenticate using the supplied ext-jwt-signer")
|
||||
cmd.Flags().StringVarP(&opts.RedirectURL, "redirect-url", "r", "http://localhost:20314/auth/callback", "The expected redirect URL to listen to")
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ func NewVerifyTraffic(out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
Use: "traffic",
|
||||
Short: "Verifies traffic",
|
||||
Long: "A tool to verify traffic can flow over the overlay properly. You must be authenticated to use this tool.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
logLvl := logrus.InfoLevel
|
||||
if t.verbose {
|
||||
logLvl = logrus.DebugLevel
|
||||
@@ -98,11 +98,11 @@ func NewVerifyTraffic(out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
t.bindSPName = t.prefix + ".bind"
|
||||
t.dialSPName = t.prefix + ".dial"
|
||||
|
||||
var err error
|
||||
t.client, err = t.loginOpts.NewMgmtClient()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
mgmtClient, mgmtClientErr := t.loginOpts.NewManagementClient(true)
|
||||
if mgmtClientErr != nil {
|
||||
return mgmtClientErr
|
||||
}
|
||||
t.client = mgmtClient.BaseClient.API.ZitiEdgeManagement
|
||||
|
||||
if t.cleanup {
|
||||
log.Info("attempting to cleanup based on parameters. this operation will disconnect the server if it's running.")
|
||||
@@ -121,6 +121,8 @@ func NewVerifyTraffic(out io.Writer, errOut io.Writer) *cobra.Command {
|
||||
} else {
|
||||
log.Fatal("no role supplied? should have defaulted to 'both'")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -141,4 +141,7 @@ const (
|
||||
ZitiEdgeRouterCsrOUVarDescription = "The organization unit to use for router CSRs"
|
||||
ZitiRouterCsrSansDnsVarName = "ZITI_ROUTER_CSR_SANS_DNS"
|
||||
ZitiRouterCsrSansDnsVarDescription = "Additional DNS SAN of the router"
|
||||
|
||||
ZitiCliNetworkIdVarName = "ZITI_CLI_NETWORK_ID"
|
||||
ZitiCliNetworkIdVarDescription = "Necessary when using the CLI over a zitified transport"
|
||||
)
|
||||
|
||||
+152
-58
@@ -20,8 +20,6 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/openziti/ziti/ziti/cmd/edge"
|
||||
"github.com/openziti/ziti/ziti/enroll"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -35,6 +33,9 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/openziti/ziti/ziti/cmd/edge"
|
||||
"github.com/openziti/ziti/ziti/enroll"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/ziti/common/version"
|
||||
@@ -65,13 +66,16 @@ type QuickstartOpts struct {
|
||||
errOut io.Writer
|
||||
cleanOnExit bool
|
||||
TrustDomain string
|
||||
isHA bool
|
||||
IsHA bool
|
||||
InstanceID string
|
||||
ClusterMember string
|
||||
joinCommand bool
|
||||
verbose bool
|
||||
nonVoter bool
|
||||
routerless bool
|
||||
Routerless bool
|
||||
ConfigureAndExit bool
|
||||
ConfigFile string
|
||||
|
||||
joinCommand bool
|
||||
verbose bool
|
||||
nonVoter bool
|
||||
}
|
||||
|
||||
func addCommonQuickstartFlags(cmd *cobra.Command, options *QuickstartOpts) {
|
||||
@@ -91,9 +95,10 @@ func addCommonQuickstartFlags(cmd *cobra.Command, options *QuickstartOpts) {
|
||||
cmd.Flags().Uint16Var(&options.ControllerPort, "ctrl-port", uint16(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().Uint16Var(&options.RouterPort, "router-port", uint16(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.Routerless, "no-router", false, "specifies the quickstart should not start a router")
|
||||
|
||||
cmd.Flags().BoolVar(&options.verbose, "verbose", false, "Show additional output.")
|
||||
cmd.Flags().BoolVar(&options.ConfigureAndExit, "configure-and-exit", false, "Configures everything and then exits gracefully")
|
||||
}
|
||||
|
||||
func addQuickstartHaFlags(cmd *cobra.Command, options *QuickstartOpts) {
|
||||
@@ -108,15 +113,12 @@ func NewQuickStartCmd(out io.Writer, errOut io.Writer, context context.Context)
|
||||
Use: "quickstart",
|
||||
Short: "runs a Controller and Router in quickstart mode",
|
||||
Long: "runs a Controller and Router in quickstart mode with a temporary directory; suitable for testing and development",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.out = out
|
||||
options.errOut = errOut
|
||||
options.TrustDomain = "quickstart"
|
||||
options.InstanceID = "quickstart"
|
||||
err := options.run(context)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
return options.run(context)
|
||||
},
|
||||
}
|
||||
addCommonQuickstartFlags(cmd, options)
|
||||
@@ -134,7 +136,7 @@ func NewQuickStartHaCmd(out io.Writer, errOut io.Writer, context context.Context
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
options.out = out
|
||||
options.errOut = errOut
|
||||
options.isHA = true
|
||||
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)
|
||||
@@ -175,7 +177,7 @@ func NewQuickStartJoinClusterCmd(out io.Writer, errOut io.Writer, context contex
|
||||
}
|
||||
|
||||
func (o *QuickstartOpts) cleanupHome() {
|
||||
if o.cleanOnExit {
|
||||
if o.cleanOnExit && !o.ConfigureAndExit {
|
||||
fmt.Println("Removing temp directory at: " + o.Home)
|
||||
_ = os.RemoveAll(o.Home)
|
||||
} else {
|
||||
@@ -195,7 +197,7 @@ func (o *QuickstartOpts) join(ctx context.Context) error {
|
||||
logrus.Fatalf("--cluster-member is required")
|
||||
}
|
||||
|
||||
o.isHA = true
|
||||
o.IsHA = true
|
||||
o.joinCommand = true
|
||||
return o.run(ctx)
|
||||
}
|
||||
@@ -250,7 +252,7 @@ func (o *QuickstartOpts) run(ctx context.Context) error {
|
||||
o.InstanceID = uuid.New().String()
|
||||
}
|
||||
|
||||
ctrlYaml := path.Join(o.instHome(), "ctrl.yaml")
|
||||
o.ConfigFile = 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'
|
||||
@@ -280,14 +282,14 @@ func (o *QuickstartOpts) run(ctx context.Context) error {
|
||||
_ = os.MkdirAll(dbDir, 0o700)
|
||||
logrus.Debugf("made directory '%s'", dbDir)
|
||||
|
||||
o.createMinimalPki()
|
||||
o.CreateMinimalPki()
|
||||
|
||||
_ = os.Setenv("ZITI_HOME", o.instHome())
|
||||
ctrl := create.NewCmdCreateConfigController()
|
||||
args := []string{
|
||||
fmt.Sprintf("--output=%s", ctrlYaml),
|
||||
fmt.Sprintf("--output=%s", o.ConfigFile),
|
||||
}
|
||||
if o.isHA {
|
||||
if o.IsHA {
|
||||
args = append(args, "--clustered")
|
||||
}
|
||||
ctrl.SetArgs(args)
|
||||
@@ -296,12 +298,12 @@ func (o *QuickstartOpts) run(ctx context.Context) error {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !o.isHA {
|
||||
if !o.IsHA {
|
||||
initCmd := edgeSubCmd.NewEdgeInitializeCmd(version.GetCmdBuildInfo())
|
||||
initCmd.SetArgs([]string{
|
||||
fmt.Sprintf("--username=%s", o.Username),
|
||||
fmt.Sprintf("--password=%s", o.Password),
|
||||
ctrlYaml,
|
||||
o.ConfigFile,
|
||||
})
|
||||
initErr := initCmd.Execute()
|
||||
if initErr != nil {
|
||||
@@ -310,12 +312,14 @@ func (o *QuickstartOpts) run(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
curCtx, cancel := context.WithCancel(ctx) //used to cancel controller and router when configure-and-exit is selected
|
||||
fmt.Println("Starting controller...")
|
||||
go func() {
|
||||
runCtrl := NewRunControllerCmd()
|
||||
runCtrl.SetArgs([]string{
|
||||
ctrlYaml,
|
||||
o.ConfigFile,
|
||||
})
|
||||
runCtrl.SetContext(curCtx)
|
||||
runCtrlErr := runCtrl.Execute()
|
||||
if runCtrlErr != nil {
|
||||
logrus.Fatal(runCtrlErr)
|
||||
@@ -323,23 +327,34 @@ func (o *QuickstartOpts) run(ctx context.Context) error {
|
||||
}()
|
||||
fmt.Println("Controller running...")
|
||||
|
||||
ctrlAddy := helpers.GetCtrlEdgeAdvertisedAddress()
|
||||
ctrlPort := helpers.GetCtrlEdgeAdvertisedPort()
|
||||
ctrlUrl := fmt.Sprintf("https://%s:%s", ctrlAddy, ctrlPort)
|
||||
o.ControllerAddress = helpers.GetCtrlEdgeAdvertisedAddress()
|
||||
|
||||
portStr := helpers.GetCtrlEdgeAdvertisedPort()
|
||||
port, portErr := strconv.Atoi(portStr)
|
||||
if portErr != nil {
|
||||
cancel()
|
||||
return fmt.Errorf("invalid controller port: %s", portStr)
|
||||
}
|
||||
o.ControllerPort = uint16(port)
|
||||
|
||||
c := make(chan error)
|
||||
timeout, _ := time.ParseDuration("30s")
|
||||
go waitForController(ctrlUrl, c)
|
||||
timeout := 30 * time.Second
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer stop()
|
||||
go waitForController(ctx, o.ControllerHostPort(), c)
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
//completed normally
|
||||
logrus.Info("Controller online. Continuing...")
|
||||
case <-time.After(timeout):
|
||||
o.cleanupHome()
|
||||
return fmt.Errorf("timed out waiting for controller: %s", ctrlUrl)
|
||||
cancel()
|
||||
return fmt.Errorf("timed out waiting for controller: %s", o.ControllerHostPort())
|
||||
}
|
||||
|
||||
if o.isHA {
|
||||
if o.IsHA {
|
||||
p := common.NewOptionsProvider(o.out, o.errOut)
|
||||
fmt.Println("waiting three seconds for controller to become ready...")
|
||||
|
||||
@@ -364,6 +379,7 @@ func (o *QuickstartOpts) run(ctx context.Context) error {
|
||||
time.Sleep(2 * time.Second) // Wait before retrying
|
||||
} else {
|
||||
fmt.Println("Max retries reached. Failing.")
|
||||
cancel()
|
||||
return agentInitErr
|
||||
}
|
||||
} else {
|
||||
@@ -397,14 +413,16 @@ func (o *QuickstartOpts) run(ctx context.Context) error {
|
||||
logrus.Info("Add command successful. continuing...")
|
||||
case <-time.After(addTimeout):
|
||||
o.cleanupHome()
|
||||
cancel()
|
||||
return fmt.Errorf("timed out adding to cluster")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
erConfigFile := path.Join(o.instHome(), routerName+".yaml")
|
||||
err := o.configureRouter(routerName, erConfigFile, ctrlUrl)
|
||||
err := o.configureRouter(routerName, erConfigFile, o.ControllerHostPort())
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
o.runRouter(erConfigFile)
|
||||
@@ -412,21 +430,26 @@ func (o *QuickstartOpts) run(ctx context.Context) error {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, os.Interrupt, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
if !o.routerless {
|
||||
r := make(chan struct{})
|
||||
if !o.Routerless {
|
||||
r := make(chan error)
|
||||
timeout, _ = time.ParseDuration("30s")
|
||||
logrus.Infof("waiting for router at: %s:%d", o.RouterAddress, o.RouterPort)
|
||||
go waitForRouter(o.RouterAddress, o.RouterPort, r)
|
||||
go o.WaitForRouter(timeout, r)
|
||||
select {
|
||||
case <-r:
|
||||
//completed normally
|
||||
case waitErr := <-r:
|
||||
if waitErr != nil {
|
||||
o.cleanupHome()
|
||||
cancel()
|
||||
return fmt.Errorf("router failed: %w", waitErr)
|
||||
}
|
||||
case <-time.After(timeout):
|
||||
o.cleanupHome()
|
||||
cancel()
|
||||
return fmt.Errorf("timed out waiting for router on port: %d", o.RouterPort)
|
||||
}
|
||||
}
|
||||
|
||||
if o.isHA {
|
||||
if o.IsHA {
|
||||
go func() {
|
||||
time.Sleep(3 * time.Second) // output this after a bit...
|
||||
nextInstId := incrementStringSuffix(o.InstanceID)
|
||||
@@ -439,7 +462,7 @@ func (o *QuickstartOpts) run(ctx context.Context) error {
|
||||
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(" --cluster-member tls:%s:%s\\ \n", ctrlAddy, ctrlPort)
|
||||
fmt.Printf(" --cluster-member tls:%s:%d\\ \n", o.ControllerAddress, o.ControllerPort)
|
||||
fmt.Printf(" --instance-id \"%s\"\n", nextInstId)
|
||||
fmt.Println("=======================================================================================")
|
||||
fmt.Println()
|
||||
@@ -458,6 +481,7 @@ func (o *QuickstartOpts) run(ctx context.Context) error {
|
||||
}
|
||||
|
||||
o.cleanupHome()
|
||||
cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -472,14 +496,14 @@ func (o *QuickstartOpts) printDetails() {
|
||||
}
|
||||
|
||||
func (o *QuickstartOpts) configureRouter(routerName string, configFile string, ctrlUrl string) error {
|
||||
if o.routerless {
|
||||
if o.Routerless {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !o.AlreadyInitialized {
|
||||
loginCmd := edge.NewLoginCmd(o.out, o.errOut)
|
||||
loginCmd.SetArgs([]string{
|
||||
ctrlUrl,
|
||||
o.ControllerHostPort(),
|
||||
fmt.Sprintf("--username=%s", o.Username),
|
||||
fmt.Sprintf("--password=%s", o.Password),
|
||||
"-y",
|
||||
@@ -520,7 +544,7 @@ func (o *QuickstartOpts) configureRouter(routerName string, configFile string, c
|
||||
|
||||
data := &create.ConfigTemplateValues{}
|
||||
data.PopulateConfigValues()
|
||||
opts.IsHA = o.isHA
|
||||
opts.IsHA = o.IsHA
|
||||
create.SetZitiRouterIdentity(&data.Router, routerName)
|
||||
erCfg := create.NewCmdCreateConfigRouterEdge(opts, data)
|
||||
erCfg.SetArgs([]string{
|
||||
@@ -551,7 +575,7 @@ func (o *QuickstartOpts) configureRouter(routerName string, configFile string, c
|
||||
}
|
||||
|
||||
func (o *QuickstartOpts) runRouter(configFile string) {
|
||||
if o.routerless {
|
||||
if o.Routerless {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -570,7 +594,7 @@ func (o *QuickstartOpts) runRouter(configFile string) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (o *QuickstartOpts) createMinimalPki() {
|
||||
func (o *QuickstartOpts) CreateMinimalPki() {
|
||||
where := path.Join(o.Home, "pki")
|
||||
fmt.Println("emitting a minimal PKI")
|
||||
|
||||
@@ -660,31 +684,50 @@ func (o *QuickstartOpts) createMinimalPki() {
|
||||
}
|
||||
}
|
||||
|
||||
func waitForController(ctrlUrl string, done chan error) {
|
||||
func waitForController(ctx context.Context, ctrlUrl string, done chan error) {
|
||||
if !strings.HasPrefix(ctrlUrl, "https://") {
|
||||
ctrlUrl = "https://" + ctrlUrl
|
||||
}
|
||||
tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||
client := &http.Client{Transport: tr}
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
r, e := client.Get(ctrlUrl)
|
||||
if e != nil || r == nil || r.StatusCode != 200 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
done <- ctx.Err()
|
||||
return
|
||||
case <-ticker.C:
|
||||
fmt.Printf("waiting for controller: %s\n", ctrlUrl)
|
||||
default:
|
||||
r, e := client.Get(ctrlUrl)
|
||||
if e == nil && r != nil && r.StatusCode == 200 {
|
||||
done <- nil
|
||||
return
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
done <- nil
|
||||
}
|
||||
|
||||
func waitForRouter(address string, port uint16, done chan struct{}) {
|
||||
func (o *QuickstartOpts) WaitForRouter(timeout time.Duration, done chan error) {
|
||||
for {
|
||||
addr := net.JoinHostPort(address, strconv.Itoa(int(port)))
|
||||
conn, err := net.DialTimeout("tcp", addr, 2*time.Second)
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
fmt.Printf("Router is available on %s\n", addr)
|
||||
close(done)
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
done <- fmt.Errorf("router not available after %s at %s:%d", timeout, o.RouterAddress, o.RouterPort)
|
||||
return
|
||||
default:
|
||||
addr := net.JoinHostPort(o.RouterAddress, strconv.Itoa(int(o.RouterPort)))
|
||||
conn, err := net.DialTimeout("tcp", addr, 2*time.Second)
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
fmt.Printf("Router is available on %s:%d\n", o.RouterAddress, o.RouterPort)
|
||||
done <- nil
|
||||
return
|
||||
}
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
}
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,7 +743,7 @@ func (o *QuickstartOpts) scopedName(name string) string {
|
||||
}
|
||||
|
||||
func (o *QuickstartOpts) instHome() string {
|
||||
if o.isHA {
|
||||
if o.IsHA {
|
||||
return path.Join(o.Home, o.InstanceID)
|
||||
}
|
||||
return o.Home
|
||||
@@ -789,3 +832,54 @@ func incrementStringSuffix(input string) string {
|
||||
|
||||
return strings.TrimSuffix(input, numStr) + incremented
|
||||
}
|
||||
|
||||
func (o *QuickstartOpts) InitHA(p common.OptionsProvider) error {
|
||||
maxRetries := 5
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
fmt.Printf("initializing controller at port: %d\n", o.ControllerPort)
|
||||
agentInitCmd := agentcli.NewAgentClusterInit(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 {
|
||||
if attempt < maxRetries {
|
||||
fmt.Println("initialization failed. waiting two seconds and trying again")
|
||||
time.Sleep(2 * time.Second) // Wait before retrying
|
||||
} else {
|
||||
fmt.Println("Max retries reached. Failing.")
|
||||
return agentInitErr
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *QuickstartOpts) InitLegacy() error {
|
||||
initCmd := edgeSubCmd.NewEdgeInitializeCmd(version.GetCmdBuildInfo())
|
||||
initCmd.SetArgs([]string{
|
||||
fmt.Sprintf("--username=%s", o.Username),
|
||||
fmt.Sprintf("--password=%s", o.Password),
|
||||
o.ConfigFile,
|
||||
})
|
||||
initErr := initCmd.Execute()
|
||||
if initErr != nil {
|
||||
return initErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *QuickstartOpts) ControllerHostPort() string {
|
||||
return net.JoinHostPort(o.ControllerAddress, strconv.Itoa(int(o.ControllerPort)))
|
||||
}
|
||||
func (o *QuickstartOpts) RouterHostPort() string {
|
||||
return net.JoinHostPort(o.RouterAddress, strconv.Itoa(int(o.RouterPort)))
|
||||
}
|
||||
|
||||
@@ -17,13 +17,15 @@
|
||||
package run
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/openziti/ziti/controller/config"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/openziti/ziti/controller/config"
|
||||
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/agent"
|
||||
"github.com/openziti/ziti/common/version"
|
||||
@@ -100,7 +102,7 @@ func (self *ControllerAction) Run(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
go self.waitForShutdown()
|
||||
go self.waitForShutdown(cmd.Context())
|
||||
|
||||
self.edgeController.Run()
|
||||
if err := self.fabricController.Run(); err != nil {
|
||||
@@ -108,13 +110,22 @@ func (self *ControllerAction) Run(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ControllerAction) waitForShutdown() {
|
||||
func (self *ControllerAction) waitForShutdown(ctx context.Context) {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
|
||||
defer signal.Stop(ch)
|
||||
|
||||
<-ch
|
||||
pfxlog.Logger().Info("waiting for shutdown signal or context cancel")
|
||||
|
||||
select {
|
||||
case sig := <-ch:
|
||||
pfxlog.Logger().Infof("received signal: %v", sig)
|
||||
case <-ctx.Done():
|
||||
pfxlog.Logger().Info("context cancelled, shutting down")
|
||||
}
|
||||
|
||||
pfxlog.Logger().Info("shutting down ziti-controller")
|
||||
self.edgeController.Shutdown()
|
||||
self.fabricController.Shutdown()
|
||||
|
||||
pfxlog.Logger().Info("shutdown complete")
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func (self *RouterAction) Run(cmd *cobra.Command, args []string) {
|
||||
r.RunCliAgent(self.CliAgentAddr, self.CliAgentAlias)
|
||||
}
|
||||
|
||||
go r.ListenForShutdownSignal()
|
||||
go r.ListenForShutdownSignal(cmd.Context())
|
||||
|
||||
if err = r.Run(); err != nil {
|
||||
logrus.WithError(err).Fatal("error starting")
|
||||
|
||||
+53
-32
@@ -7,7 +7,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/openziti/ziti/ziti/cmd/common"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -17,11 +16,26 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/openziti/ziti/ziti/cmd/common"
|
||||
|
||||
"github.com/fullsailor/pkcs7"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func WriteCert(p common.Printer, id string, cert []byte) (string, error) {
|
||||
func urlToId(url *url.URL) string {
|
||||
p := url.Port()
|
||||
if p == "" {
|
||||
if url.Scheme == "https" {
|
||||
p = "443"
|
||||
} else {
|
||||
p = "80"
|
||||
}
|
||||
}
|
||||
return url.Hostname() + "_" + p
|
||||
}
|
||||
|
||||
func WriteCert(p common.Printer, url *url.URL, cert []byte) (string, error) {
|
||||
id := urlToId(url)
|
||||
cfgDir, err := ConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -38,7 +52,8 @@ func WriteCert(p common.Printer, id string, cert []byte) (string, error) {
|
||||
return certFile, nil
|
||||
}
|
||||
|
||||
func ReadCert(id string) ([]byte, string, error) {
|
||||
func ReadCert(url *url.URL) ([]byte, string, error) {
|
||||
id := urlToId(url)
|
||||
cfgDir, err := ConfigDir()
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrapf(err, "couldn't get config dir while reading cert for %v", id)
|
||||
@@ -58,8 +73,11 @@ func ReadCert(id string) ([]byte, string, error) {
|
||||
return result, certFile, nil
|
||||
}
|
||||
|
||||
func IsServerTrusted(host string) (bool, error) {
|
||||
resp, err := http.DefaultClient.Get(fmt.Sprintf("%v/.well-known/est/cacerts", host))
|
||||
func IsServerTrusted(host string, client *http.Client) (bool, error) {
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
resp, err := client.Get(fmt.Sprintf("%v/.well-known/est/cacerts", host))
|
||||
if err != nil {
|
||||
if ue, ok := err.(*url.Error); ok && (errors.As(ue.Err, &x509.UnknownAuthorityError{}) || strings.Contains(err.Error(), "x509")) {
|
||||
return false, nil
|
||||
@@ -70,25 +88,10 @@ func IsServerTrusted(host string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func AreCertsTrusted(host string, certs []byte) (bool, error) {
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
func AreCertsTrusted(host string, certs []byte, client http.Client) (bool, error) {
|
||||
c := InsecureClient(&client, certs)
|
||||
|
||||
if tlsConfig.RootCAs == nil {
|
||||
tlsConfig.RootCAs = x509.NewCertPool()
|
||||
}
|
||||
|
||||
tlsConfig.RootCAs.AppendCertsFromPEM(certs)
|
||||
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
transport.TLSClientConfig = tlsConfig
|
||||
|
||||
client := http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("%v/.well-known/est/cacerts", host))
|
||||
resp, err := c.Get(fmt.Sprintf("%v/.well-known/est/cacerts", host))
|
||||
if err != nil {
|
||||
if ue, ok := err.(*url.Error); ok && errors.As(ue.Err, &x509.UnknownAuthorityError{}) {
|
||||
return false, nil
|
||||
@@ -99,16 +102,10 @@ func AreCertsTrusted(host string, certs []byte) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func GetWellKnownCerts(host string) ([]byte, []*x509.Certificate, error) {
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
client := http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
func GetWellKnownCerts(host string, client http.Client) ([]byte, []*x509.Certificate, error) {
|
||||
c := InsecureClient(&client, nil)
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("%v/.well-known/est/cacerts", host))
|
||||
resp, err := c.Get(fmt.Sprintf("%v/.well-known/est/cacerts", host))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -198,3 +195,27 @@ func (self blockSort) Swap(i, j int) {
|
||||
self[i] = self[j]
|
||||
self[j] = tmp
|
||||
}
|
||||
|
||||
func InsecureClient(client *http.Client, certs []byte) *http.Client {
|
||||
tlsConfig := &tls.Config{
|
||||
RootCAs: x509.NewCertPool(),
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
if len(certs) > 0 {
|
||||
tlsConfig.RootCAs.AppendCertsFromPEM(certs)
|
||||
}
|
||||
|
||||
var t *http.Transport
|
||||
if origTransport, ok := client.Transport.(*http.Transport); ok {
|
||||
t = origTransport.Clone()
|
||||
t.TLSClientConfig = tlsConfig
|
||||
} else {
|
||||
t = &http.Transport{TLSClientConfig: tlsConfig}
|
||||
}
|
||||
|
||||
c := &http.Client{
|
||||
Transport: t,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
+85
-14
@@ -17,13 +17,22 @@ import (
|
||||
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
edge_apis "github.com/openziti/sdk-golang/edge-apis"
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
"github.com/openziti/ziti/controller/env"
|
||||
fabric_rest_client "github.com/openziti/ziti/controller/rest_client"
|
||||
"github.com/openziti/ziti/ziti/cmd/common"
|
||||
"github.com/openziti/ziti/ziti/constants"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/resty.v1"
|
||||
)
|
||||
|
||||
var zitiCliContextCollection *ziti.CtxCollection
|
||||
|
||||
func init() {
|
||||
zitiCliContextCollection = ziti.NewSdkCollection()
|
||||
}
|
||||
|
||||
type API string
|
||||
|
||||
const (
|
||||
@@ -50,6 +59,7 @@ func (self *RestClientConfig) GetIdentity() string {
|
||||
type RestClientIdentity interface {
|
||||
NewTlsClientConfig() (*tls.Config, error)
|
||||
NewClient(timeout time.Duration, verbose bool) (*resty.Client, error)
|
||||
NewClientByTerminator(timeout time.Duration, verbose bool, terminator string) (*resty.Client, error)
|
||||
NewRequest(client *resty.Client) *resty.Request
|
||||
IsReadOnly() bool
|
||||
GetBaseUrlForApi(api API) (string, error)
|
||||
@@ -59,7 +69,11 @@ type RestClientIdentity interface {
|
||||
}
|
||||
|
||||
func NewRequest(restClientIdentity RestClientIdentity, timeoutInSeconds int, verbose bool) (*resty.Request, error) {
|
||||
client, err := restClientIdentity.NewClient(time.Duration(timeoutInSeconds)*time.Second, verbose)
|
||||
return NewRequestByTerminator(restClientIdentity, timeoutInSeconds, verbose, "")
|
||||
}
|
||||
|
||||
func NewRequestByTerminator(restClientIdentity RestClientIdentity, timeoutInSeconds int, verbose bool, terminator string) (*resty.Request, error) {
|
||||
client, err := restClientIdentity.NewClientByTerminator(time.Duration(timeoutInSeconds)*time.Second, verbose, terminator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -67,12 +81,14 @@ func NewRequest(restClientIdentity RestClientIdentity, timeoutInSeconds int, ver
|
||||
}
|
||||
|
||||
type RestClientEdgeIdentity struct {
|
||||
Url string `json:"url"`
|
||||
Username string `json:"username"`
|
||||
Token string `json:"token"`
|
||||
LoginTime string `json:"loginTime"`
|
||||
CaCert string `json:"caCert,omitempty"`
|
||||
ReadOnly bool `json:"readOnly"`
|
||||
Url string `json:"url"`
|
||||
Username string `json:"username"`
|
||||
Token string `json:"token"`
|
||||
LoginTime string `json:"loginTime"`
|
||||
CaCert string `json:"caCert,omitempty"`
|
||||
ReadOnly bool `json:"readOnly"`
|
||||
NetworkIdFile string `json:"networkId"`
|
||||
ApiSession *edge_apis.ApiSessionJsonWrapper `json:"apiSession"`
|
||||
}
|
||||
|
||||
func (self *RestClientEdgeIdentity) IsReadOnly() bool {
|
||||
@@ -103,7 +119,40 @@ func (self *RestClientEdgeIdentity) NewTlsClientConfig() (*tls.Config, error) {
|
||||
}
|
||||
|
||||
func (self *RestClientEdgeIdentity) NewClient(timeout time.Duration, verbose bool) (*resty.Client, error) {
|
||||
return self.NewClientByTerminator(timeout, verbose, "")
|
||||
}
|
||||
|
||||
func (self *RestClientEdgeIdentity) NewClientByTerminator(timeout time.Duration, verbose bool, terminator string) (*resty.Client, error) {
|
||||
client := NewClient()
|
||||
if ztFromEnv, ztFromEnvErr := ZitifiedTransportFromEnv(""); ztFromEnvErr != nil {
|
||||
return nil, ztFromEnvErr
|
||||
} else {
|
||||
if ztFromEnv != nil {
|
||||
if verbose {
|
||||
client.Log.Printf("Using Ziti Transport from environment var: %s", constants.ZitiCliNetworkIdVarName)
|
||||
}
|
||||
client.GetClient().Transport = ztFromEnv
|
||||
} else {
|
||||
if self.NetworkIdFile != "" {
|
||||
if ztFromFile, ztFromFileErr := NewZitifiedTransportFromFile(self.NetworkIdFile, terminator); ztFromFileErr != nil {
|
||||
// ignore any error around the networkId file
|
||||
if verbose {
|
||||
client.Log.Printf("Ziti transport from cached file failed: %v", ztFromFileErr)
|
||||
}
|
||||
} else {
|
||||
if verbose {
|
||||
client.Log.Printf("Using Ziti transport from cached file: %s", self.NetworkIdFile)
|
||||
}
|
||||
client.GetClient().Transport = ztFromFile
|
||||
}
|
||||
} else {
|
||||
if verbose {
|
||||
client.Log.Printf("Using default http transport")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.CaCert != "" {
|
||||
client.SetRootCertificate(self.CaCert)
|
||||
}
|
||||
@@ -114,7 +163,19 @@ func (self *RestClientEdgeIdentity) NewClient(timeout time.Duration, verbose boo
|
||||
|
||||
func (self *RestClientEdgeIdentity) NewRequest(client *resty.Client) *resty.Request {
|
||||
r := client.R()
|
||||
r.SetHeader(env.ZitiSession, self.Token)
|
||||
if self.ApiSession != nil && self.ApiSession.ApiSession != nil {
|
||||
switch self.ApiSession.ApiSession.GetType() {
|
||||
case edge_apis.ApiSessionTypeOidc:
|
||||
authHeader := "Bearer " + strings.TrimSpace(string(self.ApiSession.ApiSession.GetToken()))
|
||||
r.SetHeader("Authorization", authHeader)
|
||||
case edge_apis.ApiSessionTypeLegacy:
|
||||
r.SetHeader(env.ZitiSession, string(self.ApiSession.ApiSession.GetToken()))
|
||||
default:
|
||||
panic("unsupported api session type " + self.ApiSession.ApiSession.GetType())
|
||||
}
|
||||
} else {
|
||||
r.SetHeader(env.ZitiSession, self.Token)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -145,9 +206,7 @@ func (self *RestClientEdgeIdentity) NewEdgeManagementClient(clientOpts ClientOpt
|
||||
|
||||
clientRuntime := httptransport.NewWithClient(parsedHost.Host, rest_management_api_client.DefaultBasePath, rest_management_api_client.DefaultSchemes, httpClient)
|
||||
|
||||
clientRuntime.DefaultAuthentication = &EdgeManagementAuth{
|
||||
Token: self.Token,
|
||||
}
|
||||
clientRuntime.DefaultAuthentication = self.newEdgeAuth()
|
||||
|
||||
return rest_management_api_client.New(clientRuntime, nil), nil
|
||||
}
|
||||
@@ -165,9 +224,7 @@ func (self *RestClientEdgeIdentity) NewFabricManagementClient(clientOpts ClientO
|
||||
|
||||
clientRuntime := httptransport.NewWithClient(parsedHost.Host, fabric_rest_client.DefaultBasePath, fabric_rest_client.DefaultSchemes, httpClient)
|
||||
|
||||
clientRuntime.DefaultAuthentication = &EdgeManagementAuth{
|
||||
Token: self.Token,
|
||||
}
|
||||
clientRuntime.DefaultAuthentication = self.newEdgeAuth()
|
||||
|
||||
return fabric_rest_client.New(clientRuntime, nil), nil
|
||||
}
|
||||
@@ -364,3 +421,17 @@ func newRestClientTransport(clientOpts ClientOpts, clientIdentity RestClientIden
|
||||
}
|
||||
return httpClient, nil
|
||||
}
|
||||
|
||||
func (self *RestClientEdgeIdentity) newEdgeAuth() EdgeManagementAuth {
|
||||
ea := EdgeManagementAuth{}
|
||||
|
||||
if self.ApiSession != nil && self.ApiSession.ApiSession != nil {
|
||||
ea.BearerToken = string(self.ApiSession.ApiSession.GetToken())
|
||||
} else if self.Token != "" {
|
||||
ea.LegacyToken = self.Token
|
||||
} else {
|
||||
panic("no authentication mechanism set")
|
||||
}
|
||||
|
||||
return ea
|
||||
}
|
||||
|
||||
+34
-10
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
"github.com/openziti/edge-api/rest_model"
|
||||
"github.com/openziti/ziti/controller/api"
|
||||
fabric_rest_client "github.com/openziti/ziti/controller/rest_client"
|
||||
"gopkg.in/resty.v1"
|
||||
)
|
||||
@@ -47,6 +48,14 @@ func NewClient() *resty.Client {
|
||||
SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))
|
||||
}
|
||||
|
||||
func NewClientWithClient(client *http.Client) *resty.Client {
|
||||
return resty.
|
||||
NewWithClient(client).
|
||||
SetTimeout(2 * time.Second).
|
||||
SetRetryCount(5).
|
||||
SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))
|
||||
}
|
||||
|
||||
func PrettyPrintResponse(resp *resty.Response) string {
|
||||
out := resp.String()
|
||||
var prettyJSON bytes.Buffer
|
||||
@@ -132,17 +141,18 @@ func ControllerList(api API, path string, params url.Values, logJSON bool, out i
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseUrl, err := restClientIdentity.GetBaseUrlForApi(api)
|
||||
baseUrlStr, err := restClientIdentity.GetBaseUrlForApi(api)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := NewRequest(restClientIdentity, timeout, verbose)
|
||||
baseUrl, _ := url.Parse(baseUrlStr)
|
||||
req, err := NewRequestByTerminator(restClientIdentity, timeout, verbose, baseUrl.User.Username())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queryUrl := baseUrl + "/" + path
|
||||
queryUrl := strings.TrimRight(baseUrlStr, "/") + "/" + path
|
||||
|
||||
if len(params) > 0 {
|
||||
queryUrl += "?" + params.Encode()
|
||||
@@ -265,11 +275,16 @@ func NewFabricManagementClient(clientOpts ClientOpts) (*fabric_rest_client.ZitiF
|
||||
}
|
||||
|
||||
type EdgeManagementAuth struct {
|
||||
Token string
|
||||
LegacyToken string
|
||||
BearerToken string
|
||||
}
|
||||
|
||||
func (e EdgeManagementAuth) AuthenticateRequest(request openApiRuntime.ClientRequest, registry strfmt.Registry) error {
|
||||
return request.SetHeaderParam("zt-session", e.Token)
|
||||
if e.LegacyToken != "" {
|
||||
return request.SetHeaderParam(api.ZitiSession, e.LegacyToken)
|
||||
} else {
|
||||
return request.SetHeaderParam("Authorization", "Bearer "+e.BearerToken)
|
||||
}
|
||||
}
|
||||
|
||||
// ControllerCreate will create entities of the given type in the given Edge Controller
|
||||
@@ -506,9 +521,13 @@ func EdgeControllerRequest(entityType string, out io.Writer, logJSON bool, timeo
|
||||
return jsonParsed, nil
|
||||
}
|
||||
|
||||
func EdgeControllerGetManagementApiBasePathWithPool(host string, caPool *x509.CertPool) string {
|
||||
client := NewClient()
|
||||
|
||||
func EdgeControllerGetManagementApiBasePathWithPool(host string, caPool *x509.CertPool, httpClient *http.Client) string {
|
||||
var client *resty.Client
|
||||
if httpClient == nil {
|
||||
client = NewClient()
|
||||
} else {
|
||||
client = NewClientWithClient(httpClient)
|
||||
}
|
||||
client.SetHostURL(host)
|
||||
|
||||
if caPool != nil {
|
||||
@@ -593,8 +612,13 @@ func getManagementApiBasePath(host string, client *resty.Client) string {
|
||||
// determine the proper path that should be used to access the Edge Management API. Depending
|
||||
// on the version of the Edge Controller the API may be monolith on `/edge/<version>` and `/` or split into
|
||||
// `/edge/management/<version>` and `/edge/client/<version>`.
|
||||
func EdgeControllerGetManagementApiBasePath(host string, cert string) string {
|
||||
client := NewClient()
|
||||
func EdgeControllerGetManagementApiBasePath(host string, cert string, httpClient *http.Client) string {
|
||||
var client *resty.Client
|
||||
if httpClient == nil {
|
||||
client = NewClient()
|
||||
} else {
|
||||
client = NewClientWithClient(httpClient)
|
||||
}
|
||||
|
||||
client.SetHostURL(host)
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
// Package util provides utility functions for the Ziti CLI, including HTTP transport
|
||||
// creation and configuration for communicating over Ziti networks.
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
"github.com/openziti/ziti/ziti/constants"
|
||||
)
|
||||
|
||||
// NewZitifiedTransportFromSlice creates an HTTP transport configured to route
|
||||
// connections through a Ziti network. The provided bytes should contain a JSON-encoded
|
||||
// Ziti configuration.
|
||||
//
|
||||
// By default, urls are expected to leverage intercepts. Create a service and assign an appropriate
|
||||
// intercept config and use the intercept address when dialing.
|
||||
//
|
||||
// To support addressable terminators-based dialing a user should be specified in the URL. This activates
|
||||
// the dial-by-identity functionality. In this mode the url should be in the form of
|
||||
// "identity-to-dial@service-name-to-dial". The transport uses the Proxy hook to extract user identity
|
||||
// information from request URLs and passes it to Ziti dial operation via DialOptions.
|
||||
//
|
||||
// Returns an error if the configuration is invalid or Ziti context creation fails.
|
||||
func NewZitifiedTransportFromSlice(bytes []byte, terminator string) (*http.Transport, error) {
|
||||
cfg := &ziti.Config{}
|
||||
if err := json.Unmarshal(bytes, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.ConfigTypes = append(cfg.ConfigTypes, "all")
|
||||
|
||||
zc, zce := ziti.NewContext(cfg)
|
||||
if zce != nil {
|
||||
return nil, fmt.Errorf("failed to create ziti context: %v", zce)
|
||||
}
|
||||
zitiCliContextCollection.Add(zc)
|
||||
|
||||
if _, se := zc.GetServices(); se != nil {
|
||||
return nil, fmt.Errorf("failed to get ziti services: %v", se)
|
||||
}
|
||||
|
||||
zitiTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
|
||||
opts := &ziti.DialOptions{
|
||||
Identity: terminator,
|
||||
}
|
||||
|
||||
zitiTransport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dialer := zitiCliContextCollection.NewDialerWithFallback(ctx, &net.Dialer{})
|
||||
if opts.Identity != "" {
|
||||
hostParts := strings.Split(addr, ":")
|
||||
return zc.DialWithOptions(hostParts[0], opts)
|
||||
} else {
|
||||
return dialer.Dial(network, addr)
|
||||
}
|
||||
}
|
||||
|
||||
_, se := zc.GetServices() // loads all the services
|
||||
if se != nil {
|
||||
return nil, fmt.Errorf("failed to get ziti services: %v", se)
|
||||
}
|
||||
return zitiTransport, nil
|
||||
}
|
||||
|
||||
// ZitifiedTransportFromEnv creates a Ziti-enabled HTTP transport by reading a
|
||||
// base64-encoded Ziti identity from the default environment variable
|
||||
// (ZitiCliNetworkIdVarName from constants).
|
||||
//
|
||||
// Returns (nil, nil) if the environment variable is not set, or (transport, error)
|
||||
// if there's an issue creating the transport.
|
||||
func ZitifiedTransportFromEnv(terminator string) (*http.Transport, error) {
|
||||
return ZitifiedTransportFromEnvByName(constants.ZitiCliNetworkIdVarName, terminator)
|
||||
}
|
||||
|
||||
// ZitifiedTransportFromEnvByName creates a Ziti-enabled HTTP transport by reading
|
||||
// a base64-encoded Ziti identity from the specified environment variable.
|
||||
//
|
||||
// The environment variable should contain a base64-encoded Ziti configuration.
|
||||
// Returns (nil, nil) if the environment variable is not set, or (transport, error)
|
||||
// if there are issues with decoding or configuration creation.
|
||||
func ZitifiedTransportFromEnvByName(envVarName string, terminator string) (*http.Transport, error) {
|
||||
b64Zid := os.Getenv(envVarName)
|
||||
if b64Zid == "" {
|
||||
return nil, nil
|
||||
}
|
||||
idReader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(b64Zid))
|
||||
data, err := io.ReadAll(idReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read and decode ziti identity: %v", err)
|
||||
}
|
||||
return NewZitifiedTransportFromSlice(data, terminator)
|
||||
}
|
||||
|
||||
// NewZitifiedTransportFromFile creates a Ziti-enabled HTTP transport by reading
|
||||
// a Ziti configuration from a file. The file should contain JSON-encoded Ziti
|
||||
// configuration data.
|
||||
//
|
||||
// Returns an error if the file cannot be read or contains invalid configuration.
|
||||
func NewZitifiedTransportFromFile(pathToFile string, terminator string) (*http.Transport, error) {
|
||||
data, err := os.ReadFile(pathToFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read ziti identity file %s: %v", pathToFile, err)
|
||||
}
|
||||
return NewZitifiedTransportFromSlice(data, terminator)
|
||||
}
|
||||
+12
-10
@@ -22,6 +22,14 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
openApiRuntime "github.com/go-openapi/runtime"
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/go-openapi/strfmt"
|
||||
@@ -34,17 +42,11 @@ import (
|
||||
"github.com/openziti/edge-api/rest_model"
|
||||
"github.com/openziti/foundation/v2/concurrenz"
|
||||
"github.com/openziti/identity"
|
||||
"github.com/openziti/ziti/controller/api"
|
||||
"github.com/openziti/ziti/controller/env"
|
||||
fabricRestClient "github.com/openziti/ziti/controller/rest_client"
|
||||
"github.com/openziti/ziti/ziti/util"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Clients struct {
|
||||
@@ -111,7 +113,7 @@ func (self *Clients) Authenticate(user, password string) error {
|
||||
}
|
||||
|
||||
func (self *Clients) AuthenticateRequest(request openApiRuntime.ClientRequest, registry strfmt.Registry) error {
|
||||
return request.SetHeaderParam("zt-session", self.token.Load())
|
||||
return request.SetHeaderParam(api.ZitiSession, self.token.Load())
|
||||
}
|
||||
|
||||
func (self *Clients) SetSessionToken(token string) {
|
||||
@@ -159,12 +161,12 @@ func (self *Clients) LoadWellKnownCerts() error {
|
||||
self.host = "https://" + self.host
|
||||
}
|
||||
|
||||
wellKnownCerts, _, err := util.GetWellKnownCerts(self.host)
|
||||
wellKnownCerts, _, err := util.GetWellKnownCerts(self.host, http.Client{})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to retrieve server certificate authority from %v", self.host)
|
||||
}
|
||||
|
||||
certsTrusted, err := util.AreCertsTrusted(self.host, wellKnownCerts)
|
||||
certsTrusted, err := util.AreCertsTrusted(self.host, wellKnownCerts, http.Client{})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to verify well known certs for host %v", self.host)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user