Files
grafana/pkg/tsdb/cloudwatch/credentials.go
Mikael Olenfalk 8e7166b5c4 Explicitly specify default region in CloudWatch datasource (#9440)
The datasource uses the default region in the query if the region
is "" in the settings. However setting the region to an empty string
is almost impossible and rendered incorrectly.

This commit introduces a special value "default" for region which
is shown in the drop down and is translated to the default region
of the data source when performing queries.
2017-12-11 09:37:27 +01:00

202 lines
5.2 KiB
Go

package cloudwatch
import (
"fmt"
"os"
"strings"
"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/endpointcreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/sts"
)
type cache struct {
credential *credentials.Credentials
expiration *time.Time
}
var awsCredentialCache map[string]cache = make(map[string]cache)
var credentialCacheLock sync.RWMutex
func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) {
cacheKey := dsInfo.AccessKey + ":" + dsInfo.Profile + ":" + dsInfo.AssumeRoleArn
credentialCacheLock.RLock()
if _, ok := awsCredentialCache[cacheKey]; ok {
if awsCredentialCache[cacheKey].expiration != nil &&
(*awsCredentialCache[cacheKey].expiration).After(time.Now().UTC()) {
result := awsCredentialCache[cacheKey].credential
credentialCacheLock.RUnlock()
return result, nil
}
}
credentialCacheLock.RUnlock()
accessKeyId := ""
secretAccessKey := ""
sessionToken := ""
var expiration *time.Time
expiration = nil
if dsInfo.AuthType == "arn" && strings.Index(dsInfo.AssumeRoleArn, "arn:aws:iam:") == 0 {
params := &sts.AssumeRoleInput{
RoleArn: aws.String(dsInfo.AssumeRoleArn),
RoleSessionName: aws.String("GrafanaSession"),
DurationSeconds: aws.Int64(900),
}
stsSess, err := session.NewSession()
if err != nil {
return nil, err
}
stsCreds := credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: dsInfo.Profile},
remoteCredProvider(stsSess),
})
stsConfig := &aws.Config{
Region: aws.String(dsInfo.Region),
Credentials: stsCreds,
}
sess, err := session.NewSession(stsConfig)
if err != nil {
return nil, err
}
svc := sts.New(sess, stsConfig)
resp, err := svc.AssumeRole(params)
if err != nil {
return nil, err
}
if resp.Credentials != nil {
accessKeyId = *resp.Credentials.AccessKeyId
secretAccessKey = *resp.Credentials.SecretAccessKey
sessionToken = *resp.Credentials.SessionToken
expiration = resp.Credentials.Expiration
}
} else {
now := time.Now()
e := now.Add(5 * time.Minute)
expiration = &e
}
sess, err := session.NewSession()
if err != nil {
return nil, err
}
creds := credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.StaticProvider{Value: credentials.Value{
AccessKeyID: accessKeyId,
SecretAccessKey: secretAccessKey,
SessionToken: sessionToken,
}},
&credentials.EnvProvider{},
&credentials.StaticProvider{Value: credentials.Value{
AccessKeyID: dsInfo.AccessKey,
SecretAccessKey: dsInfo.SecretKey,
}},
&credentials.SharedCredentialsProvider{Filename: "", Profile: dsInfo.Profile},
remoteCredProvider(sess),
})
credentialCacheLock.Lock()
awsCredentialCache[cacheKey] = cache{
credential: creds,
expiration: expiration,
}
credentialCacheLock.Unlock()
return creds, nil
}
func remoteCredProvider(sess *session.Session) credentials.Provider {
ecsCredURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
if len(ecsCredURI) > 0 {
return ecsCredProvider(sess, ecsCredURI)
}
return ec2RoleProvider(sess)
}
func ecsCredProvider(sess *session.Session, uri string) credentials.Provider {
const host = `169.254.170.2`
c := ec2metadata.New(sess)
return endpointcreds.NewProviderClient(
c.Client.Config,
c.Client.Handlers,
fmt.Sprintf("http://%s%s", host, uri),
func(p *endpointcreds.Provider) { p.ExpiryWindow = 5 * time.Minute })
}
func ec2RoleProvider(sess *session.Session) credentials.Provider {
return &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}
}
func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo {
defaultRegion := e.DataSource.JsonData.Get("defaultRegion").MustString()
if region == "default" {
region = defaultRegion
}
authType := e.DataSource.JsonData.Get("authType").MustString()
assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString()
accessKey := ""
secretKey := ""
for key, value := range e.DataSource.SecureJsonData.Decrypt() {
if key == "accessKey" {
accessKey = value
}
if key == "secretKey" {
secretKey = value
}
}
datasourceInfo := &DatasourceInfo{
Region: region,
Profile: e.DataSource.Database,
AuthType: authType,
AssumeRoleArn: assumeRoleArn,
AccessKey: accessKey,
SecretKey: secretKey,
}
return datasourceInfo
}
func (e *CloudWatchExecutor) getAwsConfig(dsInfo *DatasourceInfo) (*aws.Config, error) {
creds, err := GetCredentials(dsInfo)
if err != nil {
return nil, err
}
cfg := &aws.Config{
Region: aws.String(dsInfo.Region),
Credentials: creds,
}
return cfg, nil
}
func (e *CloudWatchExecutor) getClient(region string) (*cloudwatch.CloudWatch, error) {
datasourceInfo := e.getDsInfo(region)
cfg, err := e.getAwsConfig(datasourceInfo)
if err != nil {
return nil, err
}
sess, err := session.NewSession(cfg)
if err != nil {
return nil, err
}
client := cloudwatch.New(sess, cfg)
return client, nil
}