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", | ||||
| 		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) { | ||||
|  |  | |||
|  | @ -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: | ||||
| - scaleway_sd_configs: | ||||
|     - 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 | ||||
| 	} | ||||
| 
 | ||||
| 	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, | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -28,6 +28,7 @@ import ( | |||
| 
 | ||||
| var ( | ||||
| 	testProjectID     = "8feda53f-15f0-447f-badf-ebe32dad2fc0" | ||||
| 	testSecretKeyFile = "testdata/secret_key" | ||||
| 	testSecretKey     = "6d6579e5-a5b9-49fc-a35f-b4feb9b87301" | ||||
| 	testAccessKey     = "SCW0W8NG6024YHRJ7723" | ||||
| ) | ||||
|  | @ -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)) | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| 6d6579e5-a5b9-49fc-a35f-b4feb9b87301 | ||||
|  | @ -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> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue