From 2c53b1c2316e6f8bd85ef7f8f6a8d2561f4b3d1c Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Thu, 6 Aug 2026 16:46:45 +0530 Subject: [PATCH 1/3] chore(deps): bump github.com/auth0/go-auth0 to v1.46.0 - Upgrade the v1 Management SDK from v1.45.0 to v1.46.0, which adds the auth0_managed field on network ACL rule match criteria. - Required before the network-acl command can expose Auth0-managed curated blocklists; no behavior change on its own. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1f0520e97..0c6abf05a 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.45.0 + github.com/auth0/go-auth0 v1.46.0 github.com/auth0/go-auth0/v2 v2.14.0 github.com/briandowns/spinner v1.23.2 github.com/charmbracelet/glamour v1.0.0 diff --git a/go.sum b/go.sum index c1af98788..10e2936bd 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.45.0 h1:fQaNSWpoMneYsutOr+3fTOXIsFAQThSf2A0ShL3oaZg= -github.com/auth0/go-auth0 v1.45.0/go.mod h1:32sQB1uAn+99fJo6N819EniKq8h785p0ag0lMWhiTaE= +github.com/auth0/go-auth0 v1.46.0 h1:2awmVKsBQ+zGi66FnH5PV4NRIQCGkXZK8bnyTOPcdnE= +github.com/auth0/go-auth0 v1.46.0/go.mod h1:32sQB1uAn+99fJo6N819EniKq8h785p0ag0lMWhiTaE= github.com/auth0/go-auth0/v2 v2.14.0 h1:zDxwRHGAt6gLK/OG6wAkB5ScQEJ8WW/ex1EnJig8fFc= github.com/auth0/go-auth0/v2 v2.14.0/go.mod h1:Q/Y3VZVoI3sw87VyTPhx2TQL6Sq4Q/iCP67rW2gcn+M= github.com/aybabtme/iocontrol v0.0.0-20150809002002-ad15bcfc95a0 h1:0NmehRCgyk5rljDQLKUO+cRJCnduDyn11+zGZIc9Z48= From bd56f42b95fa56af9761345374e4cd59ca5e63c2 Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Thu, 6 Aug 2026 16:47:29 +0530 Subject: [PATCH 2/3] feat(network-acl): add auth0_managed field to rule match criteria - Add --auth0-managed flag to network-acl create and update commands for specifying Auth0-curated blocklists (auth0.low_reputation, etc.) in rule matching criteria. (Early Access only.) - Support auth0_managed in both match and not_match rule types via the existing rule builder. - Include auth0_managed in the interactive rule builder prompt and default extraction from existing rules. - Add table-driven tests for auth0_managed handling in buildNetworkACLRule and extractCurrentRuleDefaults. - Regenerate command docs with new examples. --- docs/auth0_network-acl_create.md | 5 ++ docs/auth0_network-acl_update.md | 5 ++ internal/cli/network_acl.go | 43 ++++++++++- internal/cli/network_acl_test.go | 128 +++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 3 deletions(-) diff --git a/docs/auth0_network-acl_create.md b/docs/auth0_network-acl_create.md index 26db020de..646c49060 100644 --- a/docs/auth0_network-acl_create.md +++ b/docs/auth0_network-acl_create.md @@ -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"]}}' + ``` @@ -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) diff --git a/docs/auth0_network-acl_update.md b/docs/auth0_network-acl_update.md index 9a2e32de7..5fa670454 100644 --- a/docs/auth0_network-acl_update.md +++ b/docs/auth0_network-acl_update.md @@ -24,6 +24,10 @@ auth0 network-acl update [flags] 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"]}}' 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): + auth0 network-acl update --rule '{"action":{"allow":true},"scope":"tenant","match":{"auth0_managed":["auth0.low_reputation"]}}' + ``` @@ -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) diff --git a/internal/cli/network_acl.go b/internal/cli/network_acl.go index d980f5692..9ce087d41 100644 --- a/internal/cli/network_acl.go +++ b/internal/cli/network_acl.go @@ -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. @@ -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 { @@ -183,6 +190,7 @@ func selectNetworkACLParams(cmd *cobra.Command) (map[string]bool, error) { "JA3Fingerprints", "JA4Fingerprints", "User Agents", + "Auth0 Managed", } var selected []string @@ -221,6 +229,7 @@ type ruleDefaults struct { JA3 []string JA4 []string UserAgents []string + Auth0Managed []string IsMatchRule bool HasMatchRule bool HasNotMatch bool @@ -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 @@ -313,6 +325,7 @@ type ruleInputs struct { JA3 []string JA4 []string UserAgents []string + Auth0Managed []string IsMatchRule bool MatchRule bool NoMatchRule bool @@ -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 } @@ -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, ¤tAuth0ManagedStr); err != nil { + return err + } + } + return nil } @@ -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") @@ -628,6 +652,7 @@ func createNetworkACLCmd(cli *cli) *cobra.Command { JA3 []string JA4 []string UserAgents []string + Auth0Managed []string Scope string isMatchRule bool } @@ -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") { @@ -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") @@ -797,6 +828,7 @@ func updateNetworkACLCmd(cli *cli) *cobra.Command { JA3 []string JA4 []string UserAgents []string + Auth0Managed []string MatchRule bool NoMatchRule bool } @@ -814,7 +846,11 @@ To update non-interactively, supply the description, active, priority, and rule 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"]}}' - 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"]}}'`, + 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): + auth0 network-acl update --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 { @@ -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 } diff --git a/internal/cli/network_acl_test.go b/internal/cli/network_acl_test.go index d917b2231..b160a99e2 100644 --- a/internal/cli/network_acl_test.go +++ b/internal/cli/network_acl_test.go @@ -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) + }) + } +} From 52a170fc5f650c7aa5af004c814eec93f2322882 Mon Sep 17 00:00:00 2001 From: KIRAN KUMAR B Date: Thu, 6 Aug 2026 16:47:54 +0530 Subject: [PATCH 3/3] feat(display): add auth0_managed rendering for network ACL rules - Render Auth0-managed blocklists in both match and not_match rule output, labeled as "AUTH0 MANAGED" and "NOT AUTH0 MANAGED" respectively. - Remove redundant "NOT MATCH" marker row since the "NOT " prefix on field labels already clarifies the rule type. - Fix raw data assignment in makeNetworkACLView to use a pointer. - Add table-driven tests for auth0_managed field rendering. --- internal/display/network_acl.go | 11 +++- internal/display/network_acl_test.go | 84 ++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 internal/display/network_acl_test.go diff --git a/internal/display/network_acl.go b/internal/display/network_acl.go index b55bc7d4c..9458dd54d 100644 --- a/internal/display/network_acl.go +++ b/internal/display/network_acl.go @@ -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)) @@ -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, ", ")}) + } } } @@ -192,7 +199,7 @@ func makeNetworkACLView(acl *management.NetworkACL) *networkACLView { Active: fmt.Sprintf("%v", active), Action: action, Rule: string(ruleJSON), - raw: rawData, + raw: &rawData, } } diff --git a/internal/display/network_acl_test.go b/internal/display/network_acl_test.go new file mode 100644 index 000000000..473a02e32 --- /dev/null +++ b/internal/display/network_acl_test.go @@ -0,0 +1,84 @@ +package display + +import ( + "testing" + + "github.com/auth0/go-auth0/management" + "github.com/stretchr/testify/assert" +) + +// keyValue returns the value for a given key in a KeyValues() slice, and whether it was present. +func keyValue(kvs [][]string, key string) (string, bool) { + for _, kv := range kvs { + if kv[0] == key { + return kv[1], true + } + } + return "", false +} + +func TestNetworkACLView_KeyValues_Auth0Managed(t *testing.T) { + tests := []struct { + name string + acl *management.NetworkACL + wantKey string + wantValue string + }{ + { + name: "auth0_managed on match", + acl: &management.NetworkACL{ + ID: strPtr("acl-1"), + Description: strPtr("Curated Blocklist"), + Priority: intPtr(1), + Active: boolPtr(true), + Rule: &management.NetworkACLRule{ + Scope: strPtr("tenant"), + Action: &management.NetworkACLRuleAction{Block: boolPtr(true)}, + Match: &management.NetworkACLRuleMatch{ + Auth0Managed: &[]string{"auth0.low_reputation", "auth0.icloud_relay_proxy"}, + }, + }, + }, + wantKey: "AUTH0 MANAGED", + wantValue: "auth0.low_reputation, auth0.icloud_relay_proxy", + }, + { + name: "auth0_managed on not_match", + acl: &management.NetworkACL{ + ID: strPtr("acl-2"), + Description: strPtr("Curated Blocklist Not Match"), + Priority: intPtr(2), + Active: boolPtr(true), + Rule: &management.NetworkACLRule{ + Scope: strPtr("tenant"), + Action: &management.NetworkACLRuleAction{Block: boolPtr(true)}, + NotMatch: &management.NetworkACLRuleMatch{ + Auth0Managed: &[]string{"auth0.low_reputation"}, + }, + }, + }, + wantKey: "NOT AUTH0 MANAGED", + wantValue: "auth0.low_reputation", + }, + } + + 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) + + // The "NOT " label prefix already conveys the rule type, so no + // redundant marker row is emitted. + _, hasMarker := keyValue(kvs, "NOT MATCH") + assert.False(t, hasMarker, "unexpected redundant \"NOT MATCH\" row") + }) + } +} + +func strPtr(s string) *string { return &s } +func intPtr(i int) *int { return &i } +func boolPtr(b bool) *bool { return &b }