diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index de9685c5f..aa7b6c572 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -22,5 +22,5 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.53 - args: --timeout=15m0s + version: v1.54 + args: --timeout=20m0s diff --git a/api/v1alpha2/mondoooperatorconfig_types.go b/api/v1alpha2/mondoooperatorconfig_types.go index 109207676..c7a31d7c6 100644 --- a/api/v1alpha2/mondoooperatorconfig_types.go +++ b/api/v1alpha2/mondoooperatorconfig_types.go @@ -31,6 +31,8 @@ type MondooOperatorConfigSpec struct { Metrics Metrics `json:"metrics,omitempty"` // Allows skipping Image resolution from upstream repository SkipContainerResolution bool `json:"skipContainerResolution,omitempty"` + // HttpProxy specifies a proxy to use for HTTP requests to the Mondoo platform. + HttpProxy *string `json:"httpProxy,omitempty"` } type Metrics struct { diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go index a53110d35..0b1d6e91c 100644 --- a/api/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -386,6 +386,11 @@ func (in *MondooOperatorConfigList) DeepCopyObject() runtime.Object { func (in *MondooOperatorConfigSpec) DeepCopyInto(out *MondooOperatorConfigSpec) { *out = *in in.Metrics.DeepCopyInto(&out.Metrics) + if in.HttpProxy != nil { + in, out := &in.HttpProxy, &out.HttpProxy + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MondooOperatorConfigSpec. diff --git a/cmd/mondoo-operator/garbage_collect/cmd.go b/cmd/mondoo-operator/garbage_collect/cmd.go index 745ef948b..7d9ad25ee 100644 --- a/cmd/mondoo-operator/garbage_collect/cmd.go +++ b/cmd/mondoo-operator/garbage_collect/cmd.go @@ -15,7 +15,7 @@ import ( "github.com/spf13/cobra" "go.mondoo.com/cnquery/motor/providers" "go.mondoo.com/cnspec/policy/scan" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient" "go.mondoo.com/mondoo-operator/pkg/utils/logger" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -70,10 +70,13 @@ func init() { token = strings.TrimSuffix(string(tokenBytes), "\n") } - client := mondooclient.NewClient(mondooclient.ClientOptions{ + client, err := scanapiclient.NewClient(scanapiclient.ScanApiClientOptions{ ApiEndpoint: *scanApiUrl, Token: token, }) + if err != nil { + return err + } logger.Info("triggering garbage collection") ctx, cancel := context.WithTimeout(context.Background(), time.Duration((*timeout))*time.Minute) @@ -87,7 +90,7 @@ func init() { } } -func GarbageCollectCmd(ctx context.Context, client mondooclient.Client, platformRuntime, olderThan, managedBy string, labels map[string]string, logger logr.Logger) error { +func GarbageCollectCmd(ctx context.Context, client scanapiclient.ScanApiClient, platformRuntime, olderThan, managedBy string, labels map[string]string, logger logr.Logger) error { gcOpts := &scan.GarbageCollectOptions{ ManagedBy: managedBy, Labels: labels, diff --git a/cmd/mondoo-operator/k8s_scan/cmd.go b/cmd/mondoo-operator/k8s_scan/cmd.go index 52adfeb65..8a457406d 100644 --- a/cmd/mondoo-operator/k8s_scan/cmd.go +++ b/cmd/mondoo-operator/k8s_scan/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/spf13/cobra" "go.mondoo.com/cnquery/motor/providers" "go.mondoo.com/mondoo-operator/cmd/mondoo-operator/garbage_collect" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient" "go.mondoo.com/mondoo-operator/pkg/utils/logger" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -56,15 +56,18 @@ func init() { } token := strings.TrimSuffix(string(tokenBytes), "\n") - client := mondooclient.NewClient(mondooclient.ClientOptions{ + client, err := scanapiclient.NewClient(scanapiclient.ScanApiClientOptions{ ApiEndpoint: *scanApiUrl, Token: token, }) + if err != nil { + return err + } logger.Info("triggering Kubernetes resources scan") ctx, cancel := context.WithTimeout(context.Background(), time.Duration((*timeout))*time.Minute) defer cancel() - scanOpts := &mondooclient.ScanKubernetesResourcesOpts{ + scanOpts := &scanapiclient.ScanKubernetesResourcesOpts{ IntegrationMrn: *integrationMrn, ScanContainerImages: *scanContainerImages, ManagedBy: *setManagedBy, diff --git a/config/crd/bases/k8s.mondoo.com_mondoooperatorconfigs.yaml b/config/crd/bases/k8s.mondoo.com_mondoooperatorconfigs.yaml index ac8cdfaf8..855d0769d 100644 --- a/config/crd/bases/k8s.mondoo.com_mondoooperatorconfigs.yaml +++ b/config/crd/bases/k8s.mondoo.com_mondoooperatorconfigs.yaml @@ -39,6 +39,10 @@ spec: spec: description: MondooOperatorConfigSpec defines the desired state of MondooOperatorConfig properties: + httpProxy: + description: HttpProxy specifies a proxy to use for HTTP requests + to the Mondoo platform. + type: string metrics: description: Metrics controls the enabling/disabling of metrics report of mondoo-operator diff --git a/controllers/admission/deployment_handler_test.go b/controllers/admission/deployment_handler_test.go index e51eacaa4..5f35ab642 100644 --- a/controllers/admission/deployment_handler_test.go +++ b/controllers/admission/deployment_handler_test.go @@ -32,8 +32,8 @@ import ( certmanagerv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" mondoov1alpha2 "go.mondoo.com/mondoo-operator/api/v1alpha2" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" "go.mondoo.com/mondoo-operator/pkg/constants" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" "go.mondoo.com/mondoo-operator/pkg/utils/k8s" fakeMondoo "go.mondoo.com/mondoo-operator/pkg/utils/mondoo/fake" ctrl "sigs.k8s.io/controller-runtime" diff --git a/controllers/container_image/deployment_handler.go b/controllers/container_image/deployment_handler.go index 60dd4f57c..d4aa253e2 100644 --- a/controllers/container_image/deployment_handler.go +++ b/controllers/container_image/deployment_handler.go @@ -114,7 +114,7 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error { } existing := &batchv1.CronJob{} - desired := CronJob(mondooClientImage, integrationMrn, clusterUid, privateRegistriesSecretName, *n.Mondoo) + desired := CronJob(mondooClientImage, integrationMrn, clusterUid, privateRegistriesSecretName, *n.Mondoo, *n.MondooOperatorConfig) if err := ctrl.SetControllerReference(n.Mondoo, desired, n.KubeClient.Scheme()); err != nil { logger.Error(err, "Failed to set ControllerReference", "namespace", desired.Namespace, "name", desired.Name) return err diff --git a/controllers/container_image/deployment_handler_test.go b/controllers/container_image/deployment_handler_test.go index 209d9c072..7ba95c90c 100644 --- a/controllers/container_image/deployment_handler_test.go +++ b/controllers/container_image/deployment_handler_test.go @@ -70,7 +70,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create() { image, err := s.containerImageResolver.CnspecImage("", "", false) s.NoError(err) - expected := CronJob(image, "", test.KubeSystemNamespaceUid, "", s.auditConfig) + expected := CronJob(image, "", test.KubeSystemNamespaceUid, "", s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}) s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme())) // Set some fields that the kube client sets @@ -113,7 +113,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_PrivateRegistriesSecret() image, err := s.containerImageResolver.CnspecImage("", "", false) s.NoError(err) - expected := CronJob(image, "", test.KubeSystemNamespaceUid, s.auditConfig.Spec.Scanner.PrivateRegistriesPullSecretRef.Name, s.auditConfig) + expected := CronJob(image, "", test.KubeSystemNamespaceUid, s.auditConfig.Spec.Scanner.PrivateRegistriesPullSecretRef.Name, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}) s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme())) // Set some fields that the kube client sets @@ -155,7 +155,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_ConsoleIntegration() { image, err := s.containerImageResolver.CnspecImage("", "", false) s.NoError(err) - expected := CronJob(image, integrationMrn, test.KubeSystemNamespaceUid, "", s.auditConfig) + expected := CronJob(image, integrationMrn, test.KubeSystemNamespaceUid, "", s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}) s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme())) // Set some fields that the kube client sets @@ -179,7 +179,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Update() { s.NoError(err) // Make sure a cron job exists with different container command - cronJob := CronJob(image, "", "", "", s.auditConfig) + cronJob := CronJob(image, "", "", "", s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}) cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command = []string{"test-command"} s.NoError(d.KubeClient.Create(s.ctx, cronJob)) @@ -187,7 +187,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Update() { s.NoError(err) s.True(result.IsZero()) - expected := CronJob(image, "", test.KubeSystemNamespaceUid, "", s.auditConfig) + expected := CronJob(image, "", test.KubeSystemNamespaceUid, "", s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}) s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme())) // Set some fields that the kube client sets diff --git a/controllers/container_image/resources.go b/controllers/container_image/resources.go index 1915f0013..f1af37e1b 100644 --- a/controllers/container_image/resources.go +++ b/controllers/container_image/resources.go @@ -35,9 +35,20 @@ const ( InventoryConfigMapBase = "-containers-inventory" ) -func CronJob(image, integrationMrn, clusterUid, privateImageScanningSecretName string, m v1alpha2.MondooAuditConfig) *batchv1.CronJob { +func CronJob(image, integrationMrn, clusterUid, privateImageScanningSecretName string, m v1alpha2.MondooAuditConfig, cfg v1alpha2.MondooOperatorConfig) *batchv1.CronJob { ls := CronJobLabels(m) + cmd := []string{ + "cnspec", "scan", "k8s", + "--config", "/etc/opt/mondoo/mondoo.yml", + "--inventory-file", "/etc/opt/mondoo/inventory.yml", + "--score-threshold", "0", + } + + if cfg.Spec.HttpProxy != nil { + cmd = append(cmd, []string{"--api-proxy", *cfg.Spec.HttpProxy}...) + } + // We want to start the cron job one minute after it was enabled. cronStart := time.Now().Add(1 * time.Minute) cronTab := fmt.Sprintf("%d %d * * *", cronStart.Minute(), cronStart.Hour()) @@ -65,13 +76,8 @@ func CronJob(image, integrationMrn, clusterUid, privateImageScanningSecretName s Image: image, ImagePullPolicy: corev1.PullIfNotPresent, Name: "mondoo-containers-scan", - Command: []string{ - "cnspec", "scan", "k8s", - "--config", "/etc/opt/mondoo/mondoo.yml", - "--inventory-file", "/etc/opt/mondoo/inventory.yml", - "--score-threshold", "0", - }, - Resources: k8s.ResourcesRequirementsWithDefaults(m.Spec.Containers.Resources, k8s.DefaultContainerScanningResources), + Command: cmd, + Resources: k8s.ResourcesRequirementsWithDefaults(m.Spec.Containers.Resources, k8s.DefaultContainerScanningResources), SecurityContext: &corev1.SecurityContext{ AllowPrivilegeEscalation: pointer.Bool(false), ReadOnlyRootFilesystem: pointer.Bool(true), diff --git a/controllers/integration/integration_controller.go b/controllers/integration/integration_controller.go index 5376ae423..b2dd75c01 100644 --- a/controllers/integration/integration_controller.go +++ b/controllers/integration/integration_controller.go @@ -15,15 +15,17 @@ import ( "time" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/log" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" "go.mondoo.com/mondoo-operator/api/v1alpha2" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" "go.mondoo.com/mondoo-operator/pkg/utils/k8s" "go.mondoo.com/mondoo-operator/pkg/utils/mondoo" ) @@ -60,7 +62,7 @@ type IntegrationReconciler struct { // Interval is the length of time we sleep between runs Interval time.Duration - MondooClientBuilder func(mondooclient.ClientOptions) mondooclient.Client + MondooClientBuilder func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) ctx context.Context } @@ -119,7 +121,14 @@ func (r *IntegrationReconciler) processMondooAuditConfig(m v1alpha2.MondooAuditC return err } - if err = mondoo.IntegrationCheckIn(r.ctx, integrationMrn, *serviceAccount, r.MondooClientBuilder, logger); err != nil { + config := &v1alpha2.MondooOperatorConfig{} + if err = r.Client.Get(r.ctx, types.NamespacedName{Name: v1alpha2.MondooOperatorConfigName}, config); err != nil { + if !errors.IsNotFound(err) { + return err + } + } + + if err = mondoo.IntegrationCheckIn(r.ctx, integrationMrn, *serviceAccount, r.MondooClientBuilder, config.Spec.HttpProxy, logger); err != nil { logger.Error(err, "failed to CheckIn() for integration", "integrationMRN", string(integrationMrn)) return err } diff --git a/controllers/integration/integration_controller_test.go b/controllers/integration/integration_controller_test.go index fb064aadd..7e8a4b47d 100644 --- a/controllers/integration/integration_controller_test.go +++ b/controllers/integration/integration_controller_test.go @@ -31,9 +31,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "go.mondoo.com/mondoo-operator/api/v1alpha2" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" + mockmondoo "go.mondoo.com/mondoo-operator/pkg/client/mondooclient/mock" "go.mondoo.com/mondoo-operator/pkg/constants" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" - mockmondoo "go.mondoo.com/mondoo-operator/pkg/mondooclient/mock" "go.mondoo.com/mondoo-operator/tests/credentials" ) @@ -105,15 +105,15 @@ func (s *IntegrationCheckInSuite) TestCheckIn() { mockCtrl := gomock.NewController(s.T()) - mClient := mockmondoo.NewMockClient(mockCtrl) + mClient := mockmondoo.NewMockMondooClient(mockCtrl) mClient.EXPECT().IntegrationCheckIn(gomock.Any(), &mondooclient.IntegrationCheckInInput{ Mrn: testIntegrationMRN, // make sure MRN in the CheckIn() in what is required for the real Mondoo API }).Times(1).Return(&mondooclient.IntegrationCheckInOutput{ Mrn: testIntegrationMRN, }, nil) - testMondooClientBuilder := func(mondooclient.ClientOptions) mondooclient.Client { - return mClient + testMondooClientBuilder := func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) { + return mClient, nil } fakeClient := fake.NewClientBuilder().WithRuntimeObjects(existingObjects...).Build() @@ -150,15 +150,15 @@ func (s *IntegrationCheckInSuite) TestClearPreviousCondition() { mockCtrl := gomock.NewController(s.T()) - mClient := mockmondoo.NewMockClient(mockCtrl) + mClient := mockmondoo.NewMockMondooClient(mockCtrl) mClient.EXPECT().IntegrationCheckIn(gomock.Any(), &mondooclient.IntegrationCheckInInput{ Mrn: testIntegrationMRN, // make sure MRN in the CheckIn() in what is required for the real Mondoo API }).Times(1).Return(&mondooclient.IntegrationCheckInOutput{ Mrn: testIntegrationMRN, }, nil) - testMondooClientBuilder := func(mondooclient.ClientOptions) mondooclient.Client { - return mClient + testMondooClientBuilder := func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) { + return mClient, nil } fakeClient := fake.NewClientBuilder().WithRuntimeObjects(existingObjects...).Build() @@ -192,11 +192,11 @@ func (s *IntegrationCheckInSuite) TestMissingIntegrationMRN() { mockCtrl := gomock.NewController(s.T()) - mClient := mockmondoo.NewMockClient(mockCtrl) + mClient := mockmondoo.NewMockMondooClient(mockCtrl) // EXPECT no call because of the missing integration MRN data - testMondooClientBuilder := func(mondooclient.ClientOptions) mondooclient.Client { - return mClient + testMondooClientBuilder := func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) { + return mClient, nil } fakeClient := fake.NewClientBuilder().WithRuntimeObjects(existingObjects...).Build() @@ -230,11 +230,11 @@ func (s *IntegrationCheckInSuite) TestBadServiceAccountData() { mockCtrl := gomock.NewController(s.T()) - mClient := mockmondoo.NewMockClient(mockCtrl) + mClient := mockmondoo.NewMockMondooClient(mockCtrl) // EXPECT no call because of the bad service account data - testMondooClientBuilder := func(mondooclient.ClientOptions) mondooclient.Client { - return mClient + testMondooClientBuilder := func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) { + return mClient, nil } fakeClient := fake.NewClientBuilder().WithRuntimeObjects(existingObjects...).Build() @@ -266,13 +266,13 @@ func (s *IntegrationCheckInSuite) TestFailedCheckIn() { mockCtrl := gomock.NewController(s.T()) - mClient := mockmondoo.NewMockClient(mockCtrl) + mClient := mockmondoo.NewMockMondooClient(mockCtrl) mClient.EXPECT().IntegrationCheckIn(gomock.Any(), gomock.Any()).Times(1).Return( nil, fmt.Errorf(`http status 401: {"code":16,"message":"request permission unauthenticated"}`), ) - testMondooClientBuilder := func(mondooclient.ClientOptions) mondooclient.Client { - return mClient + testMondooClientBuilder := func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) { + return mClient, nil } fakeClient := fake.NewClientBuilder().WithRuntimeObjects(existingObjects...).Build() diff --git a/controllers/k8s_scan/deployment_handler_test.go b/controllers/k8s_scan/deployment_handler_test.go index f498f28b2..09c41297e 100644 --- a/controllers/k8s_scan/deployment_handler_test.go +++ b/controllers/k8s_scan/deployment_handler_test.go @@ -32,8 +32,8 @@ import ( "go.mondoo.com/mondoo-operator/controllers/resource_monitor/scan_api_store" scanapistoremock "go.mondoo.com/mondoo-operator/controllers/resource_monitor/scan_api_store/mock" "go.mondoo.com/mondoo-operator/controllers/scanapi" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" "go.mondoo.com/mondoo-operator/pkg/constants" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" "go.mondoo.com/mondoo-operator/pkg/utils/mondoo" fakeMondoo "go.mondoo.com/mondoo-operator/pkg/utils/mondoo/fake" "go.mondoo.com/mondoo-operator/pkg/utils/test" diff --git a/controllers/mondooauditconfig_controller.go b/controllers/mondooauditconfig_controller.go index 05676c5f7..912752cad 100644 --- a/controllers/mondooauditconfig_controller.go +++ b/controllers/mondooauditconfig_controller.go @@ -38,8 +38,8 @@ import ( "go.mondoo.com/mondoo-operator/controllers/resource_monitor/scan_api_store" "go.mondoo.com/mondoo-operator/controllers/scanapi" "go.mondoo.com/mondoo-operator/controllers/status" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" "go.mondoo.com/mondoo-operator/pkg/constants" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" "go.mondoo.com/mondoo-operator/pkg/utils/k8s" "go.mondoo.com/mondoo-operator/pkg/utils/mondoo" "go.mondoo.com/mondoo-operator/pkg/version" @@ -50,7 +50,7 @@ const finalizerString = "k8s.mondoo.com/delete" // MondooAuditConfigReconciler reconciles a MondooAuditConfig object type MondooAuditConfigReconciler struct { client.Client - MondooClientBuilder func(mondooclient.ClientOptions) mondooclient.Client + MondooClientBuilder func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) ContainerImageResolver mondoo.ContainerImageResolver StatusReporter *status.StatusReporter RunningOnOpenShift bool @@ -123,7 +123,7 @@ func (r *MondooAuditConfigReconciler) Reconcile(ctx context.Context, req ctrl.Re } defer func() { - reportErr := r.StatusReporter.Report(ctx, *mondooAuditConfig) + reportErr := r.StatusReporter.Report(ctx, *mondooAuditConfig, *config) // If the err from the reconcile func is nil, the all steps were executed it successfully // If there was an error, we do not override the existing error with the status report error @@ -210,7 +210,7 @@ func (r *MondooAuditConfigReconciler) Reconcile(ctx context.Context, req ctrl.Re // If spec.MondooTokenSecretRef != "" and the Secret referenced in spec.MondooCredsSecretRef // does not exist, then attempt to trade the token for a Mondoo service account and save it // in the Secret referenced in .spec.MondooCredsSecretRef - if reconcileError = r.exchangeTokenForServiceAccount(ctx, mondooAuditConfig, log); reconcileError != nil { + if reconcileError = r.exchangeTokenForServiceAccount(ctx, mondooAuditConfig, config, log); reconcileError != nil { log.Error(reconcileError, "errors while checking if Mondoo service account needs creating") return ctrl.Result{}, reconcileError } @@ -321,7 +321,7 @@ func (r *MondooAuditConfigReconciler) nodeEventsRequestMapper(o client.Object) [ return requests } -func (r *MondooAuditConfigReconciler) exchangeTokenForServiceAccount(ctx context.Context, auditConfig *v1alpha2.MondooAuditConfig, log logr.Logger) error { +func (r *MondooAuditConfigReconciler) exchangeTokenForServiceAccount(ctx context.Context, auditConfig *v1alpha2.MondooAuditConfig, cfg *v1alpha2.MondooOperatorConfig, log logr.Logger) error { if auditConfig.Spec.MondooCredsSecretRef.Name == "" { log.Info("MondooAuditConfig without .spec.mondooCredsSecretRef defined") return nil @@ -364,7 +364,15 @@ func (r *MondooAuditConfigReconciler) exchangeTokenForServiceAccount(ctx context log.Info("Creating Mondoo service account from token") tokenData := string(mondooTokenSecret.Data[constants.MondooTokenSecretKey]) - return mondoo.CreateServiceAccountFromToken(ctx, r.Client, r.MondooClientBuilder, auditConfig.Spec.ConsoleIntegration.Enable, client.ObjectKeyFromObject(mondooCredsSecret), tokenData, log) + return mondoo.CreateServiceAccountFromToken( + ctx, + r.Client, + r.MondooClientBuilder, + auditConfig.Spec.ConsoleIntegration.Enable, + client.ObjectKeyFromObject(mondooCredsSecret), + tokenData, + cfg.Spec.HttpProxy, + log) } // SetupWithManager sets up the controller with the Manager. diff --git a/controllers/mondooauditconfig_controller_test.go b/controllers/mondooauditconfig_controller_test.go index 2992d3640..d42cbef68 100644 --- a/controllers/mondooauditconfig_controller_test.go +++ b/controllers/mondooauditconfig_controller_test.go @@ -34,8 +34,8 @@ import ( "go.mondoo.com/mondoo-operator/api/v1alpha2" "go.mondoo.com/mondoo-operator/controllers/resource_monitor/scan_api_store" "go.mondoo.com/mondoo-operator/controllers/status" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" - mockmondoo "go.mondoo.com/mondoo-operator/pkg/mondooclient/mock" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" + mockmondoo "go.mondoo.com/mondoo-operator/pkg/client/mondooclient/mock" "go.mondoo.com/mondoo-operator/pkg/version" "go.mondoo.com/mondoo-operator/tests/credentials" k8sversion "k8s.io/apimachinery/pkg/version" @@ -88,7 +88,7 @@ func TestTokenRegistration(t *testing.T) { tests := []struct { name string existingObjects []runtime.Object - mockMondooClient func(*gomock.Controller) *mockmondoo.MockClient + mockMondooClient func(*gomock.Controller) *mockmondoo.MockMondooClient verify func(*testing.T, client.Client) expectError bool }{ @@ -98,8 +98,8 @@ func TestTokenRegistration(t *testing.T) { testTokenSecret(), testMondooAuditConfig(), }, - mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockClient { - mClient := mockmondoo.NewMockClient(mockCtrl) + mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockMondooClient { + mClient := mockmondoo.NewMockMondooClient(mockCtrl) mClient.EXPECT().ExchangeRegistrationToken(gomock.Any(), gomock.Any()).Times(1).Return(&mondooclient.ExchangeRegistrationTokenOutput{ ServiceAccount: testServiceAccountData, @@ -127,8 +127,8 @@ func TestTokenRegistration(t *testing.T) { existingObjects: []runtime.Object{ testMondooAuditConfig(), }, - mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockClient { - mClient := mockmondoo.NewMockClient(mockCtrl) + mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockMondooClient { + mClient := mockmondoo.NewMockMondooClient(mockCtrl) return mClient }, @@ -161,8 +161,8 @@ func TestTokenRegistration(t *testing.T) { return objs }(), - mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockClient { - mClient := mockmondoo.NewMockClient(mockCtrl) + mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockMondooClient { + mClient := mockmondoo.NewMockMondooClient(mockCtrl) return mClient }, @@ -186,8 +186,8 @@ func TestTokenRegistration(t *testing.T) { testTokenSecret(), testMondooAuditConfig(), }, - mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockClient { - mClient := mockmondoo.NewMockClient(mockCtrl) + mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockMondooClient { + mClient := mockmondoo.NewMockMondooClient(mockCtrl) mClient.EXPECT().ExchangeRegistrationToken(gomock.Any(), gomock.Any()).Times(1).Return(nil, fmt.Errorf("an error occurred")) @@ -204,8 +204,8 @@ func TestTokenRegistration(t *testing.T) { objs = append(objs, sec) return objs }(), - mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockClient { - mClient := mockmondoo.NewMockClient(mockCtrl) + mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockMondooClient { + mClient := mockmondoo.NewMockMondooClient(mockCtrl) return mClient }, @@ -217,8 +217,8 @@ func TestTokenRegistration(t *testing.T) { testIntegrationTokenSecret(), testMondooAuditConfigWithIntegration(), }, - mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockClient { - mClient := mockmondoo.NewMockClient(mockCtrl) + mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockMondooClient { + mClient := mockmondoo.NewMockMondooClient(mockCtrl) mClient.EXPECT().IntegrationRegister(gomock.Any(), &mondooclient.IntegrationRegisterInput{ Mrn: testIntegrationMRN, // verify we are getting the expected integration MRN @@ -295,8 +295,8 @@ func TestTokenRegistration(t *testing.T) { testTokenSecret(), testMondooAuditConfigWithIntegration(), }, - mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockClient { - mClient := mockmondoo.NewMockClient(mockCtrl) + mockMondooClient: func(mockCtrl *gomock.Controller) *mockmondoo.MockMondooClient { + mClient := mockmondoo.NewMockMondooClient(mockCtrl) return mClient }, @@ -312,8 +312,8 @@ func TestTokenRegistration(t *testing.T) { mClient := test.mockMondooClient(mockCtrl) - testMondooClientBuilder := func(mondooclient.ClientOptions) mondooclient.Client { - return mClient + testMondooClientBuilder := func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) { + return mClient, nil } fakeClient := fake.NewClientBuilder().WithRuntimeObjects(test.existingObjects...).Build() @@ -358,13 +358,13 @@ func TestMondooAuditConfigStatus(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() - mClient := mockmondoo.NewMockClient(mockCtrl) + mClient := mockmondoo.NewMockMondooClient(mockCtrl) mClient.EXPECT().ExchangeRegistrationToken(gomock.Any(), gomock.Any()).Times(1).Return(&mondooclient.ExchangeRegistrationTokenOutput{ ServiceAccount: testServiceAccountData, }, nil) - testMondooClientBuilder := func(mondooclient.ClientOptions) mondooclient.Client { - return mClient + testMondooClientBuilder := func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) { + return mClient, nil } mondooAuditConfig := testMondooAuditConfig() diff --git a/controllers/nodes/deployment_handler.go b/controllers/nodes/deployment_handler.go index 7c121a5c9..f9441871c 100644 --- a/controllers/nodes/deployment_handler.go +++ b/controllers/nodes/deployment_handler.go @@ -88,7 +88,7 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error { } existing := &batchv1.CronJob{} - desired := CronJob(mondooClientImage, node, *n.Mondoo, n.IsOpenshift) + desired := CronJob(mondooClientImage, node, *n.Mondoo, n.IsOpenshift, *n.MondooOperatorConfig) if err := ctrl.SetControllerReference(n.Mondoo, desired, n.KubeClient.Scheme()); err != nil { logger.Error(err, "Failed to set ControllerReference", "namespace", desired.Namespace, "name", desired.Name) diff --git a/controllers/nodes/deployment_handler_test.go b/controllers/nodes/deployment_handler_test.go index dbbb9132d..4b993114b 100644 --- a/controllers/nodes/deployment_handler_test.go +++ b/controllers/nodes/deployment_handler_test.go @@ -16,9 +16,9 @@ import ( "time" "github.com/stretchr/testify/suite" - mondoov1alpha2 "go.mondoo.com/mondoo-operator/api/v1alpha2" + "go.mondoo.com/mondoo-operator/api/v1alpha2" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" "go.mondoo.com/mondoo-operator/pkg/constants" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" "go.mondoo.com/mondoo-operator/pkg/utils/mondoo" fakeMondoo "go.mondoo.com/mondoo-operator/pkg/utils/mondoo/fake" "go.mondoo.com/mondoo-operator/tests/framework/utils" @@ -42,14 +42,14 @@ type DeploymentHandlerSuite struct { scheme *runtime.Scheme containerImageResolver mondoo.ContainerImageResolver - auditConfig mondoov1alpha2.MondooAuditConfig + auditConfig v1alpha2.MondooAuditConfig fakeClientBuilder *fake.ClientBuilder } func (s *DeploymentHandlerSuite) SetupSuite() { s.ctx = context.Background() s.scheme = clientgoscheme.Scheme - s.Require().NoError(mondoov1alpha2.AddToScheme(s.scheme)) + s.Require().NoError(v1alpha2.AddToScheme(s.scheme)) s.containerImageResolver = fakeMondoo.NewNoOpContainerImageResolver() } @@ -237,7 +237,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateCronJobs() { s.NoError(err) for _, n := range nodes.Items { - expected := CronJob(image, n, s.auditConfig, false) + expected := CronJob(image, n, s.auditConfig, false, v1alpha2.MondooOperatorConfig{}) s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme())) // Set some fields that the kube client sets @@ -287,7 +287,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_UpdateCronJobs() { s.NoError(err) // Make sure a cron job exists for one of the nodes - cronJob := CronJob(image, nodes.Items[1], s.auditConfig, false) + cronJob := CronJob(image, nodes.Items[1], s.auditConfig, false, v1alpha2.MondooOperatorConfig{}) cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command = []string{"test-command"} s.NoError(d.KubeClient.Create(s.ctx, cronJob)) @@ -296,7 +296,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_UpdateCronJobs() { s.True(result.IsZero()) for i, n := range nodes.Items { - expected := CronJob(image, n, s.auditConfig, false) + expected := CronJob(image, n, s.auditConfig, false, v1alpha2.MondooOperatorConfig{}) s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme())) // Set some fields that the kube client sets @@ -349,7 +349,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_CleanCronJobsForDeletedNodes() { s.Equal(1, len(cronJobs.Items)) - expected := CronJob(image, nodes.Items[0], s.auditConfig, false) + expected := CronJob(image, nodes.Items[0], s.auditConfig, false, v1alpha2.MondooOperatorConfig{}) s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme())) // Set some fields that the kube client sets @@ -467,7 +467,7 @@ func (s *DeploymentHandlerSuite) createDeploymentHandler() DeploymentHandler { KubeClient: s.fakeClientBuilder.Build(), Mondoo: &s.auditConfig, ContainerImageResolver: s.containerImageResolver, - MondooOperatorConfig: &mondoov1alpha2.MondooOperatorConfig{}, + MondooOperatorConfig: &v1alpha2.MondooOperatorConfig{}, } } diff --git a/controllers/nodes/resources.go b/controllers/nodes/resources.go index 8bd681c32..8ffeb79a5 100644 --- a/controllers/nodes/resources.go +++ b/controllers/nodes/resources.go @@ -41,7 +41,7 @@ const ( ignoreAnnotationValue = "ignore" ) -func CronJob(image string, node corev1.Node, m v1alpha2.MondooAuditConfig, isOpenshift bool) *batchv1.CronJob { +func CronJob(image string, node corev1.Node, m v1alpha2.MondooAuditConfig, isOpenshift bool, cfg v1alpha2.MondooOperatorConfig) *batchv1.CronJob { ls := CronJobLabels(m) cronTab := fmt.Sprintf("%d * * * *", time.Now().Add(1*time.Minute).Minute()) @@ -55,6 +55,10 @@ func CronJob(image string, node corev1.Node, m v1alpha2.MondooAuditConfig, isOpe "--score-threshold", "0", } + if cfg.Spec.HttpProxy != nil { + cmd = append(cmd, []string{"--api-proxy", *cfg.Spec.HttpProxy}...) + } + return &batchv1.CronJob{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ diff --git a/controllers/nodes/resources_test.go b/controllers/nodes/resources_test.go index a0b1a5fa4..4326fcbda 100644 --- a/controllers/nodes/resources_test.go +++ b/controllers/nodes/resources_test.go @@ -153,7 +153,7 @@ func TestResources(t *testing.T) { }, } mac := *test.mondooauditconfig() - cronJobSepc := CronJob("test123", *testNode, mac, false) + cronJobSepc := CronJob("test123", *testNode, mac, false, v1alpha2.MondooOperatorConfig{}) assert.Equal(t, test.expectedResources, cronJobSepc.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Resources) }) } @@ -166,7 +166,7 @@ func TestCronJob_PrivilegedOpenshift(t *testing.T) { }, } mac := testMondooAuditConfig() - cronJobSepc := CronJob("test123", *testNode, *mac, true) + cronJobSepc := CronJob("test123", *testNode, *mac, true, v1alpha2.MondooOperatorConfig{}) assert.True(t, *cronJobSepc.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext.Privileged) assert.True(t, *cronJobSepc.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation) } @@ -178,7 +178,7 @@ func TestCronJob_Privileged(t *testing.T) { }, } mac := testMondooAuditConfig() - cronJobSepc := CronJob("test123", *testNode, *mac, false) + cronJobSepc := CronJob("test123", *testNode, *mac, false, v1alpha2.MondooOperatorConfig{}) assert.False(t, *cronJobSepc.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext.Privileged) assert.False(t, *cronJobSepc.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation) } diff --git a/controllers/resource_monitor/debouncer/debouncer_test.go b/controllers/resource_monitor/debouncer/debouncer_test.go index 02141dd2d..5662d804b 100644 --- a/controllers/resource_monitor/debouncer/debouncer_test.go +++ b/controllers/resource_monitor/debouncer/debouncer_test.go @@ -17,7 +17,7 @@ import ( "github.com/stretchr/testify/suite" "go.mondoo.com/mondoo-operator/controllers/resource_monitor/scan_api_store" scanapistoremock "go.mondoo.com/mondoo-operator/controllers/resource_monitor/scan_api_store/mock" - "go.mondoo.com/mondoo-operator/pkg/mondooclient/mock" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient/mock" ) type DebouncerSuite struct { @@ -25,7 +25,7 @@ type DebouncerSuite struct { ctx context.Context ctxCancel context.CancelFunc mockCtrl *gomock.Controller - mockMondooClient *mock.MockClient + mockMondooClient *mock.MockScanApiClient scanApiStore *scanapistoremock.MockScanApiStore debouncer *debouncer } @@ -33,7 +33,7 @@ type DebouncerSuite struct { func (s *DebouncerSuite) BeforeTest(suiteName, testName string) { s.ctx, s.ctxCancel = context.WithCancel(context.Background()) s.mockCtrl = gomock.NewController(s.T()) - s.mockMondooClient = mock.NewMockClient(s.mockCtrl) + s.mockMondooClient = mock.NewMockScanApiClient(s.mockCtrl) s.scanApiStore = scanapistoremock.NewMockScanApiStore(s.mockCtrl) s.debouncer = NewDebouncer(s.scanApiStore).(*debouncer) s.debouncer.flushTimeout = 1 * time.Second @@ -147,7 +147,7 @@ func (s *DebouncerSuite) TestStart_MultipleScanApiClients() { } } - mockMondooClient2 := mock.NewMockClient(s.mockCtrl) + mockMondooClient2 := mock.NewMockScanApiClient(s.mockCtrl) integrationMrn := "integration-mrn" s.scanApiStore.EXPECT().GetAll().Times(1).Return([]scan_api_store.ClientConfiguration{ diff --git a/controllers/resource_monitor/scan_api_store/scan_api_store.go b/controllers/resource_monitor/scan_api_store/scan_api_store.go index b3ae884bd..8b5a98722 100644 --- a/controllers/resource_monitor/scan_api_store/scan_api_store.go +++ b/controllers/resource_monitor/scan_api_store/scan_api_store.go @@ -11,7 +11,7 @@ package scan_api_store import ( "context" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -30,7 +30,7 @@ type ScanApiStore interface { } type ClientConfiguration struct { - Client mondooclient.Client + Client scanapiclient.ScanApiClient IntegrationMrn string IncludeNamespaces []string ExcludeNamespaces []string @@ -62,19 +62,19 @@ type scanApiStore struct { getChan chan struct{} // outChan is used to return all scan api urls - outChan chan []ClientConfiguration - scanClients map[string]ClientConfiguration - mondooClientBuilder func(mondooclient.ClientOptions) mondooclient.Client + outChan chan []ClientConfiguration + scanClients map[string]ClientConfiguration + scanApiClientBuilder func(scanapiclient.ScanApiClientOptions) (scanapiclient.ScanApiClient, error) } func NewScanApiStore(ctx context.Context) ScanApiStore { return &scanApiStore{ - ctx: ctx, - urlReqChan: make(chan urlRequest), - getChan: make(chan struct{}), - outChan: make(chan []ClientConfiguration), - scanClients: make(map[string]ClientConfiguration), - mondooClientBuilder: mondooclient.NewClient, + ctx: ctx, + urlReqChan: make(chan urlRequest), + getChan: make(chan struct{}), + outChan: make(chan []ClientConfiguration), + scanClients: make(map[string]ClientConfiguration), + scanApiClientBuilder: scanapiclient.NewClient, } } @@ -86,9 +86,14 @@ func (s *scanApiStore) Start() { case req := <-s.urlReqChan: switch req.requestType { case AddRequest: + client, err := s.scanApiClientBuilder( + scanapiclient.ScanApiClientOptions{ApiEndpoint: req.url, Token: req.token}) + if err != nil { + logger.Error(err, "Failed to create scan api client", "url", req.url) + continue + } s.scanClients[req.url] = ClientConfiguration{ - Client: s.mondooClientBuilder( - mondooclient.ClientOptions{ApiEndpoint: req.url, Token: req.token}), + Client: client, IntegrationMrn: req.integrationMrn, IncludeNamespaces: req.includeNamespaces, ExcludeNamespaces: req.excludeNamespaces, diff --git a/controllers/resource_monitor/scan_api_store/scan_api_store_test.go b/controllers/resource_monitor/scan_api_store/scan_api_store_test.go index a173c3e71..5d30c2163 100644 --- a/controllers/resource_monitor/scan_api_store/scan_api_store_test.go +++ b/controllers/resource_monitor/scan_api_store/scan_api_store_test.go @@ -14,24 +14,24 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/suite" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" - "go.mondoo.com/mondoo-operator/pkg/mondooclient/mock" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient/mock" "go.mondoo.com/mondoo-operator/tests/framework/utils" ) type ScanApiStoreSuite struct { suite.Suite - ctx context.Context - ctxCancel context.CancelFunc - mockCtrl *gomock.Controller - mockMondooClient *mock.MockClient - scanApiStore *scanApiStore + ctx context.Context + ctxCancel context.CancelFunc + mockCtrl *gomock.Controller + mockScanApiClient *mock.MockScanApiClient + scanApiStore *scanApiStore } func (s *ScanApiStoreSuite) BeforeTest(suiteName, testName string) { s.ctx, s.ctxCancel = context.WithCancel(context.Background()) s.mockCtrl = gomock.NewController(s.T()) - s.mockMondooClient = mock.NewMockClient(s.mockCtrl) + s.mockScanApiClient = mock.NewMockScanApiClient(s.mockCtrl) s.scanApiStore = NewScanApiStore(s.ctx).(*scanApiStore) } @@ -46,10 +46,10 @@ func (s *ScanApiStoreSuite) TestAdd() { url := utils.RandString(10) token := utils.RandString(10) integrationMrn := utils.RandString(10) - s.scanApiStore.mondooClientBuilder = func(opts mondooclient.ClientOptions) mondooclient.Client { + s.scanApiStore.scanApiClientBuilder = func(opts scanapiclient.ScanApiClientOptions) (scanapiclient.ScanApiClient, error) { s.Equal(url, opts.ApiEndpoint) s.Equal(token, opts.Token) - return s.mockMondooClient + return s.mockScanApiClient, nil } s.scanApiStore.Add(&ScanApiStoreAddOpts{ @@ -69,10 +69,10 @@ func (s *ScanApiStoreSuite) TestAdd_Idempotence() { url := utils.RandString(10) token := utils.RandString(10) integrationMrn := utils.RandString(10) - s.scanApiStore.mondooClientBuilder = func(opts mondooclient.ClientOptions) mondooclient.Client { + s.scanApiStore.scanApiClientBuilder = func(opts scanapiclient.ScanApiClientOptions) (scanapiclient.ScanApiClient, error) { s.Equal(url, opts.ApiEndpoint) s.Equal(token, opts.Token) - return s.mockMondooClient + return s.mockScanApiClient, nil } for i := 0; i < 100; i++ { @@ -94,10 +94,10 @@ func (s *ScanApiStoreSuite) TestDelete() { url := utils.RandString(10) token := utils.RandString(10) integrationMrn := utils.RandString(10) - s.scanApiStore.mondooClientBuilder = func(opts mondooclient.ClientOptions) mondooclient.Client { + s.scanApiStore.scanApiClientBuilder = func(opts scanapiclient.ScanApiClientOptions) (scanapiclient.ScanApiClient, error) { s.Equal(url, opts.ApiEndpoint) s.Equal(token, opts.Token) - return s.mockMondooClient + return s.mockScanApiClient, nil } s.scanApiStore.Add(&ScanApiStoreAddOpts{ @@ -108,10 +108,10 @@ func (s *ScanApiStoreSuite) TestDelete() { url2 := url + "2" integrationMrn2 := integrationMrn + "2" - s.scanApiStore.mondooClientBuilder = func(opts mondooclient.ClientOptions) mondooclient.Client { + s.scanApiStore.scanApiClientBuilder = func(opts scanapiclient.ScanApiClientOptions) (scanapiclient.ScanApiClient, error) { s.Equal(url2, opts.ApiEndpoint) s.Equal(token, opts.Token) - return s.mockMondooClient + return s.mockScanApiClient, nil } s.scanApiStore.Add(&ScanApiStoreAddOpts{ Url: url2, diff --git a/controllers/scanapi/deployment_handler.go b/controllers/scanapi/deployment_handler.go index 23115bb29..671b47784 100644 --- a/controllers/scanapi/deployment_handler.go +++ b/controllers/scanapi/deployment_handler.go @@ -57,7 +57,7 @@ func (n *DeploymentHandler) down(ctx context.Context) error { logger.Error(err, "failed to clean up scan API token Secret resource") return err } - scanApiDeployment := ScanApiDeployment(n.Mondoo.Namespace, "", *n.Mondoo, "", n.DeployOnOpenShift) // Image and private image scanning secret are not relevant when deleting. + scanApiDeployment := ScanApiDeployment(n.Mondoo.Namespace, "", *n.Mondoo, *n.MondooOperatorConfig, "", n.DeployOnOpenShift) // Image and private image scanning secret are not relevant when deleting. if err := k8s.DeleteIfExists(ctx, n.KubeClient, scanApiDeployment); err != nil { logger.Error(err, "failed to clean up scan API Deployment resource") return err @@ -126,7 +126,7 @@ func (n *DeploymentHandler) syncDeployment(ctx context.Context) error { privateRegistriesSecretName = "" } - deployment := ScanApiDeployment(n.Mondoo.Namespace, cnspecImage, *n.Mondoo, privateRegistriesSecretName, n.DeployOnOpenShift) + deployment := ScanApiDeployment(n.Mondoo.Namespace, cnspecImage, *n.Mondoo, *n.MondooOperatorConfig, privateRegistriesSecretName, n.DeployOnOpenShift) if err := ctrl.SetControllerReference(n.Mondoo, deployment, n.KubeClient.Scheme()); err != nil { return err } diff --git a/controllers/scanapi/deployment_handler_test.go b/controllers/scanapi/deployment_handler_test.go index b69b1ed53..583f68d13 100644 --- a/controllers/scanapi/deployment_handler_test.go +++ b/controllers/scanapi/deployment_handler_test.go @@ -79,7 +79,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_CustomEnvVars() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "", false) + deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "", false) deployment.ResourceVersion = "1" // Needed because the fake client sets it. s.NoError(ctrl.SetControllerReference(&s.auditConfig, deployment, s.scheme)) s.True(k8s.AreDeploymentsEqual(*deployment, ds.Items[0])) @@ -119,7 +119,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_KubernetesResources() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "", false) + deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "", false) deployment.ResourceVersion = "1" // Needed because the fake client sets it. s.NoError(ctrl.SetControllerReference(&s.auditConfig, deployment, s.scheme)) s.True(k8s.AreDeploymentsEqual(*deployment, ds.Items[0])) @@ -162,7 +162,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_PrivateRegistriesSecret() s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "my-pull-secrets", false) + deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "my-pull-secrets", false) deployment.ResourceVersion = "1" // Needed because the fake client sets it. s.NoError(ctrl.SetControllerReference(&s.auditConfig, deployment, s.scheme)) s.True(k8s.AreDeploymentsEqual(*deployment, ds.Items[0])) @@ -194,7 +194,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_PrivateRegistriesSecretNot s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "mondoo-private-registries-secrets", false) + deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "mondoo-private-registries-secrets", false) deployment.ResourceVersion = "1" // Needed because the fake client sets it. s.NoError(ctrl.SetControllerReference(&s.auditConfig, deployment, s.scheme)) s.True(k8s.AreDeploymentsEqual(*deployment, ds.Items[0])) @@ -226,7 +226,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_PrivateRegistriesSecretWro s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "", false) + deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "", false) deployment.ResourceVersion = "1" // Needed because the fake client sets it. s.NoError(ctrl.SetControllerReference(&s.auditConfig, deployment, s.scheme)) s.True(k8s.AreDeploymentsEqual(*deployment, ds.Items[0])) @@ -259,7 +259,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_Admission() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "", false) + deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "", false) deployment.ResourceVersion = "1" // Needed because the fake client sets it. s.NoError(ctrl.SetControllerReference(&s.auditConfig, deployment, s.scheme)) s.True(k8s.AreDeploymentsEqual(*deployment, ds.Items[0])) @@ -301,7 +301,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_NodeScanning() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "", false) + deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "", false) deployment.ResourceVersion = "1" // Needed because the fake client sets it. s.NoError(ctrl.SetControllerReference(&s.auditConfig, deployment, s.scheme)) s.True(k8s.AreDeploymentsEqual(*deployment, ds.Items[0])) @@ -325,7 +325,7 @@ func (s *DeploymentHandlerSuite) TestDeploy_CreateMissingServiceAccount() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "", false) + deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "", false) deployment.Status.UnavailableReplicas = 1 deployment.Status.Conditions = []appsv1.DeploymentCondition{ { @@ -363,7 +363,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Update() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "", false) + deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "", false) deployment.Spec.Replicas = pointer.Int32(3) service := ScanApiService(s.auditConfig.Namespace, s.auditConfig) @@ -380,7 +380,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Update() { s.NoError(d.KubeClient.List(s.ctx, ds)) s.Equal(1, len(ds.Items)) - deployment = ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "", false) + deployment = ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "", false) s.NoError(ctrl.SetControllerReference(&s.auditConfig, deployment, s.scheme)) deployment.ResourceVersion = "1000" // Needed because the fake client sets it. @@ -405,7 +405,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Cleanup_NoScanning() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "", false) + deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "", false) service := ScanApiService(s.auditConfig.Namespace, s.auditConfig) s.fakeClientBuilder = s.fakeClientBuilder.WithObjects(deployment, service) @@ -436,7 +436,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Cleanup_AuditConfigDeletion() { s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false) s.NoError(err) - deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, "", false) + deployment := ScanApiDeployment(s.auditConfig.Namespace, image, s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}, "", false) service := ScanApiService(s.auditConfig.Namespace, s.auditConfig) s.fakeClientBuilder = s.fakeClientBuilder.WithObjects(deployment, service) diff --git a/controllers/scanapi/resources.go b/controllers/scanapi/resources.go index dc751b7ba..444ed78b0 100644 --- a/controllers/scanapi/resources.go +++ b/controllers/scanapi/resources.go @@ -45,7 +45,7 @@ func ScanApiSecret(mondoo v1alpha2.MondooAuditConfig) *corev1.Secret { } } -func ScanApiDeployment(ns, image string, m v1alpha2.MondooAuditConfig, privateImageScanningSecretName string, deployOnOpenShift bool) *appsv1.Deployment { +func ScanApiDeployment(ns, image string, m v1alpha2.MondooAuditConfig, cfg v1alpha2.MondooOperatorConfig, privateImageScanningSecretName string, deployOnOpenShift bool) *appsv1.Deployment { labels := DeploymentLabels(m) name := "cnspec" @@ -55,6 +55,11 @@ func ScanApiDeployment(ns, image string, m v1alpha2.MondooAuditConfig, privateIm "--config", "/etc/opt/mondoo/config/mondoo.yml", "--http-timeout", "1800", } + + if cfg.Spec.HttpProxy != nil { + cmd = append(cmd, []string{"--api-proxy", *cfg.Spec.HttpProxy}...) + } + healthcheckEndpoint := "/Scan/HealthCheck" scanApiDeployment := &appsv1.Deployment{ diff --git a/controllers/status/operator_status.go b/controllers/status/operator_status.go index 15a962a51..c3c549369 100644 --- a/controllers/status/operator_status.go +++ b/controllers/status/operator_status.go @@ -10,7 +10,7 @@ package status import ( "go.mondoo.com/mondoo-operator/api/v1alpha2" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" "go.mondoo.com/mondoo-operator/pkg/utils/mondoo" "go.mondoo.com/mondoo-operator/pkg/version" v1 "k8s.io/api/core/v1" diff --git a/controllers/status/operator_status_test.go b/controllers/status/operator_status_test.go index d1f5994a4..58ef2a549 100644 --- a/controllers/status/operator_status_test.go +++ b/controllers/status/operator_status_test.go @@ -17,7 +17,7 @@ import ( k8sversion "k8s.io/apimachinery/pkg/version" "go.mondoo.com/mondoo-operator/api/v1alpha2" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" "go.mondoo.com/mondoo-operator/pkg/version" "go.mondoo.com/mondoo-operator/tests/framework/utils" ) diff --git a/controllers/status/status_reporter.go b/controllers/status/status_reporter.go index d1d173e47..186b6b127 100644 --- a/controllers/status/status_reporter.go +++ b/controllers/status/status_reporter.go @@ -13,7 +13,7 @@ import ( "reflect" "go.mondoo.com/mondoo-operator/api/v1alpha2" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" "go.mondoo.com/mondoo-operator/pkg/utils/k8s" "go.mondoo.com/mondoo-operator/pkg/utils/mondoo" v1 "k8s.io/api/core/v1" @@ -27,11 +27,11 @@ var logger = ctrl.Log.WithName("status-reporter") type StatusReporter struct { kubeClient client.Client k8sVersion *version.Info - mondooClientBuilder func(mondooclient.ClientOptions) mondooclient.Client + mondooClientBuilder func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) lastReportedStatus mondooclient.ReportStatusRequest } -func NewStatusReporter(kubeClient client.Client, mondooClientBuilder func(mondooclient.ClientOptions) mondooclient.Client, k8sVersion *version.Info) *StatusReporter { +func NewStatusReporter(kubeClient client.Client, mondooClientBuilder func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error), k8sVersion *version.Info) *StatusReporter { return &StatusReporter{ kubeClient: kubeClient, k8sVersion: k8sVersion, @@ -39,7 +39,7 @@ func NewStatusReporter(kubeClient client.Client, mondooClientBuilder func(mondoo } } -func (r *StatusReporter) Report(ctx context.Context, m v1alpha2.MondooAuditConfig) error { +func (r *StatusReporter) Report(ctx context.Context, m v1alpha2.MondooAuditConfig, cfg v1alpha2.MondooOperatorConfig) error { if !m.Spec.ConsoleIntegration.Enable { return nil // If ConsoleIntegration is not enabled, we cannot report status } @@ -74,10 +74,14 @@ func (r *StatusReporter) Report(ctx context.Context, m v1alpha2.MondooAuditConfi return err } - mondooClient := r.mondooClientBuilder(mondooclient.ClientOptions{ + mondooClient, err := r.mondooClientBuilder(mondooclient.MondooClientOptions{ ApiEndpoint: serviceAccount.ApiEndpoint, Token: token, + HttpProxy: cfg.Spec.HttpProxy, }) + if err != nil { + return err + } if err := mondooClient.IntegrationReportStatus(ctx, &operatorStatus); err != nil { return err diff --git a/controllers/status/status_reporter_test.go b/controllers/status/status_reporter_test.go index 1846db10f..36aa9867b 100644 --- a/controllers/status/status_reporter_test.go +++ b/controllers/status/status_reporter_test.go @@ -16,9 +16,9 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/suite" "go.mondoo.com/mondoo-operator/api/v1alpha2" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient/mock" "go.mondoo.com/mondoo-operator/pkg/constants" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" - "go.mondoo.com/mondoo-operator/pkg/mondooclient/mock" operatorVersion "go.mondoo.com/mondoo-operator/pkg/version" "go.mondoo.com/mondoo-operator/tests/credentials" "go.mondoo.com/mondoo-operator/tests/framework/utils" @@ -41,7 +41,7 @@ type StatusReporterSuite struct { auditConfig v1alpha2.MondooAuditConfig fakeClientBuilder *fake.ClientBuilder mockCtrl *gomock.Controller - mockMondooClient *mock.MockClient + mockMondooClient *mock.MockMondooClient } func (s *StatusReporterSuite) SetupSuite() { @@ -69,7 +69,7 @@ func (s *StatusReporterSuite) BeforeTest(suiteName, testName string) { s.fakeClientBuilder = fake.NewClientBuilder().WithObjects(secret) s.mockCtrl = gomock.NewController(s.T()) - s.mockMondooClient = mock.NewMockClient(s.mockCtrl) + s.mockMondooClient = mock.NewMockMondooClient(s.mockCtrl) } func (s *StatusReporterSuite) AfterTest(suiteName, testName string) { @@ -129,10 +129,10 @@ func (s *StatusReporterSuite) TestReport() { }, }).Times(1).Return(nil) - s.NoError(statusReport.Report(s.ctx, s.auditConfig)) + s.NoError(statusReport.Report(s.ctx, s.auditConfig, v1alpha2.MondooOperatorConfig{})) // We call Report another time to make sure IntegrationReportStatus is only called whenever the status actually changes. - s.NoError(statusReport.Report(s.ctx, s.auditConfig)) + s.NoError(statusReport.Report(s.ctx, s.auditConfig, v1alpha2.MondooOperatorConfig{})) } func (s *StatusReporterSuite) TestReport_StatusChange() { @@ -190,7 +190,7 @@ func (s *StatusReporterSuite) TestReport_StatusChange() { } s.mockMondooClient.EXPECT().IntegrationReportStatus(gomock.Any(), expected).Times(1).Return(nil) - s.NoError(statusReport.Report(s.ctx, s.auditConfig)) + s.NoError(statusReport.Report(s.ctx, s.auditConfig, v1alpha2.MondooOperatorConfig{})) s.auditConfig.Spec.Nodes.Enable = true s.auditConfig.Status.Conditions = []v1alpha2.MondooAuditConfigCondition{ @@ -205,7 +205,7 @@ func (s *StatusReporterSuite) TestReport_StatusChange() { s.mockMondooClient.EXPECT().IntegrationReportStatus(gomock.Any(), expected).Times(1).Return(nil) // We call Report another time to make sure IntegrationReportStatus is only called whenever the status actually changes. - s.NoError(statusReport.Report(s.ctx, s.auditConfig)) + s.NoError(statusReport.Report(s.ctx, s.auditConfig, v1alpha2.MondooOperatorConfig{})) } func TestStatusReporterSuite(t *testing.T) { @@ -223,8 +223,10 @@ func (s *StatusReporterSuite) seedNodes() []client.Object { func (s *StatusReporterSuite) createStatusReporter() StatusReporter { return StatusReporter{ - kubeClient: s.fakeClientBuilder.Build(), - k8sVersion: &version.Info{GitVersion: "v1.24.0"}, - mondooClientBuilder: func(opts mondooclient.ClientOptions) mondooclient.Client { return s.mockMondooClient }, + kubeClient: s.fakeClientBuilder.Build(), + k8sVersion: &version.Info{GitVersion: "v1.24.0"}, + mondooClientBuilder: func(opts mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) { + return s.mockMondooClient, nil + }, } } diff --git a/pkg/client/common/http.go b/pkg/client/common/http.go new file mode 100644 index 000000000..66b46c6f0 --- /dev/null +++ b/pkg/client/common/http.go @@ -0,0 +1,84 @@ +package common + +import ( + "bytes" + "context" + "fmt" + "io" + "net" + "net/http" + "net/url" + "time" +) + +const ( + defaultHttpTimeout = 30 * time.Second + defaultIdleConnTimeout = 30 * time.Second + defaultKeepAlive = 30 * time.Second + defaultTLSHandshakeTimeout = 10 * time.Second + maxIdleConnections = 100 +) + +func DefaultHttpClient(httpProxy *string) (http.Client, error) { + tr := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: defaultHttpTimeout, + KeepAlive: defaultKeepAlive, + }).DialContext, + MaxIdleConns: maxIdleConnections, + IdleConnTimeout: defaultIdleConnTimeout, + TLSHandshakeTimeout: defaultTLSHandshakeTimeout, + ExpectContinueTimeout: 1 * time.Second, + } + + if httpProxy != nil { + urlParsed, err := url.Parse(*httpProxy) + if err != nil { + return http.Client{}, err + } + tr.Proxy = http.ProxyURL(urlParsed) + } + return http.Client{ + Transport: tr, + Timeout: defaultHttpTimeout, + }, nil +} + +func Request(ctx context.Context, client http.Client, url, token string, reqBodyBytes []byte) ([]byte, error) { + header := make(http.Header) + header.Set("Accept", "application/json") + header.Set("Content-Type", "application/json") + if token != "" { + header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + } + + reader := bytes.NewReader(reqBodyBytes) + req, err := http.NewRequest(http.MethodPost, url, reader) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header = header + + // do http call + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to do request: %v", err) + } + + defer func() { + resp.Body.Close() + }() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read http response body: %s", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("http status %d: %s", resp.StatusCode, respBody) + } + + return respBody, nil +} diff --git a/pkg/client/common/types.go b/pkg/client/common/types.go new file mode 100644 index 000000000..d1d67b4e2 --- /dev/null +++ b/pkg/client/common/types.go @@ -0,0 +1,21 @@ +package common + +import "context" + +const HealthCheckEndpoint = "/Health/Check" + +type HealthCheckRequest struct{} + +type HealthCheckResponse struct { + Status string `json:"status,omitempty"` + // returns rfc 3339 timestamp + Time string `json:"time,omitempty"` + // returns the major api version + ApiVersion string `json:"apiVersion,omitempty"` + // returns the git commit checksum + Build string `json:"build,omitempty"` +} + +type HealthCheckClient interface { + HealthCheck(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) +} diff --git a/pkg/client/mondooclient/client.go b/pkg/client/mondooclient/client.go new file mode 100644 index 000000000..68a53cc5f --- /dev/null +++ b/pkg/client/mondooclient/client.go @@ -0,0 +1,151 @@ +/* +Copyright 2022 Mondoo, Inc. + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ + +package mondooclient + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + + "go.mondoo.com/mondoo-operator/pkg/client/common" +) + +const ( + ExchangeRegistrationTokenEndpoint = "/AgentManager/ExchangeRegistrationToken" + IntegrationRegisterEndpoint = "/IntegrationsManager/Register" + IntegrationCheckInEndpoint = "/IntegrationsManager/CheckIn" + IntegrationReportStatusEndpoint = "/IntegrationsManager/ReportStatus" +) + +type MondooClientOptions struct { + ApiEndpoint string + Token string + HttpProxy *string +} + +type mondooClient struct { + ApiEndpoint string + Token string + httpClient http.Client +} + +func NewClient(opts MondooClientOptions) (MondooClient, error) { + opts.ApiEndpoint = strings.TrimRight(opts.ApiEndpoint, "/") + client, err := common.DefaultHttpClient(opts.HttpProxy) + if err != nil { + return nil, err + } + mClient := &mondooClient{ + ApiEndpoint: opts.ApiEndpoint, + Token: opts.Token, + httpClient: client, + } + return mClient, nil +} + +func (s *mondooClient) ExchangeRegistrationToken(ctx context.Context, in *ExchangeRegistrationTokenInput) (*ExchangeRegistrationTokenOutput, error) { + url := s.ApiEndpoint + ExchangeRegistrationTokenEndpoint + + reqBodyBytes, err := json.Marshal(in) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %v", err) + } + + respBodyBytes, err := common.Request(ctx, s.httpClient, url, s.Token, reqBodyBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse response: %v", err) + } + + out := &ExchangeRegistrationTokenOutput{ + ServiceAccount: string(respBodyBytes), + } + + return out, nil +} + +func (s *mondooClient) HealthCheck(ctx context.Context, in *common.HealthCheckRequest) (*common.HealthCheckResponse, error) { + url := s.ApiEndpoint + common.HealthCheckEndpoint + + reqBodyBytes, err := json.Marshal(in) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %v", err) + } + + respBodyBytes, err := common.Request(ctx, s.httpClient, url, s.Token, reqBodyBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse response: %v", err) + } + + out := &common.HealthCheckResponse{} + if err = json.Unmarshal(respBodyBytes, out); err != nil { + return nil, fmt.Errorf("failed to unmarshal proto response: %v", err) + } + + return out, nil +} + +func (s *mondooClient) IntegrationRegister(ctx context.Context, in *IntegrationRegisterInput) (*IntegrationRegisterOutput, error) { + url := s.ApiEndpoint + IntegrationRegisterEndpoint + + reqBodyBytes, err := json.Marshal(in) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %v", err) + } + + respBodyBytes, err := common.Request(ctx, s.httpClient, url, s.Token, reqBodyBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse response: %v", err) + } + + out := &IntegrationRegisterOutput{} + if err = json.Unmarshal(respBodyBytes, out); err != nil { + return nil, fmt.Errorf("failed to unmarshal proto response: %v", err) + } + + return out, nil +} + +func (s *mondooClient) IntegrationCheckIn(ctx context.Context, in *IntegrationCheckInInput) (*IntegrationCheckInOutput, error) { + url := s.ApiEndpoint + IntegrationCheckInEndpoint + + reqBodyBytes, err := json.Marshal(in) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %v", err) + } + + respBodyBytes, err := common.Request(ctx, s.httpClient, url, s.Token, reqBodyBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse response: %v", err) + } + + out := &IntegrationCheckInOutput{} + if err = json.Unmarshal(respBodyBytes, out); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %v", err) + } + + return out, nil +} + +func (s *mondooClient) IntegrationReportStatus(ctx context.Context, in *ReportStatusRequest) error { + url := s.ApiEndpoint + IntegrationReportStatusEndpoint + + reqBodyBytes, err := json.Marshal(in) + if err != nil { + return fmt.Errorf("failed to marshal request: %v", err) + } + + _, err = common.Request(ctx, s.httpClient, url, s.Token, reqBodyBytes) + if err != nil { + return fmt.Errorf("failed to parse response: %v", err) + } + + return nil +} diff --git a/pkg/client/mondooclient/fakeserver/fakeserver.go b/pkg/client/mondooclient/fakeserver/fakeserver.go new file mode 100644 index 000000000..13452398c --- /dev/null +++ b/pkg/client/mondooclient/fakeserver/fakeserver.go @@ -0,0 +1,28 @@ +package fakeserver + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + + "go.mondoo.com/mondoo-operator/pkg/client/common" +) + +func FakeServer() *httptest.Server { + mux := http.NewServeMux() + mux.HandleFunc(common.HealthCheckEndpoint, func(w http.ResponseWriter, r *http.Request) { + result := &common.HealthCheckResponse{ + Status: "SERVING", + } + data, err := json.Marshal(result) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + if _, err = w.Write(data); err != nil { + http.Error(w, err.Error(), 500) + return + } + }) + return httptest.NewServer(mux) +} diff --git a/pkg/client/mondooclient/mock/client_generated.go b/pkg/client/mondooclient/mock/client_generated.go new file mode 100644 index 000000000..a68bde13a --- /dev/null +++ b/pkg/client/mondooclient/mock/client_generated.go @@ -0,0 +1,111 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./types.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + common "go.mondoo.com/mondoo-operator/pkg/client/common" + mondooclient "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" +) + +// MockMondooClient is a mock of MondooClient interface. +type MockMondooClient struct { + ctrl *gomock.Controller + recorder *MockMondooClientMockRecorder +} + +// MockMondooClientMockRecorder is the mock recorder for MockMondooClient. +type MockMondooClientMockRecorder struct { + mock *MockMondooClient +} + +// NewMockMondooClient creates a new mock instance. +func NewMockMondooClient(ctrl *gomock.Controller) *MockMondooClient { + mock := &MockMondooClient{ctrl: ctrl} + mock.recorder = &MockMondooClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMondooClient) EXPECT() *MockMondooClientMockRecorder { + return m.recorder +} + +// ExchangeRegistrationToken mocks base method. +func (m *MockMondooClient) ExchangeRegistrationToken(arg0 context.Context, arg1 *mondooclient.ExchangeRegistrationTokenInput) (*mondooclient.ExchangeRegistrationTokenOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExchangeRegistrationToken", arg0, arg1) + ret0, _ := ret[0].(*mondooclient.ExchangeRegistrationTokenOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExchangeRegistrationToken indicates an expected call of ExchangeRegistrationToken. +func (mr *MockMondooClientMockRecorder) ExchangeRegistrationToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExchangeRegistrationToken", reflect.TypeOf((*MockMondooClient)(nil).ExchangeRegistrationToken), arg0, arg1) +} + +// HealthCheck mocks base method. +func (m *MockMondooClient) HealthCheck(arg0 context.Context, arg1 *common.HealthCheckRequest) (*common.HealthCheckResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HealthCheck", arg0, arg1) + ret0, _ := ret[0].(*common.HealthCheckResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HealthCheck indicates an expected call of HealthCheck. +func (mr *MockMondooClientMockRecorder) HealthCheck(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockMondooClient)(nil).HealthCheck), arg0, arg1) +} + +// IntegrationCheckIn mocks base method. +func (m *MockMondooClient) IntegrationCheckIn(arg0 context.Context, arg1 *mondooclient.IntegrationCheckInInput) (*mondooclient.IntegrationCheckInOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IntegrationCheckIn", arg0, arg1) + ret0, _ := ret[0].(*mondooclient.IntegrationCheckInOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IntegrationCheckIn indicates an expected call of IntegrationCheckIn. +func (mr *MockMondooClientMockRecorder) IntegrationCheckIn(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntegrationCheckIn", reflect.TypeOf((*MockMondooClient)(nil).IntegrationCheckIn), arg0, arg1) +} + +// IntegrationRegister mocks base method. +func (m *MockMondooClient) IntegrationRegister(arg0 context.Context, arg1 *mondooclient.IntegrationRegisterInput) (*mondooclient.IntegrationRegisterOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IntegrationRegister", arg0, arg1) + ret0, _ := ret[0].(*mondooclient.IntegrationRegisterOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IntegrationRegister indicates an expected call of IntegrationRegister. +func (mr *MockMondooClientMockRecorder) IntegrationRegister(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntegrationRegister", reflect.TypeOf((*MockMondooClient)(nil).IntegrationRegister), arg0, arg1) +} + +// IntegrationReportStatus mocks base method. +func (m *MockMondooClient) IntegrationReportStatus(arg0 context.Context, arg1 *mondooclient.ReportStatusRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IntegrationReportStatus", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// IntegrationReportStatus indicates an expected call of IntegrationReportStatus. +func (mr *MockMondooClientMockRecorder) IntegrationReportStatus(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntegrationReportStatus", reflect.TypeOf((*MockMondooClient)(nil).IntegrationReportStatus), arg0, arg1) +} diff --git a/pkg/client/mondooclient/types.go b/pkg/client/mondooclient/types.go new file mode 100644 index 000000000..ba73c4f90 --- /dev/null +++ b/pkg/client/mondooclient/types.go @@ -0,0 +1,112 @@ +package mondooclient + +import ( + "context" + + "go.mondoo.com/mondoo-operator/pkg/client/common" +) + +//go:generate ./../../../bin/mockgen -source=./types.go -destination=./mock/client_generated.go -package=mock + +type MondooClient interface { + common.HealthCheckClient + ExchangeRegistrationToken(context.Context, *ExchangeRegistrationTokenInput) (*ExchangeRegistrationTokenOutput, error) + + IntegrationRegister(context.Context, *IntegrationRegisterInput) (*IntegrationRegisterOutput, error) + IntegrationCheckIn(context.Context, *IntegrationCheckInInput) (*IntegrationCheckInOutput, error) + IntegrationReportStatus(context.Context, *ReportStatusRequest) error +} + +// ExchangeRegistrationTokenInput is used for converting a JWT to a Mondoo serivce account +type ExchangeRegistrationTokenInput struct { + // JWT token, only available during creation + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` +} + +type ExchangeRegistrationTokenOutput struct { + ServiceAccount string `json:"serviceAccount,omitempty"` +} + +type IntegrationRegisterInput struct { + // Mrn is the MRN of the integration. It should be provided in the JWT under the "owner" claim + Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` + Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` +} + +type IntegrationRegisterOutput struct { + // Mrn is the integration MRN + Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` + // Creds holds all the Mondoo serivce account data + Creds *ServiceAccountCredentials `protobuf:"bytes,2,opt,name=creds,proto3" json:"creds,omitempty"` +} + +type ServiceAccountCredentials struct { + Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` + SpaceMrn string `protobuf:"bytes,2,opt,name=space_mrn,json=spaceMrn,proto3" json:"space_mrn,omitempty"` + PrivateKey string `protobuf:"bytes,3,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` + Certificate string `protobuf:"bytes,4,opt,name=certificate,proto3" json:"certificate,omitempty"` + ApiEndpoint string `protobuf:"bytes,5,opt,name=api_endpoint,json=apiEndpoint,proto3" json:"api_endpoint,omitempty"` +} +type IntegrationCheckInInput struct { + // Mrn should hold the MRN of the integration that is having the CheckIn() called for + Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` + // optional, ensure the client has the exact same configuration options + // as the ones saved to the integration/db + ConfigurationHash string `protobuf:"bytes,2,opt,name=configuration_hash,json=configurationHash,proto3" json:"configuration_hash,omitempty"` + // source identifier for the integration, e.g. AWS account id + Identifier string `protobuf:"bytes,3,opt,name=identifier,proto3" json:"identifier,omitempty"` +} + +type IntegrationCheckInOutput struct { + Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` + // true if the configuration hash sent in matches the hash of the stored configuration + ConfigurationMatch bool `protobuf:"varint,2,opt,name=configuration_match,json=configurationMatch,proto3" json:"configuration_match,omitempty"` +} + +type ReportStatusRequest struct { + Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` + // this is the status of the integration itself (is it active/checking in, errored, etc) + Status Status `protobuf:"varint,2,opt,name=status,proto3,enum=mondoo.integrations.v1.Status" json:"status,omitempty"` + // this can be any information about the current state of the integration. it will be displayed to the user as-is where supported + LastState interface{} `protobuf:"bytes,4,opt,name=last_state,json=lastState,proto3" json:"last_state,omitempty"` + // Allows the agent to report its current version + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` + // messages that convey extra information about the integration - these messages can be informational, warnings or errors. Can be used + // to report non-critical errors/warnings without neccesarily changing the whole integration status. + Messages Messages `protobuf:"bytes,7,opt,name=messages,proto3" json:"messages,omitempty"` +} + +type Messages struct { + Messages []IntegrationMessage `protobuf:"bytes,1,opt,name=messages,proto3" json:"messages,omitempty"` +} + +type Status int32 + +const ( + Status_NOT_READY Status = 0 + Status_WAITING_FOR_SETUP Status = 1 + Status_ACTIVE Status = 2 + Status_ERROR Status = 3 + Status_DELETED Status = 4 + Status_MISSING Status = 5 + Status_WARNING Status = 6 +) + +type IntegrationMessage struct { + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + Timestamp string `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Status MessageStatus `protobuf:"varint,3,opt,name=status,proto3,enum=mondoo.integrations.v1.MessageStatus" json:"status,omitempty"` + ReportedByAgent bool `protobuf:"varint,4,opt,name=reported_by_agent,json=reportedByAgent,proto3" json:"reported_by_agent,omitempty"` + Identifier string `protobuf:"bytes,5,opt,name=identifier,proto3" json:"identifier,omitempty"` + // Anything extra that the message might contain. + Extra interface{} `protobuf:"bytes,6,opt,name=extra,proto3" json:"extra,omitempty"` +} + +type MessageStatus int32 + +const ( + MessageStatus_MESSAGE_UNKNOWN MessageStatus = 0 + MessageStatus_MESSAGE_WARNING MessageStatus = 1 + MessageStatus_MESSAGE_ERROR MessageStatus = 2 + MessageStatus_MESSAGE_INFO MessageStatus = 3 +) diff --git a/pkg/client/scanapiclient/client.go b/pkg/client/scanapiclient/client.go new file mode 100644 index 000000000..12450cf0e --- /dev/null +++ b/pkg/client/scanapiclient/client.go @@ -0,0 +1,214 @@ +package scanapiclient + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + + "go.mondoo.com/cnquery/motor/asset" + v1 "go.mondoo.com/cnquery/motor/inventory/v1" + "go.mondoo.com/cnquery/motor/providers" + "go.mondoo.com/cnspec/policy/scan" + "go.mondoo.com/mondoo-operator/pkg/client/common" + "go.mondoo.com/mondoo-operator/pkg/constants" +) + +const ( + RunAdmissionReviewEndpoint = "/Scan/RunAdmissionReview" + ScanKubernetesResourcesEndpoint = "/Scan/Run" + ScheduleKubernetesResourceScanEndpoint = "/Scan/Schedule" + GarbageCollectAssetsEndpoint = "/Scan/GarbageCollectAssets" +) + +type ScanApiClientOptions struct { + ApiEndpoint string + Token string +} + +type scanApiClient struct { + ApiEndpoint string + Token string + httpClient http.Client +} + +func NewClient(opts ScanApiClientOptions) (ScanApiClient, error) { + opts.ApiEndpoint = strings.TrimRight(opts.ApiEndpoint, "/") + client, err := common.DefaultHttpClient(nil) + if err != nil { + return nil, err + } + return &scanApiClient{ + ApiEndpoint: opts.ApiEndpoint, + Token: opts.Token, + httpClient: client, + }, nil +} + +func (s *scanApiClient) HealthCheck(ctx context.Context, in *common.HealthCheckRequest) (*common.HealthCheckResponse, error) { + url := s.ApiEndpoint + common.HealthCheckEndpoint + + reqBodyBytes, err := json.Marshal(in) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %v", err) + } + + respBodyBytes, err := common.Request(ctx, s.httpClient, url, s.Token, reqBodyBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse response: %v", err) + } + + out := &common.HealthCheckResponse{} + if err = json.Unmarshal(respBodyBytes, out); err != nil { + return nil, fmt.Errorf("failed to unmarshal proto response: %v", err) + } + + return out, nil +} + +func (s *scanApiClient) RunAdmissionReview(ctx context.Context, in *AdmissionReviewJob) (*ScanResult, error) { + url := s.ApiEndpoint + RunAdmissionReviewEndpoint + + reqBodyBytes, err := json.Marshal(in) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %v", err) + } + + respBodyBytes, err := common.Request(ctx, s.httpClient, url, s.Token, reqBodyBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse response: %v", err) + } + + out := &ScanResult{} + if err = json.Unmarshal(respBodyBytes, out); err != nil { + return nil, fmt.Errorf("failed to unmarshal proto response: %v", err) + } + + return out, nil +} + +func (s *scanApiClient) ScanKubernetesResources(ctx context.Context, scanOpts *ScanKubernetesResourcesOpts) (*ScanResult, error) { + url := s.ApiEndpoint + ScanKubernetesResourcesEndpoint + scanJob := &ScanJob{ + ReportType: ReportType_ERROR, + Inventory: v1.Inventory{ + Spec: &v1.InventorySpec{ + Assets: []*asset.Asset{ + { + Connections: []*providers.Config{ + { + Backend: providers.ProviderType_K8S, + Options: map[string]string{ + "namespaces": strings.Join(scanOpts.IncludeNamespaces, ","), + "namespaces-exclude": strings.Join(scanOpts.ExcludeNamespaces, ","), + }, + Discover: &providers.Discovery{ + Targets: []string{"auto"}, + }, + }, + }, + ManagedBy: scanOpts.ManagedBy, + }, + }, + }, + }, + } + + setIntegrationMrn(scanOpts.IntegrationMrn, scanJob) + + if scanOpts.ScanContainerImages { + scanJob.Inventory.Spec.Assets[0].Connections[0].Discover.Targets = []string{"container-images"} + } + + reqBodyBytes, err := json.Marshal(scanJob) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %v", err) + } + + respBodyBytes, err := common.Request(ctx, s.httpClient, url, s.Token, reqBodyBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse response: %v", err) + } + + out := &ScanResult{} + if err = json.Unmarshal(respBodyBytes, out); err != nil { + return nil, fmt.Errorf("failed to unmarshal proto response: %v", err) + } + + return out, nil +} + +func (s *scanApiClient) ScheduleKubernetesResourceScan(ctx context.Context, integrationMrn, resourceKey, managedBy string) (*Empty, error) { + url := s.ApiEndpoint + ScheduleKubernetesResourceScanEndpoint + scanJob := &ScanJob{ + ReportType: ReportType_ERROR, + Inventory: v1.Inventory{ + Spec: &v1.InventorySpec{ + Assets: []*asset.Asset{ + { + Connections: []*providers.Config{ + { + Backend: providers.ProviderType_K8S, + Options: map[string]string{ + "k8s-resources": resourceKey, + }, + Discover: &providers.Discovery{ + Targets: []string{"auto"}, + }, + }, + }, + }, + }, + }, + }, + } + + if len(managedBy) > 0 { + scanJob.Inventory.Spec.Assets[0].ManagedBy = managedBy + } + + setIntegrationMrn(integrationMrn, scanJob) + + reqBodyBytes, err := json.Marshal(scanJob) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %v", err) + } + + respBodyBytes, err := common.Request(ctx, s.httpClient, url, s.Token, reqBodyBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse response: %v", err) + } + + out := &Empty{} + if err = json.Unmarshal(respBodyBytes, out); err != nil { + return nil, fmt.Errorf("failed to unmarshal proto response: %v", err) + } + + return out, nil +} + +func (s *scanApiClient) GarbageCollectAssets(ctx context.Context, in *scan.GarbageCollectOptions) error { + url := s.ApiEndpoint + GarbageCollectAssetsEndpoint + + reqBodyBytes, err := json.Marshal(in) + if err != nil { + return fmt.Errorf("failed to marshal request: %v", err) + } + + _, err = common.Request(ctx, s.httpClient, url, s.Token, reqBodyBytes) + if err != nil { + return fmt.Errorf("error calling GarbageCollectAssets: %s", err) + } + + return nil +} + +func setIntegrationMrn(integrationMrn string, scanJob *ScanJob) { + if integrationMrn != "" { + if scanJob.Inventory.Spec.Assets[0].Labels == nil { + scanJob.Inventory.Spec.Assets[0].Labels = make(map[string]string) + } + scanJob.Inventory.Spec.Assets[0].Labels[constants.MondooAssetsIntegrationLabel] = integrationMrn + } +} diff --git a/pkg/mondooclient/client_test.go b/pkg/client/scanapiclient/client_test.go similarity index 73% rename from pkg/mondooclient/client_test.go rename to pkg/client/scanapiclient/client_test.go index 98b28be17..ec3cb2acb 100644 --- a/pkg/mondooclient/client_test.go +++ b/pkg/client/scanapiclient/client_test.go @@ -1,7 +1,7 @@ // Copyright (c) Mondoo, Inc. // SPDX-License-Identifier: BUSL-1.1 -package mondooclient_test +package scanapiclient_test import ( "context" @@ -14,11 +14,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "sigs.k8s.io/yaml" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" - "go.mondoo.com/mondoo-operator/pkg/mondooclient/fakeserver" + "go.mondoo.com/mondoo-operator/pkg/client/common" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient/fakeserver" ) -var webhookPayload = mustRead("../../tests/data/webhook-payload.json") +var webhookPayload = mustRead("../../../tests/data/webhook-payload.json") func TestScanner(t *testing.T) { testserver := fakeserver.FakeServer() @@ -30,13 +31,14 @@ func TestScanner(t *testing.T) { // token := "" // do client request - mClient := mondooclient.NewClient(mondooclient.ClientOptions{ + mClient, err := scanapiclient.NewClient(scanapiclient.ScanApiClientOptions{ ApiEndpoint: url, Token: token, }) + require.NoError(t, err) // Run Health Check - healthResp, err := mClient.HealthCheck(context.Background(), &mondooclient.HealthCheckRequest{}) + healthResp, err := mClient.HealthCheck(context.Background(), &common.HealthCheckRequest{}) require.NoError(t, err) assert.True(t, healthResp.Status == "SERVING") @@ -44,7 +46,7 @@ func TestScanner(t *testing.T) { err = yaml.Unmarshal(webhookPayload, &request) require.NoError(t, err) - result, err := mClient.RunAdmissionReview(context.Background(), &mondooclient.AdmissionReviewJob{ + result, err := mClient.RunAdmissionReview(context.Background(), &scanapiclient.AdmissionReviewJob{ Labels: map[string]string{ "k8s.mondoo.com/author": request.UserInfo.Username, "k8s.mondoo.com/operation": string(request.Operation), @@ -71,17 +73,18 @@ func TestScanner_ScanKubernetesResources(t *testing.T) { // token := "abcdefgh" // do client request - mClient := mondooclient.NewClient(mondooclient.ClientOptions{ + mClient, err := scanapiclient.NewClient(scanapiclient.ScanApiClientOptions{ ApiEndpoint: url, Token: token, }) + require.NoError(t, err) // Run Health Check - healthResp, err := mClient.HealthCheck(context.Background(), &mondooclient.HealthCheckRequest{}) + healthResp, err := mClient.HealthCheck(context.Background(), &common.HealthCheckRequest{}) require.NoError(t, err) assert.True(t, healthResp.Status == "SERVING") - result, err := mClient.ScanKubernetesResources(context.Background(), &mondooclient.ScanKubernetesResourcesOpts{}) + result, err := mClient.ScanKubernetesResources(context.Background(), &scanapiclient.ScanKubernetesResourcesOpts{}) require.NoError(t, err) require.NotNil(t, result) diff --git a/pkg/mondooclient/fakeserver/fakeserver.go b/pkg/client/scanapiclient/fakeserver/fakeserver.go similarity index 57% rename from pkg/mondooclient/fakeserver/fakeserver.go rename to pkg/client/scanapiclient/fakeserver/fakeserver.go index 2223f6553..fee0c8d22 100644 --- a/pkg/mondooclient/fakeserver/fakeserver.go +++ b/pkg/client/scanapiclient/fakeserver/fakeserver.go @@ -8,13 +8,14 @@ import ( "net/http" "net/http/httptest" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" + "go.mondoo.com/mondoo-operator/pkg/client/common" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient" ) func FakeServer() *httptest.Server { mux := http.NewServeMux() - mux.HandleFunc(mondooclient.HealthCheckEndpoint, func(w http.ResponseWriter, r *http.Request) { - result := &mondooclient.HealthCheckResponse{ + mux.HandleFunc(common.HealthCheckEndpoint, func(w http.ResponseWriter, r *http.Request) { + result := &common.HealthCheckResponse{ Status: "SERVING", } data, err := json.Marshal(result) @@ -28,11 +29,11 @@ func FakeServer() *httptest.Server { } }) - mux.HandleFunc(mondooclient.RunAdmissionReviewEndpoint, func(w http.ResponseWriter, r *http.Request) { - result := &mondooclient.ScanResult{ + mux.HandleFunc(scanapiclient.RunAdmissionReviewEndpoint, func(w http.ResponseWriter, r *http.Request) { + result := &scanapiclient.ScanResult{ Ok: true, - WorstScore: &mondooclient.Score{ - Type: mondooclient.ValidScanResult, + WorstScore: &scanapiclient.Score{ + Type: scanapiclient.ValidScanResult, Value: 100, }, } @@ -47,11 +48,11 @@ func FakeServer() *httptest.Server { } }) - mux.HandleFunc(mondooclient.ScanKubernetesResourcesEndpoint, func(w http.ResponseWriter, r *http.Request) { - result := &mondooclient.ScanResult{ + mux.HandleFunc(scanapiclient.ScanKubernetesResourcesEndpoint, func(w http.ResponseWriter, r *http.Request) { + result := &scanapiclient.ScanResult{ Ok: true, - WorstScore: &mondooclient.Score{ - Type: mondooclient.ValidScanResult, + WorstScore: &scanapiclient.Score{ + Type: scanapiclient.ValidScanResult, Value: 100, }, } diff --git a/pkg/client/scanapiclient/mock/client_generated.go b/pkg/client/scanapiclient/mock/client_generated.go new file mode 100644 index 000000000..bedd0caa8 --- /dev/null +++ b/pkg/client/scanapiclient/mock/client_generated.go @@ -0,0 +1,112 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./types.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + scan "go.mondoo.com/cnspec/policy/scan" + common "go.mondoo.com/mondoo-operator/pkg/client/common" + scanapiclient "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient" +) + +// MockScanApiClient is a mock of ScanApiClient interface. +type MockScanApiClient struct { + ctrl *gomock.Controller + recorder *MockScanApiClientMockRecorder +} + +// MockScanApiClientMockRecorder is the mock recorder for MockScanApiClient. +type MockScanApiClientMockRecorder struct { + mock *MockScanApiClient +} + +// NewMockScanApiClient creates a new mock instance. +func NewMockScanApiClient(ctrl *gomock.Controller) *MockScanApiClient { + mock := &MockScanApiClient{ctrl: ctrl} + mock.recorder = &MockScanApiClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockScanApiClient) EXPECT() *MockScanApiClientMockRecorder { + return m.recorder +} + +// GarbageCollectAssets mocks base method. +func (m *MockScanApiClient) GarbageCollectAssets(arg0 context.Context, arg1 *scan.GarbageCollectOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GarbageCollectAssets", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GarbageCollectAssets indicates an expected call of GarbageCollectAssets. +func (mr *MockScanApiClientMockRecorder) GarbageCollectAssets(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GarbageCollectAssets", reflect.TypeOf((*MockScanApiClient)(nil).GarbageCollectAssets), arg0, arg1) +} + +// HealthCheck mocks base method. +func (m *MockScanApiClient) HealthCheck(arg0 context.Context, arg1 *common.HealthCheckRequest) (*common.HealthCheckResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HealthCheck", arg0, arg1) + ret0, _ := ret[0].(*common.HealthCheckResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HealthCheck indicates an expected call of HealthCheck. +func (mr *MockScanApiClientMockRecorder) HealthCheck(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockScanApiClient)(nil).HealthCheck), arg0, arg1) +} + +// RunAdmissionReview mocks base method. +func (m *MockScanApiClient) RunAdmissionReview(arg0 context.Context, arg1 *scanapiclient.AdmissionReviewJob) (*scanapiclient.ScanResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RunAdmissionReview", arg0, arg1) + ret0, _ := ret[0].(*scanapiclient.ScanResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RunAdmissionReview indicates an expected call of RunAdmissionReview. +func (mr *MockScanApiClientMockRecorder) RunAdmissionReview(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunAdmissionReview", reflect.TypeOf((*MockScanApiClient)(nil).RunAdmissionReview), arg0, arg1) +} + +// ScanKubernetesResources mocks base method. +func (m *MockScanApiClient) ScanKubernetesResources(ctx context.Context, scanOpts *scanapiclient.ScanKubernetesResourcesOpts) (*scanapiclient.ScanResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ScanKubernetesResources", ctx, scanOpts) + ret0, _ := ret[0].(*scanapiclient.ScanResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ScanKubernetesResources indicates an expected call of ScanKubernetesResources. +func (mr *MockScanApiClientMockRecorder) ScanKubernetesResources(ctx, scanOpts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanKubernetesResources", reflect.TypeOf((*MockScanApiClient)(nil).ScanKubernetesResources), ctx, scanOpts) +} + +// ScheduleKubernetesResourceScan mocks base method. +func (m *MockScanApiClient) ScheduleKubernetesResourceScan(ctx context.Context, integrationMrn, resourceKey, managedBy string) (*scanapiclient.Empty, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ScheduleKubernetesResourceScan", ctx, integrationMrn, resourceKey, managedBy) + ret0, _ := ret[0].(*scanapiclient.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ScheduleKubernetesResourceScan indicates an expected call of ScheduleKubernetesResourceScan. +func (mr *MockScanApiClientMockRecorder) ScheduleKubernetesResourceScan(ctx, integrationMrn, resourceKey, managedBy interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScheduleKubernetesResourceScan", reflect.TypeOf((*MockScanApiClient)(nil).ScheduleKubernetesResourceScan), ctx, integrationMrn, resourceKey, managedBy) +} diff --git a/pkg/client/scanapiclient/types.go b/pkg/client/scanapiclient/types.go new file mode 100644 index 000000000..35d06d721 --- /dev/null +++ b/pkg/client/scanapiclient/types.go @@ -0,0 +1,79 @@ +package scanapiclient + +import ( + "context" + + v1 "go.mondoo.com/cnquery/motor/inventory/v1" + "go.mondoo.com/cnquery/motor/providers" + "go.mondoo.com/cnspec/policy/scan" + "go.mondoo.com/mondoo-operator/pkg/client/common" + "google.golang.org/protobuf/types/known/structpb" +) + +//go:generate ./../../../bin/mockgen -source=./types.go -destination=./mock/client_generated.go -package=mock + +type ScanApiClient interface { + common.HealthCheckClient + RunAdmissionReview(context.Context, *AdmissionReviewJob) (*ScanResult, error) + ScanKubernetesResources(ctx context.Context, scanOpts *ScanKubernetesResourcesOpts) (*ScanResult, error) + ScheduleKubernetesResourceScan(ctx context.Context, integrationMrn, resourceKey, managedBy string) (*Empty, error) + GarbageCollectAssets(context.Context, *scan.GarbageCollectOptions) error +} + +type AdmissionReviewJob struct { + Data *structpb.Struct `json:"data,omitempty"` + // Map of string keys and values that can be used to organize and categorize the assets + Labels map[string]string `json:"labels,omitempty"` + ReportType ReportType `json:"report_type,omitempty"` + // Additional options for the manifest job + Options map[string]string `json:"options,omitempty"` + // Additional discovery settings for the manifest job + Discovery *providers.Discovery `json:"discovery,omitempty"` +} + +type File struct { + Data []byte `json:"data,omitempty"` +} + +// A valid result would come back as a '2' +const ValidScanResult = uint32(2) + +type ScanResult struct { + WorstScore *Score `json:"worstScore,omitempty"` + Ok bool `json:"ok,omitempty"` +} + +type Score struct { + QrId string `json:"qr_id,omitempty"` + Type uint32 `json:"type,omitempty"` + Value uint32 `json:"value,omitempty"` + Weight uint32 `json:"weight,omitempty"` + ScoreCompletion uint32 `json:"score_completion,omitempty"` + DataTotal uint32 `json:"data_total,omitempty"` + DataCompletion uint32 `json:"data_completion,omitempty"` + Message string `json:"message,omitempty"` +} + +type ReportType int + +const ( + ReportType_NONE ReportType = 0 + ReportType_ERROR ReportType = 1 + ReportType_FULL ReportType = 2 +) + +type ScanJob struct { + Inventory v1.Inventory `json:"inventory"` + ReportType ReportType `protobuf:"varint,22,opt,name=report_type,json=reportType,proto3,enum=mondoo.policy.scan.ReportType" json:"report_type,omitempty"` +} + +type ScanKubernetesResourcesOpts struct { + IntegrationMrn string + // If set to true, the scan will discover only container images and not Kubernetes resources + ScanContainerImages bool + ManagedBy string + IncludeNamespaces []string + ExcludeNamespaces []string +} + +type Empty struct{} diff --git a/pkg/mondooclient/client.go b/pkg/mondooclient/client.go deleted file mode 100644 index 8c34c4358..000000000 --- a/pkg/mondooclient/client.go +++ /dev/null @@ -1,561 +0,0 @@ -/* -Copyright 2022 Mondoo, Inc. - -This Source Code Form is subject to the terms of the Mozilla Public -License, v. 2.0. If a copy of the MPL was not distributed with this -file, You can obtain one at https://mozilla.org/MPL/2.0/. -*/ - -package mondooclient - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net" - "net/http" - "strings" - "time" - - "go.mondoo.com/cnquery/motor/asset" - v1 "go.mondoo.com/cnquery/motor/inventory/v1" - "go.mondoo.com/cnquery/motor/providers" - "go.mondoo.com/cnspec/policy/scan" - "go.mondoo.com/mondoo-operator/pkg/constants" - "google.golang.org/protobuf/types/known/structpb" -) - -const ( - defaultHttpTimeout = 30 * time.Second - defaultIdleConnTimeout = 30 * time.Second - defaultKeepAlive = 30 * time.Second - defaultTLSHandshakeTimeout = 10 * time.Second - maxIdleConnections = 100 -) - -//go:generate ./../../bin/mockgen -source=./client.go -destination=./mock/client_generated.go -package=mock - -type Client interface { - ExchangeRegistrationToken(context.Context, *ExchangeRegistrationTokenInput) (*ExchangeRegistrationTokenOutput, error) - - HealthCheck(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) - RunAdmissionReview(context.Context, *AdmissionReviewJob) (*ScanResult, error) - ScanKubernetesResources(ctx context.Context, scanOpts *ScanKubernetesResourcesOpts) (*ScanResult, error) - ScheduleKubernetesResourceScan(ctx context.Context, integrationMrn, resourceKey, managedBy string) (*Empty, error) - GarbageCollectAssets(context.Context, *scan.GarbageCollectOptions) error - - IntegrationRegister(context.Context, *IntegrationRegisterInput) (*IntegrationRegisterOutput, error) - IntegrationCheckIn(context.Context, *IntegrationCheckInInput) (*IntegrationCheckInOutput, error) - IntegrationReportStatus(context.Context, *ReportStatusRequest) error -} - -func DefaultHttpClient() *http.Client { - tr := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: defaultHttpTimeout, - KeepAlive: defaultKeepAlive, - }).DialContext, - MaxIdleConns: maxIdleConnections, - IdleConnTimeout: defaultIdleConnTimeout, - TLSHandshakeTimeout: defaultTLSHandshakeTimeout, - ExpectContinueTimeout: 1 * time.Second, - } - - httpClient := &http.Client{ - Transport: tr, - Timeout: defaultHttpTimeout, - } - return httpClient -} - -type ClientOptions struct { - ApiEndpoint string - Token string -} - -type mondooClient struct { - ApiEndpoint string - Token string - httpclient http.Client -} - -func (s *mondooClient) request(ctx context.Context, url string, reqBodyBytes []byte) ([]byte, error) { - client := s.httpclient - - header := make(http.Header) - header.Set("Accept", "application/json") - header.Set("Content-Type", "application/json") - if s.Token != "" { - header.Set("Authorization", fmt.Sprintf("Bearer %s", s.Token)) - } - - reader := bytes.NewReader(reqBodyBytes) - req, err := http.NewRequest(http.MethodPost, url, reader) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - req.Header = header - - // do http call - resp, err := client.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to do request: %v", err) - } - - defer func() { - resp.Body.Close() - }() - - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read http response body: %s", err) - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("http status %d: %s", resp.StatusCode, respBody) - } - - return respBody, nil -} - -const ExchangeRegistrationTokenEndpoint = "/AgentManager/ExchangeRegistrationToken" - -func (s *mondooClient) ExchangeRegistrationToken(ctx context.Context, in *ExchangeRegistrationTokenInput) (*ExchangeRegistrationTokenOutput, error) { - url := s.ApiEndpoint + ExchangeRegistrationTokenEndpoint - - reqBodyBytes, err := json.Marshal(in) - if err != nil { - return nil, fmt.Errorf("failed to marshal request: %v", err) - } - - respBodyBytes, err := s.request(ctx, url, reqBodyBytes) - if err != nil { - return nil, fmt.Errorf("failed to parse response: %v", err) - } - - out := &ExchangeRegistrationTokenOutput{ - ServiceAccount: string(respBodyBytes), - } - - return out, nil -} - -// ExchangeRegistrationTokenInput is used for converting a JWT to a Mondoo serivce account -type ExchangeRegistrationTokenInput struct { - // JWT token, only available during creation - Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` -} - -type ExchangeRegistrationTokenOutput struct { - ServiceAccount string `json:"serviceAccount,omitempty"` -} - -const HealthCheckEndpoint = "/Health/Check" - -func (s *mondooClient) HealthCheck(ctx context.Context, in *HealthCheckRequest) (*HealthCheckResponse, error) { - url := s.ApiEndpoint + HealthCheckEndpoint - - reqBodyBytes, err := json.Marshal(in) - if err != nil { - return nil, fmt.Errorf("failed to marshal request: %v", err) - } - - respBodyBytes, err := s.request(ctx, url, reqBodyBytes) - if err != nil { - return nil, fmt.Errorf("failed to parse response: %v", err) - } - - out := &HealthCheckResponse{} - if err = json.Unmarshal(respBodyBytes, out); err != nil { - return nil, fmt.Errorf("failed to unmarshal proto response: %v", err) - } - - return out, nil -} - -type HealthCheckRequest struct{} - -type HealthCheckResponse struct { - Status string `json:"status,omitempty"` - // returns rfc 3339 timestamp - Time string `json:"time,omitempty"` - // returns the major api version - ApiVersion string `json:"apiVersion,omitempty"` - // returns the git commit checksum - Build string `json:"build,omitempty"` -} - -const ( - RunAdmissionReviewEndpoint = "/Scan/RunAdmissionReview" - // A valid result would come back as a '2' - ValidScanResult = uint32(2) -) - -func (s *mondooClient) RunAdmissionReview(ctx context.Context, in *AdmissionReviewJob) (*ScanResult, error) { - url := s.ApiEndpoint + RunAdmissionReviewEndpoint - - reqBodyBytes, err := json.Marshal(in) - if err != nil { - return nil, fmt.Errorf("failed to marshal request: %v", err) - } - - respBodyBytes, err := s.request(ctx, url, reqBodyBytes) - if err != nil { - return nil, fmt.Errorf("failed to parse response: %v", err) - } - - out := &ScanResult{} - if err = json.Unmarshal(respBodyBytes, out); err != nil { - return nil, fmt.Errorf("failed to unmarshal proto response: %v", err) - } - - return out, nil -} - -type AdmissionReviewJob struct { - Data *structpb.Struct `json:"data,omitempty"` - // Map of string keys and values that can be used to organize and categorize the assets - Labels map[string]string `json:"labels,omitempty"` - ReportType ReportType `json:"report_type,omitempty"` - // Additional options for the manifest job - Options map[string]string `json:"options,omitempty"` - // Additional discovery settings for the manifest job - Discovery *providers.Discovery `json:"discovery,omitempty"` -} - -type File struct { - Data []byte `json:"data,omitempty"` -} - -type ScanResult struct { - WorstScore *Score `json:"worstScore,omitempty"` - Ok bool `json:"ok,omitempty"` -} - -type Score struct { - QrId string `json:"qr_id,omitempty"` - Type uint32 `json:"type,omitempty"` - Value uint32 `json:"value,omitempty"` - Weight uint32 `json:"weight,omitempty"` - ScoreCompletion uint32 `json:"score_completion,omitempty"` - DataTotal uint32 `json:"data_total,omitempty"` - DataCompletion uint32 `json:"data_completion,omitempty"` - Message string `json:"message,omitempty"` -} - -type ScanKubernetesResourcesOpts struct { - IntegrationMrn string - // If set to true, the scan will discover only container images and not Kubernetes resources - ScanContainerImages bool - ManagedBy string - IncludeNamespaces []string - ExcludeNamespaces []string -} - -const ScanKubernetesResourcesEndpoint = "/Scan/Run" - -func (s *mondooClient) ScanKubernetesResources(ctx context.Context, scanOpts *ScanKubernetesResourcesOpts) (*ScanResult, error) { - url := s.ApiEndpoint + ScanKubernetesResourcesEndpoint - scanJob := &ScanJob{ - ReportType: ReportType_ERROR, - Inventory: v1.Inventory{ - Spec: &v1.InventorySpec{ - Assets: []*asset.Asset{ - { - Connections: []*providers.Config{ - { - Backend: providers.ProviderType_K8S, - Options: map[string]string{ - "namespaces": strings.Join(scanOpts.IncludeNamespaces, ","), - "namespaces-exclude": strings.Join(scanOpts.ExcludeNamespaces, ","), - }, - Discover: &providers.Discovery{ - Targets: []string{"auto"}, - }, - }, - }, - ManagedBy: scanOpts.ManagedBy, - }, - }, - }, - }, - } - - setIntegrationMrn(scanOpts.IntegrationMrn, scanJob) - - if scanOpts.ScanContainerImages { - scanJob.Inventory.Spec.Assets[0].Connections[0].Discover.Targets = []string{"container-images"} - } - - reqBodyBytes, err := json.Marshal(scanJob) - if err != nil { - return nil, fmt.Errorf("failed to marshal request: %v", err) - } - - respBodyBytes, err := s.request(ctx, url, reqBodyBytes) - if err != nil { - return nil, fmt.Errorf("failed to parse response: %v", err) - } - - out := &ScanResult{} - if err = json.Unmarshal(respBodyBytes, out); err != nil { - return nil, fmt.Errorf("failed to unmarshal proto response: %v", err) - } - - return out, nil -} - -type Empty struct{} - -const ScheduleKubernetesResourceScanEndpoint = "/Scan/Schedule" - -func (s *mondooClient) ScheduleKubernetesResourceScan(ctx context.Context, integrationMrn, resourceKey, managedBy string) (*Empty, error) { - url := s.ApiEndpoint + ScheduleKubernetesResourceScanEndpoint - scanJob := &ScanJob{ - ReportType: ReportType_ERROR, - Inventory: v1.Inventory{ - Spec: &v1.InventorySpec{ - Assets: []*asset.Asset{ - { - Connections: []*providers.Config{ - { - Backend: providers.ProviderType_K8S, - Options: map[string]string{ - "k8s-resources": resourceKey, - }, - Discover: &providers.Discovery{ - Targets: []string{"auto"}, - }, - }, - }, - }, - }, - }, - }, - } - - if len(managedBy) > 0 { - scanJob.Inventory.Spec.Assets[0].ManagedBy = managedBy - } - - setIntegrationMrn(integrationMrn, scanJob) - - reqBodyBytes, err := json.Marshal(scanJob) - if err != nil { - return nil, fmt.Errorf("failed to marshal request: %v", err) - } - - respBodyBytes, err := s.request(ctx, url, reqBodyBytes) - if err != nil { - return nil, fmt.Errorf("failed to parse response: %v", err) - } - - out := &Empty{} - if err = json.Unmarshal(respBodyBytes, out); err != nil { - return nil, fmt.Errorf("failed to unmarshal proto response: %v", err) - } - - return out, nil -} - -type ReportType int - -const ( - ReportType_NONE ReportType = 0 - ReportType_ERROR ReportType = 1 - ReportType_FULL ReportType = 2 -) - -type ScanJob struct { - Inventory v1.Inventory `json:"inventory"` - ReportType ReportType `protobuf:"varint,22,opt,name=report_type,json=reportType,proto3,enum=mondoo.policy.scan.ReportType" json:"report_type,omitempty"` -} - -func NewClient(opts ClientOptions) Client { - opts.ApiEndpoint = strings.TrimRight(opts.ApiEndpoint, "/") - mClient := &mondooClient{ - ApiEndpoint: opts.ApiEndpoint, - Token: opts.Token, - } - return mClient -} - -type IntegrationRegisterInput struct { - // Mrn is the MRN of the integration. It should be provided in the JWT under the "owner" claim - Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` - Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` -} - -type IntegrationRegisterOutput struct { - // Mrn is the integration MRN - Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` - // Creds holds all the Mondoo serivce account data - Creds *ServiceAccountCredentials `protobuf:"bytes,2,opt,name=creds,proto3" json:"creds,omitempty"` -} - -type ServiceAccountCredentials struct { - Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` - SpaceMrn string `protobuf:"bytes,2,opt,name=space_mrn,json=spaceMrn,proto3" json:"space_mrn,omitempty"` - PrivateKey string `protobuf:"bytes,3,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` - Certificate string `protobuf:"bytes,4,opt,name=certificate,proto3" json:"certificate,omitempty"` - ApiEndpoint string `protobuf:"bytes,5,opt,name=api_endpoint,json=apiEndpoint,proto3" json:"api_endpoint,omitempty"` -} - -const IntegrationRegisterEndpoint = "/IntegrationsManager/Register" - -func (s *mondooClient) IntegrationRegister(ctx context.Context, in *IntegrationRegisterInput) (*IntegrationRegisterOutput, error) { - url := s.ApiEndpoint + IntegrationRegisterEndpoint - - reqBodyBytes, err := json.Marshal(in) - if err != nil { - return nil, fmt.Errorf("failed to marshal request: %v", err) - } - - respBodyBytes, err := s.request(ctx, url, reqBodyBytes) - if err != nil { - return nil, fmt.Errorf("failed to parse response: %v", err) - } - - out := &IntegrationRegisterOutput{} - if err = json.Unmarshal(respBodyBytes, out); err != nil { - return nil, fmt.Errorf("failed to unmarshal proto response: %v", err) - } - - return out, nil -} - -type IntegrationCheckInInput struct { - // Mrn should hold the MRN of the integration that is having the CheckIn() called for - Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` - // optional, ensure the client has the exact same configuration options - // as the ones saved to the integration/db - ConfigurationHash string `protobuf:"bytes,2,opt,name=configuration_hash,json=configurationHash,proto3" json:"configuration_hash,omitempty"` - // source identifier for the integration, e.g. AWS account id - Identifier string `protobuf:"bytes,3,opt,name=identifier,proto3" json:"identifier,omitempty"` -} - -type IntegrationCheckInOutput struct { - Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` - // true if the configuration hash sent in matches the hash of the stored configuration - ConfigurationMatch bool `protobuf:"varint,2,opt,name=configuration_match,json=configurationMatch,proto3" json:"configuration_match,omitempty"` -} - -const IntegrationCheckInEndpoint = "/IntegrationsManager/CheckIn" - -func (s *mondooClient) IntegrationCheckIn(ctx context.Context, in *IntegrationCheckInInput) (*IntegrationCheckInOutput, error) { - url := s.ApiEndpoint + IntegrationCheckInEndpoint - - reqBodyBytes, err := json.Marshal(in) - if err != nil { - return nil, fmt.Errorf("failed to marshal request: %v", err) - } - - respBodyBytes, err := s.request(ctx, url, reqBodyBytes) - if err != nil { - return nil, fmt.Errorf("failed to parse response: %v", err) - } - - out := &IntegrationCheckInOutput{} - if err = json.Unmarshal(respBodyBytes, out); err != nil { - return nil, fmt.Errorf("failed to unmarshal response: %v", err) - } - - return out, nil -} - -type ReportStatusRequest struct { - Mrn string `protobuf:"bytes,1,opt,name=mrn,proto3" json:"mrn,omitempty"` - // this is the status of the integration itself (is it active/checking in, errored, etc) - Status Status `protobuf:"varint,2,opt,name=status,proto3,enum=mondoo.integrations.v1.Status" json:"status,omitempty"` - // this can be any information about the current state of the integration. it will be displayed to the user as-is where supported - LastState interface{} `protobuf:"bytes,4,opt,name=last_state,json=lastState,proto3" json:"last_state,omitempty"` - // Allows the agent to report its current version - Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` - // messages that convey extra information about the integration - these messages can be informational, warnings or errors. Can be used - // to report non-critical errors/warnings without neccesarily changing the whole integration status. - Messages Messages `protobuf:"bytes,7,opt,name=messages,proto3" json:"messages,omitempty"` -} - -type Messages struct { - Messages []IntegrationMessage `protobuf:"bytes,1,opt,name=messages,proto3" json:"messages,omitempty"` -} - -type Status int32 - -const ( - Status_NOT_READY Status = 0 - Status_WAITING_FOR_SETUP Status = 1 - Status_ACTIVE Status = 2 - Status_ERROR Status = 3 - Status_DELETED Status = 4 - Status_MISSING Status = 5 - Status_WARNING Status = 6 -) - -type IntegrationMessage struct { - Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` - Timestamp string `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - Status MessageStatus `protobuf:"varint,3,opt,name=status,proto3,enum=mondoo.integrations.v1.MessageStatus" json:"status,omitempty"` - ReportedByAgent bool `protobuf:"varint,4,opt,name=reported_by_agent,json=reportedByAgent,proto3" json:"reported_by_agent,omitempty"` - Identifier string `protobuf:"bytes,5,opt,name=identifier,proto3" json:"identifier,omitempty"` - // Anything extra that the message might contain. - Extra interface{} `protobuf:"bytes,6,opt,name=extra,proto3" json:"extra,omitempty"` -} - -type MessageStatus int32 - -const ( - MessageStatus_MESSAGE_UNKNOWN MessageStatus = 0 - MessageStatus_MESSAGE_WARNING MessageStatus = 1 - MessageStatus_MESSAGE_ERROR MessageStatus = 2 - MessageStatus_MESSAGE_INFO MessageStatus = 3 -) - -const IntegrationReportStatusEndpoint = "/IntegrationsManager/ReportStatus" - -func (s *mondooClient) IntegrationReportStatus(ctx context.Context, in *ReportStatusRequest) error { - url := s.ApiEndpoint + IntegrationReportStatusEndpoint - - reqBodyBytes, err := json.Marshal(in) - if err != nil { - return fmt.Errorf("failed to marshal request: %v", err) - } - - _, err = s.request(ctx, url, reqBodyBytes) - if err != nil { - return fmt.Errorf("failed to parse response: %v", err) - } - - return nil -} - -func setIntegrationMrn(integrationMrn string, scanJob *ScanJob) { - if integrationMrn != "" { - if scanJob.Inventory.Spec.Assets[0].Labels == nil { - scanJob.Inventory.Spec.Assets[0].Labels = make(map[string]string) - } - scanJob.Inventory.Spec.Assets[0].Labels[constants.MondooAssetsIntegrationLabel] = integrationMrn - } -} - -const GarbageCollectAssetsEndpoint = "/Scan/GarbageCollectAssets" - -func (s *mondooClient) GarbageCollectAssets(ctx context.Context, in *scan.GarbageCollectOptions) error { - url := s.ApiEndpoint + GarbageCollectAssetsEndpoint - - reqBodyBytes, err := json.Marshal(in) - if err != nil { - return fmt.Errorf("failed to marshal request: %v", err) - } - - _, err = s.request(ctx, url, reqBodyBytes) - if err != nil { - return fmt.Errorf("error calling GarbageCollectAssets: %s", err) - } - - return nil -} diff --git a/pkg/mondooclient/mock/client_generated.go b/pkg/mondooclient/mock/client_generated.go deleted file mode 100644 index 882e49772..000000000 --- a/pkg/mondooclient/mock/client_generated.go +++ /dev/null @@ -1,170 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: ./client.go - -// Package mock is a generated GoMock package. -package mock - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - scan "go.mondoo.com/cnspec/policy/scan" - mondooclient "go.mondoo.com/mondoo-operator/pkg/mondooclient" -) - -// MockClient is a mock of Client interface. -type MockClient struct { - ctrl *gomock.Controller - recorder *MockClientMockRecorder -} - -// MockClientMockRecorder is the mock recorder for MockClient. -type MockClientMockRecorder struct { - mock *MockClient -} - -// NewMockClient creates a new mock instance. -func NewMockClient(ctrl *gomock.Controller) *MockClient { - mock := &MockClient{ctrl: ctrl} - mock.recorder = &MockClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockClient) EXPECT() *MockClientMockRecorder { - return m.recorder -} - -// ExchangeRegistrationToken mocks base method. -func (m *MockClient) ExchangeRegistrationToken(arg0 context.Context, arg1 *mondooclient.ExchangeRegistrationTokenInput) (*mondooclient.ExchangeRegistrationTokenOutput, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ExchangeRegistrationToken", arg0, arg1) - ret0, _ := ret[0].(*mondooclient.ExchangeRegistrationTokenOutput) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ExchangeRegistrationToken indicates an expected call of ExchangeRegistrationToken. -func (mr *MockClientMockRecorder) ExchangeRegistrationToken(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExchangeRegistrationToken", reflect.TypeOf((*MockClient)(nil).ExchangeRegistrationToken), arg0, arg1) -} - -// GarbageCollectAssets mocks base method. -func (m *MockClient) GarbageCollectAssets(arg0 context.Context, arg1 *scan.GarbageCollectOptions) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GarbageCollectAssets", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// GarbageCollectAssets indicates an expected call of GarbageCollectAssets. -func (mr *MockClientMockRecorder) GarbageCollectAssets(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GarbageCollectAssets", reflect.TypeOf((*MockClient)(nil).GarbageCollectAssets), arg0, arg1) -} - -// HealthCheck mocks base method. -func (m *MockClient) HealthCheck(arg0 context.Context, arg1 *mondooclient.HealthCheckRequest) (*mondooclient.HealthCheckResponse, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HealthCheck", arg0, arg1) - ret0, _ := ret[0].(*mondooclient.HealthCheckResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// HealthCheck indicates an expected call of HealthCheck. -func (mr *MockClientMockRecorder) HealthCheck(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockClient)(nil).HealthCheck), arg0, arg1) -} - -// IntegrationCheckIn mocks base method. -func (m *MockClient) IntegrationCheckIn(arg0 context.Context, arg1 *mondooclient.IntegrationCheckInInput) (*mondooclient.IntegrationCheckInOutput, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IntegrationCheckIn", arg0, arg1) - ret0, _ := ret[0].(*mondooclient.IntegrationCheckInOutput) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// IntegrationCheckIn indicates an expected call of IntegrationCheckIn. -func (mr *MockClientMockRecorder) IntegrationCheckIn(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntegrationCheckIn", reflect.TypeOf((*MockClient)(nil).IntegrationCheckIn), arg0, arg1) -} - -// IntegrationRegister mocks base method. -func (m *MockClient) IntegrationRegister(arg0 context.Context, arg1 *mondooclient.IntegrationRegisterInput) (*mondooclient.IntegrationRegisterOutput, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IntegrationRegister", arg0, arg1) - ret0, _ := ret[0].(*mondooclient.IntegrationRegisterOutput) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// IntegrationRegister indicates an expected call of IntegrationRegister. -func (mr *MockClientMockRecorder) IntegrationRegister(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntegrationRegister", reflect.TypeOf((*MockClient)(nil).IntegrationRegister), arg0, arg1) -} - -// IntegrationReportStatus mocks base method. -func (m *MockClient) IntegrationReportStatus(arg0 context.Context, arg1 *mondooclient.ReportStatusRequest) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IntegrationReportStatus", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// IntegrationReportStatus indicates an expected call of IntegrationReportStatus. -func (mr *MockClientMockRecorder) IntegrationReportStatus(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntegrationReportStatus", reflect.TypeOf((*MockClient)(nil).IntegrationReportStatus), arg0, arg1) -} - -// RunAdmissionReview mocks base method. -func (m *MockClient) RunAdmissionReview(arg0 context.Context, arg1 *mondooclient.AdmissionReviewJob) (*mondooclient.ScanResult, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RunAdmissionReview", arg0, arg1) - ret0, _ := ret[0].(*mondooclient.ScanResult) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// RunAdmissionReview indicates an expected call of RunAdmissionReview. -func (mr *MockClientMockRecorder) RunAdmissionReview(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunAdmissionReview", reflect.TypeOf((*MockClient)(nil).RunAdmissionReview), arg0, arg1) -} - -// ScanKubernetesResources mocks base method. -func (m *MockClient) ScanKubernetesResources(ctx context.Context, scanOpts *mondooclient.ScanKubernetesResourcesOpts) (*mondooclient.ScanResult, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ScanKubernetesResources", ctx, scanOpts) - ret0, _ := ret[0].(*mondooclient.ScanResult) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ScanKubernetesResources indicates an expected call of ScanKubernetesResources. -func (mr *MockClientMockRecorder) ScanKubernetesResources(ctx, scanOpts interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanKubernetesResources", reflect.TypeOf((*MockClient)(nil).ScanKubernetesResources), ctx, scanOpts) -} - -// ScheduleKubernetesResourceScan mocks base method. -func (m *MockClient) ScheduleKubernetesResourceScan(ctx context.Context, integrationMrn, resourceKey, managedBy string) (*mondooclient.Empty, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ScheduleKubernetesResourceScan", ctx, integrationMrn, resourceKey, managedBy) - ret0, _ := ret[0].(*mondooclient.Empty) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ScheduleKubernetesResourceScan indicates an expected call of ScheduleKubernetesResourceScan. -func (mr *MockClientMockRecorder) ScheduleKubernetesResourceScan(ctx, integrationMrn, resourceKey, managedBy interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScheduleKubernetesResourceScan", reflect.TypeOf((*MockClient)(nil).ScheduleKubernetesResourceScan), ctx, integrationMrn, resourceKey, managedBy) -} diff --git a/pkg/utils/k8s/integration_mrn.go b/pkg/utils/k8s/integration_mrn.go index 115995b38..34359bdf7 100644 --- a/pkg/utils/k8s/integration_mrn.go +++ b/pkg/utils/k8s/integration_mrn.go @@ -14,8 +14,8 @@ import ( "fmt" "go.mondoo.com/mondoo-operator/api/v1alpha2" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" "go.mondoo.com/mondoo-operator/pkg/constants" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" diff --git a/pkg/utils/mondoo/integration.go b/pkg/utils/mondoo/integration.go index 24adb55b0..8c27d6aa3 100644 --- a/pkg/utils/mondoo/integration.go +++ b/pkg/utils/mondoo/integration.go @@ -13,14 +13,15 @@ import ( "fmt" "github.com/go-logr/logr" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" ) func IntegrationCheckIn( ctx context.Context, integrationMrn string, sa mondooclient.ServiceAccountCredentials, - mondooClientBuilder func(mondooclient.ClientOptions) mondooclient.Client, + mondooClientBuilder MondooClientBuilder, + httpProxy *string, logger logr.Logger, ) error { token, err := GenerateTokenFromServiceAccount(sa, logger) @@ -28,10 +29,14 @@ func IntegrationCheckIn( msg := "unable to generate token from service account" return fmt.Errorf("%s: %s", msg, err) } - mondooClient := mondooClientBuilder(mondooclient.ClientOptions{ + mondooClient, err := mondooClientBuilder(mondooclient.MondooClientOptions{ ApiEndpoint: sa.ApiEndpoint, Token: token, + HttpProxy: httpProxy, }) + if err != nil { + return err + } // Do the actual check-in if _, err := mondooClient.IntegrationCheckIn(ctx, &mondooclient.IntegrationCheckInInput{ diff --git a/pkg/utils/mondoo/token.go b/pkg/utils/mondoo/token.go index 0a49e3a7e..f8bbbd20f 100644 --- a/pkg/utils/mondoo/token.go +++ b/pkg/utils/mondoo/token.go @@ -12,7 +12,7 @@ import ( "github.com/go-logr/logr" jwt "github.com/golang-jwt/jwt/v4" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" ) // must be set to "mondoo/ams" when making Mondoo API calls diff --git a/pkg/utils/mondoo/token_exchange.go b/pkg/utils/mondoo/token_exchange.go index 20a21b692..91f64ba94 100644 --- a/pkg/utils/mondoo/token_exchange.go +++ b/pkg/utils/mondoo/token_exchange.go @@ -22,16 +22,16 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "go.mondoo.com/mondoo-operator/pkg/client/mondooclient" "go.mondoo.com/mondoo-operator/pkg/constants" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" "go.mondoo.com/mondoo-operator/pkg/utils/k8s" ) -type MondooClientBuilder func(mondooclient.ClientOptions) mondooclient.Client +type MondooClientBuilder func(mondooclient.MondooClientOptions) (mondooclient.MondooClient, error) // CreateServiceAccountFromToken will take the provided Mondoo token and exchange it with the Mondoo API // for a long lived Mondoo ServiceAccount -func CreateServiceAccountFromToken(ctx context.Context, kubeClient client.Client, mondooClientBuilder MondooClientBuilder, withConsoleIntegration bool, serviceAccountSecret types.NamespacedName, tokenSecretData string, log logr.Logger) error { +func CreateServiceAccountFromToken(ctx context.Context, kubeClient client.Client, mondooClientBuilder MondooClientBuilder, withConsoleIntegration bool, serviceAccountSecret types.NamespacedName, tokenSecretData string, httpProxy *string, log logr.Logger) error { jwtString := strings.TrimSpace(tokenSecretData) parser := &jwt.Parser{} @@ -49,12 +49,16 @@ func CreateServiceAccountFromToken(ctx context.Context, kubeClient client.Client } apiEndpoint := claims["api_endpoint"] - opts := mondooclient.ClientOptions{ + opts := mondooclient.MondooClientOptions{ ApiEndpoint: fmt.Sprintf("%v", apiEndpoint), Token: jwtString, + HttpProxy: httpProxy, } - mClient := mondooClientBuilder(opts) + mClient, err := mondooClientBuilder(opts) + if err != nil { + return err + } tokenSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -99,7 +103,7 @@ func CreateServiceAccountFromToken(ctx context.Context, kubeClient client.Client // No easy way to retry this one-off CheckIn(). An error on initial CheckIn() // means we'll just retry on the regularly scheduled interval via the integration controller - _ = performInitialCheckIn(ctx, mondooClientBuilder, integrationMrn, *resp.Creds, log) + _ = performInitialCheckIn(ctx, mondooClientBuilder, integrationMrn, *resp.Creds, httpProxy, log) } else { // Do a vanilla token-for-service-account exchange resp, err := mClient.ExchangeRegistrationToken(ctx, &mondooclient.ExchangeRegistrationTokenInput{ @@ -126,8 +130,8 @@ func CreateServiceAccountFromToken(ctx context.Context, kubeClient client.Client return nil } -func performInitialCheckIn(ctx context.Context, mondooClientBuilder MondooClientBuilder, integrationMrn string, sa mondooclient.ServiceAccountCredentials, logger logr.Logger) error { - if err := IntegrationCheckIn(ctx, integrationMrn, sa, mondooClientBuilder, logger); err != nil { +func performInitialCheckIn(ctx context.Context, mondooClientBuilder MondooClientBuilder, integrationMrn string, sa mondooclient.ServiceAccountCredentials, httpProxy *string, logger logr.Logger) error { + if err := IntegrationCheckIn(ctx, integrationMrn, sa, mondooClientBuilder, httpProxy, logger); err != nil { logger.Error(err, "initial CheckIn() failed, will CheckIn() periodically", "integrationMRN", integrationMrn) return err } diff --git a/pkg/webhooks/handler/webhook.go b/pkg/webhooks/handler/webhook.go index 7e27174b2..907f59cea 100644 --- a/pkg/webhooks/handler/webhook.go +++ b/pkg/webhooks/handler/webhook.go @@ -24,9 +24,9 @@ import ( "go.mondoo.com/cnquery/motor/providers" mondoov1alpha2 "go.mondoo.com/mondoo-operator/api/v1alpha2" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient" "go.mondoo.com/mondoo-operator/pkg/constants" "go.mondoo.com/mondoo-operator/pkg/feature_flags" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" "go.mondoo.com/mondoo-operator/pkg/utils" wutils "go.mondoo.com/mondoo-operator/pkg/webhooks/utils" ) @@ -66,7 +66,7 @@ type webhookValidator struct { client client.Client decoder *admission.Decoder mode mondoov1alpha2.AdmissionMode - scanner mondooclient.Client + scanner scanapiclient.ScanApiClient integrationMRN string clusterID string uniDecoder runtime.Decoder @@ -93,13 +93,18 @@ func NewWebhookValidator(opts *NewWebhookValidatorOpts) (admission.Handler, erro return nil, err } + clnt, err := scanapiclient.NewClient(scanapiclient.ScanApiClientOptions{ + ApiEndpoint: opts.ScanUrl, + Token: opts.Token, + }) + if err != nil { + return nil, err + } + return &webhookValidator{ - client: opts.Client, - mode: webhookMode, - scanner: mondooclient.NewClient(mondooclient.ClientOptions{ - ApiEndpoint: opts.ScanUrl, - Token: opts.Token, - }), + client: opts.Client, + mode: webhookMode, + scanner: clnt, integrationMRN: opts.IntegrationMrn, clusterID: opts.ClusterId, uniDecoder: serializer.NewCodecFactory(opts.Client.Scheme()).UniversalDeserializer(), @@ -182,10 +187,10 @@ func (a *webhookValidator) Handle(ctx context.Context, req admission.Request) (r handlerlog.Error(err, "failed to create proto struct from admission request") return } - scanJob := &mondooclient.AdmissionReviewJob{ + scanJob := &scanapiclient.AdmissionReviewJob{ Data: data, Labels: k8sLabels, - ReportType: mondooclient.ReportType_ERROR, + ReportType: scanapiclient.ReportType_ERROR, } scanJob.Discovery = &providers.Discovery{} @@ -204,7 +209,7 @@ func (a *webhookValidator) Handle(ctx context.Context, req admission.Request) (r } passed := false - if result.WorstScore != nil && result.WorstScore.Type == mondooclient.ValidScanResult && result.WorstScore.Value == 100 { + if result.WorstScore != nil && result.WorstScore.Type == scanapiclient.ValidScanResult && result.WorstScore.Value == 100 { passed = true } diff --git a/pkg/webhooks/handler/webhook_test.go b/pkg/webhooks/handler/webhook_test.go index 3702aad98..fe9488a21 100644 --- a/pkg/webhooks/handler/webhook_test.go +++ b/pkg/webhooks/handler/webhook_test.go @@ -27,9 +27,9 @@ import ( "sigs.k8s.io/yaml" mondoov1alpha2 "go.mondoo.com/mondoo-operator/api/v1alpha2" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient" + "go.mondoo.com/mondoo-operator/pkg/client/scanapiclient/fakeserver" "go.mondoo.com/mondoo-operator/pkg/constants" - "go.mondoo.com/mondoo-operator/pkg/mondooclient" - "go.mondoo.com/mondoo-operator/pkg/mondooclient/fakeserver" ) const ( @@ -166,12 +166,15 @@ func TestWebhookValidate(t *testing.T) { } testserver := fakeserver.FakeServer() + clnt, err := scanapiclient.NewClient(scanapiclient.ScanApiClientOptions{ + ApiEndpoint: testserver.URL, + }) + require.NoError(t, err) + validator := &webhookValidator{ - decoder: decoder, - mode: test.mode, - scanner: mondooclient.NewClient(mondooclient.ClientOptions{ - ApiEndpoint: testserver.URL, - }), + decoder: decoder, + mode: test.mode, + scanner: clnt, uniDecoder: serializer.NewCodecFactory(clientgoscheme.Scheme).UniversalDeserializer(), } @@ -298,15 +301,18 @@ func TestWebhookNamespaceFiltering(t *testing.T) { t.Run(test.name, func(t *testing.T) { // Arrange testserver := fakeserver.FakeServer() + clnt, err := scanapiclient.NewClient(scanapiclient.ScanApiClientOptions{ + ApiEndpoint: testserver.URL, + }) + require.NoError(t, err) + validator := &webhookValidator{ excludeNamespaces: test.excludeList, includeNamespaces: test.includeList, decoder: decoder, mode: mondoov1alpha2.Permissive, - scanner: mondooclient.NewClient(mondooclient.ClientOptions{ - ApiEndpoint: testserver.URL, - }), - uniDecoder: serializer.NewCodecFactory(clientgoscheme.Scheme).UniversalDeserializer(), + scanner: clnt, + uniDecoder: serializer.NewCodecFactory(clientgoscheme.Scheme).UniversalDeserializer(), } request := admission.Request{