From 028c3df4c2b8455f4f0fba27e11507490b140590 Mon Sep 17 00:00:00 2001 From: Alexandre Mclean Date: Tue, 14 Sep 2021 08:07:06 -0400 Subject: [PATCH] fix(controller): Ensure that OAuth2Client reconciliation creates hydra client for specs (#83) --- api/v1alpha1/oauth2client_types.go | 51 ------- .../Client.go} | 20 +-- controllers/oauth2client_controller.go | 127 +++++++++++++----- ...auth2client_controller_integration_test.go | 35 +++-- hydra/client.go | 54 ++++++-- hydra/client_test.go | 7 +- hydra/types.go | 53 +++++++- main.go | 47 ++----- 8 files changed, 240 insertions(+), 154 deletions(-) rename controllers/mocks/{HydraClientInterface.go => hydra/Client.go} (75%) diff --git a/api/v1alpha1/oauth2client_types.go b/api/v1alpha1/oauth2client_types.go index b4324d3..d06671f 100644 --- a/api/v1alpha1/oauth2client_types.go +++ b/api/v1alpha1/oauth2client_types.go @@ -16,11 +16,6 @@ limitations under the License. package v1alpha1 import ( - "encoding/json" - "fmt" - - "github.com/ory/hydra-maester/hydra" - "github.com/pkg/errors" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -185,49 +180,3 @@ type OAuth2ClientList struct { func init() { SchemeBuilder.Register(&OAuth2Client{}, &OAuth2ClientList{}) } - -// ToOAuth2ClientJSON converts an OAuth2Client into a OAuth2ClientJSON object that represents an OAuth2 client digestible by ORY Hydra -func (c *OAuth2Client) ToOAuth2ClientJSON() (*hydra.OAuth2ClientJSON, error) { - meta, err := json.Marshal(c.Spec.Metadata) - if err != nil { - return nil, errors.WithMessage(err, "unable to encode `metadata` property value to json") - } - - return &hydra.OAuth2ClientJSON{ - ClientName: c.Spec.ClientName, - GrantTypes: grantToStringSlice(c.Spec.GrantTypes), - ResponseTypes: responseToStringSlice(c.Spec.ResponseTypes), - RedirectURIs: redirectToStringSlice(c.Spec.RedirectURIs), - PostLogoutRedirectURIs: redirectToStringSlice(c.Spec.PostLogoutRedirectURIs), - AllowedCorsOrigins: redirectToStringSlice(c.Spec.AllowedCorsOrigins), - Audience: c.Spec.Audience, - Scope: c.Spec.Scope, - Owner: fmt.Sprintf("%s/%s", c.Name, c.Namespace), - TokenEndpointAuthMethod: string(c.Spec.TokenEndpointAuthMethod), - Metadata: meta, - }, nil -} - -func responseToStringSlice(rt []ResponseType) []string { - var output = make([]string, len(rt)) - for i, elem := range rt { - output[i] = string(elem) - } - return output -} - -func grantToStringSlice(gt []GrantType) []string { - var output = make([]string, len(gt)) - for i, elem := range gt { - output[i] = string(elem) - } - return output -} - -func redirectToStringSlice(ru []RedirectURI) []string { - var output = make([]string, len(ru)) - for i, elem := range ru { - output[i] = string(elem) - } - return output -} diff --git a/controllers/mocks/HydraClientInterface.go b/controllers/mocks/hydra/Client.go similarity index 75% rename from controllers/mocks/HydraClientInterface.go rename to controllers/mocks/hydra/Client.go index 84943a4..bb17b9c 100644 --- a/controllers/mocks/HydraClientInterface.go +++ b/controllers/mocks/hydra/Client.go @@ -2,16 +2,18 @@ package mocks -import hydra "github.com/ory/hydra-maester/hydra" -import mock "github.com/stretchr/testify/mock" +import ( + hydra "github.com/ory/hydra-maester/hydra" + mock "github.com/stretchr/testify/mock" +) -// HydraClientInterface is an autogenerated mock type for the HydraClientInterface type -type HydraClientInterface struct { +// Client is an autogenerated mock type for the Client type +type Client struct { mock.Mock } // DeleteOAuth2Client provides a mock function with given fields: id -func (_m *HydraClientInterface) DeleteOAuth2Client(id string) error { +func (_m *Client) DeleteOAuth2Client(id string) error { ret := _m.Called(id) var r0 error @@ -25,7 +27,7 @@ func (_m *HydraClientInterface) DeleteOAuth2Client(id string) error { } // GetOAuth2Client provides a mock function with given fields: id -func (_m *HydraClientInterface) GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error) { +func (_m *Client) GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error) { ret := _m.Called(id) var r0 *hydra.OAuth2ClientJSON @@ -55,7 +57,7 @@ func (_m *HydraClientInterface) GetOAuth2Client(id string) (*hydra.OAuth2ClientJ } // ListOAuth2Client provides a mock function with given fields: -func (_m *HydraClientInterface) ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, error) { +func (_m *Client) ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, error) { ret := _m.Called() var r0 []*hydra.OAuth2ClientJSON @@ -78,7 +80,7 @@ func (_m *HydraClientInterface) ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, e } // PostOAuth2Client provides a mock function with given fields: o -func (_m *HydraClientInterface) PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) { +func (_m *Client) PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) { ret := _m.Called(o) var r0 *hydra.OAuth2ClientJSON @@ -101,7 +103,7 @@ func (_m *HydraClientInterface) PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hy } // PutOAuth2Client provides a mock function with given fields: o -func (_m *HydraClientInterface) PutOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) { +func (_m *Client) PutOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) { ret := _m.Called(o) var r0 *hydra.OAuth2ClientJSON diff --git a/controllers/oauth2client_controller.go b/controllers/oauth2client_controller.go index 623f420..a839d39 100644 --- a/controllers/oauth2client_controller.go +++ b/controllers/oauth2client_controller.go @@ -18,9 +18,9 @@ package controllers import ( "context" "fmt" + "sync" + "github.com/go-logr/logr" - hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1" - "github.com/ory/hydra-maester/hydra" "github.com/pkg/errors" apiv1 "k8s.io/api/core/v1" apierrs "k8s.io/apimachinery/pkg/api/errors" @@ -28,36 +28,89 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + + hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1" + "github.com/ory/hydra-maester/hydra" ) const ( ClientIDKey = "client_id" ClientSecretKey = "client_secret" FinalizerName = "finalizer.ory.hydra.sh" + + DefaultNamespace = "default" ) -type clientMapKey struct { +type clientKey struct { url string port int endpoint string forwardedProto string } -type HydraClientInterface interface { - GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error) - ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, error) - PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) - PutOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) - DeleteOAuth2Client(id string) error -} +// OAuth2ClientFactory is a function that creates oauth2 client. +// The OAuth2ClientReconciler defaults to use hydra.New and the factory allows +// to override this behavior for mocks during tests. +type OAuth2ClientFactory func( + spec hydrav1alpha1.OAuth2ClientSpec, + tlsTrustStore string, + insecureSkipVerify bool, +) (hydra.Client, error) -// OAuth2ClientReconciler reconciles a OAuth2Client object +// OAuth2ClientReconciler reconciles a OAuth2Client object. type OAuth2ClientReconciler struct { - HydraClient HydraClientInterface - Log logr.Logger - otherClients map[clientMapKey]HydraClientInterface client.Client + HydraClient hydra.Client + Log logr.Logger ControllerNamespace string + + oauth2Clients map[clientKey]hydra.Client + oauth2ClientFactory OAuth2ClientFactory + mu sync.Mutex +} + +// Options represent options to pass to the oauth2 client reconciler. +type Options struct { + Namespace string + OAuth2ClientFactory OAuth2ClientFactory +} + +// Option is a functional option. +type Option func(*Options) + +// WithNamespace sets the kubernetes namespace for the controller. +// The default is "default". +func WithNamespace(ns string) Option { + return func(o *Options) { + o.Namespace = ns + } +} + +// WithClientFactory sets a function to create new oauth2 clients during the reconciliation logic. +func WithClientFactory(factory OAuth2ClientFactory) Option { + return func(o *Options) { + o.OAuth2ClientFactory = factory + } +} + +// New returns a new Oauth2ClientReconciler. +func New(c client.Client, hydraClient hydra.Client, log logr.Logger, opts ...Option) *OAuth2ClientReconciler { + options := &Options{ + Namespace: DefaultNamespace, + OAuth2ClientFactory: hydra.New, + } + for _, opt := range opts { + opt(options) + } + + return &OAuth2ClientReconciler{ + Client: c, + HydraClient: hydraClient, + Log: log, + ControllerNamespace: options.Namespace, + oauth2Clients: make(map[clientKey]hydra.Client, 0), + oauth2ClientFactory: options.OAuth2ClientFactory, + } } // +kubebuilder:rbac:groups=hydra.ory.sh,resources=oauth2clients,verbs=get;list;watch;create;update;patch;delete @@ -200,12 +253,12 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy return err } - hydra, err := r.getHydraClientForClient(*c) + hydraClient, err := r.getHydraClientForClient(*c) if err != nil { return err } - oauth2client, err := c.ToOAuth2ClientJSON() + oauth2client, err := hydra.FromOAuth2Client(c) if err != nil { if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil { return updateErr @@ -214,7 +267,7 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy } if credentials != nil { - if _, err := hydra.PostOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil { + if _, err := hydraClient.PostOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil { if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil { return updateErr } @@ -222,7 +275,7 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy return r.ensureEmptyStatusError(ctx, c) } - created, err := hydra.PostOAuth2Client(oauth2client) + created, err := hydraClient.PostOAuth2Client(oauth2client) if err != nil { if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil { return updateErr @@ -260,12 +313,12 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy } func (r *OAuth2ClientReconciler) updateRegisteredOAuth2Client(ctx context.Context, c *hydrav1alpha1.OAuth2Client, credentials *hydra.Oauth2ClientCredentials) error { - hydra, err := r.getHydraClientForClient(*c) + hydraClient, err := r.getHydraClientForClient(*c) if err != nil { return err } - oauth2client, err := c.ToOAuth2ClientJSON() + oauth2client, err := hydra.FromOAuth2Client(c) if err != nil { if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusUpdateFailed, err); updateErr != nil { return updateErr @@ -273,7 +326,7 @@ func (r *OAuth2ClientReconciler) updateRegisteredOAuth2Client(ctx context.Contex return errors.WithStack(err) } - if _, err := hydra.PutOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil { + if _, err := hydraClient.PutOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil { if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusUpdateFailed, err); updateErr != nil { return updateErr } @@ -352,17 +405,31 @@ func parseSecret(secret apiv1.Secret, authMethod hydrav1alpha1.TokenEndpointAuth }, nil } -func (r *OAuth2ClientReconciler) getHydraClientForClient(oauth2client hydrav1alpha1.OAuth2Client) (HydraClientInterface, error) { +func (r *OAuth2ClientReconciler) getHydraClientForClient( + oauth2client hydrav1alpha1.OAuth2Client) (hydra.Client, error) { spec := oauth2client.Spec - key := clientMapKey{ - url: spec.HydraAdmin.URL, - port: spec.HydraAdmin.Port, - endpoint: spec.HydraAdmin.Endpoint, - forwardedProto: spec.HydraAdmin.ForwardedProto, - } - if c, ok := r.otherClients[key]; ok { - return c, nil + if spec.HydraAdmin.URL != "" { + key := clientKey{ + url: spec.HydraAdmin.URL, + port: spec.HydraAdmin.Port, + endpoint: spec.HydraAdmin.Endpoint, + forwardedProto: spec.HydraAdmin.ForwardedProto, + } + r.mu.Lock() + defer r.mu.Unlock() + if c, ok := r.oauth2Clients[key]; ok { + return c, nil + } + + client, err := r.oauth2ClientFactory(spec, "", false) + if err != nil { + return nil, errors.Wrap(err, "cannot create oauth2 client from CRD") + } + + r.oauth2Clients[key] = client + return client, nil } + if r.HydraClient == nil { return nil, errors.New("Not default client or other clients configured") } diff --git a/controllers/oauth2client_controller_integration_test.go b/controllers/oauth2client_controller_integration_test.go index 4ddc2f6..64c77cc 100644 --- a/controllers/oauth2client_controller_integration_test.go +++ b/controllers/oauth2client_controller_integration_test.go @@ -10,10 +10,6 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1" - "github.com/ory/hydra-maester/controllers" - "github.com/ory/hydra-maester/controllers/mocks" - "github.com/ory/hydra-maester/hydra" . "github.com/stretchr/testify/mock" apiv1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -27,6 +23,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" + + hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1" + "github.com/ory/hydra-maester/controllers" + mocks "github.com/ory/hydra-maester/controllers/mocks/hydra" + "github.com/ory/hydra-maester/hydra" ) const ( @@ -59,7 +60,7 @@ var _ = Describe("OAuth2Client Controller", func() { Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() - mch := &mocks.HydraClientInterface{} + mch := &mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("DeleteOAuth2Client", Anything).Return(nil) mch.On("ListOAuth2Client", Anything).Return(nil, nil) @@ -138,7 +139,7 @@ var _ = Describe("OAuth2Client Controller", func() { Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() - mch := &mocks.HydraClientInterface{} + mch := &mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("PostOAuth2Client", Anything).Return(nil, errors.New("error")) mch.On("DeleteOAuth2Client", Anything).Return(nil) @@ -168,6 +169,7 @@ var _ = Describe("OAuth2Client Controller", func() { err = c.Get(context.TODO(), ok, &retrieved) Expect(err).NotTo(HaveOccurred()) Expect(retrieved.Status.ReconciliationError).NotTo(BeNil()) + Expect(retrieved.Status.ReconciliationError.Code).To(Equal(hydrav1alpha1.StatusRegistrationFailed)) Expect(retrieved.Status.ReconciliationError.Description).To(Equal("error")) @@ -204,7 +206,7 @@ var _ = Describe("OAuth2Client Controller", func() { Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() - mch := mocks.HydraClientInterface{} + mch := mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("DeleteOAuth2Client", Anything).Return(nil) mch.On("ListOAuth2Client", Anything).Return(nil, nil) @@ -299,7 +301,7 @@ var _ = Describe("OAuth2Client Controller", func() { Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() - mch := mocks.HydraClientInterface{} + mch := mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("DeleteOAuth2Client", Anything).Return(nil) mch.On("ListOAuth2Client", Anything).Return(nil, nil) @@ -369,7 +371,7 @@ var _ = Describe("OAuth2Client Controller", func() { Expect(err).NotTo(HaveOccurred()) c := mgr.GetClient() - mch := &mocks.HydraClientInterface{} + mch := &mocks.Client{} mch.On("GetOAuth2Client", Anything).Return(nil, false, nil) mch.On("DeleteOAuth2Client", Anything).Return(nil) mch.On("ListOAuth2Client", Anything).Return(nil, nil) @@ -460,12 +462,17 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { return nil } -func getAPIReconciler(mgr ctrl.Manager, mock controllers.HydraClientInterface) reconcile.Reconciler { - return &controllers.OAuth2ClientReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("OAuth2Client"), - HydraClient: mock, +func getAPIReconciler(mgr ctrl.Manager, mock hydra.Client) reconcile.Reconciler { + clientMocker := func(spec hydrav1alpha1.OAuth2ClientSpec, tlsTrustStore string, insecureSkipVerify bool) (hydra.Client, error) { + return mock, nil } + + return controllers.New( + mgr.GetClient(), + mock, + ctrl.Log.WithName("controllers").WithName("OAuth2Client"), + controllers.WithClientFactory(clientMocker), + ) } func testInstance(name, secretName string) *hydrav1alpha1.OAuth2Client { diff --git a/hydra/client.go b/hydra/client.go index 40daba0..2bae5ab 100644 --- a/hydra/client.go +++ b/hydra/client.go @@ -8,15 +8,51 @@ import ( "net/http" "net/url" "path" + + hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1" + "github.com/ory/hydra-maester/helpers" ) -type Client struct { +type Client interface { + GetOAuth2Client(id string) (*OAuth2ClientJSON, bool, error) + ListOAuth2Client() ([]*OAuth2ClientJSON, error) + PostOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) + PutOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) + DeleteOAuth2Client(id string) error +} + +type InternalClient struct { HydraURL url.URL HTTPClient *http.Client ForwardedProto string } -func (c *Client) GetOAuth2Client(id string) (*OAuth2ClientJSON, bool, error) { +// New returns a new hydra InternalClient instance. +func New(spec hydrav1alpha1.OAuth2ClientSpec, tlsTrustStore string, insecureSkipVerify bool) (Client, error) { + address := fmt.Sprintf("%s:%d", spec.HydraAdmin.URL, spec.HydraAdmin.Port) + u, err := url.Parse(address) + if err != nil { + return nil, err + } + + c, err := helpers.CreateHttpClient(insecureSkipVerify, tlsTrustStore) + if err != nil { + return nil, err + } + + client := &InternalClient{ + HydraURL: *u.ResolveReference(&url.URL{Path: spec.HydraAdmin.Endpoint}), + HTTPClient: c, + } + + if spec.HydraAdmin.ForwardedProto != "" && spec.HydraAdmin.ForwardedProto != "off" { + client.ForwardedProto = spec.HydraAdmin.ForwardedProto + } + + return client, nil +} + +func (c *InternalClient) GetOAuth2Client(id string) (*OAuth2ClientJSON, bool, error) { var jsonClient *OAuth2ClientJSON @@ -40,7 +76,7 @@ func (c *Client) GetOAuth2Client(id string) (*OAuth2ClientJSON, bool, error) { } } -func (c *Client) ListOAuth2Client() ([]*OAuth2ClientJSON, error) { +func (c *InternalClient) ListOAuth2Client() ([]*OAuth2ClientJSON, error) { var jsonClientList []*OAuth2ClientJSON @@ -62,7 +98,7 @@ func (c *Client) ListOAuth2Client() ([]*OAuth2ClientJSON, error) { } } -func (c *Client) PostOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) { +func (c *InternalClient) PostOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) { var jsonClient *OAuth2ClientJSON @@ -86,7 +122,7 @@ func (c *Client) PostOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error } } -func (c *Client) PutOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) { +func (c *InternalClient) PutOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) { var jsonClient *OAuth2ClientJSON @@ -107,7 +143,7 @@ func (c *Client) PutOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) return jsonClient, nil } -func (c *Client) DeleteOAuth2Client(id string) error { +func (c *InternalClient) DeleteOAuth2Client(id string) error { req, err := c.newRequest(http.MethodDelete, id, nil) if err != nil { @@ -123,14 +159,14 @@ func (c *Client) DeleteOAuth2Client(id string) error { case http.StatusNoContent: return nil case http.StatusNotFound: - fmt.Printf("client with id %s does not exist", id) + fmt.Printf("InternalClient with id %s does not exist", id) return nil default: return fmt.Errorf("%s %s http request returned unexpected status code %s", req.Method, req.URL.String(), resp.Status) } } -func (c *Client) newRequest(method, relativePath string, body interface{}) (*http.Request, error) { +func (c *InternalClient) newRequest(method, relativePath string, body interface{}) (*http.Request, error) { var buf io.ReadWriter if body != nil { @@ -162,7 +198,7 @@ func (c *Client) newRequest(method, relativePath string, body interface{}) (*htt } -func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) { +func (c *InternalClient) do(req *http.Request, v interface{}) (*http.Response, error) { resp, err := c.HTTPClient.Do(req) if err != nil { return nil, err diff --git a/hydra/client_test.go b/hydra/client_test.go index cbc3913..f33bd2e 100644 --- a/hydra/client_test.go +++ b/hydra/client_test.go @@ -12,9 +12,10 @@ import ( "k8s.io/utils/pointer" - "github.com/ory/hydra-maester/hydra" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/ory/hydra-maester/hydra" ) const ( @@ -60,7 +61,7 @@ func TestCRUD(t *testing.T) { assert := assert.New(t) - c := hydra.Client{ + c := hydra.InternalClient{ HTTPClient: &http.Client{}, HydraURL: url.URL{Scheme: schemeHTTP}, } @@ -397,7 +398,7 @@ func TestCRUD(t *testing.T) { }) } -func runServer(c *hydra.Client, h http.HandlerFunc) { +func runServer(c *hydra.InternalClient, h http.HandlerFunc) { s := httptest.NewServer(h) serverUrl, _ := url.Parse(s.URL) c.HydraURL = *serverUrl.ResolveReference(&url.URL{Path: clientsEndpoint}) diff --git a/hydra/types.go b/hydra/types.go index 24de0cf..b71c4dc 100644 --- a/hydra/types.go +++ b/hydra/types.go @@ -2,8 +2,12 @@ package hydra import ( "encoding/json" + "fmt" + "github.com/pkg/errors" "k8s.io/utils/pointer" + + hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1" ) // OAuth2ClientJSON represents an OAuth2 client digestible by ORY Hydra @@ -23,7 +27,8 @@ type OAuth2ClientJSON struct { Metadata json.RawMessage `json:"metadata,omitempty"` } -// Oauth2ClientCredentials represents client ID and password fetched from a Kubernetes secret +// Oauth2ClientCredentials represents client ID and password fetched from a +// Kubernetes secret type Oauth2ClientCredentials struct { ID []byte Password []byte @@ -36,3 +41,49 @@ func (oj *OAuth2ClientJSON) WithCredentials(credentials *Oauth2ClientCredentials } return oj } + +// FromOAuth2Client converts an OAuth2Client into a OAuth2ClientJSON object that represents an OAuth2 InternalClient digestible by ORY Hydra +func FromOAuth2Client(c *hydrav1alpha1.OAuth2Client) (*OAuth2ClientJSON, error) { + meta, err := json.Marshal(c.Spec.Metadata) + if err != nil { + return nil, errors.WithMessage(err, "unable to encode `metadata` property value to json") + } + + return &OAuth2ClientJSON{ + ClientName: c.Spec.ClientName, + GrantTypes: grantToStringSlice(c.Spec.GrantTypes), + ResponseTypes: responseToStringSlice(c.Spec.ResponseTypes), + RedirectURIs: redirectToStringSlice(c.Spec.RedirectURIs), + PostLogoutRedirectURIs: redirectToStringSlice(c.Spec.PostLogoutRedirectURIs), + AllowedCorsOrigins: redirectToStringSlice(c.Spec.AllowedCorsOrigins), + Audience: c.Spec.Audience, + Scope: c.Spec.Scope, + Owner: fmt.Sprintf("%s/%s", c.Name, c.Namespace), + TokenEndpointAuthMethod: string(c.Spec.TokenEndpointAuthMethod), + Metadata: meta, + }, nil +} + +func responseToStringSlice(rt []hydrav1alpha1.ResponseType) []string { + var output = make([]string, len(rt)) + for i, elem := range rt { + output[i] = string(elem) + } + return output +} + +func grantToStringSlice(gt []hydrav1alpha1.GrantType) []string { + var output = make([]string, len(gt)) + for i, elem := range gt { + output[i] = string(elem) + } + return output +} + +func redirectToStringSlice(ru []hydrav1alpha1.RedirectURI) []string { + var output = make([]string, len(ru)) + for i, elem := range ru { + output[i] = string(elem) + } + return output +} diff --git a/main.go b/main.go index f808538..e11ca65 100644 --- a/main.go +++ b/main.go @@ -18,21 +18,19 @@ package main import ( "flag" "fmt" - "net/url" "os" "time" - "github.com/ory/hydra-maester/helpers" - "github.com/ory/hydra-maester/hydra" - hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1" - "github.com/ory/hydra-maester/controllers" apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log/zap" + + hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1" + "github.com/ory/hydra-maester/controllers" // +kubebuilder:scaffold:imports ) @@ -109,19 +107,19 @@ func main() { } } - hydraClient, err := getHydraClient(defaultSpec, tlsTrustStore, insecureSkipVerify) + hydraClient, err := hydra.New(defaultSpec, tlsTrustStore, insecureSkipVerify) if err != nil { setupLog.Error(err, "making default hydra client", "controller", "OAuth2Client") os.Exit(1) } - err = (&controllers.OAuth2ClientReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("OAuth2Client"), - HydraClient: hydraClient, - ControllerNamespace: namespace, - }).SetupWithManager(mgr) + err = controllers.New( + mgr.GetClient(), + hydraClient, + ctrl.Log.WithName("controllers").WithName("OAuth2Client"), + controllers.WithNamespace(namespace), + ).SetupWithManager(mgr) if err != nil { setupLog.Error(err, "unable to create controller", "controller", "OAuth2Client") os.Exit(1) @@ -134,28 +132,3 @@ func main() { os.Exit(1) } } - -func getHydraClient(spec hydrav1alpha1.OAuth2ClientSpec, tlsTrustStore string, insecureSkipVerify bool) (controllers.HydraClientInterface, error) { - - address := fmt.Sprintf("%s:%d", spec.HydraAdmin.URL, spec.HydraAdmin.Port) - u, err := url.Parse(address) - if err != nil { - return nil, err - } - - c, err := helpers.CreateHttpClient(insecureSkipVerify, tlsTrustStore) - if err != nil { - return nil, err - } - - client := &hydra.Client{ - HydraURL: *u.ResolveReference(&url.URL{Path: spec.HydraAdmin.Endpoint}), - HTTPClient: c, - } - - if spec.HydraAdmin.ForwardedProto != "" && spec.HydraAdmin.ForwardedProto != "off" { - client.ForwardedProto = spec.HydraAdmin.ForwardedProto - } - - return client, nil -}