Repository: clouditor/clouditor
Pull request #824 (2022-10-19 07:40:25)

Basic concept for authorization
This PR prepares a basic multi-tenancy by introducing a so-called `AuthorizationStrategy`. There are currently two implementations:
- Allow all (the default)
- A JWT-based strategy that retrieves the list of allows cloud services from a claim key in the JWT

codecov[bot]: # [Codecov](https://codecov.io/gh/clouditor/clouditor/pull/824?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) Report
> Merging [#824](https://codecov.io/gh/clouditor/clouditor/pull/824?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) (a28b282) into [main](https://codecov.io/gh/clouditor/clouditor/commit/04a66202a79b54294453739f34ab096b5fa80a7f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) (04a6620) will **decrease** coverage by `0.29%`.
> The diff coverage is `10.71%`.

```diff
@@            Coverage Diff             @@
##             main     #824      +/-   ##
==========================================
- Coverage   81.12%   80.82%   -0.30%     
==========================================
  Files          71       72       +1     
  Lines        6564     6592      +28     
==========================================
+ Hits         5325     5328       +3     
- Misses        962      987      +25     
  Partials      277      277              
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `80.82% <10.71%> (-0.30%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/clouditor/clouditor/pull/824?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) | Coverage Δ | |
|---|---|---|
| [service/orchestrator/authorization.go](https://codecov.io/gh/clouditor/clouditor/pull/824/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-c2VydmljZS9vcmNoZXN0cmF0b3IvYXV0aG9yaXphdGlvbi5nbw==) | `0.00% <0.00%> (ø)` | |
| [service/orchestrator/orchestrator.go](https://codecov.io/gh/clouditor/clouditor/pull/824/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-c2VydmljZS9vcmNoZXN0cmF0b3Ivb3JjaGVzdHJhdG9yLmdv) | `76.44% <100.00%> (+0.29%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)


Patch:
diff --git a/api/orchestrator/orchestrator.go b/api/orchestrator/orchestrator.go
index fa033107..b4125756 100644
--- a/api/orchestrator/orchestrator.go
+++ b/api/orchestrator/orchestrator.go
@@ -42,3 +42,7 @@ var (
 	ErrCatalogIDIsMissing = errors.New("catalog ID is empty")
 	ErrToEIDIsMissing     = errors.New("toe ID is empty")
 )
+
+type CloudServiceRequest interface {
+	GetCloudServiceId() string
+}
diff --git a/service/authz.go b/service/authz.go
new file mode 100644
index 00000000..88501727
--- /dev/null
+++ b/service/authz.go
@@ -0,0 +1,133 @@
+// Copyright 2021-2022 Fraunhofer AISEC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package service
+
+import (
+	"context"
+
+	"clouditor.io/clouditor/api/orchestrator"
+	"golang.org/x/exp/slices"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+
+	"github.com/golang-jwt/jwt/v4"
+	grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
+)
+
+// RequestType specifies the type of request, usually CRUD.
+type RequestType int
+
+const (
+	AccessCreate RequestType = iota
+	AccessRead
+	AccessUpdate
+	AccessDelete
+)
+
+// ErrPermissionDenied represents an error, where permission to fulfill the request is denied.
+var ErrPermissionDenied = status.Errorf(codes.PermissionDenied, "access denied")
+
+// AuthorizationStrategy is an interface that implements a function which
+// checkers whether the current cloud service request can be fulfilled using the
+// supplied context (e.g., based on the authenticated user).
+type AuthorizationStrategy interface {
+	CheckAccess(ctx context.Context, typ RequestType, req orchestrator.CloudServiceRequest) bool
+	AllowedCloudServices(ctx context.Context) (all bool, IDs []string)
+}
+
+// AuthorizationStrategyJWT is an AuthorizationStrategy that expects a list of cloud service IDs to be in a specific JWT
+// claim key.
+type AuthorizationStrategyJWT struct {
+	Key string
+}
+
+// CheckAccess checks whether the current request can be fulfilled using the current access strategy.
+func (a *AuthorizationStrategyJWT) CheckAccess(ctx context.Context, _ RequestType, req orchestrator.CloudServiceRequest) bool {
+	var list []string
+
+	// Retrieve the list of allowed cloud services. we never allow to retrieve
+	// "all" services with the token strategy.
+	_, list = a.AllowedCloudServices(ctx)
+
+	return slices.Contains(list, req.GetCloudServiceId())
+}
+
+// AllowedCloudServices retrieves a list of allowed cloud service IDs according to the current access strategy.
+func (a *AuthorizationStrategyJWT) AllowedCloudServices(ctx context.Context) (all bool, list []string) {
+	var (
+		err    error
+		ok     bool
+		token  string
+		claims jwt.MapClaims
+		l      []interface{}
+		s      string
+	)
+
+	// Retrieve the raw token from the context
+	token, err = grpc_auth.AuthFromMD(ctx, "bearer")
+	if err != nil {
+		log.Debugf("Retrieving allowed cloud services from token failed: %v", err)
+		return false, nil
+	}
+
+	// We need to re-parse the already validated claim to get a specific key from the claims map.
+	parser := jwt.NewParser()
+	_, _, err = parser.ParseUnverified(token, &claims)
+	if err != nil {
+		log.Debugf("Retrieving allowed cloud services from token failed: %v", err)
+		return false, nil
+	}
+
+	// We are looking for an array claim
+	if l, ok = claims[a.Key].([]interface{}); !ok {
+		log.Debug("Retrieving allowed cloud services from token failed: specified claims key is not an array", err)
+		return false, nil
+	}
+
+	// Loop through the array claim and add all string values to our list
+	for _, item := range l {
+		if s, ok = item.(string); !ok {
+			continue
+		}
+
+		list = append(list, s)
+	}
+
+	return false, list
+}
+
+// AuthorizationStrategyJWT is an AuthorizationStrategy that allows all requests.
+type AuthorizationStrategyAllowAll struct{}
+
+// CheckAccess checks whether the current request can be fulfilled using the current access strategy.
+func (a *AuthorizationStrategyAllowAll) CheckAccess(_ context.Context, _ RequestType, _ orchestrator.CloudServiceRequest) bool {
+	return true
+}
+
+// AllowedCloudServices retrieves a list of allowed cloud service IDs according to the current access strategy.
+func (a *AuthorizationStrategyAllowAll) AllowedCloudServices(_ context.Context) (all bool, list []string) {
+	return true, nil
+}
diff --git a/service/orchestrator/assessment_results.go b/service/orchestrator/assessment_results.go
new file mode 100644
index 00000000..a64f3e1f
--- /dev/null
+++ b/service/orchestrator/assessment_results.go
@@ -0,0 +1,74 @@
+// Copyright 2022 Fraunhofer AISEC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package orchestrator
+
+import (
+	"context"
+
+	"clouditor.io/clouditor/api/assessment"
+	"clouditor.io/clouditor/service"
+	"golang.org/x/exp/maps"
+	"golang.org/x/exp/slices"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+)
+
+// ListAssessmentResults is a method implementation of the orchestrator interface
+func (svc *Service) ListAssessmentResults(ctx context.Context, req *assessment.ListAssessmentResultsRequest) (res *assessment.ListAssessmentResultsResponse, err error) {
+	var values = maps.Values(svc.results)
+	var filtered []*assessment.AssessmentResult
+	var allowed []string
+	var all bool
+
+	// Check, if the current filter is valid according to the authorization strategy. Omitting the cloud service ID is
+	// only allowed, if one can access all the cloud services. Furthermore, the content of the filtered cloud service ID
+	// must be in the list of allowed cloud service IDs.
+	all, allowed = svc.authz.AllowedCloudServices(ctx)
+	if (req.FilteredCloudServiceId == "" && !all) ||
+		(req.FilteredCloudServiceId != "" && !slices.Contains(allowed, req.FilteredCloudServiceId)) {
+		return nil, service.ErrPermissionDenied
+	}
+
+	for _, v := range values {
+		if req.FilteredCloudServiceId != "" && v.CloudServiceId != req.FilteredCloudServiceId {
+			continue
+		}
+
+		filtered = append(filtered, v)
+	}
+
+	res = new(assessment.ListAssessmentResultsResponse)
+
+	// Paginate the results according to the request
+	res.Results, res.NextPageToken, err = service.PaginateSlice(req, filtered, func(a *assessment.AssessmentResult, b *assessment.AssessmentResult) bool {
+		return a.Timestamp.AsTime().After(b.Timestamp.AsTime())
+	}, service.DefaultPaginationOpts)
+	if err != nil {
+		return nil, status.Errorf(codes.Internal, "could not paginate results: %v", err)
+	}
+
+	return
+}
diff --git a/service/orchestrator/cloud_service.go b/service/orchestrator/cloud_service.go
index ec11c08b..9575992d 100644
--- a/service/orchestrator/cloud_service.go
+++ b/service/orchestrator/cloud_service.go
@@ -75,9 +75,11 @@ func (s *Service) RegisterCloudService(_ context.Context, req *orchestrator.Regi
 }
 
 // ListCloudServices implements method for OrchestratorServer interface for listing all cloud services
-func (svc *Service) ListCloudServices(_ context.Context, req *orchestrator.ListCloudServicesRequest) (
+func (svc *Service) ListCloudServices(ctx context.Context, req *orchestrator.ListCloudServicesRequest) (
 	res *orchestrator.ListCloudServicesResponse, err error) {
-	res = new(orchestrator.ListCloudServicesResponse)
+	var conds []any
+	var allowed []string
+	var all bool
 
 	// Validate tne request
 	if err = api.ValidateListRequest[*orchestrator.CloudService](req); err != nil {
@@ -87,9 +89,18 @@ func (svc *Service) ListCloudServices(_ context.Context, req *orchestrator.ListC
 		return
 	}
 
+	res = new(orchestrator.ListCloudServicesResponse)
+
+	// Retrieve list of allowed cloud service according to our authorization strategy. No need to specify any conditions
+	// to our storage request, if we are allowed to see all cloud services.
+	all, allowed = svc.authz.AllowedCloudServices(ctx)
+	if !all {
+		conds = append(conds, allowed)
+	}
+
 	// Paginate the cloud services according to the request
 	res.Services, res.NextPageToken, err = service.PaginateStorage[*orchestrator.CloudService](req, svc.storage,
-		service.DefaultPaginationOpts)
+		service.DefaultPaginationOpts, conds...)
 	if err != nil {
 		return nil, status.Errorf(codes.Internal, "could not paginate results: %v", err)
 	}
@@ -98,7 +109,7 @@ func (svc *Service) ListCloudServices(_ context.Context, req *orchestrator.ListC
 }
 
 // GetCloudService implements method for OrchestratorServer interface for getting a cloud service with provided id
-func (s *Service) GetCloudService(_ context.Context, req *orchestrator.GetCloudServiceRequest) (response *orchestrator.CloudService, err error) {
+func (s *Service) GetCloudService(ctx context.Context, req *orchestrator.GetCloudServiceRequest) (response *orchestrator.CloudService, err error) {
 	if req == nil {
 		return nil, status.Errorf(codes.InvalidArgument, api.ErrRequestIsNil.Error())
 	}
@@ -106,6 +117,11 @@ func (s *Service) GetCloudService(_ context.Context, req *orchestrator.GetCloudS
 		return nil, status.Errorf(codes.InvalidArgument, orchestrator.ErrIDIsMissing.Error())
 	}
 
+	// Check, if this request has access to the cloud service according to our authorization strategy.
+	if !s.authz.CheckAccess(ctx, service.AccessRead, req) {
+		return nil, service.ErrPermissionDenied
+	}
+
 	response = new(orchestrator.CloudService)
 	err = s.storage.Get(response, "Id = ?", req.CloudServiceId)
 	if errors.Is(err, persistence.ErrRecordNotFound) {
@@ -127,6 +143,11 @@ func (s *Service) UpdateCloudService(ctx context.Context, req *orchestrator.Upda
 		return nil, status.Errorf(codes.InvalidArgument, "service id is empty")
 	}
 
+	// Check, if this request has access to the cloud service according to our authorization strategy.
+	if !s.authz.CheckAccess(ctx, service.AccessUpdate, req) {
+		return nil, service.ErrPermissionDenied
+	}
+
 	count, err := s.storage.Count(req.Service, "id = ?", req.CloudServiceId)
 	if err != nil {
 		return nil, status.Errorf(codes.Internal, "database error: %s", err)
@@ -150,11 +171,16 @@ func (s *Service) UpdateCloudService(ctx context.Context, req *orchestrator.Upda
 }
 
 // RemoveCloudService implements method for OrchestratorServer interface for removing a cloud service
-func (s *Service) RemoveCloudService(_ context.Context, req *orchestrator.RemoveCloudServiceRequest) (response *emptypb.Empty, err error) {
+func (s *Service) RemoveCloudService(ctx context.Context, req *orchestrator.RemoveCloudServiceRequest) (response *emptypb.Empty, err error) {
 	if req.CloudServiceId == "" {
 		return nil, status.Errorf(codes.InvalidArgument, "service id is empty")
 	}
 
+	// Check, if this request has access to the cloud service according to our authorization strategy.
+	if !s.authz.CheckAccess(ctx, service.AccessDelete, req) {
+		return nil, service.ErrPermissionDenied
+	}
+
 	err = s.storage.Delete(&orchestrator.CloudService{Id: req.CloudServiceId})
 	if errors.Is(err, persistence.ErrRecordNotFound) {
 		return nil, status.Errorf(codes.NotFound, "service not found")
diff --git a/service/orchestrator/metrics.go b/service/orchestrator/metrics.go
index 6b5eb923..16505a78 100644
--- a/service/orchestrator/metrics.go
+++ b/service/orchestrator/metrics.go
@@ -319,8 +319,14 @@ func (svc *Service) GetMetric(_ context.Context, req *orchestrator.GetMetricRequ
 	return
 }
 
-func (svc *Service) GetMetricConfiguration(_ context.Context, req *orchestrator.GetMetricConfigurationRequest) (res *assessment.MetricConfiguration, err error) {
+func (svc *Service) GetMetricConfiguration(ctx context.Context, req *orchestrator.GetMetricConfigurationRequest) (res *assessment.MetricConfiguration, err error) {
+	// Check, if this request has access to the cloud service according to our authorization strategy.
+	if !svc.authz.CheckAccess(ctx, service.AccessRead, req) {
+		return nil, service.ErrPermissionDenied
+	}
+
 	res = new(assessment.MetricConfiguration)
+
 	err = svc.storage.Get(res, gorm.WithoutPreload(), "cloud_service_id = ? AND metric_id = ?", req.CloudServiceId, req.MetricId)
 	if errors.Is(err, persistence.ErrRecordNotFound) {
 		// Otherwise, fall back to our default configuration
@@ -337,9 +343,14 @@ func (svc *Service) GetMetricConfiguration(_ context.Context, req *orchestrator.
 }
 
 // UpdateMetricConfiguration updates the configuration for a metric, specified by the identifier in req.MetricId.
-func (svc *Service) UpdateMetricConfiguration(_ context.Context, req *orchestrator.UpdateMetricConfigurationRequest) (res *assessment.MetricConfiguration, err error) {
+func (svc *Service) UpdateMetricConfiguration(ctx context.Context, req *orchestrator.UpdateMetricConfigurationRequest) (res *assessment.MetricConfiguration, err error) {
 	// TODO(oxisto): Validate the request
 
+	// Check, if this request has access to the cloud service according to our authorization strategy.
+	if !svc.authz.CheckAccess(ctx, service.AccessRead, req) {
+		return nil, service.ErrPermissionDenied
+	}
+
 	// Make sure that the configuration also has metric/service ID set
 	req.Configuration.CloudServiceId = req.CloudServiceId
 	req.Configuration.MetricId = req.MetricId
@@ -373,6 +384,11 @@ func (svc *Service) UpdateMetricConfiguration(_ context.Context, req *orchestrat
 // configuration for a particular metric within the service, the default metric configuration is
 // inserted into the list.
 func (svc *Service) ListMetricConfigurations(ctx context.Context, req *orchestrator.ListMetricConfigurationRequest) (response *orchestrator.ListMetricConfigurationResponse, err error) {
+	// Check, if this request has access to the cloud service according to our authorization strategy.
+	if !svc.authz.CheckAccess(ctx, service.AccessRead, req) {
+		return nil, service.ErrPermissionDenied
+	}
+
 	response = &orchestrator.ListMetricConfigurationResponse{
 		Configurations: make(map[string]*assessment.MetricConfiguration),
 	}
diff --git a/service/orchestrator/orchestrator.go b/service/orchestrator/orchestrator.go
index 2001decf..9c4ef5da 100644
--- a/service/orchestrator/orchestrator.go
+++ b/service/orchestrator/orchestrator.go
@@ -39,7 +39,6 @@ import (
 	"clouditor.io/clouditor/persistence"
 	"clouditor.io/clouditor/persistence/inmemory"
 	"clouditor.io/clouditor/service"
-	"golang.org/x/exp/maps"
 
 	"github.com/sirupsen/logrus"
 	"google.golang.org/grpc/codes"
@@ -89,6 +88,10 @@ type Service struct {
 	loadCatalogsFunc func() ([]*orchestrator.Catalog, error)
 
 	events chan *orchestrator.MetricChangeEvent
+
+	// authz defines our authorization strategy, e.g., which user can access which cloud service and associated
+	// resources, such as evidences and assessment results.
+	authz service.AuthorizationStrategy
 }
 
 func init() {
@@ -133,6 +136,13 @@ func WithStorage(storage persistence.Storage) ServiceOption {
 	}
 }
 
+// WithAuthorizationStrategyJWT is an option that configures an JWT-based authorization strategy using a specific claim key.
+func WithAuthorizationStrategyJWT(key string) ServiceOption {
+	return func(s *Service) {
+		s.authz = &service.AuthorizationStrategyJWT{Key: key}
+	}
+}
+
 // NewService creates a new Orchestrator service
 func NewService(opts ...ServiceOption) *Service {
 	var err error
@@ -156,6 +166,11 @@ func NewService(opts ...ServiceOption) *Service {
 		}
 	}
 
+	// Default to an allow-all authorization strategy
+	if s.authz == nil {
+		s.authz = &service.AuthorizationStrategyAllowAll{}
+	}
+
 	if err = s.loadMetrics(); err != nil {
 		log.Errorf("Could not load embedded metrics. Will continue with empty metric list: %v", err)
 	}
@@ -261,32 +276,6 @@ func (s *Service) StoreAssessmentResults(stream orchestrator.Orchestrator_StoreA
 	}
 }
 
-// ListAssessmentResults is a method implementation of the orchestrator interface
-func (svc *Service) ListAssessmentResults(_ context.Context, req *assessment.ListAssessmentResultsRequest) (res *assessment.ListAssessmentResultsResponse, err error) {
-	res = new(assessment.ListAssessmentResultsResponse)
-
-	var values = maps.Values(svc.results)
-	var filtered []*assessment.AssessmentResult
-
-	for _, v := range values {
-		if req.FilteredCloudServiceId != "" && v.CloudServiceId != req.FilteredCloudServiceId {
-			continue
-		}
-
-		filtered = append(filtered, v)
-	}
-
-	// Paginate the results according to the request
-	res.Results, res.NextPageToken, err = service.PaginateSlice(req, filtered, func(a *assessment.AssessmentResult, b *assessment.AssessmentResult) bool {
-		return a.Timestamp.AsTime().After(b.Timestamp.AsTime())
-	}, service.DefaultPaginationOpts)
-	if err != nil {
-		return nil, status.Errorf(codes.Internal, "could not paginate results: %v", err)
-	}
-
-	return
-}
-
 func (s *Service) RegisterAssessmentResultHook(hook func(result *assessment.AssessmentResult, err error)) {
 	s.hookMutex.Lock()
 	defer s.hookMutex.Unlock()


Test patch:
diff --git a/internal/testutil/auth.go b/internal/testutil/auth.go
index f17f0d90..b3fe99c1 100644
--- a/internal/testutil/auth.go
+++ b/internal/testutil/auth.go
@@ -1,6 +1,7 @@
 package testutil
 
 import (
+	"context"
 	"fmt"
 	"net"
 	"net/http"
@@ -8,6 +9,7 @@ import (
 	oauth2 "github.com/oxisto/oauth2go"
 	"github.com/oxisto/oauth2go/login"
 	"golang.org/x/oauth2/clientcredentials"
+	"google.golang.org/grpc/metadata"
 )
 
 const (
@@ -16,8 +18,23 @@ const (
 
 	TestAuthClientID     = "client"
 	TestAuthClientSecret = "secret"
+
+	TestCustomClaims  = "cloudserviceid"
+	TestCloudService1 = "11111111-1111-1111-1111-111111111111"
+	TestCloudService2 = "22222222-2222-2222-2222-222222222222"
 )
 
+// TestContextOnlyService1 is an incoming context with a JWT that only allows access to cloud service ID
+// 11111111-1111-1111-1111-111111111111
+var TestContextOnlyService1 = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{
+	"authorization": "bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJjbG91ZHNlcnZpY2VpZCI6WyIxMTExMTExMS0xMTExLTExMTEtMTExMS0xMTExMTExMTExMTEiXSwib3RoZXIiOlsxLDJdfQ.A4_2-yRcoPui-udHifvQxB6SJj7fR1EPjBnFs0Nl80k",
+}))
+
+// TestBrokenContext contains an invalid JWT
+var TestBrokenContext = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{
+	"authorization": "bearer what",
+}))
+
 // StartAuthenticationServer starts an authentication server on a random port with
 // users and clients specified in the TestAuthUser and TestAuthClientID constants.
 func StartAuthenticationServer() (srv *oauth2.AuthorizationServer, port uint16, err error) {
diff --git a/service/authz_test.go b/service/authz_test.go
new file mode 100644
index 00000000..df26ce24
--- /dev/null
+++ b/service/authz_test.go
@@ -0,0 +1,191 @@
+// Copyright 2021-2022 Fraunhofer AISEC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package service
+
+import (
+	"context"
+	"reflect"
+	"testing"
+
+	"clouditor.io/clouditor/api/discovery"
+	"clouditor.io/clouditor/api/orchestrator"
+	"clouditor.io/clouditor/internal/testutil"
+)
+
+func TestAuthorizationStrategyAllowAll_CheckAccess(t *testing.T) {
+	type args struct {
+		ctx context.Context
+		typ RequestType
+		req orchestrator.CloudServiceRequest
+	}
+	tests := []struct {
+		name string
+		a    *AuthorizationStrategyAllowAll
+		args args
+		want bool
+	}{
+		{
+			name: "always true",
+			a:    &AuthorizationStrategyAllowAll{},
+			args: args{
+				ctx: context.Background(),
+				typ: AccessCreate,
+				req: &orchestrator.GetCloudServiceRequest{CloudServiceId: discovery.DefaultCloudServiceID},
+			},
+			want: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := &AuthorizationStrategyAllowAll{}
+			if got := a.CheckAccess(tt.args.ctx, tt.args.typ, tt.args.req); got != tt.want {
+				t.Errorf("AuthorizationStrategyAllowAll.CheckAccess() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestAuthorizationStrategyAllowAll_AllowedCloudServices(t *testing.T) {
+	type args struct {
+		ctx context.Context
+	}
+	tests := []struct {
+		name     string
+		a        *AuthorizationStrategyAllowAll
+		args     args
+		wantAll  bool
+		wantList []string
+	}{
+		{
+			name:     "all allowed",
+			a:        &AuthorizationStrategyAllowAll{},
+			args:     args{},
+			wantAll:  true,
+			wantList: nil,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := &AuthorizationStrategyAllowAll{}
+			gotAll, gotList := a.AllowedCloudServices(tt.args.ctx)
+			if gotAll != tt.wantAll {
+				t.Errorf("AuthorizationStrategyAllowAll.AllowedCloudServices() got = %v, want %v", gotAll, tt.wantAll)
+			}
+			if !reflect.DeepEqual(gotList, tt.wantList) {
+				t.Errorf("AuthorizationStrategyAllowAll.AllowedCloudServices() got1 = %v, want %v", gotList, tt.wantList)
+			}
+		})
+	}
+}
+
+func TestAuthorizationStrategyJWT_CheckAccess(t *testing.T) {
+	type fields struct {
+		Key string
+	}
+	type args struct {
+		ctx context.Context
+		typ RequestType
+		req orchestrator.CloudServiceRequest
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		args   args
+		want   bool
+	}{
+		{
+			name: "valid context",
+			fields: fields{
+				Key: testutil.TestCustomClaims,
+			},
+			args: args{
+				ctx: testutil.TestContextOnlyService1,
+				typ: AccessRead,
+				req: &orchestrator.GetCloudServiceRequest{CloudServiceId: testutil.TestCloudService1},
+			},
+			want: true,
+		},
+		{
+			name: "valid context, wrong claim",
+			fields: fields{
+				Key: "sub",
+			},
+			args: args{
+				ctx: testutil.TestContextOnlyService1,
+				typ: AccessRead,
+				req: &orchestrator.GetCloudServiceRequest{CloudServiceId: testutil.TestCloudService1},
+			},
+			want: false,
+		},
+		{
+			name: "valid context, ignore non-string",
+			fields: fields{
+				Key: "other",
+			},
+			args: args{
+				ctx: testutil.TestContextOnlyService1,
+				typ: AccessRead,
+				req: &orchestrator.GetCloudServiceRequest{CloudServiceId: testutil.TestCloudService1},
+			},
+			want: false,
+		},
+		{
+			name: "missing token",
+			fields: fields{
+				Key: testutil.TestCustomClaims,
+			},
+			args: args{
+				ctx: context.Background(),
+				typ: AccessRead,
+				req: &orchestrator.GetCloudServiceRequest{CloudServiceId: testutil.TestCloudService1},
+			},
+			want: false,
+		},
+		{
+			name: "broken token",
+			fields: fields{
+				Key: testutil.TestCustomClaims,
+			},
+			args: args{
+				ctx: testutil.TestBrokenContext,
+				typ: AccessRead,
+				req: &orchestrator.GetCloudServiceRequest{CloudServiceId: testutil.TestCloudService1},
+			},
+			want: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := &AuthorizationStrategyJWT{
+				Key: tt.fields.Key,
+			}
+			if got := a.CheckAccess(tt.args.ctx, tt.args.typ, tt.args.req); got != tt.want {
+				t.Errorf("AuthorizationStrategyJWT.CheckAccess() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/service/orchestrator/assessment_results_test.go b/service/orchestrator/assessment_results_test.go
new file mode 100644
index 00000000..b0c9a87a
--- /dev/null
+++ b/service/orchestrator/assessment_results_test.go
@@ -0,0 +1,112 @@
+// Copyright 2022 Fraunhofer AISEC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package orchestrator
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"clouditor.io/clouditor/api/assessment"
+	"clouditor.io/clouditor/internal/testutil"
+	"clouditor.io/clouditor/persistence"
+	"clouditor.io/clouditor/service"
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/types/known/timestamppb"
+)
+
+func TestService_ListAssessmentResults(t *testing.T) {
+	type fields struct {
+		results map[string]*assessment.AssessmentResult
+		storage persistence.Storage
+		authz   service.AuthorizationStrategy
+	}
+	type args struct {
+		ctx context.Context
+		req *assessment.ListAssessmentResultsRequest
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantRes *assessment.ListAssessmentResultsResponse
+		wantErr assert.ErrorAssertionFunc
+	}{
+		{
+			name: "list all with allow all",
+			fields: fields{
+				results: map[string]*assessment.AssessmentResult{
+					"1": {Id: "1", Timestamp: timestamppb.New(time.Unix(1, 0))},
+					"2": {Id: "2", Timestamp: timestamppb.New(time.Unix(0, 0))},
+				},
+				authz: &service.AuthorizationStrategyAllowAll{},
+			},
+			args: args{req: &assessment.ListAssessmentResultsRequest{}},
+			wantRes: &assessment.ListAssessmentResultsResponse{
+				Results: []*assessment.AssessmentResult{
+					{Id: "1", Timestamp: timestamppb.New(time.Unix(1, 0))},
+					{Id: "2", Timestamp: timestamppb.New(time.Unix(0, 0))},
+				},
+			},
+			wantErr: assert.NoError,
+		},
+		{
+			name: "list all denied",
+			fields: fields{
+				results: map[string]*assessment.AssessmentResult{
+					"1": {Id: "1", Timestamp: timestamppb.New(time.Unix(1, 0))},
+					"2": {Id: "2", Timestamp: timestamppb.New(time.Unix(0, 0))},
+				},
+				authz: &service.AuthorizationStrategyJWT{Key: testutil.TestCustomClaims},
+			},
+			args: args{
+				ctx: testutil.TestContextOnlyService1,
+				req: &assessment.ListAssessmentResultsRequest{},
+			},
+			wantRes: nil,
+			wantErr: func(tt assert.TestingT, err error, i ...interface{}) bool {
+				return assert.Equal(t, err, service.ErrPermissionDenied)
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			svc := &Service{
+				results: tt.fields.results,
+				storage: tt.fields.storage,
+				authz:   tt.fields.authz,
+			}
+			gotRes, err := svc.ListAssessmentResults(tt.args.ctx, tt.args.req)
+			tt.wantErr(t, err)
+
+			if !proto.Equal(gotRes, tt.wantRes) {
+				t.Errorf("Service.ListAssessmentResults() = %v, want %v", gotRes, tt.wantRes)
+			}
+		})
+	}
+}
diff --git a/service/orchestrator/cloud_service_test.go b/service/orchestrator/cloud_service_test.go
index 1c29270e..bfe9e3df 100644
--- a/service/orchestrator/cloud_service_test.go
+++ b/service/orchestrator/cloud_service_test.go
@@ -30,8 +30,11 @@ import (
 	"testing"
 
 	"clouditor.io/clouditor/api"
+	"clouditor.io/clouditor/api/assessment"
 	"clouditor.io/clouditor/api/orchestrator"
-
+	"clouditor.io/clouditor/internal/testutil"
+	"clouditor.io/clouditor/persistence"
+	"clouditor.io/clouditor/service"
 	"github.com/stretchr/testify/assert"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
@@ -97,61 +100,35 @@ func TestService_RegisterCloudService(t *testing.T) {
 	}
 }
 
-func TestService_ListCloudServices(t *testing.T) {
-	var (
-		listCloudServicesResponse *orchestrator.ListCloudServicesResponse
-		cloudService              *orchestrator.CloudService
-		err                       error
-	)
-
-	orchestratorService := NewService()
-	// 1st case: No services stored
-	listCloudServicesResponse, err = orchestratorService.ListCloudServices(context.Background(), &orchestrator.ListCloudServicesRequest{})
-	assert.NoError(t, err)
-	assert.NotNil(t, listCloudServicesResponse.Services)
-	assert.Empty(t, listCloudServicesResponse.Services)
-
-	// 2nd case: One service stored
-	cloudService, err = orchestratorService.CreateDefaultTargetCloudService()
-	assert.NoError(t, err)
-	assert.NotNil(t, cloudService)
-
-	listCloudServicesResponse, err = orchestratorService.ListCloudServices(context.Background(), &orchestrator.ListCloudServicesRequest{})
-	assert.NoError(t, err)
-	assert.NotNil(t, listCloudServicesResponse.Services)
-	assert.NotEmpty(t, listCloudServicesResponse.Services)
-	assert.Equal(t, len(listCloudServicesResponse.Services), 1)
-
-	// 3rd case: Invalid request
-	_, err = orchestratorService.ListCloudServices(context.Background(), &orchestrator.ListCloudServicesRequest{
-		OrderBy: "not a field",
-	})
-	assert.Equal(t, codes.InvalidArgument, status.Code(err))
-	assert.Contains(t, err.Error(), api.ErrInvalidColumnName.Error())
-
-}
-
-func TestGetCloudService(t *testing.T) {
+func TestService_GetCloudService(t *testing.T) {
 	tests := []struct {
 		name string
+		svc  *Service
+		ctx  context.Context
 		req  *orchestrator.GetCloudServiceRequest
 		res  *orchestrator.CloudService
 		err  error
 	}{
 		{
 			"invalid request",
+			NewService(),
+			context.Background(),
 			nil,
 			nil,
 			status.Error(codes.InvalidArgument, api.ErrRequestIsNil.Error()),
 		},
 		{
 			"cloud service not found",
+			NewService(),
+			context.Background(),
 			&orchestrator.GetCloudServiceRequest{CloudServiceId: "does-not-exist"},
 			nil,
 			status.Error(codes.NotFound, "service not found"),
 		},
 		{
 			"valid",
+			NewService(),
+			context.Background(),
 			&orchestrator.GetCloudServiceRequest{CloudServiceId: DefaultTargetCloudServiceId},
 			&orchestrator.CloudService{
 				Id:          DefaultTargetCloudServiceId,
@@ -160,16 +137,38 @@ func TestGetCloudService(t *testing.T) {
 			},
 			nil,
 		},
+		{
+			"permission denied",
+			NewService(WithAuthorizationStrategyJWT(testutil.TestCustomClaims)),
+			testutil.TestContextOnlyService1,
+			&orchestrator.GetCloudServiceRequest{CloudServiceId: DefaultTargetCloudServiceId},
+			nil,
+			service.ErrPermissionDenied,
+		},
+		{
+			"permission granted",
+			NewService(WithAuthorizationStrategyJWT(testutil.TestCustomClaims), WithStorage(testutil.NewInMemoryStorage(t, func(s persistence.Storage) {
+				_ = s.Create(&orchestrator.CloudService{
+					Id:   testutil.TestCloudService1,
+					Name: "service1",
+				})
+			}))),
+			testutil.TestContextOnlyService1,
+			&orchestrator.GetCloudServiceRequest{CloudServiceId: testutil.TestCloudService1},
+			&orchestrator.CloudService{
+				Id:   testutil.TestCloudService1,
+				Name: "service1",
+			},
+			nil,
+		},
 	}
-	orchestratorService := NewService()
-
-	cloudService, err := orchestratorService.CreateDefaultTargetCloudService()
-	assert.NoError(t, err)
-	assert.NotNil(t, cloudService)
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			res, err := orchestratorService.GetCloudService(context.Background(), tt.req)
+			_, err := tt.svc.CreateDefaultTargetCloudService()
+			assert.NoError(t, err)
+
+			res, err := tt.svc.GetCloudService(tt.ctx, tt.req)
 
 			if tt.err == nil {
 				assert.Equal(t, tt.err, err)
@@ -293,3 +292,125 @@ func TestService_CreateDefaultTargetCloudService(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Nil(t, cloudServiceResponse)
 }
+
+func TestService_ListCloudServices(t *testing.T) {
+	type fields struct {
+		results               map[string]*assessment.AssessmentResult
+		AssessmentResultHooks []func(result *assessment.AssessmentResult, err error)
+		storage               persistence.Storage
+		metricsFile           string
+		loadMetricsFunc       func() ([]*assessment.Metric, error)
+		catalogsFile          string
+		loadCatalogsFunc      func() ([]*orchestrator.Catalog, error)
+		events                chan *orchestrator.MetricChangeEvent
+		authz                 service.AuthorizationStrategy
+	}
+	type args struct {
+		ctx context.Context
+		req *orchestrator.ListCloudServicesRequest
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantRes *orchestrator.ListCloudServicesResponse
+		wantErr assert.ErrorAssertionFunc
+	}{
+		{
+			name: "retrieve empty list",
+			args: args{req: &orchestrator.ListCloudServicesRequest{}},
+			fields: fields{
+				storage: testutil.NewInMemoryStorage(t),
+				authz:   &service.AuthorizationStrategyAllowAll{},
+			},
+			wantRes: &orchestrator.ListCloudServicesResponse{},
+			wantErr: assert.NoError,
+		},
+		{
+			name: "list with one item",
+			args: args{req: &orchestrator.ListCloudServicesRequest{}},
+			fields: fields{
+				storage: testutil.NewInMemoryStorage(t, func(s persistence.Storage) {
+					service := &orchestrator.CloudService{
+						Id:          DefaultTargetCloudServiceId,
+						Name:        DefaultTargetCloudServiceName,
+						Description: DefaultTargetCloudServiceDescription,
+					}
+
+					_ = s.Create(service)
+				}),
+				authz: &service.AuthorizationStrategyAllowAll{},
+			},
+			wantRes: &orchestrator.ListCloudServicesResponse{
+				Services: []*orchestrator.CloudService{
+					{
+						Id:          DefaultTargetCloudServiceId,
+						Name:        DefaultTargetCloudServiceName,
+						Description: DefaultTargetCloudServiceDescription,
+					},
+				},
+			},
+			wantErr: assert.NoError,
+		},
+		{
+			name: "invalid request",
+			fields: fields{
+				storage: testutil.NewInMemoryStorage(t),
+				authz:   &service.AuthorizationStrategyAllowAll{},
+			},
+			args: args{
+				req: &orchestrator.ListCloudServicesRequest{
+					OrderBy: "not a field",
+				},
+			},
+			wantRes: nil,
+			wantErr: func(tt assert.TestingT, err error, i ...interface{}) bool {
+				return assert.ErrorContains(t, err, api.ErrInvalidColumnName.Error())
+			},
+		},
+		{
+			name: "retrieve only allowed cloud services",
+			fields: fields{
+				storage: testutil.NewInMemoryStorage(t, func(s persistence.Storage) {
+					// Store two cloud services, of which only one we are allowed to retrieve in the test
+					_ = s.Create(&orchestrator.CloudService{Id: "11111111-1111-1111-1111-111111111111"})
+					_ = s.Create(&orchestrator.CloudService{Id: "22222222-2222-2222-2222-222222222222"})
+				}),
+				authz: &service.AuthorizationStrategyJWT{Key: testutil.TestCustomClaims},
+			},
+			args: args{
+				ctx: testutil.TestContextOnlyService1,
+				req: &orchestrator.ListCloudServicesRequest{},
+			},
+			wantRes: &orchestrator.ListCloudServicesResponse{
+				Services: []*orchestrator.CloudService{
+					{Id: "11111111-1111-1111-1111-111111111111"},
+				},
+			},
+			wantErr: assert.NoError,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			svc := &Service{
+				results:               tt.fields.results,
+				AssessmentResultHooks: tt.fields.AssessmentResultHooks,
+				storage:               tt.fields.storage,
+				metricsFile:           tt.fields.metricsFile,
+				loadMetricsFunc:       tt.fields.loadMetricsFunc,
+				catalogsFile:          tt.fields.catalogsFile,
+				loadCatalogsFunc:      tt.fields.loadCatalogsFunc,
+				events:                tt.fields.events,
+				authz:                 tt.fields.authz,
+			}
+
+			gotRes, err := svc.ListCloudServices(tt.args.ctx, tt.args.req)
+			tt.wantErr(t, err, tt.args)
+
+			if !proto.Equal(gotRes, tt.wantRes) {
+				t.Errorf("Service.ListCloudServices() = %v, want %v", gotRes, tt.wantRes)
+			}
+		})
+	}
+}
diff --git a/service/orchestrator/metrics_test.go b/service/orchestrator/metrics_test.go
index 6876f603..992563bd 100644
--- a/service/orchestrator/metrics_test.go
+++ b/service/orchestrator/metrics_test.go
@@ -45,6 +45,7 @@ import (
 	"clouditor.io/clouditor/internal/testutil"
 	"clouditor.io/clouditor/persistence"
 	"clouditor.io/clouditor/persistence/gorm"
+	"clouditor.io/clouditor/service"
 )
 
 var ErrSomeError = errors.New("some error")
@@ -685,6 +686,7 @@ func TestService_GetMetricConfiguration(t *testing.T) {
 		metricsFile           string
 		catalogsFile          string
 		events                chan *orchestrator.MetricChangeEvent
+		authz                 service.AuthorizationStrategy
 	}
 	type args struct {
 		in0 context.Context
@@ -710,7 +712,9 @@ func TestService_GetMetricConfiguration(t *testing.T) {
 						CloudServiceId: DefaultTargetCloudServiceId,
 						Operator:       "==",
 					})
-				})},
+				}),
+				authz: &service.AuthorizationStrategyAllowAll{},
+			},
 			args: args{
 				req: &orchestrator.GetMetricConfigurationRequest{
 					MetricId:       MockMetricID,
@@ -728,6 +732,7 @@ func TestService_GetMetricConfiguration(t *testing.T) {
 			name: "metric not found",
 			fields: fields{
 				storage: testutil.NewInMemoryStorage(t),
+				authz:   &service.AuthorizationStrategyAllowAll{},
 			},
 			args: args{
 				req: &orchestrator.GetMetricConfigurationRequest{
@@ -753,6 +758,7 @@ func TestService_GetMetricConfiguration(t *testing.T) {
 				metricsFile:           tt.fields.metricsFile,
 				catalogsFile:          tt.fields.catalogsFile,
 				events:                tt.fields.events,
+				authz:                 tt.fields.authz,
 			}
 			gotResponse, err := s.GetMetricConfiguration(tt.args.in0, tt.args.req)
 			if tt.wantErr != nil {
@@ -773,6 +779,7 @@ func TestService_ListMetricConfigurations(t *testing.T) {
 		metricsFile           string
 		catalogsFile          string
 		events                chan *orchestrator.MetricChangeEvent
+		authz                 service.AuthorizationStrategy
 	}
 	type args struct {
 		ctx context.Context
@@ -789,6 +796,7 @@ func TestService_ListMetricConfigurations(t *testing.T) {
 			name: "error",
 			fields: fields{
 				storage: &testutil.StorageWithError{ListErr: ErrSomeError},
+				authz:   &service.AuthorizationStrategyAllowAll{},
 			},
 			args: args{
 				req: &orchestrator.ListMetricConfigurationRequest{},
@@ -809,6 +817,7 @@ func TestService_ListMetricConfigurations(t *testing.T) {
 						Operator:       "==",
 					})
 				}),
+				authz: &service.AuthorizationStrategyAllowAll{},
 			},
 			args: args{
 				req: &orchestrator.ListMetricConfigurationRequest{
@@ -836,6 +845,7 @@ func TestService_ListMetricConfigurations(t *testing.T) {
 				metricsFile:           tt.fields.metricsFile,
 				catalogsFile:          tt.fields.catalogsFile,
 				events:                tt.fields.events,
+				authz:                 tt.fields.authz,
 			}
 			gotResponse, err := svc.ListMetricConfigurations(tt.args.ctx, tt.args.req)
 			if (err != nil) != tt.wantErr {
@@ -859,6 +869,7 @@ func TestService_UpdateMetricConfiguration(t *testing.T) {
 		catalogsFile          string
 		loadCatalogsFunc      func() ([]*orchestrator.Catalog, error)
 		events                chan *orchestrator.MetricChangeEvent
+		authz                 service.AuthorizationStrategy
 	}
 	type args struct {
 		in0 context.Context
@@ -872,8 +883,11 @@ func TestService_UpdateMetricConfiguration(t *testing.T) {
 		wantErr assert.ErrorAssertionFunc
 	}{
 		{
-			name:   "metric does not exist",
-			fields: fields{storage: testutil.NewInMemoryStorage(t)},
+			name: "metric does not exist",
+			fields: fields{
+				storage: testutil.NewInMemoryStorage(t),
+				authz:   &service.AuthorizationStrategyAllowAll{},
+			},
 			args: args{
 				req: &orchestrator.UpdateMetricConfigurationRequest{
 					CloudServiceId: DefaultTargetCloudServiceId,
@@ -891,6 +905,7 @@ func TestService_UpdateMetricConfiguration(t *testing.T) {
 				storage: testutil.NewInMemoryStorage(t, func(s persistence.Storage) {
 					_ = s.Create(assessment.Metric{Id: "MyMetric"})
 				}),
+				authz: &service.AuthorizationStrategyAllowAll{},
 			},
 			args: args{
 				req: &orchestrator.UpdateMetricConfigurationRequest{
@@ -910,6 +925,7 @@ func TestService_UpdateMetricConfiguration(t *testing.T) {
 					_ = s.Create(assessment.Metric{Id: MockMetricID})
 					_ = s.Create(&orchestrator.CloudService{Id: DefaultTargetCloudServiceId})
 				}),
+				authz: &service.AuthorizationStrategyAllowAll{},
 			},
 			args: args{
 				req: &orchestrator.UpdateMetricConfigurationRequest{
@@ -949,6 +965,7 @@ func TestService_UpdateMetricConfiguration(t *testing.T) {
 						Operator:       ">",
 					})
 				}),
+				authz: &service.AuthorizationStrategyAllowAll{},
 			},
 			args: args{
 				req: &orchestrator.UpdateMetricConfigurationRequest{
@@ -987,6 +1004,7 @@ func TestService_UpdateMetricConfiguration(t *testing.T) {
 				catalogsFile:          tt.fields.catalogsFile,
 				loadCatalogsFunc:      tt.fields.loadCatalogsFunc,
 				events:                tt.fields.events,
+				authz:                 tt.fields.authz,
 			}
 			gotRes, err := svc.UpdateMetricConfiguration(tt.args.in0, tt.args.req)
 			if tt.wantErr != nil {
diff --git a/service/orchestrator/orchestrator_test.go b/service/orchestrator/orchestrator_test.go
index 2d7492d4..2925fc68 100644
--- a/service/orchestrator/orchestrator_test.go
+++ b/service/orchestrator/orchestrator_test.go
@@ -37,6 +37,12 @@ import (
 	"sync"
 	"testing"
 
+	"clouditor.io/clouditor/api"
+	"clouditor.io/clouditor/api/assessment"
+	"clouditor.io/clouditor/api/orchestrator"
+	"clouditor.io/clouditor/internal/testutil/clitest"
+	"clouditor.io/clouditor/internal/testutil/orchestratortest"
+	"clouditor.io/clouditor/persistence/inmemory"
 	"github.com/google/uuid"
 	"github.com/stretchr/testify/assert"
 	"google.golang.org/grpc"
@@ -47,13 +53,6 @@ import (
 	"google.golang.org/protobuf/types/known/emptypb"
 	"google.golang.org/protobuf/types/known/structpb"
 	"google.golang.org/protobuf/types/known/timestamppb"
-
-	"clouditor.io/clouditor/api"
-	"clouditor.io/clouditor/api/assessment"
-	"clouditor.io/clouditor/api/orchestrator"
-	"clouditor.io/clouditor/internal/testutil/clitest"
-	"clouditor.io/clouditor/internal/testutil/orchestratortest"
-	"clouditor.io/clouditor/persistence/inmemory"
 )
 
 const (