From 5e7970f1d456be1d8879641e16968a87adaf0e2f Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Tue, 8 Sep 2026 14:00:37 +0530 Subject: [PATCH 1/8] refactor(network-acl)!: remove redundant rule flags - Remove the 12 rule flags (--action, --redirect-uri, --scope, --asns, --country-codes, --subdivision-codes, --ipv4-cidrs, --ipv6-cidrs, --ja3-fingerprints, --ja4-fingerprints, --user-agents, --auth0-managed) from create and update; they were never read non-interactively, where the rule is built only from --rule. Interactive prompting for these values is unchanged. - Register --description, --active, --priority and --rule through the package-level Flag structs (RegisterString/RegisterInt and the U variants) and switch flag checks to IsSet, dropping raw cmd.Flags() calls. - Collapse the two update branches that patch from flags into one guard placed before the ACL read, removing a wasted API call when flags are provided in interactive mode. - Extract validateNetworkACLDescription and reuse it across create and update. - Regenerate command docs. BREAKING CHANGE: the 12 removed flags now fail with an "unknown flag" error. Pass the full rule as JSON via --rule to configure it non-interactively. --- docs/auth0_network-acl_create.md | 24 +-- docs/auth0_network-acl_update.md | 24 +-- internal/cli/network_acl.go | 339 ++++++++++--------------------- 3 files changed, 124 insertions(+), 263 deletions(-) diff --git a/docs/auth0_network-acl_create.md b/docs/auth0_network-acl_create.md index 234eaa583..3442018ea 100644 --- a/docs/auth0_network-acl_create.md +++ b/docs/auth0_network-acl_create.md @@ -34,24 +34,12 @@ auth0 network-acl create [flags] ## Flags ``` - --action string Action for the rule (block, allow, log, redirect) - --active string Whether the network ACL is active (required, 'true' or 'false') - --asns ints Comma-separated list of ASNs to match (Eg. 64496,64497,64498) - --auth0-managed strings Comma-separated list of Auth0-curated blocklists to match (Eg. auth0.icloud_relay_proxy,auth0.low_reputation). (EA only). - --country-codes strings Comma-separated list of country codes to match (Eg. US,CA,MX) - -d, --description string Description of the network ACL (required) - --ipv4-cidrs strings Comma-separated list of IPv4 CIDR ranges (Eg. 192.168.1.0/24,10.0.0.0/8) - --ipv6-cidrs strings Comma-separated list of IPv6 CIDR ranges (Eg. 2001:db8::/32,2001:db8:1234::/48) - --ja3-fingerprints strings Comma-separated list of JA3 fingerprints to match (Eg. deadbeef,cafebabe) - --ja4-fingerprints strings Comma-separated list of JA4 fingerprints to match (Eg. t13d1516h2_8daaf6152771) - --json Output in json format. - --json-compact Output in compact json format. - -p, --priority int Priority of the network ACL (required) - --redirect-uri string URI to redirect to when action is redirect - --rule string Network ACL rule configuration in JSON format (required for non-interactive mode) - --scope string Scope of the rule (management, authentication, tenant) - --subdivision-codes strings Comma-separated list of subdivision codes to match (Eg. US-NY,US-CA) - --user-agents strings Comma-separated list of user agents to match (Eg. badbot/*,malicious/*) + --active string Whether the network ACL is active ('true' or 'false'). + -d, --description string Description of the network ACL (Eg. "Block suspicious IPs"). + --json Output in json format. + --json-compact Output in compact json format. + -p, --priority int Priority of the network ACL (Eg. 5). + --rule string Network ACL rule configuration in JSON format (required for non-interactive mode). ``` diff --git a/docs/auth0_network-acl_update.md b/docs/auth0_network-acl_update.md index 7081a4966..1461b2075 100644 --- a/docs/auth0_network-acl_update.md +++ b/docs/auth0_network-acl_update.md @@ -19,7 +19,7 @@ auth0 network-acl update [flags] ``` auth0 network-acl update - auth0 network-acl update --priority 5 + auth0 network-acl update --priority 5 auth0 network-acl update --active true auth0 network-acl update --description "Updated description" auth0 network-acl update --rule '{"action":{"block":true},"scope":"tenant","match":{"ipv4_cidrs":["192.168.1.0/24"]}}' @@ -34,23 +34,11 @@ auth0 network-acl update [flags] ## Flags ``` - --action string Action for the rule (block, allow, log, redirect) - --active string Whether the network ACL is active ('true' or 'false') - --asns ints Comma-separated list of ASNs to match (Eg. 64496,64497,64498) - --auth0-managed strings Comma-separated list of Auth0-curated blocklists to match (Eg. auth0.icloud_relay_proxy,auth0.low_reputation). (EA only). - --country-codes strings Comma-separated list of country codes to match (Eg. US,CA,MX) - -d, --description string Description of the network ACL - --ipv4-cidrs strings Comma-separated list of IPv4 CIDR ranges (Eg. 192.168.1.0/24,10.0.0.0/8) - --ipv6-cidrs strings Comma-separated list of IPv6 CIDR ranges (Eg. 2001:db8::/32,2001:db8:1234::/48) - --ja3-fingerprints strings Comma-separated list of JA3 fingerprints to match (Eg. deadbeef,cafebabe) - --ja4-fingerprints strings Comma-separated list of JA4 fingerprints to match (Eg. t13d1516h2_8daaf6152771) - --json Output in JSON format - -p, --priority int Priority of the network ACL (default 1) - --redirect-uri string URI to redirect to when action is redirect - --rule string Network ACL rule configuration in JSON format - --scope string Scope of the rule (management, authentication, tenant) - --subdivision-codes strings Comma-separated list of subdivision codes to match (Eg. US-NY,US-CA) - --user-agents strings Comma-separated list of user agents to match (Eg. badbot/*,malicious/*) + --active string Whether the network ACL is active ('true' or 'false'). + -d, --description string Description of the network ACL (Eg. "Block suspicious IPs"). + --json Output in JSON format + -p, --priority int Priority of the network ACL (Eg. 5). (default 1) + --rule string Network ACL rule configuration in JSON format (required for non-interactive mode). ``` diff --git a/internal/cli/network_acl.go b/internal/cli/network_acl.go index d406eaabc..d35b66fc9 100644 --- a/internal/cli/network_acl.go +++ b/internal/cli/network_acl.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "strconv" "strings" "github.com/auth0/go-auth0/management" @@ -22,23 +23,33 @@ var ( } networkACLDescription = Flag{ - Name: "Description", - LongForm: "description", - ShortForm: "d", - Help: "Description of the network ACL (Eg. \"Block suspicious IPs\", required)", + Name: "Description", + LongForm: "description", + ShortForm: "d", + Help: "Description of the network ACL (Eg. \"Block suspicious IPs\").", + IsRequired: true, } networkACLActive = Flag{ - Name: "Active", - LongForm: "active", - Help: "Whether the network ACL is active (Eg. true, default: false)", + Name: "Active", + LongForm: "active", + Help: "Whether the network ACL is active ('true' or 'false').", + IsRequired: true, } networkACLPriority = Flag{ - Name: "Priority", - LongForm: "priority", - ShortForm: "p", - Help: "Priority of the network ACL in number(Eg. 5)", + Name: "Priority", + LongForm: "priority", + ShortForm: "p", + Help: "Priority of the network ACL (Eg. 5).", + IsRequired: true, + } + + networkACLRule = Flag{ + Name: "Rule", + LongForm: "rule", + Help: "Network ACL rule configuration in JSON format (required for non-interactive mode).", + IsRequired: true, } networkACLRuleAction = Flag{ @@ -108,56 +119,50 @@ var ( } ) +// networkACLBasicInputs holds the flag-driven fields shared by create and update. +type networkACLBasicInputs struct { + ID string + Description string + Active bool + ActiveStr string + Priority int + RuleJSON string +} + +// validateNetworkACLDescription ensures the description is non-empty and within the API length limit. +func validateNetworkACLDescription(description string) error { + if len(description) == 0 { + return fmt.Errorf("description cannot be empty") + } + if len(description) > 255 { + return fmt.Errorf("description cannot exceed 255 characters") + } + return nil +} + // validateAndSetBasicFields handles the common validation and patch building logic for basic fields. -func validateAndSetBasicFields(inputs *struct { - ID string - Description string - Active bool - ActiveStr string - Priority int - RuleJSON string - Action string - RedirectURI string - Scope string - ASNs []int - CountryCodes []string - SubdivCodes []string - IPv4CIDRs []string - IPv6CIDRs []string - JA3 []string - JA4 []string - UserAgents []string - Auth0Managed []string - MatchRule bool - NoMatchRule bool -}, patch *management.NetworkACL, cmd *cobra.Command) error { - if cmd.Flags().Changed("description") { - if len(inputs.Description) > 255 { - return fmt.Errorf("description cannot exceed 255 characters") - } - if len(inputs.Description) == 0 { - return fmt.Errorf("description cannot be empty") +func validateAndSetBasicFields(inputs *networkACLBasicInputs, patch *management.NetworkACL, cmd *cobra.Command) error { + if networkACLDescription.IsSet(cmd) { + if err := validateNetworkACLDescription(inputs.Description); err != nil { + return err } patch.Description = &inputs.Description } - if cmd.Flags().Changed("active") { - switch inputs.ActiveStr { - case "true": - inputs.Active = true - case "false": - inputs.Active = false - default: + if networkACLActive.IsSet(cmd) { + active, err := strconv.ParseBool(inputs.ActiveStr) + if err != nil { return fmt.Errorf("--active must be either 'true' or 'false', got %q", inputs.ActiveStr) } + inputs.Active = active patch.Active = &inputs.Active } - if cmd.Flags().Changed("priority") { + if networkACLPriority.IsSet(cmd) { patch.Priority = &inputs.Priority } - if cmd.Flags().Changed("rule") { + if networkACLRule.IsSet(cmd) { var rule management.NetworkACLRule if err := json.Unmarshal([]byte(inputs.RuleJSON), &rule); err != nil { return fmt.Errorf("invalid rule JSON: %w", err) @@ -176,11 +181,10 @@ func applyNetworkACLPatch(ctx context.Context, cli *cli, id string, patch *manag return fmt.Errorf("failed to update network ACL with ID %q: %w", id, err) } - cli.renderer.NetworkACLUpdate(patch) - return nil + return cli.renderer.NetworkACLUpdate(patch) } -func selectNetworkACLParams(cmd *cobra.Command) (map[string]bool, error) { +func selectNetworkACLParams() (map[string]bool, error) { options := []string{ "ASNs", "Country Codes", @@ -394,7 +398,7 @@ func promptForRuleDetails(cmd *cobra.Command, cli *cli, defaults *ruleDefaults, } // Select which parameters to provide. - selectedParams, err := selectNetworkACLParams(cmd) + selectedParams, err := selectNetworkACLParams() if err != nil { return nil, err } @@ -636,26 +640,7 @@ func showNetworkACLCmd(cli *cli) *cobra.Command { } func createNetworkACLCmd(cli *cli) *cobra.Command { - var inputs struct { - Description string - Active bool - ActiveStr string // Added for handling --active true/false. - Priority int - RuleJSON string - Action string - RedirectURI string - ASNs []int - CountryCodes []string - SubdivCodes []string - IPv4CIDRs []string - IPv6CIDRs []string - JA3 []string - JA4 []string - UserAgents []string - Auth0Managed []string - Scope string - isMatchRule bool - } + var inputs networkACLBasicInputs cmd := &cobra.Command{ Use: "create", @@ -676,66 +661,31 @@ The --rule parameter is required and must contain a valid JSON object with actio auth0 network-acl create -d "Curated Blocklist" -p 6 --active true --rule '{"action":{"log":true},"scope":"tenant","not_match":{"auth0_managed":["auth0.vpn","auth0.proxy"]}}' `, RunE: func(cmd *cobra.Command, args []string) error { - // Check if we're in non-interactive mode (flags provided) but rule JSON is missing. - if !canPrompt(cmd) && !cmd.Flags().Changed("rule") { - return fmt.Errorf("the --rule parameter is required for non-interactive mode. Please provide a valid JSON rule") - } - - // Parse the active flag if provided. - if cmd.Flags().Changed("active") { - switch inputs.ActiveStr { - case "true": - inputs.Active = true - case "false": - inputs.Active = false - default: - return fmt.Errorf("--active must be either 'true' or 'false', got %q", inputs.ActiveStr) - } - } - // Check if rule JSON was provided. - if cmd.Flags().Changed("rule") { - // Parse the rule JSON. - var rule map[string]interface{} - if err := json.Unmarshal([]byte(inputs.RuleJSON), &rule); err != nil { + // Validate --rule JSON up front, before prompting for other fields, so + // an invalid rule fails immediately instead of after the prompts. + var rule *management.NetworkACLRule + if networkACLRule.IsSet(cmd) { + rule = &management.NetworkACLRule{} + if err := json.Unmarshal([]byte(inputs.RuleJSON), rule); err != nil { return fmt.Errorf("invalid rule JSON: %w", err) } - - // Create the network ACL with the provided rule. - acl := &management.NetworkACL{ - Description: &inputs.Description, - Active: &inputs.Active, - Priority: &inputs.Priority, - } - - // Convert the rule map to the appropriate structure. - if err := json.Unmarshal([]byte(inputs.RuleJSON), &acl.Rule); err != nil { - return fmt.Errorf("failed to parse rule JSON: %w", err) - } - - if err := ansi.Waiting(func() error { - return cli.api.NetworkACL.Create(cmd.Context(), acl) - }); err != nil { - return fmt.Errorf("failed to create network ACL: %w", err) - } - - cli.renderer.NetworkACLCreate(acl) - return nil } - // Interactive or flag-based creation. if err := networkACLDescription.Ask(cmd, &inputs.Description, nil); err != nil { return err } - - if len(inputs.Description) > 255 { - return fmt.Errorf("description cannot exceed 255 characters") + if err := validateNetworkACLDescription(inputs.Description); err != nil { + return err } - if len(inputs.Description) == 0 { - return fmt.Errorf("description is required") + if networkACLActive.IsSet(cmd) { + active, err := strconv.ParseBool(inputs.ActiveStr) + if err != nil { + return fmt.Errorf("--active must be either 'true' or 'false', got %q", inputs.ActiveStr) + } + inputs.Active = active } - defaultStatus := false if err := networkACLActive.AskBool(cmd, &inputs.Active, &defaultStatus); err != nil { return err @@ -745,28 +695,30 @@ The --rule parameter is required and must contain a valid JSON object with actio return err } - // Use helper functions for rule configuration. - defaults := &ruleDefaults{ - Scope: "tenant", - Action: "log", - } - - ruleInputs, err := promptForRuleDetails(cmd, cli, defaults, false) - if err != nil { - return err - } - - // Build the network ACL. acl := &management.NetworkACL{ Description: &inputs.Description, Active: &inputs.Active, Priority: &inputs.Priority, } - // Build the rule. - acl.Rule, err = buildNetworkACLRule(ruleInputs) - if err != nil { - return err + // Use the rule parsed from --rule when provided, otherwise prompt for it. + if rule != nil { + acl.Rule = rule + } else { + defaults := &ruleDefaults{ + Scope: "tenant", + Action: "log", + } + + ruleInputs, err := promptForRuleDetails(cmd, cli, defaults, false) + if err != nil { + return err + } + + acl.Rule, err = buildNetworkACLRule(ruleInputs) + if err != nil { + return err + } } if err := ansi.Waiting(func() error { @@ -775,63 +727,22 @@ The --rule parameter is required and must contain a valid JSON object with actio return fmt.Errorf("failed to create network ACL: %w", err) } - cli.renderer.NetworkACLCreate(acl) - return nil + return cli.renderer.NetworkACLCreate(acl) }, } cmd.Flags().BoolVar(&cli.json, "json", false, "Output in json format.") cmd.Flags().BoolVar(&cli.jsonCompact, "json-compact", false, "Output in compact json format.") - cmd.Flags().StringVarP(&inputs.Description, "description", "d", "", "Description of the network ACL (required)") - cmd.Flags().StringVar(&inputs.ActiveStr, "active", "", "Whether the network ACL is active (required, 'true' or 'false')") - cmd.Flags().IntVarP(&inputs.Priority, "priority", "p", 0, "Priority of the network ACL (required)") - cmd.Flags().StringVar(&inputs.RuleJSON, "rule", "", "Network ACL rule configuration in JSON format (required for non-interactive mode)") - cmd.Flags().StringVar(&inputs.Action, "action", "", "Action for the rule (block, allow, log, redirect)") - cmd.Flags().StringVar(&inputs.RedirectURI, "redirect-uri", "", "URI to redirect to when action is redirect") - cmd.Flags().StringVar(&inputs.Scope, "scope", "", "Scope of the rule (management, authentication, tenant)") - - // Register the string slice flags. - networkACLASNs.RegisterIntSlice(cmd, &inputs.ASNs, nil) - networkACLCountryCodes.RegisterStringSlice(cmd, &inputs.CountryCodes, nil) - networkACLSubdivisionCodes.RegisterStringSlice(cmd, &inputs.SubdivCodes, nil) - networkACLIPv4CIDRs.RegisterStringSlice(cmd, &inputs.IPv4CIDRs, nil) - networkACLIPv6CIDRs.RegisterStringSlice(cmd, &inputs.IPv6CIDRs, nil) - networkACLJA3Fingerprints.RegisterStringSlice(cmd, &inputs.JA3, nil) - networkACLJA4Fingerprints.RegisterStringSlice(cmd, &inputs.JA4, nil) - networkACLUserAgents.RegisterStringSlice(cmd, &inputs.UserAgents, nil) - networkACLAuth0Managed.RegisterStringSlice(cmd, &inputs.Auth0Managed, nil) - - // These flags must be passed in non-interactive mode. - cmd.MarkFlagRequired("description") - cmd.MarkFlagRequired("active") - cmd.MarkFlagRequired("priority") - cmd.MarkFlagRequired("rule") + networkACLDescription.RegisterString(cmd, &inputs.Description, "") + networkACLActive.RegisterString(cmd, &inputs.ActiveStr, "") + networkACLPriority.RegisterInt(cmd, &inputs.Priority, 0) + networkACLRule.RegisterString(cmd, &inputs.RuleJSON, "") + return cmd } func updateNetworkACLCmd(cli *cli) *cobra.Command { - var inputs struct { - ID string - Description string - Active bool - ActiveStr string - Priority int - RuleJSON string - Action string - RedirectURI string - Scope string - ASNs []int - CountryCodes []string - SubdivCodes []string - IPv4CIDRs []string - IPv6CIDRs []string - JA3 []string - JA4 []string - UserAgents []string - Auth0Managed []string - MatchRule bool - NoMatchRule bool - } + var inputs networkACLBasicInputs cmd := &cobra.Command{ Use: "update", @@ -842,7 +753,7 @@ To update interactively, use "auth0 network-acl update" with no arguments. To update non-interactively, supply the description, active, priority, and rule through flags. `, Example: ` auth0 network-acl update - auth0 network-acl update --priority 5 + auth0 network-acl update --priority 5 auth0 network-acl update --active true auth0 network-acl update --description "Updated description" auth0 network-acl update --rule '{"action":{"block":true},"scope":"tenant","match":{"ipv4_cidrs":["192.168.1.0/24"]}}' @@ -862,8 +773,8 @@ To update non-interactively, supply the description, active, priority, and rule } // Check if we're in non-interactive mode (any flags provided). - flagsProvided := cmd.Flags().Changed("description") || cmd.Flags().Changed("active") || - cmd.Flags().Changed("priority") || cmd.Flags().Changed("rule") + flagsProvided := networkACLDescription.IsSet(cmd) || networkACLActive.IsSet(cmd) || + networkACLPriority.IsSet(cmd) || networkACLRule.IsSet(cmd) if !canPrompt(cmd) && !flagsProvided { return fmt.Errorf("in non-interactive mode, at least one field must be specified to update") @@ -872,18 +783,18 @@ To update non-interactively, supply the description, active, priority, and rule // Build patch object with only the fields that should be updated. patch := &management.NetworkACL{} - // Non-interactive mode with flags only - no need to read current ACL. - if !canPrompt(cmd) && flagsProvided { - // Validate and set basic fields from flags. + // When flags are provided, update only those fields. This applies in both + // interactive and non-interactive mode, so there is no need to read the + // current ACL first. + if flagsProvided { if err := validateAndSetBasicFields(&inputs, patch, cmd); err != nil { return err } - // Apply the patch. return applyNetworkACLPatch(cmd.Context(), cli, inputs.ID, patch) } - // Interactive mode - read current ACL first for defaults. + // Full interactive mode - read the current ACL to use its values as defaults. var currentACL *management.NetworkACL err := ansi.Waiting(func() (err error) { currentACL, err = cli.api.NetworkACL.Read(cmd.Context(), inputs.ID) @@ -893,20 +804,11 @@ To update non-interactively, supply the description, active, priority, and rule return fmt.Errorf("failed to get network ACL with ID %q: %w", inputs.ID, err) } - // If some flags were provided in interactive mode, only update those fields. - if canPrompt(cmd) && flagsProvided { - // Only update the fields that were specified via flags. - if err := validateAndSetBasicFields(&inputs, patch, cmd); err != nil { - return err - } - - // Apply the patch. - return applyNetworkACLPatch(cmd.Context(), cli, inputs.ID, patch) + // Use current values as defaults for interactive prompts. + if err := networkACLDescription.Ask(cmd, &inputs.Description, currentACL.Description); err != nil { + return err } - - // Full Interactive mode, use current values as defaults for interactive prompts. - currentDescriptionStr := *currentACL.Description - if err := networkACLDescription.Ask(cmd, &inputs.Description, ¤tDescriptionStr); err != nil { + if err := validateNetworkACLDescription(inputs.Description); err != nil { return err } patch.Description = &inputs.Description @@ -920,17 +822,14 @@ To update non-interactively, supply the description, active, priority, and rule if err := networkACLPriority.AskInt(cmd, &inputs.Priority, ¤tPriorityStr); err != nil { return err } - patch.Priority = &inputs.Priority // Use helper functions for rule configuration. defaults := extractCurrentRuleDefaults(currentACL) - ruleInputs, err := promptForRuleDetails(cmd, cli, defaults, true) if err != nil { return err } - // Build the rule for the patch. patch.Rule, err = buildNetworkACLRule(ruleInputs) if err != nil { @@ -943,24 +842,10 @@ To update non-interactively, supply the description, active, priority, and rule } cmd.Flags().BoolVar(&cli.json, "json", false, "Output in JSON format") - cmd.Flags().StringVarP(&inputs.Description, "description", "d", "", "Description of the network ACL") - cmd.Flags().StringVar(&inputs.ActiveStr, "active", "", "Whether the network ACL is active ('true' or 'false')") - cmd.Flags().IntVarP(&inputs.Priority, "priority", "p", 1, "Priority of the network ACL") - cmd.Flags().StringVar(&inputs.RuleJSON, "rule", "", "Network ACL rule configuration in JSON format") - cmd.Flags().StringVar(&inputs.Action, "action", "", "Action for the rule (block, allow, log, redirect)") - cmd.Flags().StringVar(&inputs.RedirectURI, "redirect-uri", "", "URI to redirect to when action is redirect") - cmd.Flags().StringVar(&inputs.Scope, "scope", "", "Scope of the rule (management, authentication, tenant)") - - // Register the string slice flags. - networkACLASNs.RegisterIntSlice(cmd, &inputs.ASNs, nil) - networkACLCountryCodes.RegisterStringSlice(cmd, &inputs.CountryCodes, nil) - networkACLSubdivisionCodes.RegisterStringSlice(cmd, &inputs.SubdivCodes, nil) - networkACLIPv4CIDRs.RegisterStringSlice(cmd, &inputs.IPv4CIDRs, nil) - networkACLIPv6CIDRs.RegisterStringSlice(cmd, &inputs.IPv6CIDRs, nil) - networkACLJA3Fingerprints.RegisterStringSlice(cmd, &inputs.JA3, nil) - networkACLJA4Fingerprints.RegisterStringSlice(cmd, &inputs.JA4, nil) - networkACLUserAgents.RegisterStringSlice(cmd, &inputs.UserAgents, nil) - networkACLAuth0Managed.RegisterStringSlice(cmd, &inputs.Auth0Managed, nil) + networkACLDescription.RegisterStringU(cmd, &inputs.Description, "") + networkACLActive.RegisterStringU(cmd, &inputs.ActiveStr, "") + networkACLPriority.RegisterIntU(cmd, &inputs.Priority, 1) + networkACLRule.RegisterStringU(cmd, &inputs.RuleJSON, "") return cmd } From 013420b54d5a399d06a0c9c2f3744683a500fdd1 Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Tue, 8 Sep 2026 14:07:20 +0530 Subject: [PATCH 2/8] chore: lint fix --- internal/cli/network_acl.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/cli/network_acl.go b/internal/cli/network_acl.go index d35b66fc9..173329032 100644 --- a/internal/cli/network_acl.go +++ b/internal/cli/network_acl.go @@ -661,7 +661,6 @@ The --rule parameter is required and must contain a valid JSON object with actio auth0 network-acl create -d "Curated Blocklist" -p 6 --active true --rule '{"action":{"log":true},"scope":"tenant","not_match":{"auth0_managed":["auth0.vpn","auth0.proxy"]}}' `, RunE: func(cmd *cobra.Command, args []string) error { - // Validate --rule JSON up front, before prompting for other fields, so // an invalid rule fails immediately instead of after the prompts. var rule *management.NetworkACLRule From e735a349334a60841b79a26d64fc8f00448e78dc Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Wed, 9 Sep 2026 12:31:28 +0530 Subject: [PATCH 3/8] refactor(network-acl): deprecate rule flags instead of removing them - Re-add the 12 per-criteria rule flags (--action, --redirect-uri, --scope, --asns, --country-codes, --subdivision-codes, --ipv4-cidrs, --ipv6-cidrs, --ja3-fingerprints, --ja4-fingerprints, --user-agents, --auth0-managed) to create and update, restoring backward compatibility so existing scripts no longer fail with an "unknown flag" error. - Mark each flag deprecated via a new Flag.Deprecate helper, which wraps cobra's MarkDeprecated and panics with the flag name on failure. Using a flag now prints guidance to pass the rule as JSON via --rule, and the flag is hidden from help and generated docs. - The flags are accepted but ignored when building the rule, matching their prior non-interactive behavior; the rule is still built solely from --rule or the interactive prompts. --- internal/cli/flags.go | 8 ++++++ internal/cli/network_acl.go | 57 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/internal/cli/flags.go b/internal/cli/flags.go index ca10593a2..ec157a58f 100644 --- a/internal/cli/flags.go +++ b/internal/cli/flags.go @@ -173,6 +173,14 @@ func (f *Flag) RegisterIntSlice(cmd *cobra.Command, value *[]int, defaultValue [ registerIntSlice(cmd, f, value, defaultValue, false) } +// Deprecate marks an already-registered flag as deprecated, +// prints message and hides it from help and generated docs. +func (f *Flag) Deprecate(cmd *cobra.Command, message string) { + if err := cmd.Flags().MarkDeprecated(f.LongForm, message); err != nil { + panic(auth0.Error(err, fmt.Sprintf("failed to deprecate flag %q", f.LongForm))) + } +} + func (f *Flag) AskIntSlice(cmd *cobra.Command, value *[]int, defaultValue *[]int) error { if shouldAsk(cmd, f, false) { return askIntSlice(f, value, defaultValue) diff --git a/internal/cli/network_acl.go b/internal/cli/network_acl.go index 173329032..fbd3d5781 100644 --- a/internal/cli/network_acl.go +++ b/internal/cli/network_acl.go @@ -64,6 +64,12 @@ var ( Help: "URI to redirect to when action is redirect (Eg. \"https://example.com/blocked\")", } + networkACLScope = Flag{ + Name: "Scope", + LongForm: "scope", + Help: "Scope of the rule (management, authentication, tenant)", + } + networkACLASNs = Flag{ Name: "ASNs", LongForm: "asns", @@ -736,6 +742,7 @@ The --rule parameter is required and must contain a valid JSON object with actio networkACLActive.RegisterString(cmd, &inputs.ActiveStr, "") networkACLPriority.RegisterInt(cmd, &inputs.Priority, 0) networkACLRule.RegisterString(cmd, &inputs.RuleJSON, "") + registerDeprecatedRuleFlags(cmd) return cmd } @@ -845,6 +852,7 @@ To update non-interactively, supply the description, active, priority, and rule networkACLActive.RegisterStringU(cmd, &inputs.ActiveStr, "") networkACLPriority.RegisterIntU(cmd, &inputs.Priority, 1) networkACLRule.RegisterStringU(cmd, &inputs.RuleJSON, "") + registerDeprecatedRuleFlags(cmd) return cmd } @@ -964,3 +972,52 @@ func (c *cli) networkACLPickerOptions(ctx context.Context) (pickerOptions, error return opts, nil } + +const deprecatedRuleFlagMessage = "use `--rule` flag to set the configuration as JSON." + +func registerDeprecatedRuleFlags(cmd *cobra.Command) { + var ( + action string + redirectURI string + scope string + asns []int + countryCodes []string + subdivCodes []string + ipv4CIDRs []string + ipv6CIDRs []string + ja3 []string + ja4 []string + userAgents []string + auth0Managed []string + ) + + networkACLRuleAction.RegisterString(cmd, &action, "") + networkACLRedirectURI.RegisterString(cmd, &redirectURI, "") + networkACLScope.RegisterString(cmd, &scope, "") + networkACLASNs.RegisterIntSlice(cmd, &asns, nil) + networkACLCountryCodes.RegisterStringSlice(cmd, &countryCodes, nil) + networkACLSubdivisionCodes.RegisterStringSlice(cmd, &subdivCodes, nil) + networkACLIPv4CIDRs.RegisterStringSlice(cmd, &ipv4CIDRs, nil) + networkACLIPv6CIDRs.RegisterStringSlice(cmd, &ipv6CIDRs, nil) + networkACLJA3Fingerprints.RegisterStringSlice(cmd, &ja3, nil) + networkACLJA4Fingerprints.RegisterStringSlice(cmd, &ja4, nil) + networkACLUserAgents.RegisterStringSlice(cmd, &userAgents, nil) + networkACLAuth0Managed.RegisterStringSlice(cmd, &auth0Managed, nil) + + for _, f := range []*Flag{ + &networkACLRuleAction, + &networkACLRedirectURI, + &networkACLScope, + &networkACLASNs, + &networkACLCountryCodes, + &networkACLSubdivisionCodes, + &networkACLIPv4CIDRs, + &networkACLIPv6CIDRs, + &networkACLJA3Fingerprints, + &networkACLJA4Fingerprints, + &networkACLUserAgents, + &networkACLAuth0Managed, + } { + f.Deprecate(cmd, deprecatedRuleFlagMessage) + } +} From 96b016d108c917e2040b5219ce516360f12d263d Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Wed, 9 Sep 2026 17:29:35 +0530 Subject: [PATCH 4/8] refactor(network-acl): inline flag structs for interactive rule prompts - Replace shared top-level Flag variables with locally-defined Flag structs in promptForRuleDetails and promptForMatchCriteria, so interactive prompts no longer reuse the deprecated command flags. - Drop the LongForm on the inlined prompt flags, which is unused for interactive prompting. --- internal/cli/network_acl.go | 55 +++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/internal/cli/network_acl.go b/internal/cli/network_acl.go index fbd3d5781..9c91e3204 100644 --- a/internal/cli/network_acl.go +++ b/internal/cli/network_acl.go @@ -358,13 +358,19 @@ func promptForRuleDetails(cmd *cobra.Command, cli *cli, defaults *ruleDefaults, // Ask for action. actions := []string{"block", "allow", "log", "redirect"} - if err := networkACLRuleAction.Select(cmd, &inputs.Action, actions, &defaults.Action); err != nil { + if err := (&Flag{ + Name: "Action", + Help: "Action for the rule (block, allow, log, redirect)", + }).Select(cmd, &inputs.Action, actions, &defaults.Action); err != nil { return nil, err } // If action is redirect, ask for redirect URI. if inputs.Action == "redirect" { - if err := networkACLRedirectURI.Ask(cmd, &inputs.RedirectURI, &defaults.RedirectURI); err != nil { + if err := (&Flag{ + Name: "RedirectURI", + Help: "URI to redirect to when action is redirect (Eg. \"https://example.com/blocked\")", + }).Ask(cmd, &inputs.RedirectURI, &defaults.RedirectURI); err != nil { return nil, err } if inputs.RedirectURI == "" { @@ -420,63 +426,90 @@ func promptForRuleDetails(cmd *cobra.Command, cli *cli, defaults *ruleDefaults, // promptForMatchCriteria handles prompting for all match criteria based on selected parameters. func promptForMatchCriteria(cmd *cobra.Command, selectedParams map[string]bool, inputs *ruleInputs, defaults *ruleDefaults) error { if selectedParams["ASNs"] { - if err := networkACLASNs.AskIntSlice(cmd, &inputs.ASNs, &defaults.ASNs); err != nil { + if err := (&Flag{ + Name: "ASNs", + Help: "Comma-separated list of ASNs to match (Eg. 64496,64497,64498)", + }).AskIntSlice(cmd, &inputs.ASNs, &defaults.ASNs); err != nil { return err } } if selectedParams["Country Codes"] { currentCountryCodesStr := strings.Join(defaults.CountryCodes, ",") - if err := networkACLCountryCodes.AskMany(cmd, &inputs.CountryCodes, ¤tCountryCodesStr); err != nil { + if err := (&Flag{ + Name: "CountryCodes", + Help: "Comma-separated list of country codes to match (Eg. US,CA,MX)", + }).AskMany(cmd, &inputs.CountryCodes, ¤tCountryCodesStr); err != nil { return err } } if selectedParams["Subdivision Codes"] { currentSubDivCodesStr := strings.Join(defaults.SubdivCodes, ",") - if err := networkACLSubdivisionCodes.AskMany(cmd, &inputs.SubdivCodes, ¤tSubDivCodesStr); err != nil { + if err := (&Flag{ + Name: "SubdivisionCodes", + Help: "Comma-separated list of subdivision codes to match (Eg. US-NY,US-CA)", + }).AskMany(cmd, &inputs.SubdivCodes, ¤tSubDivCodesStr); err != nil { return err } } if selectedParams["IPv4CIDRs"] { currentIPv4CIDRsStr := strings.Join(defaults.IPv4CIDRs, ",") - if err := networkACLIPv4CIDRs.AskMany(cmd, &inputs.IPv4CIDRs, ¤tIPv4CIDRsStr); err != nil { + if err := (&Flag{ + Name: "IPv4CIDRs", + Help: "Comma-separated list of IPv4 CIDR ranges (Eg. 192.168.1.0/24,10.0.0.0/8)", + }).AskMany(cmd, &inputs.IPv4CIDRs, ¤tIPv4CIDRsStr); err != nil { return err } } if selectedParams["IPv6CIDRs"] { currentIPv6CIDRsStr := strings.Join(defaults.IPv6CIDRs, ",") - if err := networkACLIPv6CIDRs.AskMany(cmd, &inputs.IPv6CIDRs, ¤tIPv6CIDRsStr); err != nil { + if err := (&Flag{ + Name: "IPv6CIDRs", + Help: "Comma-separated list of IPv6 CIDR ranges (Eg. 2001:db8::/32,2001:db8:1234::/48)", + }).AskMany(cmd, &inputs.IPv6CIDRs, ¤tIPv6CIDRsStr); err != nil { return err } } if selectedParams["JA3Fingerprints"] { currentJA3Str := strings.Join(defaults.JA3, ",") - if err := networkACLJA3Fingerprints.AskMany(cmd, &inputs.JA3, ¤tJA3Str); err != nil { + if err := (&Flag{ + Name: "JA3Fingerprints", + Help: "Comma-separated list of JA3 fingerprints to match (Eg. deadbeef,cafebabe)", + }).AskMany(cmd, &inputs.JA3, ¤tJA3Str); err != nil { return err } } if selectedParams["JA4Fingerprints"] { currentJA4Str := strings.Join(defaults.JA4, ",") - if err := networkACLJA4Fingerprints.AskMany(cmd, &inputs.JA4, ¤tJA4Str); err != nil { + if err := (&Flag{ + Name: "JA4Fingerprints", + Help: "Comma-separated list of JA4 fingerprints to match (Eg. t13d1516h2_8daaf6152771)", + }).AskMany(cmd, &inputs.JA4, ¤tJA4Str); err != nil { return err } } if selectedParams["User Agents"] { currentUserAgentsStr := strings.Join(defaults.UserAgents, ",") - if err := networkACLUserAgents.AskMany(cmd, &inputs.UserAgents, ¤tUserAgentsStr); err != nil { + if err := (&Flag{ + Name: "UserAgents", + Help: "Comma-separated list of user agents to match (Eg. badbot/*,malicious/*)", + }).AskMany(cmd, &inputs.UserAgents, ¤tUserAgentsStr); err != nil { return err } } if selectedParams["Auth0 Managed"] { currentAuth0ManagedStr := strings.Join(defaults.Auth0Managed, ",") - if err := networkACLAuth0Managed.AskMany(cmd, &inputs.Auth0Managed, ¤tAuth0ManagedStr); err != nil { + if err := (&Flag{ + Name: "Auth0Managed", + Help: "Comma-separated list of Auth0-curated blocklists to match (Eg. auth0.icloud_relay_proxy,auth0.low_reputation). (EA only).", + }).AskMany(cmd, &inputs.Auth0Managed, ¤tAuth0ManagedStr); err != nil { return err } } From d27aa3b748849a3091b18a6615fee80a982ddbb0 Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Tue, 8 Sep 2026 16:39:40 +0530 Subject: [PATCH 5/8] feat(auth0): add NetworkACLKey V3 API for listing signing keys - Introduce the NetworkACLKeyAPIV3 interface wrapping the V3 SDK /keys/network-acls List endpoint, plus its generated mock. - Wire NetworkACLKey into the APIV3 struct and NewAPIV3 via m.Keys.NetworkACLs so commands can resolve existing signing keys. - Only List is exposed; key create/delete is intentionally deferred. List requires the read:network_acl_keys scope. --- internal/auth0/auth0.go | 2 + internal/auth0/mock/network_acl_key_mock.go | 57 +++++++++++++++++++++ internal/auth0/network_acl_key.go | 22 ++++++++ 3 files changed, 81 insertions(+) create mode 100644 internal/auth0/mock/network_acl_key_mock.go create mode 100644 internal/auth0/network_acl_key.go diff --git a/internal/auth0/auth0.go b/internal/auth0/auth0.go index 1ec8df22f..61dcc60c2 100644 --- a/internal/auth0/auth0.go +++ b/internal/auth0/auth0.go @@ -89,6 +89,7 @@ type APIV3 struct { UserRefreshToken UserRefreshTokenAPIV3 ActionModule ActionModuleAPIV3 ActionModuleVersion ActionModuleVersionAPIV3 + NetworkACLKey NetworkACLKeyAPIV3 } func NewAPIV3(m *managementv3.Management) *APIV3 { @@ -108,6 +109,7 @@ func NewAPIV3(m *managementv3.Management) *APIV3 { UserRefreshToken: m.Users.RefreshToken, ActionModule: m.Actions.Modules, ActionModuleVersion: m.Actions.Modules.Versions, + NetworkACLKey: m.Keys.NetworkACLs, } } diff --git a/internal/auth0/mock/network_acl_key_mock.go b/internal/auth0/mock/network_acl_key_mock.go new file mode 100644 index 000000000..f42502f7b --- /dev/null +++ b/internal/auth0/mock/network_acl_key_mock.go @@ -0,0 +1,57 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: network_acl_key.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + management "github.com/auth0/go-auth0/v3/management" + option "github.com/auth0/go-auth0/v3/management/option" + gomock "github.com/golang/mock/gomock" +) + +// MockNetworkACLKeyAPIV3 is a mock of NetworkACLKeyAPIV3 interface. +type MockNetworkACLKeyAPIV3 struct { + ctrl *gomock.Controller + recorder *MockNetworkACLKeyAPIV3MockRecorder +} + +// MockNetworkACLKeyAPIV3MockRecorder is the mock recorder for MockNetworkACLKeyAPIV3. +type MockNetworkACLKeyAPIV3MockRecorder struct { + mock *MockNetworkACLKeyAPIV3 +} + +// NewMockNetworkACLKeyAPIV3 creates a new mock instance. +func NewMockNetworkACLKeyAPIV3(ctrl *gomock.Controller) *MockNetworkACLKeyAPIV3 { + mock := &MockNetworkACLKeyAPIV3{ctrl: ctrl} + mock.recorder = &MockNetworkACLKeyAPIV3MockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNetworkACLKeyAPIV3) EXPECT() *MockNetworkACLKeyAPIV3MockRecorder { + return m.recorder +} + +// List mocks base method. +func (m *MockNetworkACLKeyAPIV3) List(ctx context.Context, opts ...option.RequestOption) (*management.GetAllKeysNetworkACLsResponseContent, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].(*management.GetAllKeysNetworkACLsResponseContent) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockNetworkACLKeyAPIV3MockRecorder) List(ctx interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockNetworkACLKeyAPIV3)(nil).List), varargs...) +} diff --git a/internal/auth0/network_acl_key.go b/internal/auth0/network_acl_key.go new file mode 100644 index 000000000..78d7b74b1 --- /dev/null +++ b/internal/auth0/network_acl_key.go @@ -0,0 +1,22 @@ +//go:generate mockgen -source=network_acl_key.go -destination=mock/network_acl_key_mock.go -package=mock + +package auth0 + +import ( + "context" + + managementv3 "github.com/auth0/go-auth0/v3/management" + "github.com/auth0/go-auth0/v3/management/option" +) + +// NetworkACLKeyAPIV3 is the V3 SDK interface for the /keys/network-acls endpoint. +// +// Only List is exposed today: the network-acl command uses it to let a user pick +// existing signing keys by name when adding the http_message_signature signal to a +// rule. Key create/delete is intentionally not wired yet (DXCDT-2269). +type NetworkACLKeyAPIV3 interface { + // List retrieves all Network ACL keys for the tenant. + // + // Required scope: `read:network_acl_keys`. The response is not paginated. + List(ctx context.Context, opts ...option.RequestOption) (*managementv3.GetAllKeysNetworkACLsResponseContent, error) +} From 40ffbe471f4d671e1032e6b3040eb72180237b1a Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Tue, 8 Sep 2026 16:40:23 +0530 Subject: [PATCH 6/8] feat(network-acl): support http_message_signature signal in rules - Add the http_message_signature signal to network-acl create/update, set interactively via a new "Signature Keys" picker or non-interactively through the --rule JSON, with no dedicated per-field flag. - Add pickNetworkACLSignatureKeys, which lists tenant signing keys by name (V3 NetworkACLKey.List), pre-selects the rule's current keys when editing, and errors when no keys exist for the tenant. - Extend buildNetworkACLRule and extractCurrentRuleDefaults to emit and read HTTPMessageSignature on both match and not_match. - Render SIGNATURE KEY IDS / NOT SIGNATURE KEY IDS rows in the ACL view. - Add AskMultiSelectWithDefault to pre-select current options in a prompt. - Bump the go-auth0 v1 SDK to a build carrying the HTTPMessageSignature types and regenerate command docs. http_message_signature is an Early Access feature gated behind the tenant_acl_hmac_signature flag; the interactive picker needs the read:network_acl_keys scope. --- docs/auth0_network-acl_create.md | 3 +- docs/auth0_network-acl_update.md | 3 +- go.mod | 2 +- go.sum | 4 +- internal/cli/network_acl.go | 164 +++++++++++++++++++++------ internal/cli/network_acl_test.go | 140 +++++++++++++++++++++++ internal/display/network_acl.go | 25 ++++ internal/display/network_acl_test.go | 66 +++++++++++ internal/prompt/prompt.go | 11 ++ 9 files changed, 378 insertions(+), 40 deletions(-) diff --git a/docs/auth0_network-acl_create.md b/docs/auth0_network-acl_create.md index 3442018ea..72f900d18 100644 --- a/docs/auth0_network-acl_create.md +++ b/docs/auth0_network-acl_create.md @@ -25,8 +25,9 @@ auth0 network-acl create [flags] auth0 network-acl create -d "Block Bots" -p 4 --active true --rule '{"action":{"block":true},"scope":"tenant","match":{"user_agents":["badbot/*","malicious/*"],"ja3_fingerprints":["deadbeef","cafebabe"]}}' auth0 network-acl create --description "Complex Rule" --priority 5 --active true --rule '{"action":{"block":true},"scope":"tenant","match":{"ipv4_cidrs":["192.168.1.0/24"],"geo_country_codes":["US"]}}' - # Early Access (auth0_managed match/not_match value): + # Early Access (auth0_managed and http_message_signature match/not_match value): auth0 network-acl create -d "Curated Blocklist" -p 6 --active true --rule '{"action":{"log":true},"scope":"tenant","not_match":{"auth0_managed":["auth0.vpn","auth0.proxy"]}}' + auth0 network-acl create -d "Only Signed" -p 8 --active true --rule '{"action":{"allow":true},"scope":"authentication","match":{"http_message_signature":{"keys":[{"id": "key_123"}]}}}' ``` diff --git a/docs/auth0_network-acl_update.md b/docs/auth0_network-acl_update.md index 1461b2075..a1ffbf2e6 100644 --- a/docs/auth0_network-acl_update.md +++ b/docs/auth0_network-acl_update.md @@ -25,8 +25,9 @@ auth0 network-acl update [flags] auth0 network-acl update --rule '{"action":{"block":true},"scope":"tenant","match":{"ipv4_cidrs":["192.168.1.0/24"]}}' auth0 network-acl update --description "Complex Rule updated" --priority 1 --active true --rule '{"action":{"block":true},"scope":"tenant","match":{"ipv4_cidrs":["192.168.1.0/24"],"geo_country_codes":["US"]}}' - # Early Access (auth0_managed match/not_match value): + # Early Access (auth0_managed and http_message_signature match/not_match value): auth0 network-acl update --rule '{"action":{"allow":true},"scope":"tenant","match":{"auth0_managed":["auth0.low_reputation"]}}' + auth0 network-acl update --rule '{"action":{"allow":true},"scope":"authentication","match":{"http_message_signature":{"keys":[{"id": "key_123"},{"id": "key_456"}]}}}' ``` diff --git a/go.mod b/go.mod index f691c60f9..4fd94eb9c 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/PuerkitoBio/rehttp v1.4.0 github.com/atotto/clipboard v0.1.4 - github.com/auth0/go-auth0 v1.48.0 + github.com/auth0/go-auth0 v1.48.1-0.20260908093415-b605e12c0057 github.com/auth0/go-auth0/v3 v3.4.0 github.com/briandowns/spinner v1.23.2 github.com/charmbracelet/glamour v1.0.0 diff --git a/go.sum b/go.sum index d56edf4fa..5e5924ef8 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/auth0/go-auth0 v1.48.0 h1:INqEEZbDEkXVI0xUZluS1zzoB4YbYzvCysHH2CNEGzc= -github.com/auth0/go-auth0 v1.48.0/go.mod h1:32sQB1uAn+99fJo6N819EniKq8h785p0ag0lMWhiTaE= +github.com/auth0/go-auth0 v1.48.1-0.20260908093415-b605e12c0057 h1:dn9pkKCvdrwN0GFIpQjD8/vqw+1EDiXggFkEwfyaaKw= +github.com/auth0/go-auth0 v1.48.1-0.20260908093415-b605e12c0057/go.mod h1:32sQB1uAn+99fJo6N819EniKq8h785p0ag0lMWhiTaE= github.com/auth0/go-auth0/v3 v3.4.0 h1:CjdnuuDUcVG19BIT7bloEYtyQHpIYr6rwQI2d1Wz7Fk= github.com/auth0/go-auth0/v3 v3.4.0/go.mod h1:MGifkH9wfgGbWsgFPrz2QbM2wLmScH1kGgIvAtd65Xw= github.com/aybabtme/iocontrol v0.0.0-20150809002002-ad15bcfc95a0 h1:0NmehRCgyk5rljDQLKUO+cRJCnduDyn11+zGZIc9Z48= diff --git a/internal/cli/network_acl.go b/internal/cli/network_acl.go index 9c91e3204..052781267 100644 --- a/internal/cli/network_acl.go +++ b/internal/cli/network_acl.go @@ -201,6 +201,7 @@ func selectNetworkACLParams() (map[string]bool, error) { "JA4Fingerprints", "User Agents", "Auth0 Managed", + "Signature Keys", } var selected []string @@ -228,21 +229,22 @@ func selectNetworkACLParams() (map[string]bool, error) { // ruleDefaults holds default values extracted from current ACL rule. type ruleDefaults struct { - Scope string - Action string - RedirectURI string - ASNs []int - CountryCodes []string - SubdivCodes []string - IPv4CIDRs []string - IPv6CIDRs []string - JA3 []string - JA4 []string - UserAgents []string - Auth0Managed []string - IsMatchRule bool - HasMatchRule bool - HasNotMatch bool + Scope string + Action string + RedirectURI string + ASNs []int + CountryCodes []string + SubdivCodes []string + IPv4CIDRs []string + IPv6CIDRs []string + JA3 []string + JA4 []string + UserAgents []string + Auth0Managed []string + SignatureKeyIDs []string + IsMatchRule bool + HasMatchRule bool + HasNotMatch bool } // extractCurrentRuleDefaults extracts default values from current ACL rule for interactive prompts. @@ -317,28 +319,45 @@ func extractCurrentRuleDefaults(currentACL *management.NetworkACL) *ruleDefaults if match.Auth0Managed != nil { defaults.Auth0Managed = *match.Auth0Managed } + defaults.SignatureKeyIDs = signatureKeyIDs(match) } return defaults } +// signatureKeyIDs flattens the referenced key ids of a match's +// http_message_signature signal, or nil when the signal is not set. +func signatureKeyIDs(match *management.NetworkACLRuleMatch) []string { + if match == nil || match.HTTPMessageSignature == nil { + return nil + } + ids := make([]string, 0, len(match.HTTPMessageSignature.Keys)) + for _, k := range match.HTTPMessageSignature.Keys { + if k.ID != nil { + ids = append(ids, *k.ID) + } + } + return ids +} + // ruleInputs holds user inputs for rule configuration. type ruleInputs struct { - Scope string - Action string - RedirectURI string - ASNs []int - CountryCodes []string - SubdivCodes []string - IPv4CIDRs []string - IPv6CIDRs []string - JA3 []string - JA4 []string - UserAgents []string - Auth0Managed []string - IsMatchRule bool - MatchRule bool - NoMatchRule bool + Scope string + Action string + RedirectURI string + ASNs []int + CountryCodes []string + SubdivCodes []string + IPv4CIDRs []string + IPv6CIDRs []string + JA3 []string + JA4 []string + UserAgents []string + Auth0Managed []string + SignatureKeyIDs []string + IsMatchRule bool + MatchRule bool + NoMatchRule bool } // promptForRuleDetails handles interactive prompting for rule configuration. @@ -402,7 +421,7 @@ func promptForRuleDetails(cmd *cobra.Command, cli *cli, defaults *ruleDefaults, var selectedMatchOption string if err := (&Flag{ Name: "What kind of rule do you want to create?", - Help: "Match or Not Match rule (ASNs, Country Codes, Subdivision Codes, IPv4 CIDRs, IPv6 CIDRs, JA3/JA4 Fingerprints, User Agents, Auth0 Managed)", + Help: "Match or Not Match rule (ASNs, Country Codes, Subdivision Codes, IPv4 CIDRs, IPv6 CIDRs, JA3/JA4 Fingerprints, User Agents, Auth0 Managed, Signature Keys)", }).Select(cmd, &selectedMatchOption, matchOptions, nil); err != nil { return nil, err } @@ -416,7 +435,7 @@ func promptForRuleDetails(cmd *cobra.Command, cli *cli, defaults *ruleDefaults, } // Ask for values only for selected parameters. - if err := promptForMatchCriteria(cmd, selectedParams, inputs, defaults); err != nil { + if err := promptForMatchCriteria(cmd, cli, selectedParams, inputs, defaults); err != nil { return nil, err } @@ -424,7 +443,7 @@ func promptForRuleDetails(cmd *cobra.Command, cli *cli, defaults *ruleDefaults, } // promptForMatchCriteria handles prompting for all match criteria based on selected parameters. -func promptForMatchCriteria(cmd *cobra.Command, selectedParams map[string]bool, inputs *ruleInputs, defaults *ruleDefaults) error { +func promptForMatchCriteria(cmd *cobra.Command, cli *cli, selectedParams map[string]bool, inputs *ruleInputs, defaults *ruleDefaults) error { if selectedParams["ASNs"] { if err := (&Flag{ Name: "ASNs", @@ -514,9 +533,74 @@ func promptForMatchCriteria(cmd *cobra.Command, selectedParams map[string]bool, } } + if selectedParams["Signature Keys"] { + ids, err := cli.pickNetworkACLSignatureKeys(cmd, defaults.SignatureKeyIDs) + if err != nil { + return err + } + inputs.SignatureKeyIDs = ids + } + return nil } +// pickNetworkACLSignatureKeys resolves the http_message_signature key ids for a rule. +// +// It lists the tenant's Network ACL keys (v3 /keys/network-acls) and shows a multi-select +// of them, with the ids already referenced by the rule (current) pre-selected. Setting the +// signal non-interactively is done through the full rule JSON passed to --rule. +func (c *cli) pickNetworkACLSignatureKeys(cmd *cobra.Command, current []string) ([]string, error) { + var options []string + labelToID := make(map[string]string) + idToLabel := make(map[string]string) + if err := ansi.Waiting(func() error { + resp, err := c.apiv3.NetworkACLKey.List(cmd.Context()) + if err != nil { + return err + } + if resp != nil { + for _, k := range resp.Keys { + id := k.GetID() + label := fmt.Sprintf("%s (%s)", k.GetName(), id) + options = append(options, label) + idToLabel[id] = label + labelToID[label] = id + } + } + return nil + }); err != nil { + return nil, err + } + + if len(options) == 0 { + return nil, errors.New("no Network ACL keys exist for this tenant; create a key first, then reference it here") + } + + // Pre-select the keys already referenced by the rule. + defaults := make([]string, 0, len(current)) + for _, id := range current { + if label, ok := idToLabel[id]; ok { + defaults = append(defaults, label) + } + } + + var selected []string + if err := prompt.AskMultiSelectWithDefault( + "Select the signing keys whose HTTP message signature satisfies the rule using the spacebar and press Enter to confirm:", + &selected, + defaults, + options..., + ); err != nil { + return nil, err + } + + ids := make([]string, 0, len(selected)) + for _, label := range selected { + ids = append(ids, labelToID[label]) + } + return ids, nil +} + // buildNetworkACLRule creates a NetworkACLRule from the provided inputs. func buildNetworkACLRule(inputs *ruleInputs) (*management.NetworkACLRule, error) { rule := &management.NetworkACLRule{ @@ -577,6 +661,14 @@ func buildNetworkACLRule(inputs *ruleInputs) (*management.NetworkACLRule, error) match.Auth0Managed = &inputs.Auth0Managed matchProvided = true } + if len(inputs.SignatureKeyIDs) > 0 { + keys := make([]*management.NetworkACLHTTPMessageSignatureKey, len(inputs.SignatureKeyIDs)) + for i := range inputs.SignatureKeyIDs { + keys[i] = &management.NetworkACLHTTPMessageSignatureKey{ID: &inputs.SignatureKeyIDs[i]} + } + match.HTTPMessageSignature = &management.NetworkACLHTTPMessageSignature{Keys: keys} + matchProvided = true + } if !matchProvided { return nil, fmt.Errorf("at least one match criteria must be provided") @@ -696,8 +788,9 @@ The --rule parameter is required and must contain a valid JSON object with actio auth0 network-acl create -d "Block Bots" -p 4 --active true --rule '{"action":{"block":true},"scope":"tenant","match":{"user_agents":["badbot/*","malicious/*"],"ja3_fingerprints":["deadbeef","cafebabe"]}}' auth0 network-acl create --description "Complex Rule" --priority 5 --active true --rule '{"action":{"block":true},"scope":"tenant","match":{"ipv4_cidrs":["192.168.1.0/24"],"geo_country_codes":["US"]}}' - # Early Access (auth0_managed match/not_match value): + # Early Access (auth0_managed and http_message_signature match/not_match value): auth0 network-acl create -d "Curated Blocklist" -p 6 --active true --rule '{"action":{"log":true},"scope":"tenant","not_match":{"auth0_managed":["auth0.vpn","auth0.proxy"]}}' + auth0 network-acl create -d "Only Signed" -p 8 --active true --rule '{"action":{"allow":true},"scope":"authentication","match":{"http_message_signature":{"keys":[{"id": "key_123"}]}}}' `, RunE: func(cmd *cobra.Command, args []string) error { // Validate --rule JSON up front, before prompting for other fields, so @@ -798,8 +891,9 @@ To update non-interactively, supply the description, active, priority, and rule auth0 network-acl update --rule '{"action":{"block":true},"scope":"tenant","match":{"ipv4_cidrs":["192.168.1.0/24"]}}' auth0 network-acl update --description "Complex Rule updated" --priority 1 --active true --rule '{"action":{"block":true},"scope":"tenant","match":{"ipv4_cidrs":["192.168.1.0/24"],"geo_country_codes":["US"]}}' - # Early Access (auth0_managed match/not_match value): + # Early Access (auth0_managed and http_message_signature match/not_match value): auth0 network-acl update --rule '{"action":{"allow":true},"scope":"tenant","match":{"auth0_managed":["auth0.low_reputation"]}}' + auth0 network-acl update --rule '{"action":{"allow":true},"scope":"authentication","match":{"http_message_signature":{"keys":[{"id": "key_123"},{"id": "key_456"}]}}}' `, RunE: func(cmd *cobra.Command, args []string) error { // Get the network ACL ID. diff --git a/internal/cli/network_acl_test.go b/internal/cli/network_acl_test.go index b160a99e2..d43ecd89e 100644 --- a/internal/cli/network_acl_test.go +++ b/internal/cli/network_acl_test.go @@ -177,6 +177,146 @@ func TestBuildNetworkACLRule_Auth0Managed(t *testing.T) { } } +func TestBuildNetworkACLRule_HTTPMessageSignature(t *testing.T) { + tests := []struct { + name string + inputs *ruleInputs + assertRule func(t testing.TB, rule *management.NetworkACLRule) + expectError bool + }{ + { + name: "http_message_signature on match", + inputs: &ruleInputs{ + Scope: "authentication", + Action: "block", + SignatureKeyIDs: []string{"key_123", "key_456"}, + IsMatchRule: true, + }, + assertRule: func(t testing.TB, rule *management.NetworkACLRule) { + assert.Nil(t, rule.NotMatch) + assert.NotNil(t, rule.Match) + assert.NotNil(t, rule.Match.HTTPMessageSignature) + assert.Len(t, rule.Match.HTTPMessageSignature.Keys, 2) + assert.Equal(t, "key_123", *rule.Match.HTTPMessageSignature.Keys[0].ID) + assert.Equal(t, "key_456", *rule.Match.HTTPMessageSignature.Keys[1].ID) + }, + }, + { + name: "http_message_signature on not_match", + inputs: &ruleInputs{ + Scope: "authentication", + Action: "block", + SignatureKeyIDs: []string{"key_123"}, + IsMatchRule: false, + }, + assertRule: func(t testing.TB, rule *management.NetworkACLRule) { + assert.Nil(t, rule.Match) + assert.NotNil(t, rule.NotMatch) + assert.NotNil(t, rule.NotMatch.HTTPMessageSignature) + assert.Len(t, rule.NotMatch.HTTPMessageSignature.Keys, 1) + assert.Equal(t, "key_123", *rule.NotMatch.HTTPMessageSignature.Keys[0].ID) + }, + }, + { + name: "http_message_signature coexists with other criteria", + inputs: &ruleInputs{ + Scope: "authentication", + Action: "block", + IPv4CIDRs: []string{"192.168.1.0/24"}, + SignatureKeyIDs: []string{"key_123"}, + IsMatchRule: true, + }, + assertRule: func(t testing.TB, rule *management.NetworkACLRule) { + assert.NotNil(t, rule.Match) + assert.NotNil(t, rule.Match.IPv4Cidrs) + assert.NotNil(t, rule.Match.HTTPMessageSignature) + assert.Len(t, rule.Match.HTTPMessageSignature.Keys, 1) + }, + }, + { + name: "http_message_signature empty is not set", + inputs: &ruleInputs{ + Scope: "authentication", + Action: "block", + IsMatchRule: true, + }, + expectError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + rule, err := buildNetworkACLRule(test.inputs) + + if test.expectError { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + test.assertRule(t, rule) + }) + } +} + +func TestExtractCurrentRuleDefaults_HTTPMessageSignature(t *testing.T) { + tests := []struct { + name string + acl *management.NetworkACL + wantKeys []string + }{ + { + name: "extracts signature key ids from match", + acl: &management.NetworkACL{ + Rule: &management.NetworkACLRule{ + Match: &management.NetworkACLRuleMatch{ + HTTPMessageSignature: &management.NetworkACLHTTPMessageSignature{ + Keys: []*management.NetworkACLHTTPMessageSignatureKey{ + {ID: auth0.String("key_123")}, + {ID: auth0.String("key_456")}, + }, + }, + }, + }, + }, + wantKeys: []string{"key_123", "key_456"}, + }, + { + name: "extracts signature key ids from not_match", + acl: &management.NetworkACL{ + Rule: &management.NetworkACLRule{ + NotMatch: &management.NetworkACLRuleMatch{ + HTTPMessageSignature: &management.NetworkACLHTTPMessageSignature{ + Keys: []*management.NetworkACLHTTPMessageSignatureKey{ + {ID: auth0.String("key_123")}, + }, + }, + }, + }, + }, + wantKeys: []string{"key_123"}, + }, + { + name: "no signature keys set", + acl: &management.NetworkACL{ + Rule: &management.NetworkACLRule{ + Match: &management.NetworkACLRuleMatch{ + IPv4Cidrs: &[]string{"192.168.1.0/24"}, + }, + }, + }, + wantKeys: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + defaults := extractCurrentRuleDefaults(test.acl) + assert.Equal(t, test.wantKeys, defaults.SignatureKeyIDs) + }) + } +} + func TestExtractCurrentRuleDefaults_Auth0Managed(t *testing.T) { tests := []struct { name string diff --git a/internal/display/network_acl.go b/internal/display/network_acl.go index 70d238c31..ecc54cd7d 100644 --- a/internal/display/network_acl.go +++ b/internal/display/network_acl.go @@ -104,6 +104,10 @@ func (v *networkACLView) KeyValues() [][]string { if match.Auth0Managed != nil && len(*match.Auth0Managed) > 0 { keyValues = append(keyValues, []string{"AUTH0 MANAGED", strings.Join(*match.Auth0Managed, ", ")}) } + + if ids := httpMessageSignatureKeyIDs(match); len(ids) > 0 { + keyValues = append(keyValues, []string{"SIGNATURE KEY IDS", strings.Join(ids, ", ")}) + } } // Add not_match criteria if present. @@ -149,12 +153,33 @@ func (v *networkACLView) KeyValues() [][]string { if notMatch.Auth0Managed != nil && len(*notMatch.Auth0Managed) > 0 { keyValues = append(keyValues, []string{"NOT AUTH0 MANAGED", strings.Join(*notMatch.Auth0Managed, ", ")}) } + + if ids := httpMessageSignatureKeyIDs(notMatch); len(ids) > 0 { + keyValues = append(keyValues, []string{"NOT SIGNATURE KEY IDS", strings.Join(ids, ", ")}) + } } } return keyValues } +// httpMessageSignatureKeyIDs returns the signing key IDs referenced by a rule's +// http_message_signature signal, or nil when the signal is absent. +func httpMessageSignatureKeyIDs(match *management.NetworkACLRuleMatch) []string { + if match == nil || match.HTTPMessageSignature == nil { + return nil + } + + ids := make([]string, 0, len(match.HTTPMessageSignature.Keys)) + for _, k := range match.HTTPMessageSignature.Keys { + if k.ID != nil { + ids = append(ids, *k.ID) + } + } + + return ids +} + func (v *networkACLView) Object() interface{} { return v.raw } diff --git a/internal/display/network_acl_test.go b/internal/display/network_acl_test.go index 561a739e3..6c3e1ac50 100644 --- a/internal/display/network_acl_test.go +++ b/internal/display/network_acl_test.go @@ -80,6 +80,72 @@ func TestNetworkACLView_KeyValues_Auth0Managed(t *testing.T) { } } +func TestNetworkACLView_KeyValues_HTTPMessageSignature(t *testing.T) { + tests := []struct { + name string + acl *management.NetworkACL + wantKey string + wantValue string + }{ + { + name: "http_message_signature on match", + acl: &management.NetworkACL{ + ID: strPtr("acl-1"), + Description: strPtr("Only Signed"), + Priority: intPtr(1), + Active: boolPtr(true), + Rule: &management.NetworkACLRule{ + Scope: strPtr("authentication"), + Action: &management.NetworkACLRuleAction{Block: boolPtr(true)}, + Match: &management.NetworkACLRuleMatch{ + HTTPMessageSignature: &management.NetworkACLHTTPMessageSignature{ + Keys: []*management.NetworkACLHTTPMessageSignatureKey{ + {ID: strPtr("key_123")}, + {ID: strPtr("key_456")}, + }, + }, + }, + }, + }, + wantKey: "SIGNATURE KEY IDS", + wantValue: "key_123, key_456", + }, + { + name: "http_message_signature on not_match", + acl: &management.NetworkACL{ + ID: strPtr("acl-2"), + Description: strPtr("Reject Signed"), + Priority: intPtr(2), + Active: boolPtr(true), + Rule: &management.NetworkACLRule{ + Scope: strPtr("authentication"), + Action: &management.NetworkACLRuleAction{Block: boolPtr(true)}, + NotMatch: &management.NetworkACLRuleMatch{ + HTTPMessageSignature: &management.NetworkACLHTTPMessageSignature{ + Keys: []*management.NetworkACLHTTPMessageSignatureKey{ + {ID: strPtr("key_123")}, + }, + }, + }, + }, + }, + wantKey: "NOT SIGNATURE KEY IDS", + wantValue: "key_123", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + view := makeNetworkACLView(test.acl) + kvs := view.KeyValues() + + value, ok := keyValue(kvs, test.wantKey) + assert.True(t, ok, "expected key %q to be present in KeyValues()", test.wantKey) + assert.Equal(t, test.wantValue, value) + }) + } +} + // TestNetworkACLView_Object_IncludesID guards against a regression where storing // a *management.NetworkACL in the view's raw field engaged that type's pointer // receiver MarshalJSON, which emits only the writable subset of fields and drops diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index 1cc8e53ab..cbd7a85c2 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -35,6 +35,17 @@ func AskMultiSelect(message string, response interface{}, options ...string) err return err } +// AskMultiSelectWithDefault is AskMultiSelect with the given default options pre-selected. +func AskMultiSelectWithDefault(message string, response interface{}, defaults []string, options ...string) error { + prompt := &survey.MultiSelect{ + Message: message, + Options: options, + Default: defaults, + } + + return askOne(prompt, response) +} + func AskBool(message string, value *bool, defaultValue bool) error { *value = defaultValue prompt := &survey.Confirm{ From a2c42a20625d10e766c54ac096490008f86143c8 Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Wed, 9 Sep 2026 17:46:57 +0530 Subject: [PATCH 7/8] chore(deps): bump github.com/auth0/go-auth0 to v1.49.0 - Upgrade go-auth0 from the v1.48.1 pre-release pseudo-version to the tagged v1.49.0 release for a stable, reproducible dependency. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4fd94eb9c..918a7f800 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/PuerkitoBio/rehttp v1.4.0 github.com/atotto/clipboard v0.1.4 - github.com/auth0/go-auth0 v1.48.1-0.20260908093415-b605e12c0057 + github.com/auth0/go-auth0 v1.49.0 github.com/auth0/go-auth0/v3 v3.4.0 github.com/briandowns/spinner v1.23.2 github.com/charmbracelet/glamour v1.0.0 diff --git a/go.sum b/go.sum index 5e5924ef8..b81f2845a 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/auth0/go-auth0 v1.48.1-0.20260908093415-b605e12c0057 h1:dn9pkKCvdrwN0GFIpQjD8/vqw+1EDiXggFkEwfyaaKw= -github.com/auth0/go-auth0 v1.48.1-0.20260908093415-b605e12c0057/go.mod h1:32sQB1uAn+99fJo6N819EniKq8h785p0ag0lMWhiTaE= +github.com/auth0/go-auth0 v1.49.0 h1:JQH/zoHjeKywqJHu0qUWJP7RAE7IET8yU8SvqIFNKKg= +github.com/auth0/go-auth0 v1.49.0/go.mod h1:32sQB1uAn+99fJo6N819EniKq8h785p0ag0lMWhiTaE= github.com/auth0/go-auth0/v3 v3.4.0 h1:CjdnuuDUcVG19BIT7bloEYtyQHpIYr6rwQI2d1Wz7Fk= github.com/auth0/go-auth0/v3 v3.4.0/go.mod h1:MGifkH9wfgGbWsgFPrz2QbM2wLmScH1kGgIvAtd65Xw= github.com/aybabtme/iocontrol v0.0.0-20150809002002-ad15bcfc95a0 h1:0NmehRCgyk5rljDQLKUO+cRJCnduDyn11+zGZIc9Z48= From 92c92eafac6d1c06a628d95bce499dd8b2fa86ad Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Wed, 9 Sep 2026 19:10:18 +0530 Subject: [PATCH 8/8] chore: lint fix --- internal/cli/network_acl.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/cli/network_acl.go b/internal/cli/network_acl.go index f43d1a324..0cfc8ecbe 100644 --- a/internal/cli/network_acl.go +++ b/internal/cli/network_acl.go @@ -245,7 +245,7 @@ type ruleDefaults struct { IsMatchRule bool HasMatchRule bool HasNotMatch bool - MatchAll bool + MatchAll bool } // extractCurrentRuleDefaults extracts default values from current ACL rule for interactive prompts. @@ -363,7 +363,7 @@ type ruleInputs struct { IsMatchRule bool MatchRule bool NoMatchRule bool - MatchAll bool + MatchAll bool } // promptForRuleDetails handles interactive prompting for rule configuration.