mirror of
https://github.com/grafana/grafana.git
synced 2026-07-30 08:18:10 -05:00
CloudWatch: Re-implement authentication (#25548)
* CloudWatch: Revisit authentication Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Simplify auth code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Use ARN Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Add Drone configuration Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove unused code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove .drone.yml Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix external ID usage Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Fix issues after merge Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove stale code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove stale code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Use auth type enum Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test snapshot * Coordinate frontend and backend option names Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove old comments Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix front-end tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Introduce session cache Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Use constants Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix field alignment * CloudWatch: Fix log message Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Tidy go.mod Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Handle arn auth type Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Fix role assumption duration Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Inline unnecessary constants Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Use serial comma in UI Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Inline unnecessary constants Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Fail if missing region Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Handle unconfigured region Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Log when using cached session Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Include region in cache key Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Add UI warnings for lecagy support * Do not clear ARN fields whenging change authentication provider * Graph NG: annotations display (#27972) * Annotations support POC * Fix markers memoization * dev dashboard update * Update public/app/plugins/panel/graph3/plugins/AnnotationsPlugin.tsx * CloudWatch: Remove errors.BadRequest Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Undo unintentional change Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove log line Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix cache key computation Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Add region to cache key Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Improve log messages Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CloudWatch: Add documentation Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Improve tooltip Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Improve docs Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Improve docs Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Improve docs Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Improve tooltip Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Add role assumption provisioning example Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Add upgrade notes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Improve docs Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Apply suggestions from code review Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * backend: use latest sdk (#28147) fixes #27713 via https://github.com/grafana/grafana-plugin-sdk-go/pull/227 * Docs: Update Permissions documentation (#28144) * removed overview.md * content updates * Update datasource_permissions.md * update content * content updates * Update organization_roles.md * Update docs/sources/enterprise/saml.md Co-authored-by: Kyle Brandt <kyle@grafana.com> * Update dashboard_folder_permissions.md Co-authored-by: Kyle Brandt <kyle@grafana.com> * area/grafana/toolkit: ci-package needs to use synchronous writes (#28148) * ci needs to use synchronous writes or the file ends up with zero length * <Enterprise Docs> Add instructions to upload license via UI (#28067) * Add UI license upload option, reformat Enterprise license activation section Added the option to upload a license file through the Server Admin UI, and did a little reformatting to make license activation look more like a process. * Headers not bold, hyphens not asterisks * Github: run metrics collector workflow every 10min (#28153) * GithubActions: Updated cron schedule * Updated * Docs: Update explore docs: remove dot at the end of line (#28151) HI - Removed Dot(.) at the end of line to make it consistent with other 2 points. Thanks, Ashish * Fix frontend tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix frontend tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Docs: Update upgrade notes Co-authored-by: Sofia Papagiannaki <sofia@grafana.com> Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> Co-authored-by: Kyle Brandt <kyle@grafana.com> Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Co-authored-by: Brian Gann <briangann@users.noreply.github.com> Co-authored-by: Mitch Seaman <mjseaman@users.noreply.github.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.org> Co-authored-by: Torkel Ödegaard <torkel@grafana.com> Co-authored-by: ashishagarwal06 <34888589+ashishagarwal06@users.noreply.github.com>
This commit is contained in:
co-authored by
Sofia Papagiannaki
Dominik Prokop
Marcus Efraimsson
Kyle Brandt
Diana Payton
Brian Gann
Mitch Seaman
Torkel Ödegaard
Torkel Ödegaard
ashishagarwal06
parent
519ec93c7d
commit
957c88eaca
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
|
||||
"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/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
@@ -31,8 +34,8 @@ import (
|
||||
type datasourceInfo struct {
|
||||
Profile string
|
||||
Region string
|
||||
AuthType string
|
||||
AssumeRoleArn string
|
||||
AuthType authType
|
||||
AssumeRoleARN string
|
||||
ExternalID string
|
||||
Namespace string
|
||||
|
||||
@@ -74,16 +77,105 @@ type cloudWatchExecutor struct {
|
||||
|
||||
func (e *cloudWatchExecutor) newSession(region string) (*session.Session, error) {
|
||||
dsInfo := e.getDSInfo(region)
|
||||
creds, err := getCredentials(dsInfo)
|
||||
|
||||
bldr := strings.Builder{}
|
||||
for i, s := range []string{
|
||||
dsInfo.AuthType.String(), dsInfo.AccessKey, dsInfo.Profile, dsInfo.AssumeRoleARN, region,
|
||||
} {
|
||||
if i != 0 {
|
||||
bldr.WriteString(":")
|
||||
}
|
||||
bldr.WriteString(strings.ReplaceAll(s, ":", `\:`))
|
||||
}
|
||||
cacheKey := bldr.String()
|
||||
|
||||
sessCacheLock.RLock()
|
||||
if env, ok := sessCache[cacheKey]; ok {
|
||||
if env.expiration.After(time.Now().UTC()) {
|
||||
sessCacheLock.RUnlock()
|
||||
return env.session, nil
|
||||
}
|
||||
}
|
||||
sessCacheLock.RUnlock()
|
||||
|
||||
cfgs := []*aws.Config{
|
||||
{
|
||||
CredentialsChainVerboseErrors: aws.Bool(true),
|
||||
},
|
||||
}
|
||||
|
||||
var regionCfg *aws.Config
|
||||
if dsInfo.Region == defaultRegion {
|
||||
plog.Warn("Region is set to \"default\", which is unsupported")
|
||||
dsInfo.Region = ""
|
||||
}
|
||||
if dsInfo.Region != "" {
|
||||
regionCfg = &aws.Config{Region: aws.String(dsInfo.Region)}
|
||||
cfgs = append(cfgs, regionCfg)
|
||||
}
|
||||
|
||||
switch dsInfo.AuthType {
|
||||
case authTypeSharedCreds:
|
||||
plog.Debug("Authenticating towards AWS with shared credentials", "profile", dsInfo.Profile,
|
||||
"region", dsInfo.Region)
|
||||
cfgs = append(cfgs, &aws.Config{
|
||||
Credentials: credentials.NewSharedCredentials("", dsInfo.Profile),
|
||||
})
|
||||
case authTypeKeys:
|
||||
plog.Debug("Authenticating towards AWS with an access key pair", "region", dsInfo.Region)
|
||||
cfgs = append(cfgs, &aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(dsInfo.AccessKey, dsInfo.SecretKey, ""),
|
||||
})
|
||||
case authTypeDefault:
|
||||
plog.Debug("Authenticating towards AWS with default SDK method", "region", dsInfo.Region)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unrecognized authType: %d", dsInfo.AuthType))
|
||||
}
|
||||
sess, err := newSession(cfgs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &aws.Config{
|
||||
Region: aws.String(dsInfo.Region),
|
||||
Credentials: creds,
|
||||
duration := stscreds.DefaultDuration
|
||||
expiration := time.Now().Add(duration)
|
||||
if dsInfo.AssumeRoleARN != "" {
|
||||
// We should assume a role in AWS
|
||||
plog.Debug("Trying to assume role in AWS", "arn", dsInfo.AssumeRoleARN)
|
||||
|
||||
cfgs := []*aws.Config{
|
||||
{
|
||||
CredentialsChainVerboseErrors: aws.Bool(true),
|
||||
},
|
||||
{
|
||||
Credentials: newSTSCredentials(sess, dsInfo.AssumeRoleARN, func(p *stscreds.AssumeRoleProvider) {
|
||||
// Not sure if this is necessary, overlaps with p.Duration and is undocumented
|
||||
p.Expiry.SetExpiration(expiration, 0)
|
||||
p.Duration = duration
|
||||
if dsInfo.ExternalID != "" {
|
||||
p.ExternalID = aws.String(dsInfo.ExternalID)
|
||||
}
|
||||
}),
|
||||
},
|
||||
}
|
||||
if regionCfg != nil {
|
||||
cfgs = append(cfgs, regionCfg)
|
||||
}
|
||||
sess, err = newSession(cfgs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return newSession(cfg)
|
||||
|
||||
plog.Debug("Successfully created AWS session")
|
||||
|
||||
sessCacheLock.Lock()
|
||||
sessCache[cacheKey] = envelope{
|
||||
session: sess,
|
||||
expiration: expiration,
|
||||
}
|
||||
sessCacheLock.Unlock()
|
||||
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
func (e *cloudWatchExecutor) getCWClient(region string) (cloudwatchiface.CloudWatchAPI, error) {
|
||||
@@ -282,18 +374,54 @@ func (e *cloudWatchExecutor) executeLogAlertQuery(ctx context.Context, queryCont
|
||||
return response, nil
|
||||
}
|
||||
|
||||
type authType int
|
||||
|
||||
const (
|
||||
authTypeDefault authType = iota
|
||||
authTypeSharedCreds
|
||||
authTypeKeys
|
||||
)
|
||||
|
||||
func (at authType) String() string {
|
||||
switch at {
|
||||
case authTypeDefault:
|
||||
return "default"
|
||||
case authTypeSharedCreds:
|
||||
return "sharedCreds"
|
||||
case authTypeKeys:
|
||||
return "keys"
|
||||
default:
|
||||
panic(fmt.Sprintf("Unrecognized auth type %d", at))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *cloudWatchExecutor) getDSInfo(region string) *datasourceInfo {
|
||||
if region == defaultRegion {
|
||||
region = e.DataSource.JsonData.Get("defaultRegion").MustString()
|
||||
}
|
||||
|
||||
authType := e.DataSource.JsonData.Get("authType").MustString()
|
||||
assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString()
|
||||
atStr := e.DataSource.JsonData.Get("authType").MustString()
|
||||
assumeRoleARN := e.DataSource.JsonData.Get("assumeRoleArn").MustString()
|
||||
externalID := e.DataSource.JsonData.Get("externalId").MustString()
|
||||
decrypted := e.DataSource.DecryptedValues()
|
||||
accessKey := decrypted["accessKey"]
|
||||
secretKey := decrypted["secretKey"]
|
||||
|
||||
at := authTypeDefault
|
||||
switch atStr {
|
||||
case "credentials":
|
||||
at = authTypeSharedCreds
|
||||
case "keys":
|
||||
at = authTypeKeys
|
||||
case "default":
|
||||
at = authTypeDefault
|
||||
case "arn":
|
||||
at = authTypeDefault
|
||||
plog.Warn("Authentication type \"arn\" is deprecated, falling back to default")
|
||||
default:
|
||||
plog.Warn("Unrecognized AWS authentication type", "type", atStr)
|
||||
}
|
||||
|
||||
profile := e.DataSource.JsonData.Get("profile").MustString()
|
||||
if profile == "" {
|
||||
profile = e.DataSource.Database // legacy support
|
||||
@@ -302,8 +430,8 @@ func (e *cloudWatchExecutor) getDSInfo(region string) *datasourceInfo {
|
||||
return &datasourceInfo{
|
||||
Region: region,
|
||||
Profile: profile,
|
||||
AuthType: authType,
|
||||
AssumeRoleArn: assumeRoleArn,
|
||||
AuthType: at,
|
||||
AssumeRoleARN: assumeRoleARN,
|
||||
ExternalID: externalID,
|
||||
AccessKey: accessKey,
|
||||
SecretKey: secretKey,
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
package cloudwatch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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/ec2rolecreds"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/endpointcreds"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"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/sts"
|
||||
"github.com/aws/aws-sdk-go/service/sts/stsiface"
|
||||
)
|
||||
|
||||
type envelope struct {
|
||||
credentials *credentials.Credentials
|
||||
expiration *time.Time
|
||||
}
|
||||
|
||||
var awsCredsCache = map[string]envelope{}
|
||||
var credsCacheLock sync.RWMutex
|
||||
|
||||
// Session factory.
|
||||
// Stubbable by tests.
|
||||
//nolint:gocritic
|
||||
var newSession = func(cfgs ...*aws.Config) (*session.Session, error) {
|
||||
return session.NewSession(cfgs...)
|
||||
}
|
||||
|
||||
// STS service factory.
|
||||
// Stubbable by tests.
|
||||
//nolint:gocritic
|
||||
var newSTSService = func(p client.ConfigProvider, cfgs ...*aws.Config) stsiface.STSAPI {
|
||||
return sts.New(p, cfgs...)
|
||||
}
|
||||
|
||||
// EC2Metadata service factory.
|
||||
// Stubbable by tests.
|
||||
//nolint:gocritic
|
||||
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)
|
||||
credsCacheLock.RLock()
|
||||
if env, ok := awsCredsCache[cacheKey]; ok {
|
||||
if env.expiration != nil && env.expiration.After(time.Now().UTC()) {
|
||||
result := env.credentials
|
||||
credsCacheLock.RUnlock()
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
credsCacheLock.RUnlock()
|
||||
|
||||
accessKeyID := ""
|
||||
secretAccessKey := ""
|
||||
sessionToken := ""
|
||||
var expiration *time.Time = nil
|
||||
if dsInfo.AuthType == "arn" {
|
||||
params := &sts.AssumeRoleInput{
|
||||
RoleArn: aws.String(dsInfo.AssumeRoleArn),
|
||||
RoleSessionName: aws.String("GrafanaSession"),
|
||||
DurationSeconds: aws.Int64(900),
|
||||
}
|
||||
if dsInfo.ExternalID != "" {
|
||||
params.ExternalId = aws.String(dsInfo.ExternalID)
|
||||
}
|
||||
|
||||
stsSess, err := newSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stsCreds := credentials.NewChainCredentials(
|
||||
[]credentials.Provider{
|
||||
&credentials.EnvProvider{},
|
||||
&credentials.SharedCredentialsProvider{Filename: "", Profile: dsInfo.Profile},
|
||||
webIdentityProvider(stsSess),
|
||||
remoteCredProvider(stsSess),
|
||||
})
|
||||
stsConfig := &aws.Config{
|
||||
Region: aws.String(dsInfo.Region),
|
||||
Credentials: stsCreds,
|
||||
}
|
||||
|
||||
sess, err := newSession(stsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
svc := newSTSService(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 := 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},
|
||||
webIdentityProvider(sess),
|
||||
remoteCredProvider(sess),
|
||||
})
|
||||
|
||||
credsCacheLock.Lock()
|
||||
awsCredsCache[cacheKey] = envelope{
|
||||
credentials: creds,
|
||||
expiration: expiration,
|
||||
}
|
||||
credsCacheLock.Unlock()
|
||||
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
func webIdentityProvider(sess client.ConfigProvider) credentials.Provider {
|
||||
svc := newSTSService(sess)
|
||||
|
||||
roleARN := os.Getenv("AWS_ROLE_ARN")
|
||||
tokenFilepath := os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE")
|
||||
roleSessionName := os.Getenv("AWS_ROLE_SESSION_NAME")
|
||||
return stscreds.NewWebIdentityRoleProvider(svc, roleARN, roleSessionName, tokenFilepath)
|
||||
}
|
||||
|
||||
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 client.ConfigProvider) credentials.Provider {
|
||||
return &ec2rolecreds.EC2RoleProvider{Client: newEC2Metadata(sess), ExpiryWindow: 5 * time.Minute}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package cloudwatch
|
||||
|
||||
import (
|
||||
"os"
|
||||
"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/endpointcreds"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"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) {
|
||||
os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/abc/123")
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
|
||||
})
|
||||
|
||||
sess, err := session.NewSession()
|
||||
require.NoError(t, err)
|
||||
provider := remoteCredProvider(sess)
|
||||
require.NotNil(t, provider)
|
||||
|
||||
ecsProvider, ok := provider.(*endpointcreds.Provider)
|
||||
require.NotNil(t, ecsProvider)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, "http://169.254.170.2/abc/123", ecsProvider.Client.Endpoint)
|
||||
}
|
||||
|
||||
func TestDefaultEC2RoleProvider(t *testing.T) {
|
||||
sess, err := session.NewSession()
|
||||
require.NoError(t, err)
|
||||
provider := remoteCredProvider(sess)
|
||||
require.NotNil(t, provider)
|
||||
|
||||
ec2Provider, ok := provider.(*ec2rolecreds.EC2RoleProvider)
|
||||
require.NotNil(t, ec2Provider)
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -1,436 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/aws/aws-sdk-go/service/sts/stsiface (interfaces: STSAPI)
|
||||
|
||||
// Package mock_stsiface is a generated GoMock package.
|
||||
package mock_stsiface
|
||||
|
||||
import (
|
||||
context "context"
|
||||
request "github.com/aws/aws-sdk-go/aws/request"
|
||||
sts "github.com/aws/aws-sdk-go/service/sts"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockSTSAPI is a mock of STSAPI interface
|
||||
type MockSTSAPI struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockSTSAPIMockRecorder
|
||||
}
|
||||
|
||||
// MockSTSAPIMockRecorder is the mock recorder for MockSTSAPI
|
||||
type MockSTSAPIMockRecorder struct {
|
||||
mock *MockSTSAPI
|
||||
}
|
||||
|
||||
// NewMockSTSAPI creates a new mock instance
|
||||
func NewMockSTSAPI(ctrl *gomock.Controller) *MockSTSAPI {
|
||||
mock := &MockSTSAPI{ctrl: ctrl}
|
||||
mock.recorder = &MockSTSAPIMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockSTSAPI) EXPECT() *MockSTSAPIMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AssumeRole mocks base method
|
||||
func (m *MockSTSAPI) AssumeRole(arg0 *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AssumeRole", arg0)
|
||||
ret0, _ := ret[0].(*sts.AssumeRoleOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AssumeRole indicates an expected call of AssumeRole
|
||||
func (mr *MockSTSAPIMockRecorder) AssumeRole(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRole", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRole), arg0)
|
||||
}
|
||||
|
||||
// AssumeRoleRequest mocks base method
|
||||
func (m *MockSTSAPI) AssumeRoleRequest(arg0 *sts.AssumeRoleInput) (*request.Request, *sts.AssumeRoleOutput) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AssumeRoleRequest", arg0)
|
||||
ret0, _ := ret[0].(*request.Request)
|
||||
ret1, _ := ret[1].(*sts.AssumeRoleOutput)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AssumeRoleRequest indicates an expected call of AssumeRoleRequest
|
||||
func (mr *MockSTSAPIMockRecorder) AssumeRoleRequest(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleRequest", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleRequest), arg0)
|
||||
}
|
||||
|
||||
// AssumeRoleWithContext mocks base method
|
||||
func (m *MockSTSAPI) AssumeRoleWithContext(arg0 context.Context, arg1 *sts.AssumeRoleInput, arg2 ...request.Option) (*sts.AssumeRoleOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "AssumeRoleWithContext", varargs...)
|
||||
ret0, _ := ret[0].(*sts.AssumeRoleOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AssumeRoleWithContext indicates an expected call of AssumeRoleWithContext
|
||||
func (mr *MockSTSAPIMockRecorder) AssumeRoleWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithContext", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithContext), varargs...)
|
||||
}
|
||||
|
||||
// AssumeRoleWithSAML mocks base method
|
||||
func (m *MockSTSAPI) AssumeRoleWithSAML(arg0 *sts.AssumeRoleWithSAMLInput) (*sts.AssumeRoleWithSAMLOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AssumeRoleWithSAML", arg0)
|
||||
ret0, _ := ret[0].(*sts.AssumeRoleWithSAMLOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AssumeRoleWithSAML indicates an expected call of AssumeRoleWithSAML
|
||||
func (mr *MockSTSAPIMockRecorder) AssumeRoleWithSAML(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithSAML", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithSAML), arg0)
|
||||
}
|
||||
|
||||
// AssumeRoleWithSAMLRequest mocks base method
|
||||
func (m *MockSTSAPI) AssumeRoleWithSAMLRequest(arg0 *sts.AssumeRoleWithSAMLInput) (*request.Request, *sts.AssumeRoleWithSAMLOutput) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AssumeRoleWithSAMLRequest", arg0)
|
||||
ret0, _ := ret[0].(*request.Request)
|
||||
ret1, _ := ret[1].(*sts.AssumeRoleWithSAMLOutput)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AssumeRoleWithSAMLRequest indicates an expected call of AssumeRoleWithSAMLRequest
|
||||
func (mr *MockSTSAPIMockRecorder) AssumeRoleWithSAMLRequest(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithSAMLRequest", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithSAMLRequest), arg0)
|
||||
}
|
||||
|
||||
// AssumeRoleWithSAMLWithContext mocks base method
|
||||
func (m *MockSTSAPI) AssumeRoleWithSAMLWithContext(arg0 context.Context, arg1 *sts.AssumeRoleWithSAMLInput, arg2 ...request.Option) (*sts.AssumeRoleWithSAMLOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "AssumeRoleWithSAMLWithContext", varargs...)
|
||||
ret0, _ := ret[0].(*sts.AssumeRoleWithSAMLOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AssumeRoleWithSAMLWithContext indicates an expected call of AssumeRoleWithSAMLWithContext
|
||||
func (mr *MockSTSAPIMockRecorder) AssumeRoleWithSAMLWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithSAMLWithContext", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithSAMLWithContext), varargs...)
|
||||
}
|
||||
|
||||
// AssumeRoleWithWebIdentity mocks base method
|
||||
func (m *MockSTSAPI) AssumeRoleWithWebIdentity(arg0 *sts.AssumeRoleWithWebIdentityInput) (*sts.AssumeRoleWithWebIdentityOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AssumeRoleWithWebIdentity", arg0)
|
||||
ret0, _ := ret[0].(*sts.AssumeRoleWithWebIdentityOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AssumeRoleWithWebIdentity indicates an expected call of AssumeRoleWithWebIdentity
|
||||
func (mr *MockSTSAPIMockRecorder) AssumeRoleWithWebIdentity(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithWebIdentity", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithWebIdentity), arg0)
|
||||
}
|
||||
|
||||
// AssumeRoleWithWebIdentityRequest mocks base method
|
||||
func (m *MockSTSAPI) AssumeRoleWithWebIdentityRequest(arg0 *sts.AssumeRoleWithWebIdentityInput) (*request.Request, *sts.AssumeRoleWithWebIdentityOutput) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AssumeRoleWithWebIdentityRequest", arg0)
|
||||
ret0, _ := ret[0].(*request.Request)
|
||||
ret1, _ := ret[1].(*sts.AssumeRoleWithWebIdentityOutput)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AssumeRoleWithWebIdentityRequest indicates an expected call of AssumeRoleWithWebIdentityRequest
|
||||
func (mr *MockSTSAPIMockRecorder) AssumeRoleWithWebIdentityRequest(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithWebIdentityRequest", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithWebIdentityRequest), arg0)
|
||||
}
|
||||
|
||||
// AssumeRoleWithWebIdentityWithContext mocks base method
|
||||
func (m *MockSTSAPI) AssumeRoleWithWebIdentityWithContext(arg0 context.Context, arg1 *sts.AssumeRoleWithWebIdentityInput, arg2 ...request.Option) (*sts.AssumeRoleWithWebIdentityOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "AssumeRoleWithWebIdentityWithContext", varargs...)
|
||||
ret0, _ := ret[0].(*sts.AssumeRoleWithWebIdentityOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AssumeRoleWithWebIdentityWithContext indicates an expected call of AssumeRoleWithWebIdentityWithContext
|
||||
func (mr *MockSTSAPIMockRecorder) AssumeRoleWithWebIdentityWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithWebIdentityWithContext", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithWebIdentityWithContext), varargs...)
|
||||
}
|
||||
|
||||
// DecodeAuthorizationMessage mocks base method
|
||||
func (m *MockSTSAPI) DecodeAuthorizationMessage(arg0 *sts.DecodeAuthorizationMessageInput) (*sts.DecodeAuthorizationMessageOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DecodeAuthorizationMessage", arg0)
|
||||
ret0, _ := ret[0].(*sts.DecodeAuthorizationMessageOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DecodeAuthorizationMessage indicates an expected call of DecodeAuthorizationMessage
|
||||
func (mr *MockSTSAPIMockRecorder) DecodeAuthorizationMessage(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeAuthorizationMessage", reflect.TypeOf((*MockSTSAPI)(nil).DecodeAuthorizationMessage), arg0)
|
||||
}
|
||||
|
||||
// DecodeAuthorizationMessageRequest mocks base method
|
||||
func (m *MockSTSAPI) DecodeAuthorizationMessageRequest(arg0 *sts.DecodeAuthorizationMessageInput) (*request.Request, *sts.DecodeAuthorizationMessageOutput) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DecodeAuthorizationMessageRequest", arg0)
|
||||
ret0, _ := ret[0].(*request.Request)
|
||||
ret1, _ := ret[1].(*sts.DecodeAuthorizationMessageOutput)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DecodeAuthorizationMessageRequest indicates an expected call of DecodeAuthorizationMessageRequest
|
||||
func (mr *MockSTSAPIMockRecorder) DecodeAuthorizationMessageRequest(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeAuthorizationMessageRequest", reflect.TypeOf((*MockSTSAPI)(nil).DecodeAuthorizationMessageRequest), arg0)
|
||||
}
|
||||
|
||||
// DecodeAuthorizationMessageWithContext mocks base method
|
||||
func (m *MockSTSAPI) DecodeAuthorizationMessageWithContext(arg0 context.Context, arg1 *sts.DecodeAuthorizationMessageInput, arg2 ...request.Option) (*sts.DecodeAuthorizationMessageOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "DecodeAuthorizationMessageWithContext", varargs...)
|
||||
ret0, _ := ret[0].(*sts.DecodeAuthorizationMessageOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DecodeAuthorizationMessageWithContext indicates an expected call of DecodeAuthorizationMessageWithContext
|
||||
func (mr *MockSTSAPIMockRecorder) DecodeAuthorizationMessageWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeAuthorizationMessageWithContext", reflect.TypeOf((*MockSTSAPI)(nil).DecodeAuthorizationMessageWithContext), varargs...)
|
||||
}
|
||||
|
||||
// GetAccessKeyInfo mocks base method
|
||||
func (m *MockSTSAPI) GetAccessKeyInfo(arg0 *sts.GetAccessKeyInfoInput) (*sts.GetAccessKeyInfoOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessKeyInfo", arg0)
|
||||
ret0, _ := ret[0].(*sts.GetAccessKeyInfoOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessKeyInfo indicates an expected call of GetAccessKeyInfo
|
||||
func (mr *MockSTSAPIMockRecorder) GetAccessKeyInfo(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessKeyInfo", reflect.TypeOf((*MockSTSAPI)(nil).GetAccessKeyInfo), arg0)
|
||||
}
|
||||
|
||||
// GetAccessKeyInfoRequest mocks base method
|
||||
func (m *MockSTSAPI) GetAccessKeyInfoRequest(arg0 *sts.GetAccessKeyInfoInput) (*request.Request, *sts.GetAccessKeyInfoOutput) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessKeyInfoRequest", arg0)
|
||||
ret0, _ := ret[0].(*request.Request)
|
||||
ret1, _ := ret[1].(*sts.GetAccessKeyInfoOutput)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessKeyInfoRequest indicates an expected call of GetAccessKeyInfoRequest
|
||||
func (mr *MockSTSAPIMockRecorder) GetAccessKeyInfoRequest(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessKeyInfoRequest", reflect.TypeOf((*MockSTSAPI)(nil).GetAccessKeyInfoRequest), arg0)
|
||||
}
|
||||
|
||||
// GetAccessKeyInfoWithContext mocks base method
|
||||
func (m *MockSTSAPI) GetAccessKeyInfoWithContext(arg0 context.Context, arg1 *sts.GetAccessKeyInfoInput, arg2 ...request.Option) (*sts.GetAccessKeyInfoOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "GetAccessKeyInfoWithContext", varargs...)
|
||||
ret0, _ := ret[0].(*sts.GetAccessKeyInfoOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessKeyInfoWithContext indicates an expected call of GetAccessKeyInfoWithContext
|
||||
func (mr *MockSTSAPIMockRecorder) GetAccessKeyInfoWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessKeyInfoWithContext", reflect.TypeOf((*MockSTSAPI)(nil).GetAccessKeyInfoWithContext), varargs...)
|
||||
}
|
||||
|
||||
// GetCallerIdentity mocks base method
|
||||
func (m *MockSTSAPI) GetCallerIdentity(arg0 *sts.GetCallerIdentityInput) (*sts.GetCallerIdentityOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCallerIdentity", arg0)
|
||||
ret0, _ := ret[0].(*sts.GetCallerIdentityOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetCallerIdentity indicates an expected call of GetCallerIdentity
|
||||
func (mr *MockSTSAPIMockRecorder) GetCallerIdentity(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCallerIdentity", reflect.TypeOf((*MockSTSAPI)(nil).GetCallerIdentity), arg0)
|
||||
}
|
||||
|
||||
// GetCallerIdentityRequest mocks base method
|
||||
func (m *MockSTSAPI) GetCallerIdentityRequest(arg0 *sts.GetCallerIdentityInput) (*request.Request, *sts.GetCallerIdentityOutput) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCallerIdentityRequest", arg0)
|
||||
ret0, _ := ret[0].(*request.Request)
|
||||
ret1, _ := ret[1].(*sts.GetCallerIdentityOutput)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetCallerIdentityRequest indicates an expected call of GetCallerIdentityRequest
|
||||
func (mr *MockSTSAPIMockRecorder) GetCallerIdentityRequest(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCallerIdentityRequest", reflect.TypeOf((*MockSTSAPI)(nil).GetCallerIdentityRequest), arg0)
|
||||
}
|
||||
|
||||
// GetCallerIdentityWithContext mocks base method
|
||||
func (m *MockSTSAPI) GetCallerIdentityWithContext(arg0 context.Context, arg1 *sts.GetCallerIdentityInput, arg2 ...request.Option) (*sts.GetCallerIdentityOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "GetCallerIdentityWithContext", varargs...)
|
||||
ret0, _ := ret[0].(*sts.GetCallerIdentityOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetCallerIdentityWithContext indicates an expected call of GetCallerIdentityWithContext
|
||||
func (mr *MockSTSAPIMockRecorder) GetCallerIdentityWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCallerIdentityWithContext", reflect.TypeOf((*MockSTSAPI)(nil).GetCallerIdentityWithContext), varargs...)
|
||||
}
|
||||
|
||||
// GetFederationToken mocks base method
|
||||
func (m *MockSTSAPI) GetFederationToken(arg0 *sts.GetFederationTokenInput) (*sts.GetFederationTokenOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetFederationToken", arg0)
|
||||
ret0, _ := ret[0].(*sts.GetFederationTokenOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetFederationToken indicates an expected call of GetFederationToken
|
||||
func (mr *MockSTSAPIMockRecorder) GetFederationToken(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFederationToken", reflect.TypeOf((*MockSTSAPI)(nil).GetFederationToken), arg0)
|
||||
}
|
||||
|
||||
// GetFederationTokenRequest mocks base method
|
||||
func (m *MockSTSAPI) GetFederationTokenRequest(arg0 *sts.GetFederationTokenInput) (*request.Request, *sts.GetFederationTokenOutput) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetFederationTokenRequest", arg0)
|
||||
ret0, _ := ret[0].(*request.Request)
|
||||
ret1, _ := ret[1].(*sts.GetFederationTokenOutput)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetFederationTokenRequest indicates an expected call of GetFederationTokenRequest
|
||||
func (mr *MockSTSAPIMockRecorder) GetFederationTokenRequest(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFederationTokenRequest", reflect.TypeOf((*MockSTSAPI)(nil).GetFederationTokenRequest), arg0)
|
||||
}
|
||||
|
||||
// GetFederationTokenWithContext mocks base method
|
||||
func (m *MockSTSAPI) GetFederationTokenWithContext(arg0 context.Context, arg1 *sts.GetFederationTokenInput, arg2 ...request.Option) (*sts.GetFederationTokenOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "GetFederationTokenWithContext", varargs...)
|
||||
ret0, _ := ret[0].(*sts.GetFederationTokenOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetFederationTokenWithContext indicates an expected call of GetFederationTokenWithContext
|
||||
func (mr *MockSTSAPIMockRecorder) GetFederationTokenWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFederationTokenWithContext", reflect.TypeOf((*MockSTSAPI)(nil).GetFederationTokenWithContext), varargs...)
|
||||
}
|
||||
|
||||
// GetSessionToken mocks base method
|
||||
func (m *MockSTSAPI) GetSessionToken(arg0 *sts.GetSessionTokenInput) (*sts.GetSessionTokenOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetSessionToken", arg0)
|
||||
ret0, _ := ret[0].(*sts.GetSessionTokenOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetSessionToken indicates an expected call of GetSessionToken
|
||||
func (mr *MockSTSAPIMockRecorder) GetSessionToken(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSessionToken", reflect.TypeOf((*MockSTSAPI)(nil).GetSessionToken), arg0)
|
||||
}
|
||||
|
||||
// GetSessionTokenRequest mocks base method
|
||||
func (m *MockSTSAPI) GetSessionTokenRequest(arg0 *sts.GetSessionTokenInput) (*request.Request, *sts.GetSessionTokenOutput) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetSessionTokenRequest", arg0)
|
||||
ret0, _ := ret[0].(*request.Request)
|
||||
ret1, _ := ret[1].(*sts.GetSessionTokenOutput)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetSessionTokenRequest indicates an expected call of GetSessionTokenRequest
|
||||
func (mr *MockSTSAPIMockRecorder) GetSessionTokenRequest(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSessionTokenRequest", reflect.TypeOf((*MockSTSAPI)(nil).GetSessionTokenRequest), arg0)
|
||||
}
|
||||
|
||||
// GetSessionTokenWithContext mocks base method
|
||||
func (m *MockSTSAPI) GetSessionTokenWithContext(arg0 context.Context, arg1 *sts.GetSessionTokenInput, arg2 ...request.Option) (*sts.GetSessionTokenOutput, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "GetSessionTokenWithContext", varargs...)
|
||||
ret0, _ := ret[0].(*sts.GetSessionTokenOutput)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetSessionTokenWithContext indicates an expected call of GetSessionTokenWithContext
|
||||
func (mr *MockSTSAPIMockRecorder) GetSessionTokenWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSessionTokenWithContext", reflect.TypeOf((*MockSTSAPI)(nil).GetSessionTokenWithContext), varargs...)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cloudwatch
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
)
|
||||
|
||||
type envelope struct {
|
||||
session *session.Session
|
||||
expiration time.Time
|
||||
}
|
||||
|
||||
var sessCache = map[string]envelope{}
|
||||
var sessCacheLock sync.RWMutex
|
||||
|
||||
// Session factory.
|
||||
// Stubbable by tests.
|
||||
//nolint:gocritic
|
||||
var newSession = func(cfgs ...*aws.Config) (*session.Session, error) {
|
||||
return session.NewSession(cfgs...)
|
||||
}
|
||||
|
||||
// STS credentials factory.
|
||||
// Stubbable by tests.
|
||||
//nolint:gocritic
|
||||
var newSTSCredentials = stscreds.NewCredentials
|
||||
|
||||
// EC2Metadata service factory.
|
||||
// Stubbable by tests.
|
||||
//nolint:gocritic
|
||||
var newEC2Metadata = ec2metadata.New
|
||||
@@ -0,0 +1,107 @@
|
||||
package cloudwatch
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"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/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Test cloudWatchExecutor.newSession with assumption of IAM role.
|
||||
func TestNewSession_AssumeRole(t *testing.T) {
|
||||
origNewSession := newSession
|
||||
origNewSTSCredentials := newSTSCredentials
|
||||
origNewEC2Metadata := newEC2Metadata
|
||||
t.Cleanup(func() {
|
||||
newSession = origNewSession
|
||||
newSTSCredentials = origNewSTSCredentials
|
||||
newEC2Metadata = origNewEC2Metadata
|
||||
})
|
||||
newSession = func(cfgs ...*aws.Config) (*session.Session, error) {
|
||||
cfg := aws.Config{}
|
||||
cfg.MergeIn(cfgs...)
|
||||
return &session.Session{
|
||||
Config: &cfg,
|
||||
}, nil
|
||||
}
|
||||
newSTSCredentials = func(c client.ConfigProvider, roleARN string,
|
||||
options ...func(*stscreds.AssumeRoleProvider)) *credentials.Credentials {
|
||||
p := &stscreds.AssumeRoleProvider{
|
||||
RoleARN: roleARN,
|
||||
}
|
||||
for _, o := range options {
|
||||
o(p)
|
||||
}
|
||||
|
||||
return credentials.NewCredentials(p)
|
||||
}
|
||||
newEC2Metadata = func(p client.ConfigProvider, cfgs ...*aws.Config) *ec2metadata.EC2Metadata {
|
||||
return nil
|
||||
}
|
||||
|
||||
duration := stscreds.DefaultDuration
|
||||
|
||||
t.Run("Without external ID", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
sessCache = map[string]envelope{}
|
||||
})
|
||||
|
||||
const roleARN = "test"
|
||||
|
||||
e := newExecutor()
|
||||
e.DataSource = fakeDataSource(fakeDataSourceCfg{
|
||||
assumeRoleARN: roleARN,
|
||||
})
|
||||
|
||||
sess, err := e.newSession(defaultRegion)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sess)
|
||||
|
||||
expCreds := credentials.NewCredentials(&stscreds.AssumeRoleProvider{
|
||||
RoleARN: roleARN,
|
||||
Duration: duration,
|
||||
})
|
||||
diff := cmp.Diff(expCreds, sess.Config.Credentials, cmp.Exporter(func(_ reflect.Type) bool {
|
||||
return true
|
||||
}), cmpopts.IgnoreFields(stscreds.AssumeRoleProvider{}, "Expiry"))
|
||||
assert.Empty(t, diff)
|
||||
})
|
||||
|
||||
t.Run("With external ID", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
sessCache = map[string]envelope{}
|
||||
})
|
||||
|
||||
const roleARN = "test"
|
||||
const externalID = "external"
|
||||
|
||||
e := newExecutor()
|
||||
e.DataSource = fakeDataSource(fakeDataSourceCfg{
|
||||
assumeRoleARN: roleARN,
|
||||
externalID: externalID,
|
||||
})
|
||||
|
||||
sess, err := e.newSession(defaultRegion)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sess)
|
||||
|
||||
expCreds := credentials.NewCredentials(&stscreds.AssumeRoleProvider{
|
||||
RoleARN: roleARN,
|
||||
ExternalID: aws.String(externalID),
|
||||
Duration: duration,
|
||||
})
|
||||
diff := cmp.Diff(expCreds, sess.Config.Credentials, cmp.Exporter(func(_ reflect.Type) bool {
|
||||
return true
|
||||
}), cmpopts.IgnoreFields(stscreds.AssumeRoleProvider{}, "Expiry"))
|
||||
assert.Empty(t, diff)
|
||||
})
|
||||
}
|
||||
@@ -18,9 +18,23 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func fakeDataSource() *models.DataSource {
|
||||
type fakeDataSourceCfg struct {
|
||||
assumeRoleARN string
|
||||
externalID string
|
||||
}
|
||||
|
||||
func fakeDataSource(cfgs ...fakeDataSourceCfg) *models.DataSource {
|
||||
jsonData := simplejson.New()
|
||||
jsonData.Set("defaultRegion", "default")
|
||||
jsonData.Set("defaultRegion", defaultRegion)
|
||||
jsonData.Set("authType", "default")
|
||||
for _, cfg := range cfgs {
|
||||
if cfg.assumeRoleARN != "" {
|
||||
jsonData.Set("assumeRoleArn", cfg.assumeRoleARN)
|
||||
}
|
||||
if cfg.externalID != "" {
|
||||
jsonData.Set("externalId", cfg.externalID)
|
||||
}
|
||||
}
|
||||
return &models.DataSource{
|
||||
Id: 1,
|
||||
Database: "default",
|
||||
|
||||
Reference in New Issue
Block a user