Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): CLI guided config support for creating Azure AD integration #1628

Merged
merged 14 commits into from
Aug 15, 2024
Merged
164 changes: 148 additions & 16 deletions cli/cmd/generate_azure.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"strconv"
"strings"
"time"

Expand All @@ -14,18 +15,26 @@

var (
// Define question text here so they can be reused in testing
QuestionAzureEnableConfig = "Enable Azure configuration integration?"
QuestionAzureConfigName = "Specify custom configuration integration name: (optional)"
QuestionEnableActivityLog = "Enable Azure Activity Log Integration?"
QuestionActivityLogName = "Specify custom Activity Log integration name: (optional)"
QuestionAddAzureSubscriptionID = "Set Azure Subscription ID?"
QuestionAzureSubscriptionID = "Specify the Azure Subscription ID to be used to provision " +
QuestionAzureEnableConfig = "Enable Azure configuration integration?"
QuestionAzureConfigName = "Specify custom configuration integration name: (optional)"
QuestionEnableActivityLog = "Enable Azure Activity Log Integration?"
QuestionActivityLogName = "Specify custom Activity Log integration name: (optional)"
QuestionEnableEntraIdActivityLog = "Enable Azure Entra ID Activity Log Integration?"
QuestionEntraIdActivityLogName = "Specify custom EntraID Activity Log integration name: (optional)"
QuestionAddAzureSubscriptionID = "Set Azure Subscription ID?"
QuestionAzureSubscriptionID = "Specify the Azure Subscription ID to be used to provision " +
"Lacework resources: (optional)"

QuestionAzureAnotherAdvancedOpt = "Configure another advanced integration option"
QuestionAzureConfigAdvanced = "Configure advanced integration options?"
QuestionAzureCustomizeOutputLocation = "Provide the location for the output to be written:"

// EntraID Activity Log
QuestionUseExistingEventHubNamespace = "Use an existing Event Hub Namespace?"
QuestionEventHubNamespaceName = "Specify existing Event Hub Namespace name"
QuestionEventHubLocation = "Specify Azure region where the event hub for logging will reside"
QuestionEventHubPartitionCount = "Specify the number of partitions in the event hub for logging"

// Active Directory
QuestionEnableAdIntegration = "Create Active Directory Integration?"
QuestionADApplicationPass = "Specify the password of an existing Active Directory application"
Expand Down Expand Up @@ -58,14 +67,15 @@
AzureUserIntegrationNames = "Customize integration name(s)"
AzureAdvancedOptLocation = "Customize output location (optional)"
AzureRegionStorage = "Customize Azure region for Storage Account (optional)"
AzureEntraIdAdvancedOpt = "Configure Entra ID activity log integration advanced options"

GenerateAzureCommandState = &azure.GenerateAzureTfConfigurationArgs{}
GenerateAzureCommandExtraState = &AzureGenerateCommandExtraState{}
CachedAzureAssetIacParams = "iac-azure-generate-params"
CachedAzureAssetExtraState = "iac-azure-extra-state"

// List of valid Azure Storage locations
validStorageLocations = map[string]bool{
validAzureLocations = map[string]bool{
"East US": true,
"East US 2": true,
"South Central US": true,
Expand Down Expand Up @@ -183,6 +193,11 @@
azure.WithStorageLocation(GenerateAzureCommandState.StorageLocation),
azure.WithActivityLogIntegrationName(GenerateAzureCommandState.ActivityLogIntegrationName),
azure.WithConfigIntegrationName(GenerateAzureCommandState.ConfigIntegrationName),
azure.WithEntraIdActivityLogIntegrationName(GenerateAzureCommandState.EntraIdIntegrationName),
azure.WithExistingEventHubNamespace(GenerateAzureCommandState.ExistingEventHubNamespace),
azure.WithEventHubNamespaceName(GenerateAzureCommandState.EventHubNamespaceName),
azure.WithEventHubLocation(GenerateAzureCommandState.EventHubLocation),
azure.WithEventHubPartitionCount(GenerateAzureCommandState.EventHubPartitionCount),
}

// Check if AD Creation is required, need to set values for current integration
Expand Down Expand Up @@ -214,6 +229,7 @@
data := azure.NewTerraform(
GenerateAzureCommandState.Config,
GenerateAzureCommandState.ActivityLog,
GenerateAzureCommandState.EntraIdActivityLog,
GenerateAzureCommandState.CreateAdIntegration,
mods...)

Expand Down Expand Up @@ -275,7 +291,7 @@
if err != nil {
return errors.Wrap(err, "failed to load command flags")
}
if err := validateStorageLocation(storageLocation); storageLocation != "" && err != nil {
if err := validateAzureLocation(storageLocation); storageLocation != "" && err != nil {
return err
}

Expand Down Expand Up @@ -346,9 +362,9 @@
}
}

func validateStorageLocation(location string) error {
if !validStorageLocations[location] {
return errors.New("invalid storage location supplied")
func validateAzureLocation(location string) error {
if !validAzureLocations[location] {
return errors.New("invalid Azure region prvovided")
}
return nil
}
Expand All @@ -359,14 +375,26 @@
&GenerateAzureCommandState.ActivityLog,
"activity_log",
false,
"enable active log integration")
"enable activity log integration")

generateAzureTfCommand.PersistentFlags().StringVar(
&GenerateAzureCommandState.ActivityLogIntegrationName,
"activity_log_integration_name",
"",
"specify a custom activity log integration name")

generateAzureTfCommand.PersistentFlags().BoolVar(
&GenerateAzureCommandState.EntraIdActivityLog,
"entra_id_activity_log",
false,
"enable Entra ID activity log integration")

generateAzureTfCommand.PersistentFlags().StringVar(
&GenerateAzureCommandState.EntraIdIntegrationName,
"entra_id_activity_log_integration_name",
"",
"specify a custom Entra ID activity log integration name")

generateAzureTfCommand.PersistentFlags().BoolVar(
&GenerateAzureCommandState.Config,
"configuration",
Expand Down Expand Up @@ -409,6 +437,30 @@
false,
"use existing storage account")

generateAzureTfCommand.PersistentFlags().BoolVar(
&GenerateAzureCommandState.ExistingEventHubNamespace,
"existing_event_hub_namespace",
false,
"use existing Event Hub Namespace")

generateAzureTfCommand.PersistentFlags().StringVar(
&GenerateAzureCommandState.EventHubNamespaceName,
"event_hub_namespace",
"",
"specify the name of the Event Hub Namespace")

generateAzureTfCommand.PersistentFlags().StringVar(
&GenerateAzureCommandState.EventHubLocation,
"event_hub_location",
"",
"specify the location where the Event Hub for logging will reside")

generateAzureTfCommand.PersistentFlags().IntVar(
&GenerateAzureCommandState.EventHubPartitionCount,
"event_hub_partition_count",
1,
"specify the number of partitions for the Event Hub")

generateAzureTfCommand.PersistentFlags().StringVar(
&GenerateAzureCommandState.StorageAccountName,
"storage_account_name",
Expand Down Expand Up @@ -492,6 +544,11 @@
Checks: []*bool{&config.ActivityLog},
Response: &config.ActivityLogIntegrationName,
},
{
Prompt: &survey.Input{Message: QuestionEntraIdActivityLogName, Default: config.EntraIdIntegrationName},
Checks: []*bool{&config.EntraIdActivityLog},
Response: &config.EntraIdIntegrationName,
},
}); err != nil {
return err
}
Expand Down Expand Up @@ -526,6 +583,51 @@
return nil
}

// Similar to the above, prompt for event hub questions starting by asking for existing event hub namespace, if answer is no, then ask for event hub location. If answer is yes, then ask for namespace name.
func promptAzureEntraIdActivityLogQuestions(config *azure.GenerateAzureTfConfigurationArgs) error {
if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Prompt: &survey.Confirm{Message: QuestionUseExistingEventHubNamespace, Default: config.ExistingEventHubNamespace},
Response: &config.ExistingEventHubNamespace,
},
}); err != nil {
return err
}

if !config.ExistingEventHubNamespace {
if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Prompt: &survey.Input{Message: QuestionEventHubLocation, Default: config.EventHubLocation},
Required: true,
Response: &config.EventHubLocation,
},
}); err != nil {
return err
}
} else {
if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Prompt: &survey.Input{Message: QuestionEventHubNamespaceName, Default: config.EventHubNamespaceName},
Required: true,
Response: &config.EventHubNamespaceName,
},
}); err != nil {
return err
}
}

if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Prompt: &survey.Input{Message: QuestionEventHubPartitionCount, Default: strconv.Itoa(config.EventHubPartitionCount)},
Response: &config.EventHubPartitionCount,
},
}); err != nil {
return err
}

return nil
}

func promptAzureSubscriptionQuestions(config *azure.GenerateAzureTfConfigurationArgs) error {

if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
Expand Down Expand Up @@ -609,7 +711,7 @@
return nil
}

func promptCustomizeAzureLoggingRegion(config *azure.GenerateAzureTfConfigurationArgs) error {
func promptCustomizeAzureStorageLoggingRegion(config *azure.GenerateAzureTfConfigurationArgs) error {
var region string
if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Expand All @@ -619,13 +721,30 @@
}); err != nil {
return err
}
if err := validateStorageLocation(region); err != nil {
if err := validateAzureLocation(region); err != nil {
return err
}
config.StorageLocation = region
return nil
}

func promptCustomizeAzureEventHubLoggingRegion(config *azure.GenerateAzureTfConfigurationArgs) error {

Check failure on line 731 in cli/cmd/generate_azure.go

View workflow job for this annotation

GitHub Actions / run-linting-and-unit-tests

func `promptCustomizeAzureEventHubLoggingRegion` is unused (unused)
var region string
if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{
{
Prompt: &survey.Input{Message: QuestionEventHubLocation, Default: config.EventHubLocation},
Response: &region,
},
}); err != nil {
return err
}
if err := validateAzureLocation(region); err != nil {
return err
}
config.EventHubLocation = region
return nil
}

func askAzureSubscriptionID(config *azure.GenerateAzureTfConfigurationArgs) error {
var addSubscription bool

Expand Down Expand Up @@ -678,6 +797,11 @@
options = append(options, AzureManagmentGroup)
}

// Only show Entra ID options in the case of Entra ID integration
if config.EntraIdActivityLog {
options = append(options, AzureEntraIdAdvancedOpt)
}

options = append(options, AzureAdvancedOptLocation, AzureAdvancedOptDone)
if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{
Prompt: &survey.Select{
Expand All @@ -699,6 +823,10 @@
if err := promptAzureStorageAccountQuestions(config); err != nil {
return err
}
case AzureEntraIdAdvancedOpt:
if err := promptAzureEntraIdActivityLogQuestions(config); err != nil {
return err
}
case AzureSubscriptions:
if err := promptAzureSubscriptionQuestions(config); err != nil {
return err
Expand All @@ -712,7 +840,7 @@
return err
}
case AzureRegionStorage:
if err := promptCustomizeAzureLoggingRegion(config); err != nil {
if err := promptCustomizeAzureStorageLoggingRegion(config); err != nil {
return err
}
case AzureAdvancedOptLocation:
Expand Down Expand Up @@ -784,12 +912,16 @@
Prompt: &survey.Confirm{Message: QuestionEnableAdIntegration, Default: config.CreateAdIntegration},
Response: &config.CreateAdIntegration,
},
{
Prompt: &survey.Confirm{Message: QuestionEnableEntraIdActivityLog, Default: config.EntraIdActivityLog},
Response: &config.EntraIdActivityLog,
},
}); err != nil {
return err
}

// Validate one of config or activity log was enabled; otherwise error out
if !config.Config && !config.ActivityLog {
if !config.Config && !config.ActivityLog && !config.EntraIdActivityLog {
return errors.New("must enable activity log or config")
}

Expand Down
12 changes: 6 additions & 6 deletions cli/cmd/generate_azure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,20 @@ func TestMissingValidEntity(t *testing.T) {
}

func TestValidStorageLocations(t *testing.T) {
err := validateStorageLocation("East US")
err := validateAzureLocation("East US")
assert.Nil(t, err)
err = validateStorageLocation("Brazil Southeast")
err = validateAzureLocation("Brazil Southeast")
assert.Nil(t, err)

}

func TestInvalidStorageLocations(t *testing.T) {
err := validateStorageLocation("Mars")
err := validateAzureLocation("Mars")
assert.Error(t, err)
assert.Equal(t, "invalid storage location supplied", err.Error())
err = validateStorageLocation("Jupiter")
assert.Equal(t, "invalid Azure region prvovided", err.Error())
err = validateAzureLocation("Jupiter")
assert.Error(t, err)
assert.Equal(t, "invalid storage location supplied", err.Error())
assert.Equal(t, "invalid Azure region prvovided", err.Error())
}

func TestAzureGenerationCache(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ Flags:
--apply run terraform apply for the generated hcl
--configuration enable configuration integration
--configuration_name string specify a custom configuration integration name
--entra_id_activity_log enable Entra ID activity log integration
--entra_id_activity_log_integration_name specify a custom Entra ID activity log integration name
--event_hub_location specify the location where the Event Hub for logging will reside
--event_hub_namespace specify the name of the Event Hub Namespace
--event_hub_partition_count specify the number of partitions for the Event Hub
--existing_event_hub_namespace use existing Event Hub Namespace
--existing_storage use existing storage account
-h, --help help for azure
--location string specify azure region where storage account logging resides
Expand Down
Loading
Loading