Repository: clouditor/clouditor
Pull request #471 (2022-01-28 11:06:16)

Azure timeout error
Azure times out after about 60 minutes. 

codecov[bot]: # [Codecov](https://codecov.io/gh/clouditor/clouditor/pull/471?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) Report
> Merging [#471](https://codecov.io/gh/clouditor/clouditor/pull/471?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) (6b8394f) into [main](https://codecov.io/gh/clouditor/clouditor/commit/9e44adc27dac2cf3dce854cd4891c74ea4b13f13?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) (9e44adc) will **decrease** coverage by `0.34%`.
> The diff coverage is `13.63%`.

[![Impacted file tree graph](https://codecov.io/gh/clouditor/clouditor/pull/471/graphs/tree.svg?width=650&height=150&src=pr&token=syrXsw5LTX&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)](https://codecov.io/gh/clouditor/clouditor/pull/471?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)

```diff
@@            Coverage Diff             @@
##             main     #471      +/-   ##
==========================================
- Coverage   76.00%   75.65%   -0.35%     
==========================================
  Files          44       44              
  Lines        3750     3767      +17     
==========================================
  Hits         2850     2850              
- Misses        774      789      +15     
- Partials      126      128       +2     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `75.65% <13.63%> (-0.35%)` | :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/471?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) | Coverage Δ | |
|---|---|---|
| [service/discovery/discovery.go](https://codecov.io/gh/clouditor/clouditor/pull/471/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-c2VydmljZS9kaXNjb3ZlcnkvZGlzY292ZXJ5Lmdv) | `61.18% <0.00%> (-4.31%)` | :arrow_down: |
| [service/discovery/azure/azure.go](https://codecov.io/gh/clouditor/clouditor/pull/471/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-c2VydmljZS9kaXNjb3ZlcnkvYXp1cmUvYXp1cmUuZ28=) | `77.77% <22.22%> (-14.33%)` | :arrow_down: |
| [service/discovery/aws/compute.go](https://codecov.io/gh/clouditor/clouditor/pull/471/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-c2VydmljZS9kaXNjb3ZlcnkvYXdzL2NvbXB1dGUuZ28=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/clouditor/clouditor/pull/471?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/clouditor/clouditor/pull/471?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor). Last update [9e44adc...6b8394f](https://codecov.io/gh/clouditor/clouditor/pull/471?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor).

garuppel: At the moment we can't test the discovery.Start() method any further, we have to wait for the Issue #473  to be resolved first.

Patch:
diff --git a/service/discovery/aws/compute.go b/service/discovery/aws/compute.go
index 554cb2c7..90e2773b 100644
--- a/service/discovery/aws/compute.go
+++ b/service/discovery/aws/compute.go
@@ -68,8 +68,8 @@ var newFromConfigEC2 = ec2.NewFromConfig
 // newFromConfigLambda holds lambda.NewFromConfig(...) allowing a test function tp mock it
 var newFromConfigLambda = lambda.NewFromConfig
 
-// NewComputeDiscovery constructs a new awsS3Discovery initializing the s3-virtualMachineAPI and isDiscovering with true
-func NewComputeDiscovery(client *Client) discovery.Discoverer {
+// NewAwsComputeDiscovery constructs a new awsS3Discovery initializing the s3-virtualMachineAPI and isDiscovering with true
+func NewAwsComputeDiscovery(client *Client) discovery.Discoverer {
 	return &computeDiscovery{
 		virtualMachineAPI: newFromConfigEC2(client.cfg),
 		functionAPI:       newFromConfigLambda(client.cfg),
@@ -79,7 +79,7 @@ func NewComputeDiscovery(client *Client) discovery.Discoverer {
 }
 
 // Name is the method implementation defined in the discovery.Discoverer interface
-func (d computeDiscovery) Name() string {
+func (*computeDiscovery) Name() string {
 	return "AWS Compute"
 }
 
@@ -218,7 +218,7 @@ func (*computeDiscovery) getOSLog(_ *typesEC2.Instance) (l *voc.OSLog) {
 }
 
 // mapBlockStorageIDsOfVM returns block storages IDs by iterating the VMs block storages
-func (d *computeDiscovery) mapBlockStorageIDsOfVM(vm *typesEC2.Instance) (blockStorageIDs []voc.ResourceID) {
+func (*computeDiscovery) mapBlockStorageIDsOfVM(vm *typesEC2.Instance) (blockStorageIDs []voc.ResourceID) {
 	for _, mapping := range vm.BlockDeviceMappings {
 		blockStorageIDs = append(blockStorageIDs, voc.ResourceID(aws.ToString(mapping.Ebs.VolumeId)))
 	}
@@ -226,7 +226,7 @@ func (d *computeDiscovery) mapBlockStorageIDsOfVM(vm *typesEC2.Instance) (blockS
 }
 
 // getNetworkInterfacesOfVM returns the network interface IDs by iterating the VMs network interfaces
-func (d *computeDiscovery) getNetworkInterfacesOfVM(vm *typesEC2.Instance) (networkInterfaceIDs []voc.ResourceID) {
+func (*computeDiscovery) getNetworkInterfacesOfVM(vm *typesEC2.Instance) (networkInterfaceIDs []voc.ResourceID) {
 	for _, networkInterface := range vm.NetworkInterfaces {
 		networkInterfaceIDs = append(networkInterfaceIDs, voc.ResourceID(aws.ToString(networkInterface.NetworkInterfaceId)))
 	}
@@ -234,7 +234,7 @@ func (d *computeDiscovery) getNetworkInterfacesOfVM(vm *typesEC2.Instance) (netw
 }
 
 // getNameOfVM returns the name if exists (i.e. a tag with key 'name' exists), otherwise instance ID is used
-func (d *computeDiscovery) getNameOfVM(vm *typesEC2.Instance) string {
+func (*computeDiscovery) getNameOfVM(vm *typesEC2.Instance) string {
 	for _, tag := range vm.Tags {
 		if aws.ToString(tag.Key) == "Name" {
 			return aws.ToString(tag.Value)
diff --git a/service/discovery/azure/azure.go b/service/discovery/azure/azure.go
index 49a31ee5..970401e3 100644
--- a/service/discovery/azure/azure.go
+++ b/service/discovery/azure/azure.go
@@ -28,6 +28,11 @@ package azure
 import (
 	"context"
 	"errors"
+	"fmt"
+	autorest_azure "github.com/Azure/go-autorest/autorest/azure"
+	"github.com/Azure/go-autorest/autorest/azure/auth"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
 	"strings"
 
 	"github.com/Azure/azure-sdk-for-go/profiles/2020-09-01/resources/mgmt/subscriptions"
@@ -83,8 +88,8 @@ func (a *azureDiscovery) authorize() (err error) {
 		return errors.New("no authorized was available")
 	}
 
-	// for now, do not re-authorize. in the future, we would probably need to check, if
-	// the token is still valid. or maybe Azure does this for us?
+	// If using NewAuthorizerFromFile() in discovery file, we do not need to re-authorize.
+	// If using NewAuthorizerFromCLI() in discovery file, the token expires after 75 minutes.
 	if a.isAuthorized {
 		return
 	}
@@ -92,8 +97,20 @@ func (a *azureDiscovery) authorize() (err error) {
 	subClient := subscriptions.NewClient()
 	a.apply(&subClient.Client)
 
+	// get subscriptions
+	page, err := subClient.List(context.Background())
+	if err != nil {
+		err = fmt.Errorf("could not get azure subscription: %v", err)
+		return
+	}
+
+	// check if list of subscriptions is empty
+	if len(page.Values()) == 0 {
+		err = errors.New("list of subscriptions is empty")
+		return
+	}
+
 	// get first subscription
-	page, _ := subClient.List(context.Background())
 	a.sub = page.Values()[0]
 
 	log.Infof("Using %s as subscription", *a.sub.SubscriptionID)
@@ -113,6 +130,26 @@ func (a azureDiscovery) apply(client *autorest.Client) {
 	}
 }
 
-func resourceGroupName(id string) string {
+func NewAuthorizer() (autorest.Authorizer, error) {
+	// create an authorizer from file or as fallback from the CLI
+	// if authorizer is from CLI, the access token expires after 75 minutes
+	authorizer, err := auth.NewAuthorizerFromFile(autorest_azure.PublicCloud.ResourceManagerEndpoint)
+	if err != nil {
+		log.Errorf("Could not authenticate to Azure with authorizer from file: %v", err)
+		log.Infof("Fallback to Azure with authorizer from CLI.")
+		authorizer, err = auth.NewAuthorizerFromCLI()
+		if err != nil {
+			log.Errorf("Could not authenticate to Azure with authorizer from CLI: %v", err)
+			return nil, status.Errorf(codes.FailedPrecondition, "could not authenticate to Azure: %v", err)
+		}
+		log.Info("Using Azure authorizer from CLI. The discovery times out after 1 hour.")
+	} else {
+		log.Info("Using Azure authorizer from file.")
+	}
+
+	return authorizer, nil
+}
+
+func getResourceGroupName(id string) string {
 	return strings.Split(id, "/")[4]
 }
diff --git a/service/discovery/azure/compute.go b/service/discovery/azure/compute.go
index fc8d0224..7285db9b 100644
--- a/service/discovery/azure/compute.go
+++ b/service/discovery/azure/compute.go
@@ -106,7 +106,7 @@ func (d *azureComputeDiscovery) discoverFunction() ([]voc.IsCloudResource, error
 	return list, err
 }
 
-func (d *azureComputeDiscovery) handleFunction(function *web.Site) voc.IsCompute {
+func (*azureComputeDiscovery) handleFunction(function *web.Site) voc.IsCompute {
 	return &voc.Function{
 		Compute: &voc.Compute{
 			CloudResource: &voc.CloudResource{
@@ -197,7 +197,7 @@ func (d *azureComputeDiscovery) extendedVirtualMachine(vm *compute.VirtualMachin
 	client := compute.NewVirtualMachinesClient(to.String(d.sub.SubscriptionID))
 	d.apply(&client.Client)
 
-	vmExtended, err := client.Get(context.Background(), resourceGroupName(*vm.ID), *vm.Name, "")
+	vmExtended, err := client.Get(context.Background(), getResourceGroupName(*vm.ID), *vm.Name, "")
 	if err != nil {
 		return nil, fmt.Errorf("could not get virtual machine: %w", err)
 	}
diff --git a/service/discovery/azure/network.go b/service/discovery/azure/network.go
index cb1ea982..bee07f58 100644
--- a/service/discovery/azure/network.go
+++ b/service/discovery/azure/network.go
@@ -203,7 +203,7 @@ func LoadBalancerPorts(lb *network.LoadBalancer) (loadBalancerPorts []int16) {
 //	d.apply(&client.Client)
 //
 //	// Get the Security Group of the network interface ni
-//	sg, err := client.Get(context.Background(), resourceGroupName(nsgID), strings.Split(nsgID, "/")[8], "")
+//	sg, err := client.Get(context.Background(), getResourceGroupName(nsgID), strings.Split(nsgID, "/")[8], "")
 //
 //	if err != nil {
 //		log.Errorf("Could not get security group: %v", err)
@@ -247,7 +247,7 @@ func (d *azureNetworkDiscovery) publicIPAddressFromLoadBalancer(lb *network.Load
 	if lb.LoadBalancerPropertiesFormat != nil && lb.LoadBalancerPropertiesFormat.FrontendIPConfigurations != nil {
 		for _, publicIpProperties := range *lb.FrontendIPConfigurations {
 
-			publicIPAddress, err := client.Get(context.Background(), resourceGroupName(*publicIpProperties.ID), *publicIpProperties.Name, "")
+			publicIPAddress, err := client.Get(context.Background(), getResourceGroupName(*publicIpProperties.ID), *publicIpProperties.Name, "")
 
 			if err != nil {
 				log.Infof("Error getting public IP address: %v", err)
diff --git a/service/discovery/azure/storage.go b/service/discovery/azure/storage.go
index 1ee726bd..90b639cc 100644
--- a/service/discovery/azure/storage.go
+++ b/service/discovery/azure/storage.go
@@ -153,7 +153,7 @@ func (d *azureStorageDiscovery) discoverFileStorages(account *storage.Account) (
 	client := storage.NewFileSharesClient(to.String(d.sub.SubscriptionID))
 	d.apply(&client.Client)
 
-	result, err := client.List(context.Background(), resourceGroupName(*account.ID), *account.Name, "", "", "")
+	result, err := client.List(context.Background(), getResourceGroupName(*account.ID), *account.Name, "", "", "")
 	if err != nil {
 		return nil, fmt.Errorf("could not list file storages: %w", err)
 	}
@@ -178,7 +178,7 @@ func (d *azureStorageDiscovery) discoverObjectStorages(account *storage.Account)
 	client := storage.NewBlobContainersClient(to.String(d.sub.SubscriptionID))
 	d.apply(&client.Client)
 
-	result, err := client.List(context.Background(), resourceGroupName(*account.ID), *account.Name, "", "", "")
+	result, err := client.List(context.Background(), getResourceGroupName(*account.ID), *account.Name, "", "", "")
 	if err != nil {
 		return nil, fmt.Errorf("could not list object storages: %w", err)
 	}
@@ -339,7 +339,7 @@ func (d *azureStorageDiscovery) sourceVaultID(discEncryptionSetID string) (strin
 	client := compute.NewDiskEncryptionSetsClient(to.String(d.sub.SubscriptionID))
 	d.apply(&client.Client)
 
-	discEncryptionSet, err := client.Get(context.Background(), resourceGroupName(discEncryptionSetID), diskEncryptionSetName(discEncryptionSetID))
+	discEncryptionSet, err := client.Get(context.Background(), getResourceGroupName(discEncryptionSetID), diskEncryptionSetName(discEncryptionSetID))
 	if err != nil {
 		return "", fmt.Errorf("could not get discEncryptionSet: %w", err)
 	}
diff --git a/service/discovery/discovery.go b/service/discovery/discovery.go
index 3a0141dd..6c1c15e3 100644
--- a/service/discovery/discovery.go
+++ b/service/discovery/discovery.go
@@ -42,7 +42,6 @@ import (
 	"clouditor.io/clouditor/api/discovery"
 	"clouditor.io/clouditor/api/evidence"
 	"clouditor.io/clouditor/voc"
-	"github.com/Azure/go-autorest/autorest/azure/auth"
 	"github.com/go-co-op/gocron"
 	"github.com/sirupsen/logrus"
 	"google.golang.org/grpc"
@@ -90,38 +89,53 @@ func NewService() *Service {
 	}
 }
 
+func (s *Service) initAssessmentStream() error {
+	// Establish connection to assessment component
+	// TODO(oxisto): support assessment on Another tcp/port
+	target := s.AssessmentAddress
+	log.Infof("Establishing connection to Assessment (%v)", target)
+
+	conn, err := grpc.Dial(s.AssessmentAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
+	if err != nil {
+		return fmt.Errorf("could not connect to Assessment service: %v", err)
+	}
+
+	client := assessment.NewAssessmentClient(conn)
+	s.assessmentStream, err = client.AssessEvidences(context.Background())
+	if err != nil {
+		return fmt.Errorf("could not set up stream for assessing evidences: %v", err)
+	}
+
+	log.Infof("Connected to Assessment")
+
+	return nil
+}
+
 // Start starts discovery
 func (s *Service) Start(_ context.Context, _ *discovery.StartDiscoveryRequest) (resp *discovery.StartDiscoveryResponse, err error) {
 	resp = &discovery.StartDiscoveryResponse{Successful: true}
 
 	log.Infof("Starting discovery...")
-
 	s.scheduler.TagsUnique()
 
 	// Establish connection to assessment component
-	var client assessment.AssessmentClient
-	// TODO(oxisto): support assessment on Another tcp/port
-	conn, err := grpc.Dial(s.AssessmentAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
-	if err != nil {
-		return nil, status.Errorf(codes.Internal, "could not connect to assessment service: %v", err)
-	}
-	client = assessment.NewAssessmentClient(conn)
-	s.assessmentStream, err = client.AssessEvidences(context.Background())
-	if err != nil {
-		return nil, status.Errorf(codes.Internal, "could not set up stream for assessing evidences: %v", err)
+	if s.assessmentStream == nil {
+		err = s.initAssessmentStream()
+		if err != nil {
+			return nil, status.Errorf(codes.Internal, "could not initialize stream to Assessment: %v", err)
+		}
 	}
 
-	// create an authorizer from env vars or Azure Managed Service Identity
-	authorizer, err := auth.NewAuthorizerFromCLI()
+	authorizer, err := azure.NewAuthorizer()
 	if err != nil {
 		log.Errorf("Could not authenticate to Azure: %v", err)
-		return nil, err
+		return nil, status.Errorf(codes.FailedPrecondition, "could not authenticate to Azure: %v", err)
 	}
 
 	k8sClient, err := k8s.AuthFromKubeConfig()
 	if err != nil {
 		log.Errorf("Could not authenticate to Kubernetes: %v", err)
-		return nil, err
+		return nil, status.Errorf(codes.FailedPrecondition, "could not authenticate to Kubernetes: %v", err)
 	}
 
 	awsClient, err := aws.NewClient()
@@ -140,7 +154,7 @@ func (s *Service) Start(_ context.Context, _ *discovery.StartDiscoveryRequest) (
 		k8s.NewKubernetesComputeDiscovery(k8sClient),
 		k8s.NewKubernetesNetworkDiscovery(k8sClient),
 		aws.NewAwsStorageDiscovery(awsClient),
-		aws.NewComputeDiscovery(awsClient),
+		aws.NewAwsComputeDiscovery(awsClient),
 	)
 
 	for _, v := range discoverer {


Test patch:
diff --git a/service/discovery/aws/compute_test.go b/service/discovery/aws/compute_test.go
index d92b0aec..4c8f1768 100644
--- a/service/discovery/aws/compute_test.go
+++ b/service/discovery/aws/compute_test.go
@@ -78,7 +78,7 @@ type mockLambdaAPIWithErrors struct {
 }
 
 // ListFunctions is the method implementation of the LambdaAPI interface
-func (m mockLambdaAPI) ListFunctions(_ context.Context, _ *lambda.ListFunctionsInput, _ ...func(*lambda.Options)) (*lambda.ListFunctionsOutput, error) {
+func (mockLambdaAPI) ListFunctions(_ context.Context, _ *lambda.ListFunctionsInput, _ ...func(*lambda.Options)) (*lambda.ListFunctionsOutput, error) {
 	return &lambda.ListFunctionsOutput{
 		Functions: []lambdaTypes.FunctionConfiguration{
 			{
@@ -92,7 +92,7 @@ func (m mockLambdaAPI) ListFunctions(_ context.Context, _ *lambda.ListFunctionsI
 	}, nil
 }
 
-func (m mockLambdaAPI51LambdaFunctions) ListFunctions(_ context.Context, input *lambda.ListFunctionsInput, _ ...func(*lambda.Options)) (output *lambda.ListFunctionsOutput, err error) {
+func (mockLambdaAPI51LambdaFunctions) ListFunctions(_ context.Context, input *lambda.ListFunctionsInput, _ ...func(*lambda.Options)) (output *lambda.ListFunctionsOutput, err error) {
 	var lambdaFunctions []lambdaTypes.FunctionConfiguration
 	nextMarker := "ShowNext"
 	if input.Marker == nil {
@@ -122,7 +122,7 @@ func (m mockLambdaAPI51LambdaFunctions) ListFunctions(_ context.Context, input *
 }
 
 // ListFunctions is the method implementation of the LambdaAPI interface
-func (m mockLambdaAPIWithErrors) ListFunctions(_ context.Context, _ *lambda.ListFunctionsInput, _ ...func(*lambda.Options)) (*lambda.ListFunctionsOutput, error) {
+func (mockLambdaAPIWithErrors) ListFunctions(_ context.Context, _ *lambda.ListFunctionsInput, _ ...func(*lambda.Options)) (*lambda.ListFunctionsOutput, error) {
 	err := &smithy.GenericAPIError{
 		Code:    "500",
 		Message: "Internal Server Error",
@@ -131,7 +131,7 @@ func (m mockLambdaAPIWithErrors) ListFunctions(_ context.Context, _ *lambda.List
 }
 
 // DescribeInstances is the method implementation of the EC2API interface
-func (m mockEC2API) DescribeInstances(_ context.Context, _ *ec2.DescribeInstancesInput, _ ...func(options *ec2.Options)) (*ec2.DescribeInstancesOutput, error) {
+func (mockEC2API) DescribeInstances(_ context.Context, _ *ec2.DescribeInstancesInput, _ ...func(options *ec2.Options)) (*ec2.DescribeInstancesOutput, error) {
 	// block device mappings for output struct
 	blockDeviceMappings := []types.InstanceBlockDeviceMapping{
 		{
@@ -184,7 +184,7 @@ func (m mockEC2API) DescribeInstances(_ context.Context, _ *ec2.DescribeInstance
 }
 
 // DescribeInstances is the method implementation of the EC2API interface
-func (m mockEC2APIWithErrors) DescribeInstances(_ context.Context, _ *ec2.DescribeInstancesInput, _ ...func(options *ec2.Options)) (*ec2.DescribeInstancesOutput, error) {
+func (mockEC2APIWithErrors) DescribeInstances(_ context.Context, _ *ec2.DescribeInstancesInput, _ ...func(options *ec2.Options)) (*ec2.DescribeInstancesOutput, error) {
 	err := &smithy.GenericAPIError{
 		Code:    "ConnectionError",
 		Message: "Couldn't resolve host. Bad connection?",
@@ -437,8 +437,8 @@ func TestComputeDiscovery_NewComputeDiscovery(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if got := NewComputeDiscovery(tt.args.client); !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("NewComputeDiscovery() = %v, want %v", got, tt.want)
+			if got := NewAwsComputeDiscovery(tt.args.client); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewAwsComputeDiscovery() = %v, want %v", got, tt.want)
 			}
 		})
 	}
diff --git a/service/discovery/azure/azure_test.go b/service/discovery/azure/azure_test.go
index 21a5e04b..7dcf8a36 100644
--- a/service/discovery/azure/azure_test.go
+++ b/service/discovery/azure/azure_test.go
@@ -31,9 +31,11 @@ import (
 	"fmt"
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
 	"io"
 	"net/http"
 	"net/http/httputil"
+	"testing"
 )
 
 func init() {
@@ -43,7 +45,7 @@ func init() {
 type mockSender struct {
 }
 
-func (m mockSender) Do(req *http.Request) (res *http.Response, err error) {
+func (mockSender) Do(req *http.Request) (res *http.Response, err error) {
 	if req.URL.Path == "/subscriptions" {
 		res, err = createResponse(map[string]interface{}{
 			"value": &[]map[string]interface{}{
@@ -64,7 +66,7 @@ func (m mockSender) Do(req *http.Request) (res *http.Response, err error) {
 
 type mockAuthorizer struct{}
 
-func (a mockAuthorizer) WithAuthorization() autorest.PrepareDecorator {
+func (mockAuthorizer) WithAuthorization() autorest.PrepareDecorator {
 	return func(p autorest.Preparer) autorest.Preparer {
 		return p
 	}
@@ -119,3 +121,60 @@ func LogResponse() autorest.RespondDecorator {
 		})
 	}
 }
+
+func TestGetResourceGroupName(t *testing.T) {
+	accountId := "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/res1/providers/Microsoft.Storage/storageAccounts/account3"
+	result := getResourceGroupName(accountId)
+
+	assert.Equal(t, "res1", result)
+}
+
+func TestApply(t *testing.T) {
+
+	// Test senderOption
+	so := senderOption{
+		sender: mockStorageSender{},
+	}
+
+	client := autorest.Client{}
+	so.apply(&client)
+	assert.Equal(t, so.sender, client.Sender)
+
+	// Test authorizerOption
+	ao := authorizerOption{
+		authorizer: mockAuthorizer{},
+	}
+
+	ao.apply(&client)
+	assert.Equal(t, ao.authorizer, client.Authorizer)
+
+	// Test azureDiscovery
+	ad := azureDiscovery{
+		authOption: &authorizerOption{
+			authorizer: mockAuthorizer{},
+		},
+	}
+
+	ad.apply(&client)
+	assert.Equal(t, ad.authOption.authorizer, client.Authorizer)
+}
+
+func TestWithSender(t *testing.T) {
+	expected := &senderOption{
+		sender: mockStorageSender{},
+	}
+
+	resp := WithSender(mockStorageSender{})
+
+	assert.Equal(t, expected, resp)
+}
+
+func TestWithAuthorizer(t *testing.T) {
+	expected := &authorizerOption{
+		authorizer: mockAuthorizer{},
+	}
+
+	resp := WithAuthorizer(mockAuthorizer{})
+
+	assert.Equal(t, expected, resp)
+}
diff --git a/service/discovery/discovery_test.go b/service/discovery/discovery_test.go
index f2ad8418..1236619a 100644
--- a/service/discovery/discovery_test.go
+++ b/service/discovery/discovery_test.go
@@ -26,11 +26,17 @@
 package discovery
 
 import (
+	service_assessment "clouditor.io/clouditor/service/assessment"
 	"context"
 	"errors"
 	"fmt"
+	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/status"
+	"google.golang.org/grpc/test/bufconn"
+	"net"
+	"os"
 	"testing"
 	"time"
 
@@ -43,6 +49,29 @@ import (
 	"google.golang.org/protobuf/types/known/emptypb"
 )
 
+var lis *bufconn.Listener
+
+func TestMain(m *testing.M) {
+	// pre-configuration for mocking assessment
+	const bufSize = 1024 * 1024 * 2
+	lis = bufconn.Listen(bufSize)
+	s := grpc.NewServer()
+	assessment.RegisterAssessmentServer(s, service_assessment.NewService())
+	go func() {
+		if err := s.Serve(lis); err != nil {
+			log.Fatalf("Server exited with error: %v", err)
+		}
+	}()
+
+	// make sure, that we are in the clouditor root folder to find the policies
+	err := os.Chdir("../../")
+	if err != nil {
+		panic(err)
+	}
+
+	os.Exit(m.Run())
+}
+
 func TestStartDiscovery(t *testing.T) {
 	discoveryService := NewService()
 
@@ -172,7 +201,187 @@ func TestQuery(t *testing.T) {
 		// If a bad resource was added it will be removed. Otherwise no-op
 		delete(s.resources, "MockResourceId")
 	}
+}
+
+func TestStart(t *testing.T) {
+
+	type envVariable struct {
+		hasEnvVariable   bool
+		envVariableKey   string
+		envVariableValue string
+	}
+
+	type fields struct {
+		hasRPCConnection bool
+		envVariables     []envVariable
+	}
 
+	tests := []struct {
+		name           string
+		fields         fields
+		wantResp       *discovery.StartDiscoveryResponse
+		wantErr        bool
+		wantErrMessage string
+	}{
+		{
+			name: "Azure authorizer from file",
+			fields: fields{
+				hasRPCConnection: true,
+				envVariables: []envVariable{
+					// We must set AZURE_AUTH_LOCATION to the Azure credentials test file and the set HOME to a wrong path so that the Azure authorizer passes and the K8S authorizer fails
+					{
+						hasEnvVariable:   true,
+						envVariableKey:   "AZURE_AUTH_LOCATION",
+						envVariableValue: "service/discovery/testdata/credentials_test_file",
+					},
+					{
+						hasEnvVariable:   true,
+						envVariableKey:   "HOME",
+						envVariableValue: "",
+					},
+				},
+			},
+			wantResp:       nil,
+			wantErr:        true,
+			wantErrMessage: "could not authenticate to Kubernetes",
+		},
+		{
+			name: "No Azure authorizer from file nor CLI",
+			fields: fields{
+				hasRPCConnection: true,
+				envVariables: []envVariable{
+					// We must set AZURE_AUTH_LOCATION and HOME to a wrong pahth so that both Azure authorizer fail
+					{
+						hasEnvVariable:   true,
+						envVariableKey:   "AZURE_AUTH_LOCATION",
+						envVariableValue: "",
+					},
+					{
+						hasEnvVariable:   true,
+						envVariableKey:   "HOME",
+						envVariableValue: "",
+					},
+				},
+			},
+			wantResp:       nil,
+			wantErr:        true,
+			wantErrMessage: "could not authenticate to Azure",
+		},
+		{
+			name: "No K8s authorizer",
+			fields: fields{
+				hasRPCConnection: true,
+				envVariables: []envVariable{
+					// We must set AZURE_AUTH_LOCATION to the Azure credentials test file and the set HOME to a wrong path so that the Azure authorizer passes and the K8S authorizer fails
+					{
+						hasEnvVariable:   true,
+						envVariableKey:   "AZURE_AUTH_LOCATION",
+						envVariableValue: "service/discovery/testdata/credentials_test_file",
+					},
+					{
+						hasEnvVariable:   true,
+						envVariableKey:   "HOME",
+						envVariableValue: "",
+					},
+				},
+			},
+			wantResp:       nil,
+			wantErr:        true,
+			wantErrMessage: "could not authenticate to Kubernetes",
+		},
+		{
+			name: "No RPC connection",
+			fields: fields{
+				hasRPCConnection: false,
+			},
+			wantResp:       nil,
+			wantErr:        true,
+			wantErrMessage: "could not initialize stream to Assessment",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewService()
+			if tt.fields.hasRPCConnection {
+				assert.NoError(t, s.mockAssessmentStream())
+			}
+
+			for _, env := range tt.fields.envVariables {
+				if env.hasEnvVariable {
+					t.Setenv(env.envVariableKey, env.envVariableValue)
+				}
+			}
+
+			resp, err := s.Start(context.TODO(), nil)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Got Start() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			} else {
+				assert.Equal(t, tt.wantResp, resp)
+			}
+
+			if err != nil {
+				assert.Contains(t, err.Error(), tt.wantErrMessage)
+			}
+		})
+	}
+}
+
+func TestInitAssessmentStream(t *testing.T) {
+	type fields struct {
+		hasRPCConnection bool
+	}
+
+	tests := []struct {
+		name           string
+		fields         fields
+		wantErr        bool
+		wantErrMessage string
+	}{
+		{
+			name: "No RPC connection",
+			fields: fields{
+				hasRPCConnection: false,
+			},
+			wantErr:        true,
+			wantErrMessage: "could not set up stream for assessing evidences",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewService()
+
+			err := s.initAssessmentStream()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Got initAssessmentStream() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if err != nil {
+				assert.Contains(t, err.Error(), tt.wantErrMessage)
+			}
+		})
+	}
+}
+
+// Mocking assessment service
+func bufDialer(context.Context, string) (net.Conn, error) {
+	return lis.Dial()
+}
+
+func (s *Service) mockAssessmentStream() error {
+	ctx := context.Background()
+	conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithTransportCredentials(insecure.NewCredentials()))
+	if err != nil {
+		return err
+	}
+	client, err := assessment.NewAssessmentClient(conn).AssessEvidences(ctx)
+	if err != nil {
+		return err
+	}
+	s.assessmentStream = client
+	return nil
 }
 
 func TestShutdown(t *testing.T) {
diff --git a/service/discovery/testdata/credentials_test_file b/service/discovery/testdata/credentials_test_file
new file mode 100644
index 00000000..35141d60
--- /dev/null
+++ b/service/discovery/testdata/credentials_test_file
@@ -0,0 +1,12 @@
+{
+  "clientId": "client-id-123",
+  "clientSecret": "client-secret-456",
+  "subscriptionId": "sub-id-789",
+  "tenantId": "tenant-id-123",
+  "activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
+  "resourceManagerEndpointUrl": "https://management.azure.com/",
+  "activeDirectoryGraphResourceId": "https://graph.windows.net/",
+  "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
+  "galleryEndpointUrl": "https://gallery.azure.com/",
+  "managementEndpointUrl": "https://management.core.windows.net/"
+}