diff --git a/pkg/registry/apis/secret/inline/service.go b/pkg/registry/apis/secret/inline/service.go index 33fbc50736e..8d48dfa8fb9 100644 --- a/pkg/registry/apis/secret/inline/service.go +++ b/pkg/registry/apis/secret/inline/service.go @@ -19,37 +19,46 @@ func ProvideInlineSecureValueService( accessClient authlib.AccessClient, ) (contracts.InlineSecureValueSupport, error) { if cfg.SecretsManagement.GrpcClientEnable { - grpcClientConfig := grpcutils.ReadGrpcClientConfig(cfg) - - if cfg.SecretsManagement.GrpcServerAddress == "" { - return nil, fmt.Errorf("grpc_server_address is required when grpc client is enabled") - } - - if grpcClientConfig.Token == "" || grpcClientConfig.TokenExchangeURL == "" { - return nil, fmt.Errorf("grpc_client_authentication.token and grpc_client_authentication.token_exchange_url are required when grpc client is enabled") - } - - tokenExchangeClient, err := authnlib.NewTokenExchangeClient(authnlib.TokenExchangeConfig{ - Token: grpcClientConfig.Token, - TokenExchangeURL: grpcClientConfig.TokenExchangeURL, - }) - if err != nil { - return nil, fmt.Errorf("failed to create token exchange client: %w", err) - } - - tlsConfig := readTLSFromConfig(cfg) - - client, err := NewGRPCInlineClient(tokenExchangeClient, tracer, cfg.SecretsManagement.GrpcServerAddress, tlsConfig) - if err != nil { - return nil, fmt.Errorf("failed to create grpc inline secure value client: %w", err) - } - - return client, nil + return NewGRPCSecureValueService( + grpcutils.ReadGrpcClientConfig(cfg), + cfg.SecretsManagement.GrpcServerAddress, + readTLSFromConfig(cfg), + tracer, + ) } return NewLocalInlineSecureValueService(tracer, secureValueService, accessClient), nil } +func NewGRPCSecureValueService(tokenCfg *grpcutils.GrpcClientConfig, + address string, + tlsCfg TLSConfig, + tracer trace.Tracer, +) (contracts.InlineSecureValueSupport, error) { + if address == "" { + return nil, fmt.Errorf("grpc_server_address is required when grpc client is enabled") + } + + if tokenCfg.Token == "" || tokenCfg.TokenExchangeURL == "" { + return nil, fmt.Errorf("grpc_client_authentication.token and grpc_client_authentication.token_exchange_url are required when grpc client is enabled") + } + + tokenExchangeClient, err := authnlib.NewTokenExchangeClient(authnlib.TokenExchangeConfig{ + Token: tokenCfg.Token, + TokenExchangeURL: tokenCfg.TokenExchangeURL, + }) + if err != nil { + return nil, fmt.Errorf("failed to create token exchange client: %w", err) + } + + client, err := NewGRPCInlineClient(tokenExchangeClient, tracer, address, tlsCfg) + if err != nil { + return nil, fmt.Errorf("failed to create grpc inline secure value client: %w", err) + } + + return client, nil +} + func readTLSFromConfig(cfg *setting.Cfg) TLSConfig { if !cfg.SecretsManagement.GrpcServerUseTLS { return TLSConfig{ diff --git a/pkg/services/apiserver/options/storage.go b/pkg/services/apiserver/options/storage.go index 5d7661a99e1..db4a0fe891d 100644 --- a/pkg/services/apiserver/options/storage.go +++ b/pkg/services/apiserver/options/storage.go @@ -15,6 +15,8 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" secret "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + inlinesecurevalue "github.com/grafana/grafana/pkg/registry/apis/secret/inline" + "github.com/grafana/grafana/pkg/services/authn/grpcutils" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/apistore" "github.com/grafana/grafana/pkg/storage/unified/resource" @@ -51,6 +53,14 @@ type StorageOptions struct { GrpcClientAuthenticationTokenNamespace string GrpcClientAuthenticationAllowInsecure bool + // Secrets Manager Configuration for InlineSecureValueSupport + SecretsManagerGrpcClientEnable bool + SecretsManagerGrpcServerAddress string + SecretsManagerGrpcServerUseTLS bool + SecretsManagerGrpcServerTLSSkipVerify bool + SecretsManagerGrpcServerTLSServerName string + SecretsManagerGrpcServerTLSCAFile string + // For file storage, this is the requested path DataPath string @@ -92,6 +102,14 @@ func (o *StorageOptions) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&o.GrpcClientAuthenticationTokenExchangeURL, "grpc-client-authentication-token-exchange-url", o.GrpcClientAuthenticationTokenExchangeURL, "Token exchange url for grpc client authentication") fs.StringVar(&o.GrpcClientAuthenticationTokenNamespace, "grpc-client-authentication-token-namespace", o.GrpcClientAuthenticationTokenNamespace, "Token namespace for grpc client authentication") fs.BoolVar(&o.GrpcClientAuthenticationAllowInsecure, "grpc-client-authentication-allow-insecure", o.GrpcClientAuthenticationAllowInsecure, "Allow insecure grpc client authentication") + + // Secrets Manager Configuration flags + fs.BoolVar(&o.SecretsManagerGrpcClientEnable, "grafana.secrets-manager.grpc-client-enable", false, "Enable gRPC client for secrets manager") + fs.StringVar(&o.SecretsManagerGrpcServerAddress, "grafana.secrets-manager.grpc-server-address", "", "gRPC server address for secrets manager") + fs.BoolVar(&o.SecretsManagerGrpcServerUseTLS, "grafana.secrets-manager.grpc-server-use-tls", false, "Use TLS for gRPC server communication") + fs.BoolVar(&o.SecretsManagerGrpcServerTLSSkipVerify, "grafana.secrets-manager.grpc-server-tls-skip-verify", false, "Skip TLS verification for gRPC server") + fs.StringVar(&o.SecretsManagerGrpcServerTLSServerName, "grafana.secrets-manager.grpc-server-tls-server-name", "", "Server name for TLS verification") + fs.StringVar(&o.SecretsManagerGrpcServerTLSCAFile, "grafana.secrets-manager.grpc-server-tls-ca-file", "", "CA file for TLS verification") } func (o *StorageOptions) Validate() []error { @@ -130,10 +148,19 @@ func (o *StorageOptions) Validate() []error { errs = append(errs, fmt.Errorf("grpc client auth namespace is required for unified-grpc storage")) } } + + if o.SecretsManagerGrpcClientEnable { + if o.SecretsManagerGrpcServerAddress == "" { + errs = append(errs, fmt.Errorf("secrets manager grpc server address is required for secrets manager grpc client")) + } + if o.SecretsManagerGrpcServerUseTLS && !o.SecretsManagerGrpcServerTLSSkipVerify && o.SecretsManagerGrpcServerTLSCAFile == "" { + errs = append(errs, fmt.Errorf("secrets manager grpc server ca file is required for secrets manager grpc client")) + } + } return errs } -func (o *StorageOptions) ApplyTo(serverConfig *genericapiserver.RecommendedConfig, etcdOptions *options.EtcdOptions, tracer tracing.Tracer) error { +func (o *StorageOptions) ApplyTo(serverConfig *genericapiserver.RecommendedConfig, etcdOptions *options.EtcdOptions, tracer tracing.Tracer, secureServing *options.SecureServingOptions) error { if o.StorageType != StorageTypeUnifiedGrpc { return nil } @@ -168,6 +195,35 @@ func (o *StorageOptions) ApplyTo(serverConfig *genericapiserver.RecommendedConfi if err != nil { return err } + + // setup inline secrets if configured + if o.InlineSecrets == nil && o.SecretsManagerGrpcClientEnable { + tlsCfg := inlinesecurevalue.TLSConfig{ + UseTLS: o.SecretsManagerGrpcServerUseTLS, + CAFile: o.SecretsManagerGrpcServerTLSCAFile, + ServerName: o.SecretsManagerGrpcServerTLSServerName, + InsecureSkipVerify: o.SecretsManagerGrpcServerTLSSkipVerify, + } + if o.SecretsManagerGrpcServerUseTLS && secureServing != nil { + tlsCfg.CertFile = secureServing.ServerCert.CertKey.CertFile + tlsCfg.KeyFile = secureServing.ServerCert.CertKey.KeyFile + } + inlineSecureValueService, err := inlinesecurevalue.NewGRPCSecureValueService( + &grpcutils.GrpcClientConfig{ + Token: o.GrpcClientAuthenticationToken, + TokenExchangeURL: o.GrpcClientAuthenticationTokenExchangeURL, + TokenNamespace: o.GrpcClientAuthenticationTokenNamespace, + }, + o.SecretsManagerGrpcServerAddress, + tlsCfg, + tracer, + ) + if err != nil { + return fmt.Errorf("failed to create inline secure value service: %w", err) + } + o.InlineSecrets = inlineSecureValueService + } + getter := apistore.NewRESTOptionsGetterForClient(unified, o.InlineSecrets, etcdOptions.StorageConfig, o.ConfigProvider) serverConfig.RESTOptionsGetter = getter return nil diff --git a/pkg/services/apiserver/options/storage_test.go b/pkg/services/apiserver/options/storage_test.go index 4710442c65b..3bdbc96b248 100644 --- a/pkg/services/apiserver/options/storage_test.go +++ b/pkg/services/apiserver/options/storage_test.go @@ -30,6 +30,47 @@ func TestStorageOptions_Validate(t *testing.T) { }, wantErr: false, }, + { + name: "with secrets manager grpc client and no server address", + Opts: StorageOptions{ + StorageType: StorageTypeUnifiedGrpc, + Address: "localhost:10000", + GrpcClientAuthenticationToken: "1234", + GrpcClientAuthenticationTokenExchangeURL: "http://localhost:8080", + GrpcClientAuthenticationTokenNamespace: "*", + SecretsManagerGrpcClientEnable: true, + }, + wantErr: true, + }, + { + name: "with secrets manager grpc client and no server ca file", + Opts: StorageOptions{ + StorageType: StorageTypeUnifiedGrpc, + Address: "localhost:10000", + GrpcClientAuthenticationToken: "1234", + GrpcClientAuthenticationTokenExchangeURL: "http://localhost:8080", + GrpcClientAuthenticationTokenNamespace: "*", + SecretsManagerGrpcClientEnable: true, + SecretsManagerGrpcServerAddress: "localhost:10000", + SecretsManagerGrpcServerUseTLS: true, + }, + wantErr: true, + }, + { + name: "with secrets manager grpc client and server ca file", + Opts: StorageOptions{ + StorageType: StorageTypeUnifiedGrpc, + Address: "localhost:10000", + GrpcClientAuthenticationToken: "1234", + GrpcClientAuthenticationTokenExchangeURL: "http://localhost:8080", + GrpcClientAuthenticationTokenNamespace: "*", + SecretsManagerGrpcClientEnable: true, + SecretsManagerGrpcServerAddress: "localhost:10000", + SecretsManagerGrpcServerUseTLS: true, + SecretsManagerGrpcServerTLSCAFile: "ca.crt", + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/storage/unified/sql/server.go b/pkg/storage/unified/sql/server.go index 6d2639007db..4037e4253c6 100644 --- a/pkg/storage/unified/sql/server.go +++ b/pkg/storage/unified/sql/server.go @@ -2,6 +2,7 @@ package sql import ( "context" + "fmt" "os" "strings" @@ -13,6 +14,7 @@ import ( "github.com/grafana/dskit/services" infraDB "github.com/grafana/grafana/pkg/infra/db" secrets "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + inlinesecurevalue "github.com/grafana/grafana/pkg/registry/apis/secret/inline" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/setting" @@ -48,6 +50,20 @@ func NewResourceServer( opts ServerOptions, ) (resource.ResourceServer, error) { apiserverCfg := opts.Cfg.SectionWithEnvOverrides("grafana-apiserver") + + if opts.SecureValues == nil && opts.Cfg != nil && opts.Cfg.SecretsManagement.GrpcClientEnable { + inlineSecureValueService, err := inlinesecurevalue.ProvideInlineSecureValueService( + opts.Cfg, + opts.Tracer, + nil, // not needed for gRPC client mode + nil, // not needed for gRPC client mode + ) + if err != nil { + return nil, fmt.Errorf("failed to create inline secure value service: %w", err) + } + opts.SecureValues = inlineSecureValueService + } + serverOptions := resource.ResourceServerOptions{ Tracer: opts.Tracer, Blob: resource.BlobConfig{