Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions edge-apis/authwrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"sync"
"time"

"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/go-resty/resty/v2"
Expand All @@ -28,10 +33,6 @@ import (
"github.com/zitadel/oidc/v3/pkg/client/tokenexchange"
"github.com/zitadel/oidc/v3/pkg/oidc"
"golang.org/x/oauth2"
"net/http"
"net/url"
"sync"
"time"
)

const (
Expand Down Expand Up @@ -81,8 +82,25 @@ type ApiSession interface {
RequiresRouterTokenUpdate() bool

GetRequestHeaders() http.Header

// GetType returns the authentication method used to establish this session, enabling
// callers to determine whether legacy or OIDC-based authentication is in use.
GetType() ApiSessionType
}

// ApiSessionType identifies the authentication mechanism used to establish an API session.
type ApiSessionType string

const (
// ApiSessionTypeLegacy indicates a session created using the original Ziti authentication
// with session tokens passed in the zt-session header.
ApiSessionTypeLegacy ApiSessionType = "legacy"

// ApiSessionTypeOidc indicates a session created using OpenID Connect authentication
// with JWT bearer tokens.
ApiSessionTypeOidc ApiSessionType = "oidc"
)

var _ ApiSession = (*ApiSessionLegacy)(nil)
var _ ApiSession = (*ApiSessionOidc)(nil)

Expand All @@ -93,6 +111,10 @@ type ApiSessionLegacy struct {
RequestHeaders http.Header
}

func (a *ApiSessionLegacy) GetType() ApiSessionType {
return ApiSessionTypeLegacy
}

func (a *ApiSessionLegacy) GetRequestHeaders() http.Header {
return a.RequestHeaders
}
Expand Down Expand Up @@ -170,6 +192,10 @@ type ApiSessionOidc struct {
RequestHeaders http.Header
}

func (a *ApiSessionOidc) GetType() ApiSessionType {
return ApiSessionTypeOidc
}

func (a *ApiSessionOidc) GetRequestHeaders() http.Header {
return a.RequestHeaders
}
Expand Down
13 changes: 13 additions & 0 deletions edge-apis/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,13 @@ type BaseClient[A ApiType] struct {
onControllerListeners []func([]*url.URL)
}

// Url returns the URL of the currently active controller endpoint.
func (self *BaseClient[A]) Url() url.URL {
return *self.AuthEnabledApi.GetClientTransportPool().GetActiveTransport().ApiUrl
}

// AddOnControllerUpdateListeners registers a callback that is invoked when the list of
// available controller endpoints changes.
func (self *BaseClient[A]) AddOnControllerUpdateListeners(listener func([]*url.URL)) {
self.onControllerListeners = append(self.onControllerListeners, listener)
}
Expand All @@ -82,12 +85,15 @@ func (self *BaseClient[A]) GetCurrentApiSession() ApiSession {
return *ptr
}

// SetUseOidc forces the API client to operate in OIDC mode when true, or legacy mode when false.
func (self *BaseClient[A]) SetUseOidc(use bool) {
v := any(self.API)
apiType := v.(OidcEnabledApi)
apiType.SetUseOidc(use)
}

// SetAllowOidcDynamicallyEnabled configures whether the client checks the controller for
// OIDC support and switches modes accordingly.
func (self *BaseClient[A]) SetAllowOidcDynamicallyEnabled(allow bool) {
v := any(self.API)
apiType := v.(OidcEnabledApi)
Expand Down Expand Up @@ -134,6 +140,7 @@ func (self *BaseClient[A]) initializeComponents(config *ApiClientConfig) {
self.Components = *components
}

// NewRuntime creates an OpenAPI runtime configured for the specified API endpoint.
func NewRuntime(apiUrl *url.URL, schemes []string, httpClient *http.Client) *openapiclient.Runtime {
return openapiclient.NewWithClient(apiUrl.Host, apiUrl.Path, schemes, httpClient)
}
Expand Down Expand Up @@ -170,6 +177,8 @@ func (self *BaseClient[A]) AuthenticateRequest(request runtime.ClientRequest, re
return nil
}

// ProcessControllers queries the authenticated controller for its list of peer controllers
// and registers them for high-availability failover.
func (self *BaseClient[A]) ProcessControllers(authEnabledApi AuthEnabledApi) {
list, err := authEnabledApi.ListControllers()

Expand Down Expand Up @@ -208,6 +217,7 @@ type ManagementApiClient struct {
BaseClient[ZitiEdgeManagement]
}

// ApiClientConfig contains configuration options for creating API clients.
type ApiClientConfig struct {
ApiUrls []*url.URL
CaPool *x509.CertPool
Expand Down Expand Up @@ -235,6 +245,7 @@ func NewManagementApiClient(apiUrls []*url.URL, caPool *x509.CertPool, totpCallb
})
}

// NewManagementApiClientWithConfig creates a Management API client using the provided configuration.
func NewManagementApiClientWithConfig(config *ApiClientConfig) *ManagementApiClient {
ret := &ManagementApiClient{}
ret.Schemes = rest_management_api_client.DefaultSchemes
Expand Down Expand Up @@ -264,6 +275,7 @@ func NewManagementApiClientWithConfig(config *ApiClientConfig) *ManagementApiCli
return ret
}

// ClientApiClient provides access to the Ziti Edge Client API for identity operations.
type ClientApiClient struct {
BaseClient[ZitiEdgeClient]
}
Expand All @@ -288,6 +300,7 @@ func NewClientApiClient(apiUrls []*url.URL, caPool *x509.CertPool, totpCallback
})
}

// NewClientApiClientWithConfig creates a Client API client using the provided configuration.
func NewClientApiClientWithConfig(config *ApiClientConfig) *ClientApiClient {
ret := &ClientApiClient{}
ret.ApiBinding = "edge-client"
Expand Down
8 changes: 4 additions & 4 deletions edge-apis/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import (
"time"
)

// Components provides the basic shared lower level pieces used to assemble go-swagger/openapi clients. These
// components are interconnected and have references to each other. This struct is used to set, move, and manage
// them as a set.
// Components provides the foundational HTTP client infrastructure for OpenAPI clients,
// bundling the HTTP client, transport, and certificate pool as a cohesive unit.
type Components struct {
HttpClient *http.Client
HttpTransport *http.Transport
CaPool *x509.CertPool
}

// ComponentsConfig contains configuration options for creating Components.
type ComponentsConfig struct {
Proxy func(*http.Request) (*url.URL, error)
}
Expand All @@ -29,7 +29,7 @@ func NewComponents() *Components {
})
}

// NewComponentsWithConfig assembles a new set of components with reasonable production defaults.
// NewComponentsWithConfig assembles a new set of components using the provided configuration.
func NewComponentsWithConfig(cfg *ComponentsConfig) *Components {
tlsClientConfig, _ := rest_util.NewTlsConfig()

Expand Down
12 changes: 12 additions & 0 deletions edge-apis/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ import (
"time"
)

// JwtTokenPrefix is the standard prefix for JWT tokens, representing the first two characters
// of a Base64URL-encoded JWT header. This prefix is used to identify JWT-format tokens.
const JwtTokenPrefix = "ey"

// ServiceAccessClaims represents the JWT claims for service-level access tokens, including
// identity and session binding information specific to a service connection.
type ServiceAccessClaims struct {
jwt.RegisteredClaims
ApiSessionId string `json:"z_asid"`
Expand All @@ -27,6 +31,8 @@ type ServiceAccessClaims struct {
Type string `json:"z_st"`
}

// ApiAccessClaims represents the JWT claims for API session access tokens, including
// identity attributes, administrative status, and configuration bindings.
type ApiAccessClaims struct {
jwt.RegisteredClaims
ApiSessionId string `json:"z_asid,omitempty"`
Expand Down Expand Up @@ -71,6 +77,8 @@ func (r *IdClaims) GetAudience() (jwt.ClaimStrings, error) {
return jwt.ClaimStrings(r.Audience), nil
}

// localRpServer manages a local HTTP server for OpenID Connect relying party operations,
// handling OAuth callbacks and token exchanges during authentication flows.
type localRpServer struct {
Server *http.Server
Port string
Expand All @@ -81,11 +89,13 @@ type localRpServer struct {
LoginUri string
}

// Stop shuts down the local server and closes the token channel.
func (t *localRpServer) Stop() {
_ = t.Server.Shutdown(context.Background())
close(t.TokenChan)
}

// Start launches the local server and waits for it to become available.
func (t *localRpServer) Start() {
go func() {
_ = t.Server.Serve(t.Listener)
Expand Down Expand Up @@ -118,6 +128,8 @@ func (t *localRpServer) Start() {
}
}

// newLocalRpServer creates and configures a local HTTP server for handling OpenID Connect
// authentication flows, including callback processing and token exchange.
func newLocalRpServer(apiHost string, authMethod string) (*localRpServer, error) {
tokenOutChan := make(chan *oidc.Tokens[*oidc.IDTokenClaims], 1)
result := &localRpServer{
Expand Down
35 changes: 29 additions & 6 deletions edge-apis/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,35 +30,53 @@ import (
errors "github.com/pkg/errors"
)

// ApiClientTransport wraps a runtime.ClientTransport with its associated API URL,
// enabling tracking of which controller endpoint a transport communicates with.
type ApiClientTransport struct {
runtime.ClientTransport
ApiUrl *url.URL
}

// ClientTransportPool abstracts the concept of multiple `runtime.ClientTransport` (openapi interface) representing one
// target OpenZiti network. In situations where controllers are running in HA mode (multiple controllers) this
// interface can attempt to try a different controller during outages or partitioning.
// ClientTransportPool manages multiple runtime.ClientTransport instances representing
// different controller endpoints in a high-availability OpenZiti network. It provides
// automatic failover capabilities when individual controllers become unavailable.
type ClientTransportPool interface {
runtime.ClientTransport

// Add registers a new transport for the specified API URL.
Add(apiUrl *url.URL, transport runtime.ClientTransport)

// Remove unregisters the transport for the specified API URL.
Remove(apiUrl *url.URL)

// GetActiveTransport returns the currently selected transport.
GetActiveTransport() *ApiClientTransport

// SetActiveTransport designates which transport to use for subsequent operations.
SetActiveTransport(*ApiClientTransport)

// GetApiUrls returns all registered API URLs.
GetApiUrls() []*url.URL

// IterateTransportsRandomly provides a channel for iterating through available transports
// in random order.
IterateTransportsRandomly() chan<- *ApiClientTransport

// TryTransportsForOp attempts to execute an operation, trying different transports
// on connection failures.
TryTransportsForOp(operation *runtime.ClientOperation) (any, error)

// TryTransportForF executes a callback function, trying different transports
// on connection failures.
TryTransportForF(cb func(*ApiClientTransport) (any, error)) (any, error)
}

var _ runtime.ClientTransport = (ClientTransportPool)(nil)
var _ ClientTransportPool = (*ClientTransportPoolRandom)(nil)

// ClientTransportPoolRandom selects a client transport (controller) at random until it is unreachable. Controllers
// are tried at random until a controller is reached. The newly connected controller is set for use on future requests
// until is too becomes unreachable.
// ClientTransportPoolRandom implements a randomized failover strategy for controller selection.
// It maintains an active transport and switches to randomly selected alternatives when the active
// transport becomes unreachable.
type ClientTransportPoolRandom struct {
pool cmap.ConcurrentMap[string, *ApiClientTransport]
current atomic.Pointer[ApiClientTransport]
Expand Down Expand Up @@ -107,6 +125,7 @@ func (c *ClientTransportPoolRandom) GetActiveTransport() *ApiClientTransport {
return active
}

// GetApiClientTransports returns a snapshot of all registered transports.
func (c *ClientTransportPoolRandom) GetApiClientTransports() []*ApiClientTransport {
var result []*ApiClientTransport

Expand All @@ -117,6 +136,7 @@ func (c *ClientTransportPoolRandom) GetApiClientTransports() []*ApiClientTranspo
return result
}

// NewClientTransportPoolRandom creates a new transport pool with randomized failover.
func NewClientTransportPoolRandom() *ClientTransportPoolRandom {
return &ClientTransportPoolRandom{
pool: cmap.New[*ApiClientTransport](),
Expand Down Expand Up @@ -215,6 +235,7 @@ func (c *ClientTransportPoolRandom) TryTransportForF(cb func(*ApiClientTransport
return lastResult, lastErr
}

// AnyTransport returns a randomly selected transport from the pool, or nil if empty.
func (c *ClientTransportPoolRandom) AnyTransport() *ApiClientTransport {
transportBuffer := c.pool.Items()
var keys []string
Expand All @@ -237,6 +258,8 @@ var _ ClientTransportPool = (*ClientTransportPoolRandom)(nil)

var opError = &net.OpError{}

// errorIndicatesControllerSwap determines whether an error suggests the need to
// switch to a different controller endpoint.
func errorIndicatesControllerSwap(err error) bool {
pfxlog.Logger().WithError(err).Debugf("checking for network errror on type (%T) and its wrapped errors", err)

Expand Down
2 changes: 1 addition & 1 deletion example/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/michaelquigley/pfxlog v0.6.10
github.com/openziti/edge-api v0.26.48
github.com/openziti/edge-api v0.26.50
github.com/openziti/foundation/v2 v2.0.77
github.com/openziti/runzmd v1.0.83
github.com/openziti/sdk-golang v1.2.6
Expand Down
4 changes: 2 additions & 2 deletions example/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,8 @@ github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/openziti/channel/v4 v4.2.37 h1:oFlYB7PPRzNS/CcwkM80/l2Rkw7z3FDaNbCjiDhdWeg=
github.com/openziti/channel/v4 v4.2.37/go.mod h1:G6UDW+FsTj1NR1vzrOIQEfuShitU9ElHTNlNzkd2dMg=
github.com/openziti/edge-api v0.26.48 h1:u31EKgNOxnepHjcLDqjeNJvLYXYQVvARQPacSlJ1nOE=
github.com/openziti/edge-api v0.26.48/go.mod h1:Sj8HEql6ol2Oqp0yd3ZbGayCg8t/XTlH7q608UDHrwE=
github.com/openziti/edge-api v0.26.50 h1:GNqVfAK4yhIInDl+B58lv1mEFslU0x3yjkDrwePQFys=
github.com/openziti/edge-api v0.26.50/go.mod h1:Sj8HEql6ol2Oqp0yd3ZbGayCg8t/XTlH7q608UDHrwE=
github.com/openziti/foundation/v2 v2.0.77 h1:aHB+qJuXFE9FZ+9GOF53laemoBxXmUZ2XnBbDy3fxmE=
github.com/openziti/foundation/v2 v2.0.77/go.mod h1:rwLV3heBM+S7CtCKCauiozLWGPPejy2p80M1R65d6Lk=
github.com/openziti/go-term-markdown v1.0.1 h1:9uzMpK4tav6OtvRxRt99WwPTzAzCh+Pj9zWU2FBp3Qg=
Expand Down
2 changes: 1 addition & 1 deletion example/influxdb-client-go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ require (
github.com/muhlemmer/gu v0.3.1 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/openziti/channel/v4 v4.2.37 // indirect
github.com/openziti/edge-api v0.26.48 // indirect
github.com/openziti/edge-api v0.26.50 // indirect
github.com/openziti/foundation/v2 v2.0.77 // indirect
github.com/openziti/identity v1.0.116 // indirect
github.com/openziti/metrics v1.4.2 // indirect
Expand Down
4 changes: 2 additions & 2 deletions example/influxdb-client-go/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,8 @@ github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/openziti/channel/v4 v4.2.37 h1:oFlYB7PPRzNS/CcwkM80/l2Rkw7z3FDaNbCjiDhdWeg=
github.com/openziti/channel/v4 v4.2.37/go.mod h1:G6UDW+FsTj1NR1vzrOIQEfuShitU9ElHTNlNzkd2dMg=
github.com/openziti/edge-api v0.26.48 h1:u31EKgNOxnepHjcLDqjeNJvLYXYQVvARQPacSlJ1nOE=
github.com/openziti/edge-api v0.26.48/go.mod h1:Sj8HEql6ol2Oqp0yd3ZbGayCg8t/XTlH7q608UDHrwE=
github.com/openziti/edge-api v0.26.50 h1:GNqVfAK4yhIInDl+B58lv1mEFslU0x3yjkDrwePQFys=
github.com/openziti/edge-api v0.26.50/go.mod h1:Sj8HEql6ol2Oqp0yd3ZbGayCg8t/XTlH7q608UDHrwE=
github.com/openziti/foundation/v2 v2.0.77 h1:aHB+qJuXFE9FZ+9GOF53laemoBxXmUZ2XnBbDy3fxmE=
github.com/openziti/foundation/v2 v2.0.77/go.mod h1:rwLV3heBM+S7CtCKCauiozLWGPPejy2p80M1R65d6Lk=
github.com/openziti/identity v1.0.116 h1:o+vvH1zw0vaG+sn5sVHPWWnxTelZR59QFr9D+4os19g=
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ require (
github.com/mitchellh/go-ps v1.0.0
github.com/mitchellh/mapstructure v1.5.0
github.com/openziti/channel/v4 v4.2.37
github.com/openziti/edge-api v0.26.48
github.com/openziti/edge-api v0.26.50
github.com/openziti/foundation/v2 v2.0.77
github.com/openziti/identity v1.0.116
github.com/openziti/metrics v1.4.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,8 @@ github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/openziti/channel/v4 v4.2.37 h1:oFlYB7PPRzNS/CcwkM80/l2Rkw7z3FDaNbCjiDhdWeg=
github.com/openziti/channel/v4 v4.2.37/go.mod h1:G6UDW+FsTj1NR1vzrOIQEfuShitU9ElHTNlNzkd2dMg=
github.com/openziti/edge-api v0.26.48 h1:u31EKgNOxnepHjcLDqjeNJvLYXYQVvARQPacSlJ1nOE=
github.com/openziti/edge-api v0.26.48/go.mod h1:Sj8HEql6ol2Oqp0yd3ZbGayCg8t/XTlH7q608UDHrwE=
github.com/openziti/edge-api v0.26.50 h1:GNqVfAK4yhIInDl+B58lv1mEFslU0x3yjkDrwePQFys=
github.com/openziti/edge-api v0.26.50/go.mod h1:Sj8HEql6ol2Oqp0yd3ZbGayCg8t/XTlH7q608UDHrwE=
github.com/openziti/foundation/v2 v2.0.77 h1:aHB+qJuXFE9FZ+9GOF53laemoBxXmUZ2XnBbDy3fxmE=
github.com/openziti/foundation/v2 v2.0.77/go.mod h1:rwLV3heBM+S7CtCKCauiozLWGPPejy2p80M1R65d6Lk=
github.com/openziti/identity v1.0.116 h1:o+vvH1zw0vaG+sn5sVHPWWnxTelZR59QFr9D+4os19g=
Expand Down
Loading
Loading