mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Cloudwatch: use shared library for aws auth (#29550)
* use sdk for handling auth * fix broken test * lint fixes Co-authored-by: Erik Sundell <erik.sundell87@gmail.com>
This commit is contained in:
@@ -4,15 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
@@ -31,19 +28,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type datasourceInfo struct {
|
||||
Profile string
|
||||
Region string
|
||||
AuthType authType
|
||||
AssumeRoleARN string
|
||||
ExternalID string
|
||||
Namespace string
|
||||
Endpoint string
|
||||
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
const cloudWatchTSFormat = "2006-01-02 15:04:05.000"
|
||||
const defaultRegion = "default"
|
||||
|
||||
@@ -65,20 +49,27 @@ func init() {
|
||||
type CloudWatchService struct {
|
||||
LogsService *LogsService `inject:""`
|
||||
Cfg *setting.Cfg `inject:""`
|
||||
sessions SessionCache
|
||||
}
|
||||
|
||||
func (s *CloudWatchService) Init() error {
|
||||
s.sessions = awsds.NewSessionCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CloudWatchService) NewExecutor(*models.DataSource) (plugins.DataPlugin, error) {
|
||||
return newExecutor(s.LogsService, s.Cfg), nil
|
||||
return newExecutor(s.LogsService, s.Cfg, s.sessions), nil
|
||||
}
|
||||
|
||||
func newExecutor(logsService *LogsService, cfg *setting.Cfg) *cloudWatchExecutor {
|
||||
type SessionCache interface {
|
||||
GetSession(region string, s awsds.AWSDatasourceSettings) (*session.Session, error)
|
||||
}
|
||||
|
||||
func newExecutor(logsService *LogsService, cfg *setting.Cfg, sessions SessionCache) *cloudWatchExecutor {
|
||||
return &cloudWatchExecutor{
|
||||
logsService: logsService,
|
||||
cfg: cfg,
|
||||
logsService: logsService,
|
||||
sessions: sessions,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,135 +82,13 @@ type cloudWatchExecutor struct {
|
||||
|
||||
logsService *LogsService
|
||||
cfg *setting.Cfg
|
||||
sessions SessionCache
|
||||
}
|
||||
|
||||
func (e *cloudWatchExecutor) newSession(region string) (*session.Session, error) {
|
||||
dsInfo := e.getDSInfo(region)
|
||||
awsDatasourceSettings := e.getAWSDatasourceSettings(region)
|
||||
|
||||
authTypeAllowed := false
|
||||
for _, provider := range e.cfg.AWSAllowedAuthProviders {
|
||||
if provider == dsInfo.AuthType.String() {
|
||||
authTypeAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !authTypeAllowed {
|
||||
return nil, fmt.Errorf("attempting to use an auth type that is not allowed: %q", dsInfo.AuthType.String())
|
||||
}
|
||||
|
||||
if dsInfo.AssumeRoleARN != "" && !e.cfg.AWSAssumeRoleEnabled {
|
||||
return nil, fmt.Errorf("attempting to use assume role (ARN) which is disabled in grafana.ini")
|
||||
}
|
||||
|
||||
bldr := strings.Builder{}
|
||||
for i, s := range []string{
|
||||
dsInfo.AuthType.String(), dsInfo.AccessKey, dsInfo.Profile, dsInfo.AssumeRoleARN, region, dsInfo.Endpoint,
|
||||
} {
|
||||
if i != 0 {
|
||||
bldr.WriteString(":")
|
||||
}
|
||||
bldr.WriteString(strings.ReplaceAll(s, ":", `\:`))
|
||||
}
|
||||
cacheKey := bldr.String()
|
||||
|
||||
sessCacheLock.RLock()
|
||||
if env, ok := sessCache[cacheKey]; ok {
|
||||
if env.expiration.After(time.Now().UTC()) {
|
||||
sessCacheLock.RUnlock()
|
||||
return env.session, nil
|
||||
}
|
||||
}
|
||||
sessCacheLock.RUnlock()
|
||||
|
||||
cfgs := []*aws.Config{
|
||||
{
|
||||
CredentialsChainVerboseErrors: aws.Bool(true),
|
||||
},
|
||||
}
|
||||
|
||||
var regionCfg *aws.Config
|
||||
if dsInfo.Region == defaultRegion {
|
||||
plog.Warn("Region is set to \"default\", which is unsupported")
|
||||
dsInfo.Region = ""
|
||||
}
|
||||
if dsInfo.Region != "" {
|
||||
regionCfg = &aws.Config{Region: aws.String(dsInfo.Region)}
|
||||
cfgs = append(cfgs, regionCfg)
|
||||
}
|
||||
|
||||
if dsInfo.Endpoint != "" {
|
||||
cfgs = append(cfgs, &aws.Config{Endpoint: aws.String(dsInfo.Endpoint)})
|
||||
}
|
||||
|
||||
switch dsInfo.AuthType {
|
||||
case authTypeSharedCreds:
|
||||
plog.Debug("Authenticating towards AWS with shared credentials", "profile", dsInfo.Profile,
|
||||
"region", dsInfo.Region)
|
||||
cfgs = append(cfgs, &aws.Config{
|
||||
Credentials: credentials.NewSharedCredentials("", dsInfo.Profile),
|
||||
})
|
||||
case authTypeKeys:
|
||||
plog.Debug("Authenticating towards AWS with an access key pair", "region", dsInfo.Region)
|
||||
cfgs = append(cfgs, &aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(dsInfo.AccessKey, dsInfo.SecretKey, ""),
|
||||
})
|
||||
case authTypeDefault:
|
||||
plog.Debug("Authenticating towards AWS with default SDK method", "region", dsInfo.Region)
|
||||
case authTypeEC2IAMRole:
|
||||
plog.Debug("Authenticating towards AWS with IAM Role", "region", dsInfo.Region)
|
||||
sess, err := newSession(cfgs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgs = append(cfgs, &aws.Config{Credentials: newEC2RoleCredentials(sess)})
|
||||
default:
|
||||
panic(fmt.Sprintf("Unrecognized authType: %d", dsInfo.AuthType))
|
||||
}
|
||||
sess, err := newSession(cfgs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
duration := stscreds.DefaultDuration
|
||||
expiration := time.Now().UTC().Add(duration)
|
||||
if dsInfo.AssumeRoleARN != "" && e.cfg.AWSAssumeRoleEnabled {
|
||||
// We should assume a role in AWS
|
||||
plog.Debug("Trying to assume role in AWS", "arn", dsInfo.AssumeRoleARN)
|
||||
|
||||
cfgs := []*aws.Config{
|
||||
{
|
||||
CredentialsChainVerboseErrors: aws.Bool(true),
|
||||
},
|
||||
{
|
||||
Credentials: newSTSCredentials(sess, dsInfo.AssumeRoleARN, func(p *stscreds.AssumeRoleProvider) {
|
||||
// Not sure if this is necessary, overlaps with p.Duration and is undocumented
|
||||
p.Expiry.SetExpiration(expiration, 0)
|
||||
p.Duration = duration
|
||||
if dsInfo.ExternalID != "" {
|
||||
p.ExternalID = aws.String(dsInfo.ExternalID)
|
||||
}
|
||||
}),
|
||||
},
|
||||
}
|
||||
if regionCfg != nil {
|
||||
cfgs = append(cfgs, regionCfg)
|
||||
}
|
||||
sess, err = newSession(cfgs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
plog.Debug("Successfully created AWS session")
|
||||
|
||||
sessCacheLock.Lock()
|
||||
sessCache[cacheKey] = envelope{
|
||||
session: sess,
|
||||
expiration: expiration,
|
||||
}
|
||||
sessCacheLock.Unlock()
|
||||
|
||||
return sess, nil
|
||||
return e.sessions.GetSession(region, *awsDatasourceSettings)
|
||||
}
|
||||
|
||||
func (e *cloudWatchExecutor) getCWClient(region string) (cloudwatchiface.CloudWatchAPI, error) {
|
||||
@@ -415,31 +284,7 @@ func (e *cloudWatchExecutor) executeLogAlertQuery(ctx context.Context, queryCont
|
||||
return response, nil
|
||||
}
|
||||
|
||||
type authType int
|
||||
|
||||
const (
|
||||
authTypeDefault authType = iota
|
||||
authTypeSharedCreds
|
||||
authTypeKeys
|
||||
authTypeEC2IAMRole
|
||||
)
|
||||
|
||||
func (at authType) String() string {
|
||||
switch at {
|
||||
case authTypeDefault:
|
||||
return "default"
|
||||
case authTypeSharedCreds:
|
||||
return "credentials"
|
||||
case authTypeKeys:
|
||||
return "keys"
|
||||
case authTypeEC2IAMRole:
|
||||
return "ec2_iam_role"
|
||||
default:
|
||||
panic(fmt.Sprintf("Unrecognized auth type %d", at))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *cloudWatchExecutor) getDSInfo(region string) *datasourceInfo {
|
||||
func (e *cloudWatchExecutor) getAWSDatasourceSettings(region string) *awsds.AWSDatasourceSettings {
|
||||
if region == defaultRegion {
|
||||
region = e.DataSource.JsonData.Get("defaultRegion").MustString()
|
||||
}
|
||||
@@ -452,19 +297,19 @@ func (e *cloudWatchExecutor) getDSInfo(region string) *datasourceInfo {
|
||||
accessKey := decrypted["accessKey"]
|
||||
secretKey := decrypted["secretKey"]
|
||||
|
||||
at := authTypeDefault
|
||||
at := awsds.AuthTypeDefault
|
||||
switch atStr {
|
||||
case "credentials":
|
||||
at = authTypeSharedCreds
|
||||
at = awsds.AuthTypeSharedCreds
|
||||
case "keys":
|
||||
at = authTypeKeys
|
||||
at = awsds.AuthTypeKeys
|
||||
case "default":
|
||||
at = authTypeDefault
|
||||
case "ec2_iam_role":
|
||||
at = authTypeEC2IAMRole
|
||||
at = awsds.AuthTypeDefault
|
||||
case "arn":
|
||||
at = authTypeDefault
|
||||
at = awsds.AuthTypeDefault
|
||||
plog.Warn("Authentication type \"arn\" is deprecated, falling back to default")
|
||||
case "ec2_iam_role":
|
||||
at = awsds.AuthTypeEC2IAMRole
|
||||
default:
|
||||
plog.Warn("Unrecognized AWS authentication type", "type", atStr)
|
||||
}
|
||||
@@ -474,7 +319,7 @@ func (e *cloudWatchExecutor) getDSInfo(region string) *datasourceInfo {
|
||||
profile = e.DataSource.Database // legacy support
|
||||
}
|
||||
|
||||
return &datasourceInfo{
|
||||
return &awsds.AWSDatasourceSettings{
|
||||
Region: region,
|
||||
Profile: profile,
|
||||
AuthType: at,
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
Queries: []plugins.DataSubQuery{
|
||||
{
|
||||
@@ -100,7 +100,7 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
Queries: []plugins.DataSubQuery{
|
||||
{
|
||||
@@ -170,7 +170,7 @@ func TestQuery_GetLogGroupFields(t *testing.T) {
|
||||
|
||||
const refID = "A"
|
||||
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
Queries: []plugins.DataSubQuery{
|
||||
{
|
||||
@@ -249,7 +249,7 @@ func TestQuery_StartQuery(t *testing.T) {
|
||||
To: "1584700643000",
|
||||
}
|
||||
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
_, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
TimeRange: &timeRange,
|
||||
Queries: []plugins.DataSubQuery{
|
||||
@@ -295,7 +295,7 @@ func TestQuery_StartQuery(t *testing.T) {
|
||||
To: "1584873443000",
|
||||
}
|
||||
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
TimeRange: &timeRange,
|
||||
Queries: []plugins.DataSubQuery{
|
||||
@@ -371,7 +371,7 @@ func TestQuery_StopQuery(t *testing.T) {
|
||||
To: "1584700643000",
|
||||
}
|
||||
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
TimeRange: &timeRange,
|
||||
Queries: []plugins.DataSubQuery{
|
||||
@@ -458,7 +458,7 @@ func TestQuery_GetQueryResults(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
Queries: []plugins.DataSubQuery{
|
||||
{
|
||||
|
||||
@@ -323,7 +323,7 @@ func parseMultiSelectValue(input string) []string {
|
||||
// Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html
|
||||
func (e *cloudWatchExecutor) handleGetRegions(ctx context.Context, parameters *simplejson.Json,
|
||||
queryContext plugins.DataQuery) ([]suggestData, error) {
|
||||
dsInfo := e.getDSInfo(defaultRegion)
|
||||
dsInfo := e.getAWSDatasourceSettings(defaultRegion)
|
||||
profile := dsInfo.Profile
|
||||
if cache, ok := regionCache.Load(profile); ok {
|
||||
if cache2, ok2 := cache.([]suggestData); ok2 {
|
||||
@@ -716,7 +716,7 @@ func (e *cloudWatchExecutor) getMetricsForCustomMetrics(region, namespace string
|
||||
metricsCacheLock.Lock()
|
||||
defer metricsCacheLock.Unlock()
|
||||
|
||||
dsInfo := e.getDSInfo(region)
|
||||
dsInfo := e.getAWSDatasourceSettings(region)
|
||||
|
||||
if _, ok := customMetricsMetricsMap[dsInfo.Profile]; !ok {
|
||||
customMetricsMetricsMap[dsInfo.Profile] = make(map[string]map[string]*customMetricsCache)
|
||||
@@ -760,7 +760,7 @@ func (e *cloudWatchExecutor) getDimensionsForCustomMetrics(region, namespace str
|
||||
dimensionsCacheLock.Lock()
|
||||
defer dimensionsCacheLock.Unlock()
|
||||
|
||||
dsInfo := e.getDSInfo(region)
|
||||
dsInfo := e.getAWSDatasourceSettings(region)
|
||||
|
||||
if _, ok := customMetricsDimensionsMap[dsInfo.Profile]; !ok {
|
||||
customMetricsDimensionsMap[dsInfo.Profile] = make(map[string]map[string]*customMetricsCache)
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestQuery_Metrics(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
Queries: []plugins.DataSubQuery{
|
||||
{
|
||||
@@ -102,7 +102,7 @@ func TestQuery_Metrics(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
Queries: []plugins.DataSubQuery{
|
||||
{
|
||||
@@ -164,7 +164,7 @@ func TestQuery_Regions(t *testing.T) {
|
||||
cli = fakeEC2Client{
|
||||
regions: []string{regionName},
|
||||
}
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
Queries: []plugins.DataSubQuery{
|
||||
{
|
||||
@@ -246,7 +246,7 @@ func TestQuery_InstanceAttributes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
Queries: []plugins.DataSubQuery{
|
||||
{
|
||||
@@ -349,7 +349,7 @@ func TestQuery_EBSVolumeIDs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
Queries: []plugins.DataSubQuery{
|
||||
{
|
||||
@@ -449,7 +449,7 @@ func TestQuery_ResourceARNs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
|
||||
Queries: []plugins.DataSubQuery{
|
||||
{
|
||||
@@ -528,7 +528,7 @@ func TestQuery_ListMetricsPagination(t *testing.T) {
|
||||
|
||||
t.Run("List Metrics and page limit is reached", func(t *testing.T) {
|
||||
client = FakeCWClient{Metrics: metrics, MetricsPerPage: 2}
|
||||
executor := newExecutor(nil, &setting.Cfg{AWSListMetricsPageLimit: 3, AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true})
|
||||
executor := newExecutor(nil, &setting.Cfg{AWSListMetricsPageLimit: 3, AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true}, fakeSessionCache{})
|
||||
executor.DataSource = fakeDataSource()
|
||||
response, err := executor.listMetrics("default", &cloudwatch.ListMetricsInput{})
|
||||
require.NoError(t, err)
|
||||
@@ -539,7 +539,7 @@ func TestQuery_ListMetricsPagination(t *testing.T) {
|
||||
|
||||
t.Run("List Metrics and page limit is not reached", func(t *testing.T) {
|
||||
client = FakeCWClient{Metrics: metrics, MetricsPerPage: 2}
|
||||
executor := newExecutor(nil, &setting.Cfg{AWSListMetricsPageLimit: 1000, AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true})
|
||||
executor := newExecutor(nil, &setting.Cfg{AWSListMetricsPageLimit: 1000, AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true}, fakeSessionCache{})
|
||||
executor.DataSource = fakeDataSource()
|
||||
response, err := executor.listMetrics("default", &cloudwatch.ListMetricsInput{})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TestQueryTransformer(t *testing.T) {
|
||||
executor := newExecutor(nil, &setting.Cfg{})
|
||||
executor := newExecutor(nil, &setting.Cfg{}, fakeSessionCache{})
|
||||
t.Run("One cloudwatchQuery is generated when its request query has one stat", func(t *testing.T) {
|
||||
requestQueries := []*requestQuery{
|
||||
{
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package cloudwatch
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
)
|
||||
|
||||
type envelope struct {
|
||||
session *session.Session
|
||||
expiration time.Time
|
||||
}
|
||||
|
||||
var sessCache = map[string]envelope{}
|
||||
var sessCacheLock sync.RWMutex
|
||||
|
||||
// Session factory.
|
||||
// Stubbable by tests.
|
||||
//nolint:gocritic
|
||||
var newSession = func(cfgs ...*aws.Config) (*session.Session, error) {
|
||||
return session.NewSession(cfgs...)
|
||||
}
|
||||
|
||||
// STS credentials factory.
|
||||
// Stubbable by tests.
|
||||
//nolint:gocritic
|
||||
var newSTSCredentials = stscreds.NewCredentials
|
||||
|
||||
// EC2Metadata service factory.
|
||||
// Stubbable by tests.
|
||||
//nolint:gocritic
|
||||
var newEC2Metadata = ec2metadata.New
|
||||
|
||||
// EC2 role credentials factory.
|
||||
// Stubbable by tests.
|
||||
var newEC2RoleCredentials = func(sess *session.Session) *credentials.Credentials {
|
||||
return credentials.NewCredentials(&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: stscreds.DefaultDuration})
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
package cloudwatch
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Test cloudWatchExecutor.newSession with assumption of IAM role.
|
||||
func TestNewSession_AssumeRole(t *testing.T) {
|
||||
origNewSession := newSession
|
||||
origNewSTSCredentials := newSTSCredentials
|
||||
origNewEC2Metadata := newEC2Metadata
|
||||
t.Cleanup(func() {
|
||||
newSession = origNewSession
|
||||
newSTSCredentials = origNewSTSCredentials
|
||||
newEC2Metadata = origNewEC2Metadata
|
||||
})
|
||||
newSession = func(cfgs ...*aws.Config) (*session.Session, error) {
|
||||
cfg := aws.Config{}
|
||||
cfg.MergeIn(cfgs...)
|
||||
return &session.Session{
|
||||
Config: &cfg,
|
||||
}, nil
|
||||
}
|
||||
newSTSCredentials = func(c client.ConfigProvider, roleARN string,
|
||||
options ...func(*stscreds.AssumeRoleProvider)) *credentials.Credentials {
|
||||
p := &stscreds.AssumeRoleProvider{
|
||||
RoleARN: roleARN,
|
||||
}
|
||||
for _, o := range options {
|
||||
o(p)
|
||||
}
|
||||
|
||||
return credentials.NewCredentials(p)
|
||||
}
|
||||
newEC2Metadata = func(p client.ConfigProvider, cfgs ...*aws.Config) *ec2metadata.EC2Metadata {
|
||||
return nil
|
||||
}
|
||||
|
||||
duration := stscreds.DefaultDuration
|
||||
|
||||
t.Run("Without external ID", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
sessCache = map[string]envelope{}
|
||||
})
|
||||
|
||||
const roleARN = "test"
|
||||
|
||||
e := newExecutor(nil, newTestConfig())
|
||||
e.DataSource = fakeDataSource(fakeDataSourceCfg{
|
||||
assumeRoleARN: roleARN,
|
||||
})
|
||||
|
||||
sess, err := e.newSession(defaultRegion)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sess)
|
||||
|
||||
expCreds := credentials.NewCredentials(&stscreds.AssumeRoleProvider{
|
||||
RoleARN: roleARN,
|
||||
Duration: duration,
|
||||
})
|
||||
diff := cmp.Diff(expCreds, sess.Config.Credentials, cmp.Exporter(func(_ reflect.Type) bool {
|
||||
return true
|
||||
}), cmpopts.IgnoreFields(stscreds.AssumeRoleProvider{}, "Expiry"))
|
||||
assert.Empty(t, diff)
|
||||
})
|
||||
|
||||
t.Run("With external ID", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
sessCache = map[string]envelope{}
|
||||
})
|
||||
|
||||
const roleARN = "test"
|
||||
const externalID = "external"
|
||||
|
||||
e := newExecutor(nil, newTestConfig())
|
||||
e.DataSource = fakeDataSource(fakeDataSourceCfg{
|
||||
assumeRoleARN: roleARN,
|
||||
externalID: externalID,
|
||||
})
|
||||
|
||||
sess, err := e.newSession(defaultRegion)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sess)
|
||||
|
||||
expCreds := credentials.NewCredentials(&stscreds.AssumeRoleProvider{
|
||||
RoleARN: roleARN,
|
||||
ExternalID: aws.String(externalID),
|
||||
Duration: duration,
|
||||
})
|
||||
diff := cmp.Diff(expCreds, sess.Config.Credentials, cmp.Exporter(func(_ reflect.Type) bool {
|
||||
return true
|
||||
}), cmpopts.IgnoreFields(stscreds.AssumeRoleProvider{}, "Expiry"))
|
||||
assert.Empty(t, diff)
|
||||
})
|
||||
|
||||
t.Run("Assume role not enabled", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
sessCache = map[string]envelope{}
|
||||
})
|
||||
|
||||
const roleARN = "test"
|
||||
|
||||
e := newExecutor(nil, &setting.Cfg{AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: false})
|
||||
e.DataSource = fakeDataSource(fakeDataSourceCfg{
|
||||
assumeRoleARN: roleARN,
|
||||
})
|
||||
|
||||
sess, err := e.newSession(defaultRegion)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, sess)
|
||||
|
||||
expectedError := "attempting to use assume role (ARN) which is disabled in grafana.ini"
|
||||
assert.Equal(t, expectedError, err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewSession_AllowedAuthProviders(t *testing.T) {
|
||||
t.Run("Not allowed auth type is used", func(t *testing.T) {
|
||||
e := newExecutor(nil, &setting.Cfg{AWSAllowedAuthProviders: []string{"keys"}})
|
||||
e.DataSource = fakeDataSource()
|
||||
e.DataSource.JsonData.Set("authType", "default")
|
||||
|
||||
sess, err := e.newSession(defaultRegion)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, sess)
|
||||
|
||||
assert.Equal(t, `attempting to use an auth type that is not allowed: "default"`, err.Error())
|
||||
})
|
||||
|
||||
t.Run("Allowed auth type is used", func(t *testing.T) {
|
||||
e := newExecutor(nil, &setting.Cfg{AWSAllowedAuthProviders: []string{"keys"}})
|
||||
e.DataSource = fakeDataSource()
|
||||
e.DataSource.JsonData.Set("authType", "keys")
|
||||
|
||||
sess, err := e.newSession(defaultRegion)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sess)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewSession_EC2IAMRole(t *testing.T) {
|
||||
newSession = func(cfgs ...*aws.Config) (*session.Session, error) {
|
||||
cfg := aws.Config{}
|
||||
cfg.MergeIn(cfgs...)
|
||||
return &session.Session{
|
||||
Config: &cfg,
|
||||
}, nil
|
||||
}
|
||||
newEC2Metadata = func(p client.ConfigProvider, cfgs ...*aws.Config) *ec2metadata.EC2Metadata {
|
||||
return nil
|
||||
}
|
||||
newEC2RoleCredentials = func(sess *session.Session) *credentials.Credentials {
|
||||
return credentials.NewCredentials(&ec2rolecreds.EC2RoleProvider{Client: newEC2Metadata(nil), ExpiryWindow: stscreds.DefaultDuration})
|
||||
}
|
||||
|
||||
t.Run("Credentials are created", func(t *testing.T) {
|
||||
e := newExecutor(nil, &setting.Cfg{AWSAllowedAuthProviders: []string{"ec2_iam_role"}, AWSAssumeRoleEnabled: true})
|
||||
e.DataSource = fakeDataSource()
|
||||
e.DataSource.JsonData.Set("authType", "ec2_iam_role")
|
||||
|
||||
sess, err := e.newSession(defaultRegion)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sess)
|
||||
|
||||
expCreds := credentials.NewCredentials(&ec2rolecreds.EC2RoleProvider{
|
||||
Client: newEC2Metadata(nil), ExpiryWindow: stscreds.DefaultDuration,
|
||||
})
|
||||
|
||||
diff := cmp.Diff(expCreds, sess.Config.Credentials, cmp.Exporter(func(_ reflect.Type) bool {
|
||||
return true
|
||||
}), cmpopts.IgnoreFields(stscreds.AssumeRoleProvider{}, "Expiry"))
|
||||
assert.Empty(t, diff)
|
||||
})
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
|
||||
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface"
|
||||
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
|
||||
"github.com/grafana/grafana/pkg/components/securejsondata"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@@ -179,3 +181,12 @@ func chunkSlice(slice []*cloudwatch.Metric, chunkSize int) [][]*cloudwatch.Metri
|
||||
func newTestConfig() *setting.Cfg {
|
||||
return &setting.Cfg{AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true, AWSListMetricsPageLimit: 1000}
|
||||
}
|
||||
|
||||
type fakeSessionCache struct {
|
||||
}
|
||||
|
||||
func (s fakeSessionCache) GetSession(region string, settings awsds.AWSDatasourceSettings) (*session.Session, error) {
|
||||
return &session.Session{
|
||||
Config: &aws.Config{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestTimeSeriesQuery(t *testing.T) {
|
||||
executor := newExecutor(nil, newTestConfig())
|
||||
executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
|
||||
|
||||
t.Run("End time before start time should result in error", func(t *testing.T) {
|
||||
timeRange := plugins.NewDataTimeRange("now-1h", "now-2h")
|
||||
|
||||
Reference in New Issue
Block a user