K8s/SecureValues: Wire InlineSecureValueSupport to apistore (#109449)

* inline wire

* extra fields

* add variable

* wire
This commit is contained in:
Ryan McKinley
2025-08-11 15:22:56 +03:00
committed by GitHub
parent 4682a288a3
commit e0404f924c
13 changed files with 245 additions and 191 deletions
@@ -42,7 +42,7 @@ func (s *DashboardStorage) NewStore(dash utils.ResourceInfo, scheme *runtime.Sch
return nil, err
}
client := legacy.NewDirectResourceClient(server) // same context
optsGetter := apistore.NewRESTOptionsGetterForClient(client,
optsGetter := apistore.NewRESTOptionsGetterForClient(client, nil,
defaultOpts.StorageConfig.Config, nil,
)
optsGetter.RegisterOptions(dash.GroupResource(), apistore.StorageOptions{
+1 -1
View File
@@ -333,7 +333,7 @@ func NewLocalStore(resourceInfo utils.ResourceInfo, scheme *runtime.Scheme, defa
}
client := resource.NewLocalResourceClient(server)
optsGetter := apistore.NewRESTOptionsGetterForClient(client, defaultOpts.StorageConfig.Config, nil)
optsGetter := apistore.NewRESTOptionsGetterForClient(client, nil, defaultOpts.StorageConfig.Config, nil)
store, err := grafanaregistry.NewRegistryStore(scheme, resourceInfo, optsGetter)
return store, err
+7 -1
View File
@@ -59,6 +59,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/usage"
"github.com/grafana/grafana/pkg/registry/apis/secret"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -107,7 +108,8 @@ type APIBuilder struct {
legacyMigrator legacy.LegacyMigrator
storageStatus dualwrite.Service
unified resource.ResourceClient
repositorySecrets secrets.RepositorySecrets
decryptSvc secret.DecryptService
repositorySecrets secrets.RepositorySecrets // << Will be removed when the decryptSvc usage is stable
client client.ProvisioningV0alpha1Interface
access authlib.AccessChecker
mutators []controller.Mutator
@@ -130,6 +132,7 @@ func NewAPIBuilder(
legacyMigrator legacy.LegacyMigrator,
storageStatus dualwrite.Service,
usageStats usagestats.Service,
decryptSvc secret.DecryptService,
repositorySecrets secrets.RepositorySecrets,
access authlib.AccessChecker,
tracer tracing.Tracer,
@@ -160,6 +163,7 @@ func NewAPIBuilder(
legacyMigrator: legacyMigrator,
storageStatus: storageStatus,
unified: unified,
decryptSvc: decryptSvc,
repositorySecrets: repositorySecrets,
access: access,
jobHistoryConfig: jobHistoryConfig,
@@ -232,6 +236,7 @@ func RegisterAPIService(
legacyMigrator legacy.LegacyMigrator,
storageStatus dualwrite.Service,
usageStats usagestats.Service,
decryptSvc secret.DecryptService,
repositorySecrets secrets.RepositorySecrets,
tracer tracing.Tracer,
extraBuilders []ExtraBuilder,
@@ -250,6 +255,7 @@ func RegisterAPIService(
configProvider, ghFactory,
legacyMigrator, storageStatus,
usageStats,
decryptSvc,
repositorySecrets,
access,
tracer,
+160 -150
View File
File diff suppressed because one or more lines are too long
@@ -75,7 +75,7 @@ func (o *GrafanaAggregatorOptions) ApplyTo(aggregatorConfig *aggregatorapiserver
return err
}
// override the RESTOptionsGetter to use the in memory storage options
restOptionsGetter, err := apistore.NewRESTOptionsGetterMemory(etcdOptions.StorageConfig)
restOptionsGetter, err := apistore.NewRESTOptionsGetterMemory(etcdOptions.StorageConfig, nil)
if err != nil {
return err
}
+5 -1
View File
@@ -14,6 +14,7 @@ import (
"k8s.io/client-go/rest"
"github.com/grafana/grafana/pkg/infra/tracing"
secret "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
@@ -63,6 +64,9 @@ type StorageOptions struct {
// value, it is considered large and gets partially stored in blob storage.
BlobThresholdBytes int
// Support writing secrets inline
InlineSecrets secret.InlineSecureValueSupport
// {resource}.{group} = 1|2|3|4
UnifiedStorageConfig map[string]setting.UnifiedStorageConfig
@@ -164,7 +168,7 @@ func (o *StorageOptions) ApplyTo(serverConfig *genericapiserver.RecommendedConfi
if err != nil {
return err
}
getter := apistore.NewRESTOptionsGetterForClient(unified, etcdOptions.StorageConfig, o.ConfigProvider)
getter := apistore.NewRESTOptionsGetterForClient(unified, o.InlineSecrets, etcdOptions.StorageConfig, o.ConfigProvider)
serverConfig.RESTOptionsGetter = getter
return nil
}
+5 -1
View File
@@ -39,6 +39,7 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/registry/apis/datasource"
secret "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/services/apiserver/aggregatorrunner"
"github.com/grafana/grafana/pkg/services/apiserver/appinstaller"
"github.com/grafana/grafana/pkg/services/apiserver/auth/authenticator"
@@ -105,6 +106,7 @@ type service struct {
contextProvider datasource.PluginContextWrapper
pluginStore pluginstore.Store
unified resource.ResourceClient
secrets secret.InlineSecureValueSupport
restConfigProvider RestConfigProvider
buildHandlerChainFuncFromBuilders builder.BuildHandlerChainFuncFromBuilders
@@ -128,6 +130,7 @@ func ProvideService(
pluginStore pluginstore.Store,
storageStatus dualwrite.Service,
unified resource.ResourceClient,
secrets secret.InlineSecureValueSupport,
restConfigProvider RestConfigProvider,
buildHandlerChainFuncFromBuilders builder.BuildHandlerChainFuncFromBuilders,
eventualRestConfigProvider *eventualRestConfigProvider,
@@ -159,6 +162,7 @@ func ProvideService(
serverLockService: serverLockService,
storageStatus: storageStatus,
unified: unified,
secrets: secrets,
restConfigProvider: restConfigProvider,
buildHandlerChainFuncFromBuilders: buildHandlerChainFuncFromBuilders,
aggregatorRunner: aggregatorRunner,
@@ -337,7 +341,7 @@ func (s *service) start(ctx context.Context) error {
return err
}
} else {
getter := apistore.NewRESTOptionsGetterForClient(s.unified, o.RecommendedOptions.Etcd.StorageConfig, s.restConfigProvider)
getter := apistore.NewRESTOptionsGetterForClient(s.unified, s.secrets, o.RecommendedOptions.Etcd.StorageConfig, s.restConfigProvider)
optsregister = getter.RegisterOptions
serverConfig.RESTOptionsGetter = getter
}
+16 -4
View File
@@ -19,6 +19,7 @@ import (
flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request"
"k8s.io/client-go/tools/cache"
secret "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/storage/unified/resource"
)
@@ -28,6 +29,7 @@ type StorageOptionsRegister func(gr schema.GroupResource, opts StorageOptions)
type RESTOptionsGetter struct {
client resource.ResourceClient
secrets secret.InlineSecureValueSupport
original storagebackend.Config
configProvider RestConfigProvider
@@ -35,16 +37,22 @@ type RESTOptionsGetter struct {
options map[string]StorageOptions
}
func NewRESTOptionsGetterForClient(client resource.ResourceClient, original storagebackend.Config, configProvider RestConfigProvider) *RESTOptionsGetter {
func NewRESTOptionsGetterForClient(
client resource.ResourceClient,
secrets secret.InlineSecureValueSupport,
original storagebackend.Config,
configProvider RestConfigProvider,
) *RESTOptionsGetter {
return &RESTOptionsGetter{
client: client,
secrets: secrets,
original: original,
options: make(map[string]StorageOptions),
configProvider: configProvider,
}
}
func NewRESTOptionsGetterMemory(originalStorageConfig storagebackend.Config) (*RESTOptionsGetter, error) {
func NewRESTOptionsGetterMemory(originalStorageConfig storagebackend.Config, secrets secret.InlineSecureValueSupport) (*RESTOptionsGetter, error) {
backend, err := resource.NewCDKBackend(context.Background(), resource.CDKBackendOptions{
Bucket: memblob.OpenBucket(&memblob.Options{}),
})
@@ -59,6 +67,7 @@ func NewRESTOptionsGetterMemory(originalStorageConfig storagebackend.Config) (*R
}
return NewRESTOptionsGetterForClient(
resource.NewLocalResourceClient(server),
secrets,
originalStorageConfig,
nil,
), nil
@@ -67,7 +76,7 @@ func NewRESTOptionsGetterMemory(originalStorageConfig storagebackend.Config) (*R
// Optionally, this constructor allows specifying directories
// for resources that are required to be read/watched on startup and there
// won't be any write operations that initially bootstrap their directories
func NewRESTOptionsGetterForFile(path string,
func NewRESTOptionsGetterForFileXX(path string,
originalStorageConfig storagebackend.Config,
features map[string]any) (*RESTOptionsGetter, error) {
if path == "" {
@@ -95,6 +104,7 @@ func NewRESTOptionsGetterForFile(path string,
}
return NewRESTOptionsGetterForClient(
resource.NewLocalResourceClient(server),
nil, // secrets
originalStorageConfig,
nil,
), nil
@@ -137,8 +147,10 @@ func (r *RESTOptionsGetter) GetRESTOptions(resource schema.GroupResource, _ runt
trigger storage.IndexerFuncs,
indexers *cache.Indexers,
) (storage.Interface, factory.DestroyFunc, error) {
opts := r.options[resource.String()]
opts.SecureValues = r.secrets
return NewStorage(config, r.client, keyFunc, nil, newFunc, newListFunc, getAttrsFunc,
trigger, indexers, r.configProvider, r.options[resource.String()])
trigger, indexers, r.configProvider, opts)
},
DeleteCollectionWorkers: 0,
EnableGarbageCollection: false,
+4 -1
View File
@@ -32,9 +32,9 @@ import (
"k8s.io/client-go/tools/cache"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
secrets "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
@@ -61,6 +61,9 @@ type StorageOptions struct {
// Add internalID label when missing
RequireDeprecatedInternalID bool
// Process inline secure values
SecureValues secrets.InlineSecureValueSupport
// Temporary fix to support adding default permissions AfterCreate
Permissions DefaultPermissionSetter
}
+16 -14
View File
@@ -6,24 +6,23 @@ import (
"path/filepath"
"time"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"gocloud.dev/blob/fileblob"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
otgrpc "github.com/opentracing-contrib/go-grpc"
"github.com/opentracing/opentracing-go"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"gocloud.dev/blob/fileblob"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/grafana/authlib/types"
"github.com/grafana/dskit/flagext"
"github.com/grafana/dskit/grpcclient"
"github.com/grafana/dskit/middleware"
"github.com/grafana/dskit/services"
infraDB "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
secrets "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/services/apiserver/options"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
@@ -36,13 +35,14 @@ import (
)
type Options struct {
Cfg *setting.Cfg
Features featuremgmt.FeatureToggles
DB infraDB.DB
Tracer tracing.Tracer
Reg prometheus.Registerer
Authzc types.AccessClient
Docs resource.DocumentBuilderSupplier
Cfg *setting.Cfg
Features featuremgmt.FeatureToggles
DB infraDB.DB
Tracer tracing.Tracer
Reg prometheus.Registerer
Authzc types.AccessClient
Docs resource.DocumentBuilderSupplier
SecureValues secrets.InlineSecureValueSupport
}
type clientMetrics struct {
@@ -64,7 +64,7 @@ func ProvideUnifiedStorageClient(opts *Options,
SearchServerAddress: apiserverCfg.Key("search_server_address").MustString(""),
BlobStoreURL: apiserverCfg.Key("blob_url").MustString(""),
BlobThresholdBytes: apiserverCfg.Key("blob_threshold_bytes").MustInt(options.BlobThresholdDefault),
}, opts.Cfg, opts.Features, opts.DB, opts.Tracer, opts.Reg, opts.Authzc, opts.Docs, storageMetrics, indexMetrics)
}, opts.Cfg, opts.Features, opts.DB, opts.Tracer, opts.Reg, opts.Authzc, opts.Docs, storageMetrics, indexMetrics, opts.SecureValues)
if err == nil {
// Used to get the folder stats
client = federated.NewFederatedClient(
@@ -86,6 +86,7 @@ func newClient(opts options.StorageOptions,
docs resource.DocumentBuilderSupplier,
storageMetrics *resource.StorageMetrics,
indexMetrics *resource.BleveIndexMetrics,
secure secrets.InlineSecureValueSupport,
) (resource.ResourceClient, error) {
ctx := context.Background()
@@ -168,6 +169,7 @@ func newClient(opts options.StorageOptions,
StorageMetrics: storageMetrics,
IndexMetrics: indexMetrics,
Features: features,
SecureValues: secure,
}
if cfg.QOSEnabled {
+5 -3
View File
@@ -6,6 +6,9 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
authlib "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/apiserver/options"
@@ -13,9 +16,6 @@ import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)
func TestUnifiedStorageClient(t *testing.T) {
@@ -45,6 +45,7 @@ func TestUnifiedStorageClient(t *testing.T) {
nil,
nil,
nil,
nil,
)
require.NoError(t, err)
@@ -78,6 +79,7 @@ func TestUnifiedStorageClient(t *testing.T) {
nil,
nil,
nil,
nil,
)
require.NoError(t, err)
+20 -11
View File
@@ -21,8 +21,8 @@ import (
claims "github.com/grafana/authlib/types"
"github.com/grafana/dskit/backoff"
"github.com/grafana/dskit/ring"
"github.com/grafana/grafana/pkg/apimachinery/utils"
secrets "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/util/scheduler"
)
@@ -208,6 +208,9 @@ type ResourceServerOptions struct {
// Link RBAC
AccessClient claims.AccessClient
// Manage secure values
SecureValues secrets.InlineSecureValueSupport
// Callbacks for startup and shutdown
Lifecycle LifecycleHooks
@@ -297,6 +300,7 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) {
blob: blobstore,
diagnostics: opts.Diagnostics,
access: opts.AccessClient,
secure: opts.SecureValues,
writeHooks: opts.WriteHooks,
lifecycle: opts.Lifecycle,
now: opts.Now,
@@ -333,6 +337,7 @@ type server struct {
log *slog.Logger
backend StorageBackend
blob BlobSupport
secure secrets.InlineSecureValueSupport
search *searchSupport
diagnostics resourcepb.DiagnosticsServer
access claims.AccessClient
@@ -444,16 +449,6 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *resour
return nil, NewBadRequestError("can not save annotation: " + utils.AnnoKeyGrantPermissions)
}
// Verify that this resource can reference secure values
secure, err := obj.GetSecureValues()
if err != nil {
return nil, AsErrorResult(err)
}
if len(secure) > 0 {
// See: https://github.com/grafana/grafana/pull/107803
return nil, NewBadRequestError("Saving secure values is not yet supported")
}
event := &WriteEvent{
Value: value,
Key: key,
@@ -477,6 +472,20 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *resour
}
}
// Verify that this resource can reference secure values
secure, err := obj.GetSecureValues()
if err != nil {
return nil, AsErrorResult(err)
}
if len(secure) > 0 {
if s.secure == nil {
return nil, NewBadRequestError("secure storage not configured")
}
// See: https://github.com/grafana/grafana/pull/107803
return nil, NewBadRequestError("Saving secure values is not yet supported")
}
if key.Namespace != obj.GetNamespace() {
return nil, NewBadRequestError("key/namespace do not match")
}
+4 -2
View File
@@ -11,8 +11,8 @@ import (
"github.com/grafana/authlib/types"
"github.com/grafana/dskit/ring"
"github.com/grafana/dskit/services"
infraDB "github.com/grafana/grafana/pkg/infra/db"
secrets "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/setting"
@@ -38,6 +38,7 @@ type ServerOptions struct {
IndexMetrics *resource.BleveIndexMetrics
Features featuremgmt.FeatureToggles
QOSQueue QOSEnqueueDequeuer
SecureValues secrets.InlineSecureValueSupport
Ring *ring.Ring
RingLifecycler *ring.BasicLifecycler
}
@@ -52,7 +53,8 @@ func NewResourceServer(
Blob: resource.BlobConfig{
URL: apiserverCfg.Key("blob_url").MustString(""),
},
Reg: opts.Reg,
Reg: opts.Reg,
SecureValues: opts.SecureValues,
}
if opts.AccessClient != nil {
serverOptions.AccessClient = resource.NewAuthzLimitedClient(opts.AccessClient, resource.AuthzOptions{Tracer: opts.Tracer, Registry: opts.Reg})