/* 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 rest_util import ( "context" "crypto" "crypto/tls" "crypto/x509" "fmt" "github.com/go-openapi/runtime" openapiclient "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" "github.com/openziti/edge-api/rest_management_api_client" "github.com/openziti/edge-api/rest_management_api_client/authentication" "github.com/openziti/edge-api/rest_model" "net/http" "net/url" ) // Authenticator is an interface that facilitates obtaining an API Session. type Authenticator interface { //Authenticate issues an authentication HTTP requests to the designated controller. The method and operation // of this authentication request is determined by the implementor. Authenticate(controllerAddress *url.URL) (*rest_model.CurrentAPISessionDetail, error) //BuildHttpClient returns a http.Client to use for an API client. This specifically allows //client certificate authentication to be configured in the http.Client's transport/tls.Config BuildHttpClient() (*http.Client, error) //SetInfo sets the env and sdk info submitted on Authenticate SetInfo(*rest_model.EnvInfo, *rest_model.SdkInfo) } // HttpClientFunc allows an external HttpClient to be created and used. type HttpClientFunc func(tlsClientConfig *tls.Config) (*http.Client, error) // TlsConfigFunc allows the tls.Config to be modified before use. type TlsConfigFunc func() (*tls.Config, error) // AuthenticatorBase provides embeddable shared capabilities for all // authenticators. type AuthenticatorBase struct { ConfigTypes rest_model.ConfigTypes EnvInfo *rest_model.EnvInfo SdkInfo *rest_model.SdkInfo HttpClientFunc HttpClientFunc TlsConfigFunc TlsConfigFunc RootCas *x509.CertPool } // BuildHttpClientWithModifyTls builds a new http.Client with the provided HttpClientFunc and TlsConfigFunc. // If not set, default NewHttpClientWithTlsConfig and NewTlsConfig will be used. func (a *AuthenticatorBase) BuildHttpClientWithModifyTls(modifyTls func(*tls.Config)) (*http.Client, error) { if a.HttpClientFunc == nil { a.HttpClientFunc = NewHttpClientWithTlsConfig } if a.TlsConfigFunc == nil { a.TlsConfigFunc = NewTlsConfig } tlsConfig, err := a.TlsConfigFunc() tlsConfig.RootCAs = a.RootCas if modifyTls != nil { modifyTls(tlsConfig) } if err != nil { return nil, err } httpClient, err := a.HttpClientFunc(tlsConfig) if err != nil { return nil, err } return httpClient, err } func (a *AuthenticatorBase) SetInfo(env *rest_model.EnvInfo, sdk *rest_model.SdkInfo) { a.EnvInfo = env a.SdkInfo = sdk } var _ Authenticator = &AuthenticatorUpdb{} // AuthenticatorUpdb is an implementation of Authenticator that can fulfill username/password authentication // requests. type AuthenticatorUpdb struct { AuthenticatorBase Username string Password string } func NewAuthenticatorUpdb(username, password string) *AuthenticatorUpdb { return &AuthenticatorUpdb{ Username: username, Password: password, } } func (a *AuthenticatorUpdb) BuildHttpClient() (*http.Client, error) { return a.BuildHttpClientWithModifyTls(nil) } func (a *AuthenticatorUpdb) Params() *authentication.AuthenticateParams { return &authentication.AuthenticateParams{ Auth: &rest_model.Authenticate{ ConfigTypes: a.ConfigTypes, EnvInfo: a.EnvInfo, SdkInfo: a.SdkInfo, Username: rest_model.Username(a.Username), Password: rest_model.Password(a.Password), }, Method: "password", Context: context.Background(), } } func (a *AuthenticatorUpdb) Authenticate(controllerAddress *url.URL) (*rest_model.CurrentAPISessionDetail, error) { httpClient, err := a.BuildHttpClientWithModifyTls(nil) if err != nil { return nil, err } path := rest_management_api_client.DefaultBasePath if controllerAddress.Path != "" && controllerAddress.Path != "/" { path = controllerAddress.Path } clientRuntime := openapiclient.NewWithClient(controllerAddress.Host, path, rest_management_api_client.DefaultSchemes, httpClient) client := rest_management_api_client.New(clientRuntime, nil) params := a.Params() resp, err := client.Authentication.Authenticate(params) if err != nil { return nil, err } if resp.GetPayload() == nil { return nil, fmt.Errorf("error, nil payload: %v", resp.Error()) } return resp.GetPayload().Data, nil } var _ Authenticator = &AuthenticatorCert{} // CertProvider scopes a subset of the identity.Identity interface type CertProvider interface { Cert() *tls.Certificate CA() *x509.CertPool ClientTLSConfig() *tls.Config } // AuthenticatorIdentity is meant to deal with OpenZiti identity files and interfaces defined in the `identity` // repository type AuthenticatorIdentity struct { CertProvider AuthenticatorBase } func (a *AuthenticatorIdentity) BuildHttpClient() (*http.Client, error) { return a.BuildHttpClientWithModifyTls(func(config *tls.Config) { src := a.CertProvider.ClientTLSConfig() config.Certificates = src.Certificates config.RootCAs = src.RootCAs }) } // AuthenticatorCert is an implementation of Authenticator that can fulfill client certificate authentication // requests. type AuthenticatorCert struct { AuthenticatorBase Certificate *x509.Certificate PrivateKey crypto.PrivateKey } func NewAuthenticatorCert(cert *x509.Certificate, privateKey crypto.PrivateKey) *AuthenticatorCert { return &AuthenticatorCert{ Certificate: cert, PrivateKey: privateKey, } } func (a *AuthenticatorCert) BuildHttpClient() (*http.Client, error) { return a.BuildHttpClientWithModifyTls(func(config *tls.Config) { config.Certificates = []tls.Certificate{ { PrivateKey: a.PrivateKey, Leaf: a.Certificate, Certificate: [][]byte{a.Certificate.Raw}, }, } }) } func (a *AuthenticatorCert) Authenticate(controllerAddress *url.URL) (*rest_model.CurrentAPISessionDetail, error) { httpClient, err := a.BuildHttpClient() if err != nil { return nil, err } path := rest_management_api_client.DefaultBasePath if controllerAddress.Path != "" && controllerAddress.Path != "/" { path = controllerAddress.Path } clientRuntime := openapiclient.NewWithClient(controllerAddress.Host, path, rest_management_api_client.DefaultSchemes, httpClient) client := rest_management_api_client.New(clientRuntime, nil) params := a.Params() resp, err := client.Authentication.Authenticate(params) if err != nil { return nil, err } if resp.GetPayload() == nil { return nil, fmt.Errorf("error, nil payload: %v", resp.Error()) } return resp.GetPayload().Data, nil } func (a *AuthenticatorCert) Params() *authentication.AuthenticateParams { return &authentication.AuthenticateParams{ Auth: &rest_model.Authenticate{ ConfigTypes: a.ConfigTypes, EnvInfo: a.EnvInfo, SdkInfo: a.SdkInfo, }, Method: "cert", Context: context.Background(), } } type AuthenticatorAuthHeader struct { AuthenticatorBase Token string } func (a *AuthenticatorAuthHeader) AuthenticateRequest(request runtime.ClientRequest, registry strfmt.Registry) error { return request.SetHeaderParam("authorization", a.Token) } func (a *AuthenticatorAuthHeader) BuildHttpClient() (*http.Client, error) { return a.AuthenticatorBase.BuildHttpClientWithModifyTls(func(config *tls.Config) { config.InsecureSkipVerify = true }) } func NewAuthenticatorAuthHeader(token string) *AuthenticatorAuthHeader { return &AuthenticatorAuthHeader{ Token: token, } } func (a *AuthenticatorAuthHeader) Params() *authentication.AuthenticateParams { return &authentication.AuthenticateParams{ Auth: &rest_model.Authenticate{ ConfigTypes: a.ConfigTypes, EnvInfo: a.EnvInfo, SdkInfo: a.SdkInfo, }, Method: "ext-jwt", Context: context.Background(), } } func (a *AuthenticatorAuthHeader) Authenticate(controllerAddress *url.URL) (*rest_model.CurrentAPISessionDetail, error) { httpClient, err := a.BuildHttpClient() if err != nil { return nil, err } path := rest_management_api_client.DefaultBasePath if controllerAddress.Path != "" && controllerAddress.Path != "/" { path = controllerAddress.Path } clientRuntime := openapiclient.NewWithClient(controllerAddress.Host, path, rest_management_api_client.DefaultSchemes, httpClient) clientRuntime.DefaultAuthentication = &HeaderAuth{ HeaderName: "Authorization", HeaderValue: a.Token, } client := rest_management_api_client.New(clientRuntime, nil) params := a.Params() resp, err := client.Authentication.Authenticate(params) if err != nil { return nil, err } if resp.GetPayload() == nil { return nil, fmt.Errorf("error, nil payload: %v", resp.Error()) } return resp.GetPayload().Data, nil }