diff --git a/config/config_test.go b/config/config_test.go index b2ae0343f1..5e206038e4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -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) { diff --git a/config/testdata/scaleway_no_secret.bad.yml b/config/testdata/scaleway_no_secret.bad.yml new file mode 100644 index 0000000000..4b30af178f --- /dev/null +++ b/config/testdata/scaleway_no_secret.bad.yml @@ -0,0 +1,5 @@ +scrape_configs: +- scaleway_sd_configs: + - role: instance + project_id: 11111111-1111-1111-1111-111111111112 + access_key: SCWXXXXXXXXXXXXXXXXX diff --git a/config/testdata/scaleway_role.bad.yml b/config/testdata/scaleway_role.bad.yml index 97970e8357..83e51d04a2 100644 --- a/config/testdata/scaleway_role.bad.yml +++ b/config/testdata/scaleway_role.bad.yml @@ -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 diff --git a/config/testdata/scaleway_two_secrets.bad.yml b/config/testdata/scaleway_two_secrets.bad.yml new file mode 100644 index 0000000000..d3344d341a --- /dev/null +++ b/config/testdata/scaleway_two_secrets.bad.yml @@ -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 + diff --git a/discovery/scaleway/baremetal.go b/discovery/scaleway/baremetal.go index 8eca938752..6dcf3b7faa 100644 --- a/discovery/scaleway/baremetal.go +++ b/discovery/scaleway/baremetal.go @@ -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, diff --git a/discovery/scaleway/instance.go b/discovery/scaleway/instance.go index 2dae98d687..836044b61b 100644 --- a/discovery/scaleway/instance.go +++ b/discovery/scaleway/instance.go @@ -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 diff --git a/discovery/scaleway/instance_test.go b/discovery/scaleway/instance_test.go index 3c07de35f0..0e597c6226 100644 --- a/discovery/scaleway/instance_test.go +++ b/discovery/scaleway/instance_test.go @@ -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)) +} diff --git a/discovery/scaleway/scaleway.go b/discovery/scaleway/scaleway.go index fc1ace8062..c07ca92c02 100644 --- a/discovery/scaleway/scaleway.go +++ b/discovery/scaleway/scaleway.go @@ -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) +} diff --git a/discovery/scaleway/testdata/secret_key b/discovery/scaleway/testdata/secret_key new file mode 100644 index 0000000000..de2fb2331b --- /dev/null +++ b/discovery/scaleway/testdata/secret_key @@ -0,0 +1 @@ +6d6579e5-a5b9-49fc-a35f-b4feb9b87301 diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 816a090fc8..3065c19088 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -1589,7 +1589,12 @@ See below for the configuration options for Scaleway discovery: access_key: # Secret key to use when listing targets. https://console.scaleway.com/project/credentials -secret_key: +# It is mutually exclusive with `secret_key_file`. +[ secret_key: ] + +# Sets the secret key with the credentials read from the configured file. +# It is mutually exclusive with `secret_key`. +[ secret_key_file: ] # Project ID of the targets. project_id: