Secrets: Provide grpc and local inline secure value client (#109179)
* TestUtils: Add helper to create certs * InlineSecureValue: Move to its own package rather than service * Settings: Rename decrypt server configs to grpc prefixed as it is more generic * InlineSecureValue: Add gRPC client * InlineSecureValue: Add service provider grpc/local depending on setting.Cfg * Wire: Add ProvideInlineSecureValueService to basic wireset * InlineSecureValue: Re-export in root secret pkg and generate mocks for interface * Settings: Unify decrypt+inline setting into generic grpc client
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user