feat(core): support any auth scheme in HTTP-sync auth header (#1152)
## This PR - adds support for any auth scheme in HTTP-sync auth header ### Related Issues Closes #1150 --------- Signed-off-by: Best Olunusi <olunusibest@gmail.com> Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
This commit is contained in:
parent
64ee20e191
commit
df6596634e
|
|
@ -148,6 +148,7 @@ func (sb *SyncBuilder) newHTTP(config sync.SourceConfig, logger *logger.Logger)
|
|||
zap.String("sync", "remote"),
|
||||
),
|
||||
BearerToken: config.BearerToken,
|
||||
AuthHeader: config.AuthHeader,
|
||||
Interval: interval,
|
||||
Cron: cron.New(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,6 +198,11 @@ func Test_SyncsFromFromConfig(t *testing.T) {
|
|||
Provider: syncProviderHTTP,
|
||||
BearerToken: "token",
|
||||
},
|
||||
{
|
||||
URI: "https://host:port",
|
||||
Provider: syncProviderHTTP,
|
||||
AuthHeader: "scheme credentials/token",
|
||||
},
|
||||
{
|
||||
URI: "/tmp/flags.json",
|
||||
Provider: syncProviderFile,
|
||||
|
|
@ -211,6 +216,7 @@ func Test_SyncsFromFromConfig(t *testing.T) {
|
|||
wantSyncs: []sync.ISync{
|
||||
&grpc.Sync{},
|
||||
&http.Sync{},
|
||||
&http.Sync{},
|
||||
&file.Sync{},
|
||||
&kubernetes.Sync{},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ func ParseSources(sourcesFlag string) ([]sync.SourceConfig, error) {
|
|||
if sp.Provider == "" {
|
||||
return syncProvidersParsed, errors.New("sync provider argument parse: provider is a required field")
|
||||
}
|
||||
if sp.AuthHeader != "" && sp.BearerToken != "" {
|
||||
return syncProvidersParsed, errors.New(
|
||||
"sync provider argument parse: both authHeader and bearerToken are defined, only one is allowed at a time",
|
||||
)
|
||||
}
|
||||
}
|
||||
return syncProvidersParsed, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,14 +52,17 @@ func TestParseSource(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"multiple-syncs-with-options": {
|
||||
in: `[{"uri":"config/samples/example_flags.json","provider":"file"},
|
||||
{"uri":"http://my-flag-source.json","provider":"http","bearerToken":"bearer-dji34ld2l"},
|
||||
{"uri":"https://secure-remote","provider":"http","bearerToken":"bearer-dji34ld2l"},
|
||||
{"uri":"http://site.com","provider":"http","interval":77 },
|
||||
{"uri":"default/my-flag-config","provider":"kubernetes"},
|
||||
{"uri":"grpc-source:8080","provider":"grpc"},
|
||||
{"uri":"my-flag-source:8080","provider":"grpc", "tls":true, "certPath": "/certs/ca.cert", "providerID": "flagd-weatherapp-sidecar", "selector": "source=database,app=weatherapp"}]
|
||||
`,
|
||||
in: `[
|
||||
{"uri":"config/samples/example_flags.json","provider":"file"},
|
||||
{"uri":"http://my-flag-source.json","provider":"http","bearerToken":"bearer-dji34ld2l"},
|
||||
{"uri":"https://secure-remote","provider":"http","bearerToken":"bearer-dji34ld2l"},
|
||||
{"uri":"https://secure-remote","provider":"http","authHeader":"Bearer bearer-dji34ld2l"},
|
||||
{"uri":"https://secure-remote","provider":"http","authHeader":"Basic dXNlcjpwYXNz"},
|
||||
{"uri":"http://site.com","provider":"http","interval":77 },
|
||||
{"uri":"default/my-flag-config","provider":"kubernetes"},
|
||||
{"uri":"grpc-source:8080","provider":"grpc"},
|
||||
{"uri":"my-flag-source:8080","provider":"grpc", "tls":true, "certPath": "/certs/ca.cert", "providerID": "flagd-weatherapp-sidecar", "selector": "source=database,app=weatherapp"}
|
||||
]`,
|
||||
expectErr: false,
|
||||
out: []sync.SourceConfig{
|
||||
{
|
||||
|
|
@ -76,6 +79,16 @@ func TestParseSource(t *testing.T) {
|
|||
Provider: syncProviderHTTP,
|
||||
BearerToken: "bearer-dji34ld2l",
|
||||
},
|
||||
{
|
||||
URI: "https://secure-remote",
|
||||
Provider: syncProviderHTTP,
|
||||
AuthHeader: "Bearer bearer-dji34ld2l",
|
||||
},
|
||||
{
|
||||
URI: "https://secure-remote",
|
||||
Provider: syncProviderHTTP,
|
||||
AuthHeader: "Basic dXNlcjpwYXNz",
|
||||
},
|
||||
{
|
||||
URI: "http://site.com",
|
||||
Provider: syncProviderHTTP,
|
||||
|
|
@ -99,6 +112,22 @@ func TestParseSource(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"multiple-auth-options": {
|
||||
in: `[
|
||||
{"uri":"https://secure-remote","provider":"http","authHeader":"Bearer bearer-dji34ld2l","bearerToken":"bearer-dji34ld2l"}
|
||||
]`,
|
||||
expectErr: true,
|
||||
out: []sync.SourceConfig{
|
||||
{
|
||||
URI: "https://secure-remote",
|
||||
Provider: syncProviderHTTP,
|
||||
AuthHeader: "Bearer bearer-dji34ld2l",
|
||||
BearerToken: "bearer-dji34ld2l",
|
||||
TLS: false,
|
||||
Interval: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"empty": {
|
||||
in: `[]`,
|
||||
expectErr: false,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ type Sync struct {
|
|||
LastBodySHA string
|
||||
Logger *logger.Logger
|
||||
BearerToken string
|
||||
AuthHeader string
|
||||
Interval uint32
|
||||
ready bool
|
||||
}
|
||||
|
|
@ -47,7 +48,9 @@ func (hs *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error
|
|||
}
|
||||
|
||||
func (hs *Sync) Init(_ context.Context) error {
|
||||
// noop
|
||||
if hs.BearerToken != "" {
|
||||
hs.Logger.Warn("Deprecation Alert: bearerToken option is deprecated, please use authHeader instead")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +123,9 @@ func (hs *Sync) fetchBodyFromURL(ctx context.Context, url string) ([]byte, error
|
|||
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
if hs.BearerToken != "" {
|
||||
if hs.AuthHeader != "" {
|
||||
req.Header.Set("Authorization", hs.AuthHeader)
|
||||
} else if hs.BearerToken != "" {
|
||||
bearer := fmt.Sprintf("Bearer %s", hs.BearerToken)
|
||||
req.Header.Set("Authorization", bearer)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ func TestHTTPSync_Fetch(t *testing.T) {
|
|||
setup func(t *testing.T, client *syncmock.MockClient)
|
||||
uri string
|
||||
bearerToken string
|
||||
authHeader string
|
||||
lastBodySHA string
|
||||
handleResponse func(*testing.T, Sync, string, error)
|
||||
}{
|
||||
|
|
@ -111,13 +112,46 @@ func TestHTTPSync_Fetch(t *testing.T) {
|
|||
}
|
||||
},
|
||||
},
|
||||
"authorization header": {
|
||||
"authorization with bearerToken": {
|
||||
setup: func(t *testing.T, client *syncmock.MockClient) {
|
||||
client.EXPECT().Do(gomock.Any()).Return(&http.Response{
|
||||
Body: io.NopCloser(strings.NewReader("test response")),
|
||||
}, nil)
|
||||
expectedToken := "bearer-1234"
|
||||
client.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) {
|
||||
actualAuthHeader := req.Header.Get("Authorization")
|
||||
if actualAuthHeader != "Bearer "+expectedToken {
|
||||
t.Fatalf("expected Authorization header to be 'Bearer %s', got %s", expectedToken, actualAuthHeader)
|
||||
}
|
||||
return &http.Response{Body: io.NopCloser(strings.NewReader("test response"))}, nil
|
||||
})
|
||||
},
|
||||
uri: "http://localhost",
|
||||
bearerToken: "bearer-1234",
|
||||
lastBodySHA: "",
|
||||
handleResponse: func(t *testing.T, httpSync Sync, _ string, err error) {
|
||||
if err != nil {
|
||||
t.Fatalf("fetch: %v", err)
|
||||
}
|
||||
|
||||
expectedLastBodySHA := "UjeJHtCU_wb7OHK-tbPoHycw0TqlHzkWJmH4y6cqg50="
|
||||
if httpSync.LastBodySHA != expectedLastBodySHA {
|
||||
t.Errorf(
|
||||
"expected last body sha to be: '%s', got: '%s'", expectedLastBodySHA, httpSync.LastBodySHA,
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
"authorization with authHeader": {
|
||||
setup: func(t *testing.T, client *syncmock.MockClient) {
|
||||
expectedHeader := "Basic dXNlcjpwYXNz"
|
||||
client.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) {
|
||||
actualAuthHeader := req.Header.Get("Authorization")
|
||||
if actualAuthHeader != expectedHeader {
|
||||
t.Fatalf("expected Authorization header to be '%s', got %s", expectedHeader, actualAuthHeader)
|
||||
}
|
||||
return &http.Response{Body: io.NopCloser(strings.NewReader("test response"))}, nil
|
||||
})
|
||||
},
|
||||
uri: "http://localhost",
|
||||
authHeader: "Basic dXNlcjpwYXNz",
|
||||
lastBodySHA: "",
|
||||
handleResponse: func(t *testing.T, httpSync Sync, _ string, err error) {
|
||||
if err != nil {
|
||||
|
|
@ -144,6 +178,7 @@ func TestHTTPSync_Fetch(t *testing.T) {
|
|||
URI: tt.uri,
|
||||
Client: mockClient,
|
||||
BearerToken: tt.bearerToken,
|
||||
AuthHeader: tt.authHeader,
|
||||
LastBodySHA: tt.lastBodySHA,
|
||||
Logger: logger.NewLogger(nil, false),
|
||||
}
|
||||
|
|
@ -154,6 +189,29 @@ func TestHTTPSync_Fetch(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSync_Init(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
bearerToken string
|
||||
}{
|
||||
{"with bearerToken", "bearer-1234"},
|
||||
{"without bearerToken", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
httpSync := Sync{
|
||||
BearerToken: tt.bearerToken,
|
||||
Logger: logger.NewLogger(nil, false),
|
||||
}
|
||||
|
||||
if err := httpSync.Init(context.Background()); err != nil {
|
||||
t.Errorf("Init() error = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPSync_Resync(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ type SourceConfig struct {
|
|||
Provider string `json:"provider"`
|
||||
|
||||
BearerToken string `json:"bearerToken,omitempty"`
|
||||
AuthHeader string `json:"authHeader,omitempty"`
|
||||
CertPath string `json:"certPath,omitempty"`
|
||||
TLS bool `json:"tls,omitempty"`
|
||||
ProviderID string `json:"providerID,omitempty"`
|
||||
|
|
|
|||
|
|
@ -27,16 +27,17 @@ The flagd accepts a string argument, which should be a JSON representation of an
|
|||
|
||||
Alternatively, these configurations can be passed to flagd via config file, specified using the `--config` flag.
|
||||
|
||||
| Field | Type | Note |
|
||||
| ----------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| uri | required `string` | Flag configuration source of the sync |
|
||||
| provider | required `string` | Provider type - `file`, `kubernetes`, `http` or `grpc` |
|
||||
| bearerToken | optional `string` | Used for http sync; token gets appended to `Authorization` header with [bearer schema](https://www.rfc-editor.org/rfc/rfc6750#section-2.1) |
|
||||
| interval | optional `uint32` | Used for http sync; requests will be made at this interval. Defaults to 5 seconds. |
|
||||
| tls | optional `boolean` | Enable/Disable secure TLS connectivity. Currently used only by gRPC sync. Default(ex:- if unset) is false, which will use an insecure connection |
|
||||
| providerID | optional `string` | Value binds to grpc connection's providerID field. gRPC server implementations may use this to identify connecting flagd instance |
|
||||
| selector | optional `string` | Value binds to grpc connection's selector field. gRPC server implementations may use this to filter flag configurations |
|
||||
| certPath | optional `string` | Used for grpcs sync when TLS certificate is needed. If not provided, system certificates will be used for TLS connection |
|
||||
| Field | Type | Note |
|
||||
| ----------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| uri | required `string` | Flag configuration source of the sync |
|
||||
| provider | required `string` | Provider type - `file`, `kubernetes`, `http`, or `grpc` |
|
||||
| authHeader | optional `string` | Used for http sync; set this to include the complete `Authorization` header value for any authentication scheme (e.g., "Bearer token_here", "Basic base64_credentials", etc.). Cannot be used with `bearerToken` |
|
||||
| bearerToken | optional `string` | (Deprecated) Used for http sync; token gets appended to `Authorization` header with [bearer schema](https://www.rfc-editor.org/rfc/rfc6750#section-2.1). Cannot be used with `authHeader` |
|
||||
| interval | optional `uint32` | Used for http sync; requests will be made at this interval. Defaults to 5 seconds. |
|
||||
| tls | optional `boolean` | Enable/Disable secure TLS connectivity. Currently used only by gRPC sync. Default (ex: if unset) is false, which will use an insecure connection |
|
||||
| providerID | optional `string` | Value binds to grpc connection's providerID field. gRPC server implementations may use this to identify connecting flagd instance |
|
||||
| selector | optional `string` | Value binds to grpc connection's selector field. gRPC server implementations may use this to filter flag configurations |
|
||||
| certPath | optional `string` | Used for grpcs sync when TLS certificate is needed. If not provided, system certificates will be used for TLS connection |
|
||||
|
||||
The `uri` field values **do not** follow the [URI patterns](#uri-patterns). The provider type is instead derived
|
||||
from the `provider` field. Only exception is the remote provider where `http(s)://` is expected by default. Incorrect
|
||||
|
|
@ -59,6 +60,8 @@ Startup command:
|
|||
./bin/flagd start
|
||||
--sources='[{"uri":"config/samples/example_flags.json","provider":"file"},
|
||||
{"uri":"http://my-flag-source.json","provider":"http","bearerToken":"bearer-dji34ld2l"},
|
||||
{"uri":"https://secure-remote/bearer-auth","provider":"http","authHeader":"Bearer bearer-dji34ld2l"},
|
||||
{"uri":"https://secure-remote/basic-auth","provider":"http","authHeader":"Basic dXNlcjpwYXNz"},
|
||||
{"uri":"default/my-flag-config","provider":"kubernetes"},
|
||||
{"uri":"grpc-source:8080","provider":"grpc"},
|
||||
{"uri":"my-flag-source:8080","provider":"grpc", "certPath": "/certs/ca.cert", "tls": true, "providerID": "flagd-weatherapp-sidecar", "selector": "source=database,app=weatherapp"}]'
|
||||
|
|
@ -82,5 +85,5 @@ sources:
|
|||
certPath: /certs/ca.cert
|
||||
tls: true
|
||||
providerID: flagd-weatherapp-sidecar
|
||||
selector: 'source=database,app=weatherapp'
|
||||
selector: "source=database,app=weatherapp"
|
||||
```
|
||||
|
|
|
|||
Loading…
Reference in New Issue