diff --git a/conf/defaults.ini b/conf/defaults.ini
index b609005aba0..925bf1acc23 100644
--- a/conf/defaults.ini
+++ b/conf/defaults.ini
@@ -280,10 +280,10 @@ editors_can_admin = false
login_cookie_name = grafana_session
# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation (token_rotation_interval_minutes).
-login_maximum_inactive_lifetime_duration =
+login_maximum_inactive_lifetime_duration =
# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month).
-login_maximum_lifetime_duration =
+login_maximum_lifetime_duration =
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
token_rotation_interval_minutes = 10
@@ -307,6 +307,9 @@ oauth_state_cookie_max_age = 600
# limit of api_key seconds to live before expiration
api_key_max_seconds_to_live = -1
+# Set to true to enable SigV4 authentication option for HTTP-based datasources
+sigv4_auth_enabled = false
+
#################################### Anonymous Auth ######################
[auth.anonymous]
# enable anonymous access
diff --git a/conf/sample.ini b/conf/sample.ini
index 95d7dda6a72..3fd5dd58e5e 100644
--- a/conf/sample.ini
+++ b/conf/sample.ini
@@ -278,11 +278,11 @@
# Login cookie name
;login_cookie_name = grafana_session
-# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation
-;login_maximum_inactive_lifetime_duration =
+# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation
+;login_maximum_inactive_lifetime_duration =
# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month).
-;login_maximum_lifetime_duration =
+;login_maximum_lifetime_duration =
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
;token_rotation_interval_minutes = 10
@@ -306,6 +306,9 @@
# limit of api_key seconds to live before expiration
;api_key_max_seconds_to_live = -1
+# Set to true to enable SigV4 authentication option for HTTP-based datasources.
+;sigv4_auth_enabled = false
+
#################################### Anonymous Auth ######################
[auth.anonymous]
# enable anonymous access
@@ -813,4 +816,4 @@
;use_browser_locale = false
# Default timezone for user preferences. Options are 'browser' for the browser local timezone or a timezone name from IANA Time Zone database, e.g. 'UTC' or 'Europe/Amsterdam' etc.
-;default_timezone = browser
\ No newline at end of file
+;default_timezone = browser
diff --git a/docs/sources/administration/configuration.md b/docs/sources/administration/configuration.md
index c0f0d641c9c..000903e4f40 100644
--- a/docs/sources/administration/configuration.md
+++ b/docs/sources/administration/configuration.md
@@ -649,6 +649,12 @@ Administrators can increase this if they experience OAuth login state mismatch e
Limit of API key seconds to live before expiration. Default is -1 (unlimited).
+### sigv4_auth_enabled
+
+> Only available in Grafana 7.3+.
+
+Set to `true` to enable the AWS Signature Version 4 Authentication option for HTTP-based datasources. Default is `false`.
+
## [auth.anonymous]
diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts
index 0d85fcc581a..215f364f59f 100644
--- a/packages/grafana-data/src/types/config.ts
+++ b/packages/grafana-data/src/types/config.ts
@@ -83,6 +83,7 @@ export interface GrafanaConfig {
authProxyEnabled: boolean;
exploreEnabled: boolean;
ldapEnabled: boolean;
+ sigV4AuthEnabled: boolean;
samlEnabled: boolean;
autoAssignOrg: boolean;
verifyEmailEnabled: boolean;
diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts
index 7136cbd740a..0636af0219a 100644
--- a/packages/grafana-runtime/src/config.ts
+++ b/packages/grafana-runtime/src/config.ts
@@ -36,6 +36,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
authProxyEnabled = false;
exploreEnabled = false;
ldapEnabled = false;
+ sigV4AuthEnabled = false;
samlEnabled = false;
autoAssignOrg = true;
verifyEmailEnabled = false;
diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx
index da2a62446c9..7bb270bdab1 100644
--- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx
+++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx
@@ -13,7 +13,7 @@ It is used in a `ConfigEditor` for data source plugins. You can find more exampl
### Example usage
```jsx
export const ConfigEditor = (props: Props) => {
- const { options, onOptionsChange } = props;
+ const { options, onOptionsChange, config } = props;
return (
<>
{
dataSourceConfig={options}
showAccessOptions={true}
onChange={onOptionsChange}
+ sigV4AuthEnabled={false}
/>
{/* Additional configuration settings for your data source plugin.*/}
diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx
index 1db43f75f68..53f2146d3ef 100644
--- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx
+++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx
@@ -12,6 +12,7 @@ import { Icon } from '../Icon/Icon';
import { FormField } from '../FormField/FormField';
import { InlineFormLabel } from '../FormLabel/FormLabel';
import { TagsInput } from '../TagsInput/TagsInput';
+import { SigV4AuthSettings } from './SigV4AuthSettings';
import { useTheme } from '../../themes';
import { HttpSettingsProps } from './types';
@@ -55,7 +56,7 @@ const HttpAccessHelp = () => (
);
export const DataSourceHttpSettings: React.FC = props => {
- const { defaultUrl, dataSourceConfig, onChange, showAccessOptions } = props;
+ const { defaultUrl, dataSourceConfig, onChange, showAccessOptions, sigV4AuthToggleEnabled } = props;
let urlTooltip;
const [isAccessHelpVisible, setIsAccessHelpVisible] = useState(false);
const theme = useTheme();
@@ -189,6 +190,21 @@ export const DataSourceHttpSettings: React.FC = props => {
/>
+ {sigV4AuthToggleEnabled && (
+
+ {
+ onSettingsChange({
+ jsonData: { ...dataSourceConfig.jsonData, sigV4Auth: event!.currentTarget.checked },
+ });
+ }}
+ />
+
+ )}
+
{dataSourceConfig.access === 'proxy' && (
= props => {
>
)}
+ {dataSourceConfig.jsonData.sigV4Auth && }
+
{(dataSourceConfig.jsonData.tlsAuth || dataSourceConfig.jsonData.tlsAuthWithCACert) && (
)}
diff --git a/packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx
new file mode 100644
index 00000000000..40f6d23893f
--- /dev/null
+++ b/packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx
@@ -0,0 +1,250 @@
+import React from 'react';
+import { HttpSettingsProps } from './types';
+import { SelectableValue } from '@grafana/data';
+import { Button, InlineFormLabel, Input } from '..';
+import Select from '../Forms/Legacy/Select/Select';
+
+export const SigV4AuthSettings: React.FC = props => {
+ const { dataSourceConfig } = props;
+
+ const authProviderOptions = [
+ { label: 'AWS SDK Default', value: 'default' },
+ { label: 'Access & secret key', value: 'keys' },
+ { label: 'Credentials file', value: 'credentials' },
+ ] as SelectableValue[];
+
+ const regions = [
+ { value: 'af-south-1', label: 'af-south-1' },
+ { value: 'ap-east-1', label: 'ap-east-1' },
+ { value: 'ap-northeast-1', label: 'ap-northeast-1' },
+ { value: 'ap-northeast-2', label: 'ap-northeast-2' },
+ { value: 'ap-northeast-3', label: 'ap-northeast-3' },
+ { value: 'ap-south-1', label: 'ap-south-1' },
+ { value: 'ap-southeast-1', label: 'ap-southeast-1' },
+ { value: 'ap-southeast-2', label: 'ap-southeast-2' },
+ { value: 'ca-central-1', label: 'ca-central-1' },
+ { value: 'cn-north-1', label: 'cn-north-1' },
+ { value: 'cn-northwest-1', label: 'cn-northwest-1' },
+ { value: 'eu-central-1', label: 'eu-central-1' },
+ { value: 'eu-north-1', label: 'eu-north-1' },
+ { value: 'eu-west-1', label: 'eu-west-1' },
+ { value: 'eu-west-2', label: 'eu-west-2' },
+ { value: 'eu-west-3', label: 'eu-west-3' },
+ { value: 'me-south-1', label: 'me-south-1' },
+ { value: 'sa-east-1', label: 'sa-east-1' },
+ { value: 'us-east-1', label: 'us-east-1' },
+ { value: 'us-east-2', label: 'us-east-2' },
+ { value: 'us-gov-east-1', label: 'us-gov-east-1' },
+ { value: 'us-gov-west-1', label: 'us-gov-west-1' },
+ { value: 'us-iso-east-1', label: 'us-iso-east-1' },
+ { value: 'us-isob-east-1', label: 'us-isob-east-1' },
+ { value: 'us-west-1', label: 'us-west-1' },
+ { value: 'us-west-2', label: 'us-west-2' },
+ ] as SelectableValue[];
+
+ const onSecureJsonDataReset = (fieldName: string) => {
+ const state = {
+ ...dataSourceConfig,
+ secureJsonData: {
+ ...dataSourceConfig.secureJsonData,
+ [fieldName]: '',
+ },
+ secureJsonFields: {
+ ...dataSourceConfig.secureJsonFields,
+ [fieldName]: false,
+ },
+ };
+
+ props.onChange(state);
+ };
+
+ const onSecureJsonDataChange = (fieldName: string, fieldValue: string) => {
+ const state = {
+ ...dataSourceConfig,
+ secureJsonData: {
+ ...dataSourceConfig.secureJsonData,
+ [fieldName]: fieldValue,
+ },
+ };
+
+ props.onChange(state);
+ };
+
+ const onJsonDataChange = (fieldName: string, fieldValue: string) => {
+ const state = {
+ ...dataSourceConfig,
+ jsonData: {
+ ...dataSourceConfig.jsonData,
+ [fieldName]: fieldValue,
+ },
+ };
+
+ props.onChange(state);
+ };
+
+ return (
+ <>
+ Sigv4 Details
+
+
+
+
+ Authentication Provider
+
+ authProvider.value === dataSourceConfig.jsonData.authType
+ )}
+ options={authProviderOptions}
+ defaultValue={dataSourceConfig.jsonData.authType || 'keys'}
+ onChange={option => {
+ if (dataSourceConfig.jsonData.authType === 'arn' && option.value !== 'arn') {
+ delete dataSourceConfig.jsonData.assumeRoleArn;
+ delete dataSourceConfig.jsonData.externalId;
+ }
+ onJsonDataChange('authType', option.value);
+ }}
+ />
+
+
+ {dataSourceConfig.jsonData.authType === 'credentials' && (
+
+
+
+ Credentials Profile Name
+
+
+ onJsonDataChange('profile', e.currentTarget.value)}
+ />
+
+
+
+ )}
+ {dataSourceConfig.jsonData.authType === 'keys' && (
+
+ {dataSourceConfig.secureJsonFields?.accessKey ? (
+
+
+ Access Key ID
+
+
+
+
+ onSecureJsonDataReset('accessKey')}>
+ Reset
+
+
+
+
+ ) : (
+
+
+
Access Key ID
+
+ onSecureJsonDataChange('accessKey', e.currentTarget.value)}
+ />
+
+
+
+ )}
+ {dataSourceConfig.secureJsonFields?.secretKey ? (
+
+
+ Secret Access Key
+
+
+
+
+ onSecureJsonDataReset('secretKey')}>
+ Reset
+
+
+
+
+ ) : (
+
+
+
Secret Access Key
+
+ onSecureJsonDataChange('secretKey', e.currentTarget.value)}
+ />
+
+
+
+ )}
+
+ )}
+
+
+
+ Assume Role ARN
+
+
+ onJsonDataChange('assumeRoleArn', e.currentTarget.value)}
+ />
+
+
+
+
+
+ External ID
+
+
+ onJsonDataChange('externalId', e.currentTarget.value)}
+ />
+
+
+
+
+
+
+
+ Default Region
+
+ region.value === dataSourceConfig.jsonData.region)}
+ options={regions}
+ defaultValue={dataSourceConfig.jsonData.region || ''}
+ onChange={option => onJsonDataChange('region', option.value)}
+ />
+
+
+
+ >
+ );
+};
diff --git a/packages/grafana-ui/src/components/DataSourceSettings/types.ts b/packages/grafana-ui/src/components/DataSourceSettings/types.ts
index 8cb66ef0ebe..95792ca9ceb 100644
--- a/packages/grafana-ui/src/components/DataSourceSettings/types.ts
+++ b/packages/grafana-ui/src/components/DataSourceSettings/types.ts
@@ -12,4 +12,6 @@ export interface HttpSettingsProps extends HttpSettingsBaseProps {
defaultUrl: string;
/** Show the http access help box */
showAccessOptions?: boolean;
+ /** Show the SigV4 auth toggle option */
+ sigV4AuthToggleEnabled?: boolean;
}
diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go
index 44ec0f970a7..f0266f78839 100644
--- a/pkg/api/frontendsettings.go
+++ b/pkg/api/frontendsettings.go
@@ -203,6 +203,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
"alertingMinInterval": setting.AlertingMinInterval,
"autoAssignOrg": setting.AutoAssignOrg,
"verifyEmailEnabled": setting.VerifyEmailEnabled,
+ "sigV4AuthEnabled": setting.SigV4AuthEnabled,
"exploreEnabled": setting.ExploreEnabled,
"googleAnalyticsId": setting.GoogleAnalyticsId,
"disableLoginForm": setting.DisableLoginForm,
diff --git a/pkg/models/datasource_cache.go b/pkg/models/datasource_cache.go
index de547b7251a..f2728ad0742 100644
--- a/pkg/models/datasource_cache.go
+++ b/pkg/models/datasource_cache.go
@@ -69,6 +69,7 @@ type dataSourceTransport struct {
datasourceName string
headers map[string]string
transport *http.Transport
+ next http.RoundTripper
}
func instrumentRoundtrip(datasourceName string, next http.RoundTripper) promhttp.RoundTripperFunc {
@@ -108,7 +109,7 @@ func (d *dataSourceTransport) RoundTrip(req *http.Request) (*http.Response, erro
req.Header.Set(key, value)
}
- return instrumentRoundtrip(d.datasourceName, d.transport).RoundTrip(req)
+ return instrumentRoundtrip(d.datasourceName, d.next).RoundTrip(req)
}
type cachedTransport struct {
@@ -133,6 +134,7 @@ func (ds *DataSource) GetHttpClient() (*http.Client, error) {
}, nil
}
+// Creates a HTTP Transport middleware chain
func (ds *DataSource) GetHttpTransport() (*dataSourceTransport, error) {
ptc.Lock()
defer ptc.Unlock()
@@ -163,10 +165,19 @@ func (ds *DataSource) GetHttpTransport() (*dataSourceTransport, error) {
IdleConnTimeout: 90 * time.Second,
}
+ // Set default next round tripper to the default transport
+ next := http.RoundTripper(transport)
+
+ // Add SigV4 middleware if enabled, which will then defer to the default transport
+ if ds.JsonData != nil && ds.JsonData.Get("sigV4Auth").MustBool() && setting.SigV4AuthEnabled {
+ next = ds.sigV4Middleware(transport)
+ }
+
dsTransport := &dataSourceTransport{
+ datasourceName: ds.Name,
headers: customHeaders,
transport: transport,
- datasourceName: ds.Name,
+ next: next,
}
ptc.cache[ds.Id] = cachedTransport{
@@ -177,6 +188,23 @@ func (ds *DataSource) GetHttpTransport() (*dataSourceTransport, error) {
return dsTransport, nil
}
+func (ds *DataSource) sigV4Middleware(next http.RoundTripper) http.RoundTripper {
+ decrypted := ds.DecryptedValues()
+
+ return &SigV4Middleware{
+ Config: &Config{
+ AccessKey: decrypted["accessKey"],
+ SecretKey: decrypted["secretKey"],
+ Region: ds.JsonData.Get("region").MustString(),
+ AssumeRoleARN: ds.JsonData.Get("assumeRoleArn").MustString(),
+ AuthType: ds.JsonData.Get("authType").MustString(),
+ ExternalID: ds.JsonData.Get("externalId").MustString(),
+ Profile: ds.JsonData.Get("profile").MustString(),
+ },
+ Next: next,
+ }
+}
+
func (ds *DataSource) GetTLSConfig() (*tls.Config, error) {
var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool
if ds.JsonData != nil {
diff --git a/pkg/models/datasource_cache_test.go b/pkg/models/datasource_cache_test.go
index 948265e2105..c6458360f19 100644
--- a/pkg/models/datasource_cache_test.go
+++ b/pkg/models/datasource_cache_test.go
@@ -291,6 +291,78 @@ func TestDataSourceDecryptionCache(t *testing.T) {
})
}
+func TestDataSourceSigV4Auth(t *testing.T) {
+ Convey("When caching a datasource proxy with middleware", t, func() {
+ clearDSProxyCache()
+ origEnabled := setting.SigV4AuthEnabled
+ setting.SigV4AuthEnabled = true
+ t.Cleanup(func() {
+ setting.SigV4AuthEnabled = origEnabled
+ })
+
+ json, err := simplejson.NewJson([]byte(`{ "sigV4Auth": true }`))
+ So(err, ShouldBeNil)
+
+ ds := DataSource{
+ JsonData: json,
+ }
+
+ t, err := ds.GetHttpTransport()
+ So(err, ShouldBeNil)
+
+ Convey("SigV4 is in middleware chain if configured in JsonData", func() {
+ m1, ok := t.next.(*SigV4Middleware)
+ So(ok, ShouldEqual, true)
+
+ _, ok = m1.Next.(*http.Transport)
+ So(ok, ShouldEqual, true)
+ })
+ })
+
+ Convey("When caching a datasource proxy with middleware", t, func() {
+ clearDSProxyCache()
+ origEnabled := setting.SigV4AuthEnabled
+ setting.SigV4AuthEnabled = true
+ t.Cleanup(func() {
+ setting.SigV4AuthEnabled = origEnabled
+ })
+
+ ds := DataSource{}
+
+ t, err := ds.GetHttpTransport()
+ So(err, ShouldBeNil)
+
+ Convey("Should not include sigV4 middleware if not configured in JsonData", func() {
+ _, ok := t.next.(*http.Transport)
+ So(ok, ShouldEqual, true)
+ })
+ })
+
+ Convey("When caching a datasource proxy with middleware", t, func() {
+ clearDSProxyCache()
+ origEnabled := setting.SigV4AuthEnabled
+ setting.SigV4AuthEnabled = false
+ t.Cleanup(func() {
+ setting.SigV4AuthEnabled = origEnabled
+ })
+
+ json, err := simplejson.NewJson([]byte(`{ "sigV4Auth": true }`))
+ So(err, ShouldBeNil)
+
+ ds := DataSource{
+ JsonData: json,
+ }
+
+ t, err := ds.GetHttpTransport()
+ So(err, ShouldBeNil)
+
+ Convey("Should not include sigV4 middleware if not configured in app config", func() {
+ _, ok := t.next.(*http.Transport)
+ So(ok, ShouldEqual, true)
+ })
+ })
+}
+
func clearDSProxyCache() {
ptc.Lock()
defer ptc.Unlock()
diff --git a/pkg/models/sigv4.go b/pkg/models/sigv4.go
new file mode 100644
index 00000000000..10751870efc
--- /dev/null
+++ b/pkg/models/sigv4.go
@@ -0,0 +1,110 @@
+package models
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "time"
+
+ "github.com/aws/aws-sdk-go/aws"
+
+ "github.com/aws/aws-sdk-go/aws/credentials/stscreds"
+ "github.com/aws/aws-sdk-go/aws/session"
+
+ "github.com/aws/aws-sdk-go/aws/defaults"
+
+ "github.com/aws/aws-sdk-go/aws/credentials"
+ v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
+)
+
+type AuthType string
+
+const (
+ Default AuthType = "default"
+ Keys AuthType = "keys"
+ Credentials AuthType = "credentials"
+)
+
+type SigV4Middleware struct {
+ Config *Config
+ Next http.RoundTripper
+}
+
+type Config struct {
+ AuthType string
+
+ Profile string
+
+ AccessKey string
+ SecretKey string
+
+ AssumeRoleARN string
+ ExternalID string
+ Region string
+}
+
+func (m *SigV4Middleware) RoundTrip(req *http.Request) (*http.Response, error) {
+ _, err := m.signRequest(req)
+ if err != nil {
+ return nil, err
+ }
+
+ if m.Next == nil {
+ return http.DefaultTransport.RoundTrip(req)
+ }
+
+ return m.Next.RoundTrip(req)
+}
+
+func (m *SigV4Middleware) signRequest(req *http.Request) (http.Header, error) {
+ signer, err := m.signer()
+ if err != nil {
+ return nil, err
+ }
+
+ if req.Body != nil {
+ // consume entire request body so that the signer can generate a hash from the contents
+ body, err := ioutil.ReadAll(req.Body)
+ if err != nil {
+ return nil, err
+ }
+ return signer.Sign(req, bytes.NewReader(body), "grafana", m.Config.Region, time.Now().UTC())
+ }
+ return signer.Sign(req, nil, "grafana", m.Config.Region, time.Now().UTC())
+}
+
+func (m *SigV4Middleware) signer() (*v4.Signer, error) {
+ c, err := m.credentials()
+ if err != nil {
+ return nil, err
+ }
+
+ if m.Config.AssumeRoleARN != "" {
+ s, err := session.NewSession(&aws.Config{
+ Region: aws.String(m.Config.Region),
+ Credentials: c},
+ )
+ if err != nil {
+ return nil, err
+ }
+ return v4.NewSigner(stscreds.NewCredentials(s, m.Config.AssumeRoleARN)), nil
+ }
+
+ return v4.NewSigner(c), nil
+}
+
+func (m *SigV4Middleware) credentials() (*credentials.Credentials, error) {
+ authType := AuthType(m.Config.AuthType)
+
+ switch authType {
+ case Default:
+ return defaults.CredChain(defaults.Config(), defaults.Handlers()), nil
+ case Keys:
+ return credentials.NewStaticCredentials(m.Config.AccessKey, m.Config.SecretKey, ""), nil
+ case Credentials:
+ return credentials.NewSharedCredentials("", m.Config.Profile), nil
+ }
+
+ return nil, fmt.Errorf("unrecognized authType: %s", authType)
+}
diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go
index 46522ddb9d2..9bd751b0605 100644
--- a/pkg/setting/setting.go
+++ b/pkg/setting/setting.go
@@ -143,6 +143,7 @@ var (
AdminPassword string
LoginCookieName string
LoginMaxLifetime time.Duration
+ SigV4AuthEnabled bool
AnonymousEnabled bool
AnonymousOrgName string
@@ -280,6 +281,7 @@ type Cfg struct {
LoginMaxInactiveLifetime time.Duration
LoginMaxLifetime time.Duration
TokenRotationIntervalMinutes int
+ SigV4AuthEnabled bool
// OAuth
OAuthCookieMaxAge int
@@ -990,6 +992,10 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
cfg.OAuthCookieMaxAge = auth.Key("oauth_state_cookie_max_age").MustInt(600)
SignoutRedirectUrl = valueAsString(auth, "signout_redirect_url", "")
+ // SigV4
+ SigV4AuthEnabled = auth.Key("sigv4_auth_enabled").MustBool(false)
+ cfg.SigV4AuthEnabled = SigV4AuthEnabled
+
// SAML auth
cfg.SAMLEnabled = iniFile.Section("auth.saml").Key("enabled").MustBool(false)
diff --git a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx
index d2b9991b79b..7a3c1a9d72d 100644
--- a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx
+++ b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx
@@ -41,7 +41,7 @@ export class ConfigEditor extends PureComponent {
this.loadRegionsPromise = makePromiseCancelable(this.loadRegions());
this.loadRegionsPromise.promise.catch(({ isCanceled }) => {
if (isCanceled) {
- console.warn('Cloud Watch ConfigEditor has unmounted, intialization was canceled');
+ console.warn('Cloud Watch ConfigEditor has unmounted, initialization was canceled');
}
});
}
diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx
index 9d6e87e8f72..1a6431723e8 100644
--- a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx
+++ b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx
@@ -5,6 +5,7 @@ import { ElasticsearchOptions } from '../types';
import { defaultMaxConcurrentShardRequests, ElasticDetails } from './ElasticDetails';
import { LogsConfig } from './LogsConfig';
import { DataLinks } from './DataLinks';
+import { config } from 'app/core/config';
export type Props = DataSourcePluginOptionsEditorProps;
export const ConfigEditor = (props: Props) => {
@@ -34,6 +35,7 @@ export const ConfigEditor = (props: Props) => {
dataSourceConfig={options}
showAccessOptions={true}
onChange={onOptionsChange}
+ sigV4AuthToggleEnabled={config.sigV4AuthEnabled}
/>
diff --git a/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.tsx
index bfe24d40316..93b77ceddb7 100644
--- a/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.tsx
+++ b/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.tsx
@@ -3,6 +3,7 @@ import { DataSourceHttpSettings } from '@grafana/ui';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { PromSettings } from './PromSettings';
import { PromOptions } from '../types';
+import { config } from 'app/core/config';
export type Props = DataSourcePluginOptionsEditorProps;
export const ConfigEditor = (props: Props) => {
@@ -14,6 +15,7 @@ export const ConfigEditor = (props: Props) => {
dataSourceConfig={options}
showAccessOptions={true}
onChange={onOptionsChange}
+ sigV4AuthToggleEnabled={config.sigV4AuthEnabled}
/>