diff --git a/pkg/registry/apis/provisioning/secrets/secret.go b/pkg/registry/apis/provisioning/secrets/secret.go index f0c69e88153..707bbd5f9d4 100644 --- a/pkg/registry/apis/provisioning/secrets/secret.go +++ b/pkg/registry/apis/provisioning/secrets/secret.go @@ -76,8 +76,8 @@ func (s *secretsService) Encrypt(ctx context.Context, namespace, name string, da } decrypters := []string{svcName} - if s.cfg.SecretsManagement.DecryptGrafanaServiceName != "" { - decrypters = append(decrypters, s.cfg.SecretsManagement.DecryptGrafanaServiceName) + if s.cfg.SecretsManagement.GrpcGrafanaServiceName != "" { + decrypters = append(decrypters, s.cfg.SecretsManagement.GrpcGrafanaServiceName) } // Create the secret directly as unstructured diff --git a/pkg/registry/apis/secret/decrypt/service.go b/pkg/registry/apis/secret/decrypt/service.go index c0d014109d9..28a247ae103 100644 --- a/pkg/registry/apis/secret/decrypt/service.go +++ b/pkg/registry/apis/secret/decrypt/service.go @@ -4,7 +4,6 @@ import ( "fmt" authnlib "github.com/grafana/authlib/authn" - claims "github.com/grafana/authlib/types" "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" @@ -13,24 +12,15 @@ import ( ) func ProvideDecryptService(cfg *setting.Cfg, tracer trace.Tracer, decryptStorage contracts.DecryptStorage) (contracts.DecryptService, error) { - switch cfg.SecretsManagement.DecryptServerType { - case "grpc": + if cfg.SecretsManagement.GrpcClientEnable { grpcClientConfig := grpcutils.ReadGrpcClientConfig(cfg) - if cfg.SecretsManagement.DecryptServerAddress == "" { - return nil, fmt.Errorf("decrypt_server_address is required when decrypt_server_type is grpc") + 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 secrets_manager.decrypt_server_type is grpc") - } - - nsInfo, err := claims.ParseNamespace(grpcClientConfig.TokenNamespace) - if err != nil { - return nil, fmt.Errorf("failed to parse token namespace %v: %w", grpcClientConfig.TokenNamespace, err) - } - if nsInfo.OrgID < 1 { - return nil, fmt.Errorf("invalid token namepsace %v", grpcClientConfig.TokenNamespace) + 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{ @@ -43,22 +33,19 @@ func ProvideDecryptService(cfg *setting.Cfg, tracer trace.Tracer, decryptStorage tlsConfig := readTLSFromConfig(cfg) - client, err := NewGRPCDecryptClientWithTLS(tokenExchangeClient, tracer, cfg.SecretsManagement.DecryptServerAddress, tlsConfig) + client, err := NewGRPCDecryptClientWithTLS(tokenExchangeClient, tracer, cfg.SecretsManagement.GrpcServerAddress, tlsConfig) if err != nil { return nil, fmt.Errorf("failed to create grpc decrypt client: %w", err) } return client, nil - - case "local", "": - return NewLocalDecryptClient(decryptStorage) } - return nil, fmt.Errorf("unsupported storage type: %s", cfg.SecretsManagement.DecryptServerType) + return NewLocalDecryptClient(decryptStorage) } func readTLSFromConfig(cfg *setting.Cfg) TLSConfig { - if !cfg.SecretsManagement.DecryptServerUseTLS { + if !cfg.SecretsManagement.GrpcServerUseTLS { return TLSConfig{ UseTLS: false, InsecureSkipVerify: true, @@ -72,7 +59,7 @@ func readTLSFromConfig(cfg *setting.Cfg) TLSConfig { CertFile: apiServer.Key("proxy_client_cert_file").MustString(""), KeyFile: apiServer.Key("proxy_client_key_file").MustString(""), CAFile: apiServer.Key("apiservice_ca_bundle_file").MustString(""), - ServerName: cfg.SecretsManagement.DecryptServerTLSServerName, - InsecureSkipVerify: cfg.SecretsManagement.DecryptServerTLSSkipVerify, + ServerName: cfg.SecretsManagement.GrpcServerTLSServerName, + InsecureSkipVerify: cfg.SecretsManagement.GrpcServerTLSSkipVerify, } } diff --git a/pkg/registry/apis/secret/decrypt/service_test.go b/pkg/registry/apis/secret/decrypt/service_test.go index d108d0d15ff..a1baf435ba9 100644 --- a/pkg/registry/apis/secret/decrypt/service_test.go +++ b/pkg/registry/apis/secret/decrypt/service_test.go @@ -1,4 +1,4 @@ -package decrypt +package decrypt_test import ( "context" @@ -11,7 +11,6 @@ import ( decryptv1beta1 "github.com/grafana/grafana/apps/secret/decrypt/v1beta1" secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" - "github.com/madflojo/testcerts" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace/noop" @@ -21,6 +20,8 @@ import ( "google.golang.org/grpc/metadata" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/decrypt" + "github.com/grafana/grafana/pkg/registry/apis/secret/testutils" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/services/authn/clients" "github.com/grafana/grafana/pkg/setting" @@ -43,9 +44,8 @@ func TestDecryptService(t *testing.T) { } cfg := setting.NewCfg() - cfg.SecretsManagement.DecryptServerType = "local" - decryptService, err := ProvideDecryptService(cfg, tracer, mockStorage) + decryptService, err := decrypt.ProvideDecryptService(cfg, tracer, mockStorage) require.NoError(t, err) resp, err := decryptService.Decrypt(ctx, "svc-name", "default", []string{"secure-value-1"}) @@ -72,9 +72,8 @@ func TestDecryptService(t *testing.T) { } cfg := setting.NewCfg() - cfg.SecretsManagement.DecryptServerType = "local" - decryptService, err := ProvideDecryptService(cfg, tracer, mockStorage) + decryptService, err := decrypt.ProvideDecryptService(cfg, tracer, mockStorage) require.NoError(t, err) resp, err := decryptService.Decrypt(ctx, "svc-name", "default", []string{"secure-value-1", "secure-value-2"}) @@ -100,9 +99,9 @@ func TestDecryptService(t *testing.T) { } cfg := setting.NewCfg() - cfg.SecretsManagement.DecryptServerType = "local" + cfg.SecretsManagement.GrpcClientEnable = false - decryptService, err := ProvideDecryptService(cfg, tracer, mockStorage) + decryptService, err := decrypt.ProvideDecryptService(cfg, tracer, mockStorage) require.NoError(t, err) resp, err := decryptService.Decrypt(ctx, "svc-name", "default", []string{"secure-value-1", "secure-value-2"}) @@ -111,39 +110,27 @@ func TestDecryptService(t *testing.T) { require.EqualValues(t, decryptedValuesResp, resp) }) - t.Run("when storage type is unsupported, it returns an error", func(t *testing.T) { + t.Run("when grpc server is enabled but token exchange config is missing, it returns an error", func(t *testing.T) { t.Parallel() cfg := setting.NewCfg() - cfg.SecretsManagement.DecryptServerType = "unsupported" + cfg.SecretsManagement.GrpcClientEnable = true + cfg.SecretsManagement.GrpcServerAddress = "127.0.0.1:10000" - decryptService, err := ProvideDecryptService(cfg, tracer, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "unsupported storage type") - require.Nil(t, decryptService) - }) - - t.Run("when storage type is grpc but token exchange config is missing, it returns an error", func(t *testing.T) { - t.Parallel() - - cfg := setting.NewCfg() - cfg.SecretsManagement.DecryptServerType = "grpc" - cfg.SecretsManagement.DecryptServerAddress = "127.0.0.1:10000" - - _, err := ProvideDecryptService(cfg, tracer, nil) + _, err := decrypt.ProvideDecryptService(cfg, tracer, nil) require.Error(t, err) require.Contains(t, err.Error(), "grpc_client_authentication.token and grpc_client_authentication.token_exchange_url are required") }) - t.Run("when storage type is grpc but storage address is missing, it returns an error", func(t *testing.T) { + t.Run("when grpc server is enabled but storage address is missing, it returns an error", func(t *testing.T) { t.Parallel() cfg := setting.NewCfg() - cfg.SecretsManagement.DecryptServerType = "grpc" + cfg.SecretsManagement.GrpcClientEnable = true - _, err := ProvideDecryptService(cfg, tracer, nil) + _, err := decrypt.ProvideDecryptService(cfg, tracer, nil) require.Error(t, err) - require.Contains(t, err.Error(), "decrypt_server_address is required") + require.Contains(t, err.Error(), "grpc_server_address is required") }) t.Run("happy path with grpc+tls server with fake toke exchanger and server", func(t *testing.T) { @@ -165,9 +152,9 @@ func TestDecryptService(t *testing.T) { listener, err := nettest.NewLocalListener("tcp") require.NoError(t, err) - certPaths := createX509TestDir(t) + certPaths := testutils.CreateX509TestDir(t) - serverCert, err := tls.LoadX509KeyPair(certPaths.serverCert, certPaths.serverKey) + serverCert, err := tls.LoadX509KeyPair(certPaths.ServerCert, certPaths.ServerKey) require.NoError(t, err) tlsConfig := &tls.Config{ @@ -204,12 +191,12 @@ func TestDecryptService(t *testing.T) { namespace := "stacks-1234" cfg := setting.NewCfg() - cfg.SecretsManagement.DecryptServerType = "grpc" - cfg.SecretsManagement.DecryptServerAddress = listener.Addr().String() - cfg.SecretsManagement.DecryptGrafanaServiceName = grafanaSvcIdentity - cfg.SecretsManagement.DecryptServerUseTLS = true - cfg.SecretsManagement.DecryptServerTLSServerName = "localhost" - cfg.SecretsManagement.DecryptServerTLSSkipVerify = false + cfg.SecretsManagement.GrpcClientEnable = true + cfg.SecretsManagement.GrpcServerAddress = listener.Addr().String() + cfg.SecretsManagement.GrpcGrafanaServiceName = grafanaSvcIdentity + cfg.SecretsManagement.GrpcServerUseTLS = true + cfg.SecretsManagement.GrpcServerTLSServerName = "localhost" + cfg.SecretsManagement.GrpcServerTLSSkipVerify = false grpcClientAuth := cfg.Raw.Section("grpc_client_authentication") _, err = grpcClientAuth.NewKey("token", "test-token") @@ -220,15 +207,15 @@ func TestDecryptService(t *testing.T) { require.NoError(t, err) apiServer := cfg.Raw.Section("grafana-apiserver") - _, err = apiServer.NewKey("proxy_client_cert_file", certPaths.clientCert) + _, err = apiServer.NewKey("proxy_client_cert_file", certPaths.ClientCert) require.NoError(t, err) - _, err = apiServer.NewKey("proxy_client_key_file", certPaths.clientKey) + _, err = apiServer.NewKey("proxy_client_key_file", certPaths.ClientKey) require.NoError(t, err) - _, err = apiServer.NewKey("apiservice_ca_bundle_file", certPaths.ca) + _, err = apiServer.NewKey("apiservice_ca_bundle_file", certPaths.CA) require.NoError(t, err) // Create and test decryption, using the mock grpc server as we dont test the business logic here - decryptService, err := ProvideDecryptService(cfg, tracer, nil) + decryptService, err := decrypt.ProvideDecryptService(cfg, tracer, nil) require.NoError(t, err) require.NotNil(t, decryptService) @@ -271,40 +258,3 @@ func (m *mockDecryptServer) DecryptSecureValues(ctx context.Context, req *decryp args := m.Called(ctx, req) return args.Get(0).(*decryptv1beta1.SecureValueDecryptResponseCollection), args.Error(1) } - -type certPaths struct { - clientCert string - clientKey string - serverCert string - serverKey string - ca string -} - -func createX509TestDir(t *testing.T) certPaths { - t.Helper() - - tmpDir := t.TempDir() - - ca := testcerts.NewCA() - caCertFile, _, err := ca.ToTempFile(tmpDir) - require.NoError(t, err) - - serverKp, err := ca.NewKeyPair("localhost") - require.NoError(t, err) - - serverCertFile, serverKeyFile, err := serverKp.ToTempFile(tmpDir) - require.NoError(t, err) - - clientKp, err := ca.NewKeyPair() - require.NoError(t, err) - clientCertFile, clientKeyFile, err := clientKp.ToTempFile(tmpDir) - require.NoError(t, err) - - return certPaths{ - clientCert: clientCertFile.Name(), - clientKey: clientKeyFile.Name(), - serverCert: serverCertFile.Name(), - serverKey: serverKeyFile.Name(), - ca: caCertFile.Name(), - } -} diff --git a/pkg/registry/apis/secret/inline.go b/pkg/registry/apis/secret/inline.go new file mode 100644 index 00000000000..1bb6edfff05 --- /dev/null +++ b/pkg/registry/apis/secret/inline.go @@ -0,0 +1,8 @@ +package secret + +import "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + +// InlineSecureValueSupport allows resources to manage secrets inline +// +//go:generate mockery --name InlineSecureValueSupport --structname MockInlineSecureValueSupport --inpackage --filename inline_mock.go --with-expecter +type InlineSecureValueSupport = contracts.InlineSecureValueSupport diff --git a/pkg/registry/apis/secret/inline/grpc_client.go b/pkg/registry/apis/secret/inline/grpc_client.go new file mode 100644 index 00000000000..8ccfbb7dad3 --- /dev/null +++ b/pkg/registry/apis/secret/inline/grpc_client.go @@ -0,0 +1,200 @@ +package inline + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "os" + + "github.com/fullstorydev/grpchan" + authnlib "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/types" + "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + + inlinev1beta1 "github.com/grafana/grafana/apps/secret/inline/v1beta1" + secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" + "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" +) + +type GRPCInlineClient struct { + conn *grpc.ClientConn + tracer trace.Tracer + tokenExchanger authnlib.TokenExchanger +} + +var _ contracts.InlineSecureValueSupport = &GRPCInlineClient{} + +type TLSConfig struct { + UseTLS bool + CertFile string + KeyFile string + CAFile string + ServerName string + InsecureSkipVerify bool +} + +func NewGRPCInlineClient(tokenExchanger authnlib.TokenExchanger, tracer trace.Tracer, address string, tlsConfig TLSConfig) (*GRPCInlineClient, error) { + var opts []grpc.DialOption + if tlsConfig.UseTLS { + creds, err := createTLSCredentials(tlsConfig) + if err != nil { + return nil, fmt.Errorf("failed to setup TLS: %w", err) + } + + opts = append(opts, grpc.WithTransportCredentials(creds)) + } else { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } + + conn, err := grpc.NewClient(address, opts...) + if err != nil { + return nil, fmt.Errorf("failed to connect to grpc server at %s: %w", address, err) + } + + return &GRPCInlineClient{ + conn: conn, + tracer: tracer, + tokenExchanger: tokenExchanger, + }, nil +} + +func createTLSCredentials(config TLSConfig) (credentials.TransportCredentials, error) { + tlsConfig := &tls.Config{} + + if config.CAFile != "" { + caCert, err := os.ReadFile(config.CAFile) + if err != nil { + return nil, fmt.Errorf("failed to read CA: %w", err) + } + + caCertPool := x509.NewCertPool() + if !caCertPool.AppendCertsFromPEM(caCert) { + return nil, fmt.Errorf("failed to append CA") + } + tlsConfig.RootCAs = caCertPool + } + + if config.CertFile != "" && config.KeyFile != "" { + cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile) + if err != nil { + return nil, fmt.Errorf("failed to load client certificate: %w", err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + + if config.ServerName != "" { + tlsConfig.ServerName = config.ServerName + } + + if config.InsecureSkipVerify { + tlsConfig.InsecureSkipVerify = true + } + + return credentials.NewTLS(tlsConfig), nil +} + +// Close will close the underlying gRPC connection. After it is closed, the client cannot be used anymore. +func (g *GRPCInlineClient) Close() error { + if g.conn != nil { + return g.conn.Close() + } + return nil +} + +func (g *GRPCInlineClient) CanReference(ctx context.Context, owner v0alpha1.ObjectReference, names ...string) error { + client, err := g.getClient(owner.Namespace) + if err != nil { + return err + } + + req := &inlinev1beta1.CanReferenceRequest{ + Owner: &inlinev1beta1.ObjectReference{ + ApiGroup: owner.APIGroup, + ApiVersion: owner.APIVersion, + Kind: owner.Kind, + Namespace: owner.Namespace, + Name: owner.Name, + }, + Names: names, + } + + _, err = client.CanReference(ctx, req) + return err +} + +func (g *GRPCInlineClient) CreateInline(ctx context.Context, owner v0alpha1.ObjectReference, value v0alpha1.RawSecureValue) (string, error) { + client, err := g.getClient(owner.Namespace) + if err != nil { + return "", err + } + + if value.IsZero() { + return "", fmt.Errorf("empty value provided for CreateInline") + } + + req := &inlinev1beta1.CreateInlineRequest{ + Owner: &inlinev1beta1.ObjectReference{ + ApiGroup: owner.APIGroup, + ApiVersion: owner.APIVersion, + Kind: owner.Kind, + Namespace: owner.Namespace, + Name: owner.Name, + }, + Value: value.DangerouslyExposeAndConsumeValue(), + } + + resp, err := client.CreateInline(ctx, req) + if err != nil { + return "", err + } + + return resp.GetName(), nil +} + +func (g *GRPCInlineClient) DeleteWhenOwnedByResource(ctx context.Context, owner v0alpha1.ObjectReference, name string) error { + client, err := g.getClient(owner.Namespace) + if err != nil { + return err + } + + req := &inlinev1beta1.DeleteWhenOwnedByResourceRequest{ + Owner: &inlinev1beta1.ObjectReference{ + ApiGroup: owner.APIGroup, + ApiVersion: owner.APIVersion, + Kind: owner.Kind, + Namespace: owner.Namespace, + Name: owner.Name, + }, + Name: name, + } + + _, err = client.DeleteWhenOwnedByResource(ctx, req) + return err +} + +func (g *GRPCInlineClient) getClient(namespace string) (inlinev1beta1.InlineSecureValueServiceClient, error) { + _, err := types.ParseNamespace(namespace) + if err != nil { + return nil, err + } + + tokenExchangerInterceptor := authnlib.NewGrpcClientInterceptor( + g.tokenExchanger, + authnlib.WithClientInterceptorTracer(g.tracer), + authnlib.WithClientInterceptorNamespace(namespace), + authnlib.WithClientInterceptorAudience([]string{secretv1beta1.APIGroup}), + ) + + clientConn := grpchan.InterceptClientConn( + g.conn, + tokenExchangerInterceptor.UnaryClientInterceptor, + tokenExchangerInterceptor.StreamClientInterceptor, + ) + + return inlinev1beta1.NewInlineSecureValueServiceClient(clientConn), nil +} diff --git a/pkg/registry/apis/secret/service/inline_secure_value.go b/pkg/registry/apis/secret/inline/inline_secure_value.go similarity index 91% rename from pkg/registry/apis/secret/service/inline_secure_value.go rename to pkg/registry/apis/secret/inline/inline_secure_value.go index a02b9c5bf97..65aae4efe9f 100644 --- a/pkg/registry/apis/secret/service/inline_secure_value.go +++ b/pkg/registry/apis/secret/inline/inline_secure_value.go @@ -1,4 +1,4 @@ -package service +package inline import ( "context" @@ -20,25 +20,25 @@ import ( "github.com/grafana/grafana/pkg/util" ) -type inlineSecureValueService struct { +type LocalInlineSecureValueService struct { tracer trace.Tracer secureValueService contracts.SecureValueService accessChecker authlib.AccessChecker } -func ProvideInlineSecureValueService( +func NewLocalInlineSecureValueService( tracer trace.Tracer, secureValueService contracts.SecureValueService, accessClient authlib.AccessClient, ) contracts.InlineSecureValueSupport { - return &inlineSecureValueService{ + return &LocalInlineSecureValueService{ tracer: tracer, secureValueService: secureValueService, accessChecker: accessClient, } } -func (s *inlineSecureValueService) CanReference(ctx context.Context, owner common.ObjectReference, names ...string) error { +func (s *LocalInlineSecureValueService) CanReference(ctx context.Context, owner common.ObjectReference, names ...string) error { ctx, span := s.tracer.Start(ctx, "InlineSecureValueService.CanReference", trace.WithAttributes( attribute.String("owner.namespace", owner.Namespace), attribute.String("owner.apiGroup", owner.APIGroup), @@ -86,7 +86,7 @@ func (s *inlineSecureValueService) CanReference(ctx context.Context, owner commo return nil } -func (s *inlineSecureValueService) isSecureValueOwnedByResource(ctx context.Context, owner common.ObjectReference, name string) (bool, error) { +func (s *LocalInlineSecureValueService) isSecureValueOwnedByResource(ctx context.Context, owner common.ObjectReference, name string) (bool, error) { sv, err := s.secureValueService.Read(ctx, xkube.Namespace(owner.Namespace), name) if err != nil { if errors.Is(err, contracts.ErrSecureValueNotFound) { @@ -127,7 +127,7 @@ func (s *inlineSecureValueService) isSecureValueOwnedByResource(ctx context.Cont return false, nil } -func (s *inlineSecureValueService) canIdentityReadSecureValue(ctx context.Context, namespace xkube.Namespace, name string) error { +func (s *LocalInlineSecureValueService) canIdentityReadSecureValue(ctx context.Context, namespace xkube.Namespace, name string) error { authInfo, ok := authlib.AuthInfoFrom(ctx) if !ok { return fmt.Errorf("missing auth info in context") @@ -156,7 +156,7 @@ func (s *inlineSecureValueService) canIdentityReadSecureValue(ctx context.Contex return nil } -func (s *inlineSecureValueService) CreateInline(ctx context.Context, owner common.ObjectReference, value common.RawSecureValue) (string, error) { +func (s *LocalInlineSecureValueService) CreateInline(ctx context.Context, owner common.ObjectReference, value common.RawSecureValue) (string, error) { ctx, span := s.tracer.Start(ctx, "InlineSecureValueService.CreateInline", trace.WithAttributes( attribute.String("owner.namespace", owner.Namespace), attribute.String("owner.apiGroup", owner.APIGroup), @@ -219,7 +219,7 @@ func (s *inlineSecureValueService) CreateInline(ctx context.Context, owner commo return createdSv.GetName(), nil } -func (s *inlineSecureValueService) DeleteWhenOwnedByResource(ctx context.Context, owner common.ObjectReference, name string) error { +func (s *LocalInlineSecureValueService) DeleteWhenOwnedByResource(ctx context.Context, owner common.ObjectReference, name string) error { ctx, span := s.tracer.Start(ctx, "InlineSecureValueService.DeleteWhenOwnedByResource", trace.WithAttributes( attribute.String("owner.namespace", owner.Namespace), attribute.String("owner.apiGroup", owner.APIGroup), diff --git a/pkg/registry/apis/secret/service/inline_secure_value_test.go b/pkg/registry/apis/secret/inline/inline_secure_value_test.go similarity index 89% rename from pkg/registry/apis/secret/service/inline_secure_value_test.go rename to pkg/registry/apis/secret/inline/inline_secure_value_test.go index e20885b0cc3..f95a9280985 100644 --- a/pkg/registry/apis/secret/service/inline_secure_value_test.go +++ b/pkg/registry/apis/secret/inline/inline_secure_value_test.go @@ -1,4 +1,4 @@ -package service_test +package inline_test import ( "testing" @@ -6,7 +6,7 @@ import ( common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/grafana/grafana/pkg/registry/apis/secret/service" + "github.com/grafana/grafana/pkg/registry/apis/secret/inline" "github.com/grafana/grafana/pkg/registry/apis/secret/testutils" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/stretchr/testify/require" @@ -54,7 +54,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) { "securevalues:read": {"securevalues:uid:" + sv2}, }) - svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, tu.AccessClient) + svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, tu.AccessClient) err = svc.CanReference(ctx, owner, sv1, sv2) require.NoError(t, err) @@ -63,7 +63,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) { t.Run("when the auth info is missing it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) err := svc.CanReference(t.Context(), common.ObjectReference{}) require.Error(t, err) }) @@ -71,7 +71,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) { t.Run("when the owner namespace does not match auth info namespace it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) reqNs := "org-2345" ctx := testutils.CreateUserAuthContext(t.Context(), reqNs, map[string][]string{}) @@ -83,7 +83,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) { t.Run("when the owner namespace is empty it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{}) @@ -94,7 +94,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) { t.Run("when the owner reference has empty fields it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) owner := common.ObjectReference{ Namespace: defaultNs, @@ -125,7 +125,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) { t.Run("when no secure values are provided it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{}) @@ -137,7 +137,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) { t.Parallel() tu := testutils.Setup(t) - svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil) ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{}) @@ -169,7 +169,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) { ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{}) - svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil) err = svc.CanReference(ctx, owner, sv1) require.Error(t, err) @@ -189,7 +189,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) { ctx := identity.WithServiceIdentityContext(t.Context(), 1234) - svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil) err = svc.CanReference(ctx, owner, sv1) require.Error(t, err) @@ -207,7 +207,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) { }) require.NoError(t, err) - svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, tu.AccessClient) + svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, tu.AccessClient) ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{ "securevalues:read": {"securevalues:uid:another-sv"}, // can read, but another resource! @@ -248,7 +248,7 @@ func TestIntegration_InlineSecureValue_CreateInline(t *testing.T) { createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), serviceIdentity, owner.Namespace, nil, nil) - svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil) createdName, err := svc.CreateInline(createAuthCtx, owner, secret) require.NoError(t, err) @@ -265,7 +265,7 @@ func TestIntegration_InlineSecureValue_CreateInline(t *testing.T) { t.Run("when the auth info is missing it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) _, err := svc.CreateInline(t.Context(), common.ObjectReference{}, "") require.Error(t, err) }) @@ -273,7 +273,7 @@ func TestIntegration_InlineSecureValue_CreateInline(t *testing.T) { t.Run("when the request identity is not a user nor a service account, it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) createAuthCtx := testutils.CreateServiceAuthContext(t.Context(), "service-identity", defaultNs, nil) @@ -284,7 +284,7 @@ func TestIntegration_InlineSecureValue_CreateInline(t *testing.T) { t.Run("when the owner namespace does not match auth info namespace it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) reqNs := "org-2345" createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", reqNs, nil, nil) @@ -296,7 +296,7 @@ func TestIntegration_InlineSecureValue_CreateInline(t *testing.T) { t.Run("when the owner namespace is empty it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", defaultNs, nil, nil) @@ -307,7 +307,7 @@ func TestIntegration_InlineSecureValue_CreateInline(t *testing.T) { t.Run("when the owner reference has empty fields it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) owner := common.ObjectReference{ Namespace: defaultNs, @@ -339,7 +339,7 @@ func TestIntegration_InlineSecureValue_CreateInline(t *testing.T) { t.Run("when an empty secret is provided it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", defaultNs, nil, nil) @@ -376,7 +376,7 @@ func TestIntegration_InlineSecureValue_DeleteWhenOwnedByResource(t *testing.T) { require.NoError(t, err) require.NotNil(t, createdSv1) - svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil) ctx := testutils.CreateServiceAuthContext(t.Context(), "", defaultNs, nil) @@ -392,7 +392,7 @@ func TestIntegration_InlineSecureValue_DeleteWhenOwnedByResource(t *testing.T) { t.Run("when the auth info is missing it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) err := svc.DeleteWhenOwnedByResource(t.Context(), common.ObjectReference{}, "") require.Error(t, err) }) @@ -400,7 +400,7 @@ func TestIntegration_InlineSecureValue_DeleteWhenOwnedByResource(t *testing.T) { t.Run("when the owner namespace does not match auth info namespace it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) reqNs := "org-2345" ctx := testutils.CreateUserAuthContext(t.Context(), reqNs, map[string][]string{}) @@ -412,7 +412,7 @@ func TestIntegration_InlineSecureValue_DeleteWhenOwnedByResource(t *testing.T) { t.Run("when the owner namespace is empty it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{}) @@ -423,7 +423,7 @@ func TestIntegration_InlineSecureValue_DeleteWhenOwnedByResource(t *testing.T) { t.Run("when the owner reference has empty fields it returns an error", func(t *testing.T) { t.Parallel() - svc := service.ProvideInlineSecureValueService(tracer, nil, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, nil, nil) owner := common.ObjectReference{ Namespace: defaultNs, @@ -467,7 +467,7 @@ func TestIntegration_InlineSecureValue_DeleteWhenOwnedByResource(t *testing.T) { require.NoError(t, err) require.NotNil(t, createdSv1) - svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil) ctx := testutils.CreateServiceAuthContext(t.Context(), "", defaultNs, nil) @@ -494,7 +494,7 @@ func TestIntegration_InlineSecureValue_DeleteWhenOwnedByResource(t *testing.T) { require.NoError(t, err) require.NotNil(t, createdSv1) - svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil) + svc := inline.NewLocalInlineSecureValueService(tracer, tu.SecureValueService, nil) ctx := testutils.CreateServiceAuthContext(t.Context(), "", defaultNs, nil) diff --git a/pkg/registry/apis/secret/inline/service.go b/pkg/registry/apis/secret/inline/service.go new file mode 100644 index 00000000000..33fbc50736e --- /dev/null +++ b/pkg/registry/apis/secret/inline/service.go @@ -0,0 +1,71 @@ +package inline + +import ( + "fmt" + + authnlib "github.com/grafana/authlib/authn" + authlib "github.com/grafana/authlib/types" + "go.opentelemetry.io/otel/trace" + + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/services/authn/grpcutils" + "github.com/grafana/grafana/pkg/setting" +) + +func ProvideInlineSecureValueService( + cfg *setting.Cfg, + tracer trace.Tracer, + secureValueService contracts.SecureValueService, + 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 NewLocalInlineSecureValueService(tracer, secureValueService, accessClient), nil +} + +func readTLSFromConfig(cfg *setting.Cfg) TLSConfig { + if !cfg.SecretsManagement.GrpcServerUseTLS { + return TLSConfig{ + UseTLS: false, + InsecureSkipVerify: true, + } + } + + apiServer := cfg.SectionWithEnvOverrides("grafana-apiserver") + + return TLSConfig{ + UseTLS: true, + CertFile: apiServer.Key("proxy_client_cert_file").MustString(""), + KeyFile: apiServer.Key("proxy_client_key_file").MustString(""), + CAFile: apiServer.Key("apiservice_ca_bundle_file").MustString(""), + ServerName: cfg.SecretsManagement.GrpcServerTLSServerName, + InsecureSkipVerify: cfg.SecretsManagement.GrpcServerTLSSkipVerify, + } +} diff --git a/pkg/registry/apis/secret/inline/service_test.go b/pkg/registry/apis/secret/inline/service_test.go new file mode 100644 index 00000000000..9fc7679c2ad --- /dev/null +++ b/pkg/registry/apis/secret/inline/service_test.go @@ -0,0 +1,190 @@ +package inline_test + +import ( + "context" + "crypto/tls" + "net/http" + "net/http/httptest" + "strings" + "testing" + + inlinev1beta1 "github.com/grafana/grafana/apps/secret/inline/v1beta1" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" + "golang.org/x/net/nettest" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" + + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/secret/inline" + "github.com/grafana/grafana/pkg/registry/apis/secret/testutils" + "github.com/grafana/grafana/pkg/services/authn/clients" + "github.com/grafana/grafana/pkg/setting" +) + +func TestProvideInlineSecureValueService(t *testing.T) { + t.Parallel() + + tracer := noop.NewTracerProvider().Tracer("test") + + t.Run("when the grpc server is disabled, it returns local inline service", func(t *testing.T) { + t.Parallel() + + cfg := setting.NewCfg() + cfg.SecretsManagement.GrpcClientEnable = false + + service, err := inline.ProvideInlineSecureValueService(cfg, nil, nil, nil) + require.NoError(t, err) + require.IsType(t, &inline.LocalInlineSecureValueService{}, service) + }) + + t.Run("when grpc server is enabled but server address is missing, it returns an error", func(t *testing.T) { + t.Parallel() + + cfg := setting.NewCfg() + cfg.SecretsManagement.GrpcClientEnable = true + + service, err := inline.ProvideInlineSecureValueService(cfg, nil, nil, nil) + require.Error(t, err) + require.Nil(t, service) + }) + + t.Run("when grpc server is enabled but token exchange config is missing, it returns an error", func(t *testing.T) { + t.Parallel() + + cfg := setting.NewCfg() + cfg.SecretsManagement.GrpcClientEnable = true + cfg.SecretsManagement.GrpcServerAddress = "127.0.0.1:10000" + + service, err := inline.ProvideInlineSecureValueService(cfg, nil, nil, nil) + require.Error(t, err) + require.Nil(t, service) + }) + + t.Run("happy path with grpc+tls server with fake token exchanger and server", func(t *testing.T) { + t.Parallel() + + respTokenExchanged := "test-token" + tokenExchangeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response := `{ + "data": { + "token": "` + respTokenExchanged + `" + } + }` + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(response)) + })) + t.Cleanup(tokenExchangeServer.Close) + + // Set up gRPC Server with TLS + listener, err := nettest.NewLocalListener("tcp") + require.NoError(t, err) + + certPaths := testutils.CreateX509TestDir(t) + + serverCert, err := tls.LoadX509KeyPair(certPaths.ServerCert, certPaths.ServerKey) + require.NoError(t, err) + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{serverCert}, + ClientAuth: tls.NoClientCert, + InsecureSkipVerify: false, + ServerName: "localhost", + } + + grpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig))) + t.Cleanup(grpcServer.Stop) + + inlineServer := &mockInlineServer{} + inlineServer.On("CreateInline", mock.Anything, mock.Anything).Return(&inlinev1beta1.CreateInlineResponse{Name: "test-value"}, nil) + inlineServer.On("CanReference", mock.Anything, mock.Anything).Return(&inlinev1beta1.CanReferenceResponse{}, nil) + inlineServer.On("DeleteWhenOwnedByResource", mock.Anything, mock.Anything).Return(&inlinev1beta1.DeleteWhenOwnedByResourceResponse{}, nil) + + inlinev1beta1.RegisterInlineSecureValueServiceServer(grpcServer, inlineServer) + + go func() { + _ = grpcServer.Serve(listener) + <-t.Context().Done() + }() + + // Populate configuration with gRPC+TLS options and mock token exchanger + namespace := "stacks-1234" + + cfg := setting.NewCfg() + cfg.SecretsManagement.GrpcClientEnable = true + cfg.SecretsManagement.GrpcServerAddress = listener.Addr().String() + cfg.SecretsManagement.GrpcServerUseTLS = true + cfg.SecretsManagement.GrpcServerTLSServerName = "localhost" + cfg.SecretsManagement.GrpcServerTLSSkipVerify = false + + grpcClientAuth := cfg.Raw.Section("grpc_client_authentication") + _, err = grpcClientAuth.NewKey("token", "test-token") + require.NoError(t, err) + _, err = grpcClientAuth.NewKey("token_exchange_url", tokenExchangeServer.URL) + require.NoError(t, err) + _, err = grpcClientAuth.NewKey("token_namespace", namespace) + require.NoError(t, err) + + apiServer := cfg.Raw.Section("grafana-apiserver") + _, err = apiServer.NewKey("proxy_client_cert_file", certPaths.ClientCert) + require.NoError(t, err) + _, err = apiServer.NewKey("proxy_client_key_file", certPaths.ClientKey) + require.NoError(t, err) + _, err = apiServer.NewKey("apiservice_ca_bundle_file", certPaths.CA) + require.NoError(t, err) + + inlineService, err := inline.ProvideInlineSecureValueService(cfg, tracer, nil, nil) + require.NoError(t, err) + require.IsType(t, &inline.GRPCInlineClient{}, inlineService) + + owner := common.ObjectReference{ + APIGroup: "example.com", + APIVersion: "v1", + Kind: "TestResource", + Namespace: namespace, + Name: "test-resource", + } + + name, err := inlineService.CreateInline(t.Context(), owner, common.NewSecretValue("test-value")) + require.NoError(t, err) + require.Equal(t, "test-value", name) + + err = inlineService.CanReference(t.Context(), owner, "test-value") + require.NoError(t, err) + + err = inlineService.DeleteWhenOwnedByResource(t.Context(), owner, "test-value") + require.NoError(t, err) + + mock.AssertExpectationsForObjects(t, inlineServer) + + requestContext := inlineServer.Calls[0].Arguments[0].(context.Context) + + md, ok := metadata.FromIncomingContext(requestContext) + require.True(t, ok) + require.NotEmpty(t, md) + require.Equal(t, respTokenExchanged, md[strings.ToLower(clients.ExtJWTAuthenticationHeaderName)][0]) + }) +} + +type mockInlineServer struct { + mock.Mock +} + +var _ inlinev1beta1.InlineSecureValueServiceServer = (*mockInlineServer)(nil) + +func (m *mockInlineServer) CanReference(ctx context.Context, req *inlinev1beta1.CanReferenceRequest) (*inlinev1beta1.CanReferenceResponse, error) { + args := m.Called(ctx, req) + return args.Get(0).(*inlinev1beta1.CanReferenceResponse), args.Error(1) +} + +func (m *mockInlineServer) CreateInline(ctx context.Context, req *inlinev1beta1.CreateInlineRequest) (*inlinev1beta1.CreateInlineResponse, error) { + args := m.Called(ctx, req) + return args.Get(0).(*inlinev1beta1.CreateInlineResponse), args.Error(1) +} + +func (m *mockInlineServer) DeleteWhenOwnedByResource(ctx context.Context, req *inlinev1beta1.DeleteWhenOwnedByResourceRequest) (*inlinev1beta1.DeleteWhenOwnedByResourceResponse, error) { + args := m.Called(ctx, req) + return args.Get(0).(*inlinev1beta1.DeleteWhenOwnedByResourceResponse), args.Error(1) +} diff --git a/pkg/registry/apis/secret/inline_mock.go b/pkg/registry/apis/secret/inline_mock.go new file mode 100644 index 00000000000..468aaa594d2 --- /dev/null +++ b/pkg/registry/apis/secret/inline_mock.go @@ -0,0 +1,206 @@ +// Code generated by mockery v2.52.4. DO NOT EDIT. + +package secret + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + v0alpha1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" +) + +// MockInlineSecureValueSupport is an autogenerated mock type for the InlineSecureValueSupport type +type MockInlineSecureValueSupport struct { + mock.Mock +} + +type MockInlineSecureValueSupport_Expecter struct { + mock *mock.Mock +} + +func (_m *MockInlineSecureValueSupport) EXPECT() *MockInlineSecureValueSupport_Expecter { + return &MockInlineSecureValueSupport_Expecter{mock: &_m.Mock} +} + +// CanReference provides a mock function with given fields: ctx, owner, names +func (_m *MockInlineSecureValueSupport) CanReference(ctx context.Context, owner v0alpha1.ObjectReference, names ...string) error { + _va := make([]interface{}, len(names)) + for _i := range names { + _va[_i] = names[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, owner) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for CanReference") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, v0alpha1.ObjectReference, ...string) error); ok { + r0 = rf(ctx, owner, names...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockInlineSecureValueSupport_CanReference_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CanReference' +type MockInlineSecureValueSupport_CanReference_Call struct { + *mock.Call +} + +// CanReference is a helper method to define mock.On call +// - ctx context.Context +// - owner v0alpha1.ObjectReference +// - names ...string +func (_e *MockInlineSecureValueSupport_Expecter) CanReference(ctx interface{}, owner interface{}, names ...interface{}) *MockInlineSecureValueSupport_CanReference_Call { + return &MockInlineSecureValueSupport_CanReference_Call{Call: _e.mock.On("CanReference", + append([]interface{}{ctx, owner}, names...)...)} +} + +func (_c *MockInlineSecureValueSupport_CanReference_Call) Run(run func(ctx context.Context, owner v0alpha1.ObjectReference, names ...string)) *MockInlineSecureValueSupport_CanReference_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]string, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(string) + } + } + run(args[0].(context.Context), args[1].(v0alpha1.ObjectReference), variadicArgs...) + }) + return _c +} + +func (_c *MockInlineSecureValueSupport_CanReference_Call) Return(_a0 error) *MockInlineSecureValueSupport_CanReference_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockInlineSecureValueSupport_CanReference_Call) RunAndReturn(run func(context.Context, v0alpha1.ObjectReference, ...string) error) *MockInlineSecureValueSupport_CanReference_Call { + _c.Call.Return(run) + return _c +} + +// CreateInline provides a mock function with given fields: ctx, owner, value +func (_m *MockInlineSecureValueSupport) CreateInline(ctx context.Context, owner v0alpha1.ObjectReference, value v0alpha1.RawSecureValue) (string, error) { + ret := _m.Called(ctx, owner, value) + + if len(ret) == 0 { + panic("no return value specified for CreateInline") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, v0alpha1.ObjectReference, v0alpha1.RawSecureValue) (string, error)); ok { + return rf(ctx, owner, value) + } + if rf, ok := ret.Get(0).(func(context.Context, v0alpha1.ObjectReference, v0alpha1.RawSecureValue) string); ok { + r0 = rf(ctx, owner, value) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, v0alpha1.ObjectReference, v0alpha1.RawSecureValue) error); ok { + r1 = rf(ctx, owner, value) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockInlineSecureValueSupport_CreateInline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateInline' +type MockInlineSecureValueSupport_CreateInline_Call struct { + *mock.Call +} + +// CreateInline is a helper method to define mock.On call +// - ctx context.Context +// - owner v0alpha1.ObjectReference +// - value v0alpha1.RawSecureValue +func (_e *MockInlineSecureValueSupport_Expecter) CreateInline(ctx interface{}, owner interface{}, value interface{}) *MockInlineSecureValueSupport_CreateInline_Call { + return &MockInlineSecureValueSupport_CreateInline_Call{Call: _e.mock.On("CreateInline", ctx, owner, value)} +} + +func (_c *MockInlineSecureValueSupport_CreateInline_Call) Run(run func(ctx context.Context, owner v0alpha1.ObjectReference, value v0alpha1.RawSecureValue)) *MockInlineSecureValueSupport_CreateInline_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(v0alpha1.ObjectReference), args[2].(v0alpha1.RawSecureValue)) + }) + return _c +} + +func (_c *MockInlineSecureValueSupport_CreateInline_Call) Return(_a0 string, _a1 error) *MockInlineSecureValueSupport_CreateInline_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockInlineSecureValueSupport_CreateInline_Call) RunAndReturn(run func(context.Context, v0alpha1.ObjectReference, v0alpha1.RawSecureValue) (string, error)) *MockInlineSecureValueSupport_CreateInline_Call { + _c.Call.Return(run) + return _c +} + +// DeleteWhenOwnedByResource provides a mock function with given fields: ctx, owner, name +func (_m *MockInlineSecureValueSupport) DeleteWhenOwnedByResource(ctx context.Context, owner v0alpha1.ObjectReference, name string) error { + ret := _m.Called(ctx, owner, name) + + if len(ret) == 0 { + panic("no return value specified for DeleteWhenOwnedByResource") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, v0alpha1.ObjectReference, string) error); ok { + r0 = rf(ctx, owner, name) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockInlineSecureValueSupport_DeleteWhenOwnedByResource_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteWhenOwnedByResource' +type MockInlineSecureValueSupport_DeleteWhenOwnedByResource_Call struct { + *mock.Call +} + +// DeleteWhenOwnedByResource is a helper method to define mock.On call +// - ctx context.Context +// - owner v0alpha1.ObjectReference +// - name string +func (_e *MockInlineSecureValueSupport_Expecter) DeleteWhenOwnedByResource(ctx interface{}, owner interface{}, name interface{}) *MockInlineSecureValueSupport_DeleteWhenOwnedByResource_Call { + return &MockInlineSecureValueSupport_DeleteWhenOwnedByResource_Call{Call: _e.mock.On("DeleteWhenOwnedByResource", ctx, owner, name)} +} + +func (_c *MockInlineSecureValueSupport_DeleteWhenOwnedByResource_Call) Run(run func(ctx context.Context, owner v0alpha1.ObjectReference, name string)) *MockInlineSecureValueSupport_DeleteWhenOwnedByResource_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(v0alpha1.ObjectReference), args[2].(string)) + }) + return _c +} + +func (_c *MockInlineSecureValueSupport_DeleteWhenOwnedByResource_Call) Return(_a0 error) *MockInlineSecureValueSupport_DeleteWhenOwnedByResource_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockInlineSecureValueSupport_DeleteWhenOwnedByResource_Call) RunAndReturn(run func(context.Context, v0alpha1.ObjectReference, string) error) *MockInlineSecureValueSupport_DeleteWhenOwnedByResource_Call { + _c.Call.Return(run) + return _c +} + +// NewMockInlineSecureValueSupport creates a new instance of MockInlineSecureValueSupport. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockInlineSecureValueSupport(t interface { + mock.TestingT + Cleanup(func()) +}) *MockInlineSecureValueSupport { + mock := &MockInlineSecureValueSupport{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/registry/apis/secret/testutils/testutils.go b/pkg/registry/apis/secret/testutils/testutils.go index 4c000e76908..6ade8366c89 100644 --- a/pkg/registry/apis/secret/testutils/testutils.go +++ b/pkg/registry/apis/secret/testutils/testutils.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/authlib/authn" "github.com/grafana/authlib/types" + "github.com/madflojo/testcerts" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace/noop" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -80,7 +81,6 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { defaultKey := "SdlklWklckeLS" cfg := setting.NewCfg() cfg.SecretsManagement = setting.SecretsManagerSettings{ - DecryptServerType: "local", CurrentEncryptionProvider: "secret_key.v1", ConfiguredKMSProviders: map[string]map[string]string{"secret_key.v1": {"secret_key": defaultKey}}, } @@ -131,9 +131,6 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { require.NoError(t, err) testCfg := setting.NewCfg() - testCfg.SecretsManagement = setting.SecretsManagerSettings{ - DecryptServerType: "local", - } decryptService, err := decrypt.ProvideDecryptService(testCfg, tracer, decryptStorage) require.NoError(t, err) @@ -287,3 +284,40 @@ func CreateOBOAuthContext( return types.WithAuthInfo(ctx, requester) } + +type TestCertPaths struct { + ClientCert string + ClientKey string + ServerCert string + ServerKey string + CA string +} + +func CreateX509TestDir(t *testing.T) TestCertPaths { + t.Helper() + + tmpDir := t.TempDir() + + ca := testcerts.NewCA() + caCertFile, _, err := ca.ToTempFile(tmpDir) + require.NoError(t, err) + + serverKp, err := ca.NewKeyPair("localhost") + require.NoError(t, err) + + serverCertFile, serverKeyFile, err := serverKp.ToTempFile(tmpDir) + require.NoError(t, err) + + clientKp, err := ca.NewKeyPair() + require.NoError(t, err) + clientCertFile, clientKeyFile, err := clientKp.ToTempFile(tmpDir) + require.NoError(t, err) + + return TestCertPaths{ + ClientCert: clientCertFile.Name(), + ClientKey: clientKeyFile.Name(), + ServerCert: serverCertFile.Name(), + ServerKey: serverKeyFile.Name(), + CA: caCertFile.Name(), + } +} diff --git a/pkg/server/wire.go b/pkg/server/wire.go index a56c1bff1a6..e3198908997 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -44,6 +44,7 @@ import ( secretdecrypt "github.com/grafana/grafana/pkg/registry/apis/secret/decrypt" cipher "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service" encryptionManager "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager" + secretinline "github.com/grafana/grafana/pkg/registry/apis/secret/inline" secretsecurevalueservice "github.com/grafana/grafana/pkg/registry/apis/secret/service" secretvalidator "github.com/grafana/grafana/pkg/registry/apis/secret/validator" appregistry "github.com/grafana/grafana/pkg/registry/apps" @@ -428,6 +429,7 @@ var wireBasicSet = wire.NewSet( secretmetadata.ProvideDecryptStorage, secretdecrypt.ProvideDecryptAuthorizer, secretdecrypt.ProvideDecryptService, + secretinline.ProvideInlineSecureValueService, secretencryption.ProvideDataKeyStorage, secretencryption.ProvideGlobalDataKeyStorage, secretencryption.ProvideEncryptedValueStorage, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 5448bd29d20..30ce8174178 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -67,6 +67,7 @@ import ( service11 "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service" "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders" manager4 "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager" + "github.com/grafana/grafana/pkg/registry/apis/secret/inline" "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper" service12 "github.com/grafana/grafana/pkg/registry/apis/secret/service" validator3 "github.com/grafana/grafana/pkg/registry/apis/secret/validator" @@ -1581,7 +1582,7 @@ var withOTelSet = wire.NewSet( otelTracer, grpcserver.ProvideService, interceptors.ProvideAuthenticator, ) -var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service7.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service7.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets2.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets2.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service7.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service7.Service)), service7.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), mtdsclient.NewNullMTDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service5.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service5.DashboardServiceImpl)), service5.ProvideDashboardService, service5.ProvideDashboardProvisioningService, service5.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service6.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service6.Service)), service6.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptService, encryption.ProvideDataKeyStorage, encryption.ProvideGlobalDataKeyStorage, encryption.ProvideEncryptedValueStorage, encryption.ProvideGlobalEncryptedValueStorage, service12.ProvideSecureValueService, validator3.ProvideKeeperValidator, validator3.ProvideSecureValueValidator, migrator2.NewWithEngine, database5.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database5.Database)), manager4.ProvideEncryptionManager, service11.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) +var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service7.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service7.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets2.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets2.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service7.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service7.Service)), service7.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), mtdsclient.NewNullMTDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service5.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service5.DashboardServiceImpl)), service5.ProvideDashboardService, service5.ProvideDashboardProvisioningService, service5.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service6.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service6.Service)), service6.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptService, inline.ProvideInlineSecureValueService, encryption.ProvideDataKeyStorage, encryption.ProvideGlobalDataKeyStorage, encryption.ProvideEncryptedValueStorage, encryption.ProvideGlobalEncryptedValueStorage, service12.ProvideSecureValueService, validator3.ProvideKeeperValidator, validator3.ProvideSecureValueValidator, migrator2.NewWithEngine, database5.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database5.Database)), manager4.ProvideEncryptionManager, service11.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) var wireSet = wire.NewSet( wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)), diff --git a/pkg/setting/setting_secrets_manager.go b/pkg/setting/setting_secrets_manager.go index 043706dd182..ed112914f50 100644 --- a/pkg/setting/setting_secrets_manager.go +++ b/pkg/setting/setting_secrets_manager.go @@ -16,24 +16,24 @@ type SecretsManagerSettings struct { // In OSS, the provider type can only be "secret_key". In Enterprise, it can additionally be one of: "aws_kms", "azure_keyvault", "google_kms", "hashicorp_vault" ConfiguredKMSProviders map[string]map[string]string - DecryptServerType string // "local" or "grpc" - DecryptServerUseTLS bool // Applicable for decrypt_server_type=grpc. Whether to use TLS for the decrypt server - DecryptServerTLSSkipVerify bool // Applicable for decrypt_server_type=grpc. Whether to skip TLS verification for the decrypt server - DecryptServerTLSServerName string // Applicable for decrypt_server_type=grpc. Server name to use for TLS verification - DecryptServerAddress string // Applicable for decrypt_server_type=grpc. Address for external secrets server - DecryptGrafanaServiceName string // Service name to use for background grafana decryption + GrpcClientEnable bool // Whether to enable the gRPC client. If disabled, it will use the in-process services implementations. + GrpcServerUseTLS bool // Whether to use TLS when communicating with the gRPC server + GrpcServerTLSSkipVerify bool // Whether to skip TLS verification when communicating with the gRPC server + GrpcServerTLSServerName string // Server name to use for TLS verification + GrpcServerAddress string // Address for gRPC secrets server + GrpcGrafanaServiceName string // Service name to use for background grafana decryption/inline } func (cfg *Cfg) readSecretsManagerSettings() { secretsMgmt := cfg.Raw.Section("secrets_manager") cfg.SecretsManagement.CurrentEncryptionProvider = secretsMgmt.Key("encryption_provider").MustString(MisconfiguredProvider) - cfg.SecretsManagement.DecryptServerType = valueAsString(secretsMgmt, "decrypt_server_type", "local") - cfg.SecretsManagement.DecryptServerUseTLS = secretsMgmt.Key("decrypt_server_use_tls").MustBool(false) - cfg.SecretsManagement.DecryptServerTLSSkipVerify = secretsMgmt.Key("decrypt_server_tls_skip_verify").MustBool(false) - cfg.SecretsManagement.DecryptServerTLSServerName = valueAsString(secretsMgmt, "decrypt_server_tls_server_name", "") - cfg.SecretsManagement.DecryptServerAddress = valueAsString(secretsMgmt, "decrypt_server_address", "") - cfg.SecretsManagement.DecryptGrafanaServiceName = valueAsString(secretsMgmt, "decrypt_grafana_service_name", "") + cfg.SecretsManagement.GrpcClientEnable = secretsMgmt.Key("grpc_client_enable").MustBool(false) + cfg.SecretsManagement.GrpcServerUseTLS = secretsMgmt.Key("grpc_server_use_tls").MustBool(false) + cfg.SecretsManagement.GrpcServerTLSSkipVerify = secretsMgmt.Key("grpc_server_tls_skip_verify").MustBool(false) + cfg.SecretsManagement.GrpcServerTLSServerName = valueAsString(secretsMgmt, "grpc_server_tls_server_name", "") + cfg.SecretsManagement.GrpcServerAddress = valueAsString(secretsMgmt, "grpc_server_address", "") + cfg.SecretsManagement.GrpcGrafanaServiceName = valueAsString(secretsMgmt, "grpc_grafana_service_name", "") // Extract available KMS providers from configuration sections providers := make(map[string]map[string]string)