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:
		
							parent
							
								
									9dceeea5e7
								
							
						
					
					
						commit
						5a6d244b00
					
				|  | @ -1136,6 +1136,18 @@ var expectedErrors = []struct { | ||||||
| 		filename: "eureka_invalid_server.bad.yml", | 		filename: "eureka_invalid_server.bad.yml", | ||||||
| 		errMsg:   "invalid eureka server URL", | 		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) { | func TestBadConfigs(t *testing.T) { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | scrape_configs: | ||||||
|  | - scaleway_sd_configs: | ||||||
|  |     - role: instance | ||||||
|  |       project_id: 11111111-1111-1111-1111-111111111112 | ||||||
|  |       access_key: SCWXXXXXXXXXXXXXXXXX | ||||||
|  | @ -1,4 +1,7 @@ | ||||||
| scrape_configs: | scrape_configs: | ||||||
| - scaleway_sd_configs: | - scaleway_sd_configs: | ||||||
|     - role: invalid |     - role: invalid | ||||||
|  |       project_id: 11111111-1111-1111-1111-111111111112 | ||||||
|  |       access_key: SCWXXXXXXXXXXXXXXXXX | ||||||
|  |       secret_key_file: bar | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | 
 | ||||||
|  | @ -75,10 +75,18 @@ func newBaremetalDiscovery(conf *SDConfig) (*baremetalDiscovery, error) { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if conf.SecretKeyFile != "" { | ||||||
|  | 		rt, err = newAuthTokenFileRoundTripper(conf.SecretKeyFile, rt) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	profile, err := loadProfile(conf) | 	profile, err := loadProfile(conf) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	d.client, err = scw.NewClient( | 	d.client, err = scw.NewClient( | ||||||
| 		scw.WithHTTPClient(&http.Client{ | 		scw.WithHTTPClient(&http.Client{ | ||||||
| 			Transport: rt, | 			Transport: rt, | ||||||
|  |  | ||||||
|  | @ -76,7 +76,7 @@ func newInstanceDiscovery(conf *SDConfig) (*instanceDiscovery, error) { | ||||||
| 		zone:       conf.Zone, | 		zone:       conf.Zone, | ||||||
| 		project:    conf.Project, | 		project:    conf.Project, | ||||||
| 		accessKey:  conf.AccessKey, | 		accessKey:  conf.AccessKey, | ||||||
| 		secretKey:  string(conf.SecretKey), | 		secretKey:  conf.secretKeyForConfig(), | ||||||
| 		nameFilter: conf.NameFilter, | 		nameFilter: conf.NameFilter, | ||||||
| 		tagsFilter: conf.TagsFilter, | 		tagsFilter: conf.TagsFilter, | ||||||
| 	} | 	} | ||||||
|  | @ -86,6 +86,13 @@ func newInstanceDiscovery(conf *SDConfig) (*instanceDiscovery, error) { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if conf.SecretKeyFile != "" { | ||||||
|  | 		rt, err = newAuthTokenFileRoundTripper(conf.SecretKeyFile, rt) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	profile, err := loadProfile(conf) | 	profile, err := loadProfile(conf) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| 	testProjectID     = "8feda53f-15f0-447f-badf-ebe32dad2fc0" | 	testProjectID     = "8feda53f-15f0-447f-badf-ebe32dad2fc0" | ||||||
|  | 	testSecretKeyFile = "testdata/secret_key" | ||||||
| 	testSecretKey     = "6d6579e5-a5b9-49fc-a35f-b4feb9b87301" | 	testSecretKey     = "6d6579e5-a5b9-49fc-a35f-b4feb9b87301" | ||||||
| 	testAccessKey     = "SCW0W8NG6024YHRJ7723" | 	testAccessKey     = "SCW0W8NG6024YHRJ7723" | ||||||
| ) | ) | ||||||
|  | @ -137,3 +138,28 @@ func mockScalewayInstance(w http.ResponseWriter, r *http.Request) { | ||||||
| 		return | 		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)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -15,6 +15,9 @@ package scaleway | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/go-kit/kit/log" | 	"github.com/go-kit/kit/log" | ||||||
|  | @ -83,6 +86,8 @@ type SDConfig struct { | ||||||
| 	AccessKey string `yaml:"access_key"` | 	AccessKey string `yaml:"access_key"` | ||||||
| 	// SecretKey used to authenticate on Scaleway APIs.
 | 	// SecretKey used to authenticate on Scaleway APIs.
 | ||||||
| 	SecretKey config.Secret `yaml:"secret_key"` | 	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 to filter on during the ListServers.
 | ||||||
| 	NameFilter string `yaml:"name_filter,omitempty"` | 	NameFilter string `yaml:"name_filter,omitempty"` | ||||||
| 	// TagsFilter to filter on during the ListServers.
 | 	// TagsFilter to filter on during the ListServers.
 | ||||||
|  | @ -100,6 +105,15 @@ func (c SDConfig) Name() string { | ||||||
| 	return "scaleway" | 	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.
 | // UnmarshalYAML implements the yaml.Unmarshaler interface.
 | ||||||
| func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { | func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||||||
| 	*c = DefaultSDConfig | 	*c = DefaultSDConfig | ||||||
|  | @ -117,8 +131,12 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||||||
| 		return errors.New("project_id is mandatory") | 		return errors.New("project_id is mandatory") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if c.SecretKey == "" { | 	if c.SecretKey == "" && c.SecretKeyFile == "" { | ||||||
| 		return errors.New("secret_key is mandatory") | 		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 == "" { | 	if c.AccessKey == "" { | ||||||
|  | @ -145,6 +163,7 @@ func (c SDConfig) NewDiscoverer(options discovery.DiscovererOptions) (discovery. | ||||||
| 
 | 
 | ||||||
| // SetDirectory joins any relative file paths with dir.
 | // SetDirectory joins any relative file paths with dir.
 | ||||||
| func (c *SDConfig) SetDirectory(dir string) { | func (c *SDConfig) SetDirectory(dir string) { | ||||||
|  | 	c.SecretKeyFile = config.JoinDir(dir, c.SecretKeyFile) | ||||||
| 	c.HTTPClientConfig.SetDirectory(dir) | 	c.HTTPClientConfig.SetDirectory(dir) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -190,7 +209,7 @@ func loadProfile(sdConfig *SDConfig) (*scw.Profile, error) { | ||||||
| 	prometheusConfigProfile := &scw.Profile{ | 	prometheusConfigProfile := &scw.Profile{ | ||||||
| 		DefaultZone:      scw.StringPtr(sdConfig.Zone), | 		DefaultZone:      scw.StringPtr(sdConfig.Zone), | ||||||
| 		APIURL:           scw.StringPtr(sdConfig.APIURL), | 		APIURL:           scw.StringPtr(sdConfig.APIURL), | ||||||
| 		SecretKey:        scw.StringPtr(string(sdConfig.SecretKey)), | 		SecretKey:        scw.StringPtr(sdConfig.secretKeyForConfig()), | ||||||
| 		AccessKey:        scw.StringPtr(sdConfig.AccessKey), | 		AccessKey:        scw.StringPtr(sdConfig.AccessKey), | ||||||
| 		DefaultProjectID: scw.StringPtr(sdConfig.Project), | 		DefaultProjectID: scw.StringPtr(sdConfig.Project), | ||||||
| 		SendTelemetry:    scw.BoolPtr(false), | 		SendTelemetry:    scw.BoolPtr(false), | ||||||
|  | @ -198,3 +217,29 @@ func loadProfile(sdConfig *SDConfig) (*scw.Profile, error) { | ||||||
| 
 | 
 | ||||||
| 	return prometheusConfigProfile, nil | 	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) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | 6d6579e5-a5b9-49fc-a35f-b4feb9b87301 | ||||||
|  | @ -1589,7 +1589,12 @@ See below for the configuration options for Scaleway discovery: | ||||||
| access_key: <string> | access_key: <string> | ||||||
| 
 | 
 | ||||||
| # Secret key to use when listing targets. https://console.scaleway.com/project/credentials | # 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 of the targets. | ||||||
| project_id: <string> | project_id: <string> | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue