Skip to content

Commit

Permalink
updated allow config section
Browse files Browse the repository at this point in the history
  • Loading branch information
yesoreyeram committed Dec 2, 2024
1 parent 4458dfa commit 4f9df7a
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .changeset/proud-mice-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'grafana-infinity-datasource': minor
---

Updated allowed hosts configuration to skip when used with base URL
4 changes: 2 additions & 2 deletions pkg/infinity/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ func (client *Client) req(ctx context.Context, url string, body io.Reader, setti
return nil, http.StatusInternalServerError, duration, errorsource.DownstreamError(fmt.Errorf("invalid response received for the URL %s", url), false)
}
if res.StatusCode >= http.StatusBadRequest {
err = fmt.Errorf("%w. %s", ErrUnsuccessfulHTTPResponseStatus, res.Status)
err = fmt.Errorf("%w. %s", models.ErrUnsuccessfulHTTPResponseStatus, res.Status)
// Infinity can query anything and users are responsible for ensuring that endpoint/auth is correct
// therefore any incoming error is considered downstream
return nil, res.StatusCode, duration, errorsource.DownstreamError(err, false)
Expand All @@ -249,7 +249,7 @@ func (client *Client) req(ctx context.Context, url string, body io.Reader, setti
var out any
err := json.Unmarshal(bodyBytes, &out)
if err != nil {
err = fmt.Errorf("%w. %w", ErrParsingResponseBodyAsJson, err)
err = fmt.Errorf("%w. %w", models.ErrParsingResponseBodyAsJson, err)
err = errorsource.DownstreamError(err, false)
logger.Debug("error un-marshaling JSON response", "url", url, "error", err.Error())
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/infinity/errors.go → pkg/models/errors.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package infinity
package models

import "errors"

var (
ErrUnsuccessfulHTTPResponseStatus error = errors.New("unsuccessful HTTP response")
ErrParsingResponseBodyAsJson error = errors.New("unable to parse response body as JSON")
ErrMissingAllowedHosts error = errors.New("datasource is missing allowed hosts/URLs. Configure it in the datasource settings page for enhanced security")
)
27 changes: 20 additions & 7 deletions pkg/models/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,26 @@ func (s *InfinitySettings) Validate() error {
}
return nil
}
if s.AuthenticationMethod != AuthenticationMethodNone && len(s.AllowedHosts) < 1 {
return errors.New("configure allowed hosts in the authentication section")
}
if s.HaveSecureHeaders() && len(s.AllowedHosts) < 1 {
return errors.New("configure allowed hosts in the authentication section")
if s.DoesAllowedHostsRequired() && len(s.AllowedHosts) < 1 {
return ErrMissingAllowedHosts
}
return nil
}

func (s *InfinitySettings) HaveSecureHeaders() bool {
func (s *InfinitySettings) DoesAllowedHostsRequired() bool {
// If base url is configured, there is no need for allowed hosts
if strings.TrimSpace(s.URL) != "" {
return false
}
// If there is specific authentication mechanism (except none and azure blob), then allowed hosts required
if s.AuthenticationMethod != AuthenticationMethodNone && s.AuthenticationMethod != AuthenticationMethodAzureBlob {
return true
}
// If there are any TLS specific settings enabled, then allowed hosts required
if s.TLSAuthWithCACert || s.TLSClientAuth {
return true
}
// If there are custom headers (not generic headers such as Accept, Content Type etc), then allowed hosts required
if len(s.CustomHeaders) > 0 {
for k := range s.CustomHeaders {
if textproto.CanonicalMIMEHeaderKey(k) == "Accept" {
Expand All @@ -169,7 +179,10 @@ func (s *InfinitySettings) HaveSecureHeaders() bool {
}
return true
}
return false
}
// If there are custom query parameters, then allowed hosts required
if len(s.SecureQueryFields) > 0 {
return true
}
return false
}
Expand Down
31 changes: 23 additions & 8 deletions pkg/models/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,31 +339,31 @@ func TestInfinitySettings_Validate(t *testing.T) {
},
{
settings: models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodNone, CustomHeaders: map[string]string{"A": "B"}},
wantErr: errors.New("configure allowed hosts in the authentication section"),
wantErr: models.ErrMissingAllowedHosts,
},
{
settings: models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodNone, CustomHeaders: map[string]string{"A": "B", "Accept": ""}},
wantErr: errors.New("configure allowed hosts in the authentication section"),
wantErr: models.ErrMissingAllowedHosts,
},
{
settings: models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodNone, CustomHeaders: map[string]string{"A": "B", "Content-Type": ""}},
wantErr: errors.New("configure allowed hosts in the authentication section"),
wantErr: models.ErrMissingAllowedHosts,
},
{
settings: models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodNone, CustomHeaders: map[string]string{"A": "B", "Accept": "", "Content-Type": ""}},
wantErr: errors.New("configure allowed hosts in the authentication section"),
wantErr: models.ErrMissingAllowedHosts,
},
{
settings: models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodNone, CustomHeaders: map[string]string{"A": "B", "C": "D"}},
wantErr: errors.New("configure allowed hosts in the authentication section"),
wantErr: models.ErrMissingAllowedHosts,
},
{
settings: models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodBasic},
wantErr: errors.New("invalid or empty password detected"),
},
{
settings: models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodBasic, Password: "123"},
wantErr: errors.New("configure allowed hosts in the authentication section"),
wantErr: models.ErrMissingAllowedHosts,
},
{
settings: models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodApiKey},
Expand All @@ -379,15 +379,30 @@ func TestInfinitySettings_Validate(t *testing.T) {
},
{
settings: models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodApiKey, ApiKeyKey: "foo", ApiKeyValue: "bar"},
wantErr: errors.New("configure allowed hosts in the authentication section"),
wantErr: models.ErrMissingAllowedHosts,
},
{
settings: models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodBearerToken},
wantErr: errors.New("invalid or empty bearer token detected"),
},
{
settings: models.InfinitySettings{AuthenticationMethod: models.AuthenticationMethodBearerToken, BearerToken: "foo"},
wantErr: errors.New("configure allowed hosts in the authentication section"),
wantErr: models.ErrMissingAllowedHosts,
},
{
settings: models.InfinitySettings{SecureQueryFields: map[string]string{"foo": "bar"}},
wantErr: models.ErrMissingAllowedHosts,
},
{
settings: models.InfinitySettings{URL: "https://foo"},
},
{
settings: models.InfinitySettings{TLSClientAuth: true},
wantErr: models.ErrMissingAllowedHosts,
},
{
settings: models.InfinitySettings{TLSClientAuth: true},
wantErr: models.ErrMissingAllowedHosts,
},
}
for _, tt := range tests {
Expand Down
15 changes: 2 additions & 13 deletions pkg/pluginhost/handler_querydata.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana-infinity-datasource/pkg/models"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
Expand Down Expand Up @@ -103,13 +102,8 @@ func QueryDataQuery(ctx context.Context, query models.Query, infClient infinity.
query, _ := infinity.UpdateQueryWithReferenceData(ctx, query, infClient.Settings)
switch query.Source {
case "url", "azure-blob":
if infClient.Settings.AuthenticationMethod != models.AuthenticationMethodAzureBlob && infClient.Settings.AuthenticationMethod != models.AuthenticationMethodNone && len(infClient.Settings.AllowedHosts) < 1 {
response.Error = errors.New("datasource is missing allowed hosts/URLs. Configure it in the datasource settings page for enhanced security")
response.ErrorSource = backend.ErrorSourceDownstream
return response
}
if infClient.Settings.HaveSecureHeaders() && len(infClient.Settings.AllowedHosts) < 1 {
response.Error = errors.New("datasource is missing allowed hosts/URLs. Configure it in the datasource settings page for enhanced security")
if infClient.Settings.DoesAllowedHostsRequired() && len(infClient.Settings.AllowedHosts) < 1 {
response.Error = models.ErrMissingAllowedHosts
response.ErrorSource = backend.ErrorSourceDownstream
return response
}
Expand All @@ -130,11 +124,6 @@ func QueryDataQuery(ctx context.Context, query models.Query, infClient infinity.
response.ErrorSource = errorsource.SourceError(backend.ErrorSourcePlugin, err, false).Source()
return response
}
if frame != nil && infClient.Settings.AuthenticationMethod != models.AuthenticationMethodAzureBlob && infClient.Settings.AuthenticationMethod != models.AuthenticationMethodNone && infClient.Settings.AuthenticationMethod != "" && len(infClient.Settings.AllowedHosts) < 1 {
frame.AppendNotices(data.Notice{
Text: "Datasource is missing allowed hosts/URLs. Configure it in the datasource settings page for enhanced security.",
})
}
if frame != nil {
frame, _ = infinity.WrapMetaForRemoteQuery(ctx, infClient.Settings, frame, nil, query)
response.Frames = append(response.Frames, frame)
Expand Down
4 changes: 2 additions & 2 deletions pkg/testsuite/handler_querydata_errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestErrors(t *testing.T) {
}, *client, map[string]string{}, backend.PluginContext{})
require.NotNil(t, res.Error)
require.Equal(t, backend.ErrorSourceDownstream, res.ErrorSource)
require.ErrorIs(t, res.Error, infinity.ErrUnsuccessfulHTTPResponseStatus)
require.ErrorIs(t, res.Error, models.ErrUnsuccessfulHTTPResponseStatus)
require.Equal(t, "error while performing the infinity query. unsuccessful HTTP response. 403 Forbidden", res.Error.Error())
})
t.Run("fail with incorrect response from server", func(t *testing.T) {
Expand All @@ -58,7 +58,7 @@ func TestErrors(t *testing.T) {
}, *client, map[string]string{}, backend.PluginContext{})
require.NotNil(t, res.Error)
require.Equal(t, backend.ErrorSourceDownstream, res.ErrorSource)
require.ErrorIs(t, res.Error, infinity.ErrParsingResponseBodyAsJson)
require.ErrorIs(t, res.Error, models.ErrParsingResponseBodyAsJson)
require.Equal(t, "error while performing the infinity query. unable to parse response body as JSON. unexpected end of JSON input", res.Error.Error())
})
t.Run("fail with incorrect JSONata", func(t *testing.T) {
Expand Down

0 comments on commit 4f9df7a

Please sign in to comment.