grafana/pkg/tsdb/cloudwatch/credentials.go
Carl Bergquist ea2f65b4c3
Merge pull request #10110 from bwhaley/master
Fixes bug in cloudwatch regarding credentials
2017-12-12 10:16:53 +01:00

203 lines
5.3 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/defaults"
"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`
d := defaults.Get()
return endpointcreds.NewProviderClient(
*d.Config,
d.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
}