From 4a94b46f33c7562d27987c89b649877b6c2a9db3 Mon Sep 17 00:00:00 2001 From: Tomas Hozza Date: Mon, 6 Jun 2022 09:21:29 +0200 Subject: [PATCH] cloudapi: handle multi-tenancy in all `compose/` endpoints Use the `EnsureJobChannel()` middleware in all `compose/` endpoints. Specifically in the: - status - metadata - manifests - logs As a result, these endpoints now return `404` in case the server has JWT enabled and the channel associated with the request does not match the channel associated with the requested compose (job). Extend the multi-tenancy unit test to ensure that these endpoints behave as expected in case of match and mismatch between the request and compose channels. --- internal/cloudapi/v2/handler.go | 17 ++++++++ internal/cloudapi/v2/v2_multi_tenancy_test.go | 40 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/internal/cloudapi/v2/handler.go b/internal/cloudapi/v2/handler.go index b01a378b4..0079fd543 100644 --- a/internal/cloudapi/v2/handler.go +++ b/internal/cloudapi/v2/handler.go @@ -492,6 +492,10 @@ func imageTypeFromApiImageType(it ImageTypes, arch distro.Arch) string { } func (h *apiHandlers) GetComposeStatus(ctx echo.Context, id string) error { + return h.server.EnsureJobChannel(h.getComposeStatusImpl)(ctx, id) +} + +func (h *apiHandlers) getComposeStatusImpl(ctx echo.Context, id string) error { jobId, err := uuid.Parse(id) if err != nil { return HTTPError(ErrorInvalidComposeId) @@ -738,6 +742,10 @@ func composeStatusFromKojiJobStatus(js *worker.JobStatus, initResult *worker.Koj // ComposeMetadata handles a /composes/{id}/metadata GET request func (h *apiHandlers) GetComposeMetadata(ctx echo.Context, id string) error { + return h.server.EnsureJobChannel(h.getComposeMetadataImpl)(ctx, id) +} + +func (h *apiHandlers) getComposeMetadataImpl(ctx echo.Context, id string) error { jobId, err := uuid.Parse(id) if err != nil { return HTTPError(ErrorInvalidComposeId) @@ -849,6 +857,11 @@ func stagesToPackageMetadata(stages []osbuild.RPMStageMetadata) []PackageMetadat // Get logs for a compose func (h *apiHandlers) GetComposeLogs(ctx echo.Context, id string) error { + return h.server.EnsureJobChannel(h.getComposeLogsImpl)(ctx, id) +} + +func (h *apiHandlers) getComposeLogsImpl(ctx echo.Context, id string) error { + jobId, err := uuid.Parse(id) if err != nil { return HTTPError(ErrorInvalidComposeId) @@ -959,6 +972,10 @@ func manifestJobResultsFromJobDeps(w *worker.Server, deps []uuid.UUID) (*worker. // GetComposeIdManifests returns the Manifests for a given Compose (one for each image). func (h *apiHandlers) GetComposeManifests(ctx echo.Context, id string) error { + return h.server.EnsureJobChannel(h.getComposeManifestsImpl)(ctx, id) +} + +func (h *apiHandlers) getComposeManifestsImpl(ctx echo.Context, id string) error { jobId, err := uuid.Parse(id) if err != nil { return HTTPError(ErrorInvalidComposeId) diff --git a/internal/cloudapi/v2/v2_multi_tenancy_test.go b/internal/cloudapi/v2/v2_multi_tenancy_test.go index 08c884612..b80b15c63 100644 --- a/internal/cloudapi/v2/v2_multi_tenancy_test.go +++ b/internal/cloudapi/v2/v2_multi_tenancy_test.go @@ -276,5 +276,45 @@ func TestMultitenancy(t *testing.T) { } require.NoError(t, json.Unmarshal(resp.Body, &result)) require.NotEqual(t, "pending", result.Status) + + composeEndpoints := []string{"", "logs", "manifests", "metadata"} + // Verify that all compose endpoints work with the appropriate orgID + for _, endpoint := range composeEndpoints { + // TODO: "metadata" endpoint is not supported for Koji composes + jobType, err := workerServer.JobType(c.id) + require.NoError(t, err) + if jobType == worker.JobTypeKojiFinalize && endpoint == "metadata" { + continue + } + + path := "/api/image-builder-composer/v2/composes/" + c.id.String() + if endpoint != "" { + path = path + "/" + endpoint + } + + _ = test.APICall{ + Handler: handler, + Method: http.MethodGet, + Context: reqContext(c.orgID), + Path: path, + ExpectedStatus: http.StatusOK, + }.Do(t) + } + + // Verify that no compose endpoints are accessible with wrong orgID + for _, endpoint := range composeEndpoints { + path := "/api/image-builder-composer/v2/composes/" + c.id.String() + if endpoint != "" { + path = path + "/" + endpoint + } + + _ = test.APICall{ + Handler: handler, + Method: http.MethodGet, + Context: reqContext("bad-org"), + Path: path, + ExpectedStatus: http.StatusNotFound, + }.Do(t) + } } }