diff --git a/apps/cnspec/cmd/integrate.go b/apps/cnspec/cmd/integrate.go new file mode 100644 index 00000000..3cfbcdd1 --- /dev/null +++ b/apps/cnspec/cmd/integrate.go @@ -0,0 +1,200 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package cmd + +import ( + "dario.cat/mergo" + "github.com/AlecAivazis/survey/v2" + "github.com/cockroachdb/errors" + "github.com/spf13/cobra" +) + +func init() { + // cnspec integrate + rootCmd.AddCommand(integrateCmd) + // cnspec integrate azure + integrateCmd.AddCommand(integrateAzureCmd) +} + +var ( + integrateCmd = &cobra.Command{ + Use: "integrate", + Aliases: []string{"onboard"}, + Hidden: true, + Short: "Onboard integrations for continuous scanning into the Mondoo platform", + Long: "Run automation code to onboard your account and deploy Mondoo into various environments.", + } + integrateAzureCmd = &cobra.Command{ + Use: "azure", + Aliases: []string{"az"}, + Short: "Onboard Microsoft Azure", + Long: `Use this command to connect your Azure environment into the Mondoo platform.`, + RunE: func(cmd *cobra.Command, args []string) error { + // Generate TF Code + cli.StartProgress("Generating Azure Terraform Code...") + + if cli.Profile != "default" { + GenerateAzureCommandState.LaceworkProfile = cli.Profile + } + + // Setup modifiers for NewTerraform constructor + mods := []azure.AzureTerraformModifier{ + azure.WithLaceworkProfile(GenerateAzureCommandState.LaceworkProfile), + azure.WithSubscriptionID(GenerateAzureCommandState.SubscriptionID), + azure.WithAllSubscriptions(GenerateAzureCommandState.AllSubscriptions), + azure.WithManagementGroup(GenerateAzureCommandState.ManagementGroup), + azure.WithExistingStorageAccount(GenerateAzureCommandState.ExistingStorageAccount), + azure.WithStorageAccountName(GenerateAzureCommandState.StorageAccountName), + azure.WithStorageLocation(GenerateAzureCommandState.StorageLocation), + azure.WithActivityLogIntegrationName(GenerateAzureCommandState.ActivityLogIntegrationName), + azure.WithConfigIntegrationName(GenerateAzureCommandState.ConfigIntegrationName), + azure.WithEntraIdActivityLogIntegrationName(GenerateAzureCommandState.EntraIdIntegrationName), + azure.WithEventHubLocation(GenerateAzureCommandState.EventHubLocation), + azure.WithEventHubPartitionCount(GenerateAzureCommandState.EventHubPartitionCount), + } + + // Check if AD Creation is required, need to set values for current integration + if !GenerateAzureCommandState.CreateAdIntegration { + mods = append(mods, azure.WithAdApplicationId(GenerateAzureCommandState.AdApplicationId)) + mods = append(mods, azure.WithAdApplicationPassword(GenerateAzureCommandState.AdApplicationPassword)) + mods = append(mods, azure.WithAdServicePrincipalId(GenerateAzureCommandState.AdServicePrincipalId)) + } + + // Check subscriptions + if !GenerateAzureCommandState.AllSubscriptions { + if len(GenerateAzureCommandState.SubscriptionIds) > 0 { + mods = append(mods, azure.WithSubscriptionIds(GenerateAzureCommandState.SubscriptionIds)) + } + } + + // Check management groups + if GenerateAzureCommandState.ManagementGroup { + mods = append(mods, azure.WithManagementGroupId(GenerateAzureCommandState.ManagementGroupId)) + } + + // Check storage account + if GenerateAzureCommandState.ExistingStorageAccount { + mods = append(mods, + azure.WithStorageAccountResourceGroup(GenerateAzureCommandState.StorageAccountResourceGroup)) + } + + // Create new struct + data := azure.NewTerraform( + GenerateAzureCommandState.Config, + GenerateAzureCommandState.ActivityLog, + GenerateAzureCommandState.EntraIdActivityLog, + GenerateAzureCommandState.CreateAdIntegration, + mods...) + + // Generate HCL for azure deployment + hcl, err := data.Generate() + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "failed to generate terraform code") + } + + // Write-out generated code to location specified + dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "azure") + if err != nil { + return err + } + + // Prompt to execute, if the command line flag has not been set + if !GenerateAzureCommandExtraState.TerraformApply { + err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Default: GenerateAzureCommandExtraState.TerraformApply, Message: QuestionRunTfPlan}, + Response: &GenerateAzureCommandExtraState.TerraformApply, + }) + + if err != nil { + return errors.Wrap(err, "failed to prompt for terraform execution") + } + } + + locationDir, _ := determineOutputDirPath(dirname, "azure") + if GenerateAzureCommandExtraState.TerraformApply { + // Execution pre-run check + err := executionPreRunChecks(dirname, locationDir, "azure") + if err != nil { + return err + } + } + + // Output where code was generated + if !GenerateAzureCommandExtraState.TerraformApply { + cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) + } + + return nil + }, + PreRunE: func(cmd *cobra.Command, _ []string) error { + + // Validate output location is OK if supplied + dirname, err := cmd.Flags().GetString("output") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateOutputLocation(dirname); err != nil { + return err + } + + // Validate Storage Location + storageLocation, err := cmd.Flags().GetString("location") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAzureLocation(storageLocation); storageLocation != "" && err != nil { + return err + } + + // Load any cached inputs if interactive + if cli.InteractiveMode() { + cachedOptions := &azure.GenerateAzureTfConfigurationArgs{} + iacParamsExpired := cli.ReadCachedAsset(CachedAzureAssetIacParams, &cachedOptions) + if iacParamsExpired { + cli.Log.Debug("loaded previously set values for Azure iac generation") + } + + extraState := &AzureGenerateCommandExtraState{} + extraStateParamsExpired := cli.ReadCachedAsset(CachedAzureAssetExtraState, &extraState) + if extraStateParamsExpired { + cli.Log.Debug("loaded previously set values for Azure iac generation (extra state)") + } + + // Determine if previously cached options exists; prompt user if they'd like to continue + answer := false + if !iacParamsExpired || !extraStateParamsExpired { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, + Response: &answer, + }); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + + // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs + // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get + // re-prompted. + if answer { + // Merge cached inputs to current options (current options win) + if err := mergo.Merge(GenerateAzureCommandState, cachedOptions); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + if err := mergo.Merge(GenerateAzureCommandExtraState, extraState); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + } + + // Collect and/or confirm parameters + err = promptAzureGenerate(GenerateAzureCommandState, GenerateAzureCommandExtraState) + if err != nil { + return errors.Wrap(err, "collecting/confirming parameters") + } + + return nil + }, + } +)