From 3b7b49a4aa77f95405bb81a9a1e90d30c5a5d1a2 Mon Sep 17 00:00:00 2001 From: Will Browne Date: Mon, 1 Feb 2021 16:07:27 +0100 Subject: [PATCH] Auth: Use SigV4 lib from grafana-aws-sdk (#30713) * replace with lib * remove test + apply feedback --- go.mod | 1 + go.sum | 6 + pkg/models/datasource_cache.go | 37 +++++-- pkg/models/datasource_cache_test.go | 24 ---- pkg/models/sigv4.go | 166 ---------------------------- 5 files changed, 32 insertions(+), 202 deletions(-) delete mode 100644 pkg/models/sigv4.go diff --git a/go.mod b/go.mod index 5c289dd1e72..c27cc21daaa 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/google/go-cmp v0.5.4 github.com/google/uuid v1.2.0 github.com/gosimple/slug v1.9.0 + github.com/grafana/grafana-aws-sdk v0.0.0-20210112151715-f082803d987a github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4 github.com/grafana/grafana-plugin-sdk-go v0.83.0 github.com/grafana/loki v1.6.2-0.20201026154740-6978ee5d7387 diff --git a/go.sum b/go.sum index 9b6593b0220..6dd0c6312f3 100644 --- a/go.sum +++ b/go.sum @@ -172,6 +172,7 @@ github.com/aws/aws-sdk-go v1.33.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZve github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.35.5 h1:doSEOxC0UkirPcle20Rc+1kAhJ4Ip+GSEeZ3nKl7Qlk= github.com/aws/aws-sdk-go v1.35.5/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= +github.com/aws/aws-sdk-go v1.35.30/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-sdk-go v1.36.31 h1:BMVngapDGAfLBVEVzaSIw3fmJdWx7jOvhLCXgRXbXQI= github.com/aws/aws-sdk-go v1.36.31/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= @@ -673,8 +674,11 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs= github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg= +github.com/grafana/grafana-aws-sdk v0.0.0-20210112151715-f082803d987a h1:VB7i+rh7N4edvF9fJq9YYKkH1SjsKBgZYtYyDqeAfIg= +github.com/grafana/grafana-aws-sdk v0.0.0-20210112151715-f082803d987a/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U= github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4 h1:SPdxCL9BChFTlyi0Khv64vdCW4TMna8+sxL7+Chx+Ag= github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4/go.mod h1:nc0XxBzjeGcrMltCDw269LoWF9S8ibhgxolCdA1R8To= +github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60= github.com/grafana/grafana-plugin-sdk-go v0.83.0 h1:X84eJMLSx0KOTRW1EziXkqLw9pSmK0RggWC98ImX/9g= github.com/grafana/grafana-plugin-sdk-go v0.83.0/go.mod h1:exQQHhClzHs2gOwjPSO4FOKwjjZ8VrnzbbABHX8LB6U= github.com/grafana/loki v1.6.2-0.20201026154740-6978ee5d7387 h1:iwcM8lkYJ3EhytGLJ2BvRSwutb0QWoI7EWbYv3yJRsY= @@ -919,6 +923,7 @@ github.com/linkedin/goavro/v2 v2.9.7/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8 github.com/lovoo/gcloud-opentracing v0.3.0/go.mod h1:ZFqk2y38kMDDikZPAK7ynTTGuyt17nSPdS3K5e+ZTBY= github.com/lufia/iostat v1.1.0/go.mod h1:rEPNA0xXgjHQjuI5Cy05sLlS2oRcSlWHRLrvh/AQ+Pg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g= github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -934,6 +939,7 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e h1:qqXczln0qwkVGcpQ+sQuPOVntt2FytYarXXxYSNJkgw= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= +github.com/mattetti/filebuffer v1.0.0/go.mod h1:X6nyAIge2JGVmuJt2MFCqmHrb/5IHiphfHtot0s5cnI= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= diff --git a/pkg/models/datasource_cache.go b/pkg/models/datasource_cache.go index e84ddd0e5f4..63ce4da2479 100644 --- a/pkg/models/datasource_cache.go +++ b/pkg/models/datasource_cache.go @@ -10,6 +10,8 @@ import ( "sync" "time" + "github.com/grafana/grafana-aws-sdk/pkg/sigv4" + "github.com/grafana/grafana/pkg/infra/metrics/metricutil" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/client_golang/prometheus" @@ -191,19 +193,19 @@ func (ds *DataSource) GetHttpTransport() (*dataSourceTransport, error) { func (ds *DataSource) sigV4Middleware(next http.RoundTripper) http.RoundTripper { decrypted := ds.DecryptedValues() - return &SigV4Middleware{ - Config: &Config{ - DatasourceType: ds.Type, - AccessKey: decrypted["sigV4AccessKey"], - SecretKey: decrypted["sigV4SecretKey"], - Region: ds.JsonData.Get("sigV4Region").MustString(), - AssumeRoleARN: ds.JsonData.Get("sigV4AssumeRoleArn").MustString(), - AuthType: ds.JsonData.Get("sigV4AuthType").MustString(), - ExternalID: ds.JsonData.Get("sigV4ExternalId").MustString(), - Profile: ds.JsonData.Get("sigV4Profile").MustString(), + return sigv4.New( + &sigv4.Config{ + Service: awsServiceNamespace(ds.Type), + AccessKey: decrypted["sigV4AccessKey"], + SecretKey: decrypted["sigV4SecretKey"], + Region: ds.JsonData.Get("sigV4Region").MustString(), + AssumeRoleARN: ds.JsonData.Get("sigV4AssumeRoleArn").MustString(), + AuthType: ds.JsonData.Get("sigV4AuthType").MustString(), + ExternalID: ds.JsonData.Get("sigV4ExternalId").MustString(), + Profile: ds.JsonData.Get("sigV4Profile").MustString(), }, - Next: next, - } + next, + ) } func (ds *DataSource) GetTLSConfig() (*tls.Config, error) { @@ -319,3 +321,14 @@ func ClearDSDecryptionCache() { dsDecryptionCache.cache = make(map[int64]cachedDecryptedJSON) } + +func awsServiceNamespace(dsType string) string { + switch dsType { + case DS_ES, DS_ES_OPEN_DISTRO: + return "es" + case DS_PROMETHEUS: + return "aps" + default: + panic(fmt.Sprintf("Unsupported datasource %q", dsType)) + } +} diff --git a/pkg/models/datasource_cache_test.go b/pkg/models/datasource_cache_test.go index e49514d3cd5..f9dfe37ba60 100644 --- a/pkg/models/datasource_cache_test.go +++ b/pkg/models/datasource_cache_test.go @@ -214,30 +214,6 @@ func TestDataSource_GetHttpTransport(t *testing.T) { assert.Equal(t, "Ok", bodyStr) }) - t.Run("Should use SigV4 in middleware chain if configured in JsonData", func(t *testing.T) { - clearDSProxyCache(t) - - origEnabled := setting.SigV4AuthEnabled - setting.SigV4AuthEnabled = true - t.Cleanup(func() { setting.SigV4AuthEnabled = origEnabled }) - - json, err := simplejson.NewJson([]byte(`{ "sigV4Auth": true }`)) - require.NoError(t, err) - - ds := DataSource{ - JsonData: json, - } - - tr, err := ds.GetHttpTransport() - require.NoError(t, err) - - m1, ok := tr.next.(*SigV4Middleware) - require.True(t, ok) - - _, ok = m1.Next.(*http.Transport) - require.True(t, ok) - }) - t.Run("Should not include SigV4 middleware if not configured in JsonData", func(t *testing.T) { clearDSProxyCache(t) diff --git a/pkg/models/sigv4.go b/pkg/models/sigv4.go deleted file mode 100644 index dd8fb5c838a..00000000000 --- a/pkg/models/sigv4.go +++ /dev/null @@ -1,166 +0,0 @@ -package models - -import ( - "bytes" - "fmt" - "io/ioutil" - "net/http" - "strings" - "time" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/credentials/stscreds" - "github.com/aws/aws-sdk-go/aws/session" - v4 "github.com/aws/aws-sdk-go/aws/signer/v4" - "github.com/aws/aws-sdk-go/private/protocol/rest" -) - -type AuthType string - -const ( - Default AuthType = "default" - Keys AuthType = "keys" - Credentials AuthType = "credentials" -) - -// Host header is likely not necessary here -// (see https://github.com/golang/go/blob/cad6d1fef5147d31e94ee83934c8609d3ad150b7/src/net/http/request.go#L92) -// but adding for completeness -var permittedHeaders = map[string]struct{}{ - "Host": {}, - "Uber-Trace-Id": {}, - "User-Agent": {}, - "Accept": {}, - "Accept-Encoding": {}, - "Content-Type": {}, - "Content-Length": {}, - "securitytenant": {}, - "sgtenant": {}, - "kbn-xsrf": {}, -} - -type SigV4Middleware struct { - Config *Config - Next http.RoundTripper -} - -type Config struct { - AuthType string - - Profile string - - DatasourceType string - - AccessKey string - SecretKey string - - AssumeRoleARN string - ExternalID string - Region string -} - -func (m *SigV4Middleware) RoundTrip(req *http.Request) (*http.Response, error) { - _, err := m.signRequest(req) - if err != nil { - return nil, err - } - - if m.Next == nil { - return http.DefaultTransport.RoundTrip(req) - } - - return m.Next.RoundTrip(req) -} - -func (m *SigV4Middleware) signRequest(req *http.Request) (http.Header, error) { - signer, err := m.signer() - if err != nil { - return nil, err - } - - body, err := replaceBody(req) - if err != nil { - return nil, err - } - - if strings.Contains(req.URL.RawPath, "%2C") { - req.URL.RawPath = rest.EscapePath(req.URL.RawPath, false) - } - - stripHeaders(req) - - return signer.Sign(req, bytes.NewReader(body), awsServiceNamespace(m.Config.DatasourceType), m.Config.Region, time.Now().UTC()) -} - -func (m *SigV4Middleware) signer() (*v4.Signer, error) { - authType := AuthType(m.Config.AuthType) - - var c *credentials.Credentials - switch authType { - case Keys: - c = credentials.NewStaticCredentials(m.Config.AccessKey, m.Config.SecretKey, "") - case Credentials: - c = credentials.NewSharedCredentials("", m.Config.Profile) - case Default: - // passing nil credentials will force AWS to allow a more complete credential chain vs the explicit default - s, err := session.NewSession(&aws.Config{ - Region: aws.String(m.Config.Region), - }) - if err != nil { - return nil, err - } - - if m.Config.AssumeRoleARN != "" { - return v4.NewSigner(stscreds.NewCredentials(s, m.Config.AssumeRoleARN)), nil - } - - return v4.NewSigner(s.Config.Credentials), nil - case "": - return nil, fmt.Errorf("invalid SigV4 auth type") - } - - if m.Config.AssumeRoleARN != "" { - s, err := session.NewSession(&aws.Config{ - Region: aws.String(m.Config.Region), - Credentials: c}, - ) - if err != nil { - return nil, err - } - return v4.NewSigner(stscreds.NewCredentials(s, m.Config.AssumeRoleARN)), nil - } - - return v4.NewSigner(c), nil -} - -func replaceBody(req *http.Request) ([]byte, error) { - if req.Body == nil { - return []byte{}, nil - } - payload, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - req.Body = ioutil.NopCloser(bytes.NewReader(payload)) - return payload, nil -} - -func awsServiceNamespace(dsType string) string { - switch dsType { - case DS_ES, DS_ES_OPEN_DISTRO: - return "es" - case DS_PROMETHEUS: - return "aps" - default: - panic(fmt.Sprintf("Unsupported datasource %s", dsType)) - } -} - -func stripHeaders(req *http.Request) { - for h := range req.Header { - if _, exists := permittedHeaders[h]; !exists { - req.Header.Del(h) - } - } -}