Skip to content

Commit

Permalink
adds authenticator layer to the SDK for the client and management api
Browse files Browse the repository at this point in the history
- 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
andrewpmartinez committed Apr 24, 2023
1 parent ea2528b commit 341b7d0
Show file tree
Hide file tree
Showing 38 changed files with 918 additions and 500 deletions.
57 changes: 57 additions & 0 deletions edge-apis/authwrapper.go
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
}
154 changes: 154 additions & 0 deletions edge-apis/clients.go
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
}
58 changes: 58 additions & 0 deletions edge-apis/component.go
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,
}
}
Loading

0 comments on commit 341b7d0

Please sign in to comment.