-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adds authenticator layer to the SDK for the client and management api
- remove conf sub modules, renames config.New* to ziti.NewConfig* removes conflicts with app that have their own conf module - adds authenciation mechanisms for client/management APIs - uses new authentication mechanisms for all auth paths, patches old identity.ID configuration for backwards compat - remove go.work/sum file, replaced with replace stanzas in example go.mod files - adds doc - de-dupes IdentityProvider interface - renames ApiAuthenticator to Authenticator - moves client API utilities to client_api - moves management API utilities to management_api - adds edge_apis packaged - single authenticator implementation - makes CaPool on client the default unless set on Credentials
- Loading branch information
1 parent
ea2528b
commit 341b7d0
Showing
38 changed files
with
918 additions
and
500 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Package edge_apis_2 edge_apis_2 provides a wrapper around the generated Edge Client and Management APIs improve ease | ||
// of use. | ||
package edge_apis | ||
|
||
import ( | ||
"github.com/openziti/edge-api/rest_client_api_client" | ||
clientAuthentication "github.com/openziti/edge-api/rest_client_api_client/authentication" | ||
"github.com/openziti/edge-api/rest_management_api_client" | ||
managementAuthentication "github.com/openziti/edge-api/rest_management_api_client/authentication" | ||
"github.com/openziti/edge-api/rest_model" | ||
"net/http" | ||
) | ||
|
||
// AuthEnabledApi is used as a sentinel interface to detect APIs that support authentication and to work around a golang | ||
// limitation dealing with accessing field of generically typed fields. | ||
type AuthEnabledApi interface { | ||
//Authenticate will attempt to issue an authentication request using the provided credentials and http client. | ||
//These function acts as abstraction around the underlying go-swagger generated client and will use the default | ||
//http client if not provided. | ||
Authenticate(credentials Credentials, httpClient *http.Client) (*rest_model.CurrentAPISessionDetail, error) | ||
} | ||
|
||
// ZitiEdgeManagement is an alias of the go-swagger generated client that allows this package to add additional | ||
// functionality to the alias type to implement the AuthEnabledApi interface. | ||
type ZitiEdgeManagement rest_management_api_client.ZitiEdgeManagement | ||
|
||
func (self ZitiEdgeManagement) Authenticate(credentials Credentials, httpClient *http.Client) (*rest_model.CurrentAPISessionDetail, error) { | ||
params := managementAuthentication.NewAuthenticateParams() | ||
params.Auth = credentials.Payload() | ||
params.Method = credentials.Method() | ||
|
||
resp, err := self.Authentication.Authenticate(params, getClientAuthInfoOp(credentials, httpClient)) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return resp.GetPayload().Data, err | ||
} | ||
|
||
// ZitiEdgeClient is an alias of the go-swagger generated client that allows this package to add additional | ||
// functionality to the alias type to implement the AuthEnabledApi interface. | ||
type ZitiEdgeClient rest_client_api_client.ZitiEdgeClient | ||
|
||
func (self ZitiEdgeClient) Authenticate(credentials Credentials, httpClient *http.Client) (*rest_model.CurrentAPISessionDetail, error) { | ||
params := clientAuthentication.NewAuthenticateParams() | ||
params.Auth = credentials.Payload() | ||
params.Method = credentials.Method() | ||
|
||
resp, err := self.Authentication.Authenticate(params, getClientAuthInfoOp(credentials, httpClient)) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return resp.GetPayload().Data, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package edge_apis | ||
|
||
import ( | ||
"crypto/x509" | ||
"github.com/go-openapi/runtime" | ||
"github.com/go-openapi/strfmt" | ||
"github.com/openziti/edge-api/rest_client_api_client" | ||
"github.com/openziti/edge-api/rest_management_api_client" | ||
"github.com/openziti/edge-api/rest_model" | ||
"github.com/pkg/errors" | ||
"net/url" | ||
) | ||
|
||
// ApiType is an interface constraint for generics. The underlying go-swagger types only have fields, which are | ||
// insufficient to attempt to make a generic type from. Instead, this constraint is used that points at the | ||
// aliased types. | ||
type ApiType interface { | ||
ZitiEdgeManagement | ZitiEdgeClient | ||
} | ||
|
||
// BaseClient implements the Client interface specifically for the types specified in the ApiType constraint. It | ||
// provides shared functionality that all ApiType types require. | ||
type BaseClient[A ApiType] struct { | ||
API *A | ||
Components | ||
AuthInfoWriter runtime.ClientAuthInfoWriter | ||
CurrentAPISessionDetail *rest_model.CurrentAPISessionDetail | ||
Credentials Credentials | ||
} | ||
|
||
// GetCurrentApiSession returns the ApiSession that is being used to authenticate requests. | ||
func (self *BaseClient[A]) GetCurrentApiSession() *rest_model.CurrentAPISessionDetail { | ||
return self.CurrentAPISessionDetail | ||
} | ||
|
||
// 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 | ||
// made in an unauthenticated fashion. | ||
func (self *BaseClient[A]) Authenticate(credentials Credentials) (*rest_model.CurrentAPISessionDetail, error) { | ||
//casting to `any` works around golang error that happens when type asserting a generic typed field | ||
myAny := any(self.API) | ||
if a, ok := myAny.(AuthEnabledApi); ok { | ||
self.Credentials = nil | ||
self.CurrentAPISessionDetail = nil | ||
|
||
if credCaPool := credentials.GetCaPool(); credCaPool != nil { | ||
self.HttpTransport.TLSClientConfig.RootCAs = credCaPool | ||
} else { | ||
self.HttpTransport.TLSClientConfig.RootCAs = self.Components.CaPool | ||
} | ||
|
||
apiSession, err := a.Authenticate(credentials, self.HttpClient) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
self.Credentials = credentials | ||
self.CurrentAPISessionDetail = apiSession | ||
|
||
self.Runtime.DefaultAuthentication = runtime.ClientAuthInfoWriterFunc(func(request runtime.ClientRequest, registry strfmt.Registry) error { | ||
if self.CurrentAPISessionDetail != nil && self.CurrentAPISessionDetail.Token != nil && *self.CurrentAPISessionDetail.Token != "" { | ||
if err := request.SetHeaderParam("zt-session", *self.CurrentAPISessionDetail.Token); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if self.Credentials != nil { | ||
if err := self.Credentials.AuthenticateRequest(request, registry); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
}) | ||
|
||
return apiSession, nil | ||
} | ||
return nil, errors.New("authentication not supported") | ||
} | ||
|
||
// initializeComponents assembles the lower level components necessary for the go-swagger/openapi facilities. | ||
func (self *BaseClient[A]) initializeComponents(apiUrl *url.URL, schemes []string, authInfoWriter runtime.ClientAuthInfoWriter, caPool *x509.CertPool) { | ||
components := NewComponents(apiUrl, schemes) | ||
components.HttpTransport.TLSClientConfig.RootCAs = caPool | ||
components.Runtime.DefaultAuthentication = authInfoWriter | ||
components.CaPool = caPool | ||
self.Components = *components | ||
} | ||
|
||
// AuthenticateRequest implements the openapi runtime.ClientAuthInfoWriter interface from the OpenAPI libraries. It is used | ||
// to authenticate outgoing requests. | ||
func (self *BaseClient[A]) AuthenticateRequest(request runtime.ClientRequest, registry strfmt.Registry) error { | ||
if self.AuthInfoWriter != nil { | ||
return self.AuthInfoWriter.AuthenticateRequest(request, registry) | ||
} | ||
return nil | ||
} | ||
|
||
// ManagementApiClient provides the ability to authenticate and interact with the Edge Management API. | ||
type ManagementApiClient struct { | ||
BaseClient[ZitiEdgeManagement] | ||
} | ||
|
||
// NewManagementApiClient will assemble an ManagementApiClient. The apiUrl should be the full URL | ||
// to the Edge Management API (e.g. `https://example.com/edge/management/v1`). | ||
// | ||
// The `caPool` argument should be a list of trusted root CAs. If provided as `nil` here unauthenticated requests | ||
// will use the system certificate pool. If authentication occurs, and a certificate pool is set on the Credentials | ||
// the certificate pool from the Credentials will be used from that point forward. Credentials implementations | ||
// based on an identity.Identity are likely to provide a certificate pool. | ||
// | ||
// For OpenZiti instances not using publicly signed certificates, `ziti.GetControllerWellKnownCaPool()` can be used | ||
// to obtain and verify the target controllers CAs. Tools should allow users to verify and accept new controllers | ||
// that have not been verified from an outside secret (such as an enrollment token). | ||
func NewManagementApiClient(apiUrl *url.URL, caPool *x509.CertPool) *ManagementApiClient { | ||
ret := &ManagementApiClient{} | ||
|
||
ret.initializeComponents(apiUrl, rest_management_api_client.DefaultSchemes, ret, caPool) | ||
|
||
newApi := rest_management_api_client.New(ret.Components.Runtime, nil) | ||
api := ZitiEdgeManagement(*newApi) | ||
ret.API = &api | ||
|
||
return ret | ||
} | ||
|
||
type ClientApiClient struct { | ||
BaseClient[ZitiEdgeClient] | ||
} | ||
|
||
// NewClientApiClient will assemble a ClientApiClient. The apiUrl should be the full URL | ||
// to the Edge Client API (e.g. `https://example.com/edge/client/v1`). | ||
// | ||
// The `caPool` argument should be a list of trusted root CAs. If provided as `nil` here unauthenticated requests | ||
// will use the system certificate pool. If authentication occurs, and a certificate pool is set on the Credentials | ||
// the certificate pool from the Credentials will be used from that point forward. Credentials implementations | ||
// based on an identity.Identity are likely to provide a certificate pool. | ||
// | ||
// For OpenZiti instances not using publicly signed certificates, `ziti.GetControllerWellKnownCaPool()` can be used | ||
// to obtain and verify the target controllers CAs. Tools should allow users to verify and accept new controllers | ||
// that have not been verified from an outside secret (such as an enrollment token). | ||
func NewClientApiClient(apiUrl *url.URL, caPool *x509.CertPool) *ClientApiClient { | ||
ret := &ClientApiClient{} | ||
|
||
ret.initializeComponents(apiUrl, rest_client_api_client.DefaultSchemes, ret, caPool) | ||
|
||
newApi := rest_client_api_client.New(ret.Components.Runtime, nil) | ||
api := ZitiEdgeClient(*newApi) | ||
ret.API = &api | ||
|
||
return ret | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package edge_apis | ||
|
||
import ( | ||
"crypto/x509" | ||
openapiclient "github.com/go-openapi/runtime/client" | ||
"github.com/openziti/edge-api/rest_util" | ||
"net" | ||
"net/http" | ||
"net/http/cookiejar" | ||
"net/url" | ||
"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. | ||
type Components struct { | ||
Runtime *openapiclient.Runtime | ||
HttpClient *http.Client | ||
HttpTransport *http.Transport | ||
CaPool *x509.CertPool | ||
} | ||
|
||
// NewComponents assembles a new set of components with reasonable production defaults. | ||
func NewComponents(api *url.URL, schemes []string) *Components { | ||
tlsClientConfig, _ := rest_util.NewTlsConfig() | ||
|
||
httpTransport := &http.Transport{ | ||
Proxy: http.ProxyFromEnvironment, | ||
DialContext: (&net.Dialer{ | ||
Timeout: 10 * time.Second, | ||
KeepAlive: 10 * time.Second, | ||
}).DialContext, | ||
TLSClientConfig: tlsClientConfig, | ||
ForceAttemptHTTP2: true, | ||
MaxIdleConns: 10, | ||
IdleConnTimeout: 10 * time.Second, | ||
TLSHandshakeTimeout: 10 * time.Second, | ||
ExpectContinueTimeout: 1 * time.Second, | ||
} | ||
|
||
jar, _ := cookiejar.New(nil) | ||
|
||
httpClient := &http.Client{ | ||
Transport: httpTransport, | ||
CheckRedirect: nil, | ||
Jar: jar, | ||
Timeout: 10 * time.Second, | ||
} | ||
|
||
apiRuntime := openapiclient.NewWithClient(api.Host, api.Path, schemes, httpClient) | ||
|
||
return &Components{ | ||
Runtime: apiRuntime, | ||
HttpClient: httpClient, | ||
HttpTransport: httpTransport, | ||
} | ||
} |
Oops, something went wrong.