mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
13dd9dff50
commit
2d660ee502
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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" >}})
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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{
|
||||
{
|
||||
|
@ -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{
|
||||
{
|
||||
|
@ -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{
|
||||
{
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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}
|
||||
}
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user