Skip to content

Commit

Permalink
Insecure headers warning (#809)
Browse files Browse the repository at this point in the history
  • Loading branch information
yesoreyeram authored Apr 17, 2024
1 parent 6c32082 commit 17ab20b
Show file tree
Hide file tree
Showing 11 changed files with 574 additions and 30 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## 2.6.2

🚀 New settings to block/allow/warn sensitive queries in the dashboard. Read more about this in [the docs](https://grafana.com/docs/plugins/yesoreyeram-infinity-datasource/latest/setup/configuration/).

## 2.6.1

🐛 UQL dependency updated to `0.0.22` from `0.0.21`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "grafana-infinity-datasource",
"version": "2.6.1",
"version": "2.6.2",
"description": "JSON, CSV, XML, GraphQL, HTML and REST API datasource for Grafana. Do infinite things with Grafana. Transform data with UQL/GROQ. Visualize data from many apis, RSS/ATOM feeds directly",
"main": "dist/module.js",
"scripts": {
Expand Down
39 changes: 29 additions & 10 deletions pkg/infinity/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func WrapMetaForInlineQuery(ctx context.Context, frame *data.Frame, err error, q
return frame, err
}

func WrapMetaForRemoteQuery(ctx context.Context, frame *data.Frame, err error, query models.Query) (*data.Frame, error) {
func WrapMetaForRemoteQuery(ctx context.Context, settings models.InfinitySettings, frame *data.Frame, err error, query models.Query) (*data.Frame, error) {
if frame == nil {
frame = data.NewFrame(query.RefID)
}
Expand All @@ -71,17 +71,9 @@ func WrapMetaForRemoteQuery(ctx context.Context, frame *data.Frame, err error, q
if frame.Meta.Notices == nil {
frame.Meta.Notices = []data.Notice{}
}
for _, h := range query.URLOptions.Headers {
if strings.EqualFold(h.Key, headerKeyAuthorization) {
frame.Meta.Notices = append(frame.Meta.Notices, data.Notice{
Severity: data.NoticeSeverityWarning,
Text: fmt.Sprintf("for security reasons, don't include headers such as %s in the query. Instead, add them in the config where possible", h.Key),
Inspect: data.InspectTypeData,
})
}
}
frame = ApplyLogMeta(ctx, frame, query)
frame = ApplyTraceMeta(ctx, frame, query)
frame = ApplyNotices(ctx, settings, frame, query)
return frame, err
}

Expand Down Expand Up @@ -128,3 +120,30 @@ func ApplyTraceMeta(ctx context.Context, frame *data.Frame, query models.Query)
}
return frame
}

func ApplyNotices(ctx context.Context, settings models.InfinitySettings, frame *data.Frame, query models.Query) *data.Frame {
if frame.Meta == nil {
frame.Meta = &data.FrameMeta{}
}
if frame.Meta.Notices == nil {
frame.Meta.Notices = []data.Notice{}
}
if settings.UnsecuredQueryHandling == models.UnsecuredQueryHandlingWarn {
frame.Meta.Notices = append(frame.Meta.Notices, GetSecureHeaderWarnings(query)...)
}
return frame
}

func GetSecureHeaderWarnings(query models.Query) []data.Notice {
notices := []data.Notice{}
for _, h := range query.URLOptions.Headers {
if strings.EqualFold(h.Key, headerKeyAuthorization) {
notices = append(notices, data.Notice{
Severity: data.NoticeSeverityWarning,
Text: fmt.Sprintf("for security reasons, don't include headers such as %s in the query. Instead, add them in the config where possible", h.Key),
Inspect: data.InspectTypeData,
})
}
}
return notices
}
17 changes: 16 additions & 1 deletion pkg/models/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ const (
ProxyTypeUrl ProxyType = "url"
)

type UnsecuredQueryHandlingMode string

const (
UnsecuredQueryHandlingAllow UnsecuredQueryHandlingMode = "allow"
UnsecuredQueryHandlingWarn UnsecuredQueryHandlingMode = "warn"
UnsecuredQueryHandlingDeny UnsecuredQueryHandlingMode = "deny"
)

type InfinitySettings struct {
UID string
Name string
Expand Down Expand Up @@ -107,6 +115,7 @@ type InfinitySettings struct {
AzureBlobAccountUrl string
AzureBlobAccountName string
AzureBlobAccountKey string
UnsecuredQueryHandling UnsecuredQueryHandlingMode
// ProxyOpts is used for Secure Socks Proxy configuration
ProxyOpts httpclient.Options
}
Expand Down Expand Up @@ -184,12 +193,14 @@ type InfinitySettingsJson struct {
TimeoutInSeconds int64 `json:"timeoutInSeconds,omitempty"`
ProxyType ProxyType `json:"proxy_type,omitempty"`
ProxyUrl string `json:"proxy_url,omitempty"`
AllowedHosts []string `json:"allowedHosts,omitempty"`
ReferenceData []RefData `json:"refData,omitempty"`
CustomHealthCheckEnabled bool `json:"customHealthCheckEnabled,omitempty"`
CustomHealthCheckUrl string `json:"customHealthCheckUrl,omitempty"`
AzureBlobAccountUrl string `json:"azureBlobAccountUrl,omitempty"`
AzureBlobAccountName string `json:"azureBlobAccountName,omitempty"`
// Security
AllowedHosts []string `json:"allowedHosts,omitempty"`
UnsecuredQueryHandling UnsecuredQueryHandlingMode `json:"unsecuredQueryHandling,omitempty"`
}

func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings) (settings InfinitySettings, err error) {
Expand Down Expand Up @@ -235,6 +246,10 @@ func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings
if infJson.TimeoutInSeconds > 0 {
settings.TimeoutInSeconds = infJson.TimeoutInSeconds
}
settings.UnsecuredQueryHandling = infJson.UnsecuredQueryHandling
if settings.UnsecuredQueryHandling == "" {
settings.UnsecuredQueryHandling = UnsecuredQueryHandlingWarn
}
if len(infJson.AllowedHosts) > 0 {
settings.AllowedHosts = infJson.AllowedHosts
}
Expand Down
36 changes: 20 additions & 16 deletions pkg/models/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,15 @@ func TestLoadSettings(t *testing.T) {
},
},
wantSettings: models.InfinitySettings{
URL: "https://foo.com",
UserName: "user",
Password: "password",
TimeoutInSeconds: 60,
ApiKeyType: "header",
BasicAuthEnabled: false,
ProxyType: models.ProxyTypeEnv,
AuthenticationMethod: models.AuthenticationMethodNone,
URL: "https://foo.com",
UserName: "user",
Password: "password",
TimeoutInSeconds: 60,
ApiKeyType: "header",
BasicAuthEnabled: false,
ProxyType: models.ProxyTypeEnv,
AuthenticationMethod: models.AuthenticationMethodNone,
UnsecuredQueryHandling: models.UnsecuredQueryHandlingWarn,
OAuth2Settings: models.OAuth2Settings{
EndpointParams: map[string]string{},
},
Expand Down Expand Up @@ -92,14 +93,15 @@ func TestLoadSettings(t *testing.T) {
},
},
wantSettings: models.InfinitySettings{
URL: "https://foo.com",
UserName: "user",
Password: "password",
TimeoutInSeconds: 30,
ApiKeyType: "header",
BasicAuthEnabled: true,
AuthenticationMethod: models.AuthenticationMethodBasic,
ProxyType: models.ProxyTypeEnv,
URL: "https://foo.com",
UserName: "user",
Password: "password",
TimeoutInSeconds: 30,
ApiKeyType: "header",
BasicAuthEnabled: true,
AuthenticationMethod: models.AuthenticationMethodBasic,
ProxyType: models.ProxyTypeEnv,
UnsecuredQueryHandling: models.UnsecuredQueryHandlingWarn,
OAuth2Settings: models.OAuth2Settings{
EndpointParams: map[string]string{},
},
Expand Down Expand Up @@ -155,6 +157,7 @@ func TestAllSettingsAgainstFrontEnd(t *testing.T) {
"allowedHosts": ["host1","host2"],
"customHealthCheckEnabled" : true,
"customHealthCheckUrl" : "https://foo-check/",
"unsecuredQueryHandling" : "deny",
"aws" : {
"authType" : "keys",
"region" : "region1",
Expand Down Expand Up @@ -240,6 +243,7 @@ func TestAllSettingsAgainstFrontEnd(t *testing.T) {
TimeoutInSeconds: 30,
CustomHealthCheckEnabled: true,
CustomHealthCheckUrl: "https://foo-check/",
UnsecuredQueryHandling: models.UnsecuredQueryHandlingDeny,
CustomHeaders: map[string]string{
"header1": "headervalue1",
},
Expand Down
9 changes: 7 additions & 2 deletions pkg/pluginhost/handler_querydata.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,16 @@ func QueryDataQuery(ctx context.Context, query models.Query, infClient infinity.
response.Error = errors.New("datasource is missing allowed hosts/URLs. Configure it in the datasource settings page for enhanced security")
return response
}
if notices := infinity.GetSecureHeaderWarnings(query); infClient.Settings.UnsecuredQueryHandling == models.UnsecuredQueryHandlingDeny && len(notices) > 0 {
response.Error = errors.New("query contain sensitive content and denied by the unsecuredQueryHandling config")
response.Status = backend.StatusForbidden
return response
}
frame, err := infinity.GetFrameForURLSources(ctx, query, infClient, requestHeaders)
if err != nil {
logger.Error("error while performing the infinity query", "msg", err.Error())
if frame != nil {
frame, _ = infinity.WrapMetaForRemoteQuery(ctx, frame, err, query)
frame, _ = infinity.WrapMetaForRemoteQuery(ctx, infClient.Settings, frame, err, query)
response.Frames = append(response.Frames, frame)
}
span.RecordError(err)
Expand All @@ -144,7 +149,7 @@ func QueryDataQuery(ctx context.Context, query models.Query, infClient infinity.
})
}
if frame != nil {
frame, _ = infinity.WrapMetaForRemoteQuery(ctx, frame, nil, query)
frame, _ = infinity.WrapMetaForRemoteQuery(ctx, infClient.Settings, frame, nil, query)
response.Frames = append(response.Frames, frame)
}
case "inline":
Expand Down
Loading

0 comments on commit 17ab20b

Please sign in to comment.