diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4f70e46e2cc..7ce2a910e6d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -609,8 +609,6 @@ playwright.config.ts @grafana/plugins-platform-frontend /pkg/services/supportbundles/ @grafana/identity-access-team # Grafana Operator Experience Team -/pkg/infra/httpclient/httpclientprovider/sigv4_middleware.go @grafana/grafana-operator-experience-squad -/pkg/infra/httpclient/httpclientprovider/sigv4_middleware_test.go @grafana/grafana-operator-experience-squad /pkg/services/caching/ @grafana/grafana-operator-experience-squad /pkg/services/featuremgmt/ @grafana/grafana-operator-experience-squad /pkg/services/cloudmigration/ @grafana/grafana-operator-experience-squad diff --git a/go.mod b/go.mod index 7633203f3e3..31149a5745e 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20240306130925-bc622368256d // @grafana/alerting-squad-backend github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code - github.com/grafana/grafana-aws-sdk v0.24.0 // @grafana/aws-datasources + github.com/grafana/grafana-aws-sdk v0.25.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go v1.12.0 // @grafana/partner-datasources github.com/grafana/grafana-plugin-sdk-go v0.215.0 // @grafana/plugins-platform-backend github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // @grafana/backend-platform diff --git a/go.sum b/go.sum index 34b4b50c0a8..353117d54ff 100644 --- a/go.sum +++ b/go.sum @@ -2186,8 +2186,8 @@ github.com/grafana/dskit v0.0.0-20240104111617-ea101a3b86eb h1:AWE6+kvtE18HP+lRW github.com/grafana/dskit v0.0.0-20240104111617-ea101a3b86eb/go.mod h1:kkWM4WUV230bNG3urVRWPBnSJHs64y/0RmWjftnnn0c= github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 h1:jxJJ5z0GxqhWFbQUsys3BHG8jnmniJ2Q74tXAG1NaDo= github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447/go.mod h1:IxsY6mns6Q5sAnWcrptrgUrSglTZJXH/kXr9nbpb/9I= -github.com/grafana/grafana-aws-sdk v0.24.0 h1:0RKCJTeIkpEUvLCTjGOK1+jYZpaE2nJaGghGLvtUsFs= -github.com/grafana/grafana-aws-sdk v0.24.0/go.mod h1:3zghFF6edrxn0d6k6X9HpGZXDH+VfA+MwD2Pc/9X0ec= +github.com/grafana/grafana-aws-sdk v0.25.0 h1:XNi3iA/C/KPArmVbQfbwKQROaIotd38nCRjNE6P1UP0= +github.com/grafana/grafana-aws-sdk v0.25.0/go.mod h1:3zghFF6edrxn0d6k6X9HpGZXDH+VfA+MwD2Pc/9X0ec= github.com/grafana/grafana-azure-sdk-go v1.12.0 h1:q71M2QxMlBqRZOXc5mFAycJWuZqQ3hPTzVEo1r3CUTY= github.com/grafana/grafana-azure-sdk-go v1.12.0/go.mod h1:SAlwLdEuox4vw8ZaeQwnepYXnhznnQQdstJbcw8LH68= github.com/grafana/grafana-google-sdk-go v0.1.0 h1:LKGY8z2DSxKjYfr2flZsWgTRTZ6HGQbTqewE3JvRaNA= diff --git a/pkg/infra/httpclient/httpclientprovider/http_client_provider.go b/pkg/infra/httpclient/httpclientprovider/http_client_provider.go index f5c515a527e..5447999a1fd 100644 --- a/pkg/infra/httpclient/httpclientprovider/http_client_provider.go +++ b/pkg/infra/httpclient/httpclientprovider/http_client_provider.go @@ -4,6 +4,7 @@ import ( "net/http" "time" + awssdk "github.com/grafana/grafana-aws-sdk/pkg/sigv4" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/mwitkow/go-conntrack" @@ -32,7 +33,7 @@ func New(cfg *setting.Cfg, validator validations.PluginRequestValidator, tracer } if cfg.SigV4AuthEnabled { - middlewares = append(middlewares, SigV4Middleware(cfg.SigV4VerboseLogging)) + middlewares = append(middlewares, awssdk.SigV4Middleware(cfg.SigV4VerboseLogging)) } if httpLoggingEnabled(cfg.PluginSettings) { diff --git a/pkg/infra/httpclient/httpclientprovider/http_client_provider_test.go b/pkg/infra/httpclient/httpclientprovider/http_client_provider_test.go index dfbb09826c1..652372a0de4 100644 --- a/pkg/infra/httpclient/httpclientprovider/http_client_provider_test.go +++ b/pkg/infra/httpclient/httpclientprovider/http_client_provider_test.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/services/validations" + awssdk "github.com/grafana/grafana-aws-sdk/pkg/sigv4" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/setting" @@ -58,7 +59,7 @@ func TestHTTPClientProvider(t *testing.T) { require.Equal(t, sdkhttpclient.BasicAuthenticationMiddlewareName, o.Middlewares[4].(sdkhttpclient.MiddlewareName).MiddlewareName()) require.Equal(t, sdkhttpclient.CustomHeadersMiddlewareName, o.Middlewares[5].(sdkhttpclient.MiddlewareName).MiddlewareName()) require.Equal(t, sdkhttpclient.ResponseLimitMiddlewareName, o.Middlewares[6].(sdkhttpclient.MiddlewareName).MiddlewareName()) - require.Equal(t, SigV4MiddlewareName, o.Middlewares[8].(sdkhttpclient.MiddlewareName).MiddlewareName()) + require.Equal(t, awssdk.SigV4MiddlewareName, o.Middlewares[8].(sdkhttpclient.MiddlewareName).MiddlewareName()) }) t.Run("When creating new provider and http logging is enabled for one plugin, it should apply expected middleware", func(t *testing.T) { diff --git a/pkg/infra/httpclient/httpclientprovider/sigv4_middleware.go b/pkg/infra/httpclient/httpclientprovider/sigv4_middleware.go deleted file mode 100644 index 0a5de35d08a..00000000000 --- a/pkg/infra/httpclient/httpclientprovider/sigv4_middleware.go +++ /dev/null @@ -1,47 +0,0 @@ -package httpclientprovider - -import ( - "fmt" - "net/http" - - "github.com/grafana/grafana-aws-sdk/pkg/sigv4" - "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" -) - -// SigV4MiddlewareName the middleware name used by SigV4Middleware. -const SigV4MiddlewareName = "sigv4" - -var newSigV4Func = sigv4.New - -// SigV4Middleware applies AWS Signature Version 4 request signing for the outgoing request. -func SigV4Middleware(verboseLogging bool) httpclient.Middleware { - return httpclient.NamedMiddlewareFunc(SigV4MiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper { - if opts.SigV4 == nil { - return next - } - - conf := &sigv4.Config{ - Service: opts.SigV4.Service, - AccessKey: opts.SigV4.AccessKey, - SecretKey: opts.SigV4.SecretKey, - Region: opts.SigV4.Region, - AssumeRoleARN: opts.SigV4.AssumeRoleARN, - AuthType: opts.SigV4.AuthType, - ExternalID: opts.SigV4.ExternalID, - Profile: opts.SigV4.Profile, - } - - rt, err := newSigV4Func(conf, next, sigv4.Opts{VerboseMode: verboseLogging}) - if err != nil { - return invalidSigV4Config(err) - } - - return rt - }) -} - -func invalidSigV4Config(err error) http.RoundTripper { - return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) { - return nil, fmt.Errorf("invalid SigV4 configuration: %w", err) - }) -} diff --git a/pkg/infra/httpclient/httpclientprovider/sigv4_middleware_test.go b/pkg/infra/httpclient/httpclientprovider/sigv4_middleware_test.go deleted file mode 100644 index 1e021d42d7b..00000000000 --- a/pkg/infra/httpclient/httpclientprovider/sigv4_middleware_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package httpclientprovider - -import ( - "fmt" - "net/http" - "testing" - - "github.com/grafana/grafana-aws-sdk/pkg/sigv4" - "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" - "github.com/stretchr/testify/require" -) - -func TestSigV4Middleware(t *testing.T) { - t.Run("Without sigv4 options set should return next http.RoundTripper", func(t *testing.T) { - origSigV4Func := newSigV4Func - newSigV4Called := false - middlewareCalled := false - newSigV4Func = func(config *sigv4.Config, next http.RoundTripper, opts ...sigv4.Opts) (http.RoundTripper, error) { - newSigV4Called = true - return httpclient.RoundTripperFunc(func(r *http.Request) (*http.Response, error) { - middlewareCalled = true - return next.RoundTrip(r) - }), nil - } - t.Cleanup(func() { - newSigV4Func = origSigV4Func - }) - - ctx := &testContext{} - finalRoundTripper := ctx.createRoundTripper("finalrt") - mw := SigV4Middleware(false) - rt := mw.CreateMiddleware(httpclient.Options{}, finalRoundTripper) - require.NotNil(t, rt) - middlewareName, ok := mw.(httpclient.MiddlewareName) - require.True(t, ok) - require.Equal(t, SigV4MiddlewareName, middlewareName.MiddlewareName()) - - req, err := http.NewRequest(http.MethodGet, "http://", nil) - require.NoError(t, err) - res, err := rt.RoundTrip(req) - require.NoError(t, err) - require.NotNil(t, res) - if res.Body != nil { - require.NoError(t, res.Body.Close()) - } - require.Len(t, ctx.callChain, 1) - require.ElementsMatch(t, []string{"finalrt"}, ctx.callChain) - require.False(t, newSigV4Called) - require.False(t, middlewareCalled) - }) - - t.Run("With sigv4 options set should call sigv4 http.RoundTripper", func(t *testing.T) { - origSigV4Func := newSigV4Func - newSigV4Called := false - middlewareCalled := false - newSigV4Func = func(config *sigv4.Config, next http.RoundTripper, opts ...sigv4.Opts) (http.RoundTripper, error) { - newSigV4Called = true - return httpclient.RoundTripperFunc(func(r *http.Request) (*http.Response, error) { - middlewareCalled = true - return next.RoundTrip(r) - }), nil - } - t.Cleanup(func() { - newSigV4Func = origSigV4Func - }) - - ctx := &testContext{} - finalRoundTripper := ctx.createRoundTripper("final") - mw := SigV4Middleware(false) - rt := mw.CreateMiddleware(httpclient.Options{SigV4: &httpclient.SigV4Config{}}, finalRoundTripper) - require.NotNil(t, rt) - middlewareName, ok := mw.(httpclient.MiddlewareName) - require.True(t, ok) - require.Equal(t, SigV4MiddlewareName, middlewareName.MiddlewareName()) - - req, err := http.NewRequest(http.MethodGet, "http://", nil) - require.NoError(t, err) - res, err := rt.RoundTrip(req) - require.NoError(t, err) - require.NotNil(t, res) - if res.Body != nil { - require.NoError(t, res.Body.Close()) - } - require.Len(t, ctx.callChain, 1) - require.ElementsMatch(t, []string{"final"}, ctx.callChain) - - require.True(t, newSigV4Called) - require.True(t, middlewareCalled) - }) - - t.Run("With sigv4 error returned", func(t *testing.T) { - origSigV4Func := newSigV4Func - newSigV4Func = func(config *sigv4.Config, next http.RoundTripper, opts ...sigv4.Opts) (http.RoundTripper, error) { - return nil, fmt.Errorf("problem") - } - t.Cleanup(func() { - newSigV4Func = origSigV4Func - }) - - ctx := &testContext{} - finalRoundTripper := ctx.createRoundTripper("final") - mw := SigV4Middleware(false) - rt := mw.CreateMiddleware(httpclient.Options{SigV4: &httpclient.SigV4Config{}}, finalRoundTripper) - require.NotNil(t, rt) - middlewareName, ok := mw.(httpclient.MiddlewareName) - require.True(t, ok) - require.Equal(t, SigV4MiddlewareName, middlewareName.MiddlewareName()) - - req, err := http.NewRequest(http.MethodGet, "http://", nil) - require.NoError(t, err) - // response is nil - // nolint:bodyclose - res, err := rt.RoundTrip(req) - require.Error(t, err) - require.Nil(t, res) - require.Empty(t, ctx.callChain) - }) -} diff --git a/pkg/registry/apis/datasource/middleware.go b/pkg/registry/apis/datasource/middleware.go index fac100c8cac..95d8082320a 100644 --- a/pkg/registry/apis/datasource/middleware.go +++ b/pkg/registry/apis/datasource/middleware.go @@ -3,13 +3,21 @@ package datasource import ( "context" + "github.com/grafana/grafana-aws-sdk/pkg/awsds" + "github.com/grafana/grafana-aws-sdk/pkg/sigv4" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" ) func contextualMiddlewares(ctx context.Context) context.Context { cfg := backend.GrafanaConfigFromContext(ctx) - m := httpclient.ResponseLimitMiddleware(cfg.ResponseLimit()) + responseLimitMiddleware := httpclient.ResponseLimitMiddleware(cfg.ResponseLimit()) + ctx = httpclient.WithContextualMiddleware(ctx, responseLimitMiddleware) - return httpclient.WithContextualMiddleware(ctx, m) + sigv4Settings := awsds.ReadSigV4Settings(ctx) + if sigv4Settings.Enabled { + ctx = httpclient.WithContextualMiddleware(ctx, sigv4.SigV4Middleware(sigv4Settings.VerboseLogging)) + } + + return ctx } diff --git a/pkg/services/pluginsintegration/pluginconfig/config.go b/pkg/services/pluginsintegration/pluginconfig/config.go index fae18205330..913643a7301 100644 --- a/pkg/services/pluginsintegration/pluginconfig/config.go +++ b/pkg/services/pluginsintegration/pluginconfig/config.go @@ -75,6 +75,9 @@ type PluginInstanceCfg struct { SQLDatasourceMaxOpenConnsDefault int SQLDatasourceMaxIdleConnsDefault int SQLDatasourceMaxConnLifetimeDefault int + + SigV4AuthEnabled bool + SigV4VerboseLogging bool } // ProvidePluginInstanceConfig returns a new PluginInstanceCfg. @@ -120,6 +123,8 @@ func ProvidePluginInstanceConfig(cfg *setting.Cfg, settingProvider setting.Provi SQLDatasourceMaxIdleConnsDefault: cfg.SqlDatasourceMaxIdleConnsDefault, SQLDatasourceMaxConnLifetimeDefault: cfg.SqlDatasourceMaxConnLifetimeDefault, ResponseLimit: cfg.ResponseLimit, + SigV4AuthEnabled: cfg.SigV4AuthEnabled, + SigV4VerboseLogging: cfg.SigV4VerboseLogging, }, nil } diff --git a/pkg/services/pluginsintegration/pluginconfig/request.go b/pkg/services/pluginsintegration/pluginconfig/request.go index 0e969b8f40a..da7f8a164c8 100644 --- a/pkg/services/pluginsintegration/pluginconfig/request.go +++ b/pkg/services/pluginsintegration/pluginconfig/request.go @@ -151,5 +151,10 @@ func (s *RequestConfigProvider) PluginRequestConfig(ctx context.Context, pluginI m[backend.ResponseLimit] = strconv.FormatInt(s.cfg.ResponseLimit, 10) } + if s.cfg.SigV4AuthEnabled { + m[awsds.SigV4AuthEnabledEnvVarKeyName] = "true" + m[awsds.SigV4VerboseLoggingEnvVarKeyName] = strconv.FormatBool(s.cfg.SigV4VerboseLogging) + } + return m }