From b7345a7a5481f48a858c18d4cdf400a5a3f3db6e Mon Sep 17 00:00:00 2001 From: Andrew Martinez Date: Tue, 12 Mar 2024 09:08:29 -0400 Subject: [PATCH] adds OIDC dynamic SDK check flag, fix ptr ref to unauthed handler - Internal API clients now support a flag to enable or disable OIDC support checks in addition to forcefully setting the OIDC support state. - The unauthenticated SDK event will no longer pass a double pointer to handlers --- edge-apis/authwrapper.go | 88 ++++++++++++++++++++++++++++------------ edge-apis/clients.go | 12 ++++++ ziti/contexts.go | 2 +- ziti/ziti.go | 22 +++++----- 4 files changed, 87 insertions(+), 37 deletions(-) diff --git a/edge-apis/authwrapper.go b/edge-apis/authwrapper.go index 728bb53c..db6ec03c 100644 --- a/edge-apis/authwrapper.go +++ b/edge-apis/authwrapper.go @@ -220,27 +220,40 @@ func (a *ApiSessionOidc) GetExpiresAt() *time.Time { // functionality to the alias type to implement the AuthEnabledApi interface. type ZitiEdgeManagement struct { *rest_management_api_client.ZitiEdgeManagement - useOidc bool - versionOnce sync.Once - versionInfo *rest_model.Version - oidcExplicitSet bool - apiUrl *url.URL + // useOidc tracks if OIDC auth should be used + useOidc bool + + // useOidcExplicitlySet signals if useOidc was set from an external caller and should be used as is + useOidcExplicitlySet bool + + // oidcDynamicallyEnabled will cause the client to check the controller for OIDC support and use if possible as long as useOidc was not explicitly set + oidcDynamicallyEnabled bool //currently defaults false till HA release + + versionOnce sync.Once + versionInfo *rest_model.Version + + apiUrl *url.URL TotpCallback func(chan string) } func (self *ZitiEdgeManagement) Authenticate(credentials Credentials, configTypes []string, httpClient *http.Client) (ApiSession, error) { self.versionOnce.Do(func() { - if self.oidcExplicitSet { + if self.useOidcExplicitlySet { return } - versionParams := manInfo.NewListVersionParams() - versionResp, _ := self.Informational.ListVersion(versionParams) + if self.oidcDynamicallyEnabled { + versionParams := manInfo.NewListVersionParams() + + versionResp, _ := self.Informational.ListVersion(versionParams) - if versionResp != nil { - self.versionInfo = versionResp.Payload.Data - self.useOidc = stringz.Contains(self.versionInfo.Capabilities, string(rest_model.CapabilitiesOIDCAUTH)) + if versionResp != nil { + self.versionInfo = versionResp.Payload.Data + self.useOidc = stringz.Contains(self.versionInfo.Capabilities, string(rest_model.CapabilitiesOIDCAUTH)) + } + } else { + self.useOidc = false } }) @@ -279,10 +292,14 @@ func (self *ZitiEdgeManagement) oidcAuth(credentials Credentials, configTypes [] } func (self *ZitiEdgeManagement) SetUseOidc(use bool) { - self.oidcExplicitSet = true + self.useOidcExplicitlySet = true self.useOidc = use } +func (self *ZitiEdgeManagement) SetAllowOidcDynamicallyEnabled(allow bool) { + self.oidcDynamicallyEnabled = allow +} + func (self *ZitiEdgeManagement) RefreshApiSession(apiSession ApiSession) (ApiSession, error) { switch s := apiSession.(type) { case *ApiSessionLegacy: @@ -317,28 +334,39 @@ func (self *ZitiEdgeManagement) ExchangeTokens(curTokens *oidc.Tokens[*oidc.IDTo // functionality to the alias type to implement the AuthEnabledApi interface. type ZitiEdgeClient struct { *rest_client_api_client.ZitiEdgeClient - useOidc bool - versionInfo *rest_model.Version - versionOnce sync.Once - oidcExplicitSet bool - apiUrl *url.URL + // useOidc tracks if OIDC auth should be used + useOidc bool + + // useOidcExplicitlySet signals if useOidc was set from an external caller and should be used as is + useOidcExplicitlySet bool + + // oidcDynamicallyEnabled will cause the client to check the controller for OIDC support and use if possible as long as useOidc was not explicitly set. + oidcDynamicallyEnabled bool //currently defaults false till HA release + + versionInfo *rest_model.Version + versionOnce sync.Once + apiUrl *url.URL TotpCallback func(chan string) } func (self *ZitiEdgeClient) Authenticate(credentials Credentials, configTypes []string, httpClient *http.Client) (ApiSession, error) { self.versionOnce.Do(func() { - if self.oidcExplicitSet { + if self.useOidcExplicitlySet { return } - versionParams := clientInfo.NewListVersionParams() + if self.oidcDynamicallyEnabled { + versionParams := clientInfo.NewListVersionParams() - versionResp, _ := self.Informational.ListVersion(versionParams) + versionResp, _ := self.Informational.ListVersion(versionParams) - if versionResp != nil { - self.versionInfo = versionResp.Payload.Data - self.useOidc = stringz.Contains(self.versionInfo.Capabilities, string(rest_model.CapabilitiesOIDCAUTH)) + if versionResp != nil { + self.versionInfo = versionResp.Payload.Data + self.useOidc = stringz.Contains(self.versionInfo.Capabilities, string(rest_model.CapabilitiesOIDCAUTH)) + } + } else { + self.useOidc = false } }) @@ -377,21 +405,29 @@ func (self *ZitiEdgeClient) oidcAuth(credentials Credentials, configTypes []stri } func (self *ZitiEdgeClient) SetUseOidc(use bool) { - self.oidcExplicitSet = true + self.useOidcExplicitlySet = true self.useOidc = use } +func (self *ZitiEdgeClient) SetAllowOidcDynamicallyEnabled(allow bool) { + self.oidcDynamicallyEnabled = allow +} + func (self *ZitiEdgeClient) RefreshApiSession(apiSession ApiSession) (ApiSession, error) { switch s := apiSession.(type) { case *ApiSessionLegacy: params := clientApiSession.NewGetCurrentAPISessionParams() - _, err := self.CurrentAPISession.GetCurrentAPISession(params, s) + newApiSessionDetail, err := self.CurrentAPISession.GetCurrentAPISession(params, s) if err != nil { return nil, rest_util.WrapErr(err) } - return s, nil + newApiSession := &ApiSessionLegacy{ + Detail: newApiSessionDetail.Payload.Data, + } + + return newApiSession, nil case *ApiSessionOidc: tokens, err := self.ExchangeTokens(s.OidcTokens) diff --git a/edge-apis/clients.go b/edge-apis/clients.go index 830ac51f..8e70645d 100644 --- a/edge-apis/clients.go +++ b/edge-apis/clients.go @@ -35,7 +35,13 @@ type ApiType interface { } type OidcEnabledApi interface { + // SetUseOidc forces an API Client to operate in OIDC mode (true) or legacy mode (false). The state of the controller + // is ignored and dynamic enable/disable of OIDC support is suspended. SetUseOidc(use bool) + + // SetAllowOidcDynamicallyEnabled sets whether clients will check the controller for OIDC support or not. If supported + // OIDC is favored over legacy authentication. + SetAllowOidcDynamicallyEnabled(allow bool) } // BaseClient implements the Client interface specifically for the types specified in the ApiType constraint. It @@ -64,6 +70,12 @@ func (self *BaseClient[A]) SetUseOidc(use bool) { apiType.SetUseOidc(use) } +func (self *BaseClient[A]) SetAllowOidcDynamicallyEnabled(allow bool) { + v := any(self.API) + apiType := v.(OidcEnabledApi) + apiType.SetAllowOidcDynamicallyEnabled(allow) +} + // Authenticate will attempt to use the provided credentials to authenticate via the underlying ApiType. On success // the API Session details will be returned and the current client will make authenticated requests on future // calls. On an error the API Session in use will be cleared and subsequent requests will become/continue to be diff --git a/ziti/contexts.go b/ziti/contexts.go index e940f0d3..69cef095 100644 --- a/ziti/contexts.go +++ b/ziti/contexts.go @@ -143,7 +143,7 @@ func NewContextWithOpts(cfg *Config, options *Options) (Context, error) { ConfigTypes: cfg.ConfigTypes, } - newContext.CtrlClt.ClientApiClient.SetUseOidc(cfg.EnableHa) + newContext.CtrlClt.ClientApiClient.SetAllowOidcDynamicallyEnabled(cfg.EnableHa) newContext.CtrlClt.PostureCache = posture.NewCache(newContext.CtrlClt, newContext.closeNotify) return newContext, nil diff --git a/ziti/ziti.go b/ziti/ziti.go index 390e8fab..e142b02a 100644 --- a/ziti/ziti.go +++ b/ziti/ziti.go @@ -410,14 +410,15 @@ func (context *ContextImpl) AddAuthenticationStateFullListener(handler func(Cont func (context *ContextImpl) AddAuthenticationStateUnauthenticatedListener(handler func(Context, apis.ApiSession)) func() { listener := func(args ...interface{}) { - apiSession, ok := args[0].(apis.ApiSession) + var apiSession apis.ApiSession - if !ok { - pfxlog.Logger().Fatalf("could not convert args[0] to %T was %T", apiSession, args[0]) - } + if args[0] != nil { + var ok bool + apiSession, ok = args[0].(apis.ApiSession) - if apiSession == nil { - pfxlog.Logger().Fatalf("expected arg[0] was nil, unexpected") + if !ok { + pfxlog.Logger().Fatalf("could not convert args[0] to %T was %T", apiSession, args[0]) + } } handler(context, apiSession) @@ -823,8 +824,8 @@ func (context *ContextImpl) GetCurrentIdentityWithBackoff() (*rest_model.Identit } func (context *ContextImpl) setUnauthenticated() { - prevApiSession := context.CtrlClt.ApiSession.Swap(nil) - willEmit := prevApiSession != nil + prevApiSessionPtr := context.CtrlClt.ApiSession.Swap(nil) + willEmit := prevApiSessionPtr != nil context.CtrlClt.ApiSessionCertificate = nil @@ -832,7 +833,7 @@ func (context *ContextImpl) setUnauthenticated() { context.sessions.Clear() if willEmit { - context.Emit(EventAuthenticationStateUnauthenticated, prevApiSession) + context.Emit(EventAuthenticationStateUnauthenticated, *prevApiSessionPtr) } } @@ -968,8 +969,9 @@ func (context *ContextImpl) authenticateMfa(code string) error { if _, err := context.CtrlClt.Refresh(); err != nil { return err } + apiSession := context.CtrlClt.GetCurrentApiSession() - if apiSession := context.CtrlClt.GetCurrentApiSession(); apiSession != nil && len(apiSession.GetAuthQueries()) == 0 { + if apiSession != nil && len(apiSession.GetAuthQueries()) == 0 { return context.onFullAuth(apiSession) }