diff --git a/conf/defaults.ini b/conf/defaults.ini index dc1d72d96f3..eac091fec67 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -508,7 +508,7 @@ active_sync_enabled = true #################################### AWS ########################### [aws] # Enter a comma-separated list of allowed AWS authentication providers. -# Options are: default (AWS SDK Default), keys (Access && secret key), credentials (Credentials field), ec2_IAM_role (EC2 IAM Role) +# Options are: default (AWS SDK Default), keys (Access && secret key), credentials (Credentials field), ec2_iam_role (EC2 IAM Role) allowed_auth_providers = default,keys,credentials # Allow AWS users to assume a role using temporary security credentials. diff --git a/conf/sample.ini b/conf/sample.ini index 15a543b482d..770210fc5b2 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -498,7 +498,7 @@ #################################### AWS ########################### [aws] # Enter a comma-separated list of allowed AWS authentication providers. -# Options are: default (AWS SDK Default), keys (Access && secret key), credentials (Credentials field), ec2_IAM_role (EC2 IAM Role) +# Options are: default (AWS SDK Default), keys (Access && secret key), credentials (Credentials field), ec2_iam_role (EC2 IAM Role) ; allowed_auth_providers = default,keys,credentials # Allow AWS users to assume a role using temporary security credentials. diff --git a/docs/sources/administration/configuration.md b/docs/sources/administration/configuration.md index 8e9e631ae5f..dd444b1803b 100644 --- a/docs/sources/administration/configuration.md +++ b/docs/sources/administration/configuration.md @@ -780,7 +780,7 @@ You can configure core and external AWS plugins. Specify what authentication providers the AWS plugins allow. For a list of allowed providers, refer to the data-source configuration page for a given plugin. If you configure a plugin by provisioning, only providers that are specified in `allowed_auth_providers` are allowed. -Options: `default` (AWS SDK default), `keys` (Access and secret key), `credentials` (Credentials file), `ec2_IAM_role` (EC2 IAM role) +Options: `default` (AWS SDK default), `keys` (Access and secret key), `credentials` (Credentials file), `ec2_iam_role` (EC2 IAM role) ### assume_role_enabled diff --git a/docs/sources/datasources/cloudwatch.md b/docs/sources/datasources/cloudwatch.md index 5a3a85be927..c0f2f634232 100644 --- a/docs/sources/datasources/cloudwatch.md +++ b/docs/sources/datasources/cloudwatch.md @@ -373,6 +373,18 @@ To request a quota increase, visit the [AWS Service Quotas console](https://cons Please see the AWS documentation for [Service Quotas](https://docs.aws.amazon.com/servicequotas/latest/userguide/intro.html) and [CloudWatch limits](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_limits.html) for more information. +## Configure the data source with grafana.ini + +In the [Grafana configuration](https://grafana.com/docs/grafana/latest/administration/configuration/#aws) there's an `AWS` section that allows you to customize the data source. + +### allowed_auth_providers + +Specify which authentication providers are allowed for the CloudWatch data source. The following providers are enabled by default in OSS Grafana: `default` (AWS SDK default), keys (Access and secret key), credentials (Credentials file), ec2_IAM_role (EC2 IAM role). + +### assume_role_enabled + +Allows you to disable `assume role (ARN)` in the CloudWatch data source. By default, assume role (ARN) is enabled for OSS Grafana. + ## Configure the data source with provisioning It's now possible to configure data sources using config files with Grafana's provisioning system. You can read more about how it works and all the settings you can set for data sources on the [provisioning docs page]({{< relref "../administration/provisioning/#datasources" >}}) diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index 0ee444a3b73..877a54fe74a 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/pkg/tsdb/cloudwatch/cloudwatch.go @@ -64,6 +64,7 @@ func init() { type CloudWatchService struct { LogsService *LogsService `inject:""` + Cfg *setting.Cfg `inject:""` } func (s *CloudWatchService) Init() error { @@ -71,12 +72,13 @@ func (s *CloudWatchService) Init() error { } func (s *CloudWatchService) NewExecutor(*models.DataSource) (plugins.DataPlugin, error) { - return newExecutor(s.LogsService), nil + return newExecutor(s.LogsService, s.Cfg), nil } -func newExecutor(logsService *LogsService) *cloudWatchExecutor { +func newExecutor(logsService *LogsService, cfg *setting.Cfg) *cloudWatchExecutor { return &cloudWatchExecutor{ logsService: logsService, + cfg: cfg, } } @@ -88,11 +90,27 @@ type cloudWatchExecutor struct { rgtaClient resourcegroupstaggingapiiface.ResourceGroupsTaggingAPIAPI logsService *LogsService + cfg *setting.Cfg } func (e *cloudWatchExecutor) newSession(region string) (*session.Session, error) { dsInfo := e.getDSInfo(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, @@ -164,7 +182,7 @@ func (e *cloudWatchExecutor) newSession(region string) (*session.Session, error) duration := stscreds.DefaultDuration expiration := time.Now().UTC().Add(duration) - if dsInfo.AssumeRoleARN != "" { + if dsInfo.AssumeRoleARN != "" && e.cfg.AWSAssumeRoleEnabled { // We should assume a role in AWS plog.Debug("Trying to assume role in AWS", "arn", dsInfo.AssumeRoleARN) diff --git a/pkg/tsdb/cloudwatch/log_actions_test.go b/pkg/tsdb/cloudwatch/log_actions_test.go index 3e7d24c042d..89ff0aafa6e 100644 --- a/pkg/tsdb/cloudwatch/log_actions_test.go +++ b/pkg/tsdb/cloudwatch/log_actions_test.go @@ -47,7 +47,7 @@ func TestQuery_DescribeLogGroups(t *testing.T) { }, } - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) 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) + executor := newExecutor(nil, newTestConfig()) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ Queries: []plugins.DataSubQuery{ { @@ -169,8 +169,7 @@ func TestQuery_GetLogGroupFields(t *testing.T) { } const refID = "A" - - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ Queries: []plugins.DataSubQuery{ { @@ -249,7 +248,7 @@ func TestQuery_StartQuery(t *testing.T) { To: "1584700643000", } - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) _, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ TimeRange: &timeRange, Queries: []plugins.DataSubQuery{ @@ -295,7 +294,7 @@ func TestQuery_StartQuery(t *testing.T) { To: "1584873443000", } - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ TimeRange: &timeRange, Queries: []plugins.DataSubQuery{ @@ -371,7 +370,7 @@ func TestQuery_StopQuery(t *testing.T) { To: "1584700643000", } - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ TimeRange: &timeRange, Queries: []plugins.DataSubQuery{ @@ -458,7 +457,7 @@ func TestQuery_GetQueryResults(t *testing.T) { }, } - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ Queries: []plugins.DataSubQuery{ { diff --git a/pkg/tsdb/cloudwatch/metric_find_query_test.go b/pkg/tsdb/cloudwatch/metric_find_query_test.go index c2311e86bc5..66958ae0579 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query_test.go +++ b/pkg/tsdb/cloudwatch/metric_find_query_test.go @@ -44,7 +44,7 @@ func TestQuery_Metrics(t *testing.T) { }, }, } - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ Queries: []plugins.DataSubQuery{ { @@ -101,7 +101,7 @@ func TestQuery_Metrics(t *testing.T) { }, }, } - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ Queries: []plugins.DataSubQuery{ { @@ -163,7 +163,7 @@ func TestQuery_Regions(t *testing.T) { cli = fakeEC2Client{ regions: []string{regionName}, } - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ Queries: []plugins.DataSubQuery{ { @@ -245,7 +245,7 @@ func TestQuery_InstanceAttributes(t *testing.T) { }, }, } - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ Queries: []plugins.DataSubQuery{ { @@ -348,7 +348,7 @@ func TestQuery_EBSVolumeIDs(t *testing.T) { }, }, } - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ Queries: []plugins.DataSubQuery{ { @@ -448,7 +448,7 @@ func TestQuery_ResourceARNs(t *testing.T) { }, }, } - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ Queries: []plugins.DataSubQuery{ { diff --git a/pkg/tsdb/cloudwatch/query_transformer_test.go b/pkg/tsdb/cloudwatch/query_transformer_test.go index 90f64498042..451ef9ec364 100644 --- a/pkg/tsdb/cloudwatch/query_transformer_test.go +++ b/pkg/tsdb/cloudwatch/query_transformer_test.go @@ -6,12 +6,13 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" + "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestQueryTransformer(t *testing.T) { - executor := newExecutor(nil) + executor := newExecutor(nil, &setting.Cfg{}) t.Run("One cloudwatchQuery is generated when its request query has one stat", func(t *testing.T) { requestQueries := []*requestQuery{ { diff --git a/pkg/tsdb/cloudwatch/session_test.go b/pkg/tsdb/cloudwatch/session_test.go index ef501128fbd..b240c1df3d8 100644 --- a/pkg/tsdb/cloudwatch/session_test.go +++ b/pkg/tsdb/cloudwatch/session_test.go @@ -13,6 +13,7 @@ import ( "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" ) @@ -58,7 +59,7 @@ func TestNewSession_AssumeRole(t *testing.T) { const roleARN = "test" - e := newExecutor(nil) + e := newExecutor(nil, newTestConfig()) e.DataSource = fakeDataSource(fakeDataSourceCfg{ assumeRoleARN: roleARN, }) @@ -85,7 +86,7 @@ func TestNewSession_AssumeRole(t *testing.T) { const roleARN = "test" const externalID = "external" - e := newExecutor(nil) + e := newExecutor(nil, newTestConfig()) e.DataSource = fakeDataSource(fakeDataSourceCfg{ assumeRoleARN: roleARN, externalID: externalID, @@ -105,6 +106,50 @@ func TestNewSession_AssumeRole(t *testing.T) { }), 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) { @@ -123,7 +168,7 @@ func TestNewSession_EC2IAMRole(t *testing.T) { } t.Run("Credentials are created", func(t *testing.T) { - e := newExecutor(nil) + e := newExecutor(nil, &setting.Cfg{AWSAllowedAuthProviders: []string{"ec2_iam_role"}, AWSAssumeRoleEnabled: true}) e.DataSource = fakeDataSource() e.DataSource.JsonData.Set("authType", "ec2_iam_role") diff --git a/pkg/tsdb/cloudwatch/test_utils.go b/pkg/tsdb/cloudwatch/test_utils.go index 193aeddad9f..8f5725a4e90 100644 --- a/pkg/tsdb/cloudwatch/test_utils.go +++ b/pkg/tsdb/cloudwatch/test_utils.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" ) type fakeDataSourceCfg struct { @@ -145,3 +146,7 @@ func (c fakeRGTAClient) GetResourcesPages(in *resourcegroupstaggingapi.GetResour }, true) return nil } + +func newTestConfig() *setting.Cfg { + return &setting.Cfg{AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true} +} diff --git a/pkg/tsdb/cloudwatch/time_series_query_test.go b/pkg/tsdb/cloudwatch/time_series_query_test.go index ec66ec8db19..2910c314d0b 100644 --- a/pkg/tsdb/cloudwatch/time_series_query_test.go +++ b/pkg/tsdb/cloudwatch/time_series_query_test.go @@ -9,7 +9,7 @@ import ( ) func TestTimeSeriesQuery(t *testing.T) { - executor := newExecutor(nil) + executor := newExecutor(nil, newTestConfig()) t.Run("End time before start time should result in error", func(t *testing.T) { timeRange := plugins.NewDataTimeRange("now-1h", "now-2h")