Scaleway SD: Add the ability to read token from file

Prometheus adds the ability to read secrets from files. This add
this feature for the scaleway service discovery.

Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
This commit is contained in:
Julien Pivotto 2021-03-25 00:33:21 +01:00
parent 9dceeea5e7
commit 5a6d244b00
10 changed files with 129 additions and 9 deletions

View file

@ -1136,6 +1136,18 @@ var expectedErrors = []struct {
filename: "eureka_invalid_server.bad.yml",
errMsg: "invalid eureka server URL",
},
{
filename: "scaleway_role.bad.yml",
errMsg: `unknown role "invalid"`,
},
{
filename: "scaleway_no_secret.bad.yml",
errMsg: "one of secret_key & secret_key_file must be configured",
},
{
filename: "scaleway_two_secrets.bad.yml",
errMsg: "at most one of secret_key & secret_key_file must be configured",
},
}
func TestBadConfigs(t *testing.T) {

View file

@ -0,0 +1,5 @@
scrape_configs:
- scaleway_sd_configs:
- role: instance
project_id: 11111111-1111-1111-1111-111111111112
access_key: SCWXXXXXXXXXXXXXXXXX

View file

@ -1,4 +1,7 @@
scrape_configs:
- scaleway_sd_configs:
- role: invalid
- role: invalid
project_id: 11111111-1111-1111-1111-111111111112
access_key: SCWXXXXXXXXXXXXXXXXX
secret_key_file: bar

View file

@ -0,0 +1,8 @@
scrape_configs:
- scaleway_sd_configs:
- role: instance
project_id: 11111111-1111-1111-1111-111111111112
access_key: SCWXXXXXXXXXXXXXXXXX
secret_key_file: bar
secret_key: 11111111-1111-1111-1111-111111111112

View file

@ -75,10 +75,18 @@ func newBaremetalDiscovery(conf *SDConfig) (*baremetalDiscovery, error) {
return nil, err
}
if conf.SecretKeyFile != "" {
rt, err = newAuthTokenFileRoundTripper(conf.SecretKeyFile, rt)
if err != nil {
return nil, err
}
}
profile, err := loadProfile(conf)
if err != nil {
return nil, err
}
d.client, err = scw.NewClient(
scw.WithHTTPClient(&http.Client{
Transport: rt,

View file

@ -76,7 +76,7 @@ func newInstanceDiscovery(conf *SDConfig) (*instanceDiscovery, error) {
zone: conf.Zone,
project: conf.Project,
accessKey: conf.AccessKey,
secretKey: string(conf.SecretKey),
secretKey: conf.secretKeyForConfig(),
nameFilter: conf.NameFilter,
tagsFilter: conf.TagsFilter,
}
@ -86,6 +86,13 @@ func newInstanceDiscovery(conf *SDConfig) (*instanceDiscovery, error) {
return nil, err
}
if conf.SecretKeyFile != "" {
rt, err = newAuthTokenFileRoundTripper(conf.SecretKeyFile, rt)
if err != nil {
return nil, err
}
}
profile, err := loadProfile(conf)
if err != nil {
return nil, err

View file

@ -27,9 +27,10 @@ import (
)
var (
testProjectID = "8feda53f-15f0-447f-badf-ebe32dad2fc0"
testSecretKey = "6d6579e5-a5b9-49fc-a35f-b4feb9b87301"
testAccessKey = "SCW0W8NG6024YHRJ7723"
testProjectID = "8feda53f-15f0-447f-badf-ebe32dad2fc0"
testSecretKeyFile = "testdata/secret_key"
testSecretKey = "6d6579e5-a5b9-49fc-a35f-b4feb9b87301"
testAccessKey = "SCW0W8NG6024YHRJ7723"
)
func TestScalewayInstanceRefresh(t *testing.T) {
@ -137,3 +138,28 @@ func mockScalewayInstance(w http.ResponseWriter, r *http.Request) {
return
}
}
func TestScalewayInstanceAuthToken(t *testing.T) {
mock := httptest.NewServer(http.HandlerFunc(mockScalewayInstance))
defer mock.Close()
cfgString := fmt.Sprintf(`
---
role: instance
project_id: %s
secret_key_file: %s
access_key: %s
api_url: %s
`, testProjectID, testSecretKeyFile, testAccessKey, mock.URL)
var cfg SDConfig
require.NoError(t, yaml.UnmarshalStrict([]byte(cfgString), &cfg))
d, err := newRefresher(&cfg)
require.NoError(t, err)
ctx := context.Background()
tgs, err := d.refresh(ctx)
require.NoError(t, err)
require.Equal(t, 1, len(tgs))
}

View file

@ -15,6 +15,9 @@ package scaleway
import (
"context"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/go-kit/kit/log"
@ -83,6 +86,8 @@ type SDConfig struct {
AccessKey string `yaml:"access_key"`
// SecretKey used to authenticate on Scaleway APIs.
SecretKey config.Secret `yaml:"secret_key"`
// SecretKey used to authenticate on Scaleway APIs.
SecretKeyFile string `yaml:"secret_key_file"`
// NameFilter to filter on during the ListServers.
NameFilter string `yaml:"name_filter,omitempty"`
// TagsFilter to filter on during the ListServers.
@ -100,6 +105,15 @@ func (c SDConfig) Name() string {
return "scaleway"
}
// secretKeyForConfig returns a secret key that looks like a UUID, even if we
// take the actuel secret from a file.
func (c SDConfig) secretKeyForConfig() string {
if c.SecretKeyFile != "" {
return "00000000-0000-0000-0000-000000000000"
}
return string(c.SecretKey)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultSDConfig
@ -117,8 +131,12 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return errors.New("project_id is mandatory")
}
if c.SecretKey == "" {
return errors.New("secret_key is mandatory")
if c.SecretKey == "" && c.SecretKeyFile == "" {
return errors.New("one of secret_key & secret_key_file must be configured")
}
if c.SecretKey != "" && c.SecretKeyFile != "" {
return errors.New("at most one of secret_key & secret_key_file must be configured")
}
if c.AccessKey == "" {
@ -145,6 +163,7 @@ func (c SDConfig) NewDiscoverer(options discovery.DiscovererOptions) (discovery.
// SetDirectory joins any relative file paths with dir.
func (c *SDConfig) SetDirectory(dir string) {
c.SecretKeyFile = config.JoinDir(dir, c.SecretKeyFile)
c.HTTPClientConfig.SetDirectory(dir)
}
@ -190,7 +209,7 @@ func loadProfile(sdConfig *SDConfig) (*scw.Profile, error) {
prometheusConfigProfile := &scw.Profile{
DefaultZone: scw.StringPtr(sdConfig.Zone),
APIURL: scw.StringPtr(sdConfig.APIURL),
SecretKey: scw.StringPtr(string(sdConfig.SecretKey)),
SecretKey: scw.StringPtr(sdConfig.secretKeyForConfig()),
AccessKey: scw.StringPtr(sdConfig.AccessKey),
DefaultProjectID: scw.StringPtr(sdConfig.Project),
SendTelemetry: scw.BoolPtr(false),
@ -198,3 +217,29 @@ func loadProfile(sdConfig *SDConfig) (*scw.Profile, error) {
return prometheusConfigProfile, nil
}
type authTokenFileRoundTripper struct {
authTokenFile string
rt http.RoundTripper
}
// newAuthTokenFileRoundTripper adds the auth token read from the file to a request.
func newAuthTokenFileRoundTripper(tokenFile string, rt http.RoundTripper) (http.RoundTripper, error) {
// fail-fast if we can't read the file.
_, err := ioutil.ReadFile(tokenFile)
if err != nil {
return nil, errors.Wrapf(err, "unable to read auth token file %s", tokenFile)
}
return &authTokenFileRoundTripper{tokenFile, rt}, nil
}
func (rt *authTokenFileRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
b, err := ioutil.ReadFile(rt.authTokenFile)
if err != nil {
return nil, errors.Wrapf(err, "unable to read auth token file %s", rt.authTokenFile)
}
authToken := strings.TrimSpace(string(b))
request.Header.Set("X-Auth-Token", authToken)
return rt.rt.RoundTrip(request)
}

View file

@ -0,0 +1 @@
6d6579e5-a5b9-49fc-a35f-b4feb9b87301

View file

@ -1589,7 +1589,12 @@ See below for the configuration options for Scaleway discovery:
access_key: <string>
# Secret key to use when listing targets. https://console.scaleway.com/project/credentials
secret_key: <secret>
# It is mutually exclusive with `secret_key_file`.
[ secret_key: <secret> ]
# Sets the secret key with the credentials read from the configured file.
# It is mutually exclusive with `secret_key`.
[ secret_key_file: <filename> ]
# Project ID of the targets.
project_id: <string>