From 73a8130fb937848724e9b2eacd777bc9468181d3 Mon Sep 17 00:00:00 2001 From: Simon Beck Date: Mon, 14 Oct 2024 13:14:43 +0200 Subject: [PATCH] WIP --- apis/vshn/v1/dbaas_vshn_mariadb.go | 4 +- apis/vshn/v1/dbaas_vshn_postgresql.go | 1 + .../functions/vshnmariadb/user_management.go | 260 ++++++++++++++++++ .../functions/vshnpostgres/user_management.go | 14 +- 4 files changed, 270 insertions(+), 9 deletions(-) create mode 100644 pkg/comp-functions/functions/vshnmariadb/user_management.go diff --git a/apis/vshn/v1/dbaas_vshn_mariadb.go b/apis/vshn/v1/dbaas_vshn_mariadb.go index 48bfff6fb..4d9005d9c 100644 --- a/apis/vshn/v1/dbaas_vshn_mariadb.go +++ b/apis/vshn/v1/dbaas_vshn_mariadb.go @@ -99,8 +99,8 @@ type VSHNMariaDBServiceSpec struct { // ServiceLevel defines the service level of this service. Either Best Effort or Guaranteed Availability is allowed. ServiceLevel VSHNDBaaSServiceLevel `json:"serviceLevel,omitempty"` - // +kubebuilder:default="standalone" - // +kubebuilder:validation:Enum=replication;standalone + // Access defines additional users and databases for this instance. + Access []VSHNAccess `json:"access,omitempty"` } // VSHNMariaDBTLSSpec contains settings to control tls traffic of a service. diff --git a/apis/vshn/v1/dbaas_vshn_postgresql.go b/apis/vshn/v1/dbaas_vshn_postgresql.go index cc601d910..4f64b69ad 100644 --- a/apis/vshn/v1/dbaas_vshn_postgresql.go +++ b/apis/vshn/v1/dbaas_vshn_postgresql.go @@ -154,6 +154,7 @@ type VSHNPostgreSQLServiceSpec struct { // +kubebuilder:default=false VacuumEnabled bool `json:"vacuumEnabled,omitempty"` + // Access defines additional users and databases for this instance. Access []VSHNAccess `json:"access,omitempty"` } diff --git a/pkg/comp-functions/functions/vshnmariadb/user_management.go b/pkg/comp-functions/functions/vshnmariadb/user_management.go new file mode 100644 index 000000000..5d92238ee --- /dev/null +++ b/pkg/comp-functions/functions/vshnmariadb/user_management.go @@ -0,0 +1,260 @@ +package vshnmariadb + +import ( + "context" + "fmt" + + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1" + my1alpha1 "github.com/vshn/appcat/v4/apis/sql/mysql/v1alpha1" + vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" + "github.com/vshn/appcat/v4/pkg/comp-functions/functions/common" + "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func UserManagement(ctx context.Context, comp *vshnv1.VSHNMariaDB, svc *runtime.ServiceRuntime) *xfnproto.Result { + + // Nothing defined, let's return early + if len(comp.Spec.Parameters.Service.Access) == 0 { + return nil + } + + addProviderConfig(comp, svc) + + for _, access := range comp.Spec.Parameters.Service.Access { + + userPasswordRef := addUser(comp, svc, *access.User) + + dbname := *access.User + if access.Database != nil { + dbname = *access.Database + } + + addDatabase(comp, svc, dbname) + + addGrants(comp, svc, *access.User, dbname, access.Privileges) + + addConnectionDetail(comp, svc, userPasswordRef, *access.User, dbname, access.WriteConnectionSecretToReference) + } + + return nil +} + +func addUser(comp common.Composite, svc *runtime.ServiceRuntime, username string) string { + secretName, err := common.AddGenericSecret(comp, svc, "userpass-"+username, []string{"userpass"}) + if err != nil { + svc.Log.Error(err, "cannot deploy user password secret") + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot deploy user password secret: %s", err))) + } + + role := &my1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s-role", comp.GetName(), username), + Annotations: map[string]string{ + "crossplane.io/external-name": username, + }, + Labels: map[string]string{ + runtime.ProviderConfigIgnoreLabel: "true", + }, + }, + Spec: my1alpha1.UserSpec{ + ForProvider: my1alpha1.UserParameters{}, + ResourceSpec: xpv1.ResourceSpec{ + ProviderConfigReference: &xpv1.Reference{ + Name: comp.GetName(), + }, + }, + }, + } + + err = svc.SetDesiredComposedResource(role, runtime.ComposedOptionProtects(comp.GetName()+"-provider-conf-credentials"), runtime.ComposedOptionProtects(secretName)) + if err != nil { + svc.Log.Error(err, "cannot apply user") + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot apply user: %s", err))) + } + + return secretName +} + +func addConnectionDetail(comp common.Composite, svc *runtime.ServiceRuntime, secretName, username, dbname string, connectionDetailRef *xpv1.SecretReference) { + userpassCD, err := svc.GetObservedComposedResourceConnectionDetails(secretName) + if err != nil { + svc.Log.Error(err, "cannot get userpassword from secret") + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot get userpassword from secret: %s", err))) + } + + compositeCD := svc.GetConnectionDetails() + + // url := getPostgresURLCustomUser(compositeCD, string(compositeCD["MARIADB_HOST"]), username) + + om := metav1.ObjectMeta{ + Name: comp.GetLabels()["crossplane.io/claim-name"] + "-" + username, + Namespace: comp.GetClaimNamespace(), + } + if connectionDetailRef != nil { + om.Name = connectionDetailRef.Name + om.Namespace = connectionDetailRef.Namespace + } + + userpassSecret := &corev1.Secret{ + ObjectMeta: om, + Type: corev1.SecretType("connection.crossplane.io/v1alpha1"), + Data: map[string][]byte{ + "MARIADB_USER": []byte(username), + "MARIADB_PASSWORD": userpassCD["userpass"], + "MARIADB_DB": []byte(dbname), + "MARIADB_HOST": compositeCD["MARIADB_HOST"], + "MARIADB_PORT": compositeCD["MARIADB_PORT"], + // "MARIADB_URL": []byte(url), + "ca.crt": compositeCD["ca.crt"], + "tls.crt": compositeCD["tls.crt"], + "tls.key": compositeCD["tls.key"], + }, + } + + err = svc.SetDesiredKubeObject(userpassSecret, fmt.Sprintf("%s-user-%s", comp.GetName(), username)) + if err != nil { + svc.Log.Error(err, "cannot get userpassword from secret") + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot get userpassword from secret: %s", err))) + } +} + +func addProviderConfig(comp *vshnv1.VSHNMariaDB, svc *runtime.ServiceRuntime) { + cd := svc.GetConnectionDetails() + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "provider-conf-credentials", + Namespace: comp.GetInstanceNamespace(), + }, + Data: map[string][]byte{ + "username": cd["MARIADB_USER"], + "password": cd["MARIADB_PASSWORD"], + "endpoint": cd["MARIADB_HOST"], + "port": cd["MARIADB_PORT"], + }, + } + + err := svc.SetDesiredKubeObject(secret, comp.GetName()+"-provider-conf-credentials", + runtime.KubeOptionProtects("namespace-conditions"), + runtime.KubeOptionProtects("cluster"), + runtime.KubeOptionProtects(comp.GetName()+"-netpol")) + if err != nil { + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot set credential secret for provider-sql: %s", err))) + svc.Log.Error(err, "cannot set credential secret for provider-sql") + } + + tls := "preferred" + if comp.Spec.Parameters.TLS.TLSEnabled { + tls = "skip-verify" + } + + config := &my1alpha1.ProviderConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName(), + }, + Spec: my1alpha1.ProviderConfigSpec{ + TLS: &tls, + Credentials: my1alpha1.ProviderCredentials{ + Source: "MySQLConnectionSecret", + ConnectionSecretRef: &xpv1.SecretReference{ + Name: "provider-conf-credentials", + Namespace: comp.GetInstanceNamespace(), + }, + }, + }, + } + + err = svc.SetDesiredKubeObject(config, comp.GetName()+"-providerconfig") + if err != nil { + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot apply the provider config for provider sql: %s", err))) + svc.Log.Error(err, "cannot apply the provider config for provider sql") + } +} + +// We check if the database is already specified. +// If not it will be added. +// This should handle cases where there are mutliple users pointing to the same +// database, and one is deleted, that the database is not dropped. +func addDatabase(comp common.Composite, svc *runtime.ServiceRuntime, name string) { + resname := fmt.Sprintf("%s-%s-database", comp.GetName(), name) + + xdb := &my1alpha1.Database{} + + // If there's a database with the same name we will just return + err := svc.GetDesiredComposedResourceByName(xdb, resname) + if err == nil { + return + } + if err != runtime.ErrNotFound { + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot check if database exists: %s", err))) + svc.Log.Error(err, "cannot check if database exists") + } + + xdb = &my1alpha1.Database{ + ObjectMeta: metav1.ObjectMeta{ + Name: resname, + Annotations: map[string]string{ + "crossplane.io/external-name": name, + }, + Labels: map[string]string{ + runtime.ProviderConfigIgnoreLabel: "true", + }, + }, + Spec: my1alpha1.DatabaseSpec{ + ForProvider: my1alpha1.DatabaseParameters{}, + ResourceSpec: xpv1.ResourceSpec{ + ProviderConfigReference: &xpv1.Reference{ + Name: comp.GetName(), + }, + }, + }, + } + + err = svc.SetDesiredComposedResource(xdb, runtime.ComposedOptionProtects(comp.GetName()+"-provider-conf-credentials")) + if err != nil { + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot apply database: %s", err))) + svc.Log.Error(err, "cannot apply database") + } +} + +func addGrants(comp common.Composite, svc *runtime.ServiceRuntime, username, dbname string, privileges []string) { + privs := []my1alpha1.GrantPrivilege{} + + if len(privileges) == 0 { + privs = append(privs, "ALL") + } + + for _, priv := range privileges { + privs = append(privs, my1alpha1.GrantPrivilege(priv)) + } + + grant := &my1alpha1.Grant{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s-%s-grants", comp.GetName(), username, dbname), + Labels: map[string]string{ + runtime.ProviderConfigIgnoreLabel: "true", + }, + }, + Spec: my1alpha1.GrantSpec{ + ForProvider: my1alpha1.GrantParameters{ + Privileges: privs, + User: &username, + Database: &dbname, + }, + ResourceSpec: xpv1.ResourceSpec{ + ProviderConfigReference: &xpv1.Reference{ + Name: comp.GetName(), + }, + }, + }, + } + + err := svc.SetDesiredComposedResource(grant, runtime.ComposedOptionProtects(comp.GetName()+"-provider-conf-credentials")) + if err != nil { + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot apply database: %s", err))) + svc.Log.Error(err, "cannot apply database") + } +} diff --git a/pkg/comp-functions/functions/vshnpostgres/user_management.go b/pkg/comp-functions/functions/vshnpostgres/user_management.go index 532bb85a4..3510cb5da 100644 --- a/pkg/comp-functions/functions/vshnpostgres/user_management.go +++ b/pkg/comp-functions/functions/vshnpostgres/user_management.go @@ -22,7 +22,7 @@ func UserManagement(ctx context.Context, comp *vshnv1.VSHNPostgreSQL, svc *runti } // Nothing defined, let's return early - if comp.Spec.Parameters.Service.Access == nil || len(comp.Spec.Parameters.Service.Access) == 0 { + if len(comp.Spec.Parameters.Service.Access) == 0 { return nil } @@ -47,7 +47,7 @@ func UserManagement(ctx context.Context, comp *vshnv1.VSHNPostgreSQL, svc *runti return nil } -func addUser(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, username string) string { +func addUser(comp common.Composite, svc *runtime.ServiceRuntime, username string) string { secretName, err := common.AddGenericSecret(comp, svc, "userpass-"+username, []string{"userpass"}) if err != nil { svc.Log.Error(err, "cannot deploy user password secret") @@ -72,7 +72,7 @@ func addUser(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, username PasswordSecretRef: &xpv1.SecretKeySelector{ SecretReference: xpv1.SecretReference{ Name: secretName, - Namespace: comp.Status.InstanceNamespace, + Namespace: comp.GetInstanceNamespace(), }, Key: "userpass", }, @@ -94,7 +94,7 @@ func addUser(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, username return secretName } -func addConnectionDetail(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, secretName, username, dbname string, connectionDetailRef *xpv1.SecretReference) { +func addConnectionDetail(comp common.Composite, svc *runtime.ServiceRuntime, secretName, username, dbname string, connectionDetailRef *xpv1.SecretReference) { userpassCD, err := svc.GetObservedComposedResourceConnectionDetails(secretName) if err != nil { svc.Log.Error(err, "cannot get userpassword from secret") @@ -137,7 +137,7 @@ func addConnectionDetail(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntim } } -func addProviderConfig(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) { +func addProviderConfig(comp common.Composite, svc *runtime.ServiceRuntime) { cd := svc.GetConnectionDetails() secret := &corev1.Secret{ @@ -191,7 +191,7 @@ func addProviderConfig(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) // If not it will be added. // This should handle cases where there are mutliple users pointing to the same // database, and one is deleted, that the database is not dropped. -func addDatabase(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, name string) { +func addDatabase(comp common.Composite, svc *runtime.ServiceRuntime, name string) { resname := fmt.Sprintf("%s-%s-database", comp.GetName(), name) xdb := &pgv1alpha1.Database{} @@ -233,7 +233,7 @@ func addDatabase(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, name } } -func addGrants(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, username, dbname string, privileges []string) { +func addGrants(comp common.Composite, svc *runtime.ServiceRuntime, username, dbname string, privileges []string) { privs := []pgv1alpha1.GrantPrivilege{} if len(privileges) == 0 {