From cd6b3e54770a4579b13593af6fab3148efd5590c Mon Sep 17 00:00:00 2001 From: David Gogl <1381862+kengou@users.noreply.github.com> Date: Fri, 10 Jan 2025 00:00:09 +0100 Subject: [PATCH] feat(dex): create connector and client to sql along with the CRDs --- cmd/greenhouse/controllers.go | 5 ++ cmd/greenhouse/main.go | 9 +++ go.mod | 3 + pkg/controllers/organization/dex.go | 64 +++++++++++++++++++ .../organization/organization_controller.go | 23 +++++++ pkg/idproxy/storage.go | 26 ++++++++ 6 files changed, 130 insertions(+) diff --git a/cmd/greenhouse/controllers.go b/cmd/greenhouse/controllers.go index 9803b9ee7..1f4809969 100644 --- a/cmd/greenhouse/controllers.go +++ b/cmd/greenhouse/controllers.go @@ -78,6 +78,11 @@ func startOrganizationReconciler(name string, mgr ctrl.Manager) error { } return (&organizationcontrollers.OrganizationReconciler{ Namespace: namespace, + PGDB: pgDB, + PGHost: pgHost, + PGPort: pgPort, + PGUser: pgUser, + PGPasswd: pgPasswd, }).SetupWithManager(name, mgr) } diff --git a/cmd/greenhouse/main.go b/cmd/greenhouse/main.go index 4b0994e38..137f30efa 100644 --- a/cmd/greenhouse/main.go +++ b/cmd/greenhouse/main.go @@ -58,6 +58,9 @@ var ( remoteClusterBearerTokenValidity, renewRemoteClusterBearerTokenAfter time.Duration kubeClientOpts clientutil.RuntimeOptions + // DB connection parameters + pgDB, pgHost, pgUser, pgPasswd string + pgPort uint16 ) func init() { @@ -91,6 +94,12 @@ func main() { flag.StringVar(&common.DNSDomain, "dns-domain", "", "The DNS domain to use for the Greenhouse central cluster") + flag.StringVar(&pgDB, "database", os.Getenv("DB_NAME"), "Database name") + flag.StringVar(&pgHost, "dbHost", os.Getenv("DB_HOST"), "Database host") + flag.Uint16Var(&pgPort, "dbPort", 5432, "Database port") + flag.StringVar(&pgUser, "dbUser", os.Getenv("DB_USER"), "Database user") + flag.StringVar(&pgPasswd, "dbPassword", os.Getenv("DB_PASSWORD"), "Database password") + opts := zap.Options{ Development: true, TimeEncoder: zapcore.RFC3339TimeEncoder, diff --git a/go.mod b/go.mod index cc4e450de..402e1b180 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,7 @@ require ( cloud.google.com/go/auth v0.9.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect dario.cat/mergo v1.0.1 // indirect + filippo.io/edwards25519 v1.1.0 // indirect github.com/Microsoft/hcsshim v0.12.6 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/containerd/errdefs v0.1.0 // indirect @@ -64,11 +65,13 @@ require ( github.com/creack/pty v1.1.23 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/miekg/dns v1.1.58 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect diff --git a/pkg/controllers/organization/dex.go b/pkg/controllers/organization/dex.go index b73c7c13d..5ef1b75ff 100644 --- a/pkg/controllers/organization/dex.go +++ b/pkg/controllers/organization/dex.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/dexidp/dex/connector/oidc" + "github.com/dexidp/dex/storage" "github.com/pkg/errors" "golang.org/x/text/cases" "golang.org/x/text/language" @@ -88,6 +89,33 @@ func (r *OrganizationReconciler) reconcileDexConnector(ctx context.Context, org log.FromContext(ctx).Info("updated dex connector", "namespace", dexConnector.Namespace, "name", dexConnector.GetName()) r.recorder.Eventf(org, corev1.EventTypeNormal, "UpdatedDexConnector", "Updated dex connector %s/%s", dexConnector.Namespace, dexConnector.GetName()) } + + // Create the connectors also in SQL storage + var oidcConnector storage.Connector + if oidcConnector, err = r.sqlDexStorage.GetConnector(org.Name); err != nil { + if err = r.sqlDexStorage.CreateConnector(ctx, storage.Connector{ + ID: org.Name, + Type: dexConnectorTypeGreenhouse, + Name: cases.Title(language.English).String(org.Name), + Config: configByte, + }); err != nil { + return err + } + log.FromContext(ctx).Info("created dex connector in SQL storage", "name", org.Name) + r.recorder.Eventf(org, corev1.EventTypeNormal, "CreatedDexConnectorSQL", "Created dex connector in SQL storage %s", org.Name) + } + if err = r.sqlDexStorage.UpdateConnector(oidcConnector.ID, func(c storage.Connector) (storage.Connector, error) { + c.ID = org.Name + c.Type = dexConnectorTypeGreenhouse + c.Name = cases.Title(language.English).String(org.Name) + c.Config = configByte + return c, nil + }); err != nil { + return err + } + log.FromContext(ctx).Info("updated dex connector in SQL storage", "name", org.Name) + r.recorder.Eventf(org, corev1.EventTypeNormal, "UpdatedDexConnectorSQL", "Updated dex connector in SQL storage %s", org.Name) + return nil } @@ -118,6 +146,42 @@ func (r *OrganizationReconciler) discoverOIDCRedirectURL(ctx context.Context, or } func (r *OrganizationReconciler) reconcileOAuth2Client(ctx context.Context, org *greenhousesapv1alpha1.Organization) error { + var oAuthClient storage.Client + var err error + if oAuthClient, err = r.sqlDexStorage.GetClient(org.Name); err != nil { + if err = r.sqlDexStorage.CreateClient(ctx, storage.Client{ + Public: true, + ID: org.Name, + Name: org.Name, + RedirectURIs: []string{ + "https://dashboard." + common.DNSDomain, + fmt.Sprintf("https://%s.dashboard.%s", org.Name, common.DNSDomain), + }, + }); err != nil { + return err + } + log.FromContext(ctx).Info("created oauth2client", "name", org.Name) + r.recorder.Eventf(org, corev1.EventTypeNormal, "CreatedOAuth2Client", "Created oauth2client %s", org.Name) + return nil + } + + if err = r.sqlDexStorage.UpdateClient(oAuthClient.Name, func(authClient storage.Client) (storage.Client, error) { + authClient.Public = true + authClient.ID = org.Name + authClient.Name = org.Name + for _, requiredRedirectURL := range []string{ + "https://dashboard." + common.DNSDomain, + fmt.Sprintf("https://%s.dashboard.%s", org.Name, common.DNSDomain), + } { + authClient.RedirectURIs = util.AppendStringToSliceIfNotContains(requiredRedirectURL, authClient.RedirectURIs) + } + return authClient, nil + }); err != nil { + return err + } + log.FromContext(ctx).Info("updated oauth2client", "name", org.Name) + r.recorder.Eventf(org, corev1.EventTypeNormal, "UpdatedOAuth2Client", "Updated oauth2client %s", org.Name) + var oAuth2Client = new(dexapi.OAuth2Client) oAuth2Client.ObjectMeta.Name = encodedOAuth2ClientName(org.Name) oAuth2Client.ObjectMeta.Namespace = r.Namespace diff --git a/pkg/controllers/organization/organization_controller.go b/pkg/controllers/organization/organization_controller.go index fa3f90734..7a0469dd9 100644 --- a/pkg/controllers/organization/organization_controller.go +++ b/pkg/controllers/organization/organization_controller.go @@ -5,7 +5,12 @@ package organization import ( "context" + "log/slog" + "os" + "github.com/cloudoperators/greenhouse/pkg/idproxy" + "github.com/dexidp/dex/storage" + "github.com/dexidp/dex/storage/sql" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -45,6 +50,10 @@ type OrganizationReconciler struct { client.Client recorder record.EventRecorder Namespace string + // Database related configuration + PGDB, PGHost, PGUser, PGPasswd string + PGPort uint16 + sqlDexStorage storage.Storage } //+kubebuilder:rbac:groups=greenhouse.sap,resources=organizations,verbs=get;list;watch;create;update;patch;delete @@ -64,6 +73,20 @@ type OrganizationReconciler struct { func (r *OrganizationReconciler) SetupWithManager(name string, mgr ctrl.Manager) error { r.Client = mgr.GetClient() r.recorder = mgr.GetEventRecorderFor(name) + + var err error + logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + r.sqlDexStorage, err = idproxy.NewPostgresStorage(sql.SSL{Mode: "disable"}, sql.NetworkDB{ + Host: r.PGHost, + Port: r.PGPort, + User: r.PGUser, + Password: r.PGPasswd, + Database: r.PGDB, + }, logger.With("component", "storage")) + if err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). Named(name). For(&greenhousesapv1alpha1.Organization{}). diff --git a/pkg/idproxy/storage.go b/pkg/idproxy/storage.go index a14e366e1..7bb4c66f3 100644 --- a/pkg/idproxy/storage.go +++ b/pkg/idproxy/storage.go @@ -11,9 +11,35 @@ import ( "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/kubernetes" "github.com/dexidp/dex/storage/kubernetes/k8sapi" + "github.com/dexidp/dex/storage/sql" "github.com/ghodss/yaml" ) +// NewPostgresStorage creates a new Postgres storage. +func NewPostgresStorage(sslConfig sql.SSL, dbConfig sql.NetworkDB, logger *slog.Logger) (storage.Storage, error) { + sqlStorage, err := postgresStorage(sslConfig, sql.NetworkDB{ + Host: dbConfig.Host, + Port: dbConfig.Port, + User: dbConfig.User, + Password: dbConfig.Password, + Database: dbConfig.Database, + }, logger.With("component", "storage")) + if err != nil { + return nil, err + } + return sqlStorage, nil +} + +// postgresStorage creates a new Postgres storage. +func postgresStorage(sslConfig sql.SSL, dbConfig sql.NetworkDB, logger *slog.Logger) (storage.Storage, error) { + var storageConfig = sql.Postgres{ + SSL: sslConfig, + NetworkDB: dbConfig, + } + return storageConfig.Open(logger) +} + +// NewKubernetesStorage creates a new Kubernetes storage. func NewKubernetesStorage(kubeconfig, kubecontext, namespace string, logger *slog.Logger) (storage.Storage, error) { var storageConfig = kubernetes.Config{InCluster: true} if kubeconfig != "" {