use config from secrets service

This commit is contained in:
Ryan McKinley
2025-07-01 13:44:47 -07:00
parent 2d4fd99e7a
commit b2799f977f
17 changed files with 151 additions and 92 deletions
+2 -12
View File
@@ -4,6 +4,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
secret "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -15,18 +16,7 @@ type GenericDataSource struct {
Spec GenericDataSourceSpec `json:"spec"`
// Secure values placeholder (true for fields that exist)
Secure map[string]SecureValue `json:"secure,omitempty"`
}
type SecureValue struct {
// The input is only valid for writing the value -- it is replaced on read
Input string `json:"input,omitempty"`
// The name identifier for this secure value
Reference string `json:"ref,omitempty"`
// Value for write, this will remove the secret value
Remove bool `json:"remove,omitempty"`
Secure secret.InlineSecureValues `json:"secure,omitempty"`
}
// DsAccess represents how the datasource connects to the remote service
@@ -8,6 +8,7 @@
package v0alpha1
import (
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@@ -78,7 +79,7 @@ func (in *GenericDataSource) DeepCopyInto(out *GenericDataSource) {
in.Spec.DeepCopyInto(&out.Spec)
if in.Secure != nil {
in, out := &in.Secure, &out.Secure
*out = make(map[string]SecureValue, len(*in))
*out = make(map[string]secretv0alpha1.InlineSecureValue, len(*in))
for key, val := range *in {
(*out)[key] = val
}
@@ -182,19 +183,3 @@ func (in *HealthCheckResult) DeepCopyObject() runtime.Object {
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecureValue) DeepCopyInto(out *SecureValue) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecureValue.
func (in *SecureValue) DeepCopy() *SecureValue {
if in == nil {
return nil
}
out := new(SecureValue)
in.DeepCopyInto(out)
return out
}
@@ -20,7 +20,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceList": schema_pkg_apis_datasource_v0alpha1_GenericDataSourceList(ref),
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceSpec": schema_pkg_apis_datasource_v0alpha1_GenericDataSourceSpec(ref),
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.HealthCheckResult": schema_pkg_apis_datasource_v0alpha1_HealthCheckResult(ref),
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.SecureValue": schema_pkg_apis_datasource_v0alpha1_SecureValue(ref),
}
}
@@ -164,7 +163,7 @@ func schema_pkg_apis_datasource_v0alpha1_GenericDataSource(ref common.ReferenceC
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.SecureValue"),
Ref: ref("github.com/grafana/grafana/pkg/apis/secret/v0alpha1.InlineSecureValue"),
},
},
},
@@ -175,7 +174,7 @@ func schema_pkg_apis_datasource_v0alpha1_GenericDataSource(ref common.ReferenceC
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceSpec", "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.SecureValue", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceSpec", "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.InlineSecureValue", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@@ -367,36 +366,3 @@ func schema_pkg_apis_datasource_v0alpha1_HealthCheckResult(ref common.ReferenceC
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"},
}
}
func schema_pkg_apis_datasource_v0alpha1_SecureValue(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"input": {
SchemaProps: spec.SchemaProps{
Description: "The input is only valid for writing the value -- it is replaced on read",
Type: []string{"string"},
Format: "",
},
},
"ref": {
SchemaProps: spec.SchemaProps{
Description: "The name identifier for this secure value",
Type: []string{"string"},
Format: "",
},
},
"remove": {
SchemaProps: spec.SchemaProps{
Description: "Value for write, this will remove the secret value",
Type: []string{"boolean"},
Format: "",
},
},
},
},
},
}
}
@@ -1,3 +1,2 @@
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/datasource/v0alpha1,GenericDataSourceSpec,User
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/datasource/v0alpha1,SecureValue,Reference
API rule violation: streaming_list_type_json_tags,github.com/grafana/grafana/pkg/apis/datasource/v0alpha1,GenericDataSourceList,ListMeta
@@ -6,9 +6,10 @@ import (
"fmt"
"testing"
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
)
func TestExposedSecureValue(t *testing.T) {
+23
View File
@@ -0,0 +1,23 @@
package v0alpha1
// Access secure values inside any resource
// +k8s:openapi-gen=true
type InlineSecureValue struct {
// Create a secure value
// +k8s:validation:minLength=1
// +k8s:validation:maxLength=24576
Create ExposedSecureValue `json:"create,omitempty"`
// Reference a shared secret (enterprise only)
Reference string `json:"ref,omitempty"`
// The resolved UID within the secret service
UID string `json:"uid,omitempty"`
// Remove this value -- cascading delete to the secret service if necessary
Remove bool `json:"remove,omitempty"`
}
// Collection of secure values
// +k8s:openapi-gen=true
type InlineSecureValues = map[string]InlineSecureValue
@@ -179,6 +179,22 @@ func (in *HashiCorpKeeperConfig) DeepCopy() *HashiCorpKeeperConfig {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *InlineSecureValue) DeepCopyInto(out *InlineSecureValue) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InlineSecureValue.
func (in *InlineSecureValue) DeepCopy() *InlineSecureValue {
if in == nil {
return nil
}
out := new(InlineSecureValue)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Keeper) DeepCopyInto(out *Keeper) {
*out = *in
@@ -25,6 +25,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.GCPKeeperConfig": schema_pkg_apis_secret_v0alpha1_GCPKeeperConfig(ref),
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.HashiCorpCredentials": schema_pkg_apis_secret_v0alpha1_HashiCorpCredentials(ref),
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.HashiCorpKeeperConfig": schema_pkg_apis_secret_v0alpha1_HashiCorpKeeperConfig(ref),
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.InlineSecureValue": schema_pkg_apis_secret_v0alpha1_InlineSecureValue(ref),
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.Keeper": schema_pkg_apis_secret_v0alpha1_Keeper(ref),
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.KeeperList": schema_pkg_apis_secret_v0alpha1_KeeperList(ref),
"github.com/grafana/grafana/pkg/apis/secret/v0alpha1.KeeperSpec": schema_pkg_apis_secret_v0alpha1_KeeperSpec(ref),
@@ -355,6 +356,47 @@ func schema_pkg_apis_secret_v0alpha1_HashiCorpKeeperConfig(ref common.ReferenceC
}
}
func schema_pkg_apis_secret_v0alpha1_InlineSecureValue(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Access secure values inside any resource",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"create": {
SchemaProps: spec.SchemaProps{
Description: "Create a secure value",
Type: []string{"string"},
Format: "",
},
},
"ref": {
SchemaProps: spec.SchemaProps{
Description: "Reference a shared secret (enterprise only)",
Type: []string{"string"},
Format: "",
},
},
"uid": {
SchemaProps: spec.SchemaProps{
Description: "The resolved UID within the secret service",
Type: []string{"string"},
Format: "",
},
},
"remove": {
SchemaProps: spec.SchemaProps{
Description: "Remove this value -- cascading delete to the secret service if necessary",
Type: []string{"boolean"},
Format: "",
},
},
},
},
},
}
}
func schema_pkg_apis_secret_v0alpha1_Keeper(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -3,6 +3,7 @@ API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alp
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,AzureCredentials,ClientID
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,AzureCredentials,TenantID
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,GCPCredentials,ProjectID
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,InlineSecureValue,Reference
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,KeeperSpec,Azure
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,KeeperSpec,HashiCorp
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/secret/v0alpha1,SecureValueStatus,ExternalID
+13 -5
View File
@@ -2,6 +2,7 @@ package datasource
import (
"fmt"
"strconv"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -9,6 +10,7 @@ import (
"github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
secret "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
@@ -68,6 +70,12 @@ func (r *converter) asGenericDataSource(ds *datasources.DataSource) (*v0alpha1.G
}
cfg.UID = gapiutil.CalculateClusterWideUID(cfg)
if ds.ID > 0 {
cfg.Labels = map[string]string{
utils.LabelKeyDeprecatedInternalID: strconv.FormatInt(ds.ID, 10),
}
}
if ds.JsonData != nil {
val, ok := ds.JsonData.Interface().(map[string]any)
if !ok {
@@ -77,10 +85,10 @@ func (r *converter) asGenericDataSource(ds *datasources.DataSource) (*v0alpha1.G
}
if ds.SecureJsonData != nil {
cfg.Secure = make(map[string]v0alpha1.SecureValue)
cfg.Secure = make(secret.InlineSecureValues)
for k := range ds.SecureJsonData {
cfg.Secure[k] = v0alpha1.SecureValue{
Reference: "~", // ????
cfg.Secure[k] = secret.InlineSecureValue{
UID: "????", // ????
}
}
}
@@ -166,8 +174,8 @@ func toSecureJsonData(ds *v0alpha1.GenericDataSource) map[string]string {
secure := map[string]string{}
for k, v := range ds.Secure {
if v.Input != "" {
secure[k] = v.Input
if v.Create != "" {
secure[k] = v.Create.DangerouslyExposeAndConsumeValue()
}
if v.Remove {
secure[k] = "" // Weirdly, this is the best we can do with the legacy API :(
@@ -14,8 +14,6 @@
"bbb": true,
"ccc": 1.234
},
"secureJsonData": {
"password": "XXXX"
},
"secureJsonData": {},
"uid": "cejobd88i85j4d"
}
@@ -14,9 +14,7 @@
"bbb": true,
"ccc": 1.234
},
"secureJsonData": {
"password": "XXXX"
},
"secureJsonData": {},
"uid": "cejobd88i85j4d",
"version": 2
}
+8 -6
View File
@@ -4,21 +4,23 @@ import (
"maps"
"strings"
common "k8s.io/kube-openapi/pkg/common"
openapi "k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
spec "k8s.io/kube-openapi/pkg/validation/spec"
data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
secret "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
)
// This should eventually live in grafana-app-sdk
func GetOpenAPIDefinitions(builders []APIGroupBuilder) common.GetOpenAPIDefinitions {
return func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
defs := v0alpha1.GetOpenAPIDefinitions(ref) // common grafana apis
func GetOpenAPIDefinitions(builders []APIGroupBuilder) openapi.GetOpenAPIDefinitions {
return func(ref openapi.ReferenceCallback) map[string]openapi.OpenAPIDefinition {
defs := common.GetOpenAPIDefinitions(ref) // common grafana apis
maps.Copy(defs, data.GetOpenAPIDefinitions(ref))
maps.Copy(defs, secret.GetOpenAPIDefinitions(ref)) // Expose secret reference to all resources
// TODO: add timerange to upstream SDK setup
maps.Copy(defs, map[string]common.OpenAPIDefinition{
maps.Copy(defs, map[string]openapi.OpenAPIDefinition{
"github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1.TimeRange": {
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
+8 -2
View File
@@ -254,10 +254,16 @@ type GetDataSourcesByTypeQuery struct {
// GetDataSourceQuery will get a DataSource based on OrgID as well as the UID (preferred), ID, or Name.
// At least one of the UID, ID, or Name properties must be set in addition to OrgID.
type GetDataSourceQuery struct {
ID int64
UID string
// Deprecated: use UID
ID int64
// The datasource unique id
UID string
// Deprecated: Use UID
Name string
// Required
OrgID int64
}
@@ -481,12 +481,15 @@ func (s *Service) UpdateDataSource(ctx context.Context, cmd *datasources.UpdateD
query := &datasources.GetDataSourceQuery{
ID: cmd.ID,
UID: cmd.UID,
OrgID: cmd.OrgID,
}
dataSource, err = s.SQLStore.GetDataSource(ctx, query)
if err != nil {
return err
}
cmd.UID = dataSource.UID
cmd.ID = dataSource.ID
// Validate the command
jd, err := cmd.JsonData.ToDB()
+10 -6
View File
@@ -61,7 +61,7 @@ func (ss *SqlStore) GetDataSource(ctx context.Context, query *datasources.GetDat
}
func (ss *SqlStore) getDataSource(_ context.Context, query *datasources.GetDataSourceQuery, sess *db.Session) (*datasources.DataSource, error) {
if query.OrgID == 0 || (query.ID == 0 && len(query.Name) == 0 && len(query.UID) == 0) {
if query.OrgID == 0 {
return nil, datasources.ErrDataSourceIdentifierNotSet
}
@@ -69,6 +69,8 @@ func (ss *SqlStore) getDataSource(_ context.Context, query *datasources.GetDataS
if err := util.ValidateUID(query.UID); err != nil {
logDeprecatedInvalidDsUid(ss.logger, query.UID, query.Name, "read", fmt.Errorf("invalid UID"))
}
} else if query.ID == 0 {
return nil, datasources.ErrDataSourceIdentifierNotSet
}
datasource := &datasources.DataSource{Name: query.Name, OrgID: query.OrgID, ID: query.ID, UID: query.UID}
@@ -324,11 +326,13 @@ func (ss *SqlStore) UpdateDataSource(ctx context.Context, cmd *datasources.Updat
cmd.JsonData = simplejson.New()
}
if cmd.UID != "" {
if err := util.ValidateUID(cmd.UID); err != nil {
logDeprecatedInvalidDsUid(ss.logger, cmd.UID, cmd.Name, "update", err)
return datasources.ErrDataSourceUIDInvalid.Errorf("invalid UID for datasource %s: %w", cmd.Name, err)
}
if cmd.OrgID == 0 || cmd.ID == 0 || cmd.UID == "" {
return datasources.ErrDataSourceIdentifierNotSet
}
if err := util.ValidateUID(cmd.UID); err != nil {
logDeprecatedInvalidDsUid(ss.logger, cmd.UID, cmd.Name, "update", err)
return datasources.ErrDataSourceUIDInvalid.Errorf("invalid UID for datasource %s: %w", cmd.Name, err)
}
ds = &datasources.DataSource{
+17
View File
@@ -0,0 +1,17 @@
apiVersion: testdata.datasource.grafana.app/v0alpha1
kind: GenericDataSource
metadata:
name: sample-testdata
spec:
title: Sample datasource
access: proxy
isDefault: true
jsonData:
key: value
hello: 10
world: false
secure:
sampleA:
create: secret value here # replaced with UID on write
sampleB:
uid: XYZ # will not exist