diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 4a43b10aa27..6faf379e370 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -182,4 +182,5 @@ export interface FeatureToggles { kubernetesAggregator?: boolean; groupByVariable?: boolean; alertingUpgradeDryrunOnStart?: boolean; + prometheusAzureOverrideAudience?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 7497dce6288..b213435c321 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1223,6 +1223,13 @@ var ( RequiresRestart: true, Expression: "true", // enabled by default }, + { + Name: "prometheusAzureOverrideAudience", + Description: "Deprecated. Allow override default AAD audience for Azure Prometheus endpoint. Enabled by default. This feature should no longer be used and will be removed in the future.", + Stage: FeatureStageDeprecated, + Owner: grafanaPartnerPluginsSquad, + Expression: "true", // Enabled by default for now + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 7cdbf878fe3..a3aee5f2815 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -163,3 +163,4 @@ newPDFRendering,experimental,@grafana/sharing-squad,false,false,false kubernetesAggregator,experimental,@grafana/grafana-app-platform-squad,false,true,false groupByVariable,experimental,@grafana/dashboards-squad,false,false,false alertingUpgradeDryrunOnStart,GA,@grafana/alerting-squad,false,true,false +prometheusAzureOverrideAudience,deprecated,@grafana/partner-datasources,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index fc74cbdf502..2698469d9ff 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -662,4 +662,8 @@ const ( // FlagAlertingUpgradeDryrunOnStart // When activated in legacy alerting mode, this initiates a dry-run of the Unified Alerting upgrade during each startup. It logs any issues detected without implementing any actual changes. FlagAlertingUpgradeDryrunOnStart = "alertingUpgradeDryrunOnStart" + + // FlagPrometheusAzureOverrideAudience + // Deprecated. Allow override default AAD audience for Azure Prometheus endpoint. Enabled by default. This feature should no longer be used and will be removed in the future. + FlagPrometheusAzureOverrideAudience = "prometheusAzureOverrideAudience" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index f2b2625553c..e1e10e59430 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1291,7 +1291,7 @@ "name": "clientTokenRotation", "resourceVersion": "1707928895402", "creationTimestamp": "2024-02-14T16:41:35Z", - "deletionTimestamp": "2024-02-16T15:38:59Z" + "deletionTimestamp": "2024-07-17T19:01:22Z" }, "spec": { "description": "Replaces the current in-request token rotation so that the client initiates the rotation", @@ -2140,6 +2140,18 @@ "codeowner": "@grafana/alerting-squad", "requiresRestart": true } + }, + { + "metadata": { + "name": "prometheusAzureOverrideAudience", + "resourceVersion": "1721242859311", + "creationTimestamp": "2024-07-17T19:00:59Z" + }, + "spec": { + "description": "Deprecated. Allow override default AAD audience for Azure Prometheus endpoint. Enabled by default. This feature should no longer be used and will be removed in the future.", + "stage": "deprecated", + "codeowner": "@grafana/partner-datasources" + } } ] } \ No newline at end of file diff --git a/pkg/tsdb/prometheus/azureauth/azure.go b/pkg/tsdb/prometheus/azureauth/azure.go index 93b8e5263e8..183a41c5b78 100644 --- a/pkg/tsdb/prometheus/azureauth/azure.go +++ b/pkg/tsdb/prometheus/azureauth/azure.go @@ -11,11 +11,12 @@ import ( "github.com/grafana/grafana-azure-sdk-go/util/maputil" "github.com/grafana/grafana-plugin-sdk-go/backend" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana/pkg/tsdb/prometheus/utils" ) -func ConfigureAzureAuthentication(settings backend.DataSourceInstanceSettings, azureSettings *azsettings.AzureSettings, clientOpts *sdkhttpclient.Options) error { +func ConfigureAzureAuthentication(settings backend.DataSourceInstanceSettings, azureSettings *azsettings.AzureSettings, clientOpts *sdkhttpclient.Options, audienceOverride bool, log log.Logger) error { jsonData, err := utils.GetJsonData(settings) if err != nil { return fmt.Errorf("failed to get jsonData: %w", err) @@ -29,7 +30,7 @@ func ConfigureAzureAuthentication(settings backend.DataSourceInstanceSettings, a if credentials != nil { var scopes []string - if scopes, err = getOverriddenScopes(jsonData); err != nil { + if scopes, err = getOverriddenScopes(jsonData, audienceOverride, log); err != nil { return err } @@ -47,7 +48,7 @@ func ConfigureAzureAuthentication(settings backend.DataSourceInstanceSettings, a return nil } -func getOverriddenScopes(jsonData map[string]any) ([]string, error) { +func getOverriddenScopes(jsonData map[string]any, audienceOverride bool, log log.Logger) ([]string, error) { resourceIdStr, err := maputil.GetStringOptional(jsonData, "azureEndpointResourceId") if err != nil { err = fmt.Errorf("overridden resource ID (audience) invalid") @@ -56,6 +57,11 @@ func getOverriddenScopes(jsonData map[string]any) ([]string, error) { return nil, nil } + if !audienceOverride { + log.Warn("Specifying an audience override requires the prometheusAzureOverrideAudience feature toggle to be enabled. This functionality is deprecated and will be removed in a future release.") + return nil, nil + } + resourceId, err := url.Parse(resourceIdStr) if err != nil || resourceId.Scheme == "" || resourceId.Host == "" { err = fmt.Errorf("overridden endpoint resource ID (audience) '%s' invalid", resourceIdStr) diff --git a/pkg/tsdb/prometheus/azureauth/azure_test.go b/pkg/tsdb/prometheus/azureauth/azure_test.go index 3b046eb0f66..e73a252c766 100644 --- a/pkg/tsdb/prometheus/azureauth/azure_test.go +++ b/pkg/tsdb/prometheus/azureauth/azure_test.go @@ -1,18 +1,39 @@ package azureauth import ( + "bytes" + "context" "testing" "github.com/grafana/grafana-azure-sdk-go/azcredentials" "github.com/grafana/grafana-azure-sdk-go/azsettings" "github.com/grafana/grafana-plugin-sdk-go/backend" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +type fakeLogger struct { + hclog.Logger + + level log.Level +} + +func (l fakeLogger) Level() log.Level { + return l.level +} +func (l fakeLogger) FromContext(ctx context.Context) log.Logger { + return fakeLogger{} +} +func (l fakeLogger) With(args ...interface{}) log.Logger { + return fakeLogger{} +} + func TestConfigureAzureAuthentication(t *testing.T) { azureSettings := &azsettings.AzureSettings{} + testLogger := backend.Logger t.Run("should set Azure middleware when JsonData contains valid credentials", func(t *testing.T) { settings := backend.DataSourceInstanceSettings{ @@ -26,7 +47,7 @@ func TestConfigureAzureAuthentication(t *testing.T) { var opts = &sdkhttpclient.Options{CustomOptions: map[string]any{}} - err := ConfigureAzureAuthentication(settings, azureSettings, opts) + err := ConfigureAzureAuthentication(settings, azureSettings, opts, false, testLogger) require.NoError(t, err) require.NotNil(t, opts.Middlewares) @@ -40,7 +61,7 @@ func TestConfigureAzureAuthentication(t *testing.T) { var opts = &sdkhttpclient.Options{CustomOptions: map[string]any{}} - err := ConfigureAzureAuthentication(settings, azureSettings, opts) + err := ConfigureAzureAuthentication(settings, azureSettings, opts, false, testLogger) require.NoError(t, err) assert.NotContains(t, opts.CustomOptions, "_azureCredentials") @@ -55,7 +76,7 @@ func TestConfigureAzureAuthentication(t *testing.T) { } var opts = &sdkhttpclient.Options{CustomOptions: map[string]any{}} - err := ConfigureAzureAuthentication(settings, azureSettings, opts) + err := ConfigureAzureAuthentication(settings, azureSettings, opts, false, testLogger) assert.Error(t, err) }) @@ -71,7 +92,7 @@ func TestConfigureAzureAuthentication(t *testing.T) { } var opts = &sdkhttpclient.Options{CustomOptions: map[string]any{}} - err := ConfigureAzureAuthentication(settings, azureSettings, opts) + err := ConfigureAzureAuthentication(settings, azureSettings, opts, true, testLogger) require.NoError(t, err) require.NotNil(t, opts.Middlewares) @@ -87,7 +108,7 @@ func TestConfigureAzureAuthentication(t *testing.T) { } var opts = &sdkhttpclient.Options{CustomOptions: map[string]any{}} - err := ConfigureAzureAuthentication(settings, azureSettings, opts) + err := ConfigureAzureAuthentication(settings, azureSettings, opts, true, testLogger) require.NoError(t, err) if opts.Middlewares != nil { @@ -108,9 +129,36 @@ func TestConfigureAzureAuthentication(t *testing.T) { var opts = &sdkhttpclient.Options{CustomOptions: map[string]any{}} - err := ConfigureAzureAuthentication(settings, azureSettings, opts) + err := ConfigureAzureAuthentication(settings, azureSettings, opts, true, testLogger) assert.Error(t, err) }) + t.Run("should warn if an audience is specified and the feature toggle is not enabled", func(t *testing.T) { + settings := backend.DataSourceInstanceSettings{ + JSONData: []byte(`{ + "httpMethod": "POST", + "azureCredentials": { + "authType": "msi" + }, + "azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5" + }`), + } + + var opts = &sdkhttpclient.Options{CustomOptions: map[string]any{}} + var buf bytes.Buffer + testLogger := hclog.New(&hclog.LoggerOptions{ + Name: "test", + Output: &buf, + }) + log := fakeLogger{ + Logger: testLogger, + } + + err := ConfigureAzureAuthentication(settings, azureSettings, opts, false, log) + str := buf.String() + t.Log(str) + assert.NoError(t, err) + assert.Contains(t, str, "Specifying an audience override requires the prometheusAzureOverrideAudience feature toggle to be enabled. This functionality is deprecated and will be removed in a future release.") + }) } func TestGetPrometheusScopes(t *testing.T) { diff --git a/pkg/tsdb/prometheus/client/transport.go b/pkg/tsdb/prometheus/client/transport.go index 8998b49bd7a..379c5ae7752 100644 --- a/pkg/tsdb/prometheus/client/transport.go +++ b/pkg/tsdb/prometheus/client/transport.go @@ -38,6 +38,8 @@ func CreateTransportOptions(ctx context.Context, settings backend.DataSourceInst opts.SigV4.Service = "aps" } + audienceOverride := backend.GrafanaConfigFromContext(ctx).FeatureToggles().IsEnabled("prometheusAzureOverrideAudience") + azureSettings, err := azsettings.ReadSettings(ctx) if err != nil { logger.Error("failed to read Azure settings from Grafana", "error", err.Error()) @@ -46,7 +48,7 @@ func CreateTransportOptions(ctx context.Context, settings backend.DataSourceInst // Set Azure authentication if azureSettings.AzureAuthEnabled { - err = azureauth.ConfigureAzureAuthentication(settings, azureSettings, &opts) + err = azureauth.ConfigureAzureAuthentication(settings, azureSettings, &opts, audienceOverride, logger) if err != nil { return nil, fmt.Errorf("error configuring Azure auth: %v", err) } diff --git a/public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx b/public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx index 7327aa6996a..6ac2ae7e1dd 100644 --- a/public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx @@ -13,6 +13,7 @@ import { AzureCredentialsForm } from './AzureCredentialsForm'; export const AzureAuthSettings = (props: HttpSettingsBaseProps) => { const { dataSourceConfig, onChange } = props; + const [overrideAudienceAllowed] = useState(!!config.featureToggles.prometheusAzureOverrideAudience); const [overrideAudienceChecked, setOverrideAudienceChecked] = useState( !!dataSourceConfig.jsonData.azureEndpointResourceId ); @@ -64,25 +65,29 @@ export const AzureAuthSettings = (props: HttpSettingsBaseProps) => { onCredentialsChange={onCredentialsChange} disabled={dataSourceConfig.readOnly} /> -
Azure configuration
-
- - - - - - {overrideAudienceChecked && ( - - - - - - )} -
+ {overrideAudienceAllowed && ( + <> +
Azure configuration
+
+ + + + + + {overrideAudienceChecked && ( + + + + + + )} +
+ + )} ); };