187 lines
6.4 KiB
Go
187 lines
6.4 KiB
Go
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("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)
|
|
}
|