Support talking to multiple ORY Hydra deployments (#35)

This commit is contained in:
Paul Davis
2019-11-14 01:11:13 -07:00
committed by hackerman
parent c0bc5dffa5
commit 803c935b47
17 changed files with 404 additions and 51 deletions

View File

@ -34,8 +34,18 @@ import (
const (
ClientIDKey = "client_id"
ClientSecretKey = "client_secret"
FinalizerName = "finalizer.ory.hydra.sh"
)
type HydraClientMakerFunc func(hydrav1alpha1.OAuth2ClientSpec) (HydraClientInterface, error)
type clientMapKey struct {
url string
port int
endpoint string
forwardedProto string
}
type HydraClientInterface interface {
GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error)
ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, error)
@ -46,8 +56,10 @@ type HydraClientInterface interface {
// OAuth2ClientReconciler reconciles a OAuth2Client object
type OAuth2ClientReconciler struct {
HydraClient HydraClientInterface
Log logr.Logger
HydraClient HydraClientInterface
HydraClientMaker HydraClientMakerFunc
Log logr.Logger
otherClients map[clientMapKey]HydraClientInterface
client.Client
}
@ -62,7 +74,7 @@ func (r *OAuth2ClientReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error
var oauth2client hydrav1alpha1.OAuth2Client
if err := r.Get(ctx, req.NamespacedName, &oauth2client); err != nil {
if apierrs.IsNotFound(err) {
if registerErr := r.unregisterOAuth2Clients(ctx, req.Name, req.Namespace); registerErr != nil {
if registerErr := r.unregisterOAuth2Clients(ctx, &oauth2client); registerErr != nil {
return ctrl.Result{}, registerErr
}
return ctrl.Result{}, nil
@ -70,6 +82,38 @@ func (r *OAuth2ClientReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error
return ctrl.Result{}, err
}
// examine DeletionTimestamp to determine if object is under deletion
if oauth2client.ObjectMeta.DeletionTimestamp.IsZero() {
// The object is not being deleted, so if it does not have our finalizer,
// then lets add the finalizer and update the object. This is equivalent
// registering our finalizer.
if !containsString(oauth2client.ObjectMeta.Finalizers, FinalizerName) {
oauth2client.ObjectMeta.Finalizers = append(oauth2client.ObjectMeta.Finalizers, FinalizerName)
if err := r.Update(ctx, &oauth2client); err != nil {
return ctrl.Result{}, err
}
}
} else {
// The object is being deleted
if containsString(oauth2client.ObjectMeta.Finalizers, FinalizerName) {
// our finalizer is present, so lets handle any external dependency
if err := r.unregisterOAuth2Clients(ctx, &oauth2client); err != nil {
// if fail to delete the external dependency here, return with error
// so that it can be retried
return ctrl.Result{}, err
}
// remove our finalizer from the list and update it.
oauth2client.ObjectMeta.Finalizers = removeString(oauth2client.ObjectMeta.Finalizers, FinalizerName)
if err := r.Update(ctx, &oauth2client); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
if oauth2client.Generation != oauth2client.Status.ObservedGeneration {
var secret apiv1.Secret
@ -92,7 +136,21 @@ func (r *OAuth2ClientReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error
return ctrl.Result{}, nil
}
fetched, found, err := r.HydraClient.GetOAuth2Client(string(credentials.ID))
hydraClient, err := r.getHydraClientForClient(oauth2client)
if err != nil {
r.Log.Error(err, fmt.Sprintf(
"hydra address %s:%d%s is invalid",
oauth2client.Spec.HydraAdmin.URL,
oauth2client.Spec.HydraAdmin.Port,
oauth2client.Spec.HydraAdmin.Endpoint,
))
if updateErr := r.updateReconciliationStatusError(ctx, &oauth2client, hydrav1alpha1.StatusInvalidHydraAddress, err); updateErr != nil {
return ctrl.Result{}, updateErr
}
return ctrl.Result{}, nil
}
fetched, found, err := hydraClient.GetOAuth2Client(string(credentials.ID))
if err != nil {
return ctrl.Result{}, err
@ -128,12 +186,17 @@ func (r *OAuth2ClientReconciler) SetupWithManager(mgr ctrl.Manager) error {
}
func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hydrav1alpha1.OAuth2Client, credentials *hydra.Oauth2ClientCredentials) error {
if err := r.unregisterOAuth2Clients(ctx, c.Name, c.Namespace); err != nil {
if err := r.unregisterOAuth2Clients(ctx, c); err != nil {
return err
}
hydra, err := r.getHydraClientForClient(*c)
if err != nil {
return err
}
if credentials != nil {
if _, err := r.HydraClient.PostOAuth2Client(c.ToOAuth2ClientJSON().WithCredentials(credentials)); err != nil {
if _, err := hydra.PostOAuth2Client(c.ToOAuth2ClientJSON().WithCredentials(credentials)); err != nil {
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
return updateErr
}
@ -141,7 +204,7 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy
return r.ensureEmptyStatusError(ctx, c)
}
created, err := r.HydraClient.PostOAuth2Client(c.ToOAuth2ClientJSON())
created, err := hydra.PostOAuth2Client(c.ToOAuth2ClientJSON())
if err != nil {
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
return updateErr
@ -170,7 +233,12 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy
}
func (r *OAuth2ClientReconciler) updateRegisteredOAuth2Client(ctx context.Context, c *hydrav1alpha1.OAuth2Client, credentials *hydra.Oauth2ClientCredentials) error {
if _, err := r.HydraClient.PutOAuth2Client(c.ToOAuth2ClientJSON().WithCredentials(credentials)); err != nil {
hydra, err := r.getHydraClientForClient(*c)
if err != nil {
return err
}
if _, err := hydra.PutOAuth2Client(c.ToOAuth2ClientJSON().WithCredentials(credentials)); err != nil {
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusUpdateFailed, err); updateErr != nil {
return updateErr
}
@ -178,16 +246,27 @@ func (r *OAuth2ClientReconciler) updateRegisteredOAuth2Client(ctx context.Contex
return r.ensureEmptyStatusError(ctx, c)
}
func (r *OAuth2ClientReconciler) unregisterOAuth2Clients(ctx context.Context, name, namespace string) error {
func (r *OAuth2ClientReconciler) unregisterOAuth2Clients(ctx context.Context, c *hydrav1alpha1.OAuth2Client) error {
clients, err := r.HydraClient.ListOAuth2Client()
// if a reqired field is empty, that means this is a delete after
// the finalizers have done their job, so just return
if c.Spec.Scope == "" || c.Spec.SecretName == "" {
return nil
}
hydra, err := r.getHydraClientForClient(*c)
if err != nil {
return err
}
for _, c := range clients {
if c.Owner == fmt.Sprintf("%s/%s", name, namespace) {
if err := r.HydraClient.DeleteOAuth2Client(*c.ClientID); err != nil {
clients, err := hydra.ListOAuth2Client()
if err != nil {
return err
}
for _, cJSON := range clients {
if cJSON.Owner == fmt.Sprintf("%s/%s", c.Name, c.Namespace) {
if err := hydra.DeleteOAuth2Client(*cJSON.ClientID); err != nil {
return err
}
}
@ -237,3 +316,41 @@ func parseSecret(secret apiv1.Secret) (*hydra.Oauth2ClientCredentials, error) {
Password: psw,
}, nil
}
func (r *OAuth2ClientReconciler) getHydraClientForClient(oauth2client hydrav1alpha1.OAuth2Client) (HydraClientInterface, error) {
spec := oauth2client.Spec
if spec.HydraAdmin == (hydrav1alpha1.HydraAdmin{}) {
r.Log.Info(fmt.Sprintf("using default client"))
return r.HydraClient, nil
}
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
}
return r.HydraClientMaker(spec)
}
// Helper functions to check and remove string from a slice of strings.
func containsString(slice []string, s string) bool {
for _, item := range slice {
if item == s {
return true
}
}
return false
}
func removeString(slice []string, s string) (result []string) {
for _, item := range slice {
if item == s {
continue
}
result = append(result, item)
}
return
}

View File

@ -68,6 +68,7 @@ var _ = Describe("OAuth2Client Controller", func() {
Secret: pointer.StringPtr(tstSecret),
GrantTypes: o.GrantTypes,
ResponseTypes: o.ResponseTypes,
RedirectURIs: o.RedirectURIs,
Scope: o.Scope,
Owner: o.Owner,
}
@ -211,6 +212,7 @@ var _ = Describe("OAuth2Client Controller", func() {
Secret: o.Secret,
GrantTypes: o.GrantTypes,
ResponseTypes: o.ResponseTypes,
RedirectURIs: o.RedirectURIs,
Scope: o.Scope,
Owner: o.Owner,
}
@ -366,6 +368,9 @@ func getAPIReconciler(mgr ctrl.Manager, mock controllers.HydraClientInterface) r
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("OAuth2Client"),
HydraClient: mock,
HydraClientMaker: func(hydrav1alpha1.OAuth2ClientSpec) (controllers.HydraClientInterface, error) {
return mock, nil
},
}
}
@ -380,6 +385,13 @@ func testInstance(name, secretName string) *hydrav1alpha1.OAuth2Client {
GrantTypes: []hydrav1alpha1.GrantType{"client_credentials"},
ResponseTypes: []hydrav1alpha1.ResponseType{"token"},
Scope: "a b c",
RedirectURIs: []hydrav1alpha1.RedirectURI{"https://example.com"},
SecretName: secretName,
HydraAdmin: hydrav1alpha1.HydraAdmin{
URL: "http://hydra-admin",
Port: 4445,
Endpoint: "/client",
ForwardedProto: "https",
},
}}
}