mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-17 00:26:36 +03:00
lib/awsapi: add support for named AWS profile to ec2_sd_config
Add support for named AWS profiles in ec2_sd_config, matching Prometheus behavior.
Example:
```text
~/.aws/config:
[profile account-one]
source_profile = root
role_arn = arn:aws:iam::000000000001:role/prometheus
```
```yaml
scrape config:
- job: ec2
ec2_sd_configs:
- profile: account-one
```
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1685
This commit is contained in:
@@ -290,7 +290,7 @@ func getAWSAPIConfig(argIdx int) (*awsapi.Config, error) {
|
||||
accessKey := awsAccessKey.GetOptionalArg(argIdx)
|
||||
secretKey := awsSecretKey.GetOptionalArg(argIdx)
|
||||
service := awsService.GetOptionalArg(argIdx)
|
||||
cfg, err := awsapi.NewConfig(ec2Endpoint, stsEndpoint, region, roleARN, accessKey, secretKey, service)
|
||||
cfg, err := awsapi.NewConfig(ec2Endpoint, stsEndpoint, region, roleARN, accessKey, secretKey, service, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ Released at 2026-03-13
|
||||
|
||||
* SECURITY: upgrade Go builder from Go1.26.0 to Go1.26.1. See [the list of issues addressed in Go1.26.1](https://github.com/golang/go/issues?q=milestone%3AGo1.26.1%20label%3ACherryPickApproved).
|
||||
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): add `profile` option to `ec2_sd_configs` for loading credentials from named AWS profiles in `~/.aws/credentials` and `~/.aws/config`, including `source_profile` chaining and `role_arn` resolution. See [ec2_sd_configs docs](https://docs.victoriametrics.com/victoriametrics/sd_configs/#ec2_sd_configs). Issue [#1685](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1685).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): add `headers` field to `oauth2` scrape config for passing custom HTTP headers to `token_url`. Some services require different headers for the token endpoint and the scrape targets. See [#8939](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8939).
|
||||
* FEATURE: [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/): add [OIDC Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) support for JWT authentication. `vmauth` can now automatically fetch and rotate public keys from an OpenID Connect provider, eliminating the need to specify public keys manually. See [OIDC Discovery](https://docs.victoriametrics.com/victoriametrics/vmauth/#oidc-discovery) docs. See [#10585](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10585).
|
||||
* FEATURE: all VictoriaMetrics components: implement proper CORS preflight handling by responding 204 No Content to HTTP OPTIONS requests. See [#5563](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5563).
|
||||
|
||||
@@ -648,6 +648,12 @@ scrape_configs:
|
||||
#
|
||||
# role_arn: "..."
|
||||
|
||||
# profile is an optional named AWS profile from ~/.aws/config and ~/.aws/credentials.
|
||||
# When set, credentials and role_arn are resolved from the profile, with source_profile
|
||||
# chaining supported. See https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html
|
||||
#
|
||||
# profile: "..."
|
||||
|
||||
# port is an optional port to scrape metrics from.
|
||||
# By default, port 80 is used.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package awsapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -37,6 +39,9 @@ type Config struct {
|
||||
defaultAccessKey string
|
||||
defaultSecretKey string
|
||||
|
||||
// profile is the named AWS profile to use from shared credentials/config files.
|
||||
profile string
|
||||
|
||||
// Real credentials used for accessing EC2 API.
|
||||
creds *credentials
|
||||
credsLock sync.Mutex
|
||||
@@ -51,7 +56,7 @@ type credentials struct {
|
||||
}
|
||||
|
||||
// NewConfig returns new AWS Config from the given args.
|
||||
func NewConfig(ec2Endpoint, stsEndpoint, region, roleARN, accessKey, secretKey, service string) (*Config, error) {
|
||||
func NewConfig(ec2Endpoint, stsEndpoint, region, roleARN, accessKey, secretKey, service, profile string) (*Config, error) {
|
||||
cfg := &Config{
|
||||
client: http.DefaultClient,
|
||||
region: region,
|
||||
@@ -61,6 +66,7 @@ func NewConfig(ec2Endpoint, stsEndpoint, region, roleARN, accessKey, secretKey,
|
||||
service: service,
|
||||
defaultAccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
|
||||
defaultSecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
|
||||
profile: profile,
|
||||
}
|
||||
if cfg.service == "" {
|
||||
cfg.service = "aps"
|
||||
@@ -184,8 +190,8 @@ func (cfg *Config) getFreshAPICredentials() (*credentials, error) {
|
||||
// There is no need in refreshing statically set api credentials if roleARN isn't set.
|
||||
return cfg.creds, nil
|
||||
}
|
||||
if time.Until(cfg.creds.Expiration) > 10*time.Second {
|
||||
// credentials aren't expired yet.
|
||||
if cfg.creds != nil && (cfg.creds.Expiration.IsZero() || time.Until(cfg.creds.Expiration) > 10*time.Second) {
|
||||
// credentials aren't expired yet; zero expiration means they don't expire (e.g. static profile keys).
|
||||
return cfg.creds, nil
|
||||
}
|
||||
// credentials have been expired. Update them.
|
||||
@@ -207,6 +213,8 @@ func (cfg *Config) getAPICredentials() (*credentials, error) {
|
||||
if relativeURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); len(relativeURI) > 0 {
|
||||
fullURI = "http://169.254.170.2" + relativeURI
|
||||
}
|
||||
// roleARN may be overridden by profile's role_arn config entry.
|
||||
roleARN := cfg.roleARN
|
||||
switch {
|
||||
case len(acNew.AccessKeyID) > 0 && len(acNew.SecretAccessKey) > 0:
|
||||
case len(cfg.webTokenPath) > 0:
|
||||
@@ -229,6 +237,29 @@ func (cfg *Config) getAPICredentials() (*credentials, error) {
|
||||
return nil, err
|
||||
}
|
||||
acNew = ac
|
||||
case len(cfg.profile) > 0:
|
||||
sourceProfile, profileRoleARN, err := readAWSConfigFile(cfg.profile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read config file for profile %q: %w", cfg.profile, err)
|
||||
}
|
||||
credProfile := cfg.profile
|
||||
if sourceProfile != "" {
|
||||
if sourceProfile == cfg.profile {
|
||||
return nil, fmt.Errorf("source_profile for %q points to itself", cfg.profile)
|
||||
}
|
||||
credProfile = sourceProfile
|
||||
}
|
||||
if roleARN == "" {
|
||||
roleARN = profileRoleARN
|
||||
}
|
||||
ac, err := readSharedCredentials(credProfile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read shared credentials for profile %q: %w", credProfile, err)
|
||||
}
|
||||
if ac == nil {
|
||||
return nil, fmt.Errorf("missing credentials for profile %q", credProfile)
|
||||
}
|
||||
acNew = ac
|
||||
default:
|
||||
// we need instance credentials if we do not have access keys
|
||||
ac, err := getInstanceRoleCredentials(cfg.client)
|
||||
@@ -238,10 +269,10 @@ func (cfg *Config) getAPICredentials() (*credentials, error) {
|
||||
acNew = ac
|
||||
}
|
||||
// read credentials from sts api, if role_arn is defined
|
||||
if len(cfg.roleARN) > 0 {
|
||||
ac, err := cfg.getRoleARNCredentials(acNew, cfg.roleARN)
|
||||
if len(roleARN) > 0 {
|
||||
ac, err := cfg.getRoleARNCredentials(acNew, roleARN)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get credentials for role_arn %q: %w", cfg.roleARN, err)
|
||||
return nil, fmt.Errorf("cannot get credentials for role_arn %q: %w", roleARN, err)
|
||||
}
|
||||
acNew = ac
|
||||
}
|
||||
@@ -254,6 +285,112 @@ func (cfg *Config) getAPICredentials() (*credentials, error) {
|
||||
return acNew, nil
|
||||
}
|
||||
|
||||
// readSharedCredentials reads credentials from ~/.aws/credentials for the given profile.
|
||||
func readSharedCredentials(profile string) (*credentials, error) {
|
||||
path := os.Getenv("AWS_SHARED_CREDENTIALS_FILE")
|
||||
if path == "" {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get home directory: %w", err)
|
||||
}
|
||||
path = filepath.Join(home, ".aws", "credentials")
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cannot read %q: %w", path, err)
|
||||
}
|
||||
section := readSection(data, profile)
|
||||
if section == nil {
|
||||
return nil, nil
|
||||
}
|
||||
accessKey := section["aws_access_key_id"]
|
||||
secretKey := section["aws_secret_access_key"]
|
||||
if accessKey == "" || secretKey == "" {
|
||||
return nil, fmt.Errorf("missing aws_access_key_id or aws_secret_access_key for profile %q in %q", profile, path)
|
||||
}
|
||||
return &credentials{
|
||||
AccessKeyID: accessKey,
|
||||
SecretAccessKey: secretKey,
|
||||
Token: section["aws_session_token"],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// readAWSConfigFile returns source_profile and role_arn for the given profile from ~/.aws/config.
|
||||
func readAWSConfigFile(profile string) (sourceProfile, roleARN string, err error) {
|
||||
path := os.Getenv("AWS_CONFIG_FILE")
|
||||
if path == "" {
|
||||
tilde, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("cannot get home directory: %w", err)
|
||||
}
|
||||
path = filepath.Join(tilde, ".aws", "config")
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", nil
|
||||
}
|
||||
return "", "", fmt.Errorf("cannot read %q: %w", path, err)
|
||||
}
|
||||
// named profiles use "profile " prefix in config file; "default" is the exception
|
||||
sectionName := "profile " + profile
|
||||
if profile == "default" {
|
||||
sectionName = "default"
|
||||
}
|
||||
section := readSection(data, sectionName)
|
||||
if section == nil {
|
||||
return "", "", nil
|
||||
}
|
||||
return section["source_profile"], section["role_arn"], nil
|
||||
}
|
||||
|
||||
// readSection returns key-value pairs for the given section from AWS config/credentials file data.
|
||||
func readSection(data []byte, section string) map[string]string {
|
||||
sectionBytes := []byte(strings.TrimSpace(section))
|
||||
var result map[string]string
|
||||
inSection := false
|
||||
for len(data) > 0 {
|
||||
var line []byte
|
||||
if i := bytes.IndexByte(data, '\n'); i >= 0 {
|
||||
line, data = data[:i], data[i+1:]
|
||||
} else {
|
||||
line, data = data, nil
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
// '#' is the only recognized comment character. See https://stackoverflow.com/questions/43217469/how-do-you-comment-out-lines-in-aws-cli-config-and-credentials-files
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
if line[0] == '[' {
|
||||
end := bytes.IndexByte(line, ']')
|
||||
if end < 0 {
|
||||
continue
|
||||
}
|
||||
// strip double quotes to handle profile names with spaces, e.g. [profile "my profile"]
|
||||
header := bytes.ReplaceAll(bytes.TrimSpace(line[1:end]), []byte{'"'}, nil)
|
||||
inSection = bytes.EqualFold(header, sectionBytes)
|
||||
continue
|
||||
}
|
||||
if !inSection {
|
||||
continue
|
||||
}
|
||||
eq := bytes.IndexByte(line, '=')
|
||||
if eq < 0 {
|
||||
continue
|
||||
}
|
||||
key := string(bytes.TrimSpace(line[:eq]))
|
||||
value := string(bytes.TrimSpace(line[eq+1:]))
|
||||
if result == nil {
|
||||
result = make(map[string]string)
|
||||
}
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// getCredentialsByPath makes request to metadata service and retrieves container credentials
|
||||
// https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html
|
||||
func getCredentialsByPath(client *http.Client, uri, token string) (*credentials, error) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -296,6 +297,178 @@ func TestParseARNCredentialsSuccess(t *testing.T) {
|
||||
f(s2, "AssumeRoleWithWebIdentity", credsExpected2)
|
||||
}
|
||||
|
||||
func TestReadSection(t *testing.T) {
|
||||
f := func(data, section string, expectedResult map[string]string) {
|
||||
t.Helper()
|
||||
result := readSection([]byte(data), section)
|
||||
if !reflect.DeepEqual(result, expectedResult) {
|
||||
t.Fatalf("unexpected result for section %q;\ngot\n%v\nwant\n%v", section, result, expectedResult)
|
||||
}
|
||||
}
|
||||
|
||||
// missing section
|
||||
f("[foo]\nkey=val\n", "spoon", nil)
|
||||
|
||||
// happy path
|
||||
f("[default]\naws_access_key_id = HESOYAM\naws_secret_access_key = BAGUVIX\n", " default ", map[string]string{
|
||||
"aws_access_key_id": "HESOYAM",
|
||||
"aws_secret_access_key": "BAGUVIX",
|
||||
})
|
||||
|
||||
// comments and blank lines are skipped
|
||||
f("# some comment\n[default]\n\npipeline = green\ntests = well written and stable", "default", map[string]string{
|
||||
"pipeline": "green",
|
||||
"tests": "well written and stable",
|
||||
})
|
||||
|
||||
// profile prefix used in config file
|
||||
f("[profile account-one]\nsource_profile = root\nrole_arn = arn:aws:iam::000000000001:role/prometheus\n", "profile account-one", map[string]string{
|
||||
"source_profile": "root",
|
||||
"role_arn": "arn:aws:iam::000000000001:role/prometheus",
|
||||
})
|
||||
|
||||
// multiple sections - only the matching one is returned
|
||||
f("[default]\nregion=us-east-1\n[profile foo]\nrole_arn=arn:foo\n", "profile foo", map[string]string{
|
||||
"role_arn": "arn:foo",
|
||||
})
|
||||
|
||||
// quirky line endings just in case
|
||||
f("[test]\r\nfoo=bar\r\nbeep=boop\r\n", "test", map[string]string{
|
||||
"foo": "bar",
|
||||
"beep": "boop",
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadAWSConfigFile(t *testing.T) {
|
||||
f := func(content, profile, wantSourceProfile, wantRoleARN string) {
|
||||
t.Helper()
|
||||
tempDir := t.TempDir()
|
||||
cfgPath := filepath.Join(tempDir, "config")
|
||||
if err := os.WriteFile(cfgPath, []byte(content), 0600); err != nil {
|
||||
t.Fatalf("cannot write config file: %v", err)
|
||||
}
|
||||
t.Setenv("AWS_CONFIG_FILE", cfgPath)
|
||||
sourceProfile, roleARN, err := readAWSConfigFile(profile)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if sourceProfile != wantSourceProfile {
|
||||
t.Fatalf("unexpected source_profile; got %q, want %q", sourceProfile, wantSourceProfile)
|
||||
}
|
||||
if roleARN != wantRoleARN {
|
||||
t.Fatalf("unexpected role_arn; got %q, want %q", roleARN, wantRoleARN)
|
||||
}
|
||||
}
|
||||
|
||||
// profile with source_profile and role_arn
|
||||
f("[profile account-one]\nsource_profile = root\nrole_arn = arn:aws:iam::111:role/r\n",
|
||||
"account-one", "root", "arn:aws:iam::111:role/r")
|
||||
|
||||
// default profile
|
||||
f("[default]\nrole_arn = arn:aws:iam::222:role/r\n",
|
||||
"default", "", "arn:aws:iam::222:role/r")
|
||||
|
||||
// profile not found returns empty strings
|
||||
f("[profile other]\nrole_arn = arn:foo\n",
|
||||
"missing", "", "")
|
||||
}
|
||||
|
||||
func TestReadSharedCredentials(t *testing.T) {
|
||||
f := func(content, profile string, wantCreds *credentials) {
|
||||
t.Helper()
|
||||
tempDir := t.TempDir()
|
||||
credsPath := filepath.Join(tempDir, "credentials")
|
||||
if err := os.WriteFile(credsPath, []byte(content), 0600); err != nil {
|
||||
t.Fatalf("cannot write credentials file: %v", err)
|
||||
}
|
||||
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", credsPath)
|
||||
creds, err := readSharedCredentials(profile)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(creds, wantCreds) {
|
||||
t.Fatalf("unexpected creds;\ngot\n%+v\nwant\n%+v", creds, wantCreds)
|
||||
}
|
||||
}
|
||||
|
||||
// basic credentials
|
||||
f("[root]\naws_access_key_id = AKID\naws_secret_access_key = SECRET\n", "root", &credentials{
|
||||
AccessKeyID: "AKID",
|
||||
SecretAccessKey: "SECRET",
|
||||
})
|
||||
|
||||
// credentials with session token
|
||||
f("[root]\naws_access_key_id = AKID\naws_secret_access_key = SECRET\naws_session_token = TOKEN\n", "root", &credentials{
|
||||
AccessKeyID: "AKID",
|
||||
SecretAccessKey: "SECRET",
|
||||
Token: "TOKEN",
|
||||
})
|
||||
|
||||
// profile not found
|
||||
f("[other]\naws_access_key_id = AKID\naws_secret_access_key = SECRET\n", "missing", nil)
|
||||
}
|
||||
|
||||
func TestGetAPICredentialsWithProfile(t *testing.T) {
|
||||
responses := map[string]string{
|
||||
"AssumeRole": `
|
||||
<AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
|
||||
<AssumeRoleResult>
|
||||
<Credentials>
|
||||
<AccessKeyId>PROFILEROLEID</AccessKeyId>
|
||||
<SecretAccessKey>PROFILEROLESECRET</SecretAccessKey>
|
||||
<SessionToken>PROFILEROLETOKEN</SessionToken>
|
||||
<Expiration>2025-01-01T00:00:00Z</Expiration>
|
||||
</Credentials>
|
||||
</AssumeRoleResult>
|
||||
<ResponseMetadata><RequestId>test</RequestId></ResponseMetadata>
|
||||
</AssumeRoleResponse>
|
||||
`,
|
||||
}
|
||||
|
||||
tempDir := t.TempDir()
|
||||
|
||||
configContent := "[profile myprofile]\nsource_profile = root\nrole_arn = arn:aws:iam::123:role/myrole\n"
|
||||
configPath := filepath.Join(tempDir, "config")
|
||||
if err := os.WriteFile(configPath, []byte(configContent), 0600); err != nil {
|
||||
t.Fatalf("cannot write config: %v", err)
|
||||
}
|
||||
credsContent := "[root]\naws_access_key_id = ROOTAKID\naws_secret_access_key = ROOTSECRET\n"
|
||||
credsPath := filepath.Join(tempDir, "credentials")
|
||||
if err := os.WriteFile(credsPath, []byte(credsContent), 0600); err != nil {
|
||||
t.Fatalf("cannot write credentials: %v", err)
|
||||
}
|
||||
|
||||
t.Setenv("AWS_CONFIG_FILE", configPath)
|
||||
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", credsPath)
|
||||
|
||||
rt := &fakeRoundTripper{responses: make(map[string]*http.Response)}
|
||||
for action, value := range responses {
|
||||
recorder := httptest.NewRecorder()
|
||||
recorder.WriteHeader(http.StatusOK)
|
||||
_, _ = recorder.WriteString(value)
|
||||
rt.responses[action] = recorder.Result()
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
profile: "myprofile",
|
||||
stsEndpoint: "http://sts.fake",
|
||||
client: &http.Client{Transport: rt},
|
||||
}
|
||||
creds, err := cfg.getAPICredentials()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
credsExpected := &credentials{
|
||||
AccessKeyID: "PROFILEROLEID",
|
||||
SecretAccessKey: "PROFILEROLESECRET",
|
||||
Token: "PROFILEROLETOKEN",
|
||||
Expiration: mustParseRFC3339("2025-01-01T00:00:00Z"),
|
||||
}
|
||||
if !reflect.DeepEqual(creds, credsExpected) {
|
||||
t.Fatalf("unexpected creds;\ngot\n%+v\nwant\n%+v", creds, credsExpected)
|
||||
}
|
||||
}
|
||||
|
||||
func mustParseRFC3339(s string) time.Time {
|
||||
expTime, err := time.Parse(time.RFC3339, s)
|
||||
if err != nil {
|
||||
|
||||
@@ -37,7 +37,7 @@ func newAPIConfig(sdc *SDConfig) (*apiConfig, error) {
|
||||
if stsEndpoint == "" {
|
||||
stsEndpoint = sdc.Endpoint
|
||||
}
|
||||
awsCfg, err := awsapi.NewConfig(sdc.Endpoint, stsEndpoint, sdc.Region, sdc.RoleARN, sdc.AccessKey, sdc.SecretKey.String(), "ec2")
|
||||
awsCfg, err := awsapi.NewConfig(sdc.Endpoint, stsEndpoint, sdc.Region, sdc.RoleARN, sdc.AccessKey, sdc.SecretKey.String(), "ec2", sdc.Profile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ type SDConfig struct {
|
||||
STSEndpoint string `yaml:"sts_endpoint,omitempty"`
|
||||
AccessKey string `yaml:"access_key,omitempty"`
|
||||
SecretKey *promauth.Secret `yaml:"secret_key,omitempty"`
|
||||
// TODO add support for Profile, not working atm
|
||||
// Profile string `yaml:"profile,omitempty"`
|
||||
// Profile is the named AWS profile from ~/.aws/config and ~/.aws/credentials.
|
||||
Profile string `yaml:"profile,omitempty"`
|
||||
RoleARN string `yaml:"role_arn,omitempty"`
|
||||
// RefreshInterval time.Duration `yaml:"refresh_interval"`
|
||||
// refresh_interval is obtained from `-promscrape.ec2SDCheckInterval` command-line option.
|
||||
|
||||
Reference in New Issue
Block a user