CloudWatch: Restrict auth provider and assume role usage according to Grafana configuration (#31805)

* restrict usage of providers and assume role according to grafana config

* update docs

* Update docs/sources/datasources/cloudwatch.md

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/tsdb/cloudwatch/cloudwatch.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/tsdb/cloudwatch/session_test.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* pr feedback

* fix failing test

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
Erik Sundell 2021-03-09 21:20:59 +01:00 committed by GitHub
parent 13dd9dff50
commit 2d660ee502
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 105 additions and 25 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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" >}})

View File

@ -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)

View File

@ -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{
{

View File

@ -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{
{

View File

@ -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{
{

View File

@ -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")

View File

@ -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}
}

View File

@ -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")