Skip to content

Commit

Permalink
use sso settings api instead of app user-values
Browse files Browse the repository at this point in the history
  • Loading branch information
QuentinBisson committed Dec 17, 2024
1 parent b16ca41 commit 24a4b81
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 135 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Add Alertmanager controller

### Changed

- Change SSO settings configuration to use the Grafana admin API instead of app user-values.

## [0.10.1] - 2024-12-12

### Fixed
Expand Down
84 changes: 34 additions & 50 deletions internal/controller/grafanaorganization_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ limitations under the License.
package controller

import (
"cmp"
"context"
"fmt"
"slices"

grafanaAPI "github.com/grafana/grafana-openapi-client-go/client"
"github.com/pkg/errors"
Expand All @@ -38,13 +36,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"github.com/giantswarm/observability-operator/pkg/config"
grafanaclient "github.com/giantswarm/observability-operator/pkg/grafana/client"
appv1 "github.com/giantswarm/apiextensions-application/api/v1alpha1"

"github.com/giantswarm/observability-operator/api/v1alpha1"
"github.com/giantswarm/observability-operator/internal/controller/predicates"
"github.com/giantswarm/observability-operator/pkg/config"
"github.com/giantswarm/observability-operator/pkg/grafana"
"github.com/giantswarm/observability-operator/pkg/grafana/templating"
grafanaclient "github.com/giantswarm/observability-operator/pkg/grafana/client"
)

// GrafanaOrganizationReconciler reconciles a GrafanaOrganization object
Expand Down Expand Up @@ -138,10 +136,6 @@ func (r *GrafanaOrganizationReconciler) SetupWithManager(mgr ctrl.Manager) error
return []reconcile.Request{}
}

// Sort organizations by orgID to ensure the order is deterministic.
// This is important to prevent incorrect ordering of organizations on grafana restarts.
slices.SortStableFunc(organizations.Items, compareOrganizationsByID)

// Reconcile all grafana organizations when the grafana pod is recreated
requests := make([]reconcile.Request, 0, len(organizations.Items))
for _, organization := range organizations.Items {
Expand All @@ -158,19 +152,6 @@ func (r *GrafanaOrganizationReconciler) SetupWithManager(mgr ctrl.Manager) error
Complete(r)
}

func compareOrganizationsByID(i, j v1alpha1.GrafanaOrganization) int {
// if both orgs have a nil orgID, they are equal
// if one org has a nil orgID, it is higher than the other as it was not created in Grafana yet
if i.Status.OrgID == 0 && j.Status.OrgID == 0 {
return 0
} else if i.Status.OrgID == 0 {
return 1
} else if j.Status.OrgID == 0 {
return -1
}
return cmp.Compare(i.Status.OrgID, j.Status.OrgID)
}

// reconcileCreate creates the grafanaOrganization.
// reconcileCreate ensures the Grafana organization described in grafanaOrganization CR is created in Grafana.
// This function is also responsible for:
Expand Down Expand Up @@ -214,7 +195,7 @@ func (r GrafanaOrganizationReconciler) reconcileCreate(ctx context.Context, graf
}

// Configure Grafana RBAC
if err := r.configureGrafana(ctx); err != nil {
if err := r.configureGrafanaSSO(ctx); err != nil {
return ctrl.Result{}, errors.WithStack(err)
}

Expand Down Expand Up @@ -251,6 +232,9 @@ func newOrganization(grafanaOrganization *v1alpha1.GrafanaOrganization) grafana.
ID: grafanaOrganization.Status.OrgID,
Name: grafanaOrganization.Spec.DisplayName,
TenantIDs: tenantIDs,
Admins: grafanaOrganization.Spec.RBAC.Admins,
Editors: grafanaOrganization.Spec.RBAC.Editors,
Viewers: grafanaOrganization.Spec.RBAC.Viewers,
}
}

Expand Down Expand Up @@ -343,7 +327,7 @@ func (r GrafanaOrganizationReconciler) reconcileDelete(ctx context.Context, graf
}
}

err := r.configureGrafana(ctx)
err := r.configureGrafanaSSO(ctx)
if err != nil {
return errors.WithStack(err)
}
Expand All @@ -367,7 +351,7 @@ func (r GrafanaOrganizationReconciler) reconcileDelete(ctx context.Context, graf
}

// configureGrafana ensures the RBAC configuration is set in Grafana.
func (r *GrafanaOrganizationReconciler) configureGrafana(ctx context.Context) error {
func (r *GrafanaOrganizationReconciler) configureGrafanaSSO(ctx context.Context) error {
logger := log.FromContext(ctx)

organizationList := v1alpha1.GrafanaOrganizationList{}
Expand All @@ -384,34 +368,34 @@ func (r *GrafanaOrganizationReconciler) configureGrafana(ctx context.Context) er
},
}

_, err = controllerutil.CreateOrPatch(ctx, r.Client, grafanaConfig, func() error {
// We always sort the organizations to ensure the order is deterministic and the configmap is stable
// in order to prevent grafana to restarts.
slices.SortStableFunc(organizationList.Items, compareOrganizationsByID)

config, err := templating.GenerateGrafanaConfiguration(organizationList.Items)
if err != nil {
logger.Error(err, "failed to generate grafana user configmap values.")
return errors.WithStack(err)
}

// TODO: to be removed for next release
// cleanup owner references from the config map, see https://github.com/giantswarm/observability-operator/pull/183
for _, organization := range organizationList.Items {
// nolint:errcheck,gosec // ignore errors, owner references are probably already gone
controllerutil.RemoveOwnerReference(&organization, grafanaConfig, r.Scheme)
}

logger.Info("updating grafana-user-values", "config", config)

grafanaConfig.Data = make(map[string]string)
grafanaConfig.Data["values"] = config
// TODO remove after next release start
if err = r.Client.Delete(ctx, grafanaConfig); client.IgnoreNotFound(err) != nil {
return errors.WithStack(err)
}

return nil
})
// Retrieve the app.
var currentApp appv1.App = appv1.App{
ObjectMeta: metav1.ObjectMeta{
Name: "grafana",
Namespace: "giantswarm",
},
}
err = r.Client.Get(ctx, types.NamespacedName{Name: currentApp.GetName(), Namespace: currentApp.GetNamespace()}, &currentApp)
if err != nil {
return err
}
currentApp.Spec.UserConfig = appv1.AppSpecUserConfig{}
if err = r.Client.Update(ctx, &currentApp); err != nil {
return err
}

// Configure SSO settings in Grafana
organizations := make([]grafana.Organization, len(organizationList.Items))
for i, organization := range organizationList.Items {
organizations[i] = newOrganization(&organization)
}
err = grafana.ConfigureSSOSettings(ctx, r.GrafanaAPI, organizations)
if err != nil {
logger.Error(err, "failed to configure grafana.")
return errors.WithStack(err)
}

Expand Down
81 changes: 81 additions & 0 deletions pkg/grafana/sso_settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package grafana

import (
"context"
"fmt"
"strings"

"github.com/grafana/grafana-openapi-client-go/client"
"github.com/grafana/grafana-openapi-client-go/models"
"github.com/pkg/errors"
"sigs.k8s.io/controller-runtime/pkg/log"
)

const (
grafanaAdminRole = "Admin"
grafanaEditorRole = "Editor"
grafanaViewerRole = "Viewer"
)

func ConfigureSSOSettings(ctx context.Context, grafanaAPI *client.GrafanaHTTPAPI, organizations []Organization) error {
logger := log.FromContext(ctx)

provider := "generic_oauth"
resp, err := grafanaAPI.SsoSettings.GetProviderSettings(provider, nil)
if err != nil {
logger.Error(err, "failed to get sso provider settings.")
return errors.WithStack(err)
}

orgsMapping := generateGrafanaOrgsMapping(organizations)
settings := resp.Payload.Settings.(map[string]string)
settings["role_attribute_path"] = "to_string('Viewer')"
settings["org_attribute_path"] = "groups"
settings["org_mapping"] = fmt.Sprintf("'%s'", orgsMapping)

// Update the provider settings
_, err = grafanaAPI.SsoSettings.UpdateProviderSettings(provider,
&models.UpdateProviderSettingsParamsBody{
ID: resp.Payload.ID,
Provider: resp.Payload.Provider,
Settings: settings,
})

if err != nil {
logger.Error(err, "failed to configure grafana sso.")
return errors.WithStack(err)
}

return nil
}

func generateGrafanaOrgsMapping(organizations []Organization) string {
var orgMappings []string
// TODO: We need to be admins to be able to see the private dashboards for now, remove the 2 GS groups once https://github.com/giantswarm/roadmap/issues/3696 is done.
// Grant Admin role to Giantswarm users logging in via azure active directory.
orgMappings = append(orgMappings, buildOrgMapping(SharedOrg.Name, "giantswarm-ad:giantswarm-admins", grafanaAdminRole))
// Grant Admin role to Giantswarm users logging in via github.
orgMappings = append(orgMappings, buildOrgMapping(SharedOrg.Name, "giantswarm-github:giantswarm:giantswarm-admins", grafanaAdminRole))
// Grant Editor role to every other users.
orgMappings = append(orgMappings, fmt.Sprintf(`"*:%s:%s"`, SharedOrg.Name, grafanaEditorRole))
for _, organization := range organizations {
for _, adminOrgAttribute := range organization.Admins {
orgMappings = append(orgMappings, buildOrgMapping(organization.Name, adminOrgAttribute, grafanaAdminRole))
}
for _, editorOrgAttribute := range organization.Editors {
orgMappings = append(orgMappings, buildOrgMapping(organization.Name, editorOrgAttribute, grafanaEditorRole))
}
for _, viewerOrgAttribute := range organization.Viewers {
orgMappings = append(orgMappings, buildOrgMapping(organization.Name, viewerOrgAttribute, grafanaViewerRole))
}
}

return strings.Join(orgMappings, " ")
}

func buildOrgMapping(organizationName, userOrgAttribute, role string) string {
// We need to escape the colon in the userOrgAttribute
u := strings.ReplaceAll(userOrgAttribute, ":", "\\:")
// We add double quotes to the org mapping to support spaces in display names
return fmt.Sprintf(`"%s:%s:%s"`, u, organizationName, role)
}

This file was deleted.

77 changes: 0 additions & 77 deletions pkg/grafana/templating/templating.go

This file was deleted.

3 changes: 3 additions & 0 deletions pkg/grafana/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ type Organization struct {
ID int64
Name string
TenantIDs []string
Admins []string
Editors []string
Viewers []string
}

type Datasource struct {
Expand Down

0 comments on commit 24a4b81

Please sign in to comment.