diff --git a/config/config.go b/config/config.go index dc2ed19a25..562c49e59f 100644 --- a/config/config.go +++ b/config/config.go @@ -759,6 +759,33 @@ type MetadataConfig struct { MaxSamplesPerSend int `yaml:"max_samples_per_send,omitempty"` } +// SigV4Config is the configuration for signing remote write requests with +// AWS's SigV4 verification process. Empty values will be retrieved using the +// AWS default credentials chain. +type SigV4Config struct { + Region string `yaml:"region,omitempty"` + AccessKey string `yaml:"access_key,omitempty"` + SecretKey config.Secret `yaml:"secret_key,omitempty"` + SecretKeyFile string `yaml:"secret_key_file,omitempty"` + Profile string `yaml:"profile,omitempty"` + RoleARN string `yaml:"role_arn,omitempty"` +} + +func (c *SigV4Config) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain SigV4Config + if err := unmarshal((*plain)(c)); err != nil { + return err + } + + if len(c.SecretKey) == 0 && len(c.SecretKeyFile) != 0 { + if err := c.SecretKey.LoadFromFile(c.SecretKeyFile); err != nil { + return fmt.Errorf("cannot read sigv4 secret key: %w", err) + } + } + + return nil +} + // RemoteReadConfig is the configuration for reading from remote storage. type RemoteReadConfig struct { URL *config.URL `yaml:"url"` diff --git a/discovery/aws/ec2.go b/discovery/aws/ec2.go index 0bcfd05476..551763e4d7 100644 --- a/discovery/aws/ec2.go +++ b/discovery/aws/ec2.go @@ -87,6 +87,7 @@ type EC2SDConfig struct { Region string `yaml:"region"` AccessKey string `yaml:"access_key,omitempty"` SecretKey config.Secret `yaml:"secret_key,omitempty"` + SecretKeyFile string `yaml:"secret_key_file,omitempty"` Profile string `yaml:"profile,omitempty"` RoleARN string `yaml:"role_arn,omitempty"` RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` @@ -110,6 +111,12 @@ func (c *EC2SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err != nil { return err } + if len(c.SecretKey) == 0 && len(c.SecretKeyFile) != 0 { + err = c.SecretKey.LoadFromFile(c.SecretKeyFile) + if err != nil { + return fmt.Errorf("cannot read secret key: %w", err) + } + } if c.Region == "" { sess, err := session.NewSession() if err != nil { diff --git a/discovery/azure/azure.go b/discovery/azure/azure.go index b0de0abd8c..aeb0905f0e 100644 --- a/discovery/azure/azure.go +++ b/discovery/azure/azure.go @@ -78,6 +78,7 @@ type SDConfig struct { TenantID string `yaml:"tenant_id,omitempty"` ClientID string `yaml:"client_id,omitempty"` ClientSecret config_util.Secret `yaml:"client_secret,omitempty"` + ClientSecretFile string `yaml:"client_secret_file,omitempty"` RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` AuthenticationMethod string `yaml:"authentication_method,omitempty"` } @@ -117,7 +118,13 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err = validateAuthParam(c.ClientID, "client_id"); err != nil { return err } - if err = validateAuthParam(string(c.ClientSecret), "client_secret"); err != nil { + if len(c.ClientSecret) == 0 && len(c.ClientSecretFile) != 0 { + err = c.ClientSecret.LoadFromFile(c.ClientSecretFile) + if err != nil { + return fmt.Errorf("cannot read client secret from file: %w", err) + } + } + if err := validateAuthParam(string(c.ClientSecret), "client_secret or client_secret_file"); err != nil { return err } } diff --git a/discovery/consul/consul.go b/discovery/consul/consul.go index 16d0d26282..38466b540f 100644 --- a/discovery/consul/consul.go +++ b/discovery/consul/consul.go @@ -110,12 +110,14 @@ func init() { type SDConfig struct { Server string `yaml:"server,omitempty"` Token config.Secret `yaml:"token,omitempty"` + TokenFile string `yaml:"token_file,omitempty"` Datacenter string `yaml:"datacenter,omitempty"` Namespace string `yaml:"namespace,omitempty"` TagSeparator string `yaml:"tag_separator,omitempty"` Scheme string `yaml:"scheme,omitempty"` Username string `yaml:"username,omitempty"` Password config.Secret `yaml:"password,omitempty"` + PasswordFile string `yaml:"password_file,omitempty"` // See https://www.consul.io/docs/internals/consensus.html#consistency-modes, // stale reads are a lot cheaper and are a necessity if you have >5k targets. @@ -162,6 +164,12 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if strings.TrimSpace(c.Server) == "" { return errors.New("consul SD configuration requires a server address") } + if len(c.Password) == 0 && len(c.PasswordFile) != 0 { + err = c.Password.LoadFromFile(c.PasswordFile) + if err != nil { + return fmt.Errorf("cannot read password: %w", err) + } + } if c.Username != "" || c.Password != "" { if c.HTTPClientConfig.BasicAuth != nil { return errors.New("at most one of consul SD configuration username and password and basic auth can be configured") @@ -171,7 +179,13 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { Password: c.Password, } } - if c.Token != "" && (c.HTTPClientConfig.Authorization != nil || c.HTTPClientConfig.OAuth2 != nil) { + tokenSet := false + if len(c.Token) != 0 && len(c.TokenFile) != 0 { + return errors.New("at most one of SD token or token_file can be configured") + } else if len(c.Token) != 0 || len(c.TokenFile) != 0 { + tokenSet = true + } + if tokenSet && (c.HTTPClientConfig.Authorization != nil || c.HTTPClientConfig.OAuth2 != nil) { return errors.New("at most one of consul SD token, authorization, or oauth2 can be configured") } return c.HTTPClientConfig.Validate() @@ -211,6 +225,7 @@ func NewDiscovery(conf *SDConfig, logger log.Logger) (*Discovery, error) { Datacenter: conf.Datacenter, Namespace: conf.Namespace, Token: string(conf.Token), + TokenFile: conf.TokenFile, HttpClient: wrapper, } client, err := consul.NewClient(clientConf) diff --git a/discovery/openstack/openstack.go b/discovery/openstack/openstack.go index 2a341976ed..8f4e7ce02b 100644 --- a/discovery/openstack/openstack.go +++ b/discovery/openstack/openstack.go @@ -45,24 +45,26 @@ func init() { // SDConfig is the configuration for OpenStack based service discovery. type SDConfig struct { - IdentityEndpoint string `yaml:"identity_endpoint"` - Username string `yaml:"username"` - UserID string `yaml:"userid"` - Password config.Secret `yaml:"password"` - ProjectName string `yaml:"project_name"` - ProjectID string `yaml:"project_id"` - DomainName string `yaml:"domain_name"` - DomainID string `yaml:"domain_id"` - ApplicationCredentialName string `yaml:"application_credential_name"` - ApplicationCredentialID string `yaml:"application_credential_id"` - ApplicationCredentialSecret config.Secret `yaml:"application_credential_secret"` - Role Role `yaml:"role"` - Region string `yaml:"region"` - RefreshInterval model.Duration `yaml:"refresh_interval"` - Port int `yaml:"port"` - AllTenants bool `yaml:"all_tenants,omitempty"` - TLSConfig config.TLSConfig `yaml:"tls_config,omitempty"` - Availability string `yaml:"availability,omitempty"` + IdentityEndpoint string `yaml:"identity_endpoint"` + Username string `yaml:"username"` + UserID string `yaml:"userid"` + Password config.Secret `yaml:"password"` + PasswordFile string `yaml:"password_file"` + ProjectName string `yaml:"project_name"` + ProjectID string `yaml:"project_id"` + DomainName string `yaml:"domain_name"` + DomainID string `yaml:"domain_id"` + ApplicationCredentialName string `yaml:"application_credential_name"` + ApplicationCredentialID string `yaml:"application_credential_id"` + ApplicationCredentialSecret config.Secret `yaml:"application_credential_secret"` + ApplicationCredentialSecretFile string `yaml:"application_credential_secret_file"` + Role Role `yaml:"role"` + Region string `yaml:"region"` + RefreshInterval model.Duration `yaml:"refresh_interval"` + Port int `yaml:"port"` + AllTenants bool `yaml:"all_tenants,omitempty"` + TLSConfig config.TLSConfig `yaml:"tls_config,omitempty"` + Availability string `yaml:"availability,omitempty"` } // Name returns the name of the Config. @@ -113,6 +115,20 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } + if len(c.Password) == 0 && len(c.PasswordFile) != 0 { + err = c.Password.LoadFromFile(c.PasswordFile) + if err != nil { + return fmt.Errorf("cannot read password: %w", err) + } + } + + if len(c.ApplicationCredentialSecret) == 0 && len(c.ApplicationCredentialSecretFile) != 0 { + err = c.ApplicationCredentialSecret.LoadFromFile(c.ApplicationCredentialSecretFile) + if err != nil { + return fmt.Errorf("cannot read application credential secret: %w", err) + } + } + switch c.Availability { case "public", "internal", "admin": default: diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 4e25521109..d1a00cc44a 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -416,9 +416,15 @@ subscription_id: [ tenant_id: ] # Optional client ID. Only required with authentication_method OAuth. [ client_id: ] + # Optional client secret. Only required with authentication_method OAuth. +# It is given preference over `client_secret_file`. [ client_secret: ] +# Path to file containing the client secret. +# It is used if `client_secret` is not set. +[ client_secret_file: ] + # Refresh interval to re-read the instance list. [ refresh_interval: | default = 300s ] @@ -451,7 +457,15 @@ The following meta labels are available on targets during [relabeling](#relabel_ # The information to access the Consul API. It is to be defined # as the Consul documentation requires. [ server: | default = "localhost:8500" ] + +# Token to use when accessing the API. +# It is given preference over `token_file`. [ token: ] + +# Path to file containing the token to use when accessing the API. +# It is used if `token` is not set. +[ token_file: ] + [ datacenter: ] # Namespaces are only supported in Consul Enterprise. [ namespace: ] @@ -459,6 +473,7 @@ The following meta labels are available on targets during [relabeling](#relabel_ # The username and password fields are deprecated in favor of the basic_auth configuration. [ username: ] [ password: ] +[ password_file: ] # A list of services for which targets are retrieved. If omitted, all services # are scraped. @@ -936,7 +951,15 @@ See below for the configuration options for EC2 discovery: # The AWS API keys. If blank, the environment variables `AWS_ACCESS_KEY_ID` # and `AWS_SECRET_ACCESS_KEY` are used. [ access_key: ] + +# Secret key to use when accessing the API. +# It is given preference over `secret_key_file`. [ secret_key: ] + +# Path to file containing the secret key to use when accessing the API. +# It is used if `secret_key` is not set. +[ secret_key_file: ] + # Named AWS profile used to connect to the API. [ profile: ] @@ -1029,7 +1052,10 @@ region: # password for the Identity V2 and V3 APIs. Consult with your provider's # control panel to discover your account's preferred method of authentication. +# You can alternatively provide a path to a file containing the # password. +# If both are provided, `password` is used. [ password: ] +[ password_file: ] # At most one of domain_id and domain_name must be provided if using username # with Identity V3. Otherwise, either are optional. @@ -1051,8 +1077,11 @@ region: [ application_credential_id: ] # The application_credential_secret field is required if using an application -# credential to authenticate. +# credential to authenticate. Alternatively you can provide a path to the file +# containing the secret. If both are provided, `application_credential_secret` +# is used. [ application_credential_secret: ] +[ application_credential_secret_file: ] # Whether the service discovery should list all instances for all projects. # It is only relevant for the 'instance' role and usually requires admin permissions. @@ -1772,7 +1801,15 @@ See below for the configuration options for Lightsail discovery: # The AWS API keys. If blank, the environment variables `AWS_ACCESS_KEY_ID` # and `AWS_SECRET_ACCESS_KEY` are used. [ access_key: ] + +# Secret key to use when accessing the API. +# It is given preference over `secret_key_file`. [ secret_key: ] + +# Path to file containing the secret key to use when accessing the API. +# It is used if `secret_key` is not set. +[ secret_key_file: ] + # Named AWS profile used to connect to the API. [ profile: ] @@ -2590,8 +2627,15 @@ sigv4: # The AWS API keys. If blank, the environment variables `AWS_ACCESS_KEY_ID` # and `AWS_SECRET_ACCESS_KEY` are used. [ access_key: ] + + # Secret key to use when accessing the API. + # It is given preference over `secret_key_file`. [ secret_key: ] + # Path to file containing the secret key to use when accessing the API. + # It is used if `secret_key` is not set. + [ secret_key_file: ] + # Named AWS profile used to authenticate. [ profile: ] diff --git a/go.mod b/go.mod index f03bcb8a5b..e1d9bc6581 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/prometheus/alertmanager v0.23.0 github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_model v0.2.0 - github.com/prometheus/common v0.31.1 + github.com/prometheus/common v0.31.2-0.20211011203104-9789762a2ddb github.com/prometheus/common/sigv4 v0.1.0 github.com/prometheus/exporter-toolkit v0.6.1 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210223165440-c65ae3540d44 diff --git a/go.sum b/go.sum index b1fc5326cd..2340ef0e42 100644 --- a/go.sum +++ b/go.sum @@ -1141,8 +1141,8 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.31.1 h1:d18hG4PkHnNAKNMOmFuXFaiY8Us0nird/2m60uS1AMs= -github.com/prometheus/common v0.31.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.31.2-0.20211011203104-9789762a2ddb h1:nDUL0g/BSYon1707Ums2YQG50fvMgV2D8otbQZHNnEs= +github.com/prometheus/common v0.31.2-0.20211011203104-9789762a2ddb/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= github.com/prometheus/exporter-toolkit v0.6.1 h1:Aqk75wQD92N9CqmTlZwjKwq6272nOGrWIbc8Z7+xQO0=