From 3abea9ef4d9218bf9c7fe886f20ea8f8f5c2a9f9 Mon Sep 17 00:00:00 2001 From: Igor Beliakov Date: Sat, 9 Jul 2022 16:24:55 +0200 Subject: [PATCH 1/2] feat: annotate secret Signed-off-by: Igor Beliakov --- controllers/constants/constants.go | 1 + controllers/grafana/grafana_reconciler.go | 20 ++++++++++----- controllers/model/adminUserSecret.go | 31 ++++++++++++++++++++++- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/controllers/constants/constants.go b/controllers/constants/constants.go index 9ca9e1a7c..759b4058b 100644 --- a/controllers/constants/constants.go +++ b/controllers/constants/constants.go @@ -22,6 +22,7 @@ const ( GrafanaHealthEndpoint = "/api/health" GrafanaPodLabel = "grafana" LastConfigAnnotation = "last-config" + LastCredentialsAnnotation = "last-credentials" LastConfigEnvVar = "LAST_CONFIG" LastDatasourcesConfigEnvVar = "LAST_DATASOURCES" GrafanaAdminSecretName = "grafana-admin-credentials" // #nosec G101 diff --git a/controllers/grafana/grafana_reconciler.go b/controllers/grafana/grafana_reconciler.go index 3670dfc92..b90e29b31 100644 --- a/controllers/grafana/grafana_reconciler.go +++ b/controllers/grafana/grafana_reconciler.go @@ -14,10 +14,11 @@ import ( ) type GrafanaReconciler struct { - DsHash string - ConfigHash string - PluginsEnv string - Plugins *PluginsHelperImpl + DsHash string + ConfigHash string + CredentialsHash string + PluginsEnv string + Plugins *PluginsHelperImpl } func NewGrafanaReconciler() *GrafanaReconciler { @@ -225,13 +226,20 @@ func (i *GrafanaReconciler) getGrafanaAdminUserSecretDesiredState(state *common. } if state.AdminSecret == nil { + secret := model.AdminSecret(cr) + i.CredentialsHash = secret.Annotations[constants.LastCredentialsAnnotation] + return common.GenericCreateAction{ - Ref: model.AdminSecret(cr), + Ref: secret, Msg: "create admin credentials secret", } } + + secret := model.AdminSecretReconciled(cr, state.AdminSecret) + i.CredentialsHash = secret.Annotations[constants.LastCredentialsAnnotation] + return common.GenericUpdateAction{ - Ref: model.AdminSecretReconciled(cr, state.AdminSecret), + Ref: secret, Msg: "update admin credentials secret", } } diff --git a/controllers/model/adminUserSecret.go b/controllers/model/adminUserSecret.go index 1e836b2b2..0ff42fb33 100644 --- a/controllers/model/adminUserSecret.go +++ b/controllers/model/adminUserSecret.go @@ -1,6 +1,9 @@ package model import ( + "bytes" + "crypto/sha256" + "fmt" "os" "github.com/grafana-operator/grafana-operator/v4/api/integreatly/v1alpha1" @@ -46,13 +49,33 @@ func getData(cr *v1alpha1.Grafana, current *v12.Secret) map[string][]byte { return credentials } +func getAdminCredentialsHash(credentials map[string][]byte) string { + var buf [][]byte + + for _, v := range credentials { + buf = append(buf, v) + } + + h := sha256.New() + h.Write(bytes.Join(buf, []byte(":"))) + hash := fmt.Sprintf("%x", h.Sum(nil)) + + return hash +} + func AdminSecret(cr *v1alpha1.Grafana) *v12.Secret { + data := getData(cr, nil) + hash := getAdminCredentialsHash(data) + return &v12.Secret{ ObjectMeta: v1.ObjectMeta{ Name: constants.GrafanaAdminSecretName, Namespace: cr.Namespace, + Annotations: map[string]string{ + constants.LastCredentialsAnnotation: hash, + }, }, - Data: getData(cr, nil), + Data: data, Type: v12.SecretTypeOpaque, } } @@ -60,6 +83,12 @@ func AdminSecret(cr *v1alpha1.Grafana) *v12.Secret { func AdminSecretReconciled(cr *v1alpha1.Grafana, currentState *v12.Secret) *v12.Secret { reconciled := currentState.DeepCopy() reconciled.Data = getData(cr, currentState) + hash := getAdminCredentialsHash(reconciled.Data) + + reconciled.Annotations = map[string]string{ + constants.LastCredentialsAnnotation: hash, + } + return reconciled } From 46ba88bcb9c3b303cc39517d57cbd491a763b86b Mon Sep 17 00:00:00 2001 From: Igor Beliakov Date: Sat, 9 Jul 2022 16:59:33 +0200 Subject: [PATCH 2/2] feat: fix hash generation, propagate hash to pods Signed-off-by: Igor Beliakov --- controllers/constants/constants.go | 1 + controllers/grafana/grafana_reconciler.go | 4 +- controllers/model/adminUserSecret.go | 37 +++++++---------- controllers/model/grafanaDeployment.go | 46 ++++++++++++--------- controllers/model/grafanaDeployment_test.go | 4 +- 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/controllers/constants/constants.go b/controllers/constants/constants.go index 759b4058b..ed20464fa 100644 --- a/controllers/constants/constants.go +++ b/controllers/constants/constants.go @@ -24,6 +24,7 @@ const ( LastConfigAnnotation = "last-config" LastCredentialsAnnotation = "last-credentials" LastConfigEnvVar = "LAST_CONFIG" + LastCredentialsEnvVar = "LAST_CREDENTIALS" LastDatasourcesConfigEnvVar = "LAST_DATASOURCES" GrafanaAdminSecretName = "grafana-admin-credentials" // #nosec G101 DefaultAdminUser = "admin" diff --git a/controllers/grafana/grafana_reconciler.go b/controllers/grafana/grafana_reconciler.go index b90e29b31..03bc4f0e2 100644 --- a/controllers/grafana/grafana_reconciler.go +++ b/controllers/grafana/grafana_reconciler.go @@ -273,14 +273,14 @@ func (i *GrafanaReconciler) getGrafanaRouteDesiredState(state *common.ClusterSta func (i *GrafanaReconciler) getGrafanaDeploymentDesiredState(state *common.ClusterState, cr *v1alpha1.Grafana) common.ClusterAction { if state.GrafanaDeployment == nil { return common.GenericCreateAction{ - Ref: model.GrafanaDeployment(cr, i.ConfigHash, i.DsHash), + Ref: model.GrafanaDeployment(cr, i.ConfigHash, i.DsHash, i.CredentialsHash), Msg: "create grafana deployment", } } return common.GenericUpdateAction{ Ref: model.GrafanaDeploymentReconciled(cr, state.GrafanaDeployment, - i.ConfigHash, i.PluginsEnv, i.DsHash), + i.ConfigHash, i.PluginsEnv, i.DsHash, i.CredentialsHash), Msg: "update grafana deployment", } } diff --git a/controllers/model/adminUserSecret.go b/controllers/model/adminUserSecret.go index 0ff42fb33..9be28cfc8 100644 --- a/controllers/model/adminUserSecret.go +++ b/controllers/model/adminUserSecret.go @@ -35,37 +35,29 @@ func getAdminPassword(cr *v1alpha1.Grafana, current *v12.Secret) []byte { return []byte(cr.Spec.Config.Security.AdminPassword) } -func getData(cr *v1alpha1.Grafana, current *v12.Secret) map[string][]byte { +func getData(cr *v1alpha1.Grafana, current *v12.Secret) (map[string][]byte, string) { + user := getAdminUser(cr, current) + password := getAdminPassword(cr, current) + credentials := map[string][]byte{ - constants.GrafanaAdminUserEnvVar: getAdminUser(cr, current), - constants.GrafanaAdminPasswordEnvVar: getAdminPassword(cr, current), + constants.GrafanaAdminUserEnvVar: user, + constants.GrafanaAdminPasswordEnvVar: password, } + h := sha256.New() + h.Write(bytes.Join([][]byte{user, password}, []byte(":"))) + hash := fmt.Sprintf("%x", h.Sum(nil)) + // Make the credentials available to the environment when running the operator // outside of the cluster os.Setenv(constants.GrafanaAdminUserEnvVar, string(credentials[constants.GrafanaAdminUserEnvVar])) os.Setenv(constants.GrafanaAdminPasswordEnvVar, string(credentials[constants.GrafanaAdminPasswordEnvVar])) - return credentials -} - -func getAdminCredentialsHash(credentials map[string][]byte) string { - var buf [][]byte - - for _, v := range credentials { - buf = append(buf, v) - } - - h := sha256.New() - h.Write(bytes.Join(buf, []byte(":"))) - hash := fmt.Sprintf("%x", h.Sum(nil)) - - return hash + return credentials, hash } func AdminSecret(cr *v1alpha1.Grafana) *v12.Secret { - data := getData(cr, nil) - hash := getAdminCredentialsHash(data) + data, hash := getData(cr, nil) return &v12.Secret{ ObjectMeta: v1.ObjectMeta{ @@ -81,9 +73,10 @@ func AdminSecret(cr *v1alpha1.Grafana) *v12.Secret { } func AdminSecretReconciled(cr *v1alpha1.Grafana, currentState *v12.Secret) *v12.Secret { + data, hash := getData(cr, currentState) + reconciled := currentState.DeepCopy() - reconciled.Data = getData(cr, currentState) - hash := getAdminCredentialsHash(reconciled.Data) + reconciled.Data = data reconciled.Annotations = map[string]string{ constants.LastCredentialsAnnotation: hash, diff --git a/controllers/model/grafanaDeployment.go b/controllers/model/grafanaDeployment.go index 0700c6c73..cb4c12645 100644 --- a/controllers/model/grafanaDeployment.go +++ b/controllers/model/grafanaDeployment.go @@ -83,7 +83,7 @@ func getResources(cr *v1alpha1.Grafana) v13.ResourceRequirements { } func getAffinities(cr *v1alpha1.Grafana) *v13.Affinity { - var affinity = v13.Affinity{} + affinity := v13.Affinity{} if cr.Spec.Deployment != nil && cr.Spec.Deployment.Affinity != nil { affinity = *cr.Spec.Deployment.Affinity } @@ -91,7 +91,7 @@ func getAffinities(cr *v1alpha1.Grafana) *v13.Affinity { } func getSecurityContext(cr *v1alpha1.Grafana) *v13.PodSecurityContext { - var securityContext = v13.PodSecurityContext{} + securityContext := v13.PodSecurityContext{} if cr.Spec.Deployment != nil && cr.Spec.Deployment.SecurityContext != nil { securityContext = *cr.Spec.Deployment.SecurityContext } @@ -99,7 +99,7 @@ func getSecurityContext(cr *v1alpha1.Grafana) *v13.PodSecurityContext { } func getContainerSecurityContext(cr *v1alpha1.Grafana) *v13.SecurityContext { - var containerSecurityContext = v13.SecurityContext{} + containerSecurityContext := v13.SecurityContext{} if cr.Spec.Deployment != nil && cr.Spec.Deployment.ContainerSecurityContext != nil { containerSecurityContext = *cr.Spec.Deployment.ContainerSecurityContext } @@ -118,7 +118,7 @@ func getDeploymentStrategy(cr *v1alpha1.Grafana) v1.DeploymentStrategy { } func getDeploymentLabels(cr *v1alpha1.Grafana) map[string]string { - var labels = map[string]string{} + labels := map[string]string{} if cr.Spec.Deployment != nil && cr.Spec.Deployment.Labels != nil { labels = cr.Spec.Deployment.Labels } @@ -126,7 +126,7 @@ func getDeploymentLabels(cr *v1alpha1.Grafana) map[string]string { } func getDeploymentAnnotations(cr *v1alpha1.Grafana, existing map[string]string) map[string]string { - var annotations = map[string]string{} + annotations := map[string]string{} // Add fixed annotations annotations["prometheus.io/scrape"] = "true" annotations["prometheus.io/port"] = fmt.Sprintf("%v", GetGrafanaPort(cr)) @@ -148,7 +148,7 @@ func getRollingUpdateStrategy() *v1.RollingUpdateDeployment { } func getPodAnnotations(cr *v1alpha1.Grafana, existing map[string]string) map[string]string { - var annotations = map[string]string{} + annotations := map[string]string{} // Add fixed annotations annotations["prometheus.io/scrape"] = "true" annotations["prometheus.io/port"] = fmt.Sprintf("%v", GetGrafanaPort(cr)) @@ -161,7 +161,7 @@ func getPodAnnotations(cr *v1alpha1.Grafana, existing map[string]string) map[str } func getPodLabels(cr *v1alpha1.Grafana) map[string]string { - var labels = map[string]string{} + labels := map[string]string{} if cr.Spec.Deployment != nil && cr.Spec.Deployment.Labels != nil { labels = cr.Spec.Deployment.Labels } @@ -170,7 +170,7 @@ func getPodLabels(cr *v1alpha1.Grafana) map[string]string { } func getNodeSelectors(cr *v1alpha1.Grafana) map[string]string { - var nodeSelector = map[string]string{} + nodeSelector := map[string]string{} if cr.Spec.Deployment != nil && cr.Spec.Deployment.NodeSelector != nil { nodeSelector = cr.Spec.Deployment.NodeSelector @@ -196,7 +196,7 @@ func getTolerations(cr *v1alpha1.Grafana) []v13.Toleration { func getVolumes(cr *v1alpha1.Grafana) []v13.Volume { // nolint var volumes []v13.Volume // nolint - var volumeOptional = true + volumeOptional := true volumes = append(volumes, v13.Volume{ Name: constants.GrafanaProvisionPluginVolumeName, @@ -451,7 +451,7 @@ func getVolumeMounts(cr *v1alpha1.Grafana) []v13.VolumeMount { func getLivenessProbe(cr *v1alpha1.Grafana, delay, timeout, failure int32) *v13.Probe { var period int32 = 10 var success int32 = 1 - var scheme = v13.URISchemeHTTP + scheme := v13.URISchemeHTTP if cr.Spec.Config.Server != nil && cr.Spec.Config.Server.Protocol == "https" { scheme = v13.URISchemeHTTPS } @@ -499,7 +499,7 @@ func getLivenessProbe(cr *v1alpha1.Grafana, delay, timeout, failure int32) *v13. func getReadinessProbe(cr *v1alpha1.Grafana, delay, timeout, failure int32) *v13.Probe { var period int32 = 10 var success int32 = 1 - var scheme = v13.URISchemeHTTP + scheme := v13.URISchemeHTTP if cr.Spec.Config.Server != nil && cr.Spec.Config.Server.Protocol == "https" { scheme = v13.URISchemeHTTPS } @@ -544,7 +544,7 @@ func getReadinessProbe(cr *v1alpha1.Grafana, delay, timeout, failure int32) *v13 } } -func getContainers(cr *v1alpha1.Grafana, configHash, dsHash string) []v13.Container { // nolint +func getContainers(cr *v1alpha1.Grafana, configHash, dsHash, credentialsHash string) []v13.Container { // nolint var containers []v13.Container // nolint var image string @@ -566,6 +566,10 @@ func getContainers(cr *v1alpha1.Grafana, configHash, dsHash string) []v13.Contai Name: constants.LastDatasourcesConfigEnvVar, Value: dsHash, }, + { + Name: constants.LastCredentialsEnvVar, + Value: credentialsHash, + }, } if cr.Spec.Deployment != nil && cr.Spec.Deployment.HttpProxy != nil && cr.Spec.Deployment.HttpProxy.Enabled { envVars = append(envVars, v13.EnvVar{ @@ -637,7 +641,8 @@ func getContainers(cr *v1alpha1.Grafana, configHash, dsHash string) []v13.Contai }, Key: constants.GrafanaAdminPasswordEnvVar, }, - }}) + }, + }) } } @@ -682,7 +687,7 @@ func getInitContainers(cr *v1alpha1.Grafana, plugins string) []v13.Container { } } - var volumeName = constants.GrafanaPluginsVolumeName + volumeName := constants.GrafanaPluginsVolumeName if cr.Spec.Deployment != nil { for _, item := range cr.Spec.Deployment.ExtraVolumeMounts { @@ -712,7 +717,7 @@ func getInitContainers(cr *v1alpha1.Grafana, plugins string) []v13.Container { } } -func getDeploymentSpec(cr *v1alpha1.Grafana, annotations map[string]string, configHash, plugins, dsHash string) v1.DeploymentSpec { +func getDeploymentSpec(cr *v1alpha1.Grafana, annotations map[string]string, configHash, plugins, dsHash, credentialsHash string) v1.DeploymentSpec { return v1.DeploymentSpec{ Replicas: getReplicas(cr), Selector: &v12.LabelSelector{ @@ -733,7 +738,7 @@ func getDeploymentSpec(cr *v1alpha1.Grafana, annotations map[string]string, conf SecurityContext: getSecurityContext(cr), Volumes: getVolumes(cr), InitContainers: getInitContainers(cr, plugins), - Containers: getContainers(cr, configHash, dsHash), + Containers: getContainers(cr, configHash, dsHash, credentialsHash), ServiceAccountName: constants.GrafanaServiceAccountName, TerminationGracePeriodSeconds: getTerminationGracePeriod(cr), PriorityClassName: getPodPriorityClassName(cr), @@ -743,7 +748,7 @@ func getDeploymentSpec(cr *v1alpha1.Grafana, annotations map[string]string, conf } } -func GrafanaDeployment(cr *v1alpha1.Grafana, configHash, dsHash string) *v1.Deployment { +func GrafanaDeployment(cr *v1alpha1.Grafana, configHash, dsHash, credentialsHash string) *v1.Deployment { return &v1.Deployment{ ObjectMeta: v12.ObjectMeta{ Name: constants.GrafanaDeploymentName, @@ -751,7 +756,7 @@ func GrafanaDeployment(cr *v1alpha1.Grafana, configHash, dsHash string) *v1.Depl Labels: getDeploymentLabels(cr), Annotations: getDeploymentAnnotations(cr, nil), }, - Spec: getDeploymentSpec(cr, nil, configHash, "", dsHash), + Spec: getDeploymentSpec(cr, nil, configHash, "", dsHash, credentialsHash), } } @@ -762,12 +767,13 @@ func GrafanaDeploymentSelector(cr *v1alpha1.Grafana) client.ObjectKey { } } -func GrafanaDeploymentReconciled(cr *v1alpha1.Grafana, currentState *v1.Deployment, configHash, plugins, dshash string) *v1.Deployment { +func GrafanaDeploymentReconciled(cr *v1alpha1.Grafana, currentState *v1.Deployment, configHash, plugins, dshash, credentialsHash string) *v1.Deployment { reconciled := currentState.DeepCopy() reconciled.Spec = getDeploymentSpec(cr, currentState.Spec.Template.Annotations, configHash, plugins, - dshash) + dshash, + credentialsHash) return reconciled } diff --git a/controllers/model/grafanaDeployment_test.go b/controllers/model/grafanaDeployment_test.go index 0b53b0156..9f9d53767 100644 --- a/controllers/model/grafanaDeployment_test.go +++ b/controllers/model/grafanaDeployment_test.go @@ -22,7 +22,7 @@ func TestGrafanaDeployment_httpProxy(t *testing.T) { }, }, } - deployment := GrafanaDeployment(cr, "", "") + deployment := GrafanaDeployment(cr, "", "", "") for _, container := range deployment.Spec.Template.Spec.Containers { if container.Name != "grafana" { continue @@ -51,7 +51,7 @@ func TestGrafanaDeployment_httpProxy(t *testing.T) { }, }, } - deployment := GrafanaDeployment(cr, "", "") + deployment := GrafanaDeployment(cr, "", "", "") for _, container := range deployment.Spec.Template.Spec.Containers { if container.Name != "grafana" { continue