diff --git a/pkg/registry/apis/datasource/converter.go b/pkg/registry/apis/datasource/converter.go index f1db08f42b5..6e304d87141 100644 --- a/pkg/registry/apis/datasource/converter.go +++ b/pkg/registry/apis/datasource/converter.go @@ -42,7 +42,7 @@ func (r *converter) asDataSource(ds *datasources.DataSource) (*datasourceV0.Data Generation: int64(ds.Version), }, Spec: datasourceV0.UnstructuredSpec{}, - Secure: ToInlineSecureValues(ds.Type, ds.UID, maps.Keys(ds.SecureJsonData)), + Secure: ToInlineSecureValues(ds.UID, maps.Keys(ds.SecureJsonData)), } obj.UID = gapiutil.CalculateClusterWideUID(obj) obj.Spec.SetTitle(ds.Name). @@ -82,18 +82,11 @@ func (r *converter) asDataSource(ds *datasources.DataSource) (*datasourceV0.Data // ToInlineSecureValues converts secure json into InlineSecureValues with reference names // The names are predictable and can be used while we implement dual writing for secrets -func ToInlineSecureValues(dsType string, dsUID string, keys iter.Seq[string]) common.InlineSecureValues { +func ToInlineSecureValues(dsUID string, keys iter.Seq[string]) common.InlineSecureValues { values := make(common.InlineSecureValues) for k := range keys { - h := sha256.New() - h.Write([]byte(dsType)) // plugin id - h.Write([]byte("|")) - h.Write([]byte(dsUID)) // unique identifier - h.Write([]byte("|")) - h.Write([]byte(k)) // property name - n := hex.EncodeToString(h.Sum(nil)) values[k] = common.InlineSecureValue{ - Name: "ds-" + n[0:10], // predictable name for dual writing + Name: getLegacySecureValueName(dsUID, k), } } if len(values) == 0 { @@ -102,6 +95,15 @@ func ToInlineSecureValues(dsType string, dsUID string, keys iter.Seq[string]) co return values } +func getLegacySecureValueName(dsUID string, key string) string { + h := sha256.New() + h.Write([]byte(dsUID)) // unique identifier + h.Write([]byte("|")) + h.Write([]byte(key)) // property name + n := hex.EncodeToString(h.Sum(nil)) + return "ds-" + n[0:10] // predictable name for dual writing +} + func (r *converter) toAddCommand(ds *datasourceV0.DataSource) (*datasources.AddDataSourceCommand, error) { if r.group != "" && ds.APIVersion != "" && !strings.HasPrefix(ds.APIVersion, r.group) { return nil, fmt.Errorf("expecting APIGroup: %s", r.group) diff --git a/pkg/registry/apis/datasource/legacy_store.go b/pkg/registry/apis/datasource/legacy_store.go index c263129a157..2249f8ce6c2 100644 --- a/pkg/registry/apis/datasource/legacy_store.go +++ b/pkg/registry/apis/datasource/legacy_store.go @@ -11,9 +11,11 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1" "github.com/grafana/grafana/pkg/infra/metrics/metricutil" + "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" ) var ( @@ -90,6 +92,20 @@ func (s *legacyStorage) Create(ctx context.Context, obj runtime.Object, createVa if !ok { return nil, fmt.Errorf("expected a datasource object") } + + // Verify the secure value commands + for _, v := range ds.Secure { + if v.Create.IsZero() { + return nil, fmt.Errorf("secure values must use create when creating a new datasource") + } + if v.Remove { + return nil, fmt.Errorf("secure values can not use remove when creating a new datasource") + } + if v.Name != "" { + return nil, fmt.Errorf("secure values can not specify a name when creating a new datasource") + } + } + return s.datasources.CreateDataSource(ctx, ds) } @@ -122,6 +138,25 @@ func (s *legacyStorage) Update(ctx context.Context, name string, objInfo rest.Up return nil, false, fmt.Errorf("expected a datasource object (old)") } + // Expose any secure value changes to the dual writer + var secureChanges common.InlineSecureValues + for k, v := range ds.Secure { + if v.Remove || v.Create != "" { + if secureChanges == nil { + secureChanges = make(common.InlineSecureValues) + } + secureChanges[k] = v + dualwrite.SetUpdatedSecureValues(ctx, ds.Secure) + } + + // The legacy store must use fixed names generated by the internal system + // we can not support external shared secrets when using the SQL backing for datasources + validName := getLegacySecureValueName(name, k) + if v.Name != validName { + return nil, false, fmt.Errorf("invalid secure value name %q, expected %q", v.Name, validName) + } + } + // Keep all the old secure values if len(oldDS.Secure) > 0 { for k, v := range oldDS.Secure { diff --git a/pkg/storage/legacysql/dualwrite/dualwriter.go b/pkg/storage/legacysql/dualwrite/dualwriter.go index b176c457e7b..03d50d6374f 100644 --- a/pkg/storage/legacysql/dualwrite/dualwriter.go +++ b/pkg/storage/legacysql/dualwrite/dualwriter.go @@ -16,14 +16,15 @@ import ( "k8s.io/apiserver/pkg/registry/rest" "github.com/grafana/grafana-app-sdk/logging" - + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" ) var ( - _ grafanarest.Storage = (*dualWriter)(nil) - tracer = otel.Tracer("github.com/grafana/grafana/pkg/storage/legacysql/dualwrite") + _ grafanarest.Storage = (*dualWriter)(nil) + + tracer = otel.Tracer("github.com/grafana/grafana/pkg/storage/legacysql/dualwrite") ) const ( @@ -385,6 +386,7 @@ func (d *dualWriter) Update(ctx context.Context, name string, objInfo rest.Updat // but legacy failed, the user would get a failure, but see the update did apply to the source // of truth, and be less likely to retry to save (and get the stores in sync again) + ctx = addToContext(ctx) legacyInfo := objInfo legacyForceCreate := forceAllowCreate unifiedInfo := objInfo @@ -516,9 +518,10 @@ func (d *dualWriter) ConvertToTable(ctx context.Context, object runtime.Object, } type wrappedUpdateInfo struct { - objInfo rest.UpdatedObjectInfo - legacyLabels map[string]string - legacyAnnotations map[string]string + objInfo rest.UpdatedObjectInfo + legacyLabels map[string]string + legacyAnnotations map[string]string + updatedSecureValues common.InlineSecureValues } // Preconditions implements rest.UpdatedObjectInfo. @@ -561,6 +564,13 @@ func (w *wrappedUpdateInfo) UpdatedObject(ctx context.Context, oldObj runtime.Ob meta.SetResourceVersion("") meta.SetUID("") + + if w.updatedSecureValues != nil { + if err = meta.SetSecureValues(w.updatedSecureValues); err != nil { + return nil, fmt.Errorf("unable to set secure values on duplicate object %w", err) + } + } + return obj, err }