From 09d5483c6c4c59bc53c1778afdb1168f53b31454 Mon Sep 17 00:00:00 2001 From: Sarah Zinger Date: Mon, 14 Aug 2023 13:42:30 -0400 Subject: [PATCH] Cloudwatch: Upgrade aws-sdk and display external ids for temporary credentials (#72821) (under a feature toggle, not yet ready for public testing) --- package.json | 2 +- pkg/setting/setting.go | 1 + pkg/tsdb/cloudwatch/resource_handler.go | 2 ++ pkg/tsdb/cloudwatch/routes/external_id.go | 29 +++++++++++++++ .../cloudwatch/routes/external_id_test.go | 36 +++++++++++++++++++ .../ConfigEditor/ConfigEditor.test.tsx | 4 +++ .../components/ConfigEditor/ConfigEditor.tsx | 10 ++++++ .../cloudwatch/resources/ResourcesAPI.ts | 4 +++ yarn.lock | 10 +++--- 9 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 pkg/tsdb/cloudwatch/routes/external_id.go create mode 100644 pkg/tsdb/cloudwatch/routes/external_id_test.go diff --git a/package.json b/package.json index cb8b3cea0d3..ebf9ba679a1 100644 --- a/package.json +++ b/package.json @@ -261,7 +261,7 @@ "@emotion/css": "11.11.2", "@emotion/react": "11.11.1", "@glideapps/glide-data-grid": "^5.2.1", - "@grafana/aws-sdk": "0.0.47", + "@grafana/aws-sdk": "0.1.1", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", "@grafana/experimental": "1.6.1", diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 1e3ac64183d..3593cb81dbe 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -1301,6 +1301,7 @@ func (cfg *Cfg) handleAWSConfig() { } cfg.AWSExternalId = awsPluginSec.Key("external_id").Value() + err = os.Setenv(awsds.GrafanaAssumeRoleExternalIdKeyName, cfg.AWSExternalId) if err != nil { cfg.Logger.Error(fmt.Sprintf("could not set environment variable '%s'", awsds.GrafanaAssumeRoleExternalIdKeyName), err) } diff --git a/pkg/tsdb/cloudwatch/resource_handler.go b/pkg/tsdb/cloudwatch/resource_handler.go index fe41af28ec3..a6e03c1ea90 100644 --- a/pkg/tsdb/cloudwatch/resource_handler.go +++ b/pkg/tsdb/cloudwatch/resource_handler.go @@ -26,6 +26,8 @@ func (e *cloudWatchExecutor) newResourceMux() *http.ServeMux { mux.HandleFunc("/accounts", routes.ResourceRequestMiddleware(routes.AccountsHandler, logger, e.getRequestContext)) mux.HandleFunc("/namespaces", routes.ResourceRequestMiddleware(routes.NamespacesHandler, logger, e.getRequestContext)) mux.HandleFunc("/log-group-fields", routes.ResourceRequestMiddleware(routes.LogGroupFieldsHandler, logger, e.getRequestContext)) + mux.HandleFunc("/external-id", routes.ResourceRequestMiddleware(routes.ExternalIdHandler, logger, e.getRequestContext)) + return mux } diff --git a/pkg/tsdb/cloudwatch/routes/external_id.go b/pkg/tsdb/cloudwatch/routes/external_id.go new file mode 100644 index 00000000000..8be23aeccf8 --- /dev/null +++ b/pkg/tsdb/cloudwatch/routes/external_id.go @@ -0,0 +1,29 @@ +package routes + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + "os" + + "github.com/grafana/grafana-aws-sdk/pkg/awsds" + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" +) + +type ExternalIdResponse struct { + ExternalId string `json:"externalId"` +} + +func ExternalIdHandler(ctx context.Context, pluginCtx backend.PluginContext, reqCtxFactory models.RequestContextFactoryFunc, parameters url.Values) ([]byte, *models.HttpError) { + response := ExternalIdResponse{ + ExternalId: os.Getenv(awsds.GrafanaAssumeRoleExternalIdKeyName), + } + jsonResponse, err := json.Marshal(response) + if err != nil { + return nil, models.NewHttpError("error in ExternalIdHandler", http.StatusInternalServerError, err) + } + + return jsonResponse, nil +} diff --git a/pkg/tsdb/cloudwatch/routes/external_id_test.go b/pkg/tsdb/cloudwatch/routes/external_id_test.go new file mode 100644 index 00000000000..b69df5f7177 --- /dev/null +++ b/pkg/tsdb/cloudwatch/routes/external_id_test.go @@ -0,0 +1,36 @@ +package routes + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_external_id_route(t *testing.T) { + t.Run("successfully returns an external id from the env", func(t *testing.T) { + t.Setenv("AWS_AUTH_EXTERNAL_ID", "mock-external-id") + rr := httptest.NewRecorder() + + handler := http.HandlerFunc(ResourceRequestMiddleware(ExternalIdHandler, logger, nil)) + req := httptest.NewRequest("GET", "/external-id", nil) + + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + assert.JSONEq(t, `{"externalId":"mock-external-id"}`, rr.Body.String()) + }) + + t.Run("returns an empty string if there is no external id", func(t *testing.T) { + rr := httptest.NewRecorder() + + handler := http.HandlerFunc(ResourceRequestMiddleware(ExternalIdHandler, logger, nil)) + req := httptest.NewRequest("GET", "/external-id", nil) + + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + assert.JSONEq(t, `{"externalId":""}`, rr.Body.String()) + }) +} diff --git a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.test.tsx index 4384b834a4b..52ff2437498 100644 --- a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.test.tsx @@ -36,6 +36,10 @@ jest.mock('@grafana/runtime', () => ({ get: getMock, }), getAppEvents: () => mockAppEvents, + config: { + ...jest.requireActual('@grafana/runtime').config, + awsAssumeRoleEnabled: true, + }, })); const props: Props = { diff --git a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.tsx index 8e91fc344a7..e3c9a140075 100644 --- a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.tsx @@ -57,6 +57,15 @@ export const ConfigEditor = (props: Props) => { failSubscription.unsubscribe(); }; }, [options.jsonData.authType, report]); + const [externalId, setExternalId] = useState(''); + useEffect(() => { + if (!externalId && datasource) { + datasource.resources + .getExternalId() + .then(setExternalId) + .catch(() => setExternalId('Unable to fetch externalId')); + } + }, [datasource, externalId]); return ( <> @@ -76,6 +85,7 @@ export const ConfigEditor = (props: Props) => { ); }) } + externalId={externalId} > { + return await this.memoizedGetRequest<{ externalId: string }>('external-id').then(({ externalId }) => externalId); + } + getAccounts({ region }: ResourceRequest): Promise { return this.memoizedGetRequest>>('accounts', { region: this.templateSrv.replace(this.getActualRegion(region)), diff --git a/yarn.lock b/yarn.lock index adf931552f5..76da69c25ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3604,13 +3604,13 @@ __metadata: languageName: node linkType: hard -"@grafana/aws-sdk@npm:0.0.47": - version: 0.0.47 - resolution: "@grafana/aws-sdk@npm:0.0.47" +"@grafana/aws-sdk@npm:0.1.1": + version: 0.1.1 + resolution: "@grafana/aws-sdk@npm:0.1.1" dependencies: "@grafana/async-query-data": 0.1.4 "@grafana/experimental": 1.1.0 - checksum: 1e9f57bddf08f0c0b432bb8bdd5ad63382c2d40e9d8282469e3add0ea970a80bcd31e852f050412e0d41620d71e53839510261f92333d026bf79a88d7346c626 + checksum: 12d1b27b0959a4d16ddf643b360ce067f6debd4141eb28652ff36fece98a99087f865aa2b9f6fda78df2b9e38870a6a7cfa48e0fd1a1ec03de63bc340b344f05 languageName: node linkType: hard @@ -19268,7 +19268,7 @@ __metadata: "@emotion/eslint-plugin": 11.11.0 "@emotion/react": 11.11.1 "@glideapps/glide-data-grid": ^5.2.1 - "@grafana/aws-sdk": 0.0.47 + "@grafana/aws-sdk": 0.1.1 "@grafana/data": "workspace:*" "@grafana/e2e": "workspace:*" "@grafana/e2e-selectors": "workspace:*"