Skip to content
Merged
5 changes: 5 additions & 0 deletions docs/auth0_network-acl_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ 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"]}}'

# 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 @@ -33,6 +37,7 @@ auth0 network-acl create [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)
Expand Down
5 changes: 5 additions & 0 deletions docs/auth0_network-acl_update.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ 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"]}}'

# 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 @@ -33,6 +37,7 @@ auth0 network-acl update [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)
Expand Down
43 changes: 40 additions & 3 deletions internal/cli/network_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ var (
LongForm: "user-agents",
Help: "Comma-separated list of user agents to match (Eg. badbot/*,malicious/*)",
}

networkACLAuth0Managed = Flag{
Name: "Auth0Managed",
LongForm: "auth0-managed",
Help: "Comma-separated list of Auth0-curated blocklists to match (Eg. auth0.icloud_relay_proxy,auth0.low_reputation). (EA only).",
}
)

// validateAndSetBasicFields handles the common validation and patch building logic for basic fields.
Expand All @@ -121,6 +127,7 @@ func validateAndSetBasicFields(inputs *struct {
JA3 []string
JA4 []string
UserAgents []string
Auth0Managed []string
MatchRule bool
NoMatchRule bool
}, patch *management.NetworkACL, cmd *cobra.Command) error {
Expand Down Expand Up @@ -183,6 +190,7 @@ func selectNetworkACLParams(cmd *cobra.Command) (map[string]bool, error) {
"JA3Fingerprints",
"JA4Fingerprints",
"User Agents",
"Auth0 Managed",
}

var selected []string
Expand Down Expand Up @@ -221,6 +229,7 @@ type ruleDefaults struct {
JA3 []string
JA4 []string
UserAgents []string
Auth0Managed []string
IsMatchRule bool
HasMatchRule bool
HasNotMatch bool
Expand Down Expand Up @@ -295,6 +304,9 @@ func extractCurrentRuleDefaults(currentACL *management.NetworkACL) *ruleDefaults
if match.UserAgents != nil {
defaults.UserAgents = *match.UserAgents
}
if match.Auth0Managed != nil {
defaults.Auth0Managed = *match.Auth0Managed
}
}

return defaults
Expand All @@ -313,6 +325,7 @@ type ruleInputs struct {
JA3 []string
JA4 []string
UserAgents []string
Auth0Managed []string
IsMatchRule bool
MatchRule bool
NoMatchRule bool
Expand Down Expand Up @@ -373,7 +386,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)",
Help: "Match or Not Match rule (ASNs, Country Codes, Subdivision Codes, IPv4 CIDRs, IPv6 CIDRs, JA3/JA4 Fingerprints, User Agents, Auth0 Managed)",
}).Select(cmd, &selectedMatchOption, matchOptions, nil); err != nil {
return nil, err
}
Expand Down Expand Up @@ -451,6 +464,13 @@ func promptForMatchCriteria(cmd *cobra.Command, selectedParams map[string]bool,
}
}

if selectedParams["Auth0 Managed"] {
currentAuth0ManagedStr := strings.Join(defaults.Auth0Managed, ",")
if err := networkACLAuth0Managed.AskMany(cmd, &inputs.Auth0Managed, &currentAuth0ManagedStr); err != nil {
return err
}
}

return nil
}

Expand Down Expand Up @@ -510,6 +530,10 @@ func buildNetworkACLRule(inputs *ruleInputs) (*management.NetworkACLRule, error)
match.UserAgents = &inputs.UserAgents
matchProvided = true
}
if len(inputs.Auth0Managed) > 0 {
match.Auth0Managed = &inputs.Auth0Managed
matchProvided = true
}

if !matchProvided {
return nil, fmt.Errorf("at least one match criteria must be provided")
Expand Down Expand Up @@ -628,6 +652,7 @@ func createNetworkACLCmd(cli *cli) *cobra.Command {
JA3 []string
JA4 []string
UserAgents []string
Auth0Managed []string
Scope string
isMatchRule bool
}
Expand All @@ -645,7 +670,11 @@ The --rule parameter is required and must contain a valid JSON object with actio
auth0 network-acl create --description "Geo Block" --priority 2 --active true --rule '{"action":{"block":true},"scope":"authentication","match":{"geo_country_codes":["US","CA"]}}'
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 "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):
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") {
Expand Down Expand Up @@ -770,7 +799,9 @@ The --rule parameter is required and must contain a valid JSON object with actio
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")
Expand All @@ -797,6 +828,7 @@ func updateNetworkACLCmd(cli *cli) *cobra.Command {
JA3 []string
JA4 []string
UserAgents []string
Auth0Managed []string
MatchRule bool
NoMatchRule bool
}
Expand All @@ -814,7 +846,11 @@ To update non-interactively, supply the description, active, priority, and rule
auth0 network-acl update <id> --active true
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> --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):
auth0 network-acl update <id> --rule '{"action":{"allow":true},"scope":"tenant","match":{"auth0_managed":["auth0.low_reputation"]}}'
`,
RunE: func(cmd *cobra.Command, args []string) error {
// Get the network ACL ID.
if len(args) > 0 {
Expand Down Expand Up @@ -924,6 +960,7 @@ To update non-interactively, supply the description, active, priority, and rule
networkACLJA3Fingerprints.RegisterStringSlice(cmd, &inputs.JA3, nil)
networkACLJA4Fingerprints.RegisterStringSlice(cmd, &inputs.JA4, nil)
networkACLUserAgents.RegisterStringSlice(cmd, &inputs.UserAgents, nil)
networkACLAuth0Managed.RegisterStringSlice(cmd, &inputs.Auth0Managed, nil)

return cmd
}
Expand Down
128 changes: 128 additions & 0 deletions internal/cli/network_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,131 @@ func TestNetworkACLPickerOptions(t *testing.T) {
})
}
}

func TestBuildNetworkACLRule_Auth0Managed(t *testing.T) {
tests := []struct {
name string
inputs *ruleInputs
assertRule func(t testing.TB, rule *management.NetworkACLRule)
expectError bool
}{
{
name: "auth0_managed on match",
inputs: &ruleInputs{
Scope: "tenant",
Action: "block",
Auth0Managed: []string{"auth0.low_reputation", "auth0.icloud_relay_proxy"},
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.Auth0Managed)
assert.Equal(t, []string{"auth0.low_reputation", "auth0.icloud_relay_proxy"}, *rule.Match.Auth0Managed)
},
},
{
name: "auth0_managed on not_match",
inputs: &ruleInputs{
Scope: "tenant",
Action: "block",
Auth0Managed: []string{"auth0.low_reputation"},
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.Auth0Managed)
assert.Equal(t, []string{"auth0.low_reputation"}, *rule.NotMatch.Auth0Managed)
},
},
{
name: "auth0_managed coexists with other criteria",
inputs: &ruleInputs{
Scope: "tenant",
Action: "block",
IPv4CIDRs: []string{"192.168.1.0/24"},
Auth0Managed: []string{"auth0.low_reputation"},
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.Auth0Managed)
assert.Equal(t, []string{"auth0.low_reputation"}, *rule.Match.Auth0Managed)
},
},
{
name: "auth0_managed empty is not set",
inputs: &ruleInputs{
Scope: "tenant",
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_Auth0Managed(t *testing.T) {
tests := []struct {
name string
acl *management.NetworkACL
wantAuth0Managed []string
}{
{
name: "extracts auth0_managed from match",
acl: &management.NetworkACL{
Rule: &management.NetworkACLRule{
Match: &management.NetworkACLRuleMatch{
Auth0Managed: &[]string{"auth0.low_reputation", "auth0.icloud_relay_proxy"},
},
},
},
wantAuth0Managed: []string{"auth0.low_reputation", "auth0.icloud_relay_proxy"},
},
{
name: "extracts auth0_managed from not_match",
acl: &management.NetworkACL{
Rule: &management.NetworkACLRule{
NotMatch: &management.NetworkACLRuleMatch{
Auth0Managed: &[]string{"auth0.low_reputation"},
},
},
},
wantAuth0Managed: []string{"auth0.low_reputation"},
},
{
name: "no auth0_managed set",
acl: &management.NetworkACL{
Rule: &management.NetworkACLRule{
Match: &management.NetworkACLRuleMatch{
IPv4Cidrs: &[]string{"192.168.1.0/24"},
},
},
},
wantAuth0Managed: nil,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
defaults := extractCurrentRuleDefaults(test.acl)
assert.Equal(t, test.wantAuth0Managed, defaults.Auth0Managed)
})
}
}
17 changes: 14 additions & 3 deletions internal/display/network_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (v *networkACLView) KeyValues() [][]string {
return keyValues
}

acl, ok := v.raw.(*management.NetworkACL)
acl, ok := v.raw.(management.NetworkACL)
if !ok {
return keyValues
}
Expand Down Expand Up @@ -100,12 +100,15 @@ func (v *networkACLView) KeyValues() [][]string {
if match.UserAgents != nil && len(*match.UserAgents) > 0 {
keyValues = append(keyValues, []string{"USER AGENTS", strings.Join(*match.UserAgents, ", ")})
}

if match.Auth0Managed != nil && len(*match.Auth0Managed) > 0 {
keyValues = append(keyValues, []string{"AUTH0 MANAGED", strings.Join(*match.Auth0Managed, ", ")})
}
}

// Add not_match criteria if present.
if acl.Rule.NotMatch != nil {
notMatch := acl.Rule.NotMatch
keyValues = append(keyValues, []string{"NOT MATCH", "true"})

if len(notMatch.Asns) > 0 {
asns := make([]string, len(notMatch.Asns))
Expand Down Expand Up @@ -142,6 +145,10 @@ func (v *networkACLView) KeyValues() [][]string {
if notMatch.UserAgents != nil && len(*notMatch.UserAgents) > 0 {
keyValues = append(keyValues, []string{"NOT USER AGENTS", strings.Join(*notMatch.UserAgents, ", ")})
}

if notMatch.Auth0Managed != nil && len(*notMatch.Auth0Managed) > 0 {
keyValues = append(keyValues, []string{"NOT AUTH0 MANAGED", strings.Join(*notMatch.Auth0Managed, ", ")})
}
}
}

Expand Down Expand Up @@ -192,7 +199,11 @@ func makeNetworkACLView(acl *management.NetworkACL) *networkACLView {
Active: fmt.Sprintf("%v", active),
Action: action,
Rule: string(ruleJSON),
raw: rawData,
// Stored as a value, never a pointer. (*management.NetworkACL).MarshalJSON
// emits only the writable subset of fields, so a pointer here would silently
// drop "id" from --json output. Keeping it a value leaves that method out of
// the method set, so encoding/json falls back to reflecting over the struct.
raw: rawData,
}
}

Expand Down
Loading
Loading