From 7d63b2c4739e167a41e7501d9d038b4b602dc003 Mon Sep 17 00:00:00 2001 From: Will Browne Date: Thu, 8 Oct 2020 10:03:20 +0200 Subject: [PATCH] Auth: Add Sigv4 auth option to datasources (#27552) * create transport chain * add frontend * remove log * inline field updates * allow ARN, Credentials + Keys auth in frontend * configure credentials * add tests and refactor * update frontend json field names * fix tests * fix comment * add app config flag * refactor tests * add return field for tests * add flag for UI display * update comment * move logic * fix config * pass config through props * update docs * pr feedback and add docs coverage * shorten settings filename * fix imports * revert docs changes * remove log line * wrap up next as round tripper * only propagate required config * remove unused import * remove ARN option and replace with default chain * make ARN role assume as supplemental * update docs * refactor flow * sign body when necessary * remove unnecessary wrapper * remove newline * Apply suggestions from code review * PR fixes Co-authored-by: Arve Knudsen --- conf/defaults.ini | 7 +- conf/sample.ini | 11 +- docs/sources/administration/configuration.md | 6 + packages/grafana-data/src/types/config.ts | 1 + packages/grafana-runtime/src/config.ts | 1 + .../DataSourceHttpSettings.mdx | 3 +- .../DataSourceHttpSettings.tsx | 20 +- .../DataSourceSettings/SigV4AuthSettings.tsx | 250 ++++++++++++++++++ .../components/DataSourceSettings/types.ts | 2 + pkg/api/frontendsettings.go | 1 + pkg/models/datasource_cache.go | 32 ++- pkg/models/datasource_cache_test.go | 72 +++++ pkg/models/sigv4.go | 110 ++++++++ pkg/setting/setting.go | 6 + .../cloudwatch/components/ConfigEditor.tsx | 2 +- .../configuration/ConfigEditor.tsx | 2 + .../prometheus/configuration/ConfigEditor.tsx | 2 + 17 files changed, 517 insertions(+), 11 deletions(-) create mode 100644 packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx create mode 100644 pkg/models/sigv4.go 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 + + onJsonDataChange('profile', e.currentTarget.value)} + /> +
+
+
+ )} + {dataSourceConfig.jsonData.authType === 'keys' && ( +
+ {dataSourceConfig.secureJsonFields?.accessKey ? ( +
+
+ Access Key ID + +
+
+
+ +
+
+
+ ) : ( +
+
+ Access Key ID +
+ onSecureJsonDataChange('accessKey', e.currentTarget.value)} + /> +
+
+
+ )} + {dataSourceConfig.secureJsonFields?.secretKey ? ( +
+
+ Secret Access Key + +
+
+
+ +
+
+
+ ) : ( +
+
+ 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 + +