Commit 3ef06a0c by Dhananjay Committed by GitHub

Cloudwatch: Add Support for external ID in assume role (#23685)

Co-authored by: Arve Knudsen
parent 2d4bcbef
...@@ -34,6 +34,7 @@ build dashboards or use Explore with CloudWatch metrics and CloudWatch Logs. ...@@ -34,6 +34,7 @@ build dashboards or use Explore with CloudWatch metrics and CloudWatch Logs.
| _Auth Provider_ | Specify the provider to get credentials. | | _Auth Provider_ | Specify the provider to get credentials. |
| _Credentials_ profile name | Specify the name of the profile to use (if you use `~/.aws/credentials` file), leave blank for default. | | _Credentials_ profile name | Specify the name of the profile to use (if you use `~/.aws/credentials` file), leave blank for default. |
| _Assume Role Arn_ | Specify the ARN of the role to assume | | _Assume Role Arn_ | Specify the ARN of the role to assume |
| _External ID_ | If you are assuming a role in another account, that has been created with an external ID, specify the exterrnal ID here. |
## Authentication ## Authentication
......
...@@ -25,6 +25,7 @@ require ( ...@@ -25,6 +25,7 @@ require (
github.com/go-sql-driver/mysql v1.5.0 github.com/go-sql-driver/mysql v1.5.0
github.com/go-stack/stack v1.8.0 github.com/go-stack/stack v1.8.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/golang/mock v1.4.3
github.com/golang/protobuf v1.4.0 github.com/golang/protobuf v1.4.0
github.com/google/go-cmp v0.4.0 github.com/google/go-cmp v0.4.0
github.com/gorilla/websocket v1.4.1 github.com/gorilla/websocket v1.4.1
......
...@@ -108,7 +108,10 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a ...@@ -108,7 +108,10 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
...@@ -437,6 +440,7 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w ...@@ -437,6 +440,7 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
...@@ -450,6 +454,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 ...@@ -450,6 +454,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190802220118-1d1727260058/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190802220118-1d1727260058/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
...@@ -526,6 +531,8 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh ...@@ -526,6 +531,8 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8=
xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
......
...@@ -32,6 +32,7 @@ type DatasourceInfo struct { ...@@ -32,6 +32,7 @@ type DatasourceInfo struct {
Region string Region string
AuthType string AuthType string
AssumeRoleArn string AssumeRoleArn string
ExternalID string
Namespace string Namespace string
AccessKey string AccessKey string
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/aws/aws-sdk-go/aws" "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"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/credentials/endpointcreds" "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds"
...@@ -18,6 +19,7 @@ import ( ...@@ -18,6 +19,7 @@ import (
"github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/sts" "github.com/aws/aws-sdk-go/service/sts"
"github.com/aws/aws-sdk-go/service/sts/stsiface"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
...@@ -30,7 +32,25 @@ type cache struct { ...@@ -30,7 +32,25 @@ type cache struct {
var awsCredentialCache = make(map[string]cache) var awsCredentialCache = make(map[string]cache)
var credentialCacheLock sync.RWMutex var credentialCacheLock sync.RWMutex
func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { // Session factory.
// Stubbable by tests.
var newSession = func(cfgs ...*aws.Config) (*session.Session, error) {
return session.NewSession(cfgs...)
}
// STS service factory.
// Stubbable by tests.
var newSTSService = func(p client.ConfigProvider, cfgs ...*aws.Config) stsiface.STSAPI {
return sts.New(p, cfgs...)
}
// EC2Metadata service factory.
// Stubbable by tests.
var newEC2Metadata = func(p client.ConfigProvider, cfgs ...*aws.Config) *ec2metadata.EC2Metadata {
return ec2metadata.New(p, cfgs...)
}
func getCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) {
cacheKey := fmt.Sprintf("%s:%s:%s:%s", dsInfo.AuthType, dsInfo.AccessKey, dsInfo.Profile, dsInfo.AssumeRoleArn) cacheKey := fmt.Sprintf("%s:%s:%s:%s", dsInfo.AuthType, dsInfo.AccessKey, dsInfo.Profile, dsInfo.AssumeRoleArn)
credentialCacheLock.RLock() credentialCacheLock.RLock()
if _, ok := awsCredentialCache[cacheKey]; ok { if _, ok := awsCredentialCache[cacheKey]; ok {
...@@ -53,8 +73,11 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { ...@@ -53,8 +73,11 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) {
RoleSessionName: aws.String("GrafanaSession"), RoleSessionName: aws.String("GrafanaSession"),
DurationSeconds: aws.Int64(900), DurationSeconds: aws.Int64(900),
} }
if dsInfo.ExternalID != "" {
params.ExternalId = aws.String(dsInfo.ExternalID)
}
stsSess, err := session.NewSession() stsSess, err := newSession()
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -70,11 +93,11 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { ...@@ -70,11 +93,11 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) {
Credentials: stsCreds, Credentials: stsCreds,
} }
sess, err := session.NewSession(stsConfig) sess, err := newSession(stsConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
svc := sts.New(sess, stsConfig) svc := newSTSService(sess, stsConfig)
resp, err := svc.AssumeRole(params) resp, err := svc.AssumeRole(params)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -91,7 +114,7 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { ...@@ -91,7 +114,7 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) {
expiration = &e expiration = &e
} }
sess, err := session.NewSession() sess, err := newSession()
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -123,7 +146,7 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { ...@@ -123,7 +146,7 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) {
} }
func webIdentityProvider(sess *session.Session) credentials.Provider { func webIdentityProvider(sess *session.Session) credentials.Provider {
svc := sts.New(sess) svc := newSTSService(sess)
roleARN := os.Getenv("AWS_ROLE_ARN") roleARN := os.Getenv("AWS_ROLE_ARN")
tokenFilepath := os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE") tokenFilepath := os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE")
...@@ -152,7 +175,7 @@ func ecsCredProvider(sess *session.Session, uri string) credentials.Provider { ...@@ -152,7 +175,7 @@ func ecsCredProvider(sess *session.Session, uri string) credentials.Provider {
} }
func ec2RoleProvider(sess *session.Session) credentials.Provider { func ec2RoleProvider(sess *session.Session) credentials.Provider {
return &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute} return &ec2rolecreds.EC2RoleProvider{Client: newEC2Metadata(sess), ExpiryWindow: 5 * time.Minute}
} }
func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo { func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo {
...@@ -167,6 +190,7 @@ func retrieveDsInfo(datasource *models.DataSource, region string) *DatasourceInf ...@@ -167,6 +190,7 @@ func retrieveDsInfo(datasource *models.DataSource, region string) *DatasourceInf
authType := datasource.JsonData.Get("authType").MustString() authType := datasource.JsonData.Get("authType").MustString()
assumeRoleArn := datasource.JsonData.Get("assumeRoleArn").MustString() assumeRoleArn := datasource.JsonData.Get("assumeRoleArn").MustString()
externalID := datasource.JsonData.Get("externalId").MustString()
decrypted := datasource.DecryptedValues() decrypted := datasource.DecryptedValues()
accessKey := decrypted["accessKey"] accessKey := decrypted["accessKey"]
secretKey := decrypted["secretKey"] secretKey := decrypted["secretKey"]
...@@ -176,6 +200,7 @@ func retrieveDsInfo(datasource *models.DataSource, region string) *DatasourceInf ...@@ -176,6 +200,7 @@ func retrieveDsInfo(datasource *models.DataSource, region string) *DatasourceInf
Profile: datasource.Database, Profile: datasource.Database,
AuthType: authType, AuthType: authType,
AssumeRoleArn: assumeRoleArn, AssumeRoleArn: assumeRoleArn,
ExternalID: externalID,
AccessKey: accessKey, AccessKey: accessKey,
SecretKey: secretKey, SecretKey: secretKey,
} }
...@@ -184,7 +209,7 @@ func retrieveDsInfo(datasource *models.DataSource, region string) *DatasourceInf ...@@ -184,7 +209,7 @@ func retrieveDsInfo(datasource *models.DataSource, region string) *DatasourceInf
} }
func getAwsConfig(dsInfo *DatasourceInfo) (*aws.Config, error) { func getAwsConfig(dsInfo *DatasourceInfo) (*aws.Config, error) {
creds, err := GetCredentials(dsInfo) creds, err := getCredentials(dsInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -204,7 +229,7 @@ func (e *CloudWatchExecutor) getClient(region string) (*cloudwatch.CloudWatch, e ...@@ -204,7 +229,7 @@ func (e *CloudWatchExecutor) getClient(region string) (*cloudwatch.CloudWatch, e
return nil, err return nil, err
} }
sess, err := session.NewSession(cfg) sess, err := newSession(cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -224,7 +249,7 @@ func retrieveLogsClient(datasourceInfo *DatasourceInfo) (*cloudwatchlogs.CloudWa ...@@ -224,7 +249,7 @@ func retrieveLogsClient(datasourceInfo *DatasourceInfo) (*cloudwatchlogs.CloudWa
return nil, err return nil, err
} }
sess, err := session.NewSession(cfg) sess, err := newSession(cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -4,39 +4,120 @@ import ( ...@@ -4,39 +4,120 @@ import (
"os" "os"
"testing" "testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/credentials/endpointcreds" "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
. "github.com/smartystreets/goconvey/convey" "github.com/aws/aws-sdk-go/service/sts"
"github.com/aws/aws-sdk-go/service/sts/stsiface"
"github.com/golang/mock/gomock"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/mock_stsiface"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestECSCredProvider(t *testing.T) { func TestECSCredProvider(t *testing.T) {
Convey("Running in an ECS container task", t, func() {
defer os.Clearenv()
os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/abc/123") os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/abc/123")
t.Cleanup(func() {
os.Unsetenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
})
sess, _ := session.NewSession() sess, err := session.NewSession()
require.NoError(t, err)
provider := remoteCredProvider(sess) provider := remoteCredProvider(sess)
require.NotNil(t, provider)
So(provider, ShouldNotBeNil)
ecsProvider, ok := provider.(*endpointcreds.Provider) ecsProvider, ok := provider.(*endpointcreds.Provider)
So(ecsProvider, ShouldNotBeNil) require.NotNil(t, ecsProvider)
So(ok, ShouldBeTrue) require.True(t, ok)
So(ecsProvider.Client.Endpoint, ShouldEqual, "http://169.254.170.2/abc/123") assert.Equal(t, "http://169.254.170.2/abc/123", ecsProvider.Client.Endpoint)
})
} }
func TestDefaultEC2RoleProvider(t *testing.T) { func TestDefaultEC2RoleProvider(t *testing.T) {
Convey("Running outside an ECS container task", t, func() { sess, err := session.NewSession()
sess, _ := session.NewSession() require.NoError(t, err)
provider := remoteCredProvider(sess) provider := remoteCredProvider(sess)
require.NotNil(t, provider)
So(provider, ShouldNotBeNil)
ec2Provider, ok := provider.(*ec2rolecreds.EC2RoleProvider) ec2Provider, ok := provider.(*ec2rolecreds.EC2RoleProvider)
So(ec2Provider, ShouldNotBeNil) require.NotNil(t, ec2Provider)
So(ok, ShouldBeTrue) require.True(t, ok)
}
func TestGetCredentials_ARNAuthType(t *testing.T) {
ctrl := gomock.NewController(t)
var stsMock *mock_stsiface.MockSTSAPI
origNewSession := newSession
origNewSTSService := newSTSService
origNewEC2Metadata := newEC2Metadata
t.Cleanup(func() {
newSession = origNewSession
newSTSService = origNewSTSService
newEC2Metadata = origNewEC2Metadata
})
newSession = func(cfgs ...*aws.Config) (*session.Session, error) {
return &session.Session{}, nil
}
newSTSService = func(p client.ConfigProvider, cfgs ...*aws.Config) stsiface.STSAPI {
return stsMock
}
newEC2Metadata = func(p client.ConfigProvider, cfgs ...*aws.Config) *ec2metadata.EC2Metadata {
return nil
}
t.Run("Without external ID", func(t *testing.T) {
stsMock = mock_stsiface.NewMockSTSAPI(ctrl)
stsMock.
EXPECT().
AssumeRole(gomock.Eq(&sts.AssumeRoleInput{
RoleArn: aws.String(""),
DurationSeconds: aws.Int64(900),
RoleSessionName: aws.String("GrafanaSession"),
})).
Return(&sts.AssumeRoleOutput{
Credentials: &sts.Credentials{
AccessKeyId: aws.String("id"),
SecretAccessKey: aws.String("secret"),
SessionToken: aws.String("token"),
},
}, nil).
Times(1)
creds, err := getCredentials(&DatasourceInfo{
AuthType: "arn",
})
require.NoError(t, err)
require.NotNil(t, creds)
})
t.Run("With external ID", func(t *testing.T) {
stsMock = mock_stsiface.NewMockSTSAPI(ctrl)
stsMock.
EXPECT().
AssumeRole(gomock.Eq(&sts.AssumeRoleInput{
RoleArn: aws.String(""),
DurationSeconds: aws.Int64(900),
RoleSessionName: aws.String("GrafanaSession"),
ExternalId: aws.String("external-id"),
})).
Return(&sts.AssumeRoleOutput{
Credentials: &sts.Credentials{
AccessKeyId: aws.String("id"),
SecretAccessKey: aws.String("secret"),
SessionToken: aws.String("token"),
},
}, nil).
Times(1)
creds, err := getCredentials(&DatasourceInfo{
AuthType: "arn",
ExternalID: "external-id",
})
require.NoError(t, err)
require.NotNil(t, creds)
}) })
} }
...@@ -740,7 +740,7 @@ func (e *CloudWatchExecutor) resourceGroupsGetResources(region string, filters [ ...@@ -740,7 +740,7 @@ func (e *CloudWatchExecutor) resourceGroupsGetResources(region string, filters [
} }
func getAllMetrics(cwData *DatasourceInfo) (cloudwatch.ListMetricsOutput, error) { func getAllMetrics(cwData *DatasourceInfo) (cloudwatch.ListMetricsOutput, error) {
creds, err := GetCredentials(cwData) creds, err := getCredentials(cwData)
if err != nil { if err != nil {
return cloudwatch.ListMetricsOutput{}, err return cloudwatch.ListMetricsOutput{}, err
} }
......
...@@ -42,6 +42,7 @@ const setup = (propOverrides?: object) => { ...@@ -42,6 +42,7 @@ const setup = (propOverrides?: object) => {
}, },
jsonData: { jsonData: {
assumeRoleArn: '', assumeRoleArn: '',
externalId: '',
database: '', database: '',
customMetricsNamespaces: '', customMetricsNamespaces: '',
authType: 'keys', authType: 'keys',
......
...@@ -130,6 +130,7 @@ export class ConfigEditor extends PureComponent<Props, State> { ...@@ -130,6 +130,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
onChange={option => { onChange={option => {
if (options.jsonData.authType === 'arn' && option.value !== 'arn') { if (options.jsonData.authType === 'arn' && option.value !== 'arn') {
delete this.props.options.jsonData.assumeRoleArn; delete this.props.options.jsonData.assumeRoleArn;
delete this.props.options.jsonData.externalId;
} }
onUpdateDatasourceJsonDataOptionSelect(this.props, 'authType')(option); onUpdateDatasourceJsonDataOptionSelect(this.props, 'authType')(option);
}} }}
...@@ -239,6 +240,22 @@ export class ConfigEditor extends PureComponent<Props, State> { ...@@ -239,6 +240,22 @@ export class ConfigEditor extends PureComponent<Props, State> {
/> />
</div> </div>
</div> </div>
<div className="gf-form">
<InlineFormLabel
className="width-14"
tooltip="If you are assuming a role in another account, that has been created with an external ID, specify the external ID here."
>
External ID
</InlineFormLabel>
<div className="width-30">
<Input
className="width-30"
placeholder="External ID"
value={options.jsonData.externalId || ''}
onChange={onUpdateDatasourceJsonDataOption(this.props, 'externalId')}
/>
</div>
</div>
</div> </div>
)} )}
<div className="gf-form-inline"> <div className="gf-form-inline">
......
...@@ -59,6 +59,7 @@ export type SelectableStrings = Array<SelectableValue<string>>; ...@@ -59,6 +59,7 @@ export type SelectableStrings = Array<SelectableValue<string>>;
export interface CloudWatchJsonData extends DataSourceJsonData { export interface CloudWatchJsonData extends DataSourceJsonData {
timeField?: string; timeField?: string;
assumeRoleArn?: string; assumeRoleArn?: string;
externalId?: string;
database?: string; database?: string;
customMetricsNamespaces?: string; customMetricsNamespaces?: string;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment