Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/auth0_network-acl_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ auth0 network-acl create [flags]
auth0 network-acl create --description "Redirect Traffic" --priority 3 --active true --rule '{"action":{"redirect":true,"redirect_uri":"https://example.com"},"scope":"management","match":{"ipv4_cidrs":["192.168.1.0/24"]}}'
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"]}}'
auth0 network-acl create --description "Deny All" --priority 99 --active true --rule '{"action":{"block":true},"scope":"tenant","match_all":true}'

# Early Access (auth0_managed 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"]}}'
Expand All @@ -46,6 +47,7 @@ auth0 network-acl create [flags]
--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.
--match-all Match all traffic unconditionally (Eg. block all). Cannot be combined with match/not_match criteria.
-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)
Expand Down
2 changes: 2 additions & 0 deletions docs/auth0_network-acl_update.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ auth0 network-acl update [flags]
auth0 network-acl update <id> --description "Updated description"
auth0 network-acl update <id> --rule '{"action":{"block":true},"scope":"tenant","match":{"ipv4_cidrs":["192.168.1.0/24"]}}'
auth0 network-acl update <id> --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"]}}'
auth0 network-acl update <id> --rule '{"action":{"block":true},"scope":"tenant","match_all":true}'

# Early Access (auth0_managed match/not_match value):
auth0 network-acl update <id> --rule '{"action":{"allow":true},"scope":"tenant","match":{"auth0_managed":["auth0.low_reputation"]}}'
Expand All @@ -45,6 +46,7 @@ auth0 network-acl update [flags]
--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
--match-all Match all traffic unconditionally (Eg. block all). Cannot be combined with match/not_match criteria.
-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
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.20260904094659-f5a492879bcb
github.com/auth0/go-auth0/v3 v3.3.0
github.com/briandowns/spinner v1.23.2
github.com/charmbracelet/glamour v1.0.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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.20260904094659-f5a492879bcb h1:0wnPrAL2OWPu/fZgDuIUFBR0MNHbuswjbmB53rJDmIc=
github.com/auth0/go-auth0 v1.48.1-0.20260904094659-f5a492879bcb/go.mod h1:32sQB1uAn+99fJo6N819EniKq8h785p0ag0lMWhiTaE=
github.com/auth0/go-auth0/v3 v3.3.0 h1:p/OxyycZNUtFekO6uXGBqrVXtnJ92gO7ikXtVDuT0ZA=
github.com/auth0/go-auth0/v3 v3.3.0/go.mod h1:wb20iE6T4wCGWtMXAZTWTTxx1/T1aHfVK+dOWleQvlg=
github.com/aybabtme/iocontrol v0.0.0-20150809002002-ad15bcfc95a0 h1:0NmehRCgyk5rljDQLKUO+cRJCnduDyn11+zGZIc9Z48=
Expand Down
45 changes: 45 additions & 0 deletions internal/cli/network_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ var (
LongForm: "auth0-managed",
Help: "Comma-separated list of Auth0-curated blocklists to match (Eg. auth0.icloud_relay_proxy,auth0.low_reputation). (EA only).",
}

networkACLMatchAll = Flag{
Name: "MatchAll",
LongForm: "match-all",
Help: "Match all traffic unconditionally (Eg. block all). Cannot be combined with match/not_match criteria.",
}
)

// validateAndSetBasicFields handles the common validation and patch building logic for basic fields.
Expand All @@ -130,6 +136,7 @@ func validateAndSetBasicFields(inputs *struct {
Auth0Managed []string
MatchRule bool
NoMatchRule bool
MatchAll bool
}, patch *management.NetworkACL, cmd *cobra.Command) error {
if cmd.Flags().Changed("description") {
if len(inputs.Description) > 255 {
Expand Down Expand Up @@ -233,6 +240,7 @@ type ruleDefaults struct {
IsMatchRule bool
HasMatchRule bool
HasNotMatch bool
MatchAll bool
}

// extractCurrentRuleDefaults extracts default values from current ACL rule for interactive prompts.
Expand Down Expand Up @@ -267,6 +275,11 @@ func extractCurrentRuleDefaults(currentACL *management.NetworkACL) *ruleDefaults
}
}

// Extract match_all (unconditional rule, mutually exclusive with match/not_match).
if currentACL.Rule.MatchAll != nil && *currentACL.Rule.MatchAll {
defaults.MatchAll = true
}

// Extract match criteria from either Match or NotMatch.
var match *management.NetworkACLRuleMatch
if currentACL.Rule.Match != nil {
Expand Down Expand Up @@ -329,6 +342,7 @@ type ruleInputs struct {
IsMatchRule bool
MatchRule bool
NoMatchRule bool
MatchAll bool
}

// promptForRuleDetails handles interactive prompting for rule configuration.
Expand Down Expand Up @@ -362,6 +376,25 @@ func promptForRuleDetails(cmd *cobra.Command, cli *cli, defaults *ruleDefaults,
}
}

// Match All is a top-level rule signal, orthogonal to match/not_match and mutually
// exclusive with them at the API. Confirm it before the match/not_match flow, and when
// it is set, skip the criteria selection entirely (there is nothing more to ask).
// AskBool skips its prompt when --match-all was set explicitly, so seed the value
// from the flag first to preserve an explicitly supplied value.
if cmd.Flags().Changed("match-all") {
matchAll, err := cmd.Flags().GetBool("match-all")
if err != nil {
return nil, err
}
inputs.MatchAll = matchAll
}
if err := networkACLMatchAll.AskBool(cmd, &inputs.MatchAll, &defaults.MatchAll); err != nil {
return nil, err
}
if inputs.MatchAll {
return inputs, nil
}

// Handle Match/NotMatch rule changes for updates.
if isUpdate {
if defaults.HasMatchRule {
Expand Down Expand Up @@ -494,6 +527,12 @@ func buildNetworkACLRule(inputs *ruleInputs) (*management.NetworkACLRule, error)
rule.Action.RedirectURI = &inputs.RedirectURI
}

// A match_all rule is unconditional and mutually exclusive with match/not_match criteria.
if inputs.MatchAll {
rule.MatchAll = auth0.Bool(true)
return rule, nil
}

// Build match criteria.
match := &management.NetworkACLRuleMatch{}
matchProvided := false
Expand Down Expand Up @@ -654,6 +693,7 @@ func createNetworkACLCmd(cli *cli) *cobra.Command {
UserAgents []string
Auth0Managed []string
Scope string
MatchAll bool
isMatchRule bool
}

Expand All @@ -671,6 +711,7 @@ The --rule parameter is required and must contain a valid JSON object with actio
auth0 network-acl create --description "Redirect Traffic" --priority 3 --active true --rule '{"action":{"redirect":true,"redirect_uri":"https://example.com"},"scope":"management","match":{"ipv4_cidrs":["192.168.1.0/24"]}}'
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"]}}'
auth0 network-acl create --description "Deny All" --priority 99 --active true --rule '{"action":{"block":true},"scope":"tenant","match_all":true}'

# Early Access (auth0_managed 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"]}}'
Expand Down Expand Up @@ -800,6 +841,7 @@ The --rule parameter is required and must contain a valid JSON object with actio
networkACLJA4Fingerprints.RegisterStringSlice(cmd, &inputs.JA4, nil)
networkACLUserAgents.RegisterStringSlice(cmd, &inputs.UserAgents, nil)
networkACLAuth0Managed.RegisterStringSlice(cmd, &inputs.Auth0Managed, nil)
networkACLMatchAll.RegisterBool(cmd, &inputs.MatchAll, false)

// These flags must be passed in non-interactive mode.
cmd.MarkFlagRequired("description")
Expand Down Expand Up @@ -831,6 +873,7 @@ func updateNetworkACLCmd(cli *cli) *cobra.Command {
Auth0Managed []string
MatchRule bool
NoMatchRule bool
MatchAll bool
}

cmd := &cobra.Command{
Expand All @@ -847,6 +890,7 @@ To update non-interactively, supply the description, active, priority, and rule
auth0 network-acl update <id> --description "Updated description"
auth0 network-acl update <id> --rule '{"action":{"block":true},"scope":"tenant","match":{"ipv4_cidrs":["192.168.1.0/24"]}}'
auth0 network-acl update <id> --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"]}}'
auth0 network-acl update <id> --rule '{"action":{"block":true},"scope":"tenant","match_all":true}'

# Early Access (auth0_managed match/not_match value):
auth0 network-acl update <id> --rule '{"action":{"allow":true},"scope":"tenant","match":{"auth0_managed":["auth0.low_reputation"]}}'
Expand Down Expand Up @@ -961,6 +1005,7 @@ To update non-interactively, supply the description, active, priority, and rule
networkACLJA4Fingerprints.RegisterStringSlice(cmd, &inputs.JA4, nil)
networkACLUserAgents.RegisterStringSlice(cmd, &inputs.UserAgents, nil)
networkACLAuth0Managed.RegisterStringSlice(cmd, &inputs.Auth0Managed, nil)
networkACLMatchAll.RegisterBool(cmd, &inputs.MatchAll, false)

return cmd
}
Expand Down
118 changes: 118 additions & 0 deletions internal/cli/network_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/auth0/go-auth0/management"
"github.com/golang/mock/gomock"
"github.com/spf13/cobra"

"github.com/stretchr/testify/assert"

Expand Down Expand Up @@ -177,6 +178,123 @@ func TestBuildNetworkACLRule_Auth0Managed(t *testing.T) {
}
}

func TestBuildNetworkACLRule_MatchAll(t *testing.T) {
tests := []struct {
name string
inputs *ruleInputs
assertRule func(t testing.TB, rule *management.NetworkACLRule)
expectError bool
}{
{
name: "match_all block rule sets match_all and skips criteria",
inputs: &ruleInputs{
Scope: "tenant",
Action: "block",
MatchAll: true,
},
assertRule: func(t testing.TB, rule *management.NetworkACLRule) {
assert.Nil(t, rule.Match)
assert.Nil(t, rule.NotMatch)
assert.NotNil(t, rule.MatchAll)
assert.True(t, *rule.MatchAll)
assert.NotNil(t, rule.Action.Block)
assert.True(t, *rule.Action.Block)
assert.Equal(t, "tenant", *rule.Scope)
},
},
{
name: "match_all ignores any match criteria provided",
inputs: &ruleInputs{
Scope: "tenant",
Action: "block",
MatchAll: true,
IPv4CIDRs: []string{"192.168.1.0/24"},
IsMatchRule: true,
},
assertRule: func(t testing.TB, rule *management.NetworkACLRule) {
assert.Nil(t, rule.Match)
assert.Nil(t, rule.NotMatch)
assert.NotNil(t, rule.MatchAll)
assert.True(t, *rule.MatchAll)
},
},
}

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 TestPromptForRuleDetails_MatchAllFlag(t *testing.T) {
// In tests there is no TTY, so canPrompt is false and every interactive prompt is
// skipped. This lets us exercise the seeding of MatchAll from an explicitly-set
// --match-all flag and confirm it short-circuits before the match criteria prompts.
newCmd := func(setMatchAll bool) *cobra.Command {
cmd := &cobra.Command{Use: "create"}
var matchAll bool
networkACLMatchAll.RegisterBool(cmd, &matchAll, false)
if setMatchAll {
assert.NoError(t, cmd.Flags().Set("match-all", "true"))
}
return cmd
}

cli := &cli{renderer: testRenderer()}
defaults := &ruleDefaults{Scope: "tenant", Action: "block"}

// With --match-all set, seeding runs and the early return fires before the
// match/not_match selection and criteria prompts, so no TTY is needed.
inputs, err := promptForRuleDetails(newCmd(true), cli, defaults, false)
assert.NoError(t, err)
assert.True(t, inputs.MatchAll)
}

func TestExtractCurrentRuleDefaults_MatchAll(t *testing.T) {
tests := []struct {
name string
acl *management.NetworkACL
wantMatchAll bool
}{
{
name: "extracts match_all when true",
acl: &management.NetworkACL{
Rule: &management.NetworkACLRule{
MatchAll: auth0.Bool(true),
},
},
wantMatchAll: true,
},
{
name: "no match_all set",
acl: &management.NetworkACL{
Rule: &management.NetworkACLRule{
Match: &management.NetworkACLRuleMatch{
IPv4Cidrs: &[]string{"192.168.1.0/24"},
},
},
},
wantMatchAll: false,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
defaults := extractCurrentRuleDefaults(test.acl)
assert.Equal(t, test.wantMatchAll, defaults.MatchAll)
})
}
}

func TestExtractCurrentRuleDefaults_Auth0Managed(t *testing.T) {
tests := []struct {
name string
Expand Down
5 changes: 5 additions & 0 deletions internal/display/network_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ func (v *networkACLView) KeyValues() [][]string {
keyValues = append(keyValues, []string{"REDIRECT URI", *acl.Rule.Action.RedirectURI})
}

// Add match_all if set (unconditional rule, mutually exclusive with match/not_match).
if acl.Rule.MatchAll != nil && *acl.Rule.MatchAll {
keyValues = append(keyValues, []string{"MATCH ALL", "true"})
}

// Add match criteria if present.
if acl.Rule.Match != nil {
match := acl.Rule.Match
Expand Down
20 changes: 20 additions & 0 deletions internal/display/network_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,26 @@ func TestNetworkACLView_KeyValues_Auth0Managed(t *testing.T) {
}
}

func TestNetworkACLView_KeyValues_MatchAll(t *testing.T) {
acl := &management.NetworkACL{
ID: strPtr("acl-match-all"),
Description: strPtr("Deny All"),
Priority: intPtr(99),
Active: boolPtr(true),
Rule: &management.NetworkACLRule{
Scope: strPtr("tenant"),
Action: &management.NetworkACLRuleAction{Block: boolPtr(true)},
MatchAll: boolPtr(true),
},
}

kvs := makeNetworkACLView(acl).KeyValues()

value, ok := keyValue(kvs, "MATCH ALL")
assert.True(t, ok, "expected key \"MATCH ALL\" to be present in KeyValues()")
assert.Equal(t, "true", 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
Expand Down
16 changes: 14 additions & 2 deletions test/integration/network-acl-test-cases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,22 @@ tests:
rule.action.block: "true"
rule.not_match.ipv4_cidrs: "[10.0.0.0/8]"

010 - delete network ACL:
010 - create network ACL with match_all deny-all rule:
command: auth0 network-acl create --description "Integration Test ACL Match All" --active false --priority 6 --rule '{"action":{"block":true},"scope":"tenant","match_all":true}' --json --no-input
exit-code: 0
stdout:
json:
description: "Integration Test ACL Match All"
active: "false"
priority: "6"
rule.scope: "tenant"
rule.action.block: "true"
rule.match_all: "true"

011 - delete network ACL:
command: auth0 network-acl delete $(./test/integration/scripts/get-network-acl-id.sh) --force --no-input
exit-code: 0

011 - delete all network ACLs:
012 - delete all network ACLs:
command: auth0 network-acl delete --all --force --no-input
exit-code: 0
Loading