diff --git a/.github/workflows/test-integrations.yml b/.github/workflows/test-integrations.yml index 5a1c3a0ea5..8a2dffa84b 100644 --- a/.github/workflows/test-integrations.yml +++ b/.github/workflows/test-integrations.yml @@ -536,6 +536,7 @@ jobs: -tags "${{ env.GOTAGS }}" \ -timeout=20m \ -parallel=2 \ + -failfast \ -json \ `go list -tags "${{ env.GOTAGS }}" ./... | grep -v peering_commontopo | grep -v upgrade ` \ --target-image ${{ env.CONSUL_LATEST_IMAGE_NAME }} \ diff --git a/agent/xds/rbac_test.go b/agent/xds/rbac_test.go index c7805a0e49..d2a6af12b3 100644 --- a/agent/xds/rbac_test.go +++ b/agent/xds/rbac_test.go @@ -732,6 +732,58 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) { }, }, }, + "v2-path-excludes": { + intentionDefaultAllow: false, + v2TrafficPermissions: &pbproxystate.TrafficPermissions{ + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: makeSpiffe("web", nil), + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/", + Exclude: []*pbproxystate.ExcludePermissionRule{ + {PathPrefix: "/admin"}, + }, + }, + }, + }, + }, + DenyPermissions: []*pbproxystate.Permission{}, + }, + }, + "v2-path-method-header-excludes": { + intentionDefaultAllow: false, + v2TrafficPermissions: &pbproxystate.TrafficPermissions{ + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: makeSpiffe("web", nil), + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/", + Exclude: []*pbproxystate.ExcludePermissionRule{ + { + PathPrefix: "/admin", + Methods: []string{"POST", "DELETE"}, + Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "experiment", Present: true}, + }, + }, + }, + }, + }, + }, + }, + DenyPermissions: []*pbproxystate.Permission{}, + }, + }, "v2-default-deny": { intentionDefaultAllow: false, v2TrafficPermissions: &pbproxystate.TrafficPermissions{}, @@ -784,6 +836,21 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) { v1Intentions: sorted( testSourcePermIntention("web", permSlashPrefix), ), + v2TrafficPermissions: &pbproxystate.TrafficPermissions{ + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: makeSpiffe("web", nil), + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + {PathPrefix: "/"}, + }, + }, + }, + DenyPermissions: []*pbproxystate.Permission{}, + }, }, "default-allow-path-deny": { intentionDefaultAllow: true, @@ -796,6 +863,7 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) { v1Intentions: sorted( testSourcePermIntention("web", permDenySlashPrefix), ), + v2TrafficPermissions: &pbproxystate.TrafficPermissions{}, }, // ======================== "default-allow-deny-all-and-path-allow": { @@ -869,6 +937,7 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) { ), testSourceIntention("*", structs.IntentionActionDeny), ), + v2TrafficPermissions: &pbproxystate.TrafficPermissions{}, }, // ======================== "default-deny-two-path-deny-and-path-allow": { @@ -895,6 +964,26 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) { }, ), ), + v2TrafficPermissions: &pbproxystate.TrafficPermissions{ + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: makeSpiffe("web", nil), + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/", + Exclude: []*pbproxystate.ExcludePermissionRule{ + {PathExact: "/v1/admin"}, + {PathExact: "/v1/secret"}, + }, + }, + }, + }, + }, + }, }, "default-allow-two-path-deny-and-path-allow": { intentionDefaultAllow: true, @@ -964,6 +1053,163 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) { ), ), }, + "v2-single-permission-with-excludes": { + v2TrafficPermissions: &pbproxystate.TrafficPermissions{ + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: makeSpiffe("web", nil), + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/v1", + DestinationRuleHeader: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-foo", Present: true}, + }, + Exclude: []*pbproxystate.ExcludePermissionRule{ + {PathExact: "/v1/secret", Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-baz", Present: true}, + {Name: "x-bar", Present: true}, + }}, + {PathExact: "/v1/admin", Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-baz", Present: true}, + {Name: "x-bar", Present: true}, + }}, + }, + }, + }, + }, + }, + }, + }, + "v2-single-permission-multiple-destination-rules": { + v2TrafficPermissions: &pbproxystate.TrafficPermissions{ + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: makeSpiffe("web", nil), + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/v1", + DestinationRuleHeader: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-foo", Present: true}, + }, + Exclude: []*pbproxystate.ExcludePermissionRule{ + {PathExact: "/v1/secret", Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-baz", Present: true}, + {Name: "x-bar", Present: true}, + }}, + {PathExact: "/v1/admin", Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-baz", Present: true}, + {Name: "x-bar", Present: true}, + }}, + }, + }, + { + PathPrefix: "/v2", + DestinationRuleHeader: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-foo", Present: true}, + }, + Exclude: []*pbproxystate.ExcludePermissionRule{ + {PathExact: "/v2/secret", Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-baz", Present: true}, + {Name: "x-bar", Present: true}, + }}, + {PathExact: "/v2/admin", Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-baz", Present: true}, + {Name: "x-bar", Present: true}, + }}, + }, + }, + }, + }, + }, + }, + }, + "v2-single-permission-with-kitchen-sink-perms": { + v2TrafficPermissions: &pbproxystate.TrafficPermissions{ + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: makeSpiffe("web", nil), + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/v1", + Exclude: []*pbproxystate.ExcludePermissionRule{ + {PathExact: "/v1/secret"}, + }, + }, + { + PathRegex: "/v[123]", + Methods: []string{"GET", "HEAD", "OPTIONS"}, + Exclude: []*pbproxystate.ExcludePermissionRule{ + {PathExact: "/v1/secret"}, + {PathExact: "/v1/admin", Methods: []string{"GET"}}, + }, + }, + { + DestinationRuleHeader: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-foo", Present: true}, + {Name: "x-bar", Exact: "xyz"}, + {Name: "x-dib", Prefix: "gaz"}, + {Name: "x-gir", Suffix: "zim"}, + {Name: "x-zim", Regex: "gi[rR]"}, + {Name: "z-foo", Present: true, Invert: true}, + {Name: "z-bar", Exact: "xyz", Invert: true}, + {Name: "z-dib", Prefix: "gaz", Invert: true}, + {Name: "z-gir", Suffix: "zim", Invert: true}, + {Name: "z-zim", Regex: "gi[rR]", Invert: true}, + }, + Exclude: []*pbproxystate.ExcludePermissionRule{ + {PathExact: "/v1/secret"}, + {Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-baz", Present: true}, + }}, + }, + }, + }, + }, + }, + }, + }, + "v2-L4-deny-L7-allow": { + v2TrafficPermissions: &pbproxystate.TrafficPermissions{ + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: makeSpiffe("web", nil), + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/v1", + DestinationRuleHeader: []*pbproxystate.DestinationRuleHeader{ + {Name: "x-foo", Present: true}, + }, + }, + }, + }, + }, + DenyPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: makeSpiffe("web", nil), + }, + }, + }, + }, + }, + }, "default-allow-single-intention-with-kitchen-sink-perms": { intentionDefaultAllow: true, v1Intentions: sorted( @@ -1114,7 +1360,6 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) { chain.Filters = filters gotJSON = protoToJSON(t, chain) } - require.JSONEq(t, goldenSimple(t, filepath.Join("rbac", name), gotJSON), gotJSON) }) }) @@ -1152,7 +1397,6 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) { manager.HttpFilters = filters gotJSON = protoToJSON(t, manager) } - require.JSONEq(t, goldenSimple(t, filepath.Join("rbac", name+"--httpfilter"), gotJSON), gotJSON) }) }) diff --git a/agent/xds/response/response.go b/agent/xds/response/response.go index 76c189bee7..cc6f132eb6 100644 --- a/agent/xds/response/response.go +++ b/agent/xds/response/response.go @@ -78,12 +78,3 @@ func MakeEnvoyRegexMatch(patt string) *envoy_matcher_v3.RegexMatcher { Regex: patt, } } - -func MakeEnvoyStringMatcher(patt string) *envoy_matcher_v3.StringMatcher { - return &envoy_matcher_v3.StringMatcher{ - MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{ - SafeRegex: MakeEnvoyRegexMatch(patt), - }, - IgnoreCase: true, - } -} diff --git a/agent/xds/testdata/rbac/v2-L4-deny-L7-allow--httpfilter.golden b/agent/xds/testdata/rbac/v2-L4-deny-L7-allow--httpfilter.golden new file mode 100644 index 0000000000..25299cf176 --- /dev/null +++ b/agent/xds/testdata/rbac/v2-L4-deny-L7-allow--httpfilter.golden @@ -0,0 +1,77 @@ +{ + "httpFilters": [ + { + "name": "envoy.filters.http.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", + "rules": { + "action": "DENY", + "policies": { + "consul-intentions-layer4": { + "permissions": [ + { + "any": true + } + ], + "principals": [ + { + "authenticated": { + "principalName": { + "safeRegex": { + "regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$" + } + } + } + } + ] + } + } + } + } + }, + { + "name": "envoy.filters.http.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", + "rules": { + "policies": { + "consul-intentions-layer7-0": { + "permissions": [ + { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "prefix": "/v1" + } + } + }, + { + "header": { + "name": "x-foo", + "presentMatch": true + } + } + ] + } + } + ], + "principals": [ + { + "authenticated": { + "principalName": { + "safeRegex": { + "regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$" + } + } + } + } + ] + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/agent/xds/testdata/rbac/v2-L4-deny-L7-allow.golden b/agent/xds/testdata/rbac/v2-L4-deny-L7-allow.golden new file mode 100644 index 0000000000..9bccccaa99 --- /dev/null +++ b/agent/xds/testdata/rbac/v2-L4-deny-L7-allow.golden @@ -0,0 +1,42 @@ +{ + "filters": [ + { + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": { + "action": "DENY", + "policies": { + "consul-intentions-layer4": { + "permissions": [ + { + "any": true + } + ], + "principals": [ + { + "authenticated": { + "principalName": { + "safeRegex": { + "regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$" + } + } + } + } + ] + } + } + }, + "statPrefix": "connect_authz" + } + }, + { + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": {}, + "statPrefix": "connect_authz" + } + } + ] +} \ No newline at end of file diff --git a/agent/xds/testdata/rbac/v2-path-excludes--httpfilter.golden b/agent/xds/testdata/rbac/v2-path-excludes--httpfilter.golden new file mode 100644 index 0000000000..119cd92193 --- /dev/null +++ b/agent/xds/testdata/rbac/v2-path-excludes--httpfilter.golden @@ -0,0 +1,47 @@ +{ + "name": "envoy.filters.http.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", + "rules": { + "policies": { + "consul-intentions-layer7-0": { + "permissions": [ + { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "prefix": "/" + } + } + }, + { + "notRule": { + "urlPath": { + "path": { + "prefix": "/admin" + } + } + } + } + ] + } + } + ], + "principals": [ + { + "authenticated": { + "principalName": { + "safeRegex": { + "regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$" + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/agent/xds/testdata/rbac/v2-path-excludes.golden b/agent/xds/testdata/rbac/v2-path-excludes.golden new file mode 100644 index 0000000000..b1fa47ea4c --- /dev/null +++ b/agent/xds/testdata/rbac/v2-path-excludes.golden @@ -0,0 +1,8 @@ +{ + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": {}, + "statPrefix": "connect_authz" + } +} \ No newline at end of file diff --git a/agent/xds/testdata/rbac/v2-path-method-header-excludes--httpfilter.golden b/agent/xds/testdata/rbac/v2-path-method-header-excludes--httpfilter.golden new file mode 100644 index 0000000000..780d64a19f --- /dev/null +++ b/agent/xds/testdata/rbac/v2-path-method-header-excludes--httpfilter.golden @@ -0,0 +1,69 @@ +{ + "name": "envoy.filters.http.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", + "rules": { + "policies": { + "consul-intentions-layer7-0": { + "permissions": [ + { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "prefix": "/" + } + } + }, + { + "notRule": { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "prefix": "/admin" + } + } + }, + { + "header": { + "name": ":method", + "stringMatch": { + "safeRegex": { + "regex": "POST|DELETE" + } + } + } + }, + { + "header": { + "name": "experiment", + "presentMatch": true + } + } + ] + } + } + } + ] + } + } + ], + "principals": [ + { + "authenticated": { + "principalName": { + "safeRegex": { + "regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$" + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/agent/xds/testdata/rbac/v2-path-method-header-excludes.golden b/agent/xds/testdata/rbac/v2-path-method-header-excludes.golden new file mode 100644 index 0000000000..b1fa47ea4c --- /dev/null +++ b/agent/xds/testdata/rbac/v2-path-method-header-excludes.golden @@ -0,0 +1,8 @@ +{ + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": {}, + "statPrefix": "connect_authz" + } +} \ No newline at end of file diff --git a/agent/xds/testdata/rbac/v2-single-permission-multiple-destination-rules--httpfilter.golden b/agent/xds/testdata/rbac/v2-single-permission-multiple-destination-rules--httpfilter.golden new file mode 100644 index 0000000000..b4ccdccf74 --- /dev/null +++ b/agent/xds/testdata/rbac/v2-single-permission-multiple-destination-rules--httpfilter.golden @@ -0,0 +1,183 @@ +{ + "name": "envoy.filters.http.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", + "rules": { + "policies": { + "consul-intentions-layer7-0": { + "permissions": [ + { + "andRules": { + "rules": [ + { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "prefix": "/v1" + } + } + }, + { + "header": { + "name": "x-foo", + "presentMatch": true + } + } + ] + } + }, + { + "notRule": { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "exact": "/v1/secret" + } + } + }, + { + "header": { + "name": "x-baz", + "presentMatch": true + } + }, + { + "header": { + "name": "x-bar", + "presentMatch": true + } + } + ] + } + } + }, + { + "notRule": { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "exact": "/v1/admin" + } + } + }, + { + "header": { + "name": "x-baz", + "presentMatch": true + } + }, + { + "header": { + "name": "x-bar", + "presentMatch": true + } + } + ] + } + } + } + ] + } + }, + { + "andRules": { + "rules": [ + { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "prefix": "/v2" + } + } + }, + { + "header": { + "name": "x-foo", + "presentMatch": true + } + } + ] + } + }, + { + "notRule": { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "exact": "/v2/secret" + } + } + }, + { + "header": { + "name": "x-baz", + "presentMatch": true + } + }, + { + "header": { + "name": "x-bar", + "presentMatch": true + } + } + ] + } + } + }, + { + "notRule": { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "exact": "/v2/admin" + } + } + }, + { + "header": { + "name": "x-baz", + "presentMatch": true + } + }, + { + "header": { + "name": "x-bar", + "presentMatch": true + } + } + ] + } + } + } + ] + } + } + ], + "principals": [ + { + "authenticated": { + "principalName": { + "safeRegex": { + "regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$" + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/agent/xds/testdata/rbac/v2-single-permission-multiple-destination-rules.golden b/agent/xds/testdata/rbac/v2-single-permission-multiple-destination-rules.golden new file mode 100644 index 0000000000..b1fa47ea4c --- /dev/null +++ b/agent/xds/testdata/rbac/v2-single-permission-multiple-destination-rules.golden @@ -0,0 +1,8 @@ +{ + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": {}, + "statPrefix": "connect_authz" + } +} \ No newline at end of file diff --git a/agent/xds/testdata/rbac/v2-single-permission-with-excludes--httpfilter.golden b/agent/xds/testdata/rbac/v2-single-permission-with-excludes--httpfilter.golden new file mode 100644 index 0000000000..eac270dada --- /dev/null +++ b/agent/xds/testdata/rbac/v2-single-permission-with-excludes--httpfilter.golden @@ -0,0 +1,104 @@ +{ + "name": "envoy.filters.http.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", + "rules": { + "policies": { + "consul-intentions-layer7-0": { + "permissions": [ + { + "andRules": { + "rules": [ + { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "prefix": "/v1" + } + } + }, + { + "header": { + "name": "x-foo", + "presentMatch": true + } + } + ] + } + }, + { + "notRule": { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "exact": "/v1/secret" + } + } + }, + { + "header": { + "name": "x-baz", + "presentMatch": true + } + }, + { + "header": { + "name": "x-bar", + "presentMatch": true + } + } + ] + } + } + }, + { + "notRule": { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "exact": "/v1/admin" + } + } + }, + { + "header": { + "name": "x-baz", + "presentMatch": true + } + }, + { + "header": { + "name": "x-bar", + "presentMatch": true + } + } + ] + } + } + } + ] + } + } + ], + "principals": [ + { + "authenticated": { + "principalName": { + "safeRegex": { + "regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$" + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/agent/xds/testdata/rbac/v2-single-permission-with-excludes.golden b/agent/xds/testdata/rbac/v2-single-permission-with-excludes.golden new file mode 100644 index 0000000000..b1fa47ea4c --- /dev/null +++ b/agent/xds/testdata/rbac/v2-single-permission-with-excludes.golden @@ -0,0 +1,8 @@ +{ + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": {}, + "statPrefix": "connect_authz" + } +} \ No newline at end of file diff --git a/agent/xds/testdata/rbac/v2-single-permission-with-kitchen-sink-perms--httpfilter.golden b/agent/xds/testdata/rbac/v2-single-permission-with-kitchen-sink-perms--httpfilter.golden new file mode 100644 index 0000000000..7359d15faa --- /dev/null +++ b/agent/xds/testdata/rbac/v2-single-permission-with-kitchen-sink-perms--httpfilter.golden @@ -0,0 +1,226 @@ +{ + "name": "envoy.filters.http.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", + "rules": { + "policies": { + "consul-intentions-layer7-0": { + "permissions": [ + { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "prefix": "/v1" + } + } + }, + { + "notRule": { + "urlPath": { + "path": { + "exact": "/v1/secret" + } + } + } + } + ] + } + }, + { + "andRules": { + "rules": [ + { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "safeRegex": { + "regex": "/v[123]" + } + } + } + }, + { + "header": { + "name": ":method", + "stringMatch": { + "safeRegex": { + "regex": "GET|HEAD|OPTIONS" + } + } + } + } + ] + } + }, + { + "notRule": { + "urlPath": { + "path": { + "exact": "/v1/secret" + } + } + } + }, + { + "notRule": { + "andRules": { + "rules": [ + { + "urlPath": { + "path": { + "exact": "/v1/admin" + } + } + }, + { + "header": { + "name": ":method", + "stringMatch": { + "safeRegex": { + "regex": "GET" + } + } + } + } + ] + } + } + } + ] + } + }, + { + "andRules": { + "rules": [ + { + "andRules": { + "rules": [ + { + "header": { + "name": "x-foo", + "presentMatch": true + } + }, + { + "header": { + "name": "x-bar", + "stringMatch": { + "exact": "xyz" + } + } + }, + { + "header": { + "name": "x-dib", + "stringMatch": { + "prefix": "gaz" + } + } + }, + { + "header": { + "name": "x-gir", + "stringMatch": { + "suffix": "zim" + } + } + }, + { + "header": { + "name": "x-zim", + "stringMatch": { + "safeRegex": { + "regex": "gi[rR]" + } + } + } + }, + { + "header": { + "invertMatch": true, + "name": "z-foo", + "presentMatch": true + } + }, + { + "header": { + "invertMatch": true, + "name": "z-bar", + "stringMatch": { + "exact": "xyz" + } + } + }, + { + "header": { + "invertMatch": true, + "name": "z-dib", + "stringMatch": { + "prefix": "gaz" + } + } + }, + { + "header": { + "invertMatch": true, + "name": "z-gir", + "stringMatch": { + "suffix": "zim" + } + } + }, + { + "header": { + "invertMatch": true, + "name": "z-zim", + "stringMatch": { + "safeRegex": { + "regex": "gi[rR]" + } + } + } + } + ] + } + }, + { + "notRule": { + "urlPath": { + "path": { + "exact": "/v1/secret" + } + } + } + }, + { + "notRule": { + "header": { + "name": "x-baz", + "presentMatch": true + } + } + } + ] + } + } + ], + "principals": [ + { + "authenticated": { + "principalName": { + "safeRegex": { + "regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$" + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/agent/xds/testdata/rbac/v2-single-permission-with-kitchen-sink-perms.golden b/agent/xds/testdata/rbac/v2-single-permission-with-kitchen-sink-perms.golden new file mode 100644 index 0000000000..b1fa47ea4c --- /dev/null +++ b/agent/xds/testdata/rbac/v2-single-permission-with-kitchen-sink-perms.golden @@ -0,0 +1,8 @@ +{ + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": {}, + "statPrefix": "connect_authz" + } +} \ No newline at end of file diff --git a/agent/xdsv2/rbac_resources.go b/agent/xdsv2/rbac_resources.go index dbe83eb903..ba4e1599d5 100644 --- a/agent/xdsv2/rbac_resources.go +++ b/agent/xdsv2/rbac_resources.go @@ -226,139 +226,179 @@ func makeL7RBACPolicy(p *pbproxystate.Permission) *envoy_rbac_v3.Policy { } } -func permissionsFromDestinationRules(drs []*pbproxystate.DestinationRule) []*envoy_rbac_v3.Permission { +func translateRule(dr *pbproxystate.DestinationRule) *envoy_rbac_v3.Permission { var perms []*envoy_rbac_v3.Permission - - for _, dr := range drs { - // paths - switch { - case dr.PathExact != "": - perms = append(perms, &envoy_rbac_v3.Permission{ - Rule: &envoy_rbac_v3.Permission_UrlPath{ - UrlPath: &envoy_matcher_v3.PathMatcher{ - Rule: &envoy_matcher_v3.PathMatcher_Path{ - Path: &envoy_matcher_v3.StringMatcher{ - MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{ - Exact: dr.PathExact, - }, + // paths + switch { + case dr.PathExact != "": + perms = append(perms, &envoy_rbac_v3.Permission{ + Rule: &envoy_rbac_v3.Permission_UrlPath{ + UrlPath: &envoy_matcher_v3.PathMatcher{ + Rule: &envoy_matcher_v3.PathMatcher_Path{ + Path: &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{ + Exact: dr.PathExact, }, }, }, }, - }) - case dr.PathPrefix != "": - perms = append(perms, &envoy_rbac_v3.Permission{ - Rule: &envoy_rbac_v3.Permission_UrlPath{ - UrlPath: &envoy_matcher_v3.PathMatcher{ - Rule: &envoy_matcher_v3.PathMatcher_Path{ - Path: &envoy_matcher_v3.StringMatcher{ - MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{ - Prefix: dr.PathPrefix, - }, + }, + }) + case dr.PathPrefix != "": + perms = append(perms, &envoy_rbac_v3.Permission{ + Rule: &envoy_rbac_v3.Permission_UrlPath{ + UrlPath: &envoy_matcher_v3.PathMatcher{ + Rule: &envoy_matcher_v3.PathMatcher_Path{ + Path: &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{ + Prefix: dr.PathPrefix, }, }, }, }, - }) - case dr.PathRegex != "": - perms = append(perms, &envoy_rbac_v3.Permission{ - Rule: &envoy_rbac_v3.Permission_UrlPath{ - UrlPath: &envoy_matcher_v3.PathMatcher{ - Rule: &envoy_matcher_v3.PathMatcher_Path{ - Path: &envoy_matcher_v3.StringMatcher{ - MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{ - SafeRegex: response.MakeEnvoyRegexMatch(dr.PathRegex), - }, + }, + }) + case dr.PathRegex != "": + perms = append(perms, &envoy_rbac_v3.Permission{ + Rule: &envoy_rbac_v3.Permission_UrlPath{ + UrlPath: &envoy_matcher_v3.PathMatcher{ + Rule: &envoy_matcher_v3.PathMatcher_Path{ + Path: &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{ + SafeRegex: response.MakeEnvoyRegexMatch(dr.PathRegex), }, }, }, }, - }) - } + }, + }) + } - // methods - if len(dr.Methods) > 0 { - methodHeaderRegex := strings.Join(dr.Methods, "|") - eh := &envoy_route_v3.HeaderMatcher{ - Name: ":method", - HeaderMatchSpecifier: &envoy_route_v3.HeaderMatcher_StringMatch{ - StringMatch: response.MakeEnvoyStringMatcher(methodHeaderRegex), + // methods + if len(dr.Methods) > 0 { + methodHeaderRegex := strings.Join(dr.Methods, "|") + eh := &envoy_route_v3.HeaderMatcher{ + Name: ":method", + HeaderMatchSpecifier: &envoy_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{ + SafeRegex: response.MakeEnvoyRegexMatch(methodHeaderRegex), + }, }, - } - perms = append(perms, &envoy_rbac_v3.Permission{ - Rule: &envoy_rbac_v3.Permission_Header{ - Header: eh, - }}) + }, } + perms = append(perms, &envoy_rbac_v3.Permission{ + Rule: &envoy_rbac_v3.Permission_Header{ + Header: eh, + }}) + } - // headers - for _, hdr := range dr.DestinationRuleHeader { - eh := &envoy_route_v3.HeaderMatcher{ - Name: hdr.Name, - } + // headers + for _, hdr := range dr.DestinationRuleHeader { + eh := &envoy_route_v3.HeaderMatcher{ + Name: hdr.Name, + } - switch { - case hdr.Exact != "": - eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_StringMatch{ - StringMatch: &envoy_matcher_v3.StringMatcher{ - MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{ - Exact: hdr.Exact, - }, - IgnoreCase: false, + switch { + case hdr.Exact != "": + eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{ + Exact: hdr.Exact, }, - } - case hdr.Regex != "": - eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_StringMatch{ - StringMatch: &envoy_matcher_v3.StringMatcher{ - MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{ - SafeRegex: response.MakeEnvoyRegexMatch(hdr.Regex), - }, - IgnoreCase: false, + IgnoreCase: false, + }, + } + case hdr.Regex != "": + eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{ + SafeRegex: response.MakeEnvoyRegexMatch(hdr.Regex), }, - } + IgnoreCase: false, + }, + } - case hdr.Prefix != "": - eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_StringMatch{ - StringMatch: &envoy_matcher_v3.StringMatcher{ - MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{ - Prefix: hdr.Prefix, - }, - IgnoreCase: false, + case hdr.Prefix != "": + eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{ + Prefix: hdr.Prefix, }, - } + IgnoreCase: false, + }, + } - case hdr.Suffix != "": - eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_StringMatch{ - StringMatch: &envoy_matcher_v3.StringMatcher{ - MatchPattern: &envoy_matcher_v3.StringMatcher_Suffix{ - Suffix: hdr.Suffix, - }, - IgnoreCase: false, + case hdr.Suffix != "": + eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Suffix{ + Suffix: hdr.Suffix, }, - } - - case hdr.Present: - eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_PresentMatch{ - PresentMatch: true, - } - default: - continue // skip this impossible situation + IgnoreCase: false, + }, } - if hdr.Invert { - eh.InvertMatch = true + case hdr.Present: + eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_PresentMatch{ + PresentMatch: true, } + default: + continue // skip this impossible situation + } - perms = append(perms, &envoy_rbac_v3.Permission{ - Rule: &envoy_rbac_v3.Permission_Header{ - Header: eh, - }, + if hdr.Invert { + eh.InvertMatch = true + } + + perms = append(perms, &envoy_rbac_v3.Permission{ + Rule: &envoy_rbac_v3.Permission_Header{ + Header: eh, + }, + }) + } + return combineAndPermissions(perms) +} + +func permissionsFromDestinationRules(drs []*pbproxystate.DestinationRule) []*envoy_rbac_v3.Permission { + var perms []*envoy_rbac_v3.Permission + for _, dr := range drs { + subPerms := make([]*envoy_rbac_v3.Permission, len(dr.Exclude)) + for i, er := range dr.Exclude { + translated := translateRule(&pbproxystate.DestinationRule{ + PathExact: er.PathExact, + PathPrefix: er.PathPrefix, + PathRegex: er.PathRegex, + Methods: er.Methods, + DestinationRuleHeader: er.Headers, }) + subPerms[i] = &envoy_rbac_v3.Permission{ + Rule: &envoy_rbac_v3.Permission_NotRule{NotRule: translated}, + } } + subPerms = append([]*envoy_rbac_v3.Permission{translateRule(dr)}, subPerms...) + perms = append(perms, combineAndPermissions(subPerms)) } return perms } +func combineAndPermissions(perms []*envoy_rbac_v3.Permission) *envoy_rbac_v3.Permission { + switch len(perms) { + case 0: + return anyPermission() + case 1: + return perms[0] + default: + return &envoy_rbac_v3.Permission{ + Rule: &envoy_rbac_v3.Permission_AndRules{ + AndRules: &envoy_rbac_v3.Permission_Set{ + Rules: perms, + }, + }, + } + } +} + func toEnvoyPrincipal(p *pbproxystate.Principal) *envoy_rbac_v3.Principal { includePrincipal := principal(p.Spiffe) diff --git a/internal/auth/internal/types/errors.go b/internal/auth/internal/types/errors.go index 008a6a8647..f3645ded6a 100644 --- a/internal/auth/internal/types/errors.go +++ b/internal/auth/internal/types/errors.go @@ -6,10 +6,12 @@ package types import "errors" var ( - errSourcesTenancy = errors.New("permissions sources may not specify partitions, peers, and sameness_groups together") - errSourceWildcards = errors.New("permission sources may not have wildcard namespaces and explicit names.") - errSourceExcludes = errors.New("must be defined on wildcard sources") - errInvalidPrefixValues = errors.New("prefix values, regex values, and explicit names must not combined") - errHeaderRulesInvalid = errors.New("header rule must contain header name") - ErrWildcardNotSupported = errors.New("traffic permissions without explicit destinations are not yet supported") + errSourcesTenancy = errors.New("permissions sources may not specify partitions, peers, and sameness_groups together") + errSourceWildcards = errors.New("permission sources may not have wildcard namespaces and explicit names") + errSourceExcludes = errors.New("must be defined on wildcard sources") + errInvalidPrefixValues = errors.New("prefix values, regex values, and explicit names must not combined") + errInvalidRule = errors.New("rules must contain path, method, header, or port fields") + errExclValuesMustBeSubset = errors.New("exclude permission rules must select a subset of ports and methods defined in the destination rule") + errHeaderRulesInvalid = errors.New("header rule must contain header name") + ErrWildcardNotSupported = errors.New("traffic permissions without explicit destinations are not yet supported") ) diff --git a/internal/auth/internal/types/traffic_permissions.go b/internal/auth/internal/types/traffic_permissions.go index 60e2dc1223..71aa1b7d24 100644 --- a/internal/auth/internal/types/traffic_permissions.go +++ b/internal/auth/internal/types/traffic_permissions.go @@ -4,6 +4,8 @@ package types import ( + "golang.org/x/exp/slices" + "github.com/hashicorp/go-multierror" "github.com/hashicorp/consul/acl" @@ -254,6 +256,12 @@ func validatePermission(p *pbauth.Permission, id *pbresource.ID, wrapErr func(er } } } + if dest.IsEmpty() { + merr = multierror.Append(merr, wrapDestRuleErr(resource.ErrInvalidListElement{ + Name: "destination_rule", + Wrapped: errInvalidRule, + })) + } if len(dest.Exclude) > 0 { for e, excl := range dest.Exclude { wrapExclPermRuleErr := func(err error) error { @@ -271,6 +279,43 @@ func validatePermission(p *pbauth.Permission, id *pbresource.ID, wrapErr func(er Wrapped: errInvalidPrefixValues, })) } + for eh, hdr := range excl.Headers { + wrapExclHeaderErr := func(err error) error { + return wrapDestRuleErr(resource.ErrInvalidListElement{ + Name: "exclude_permission_header_rules", + Index: eh, + Wrapped: err, + }) + } + if len(hdr.Name) == 0 { + merr = multierror.Append(merr, wrapExclHeaderErr(resource.ErrInvalidListElement{ + Name: "exclude_permission_header_rule", + Wrapped: errHeaderRulesInvalid, + })) + } + } + for _, m := range excl.Methods { + if len(dest.Methods) != 0 && !slices.Contains(dest.Methods, m) { + merr = multierror.Append(merr, wrapExclPermRuleErr(resource.ErrInvalidListElement{ + Name: "exclude_permission_header_rule", + Wrapped: errExclValuesMustBeSubset, + })) + } + } + for _, port := range excl.PortNames { + if len(dest.PortNames) != 0 && !slices.Contains(dest.PortNames, port) { + merr = multierror.Append(merr, wrapExclPermRuleErr(resource.ErrInvalidListElement{ + Name: "exclude_permission_header_rule", + Wrapped: errExclValuesMustBeSubset, + })) + } + } + if excl.IsEmpty() { + merr = multierror.Append(merr, wrapExclPermRuleErr(resource.ErrInvalidListElement{ + Name: "exclude_permission_rule", + Wrapped: errInvalidRule, + })) + } } } } diff --git a/internal/auth/internal/types/traffic_permissions_ce_test.go b/internal/auth/internal/types/traffic_permissions_ce_test.go index e25360229a..d1ba5d3bb4 100644 --- a/internal/auth/internal/types/traffic_permissions_ce_test.go +++ b/internal/auth/internal/types/traffic_permissions_ce_test.go @@ -8,10 +8,11 @@ package types import ( "testing" + "github.com/stretchr/testify/require" + "github.com/hashicorp/consul/internal/resource/resourcetest" pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/require" ) func TestValidateTrafficPermissionsActionCE(t *testing.T) { diff --git a/internal/auth/internal/types/traffic_permissions_test.go b/internal/auth/internal/types/traffic_permissions_test.go index 7e780e9e2f..9d614809b8 100644 --- a/internal/auth/internal/types/traffic_permissions_test.go +++ b/internal/auth/internal/types/traffic_permissions_test.go @@ -42,6 +42,37 @@ func TestValidateTrafficPermissions(t *testing.T) { Action: pbauth.Action_ACTION_ALLOW, }, }, + "ok-permissions": { + tp: &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{IdentityName: "wi-1"}, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + IdentityName: "wi-2", + Namespace: "default", + Partition: "default", + }, + { + IdentityName: "wi-1", + Namespace: "default", + Partition: "ap1", + }, + }, + DestinationRules: []*pbauth.DestinationRule{ + { + PathPrefix: "/", + Methods: []string{"GET"}, + Headers: []*pbauth.DestinationRuleHeader{{Name: "X-Consul-Token", Present: false, Invert: true}}, + PortNames: []string{"https"}, + Exclude: []*pbauth.ExcludePermissionRule{{PathExact: "/admin"}}, + }, + }, + }, + }, + }, + }, "unspecified-action": { // Any type other than the TrafficPermissions type would work // to cause the error we are expecting @@ -441,6 +472,82 @@ func permissionsTestCases() map[string]permissionTestCase { }, expectErr: `invalid element at index 0 of list "destination_rule": prefix values, regex values, and explicit names must not combined`, }, + "destination-rule-empty": { + p: &pbauth.Permission{ + Sources: []*pbauth.Source{{IdentityName: "i1"}}, + DestinationRules: []*pbauth.DestinationRule{ + {}, + }, + }, + expectErr: `invalid element at index 0 of list "destination_rules": invalid element at index 0 of list "destination_rule": rules must contain path, method, header, or port fields`, + }, + "destination-rule-only-empty-exclude": { + p: &pbauth.Permission{ + Sources: []*pbauth.Source{{IdentityName: "i1"}}, + DestinationRules: []*pbauth.DestinationRule{ + {Exclude: []*pbauth.ExcludePermissionRule{{}}}, + }, + }, + expectErr: `invalid element at index 0 of list "destination_rules": invalid element at index 0 of list "exclude_permission_rules": invalid element at index 0 of list "exclude_permission_rule": rules must contain path, method, header, or port fields`, + }, + "destination-rule-empty-exclude": { + p: &pbauth.Permission{ + Sources: []*pbauth.Source{{IdentityName: "i1"}}, + DestinationRules: []*pbauth.DestinationRule{ + { + PathExact: "/", + Exclude: []*pbauth.ExcludePermissionRule{{}}, + }, + }, + }, + expectErr: `invalid element at index 0 of list "destination_rules": invalid element at index 0 of list "exclude_permission_rules": invalid element at index 0 of list "exclude_permission_rule": rules must contain path, method, header, or port fields`, + }, + "destination-rule-mismatched-ports-exclude": { + p: &pbauth.Permission{ + Sources: []*pbauth.Source{{IdentityName: "i1"}}, + DestinationRules: []*pbauth.DestinationRule{ + { + PortNames: []string{"foo"}, + Exclude: []*pbauth.ExcludePermissionRule{{PortNames: []string{"bar"}}}, + }, + }, + }, + expectErr: `invalid element at index 0 of list "destination_rules": invalid element at index 0 of list "exclude_permission_rules": invalid element at index 0 of list "exclude_permission_header_rule": exclude permission rules must select a subset of ports and methods defined in the destination rule`, + }, + "destination-rule-ports-exclude": { + p: &pbauth.Permission{ + Sources: []*pbauth.Source{{IdentityName: "i1"}}, + DestinationRules: []*pbauth.DestinationRule{ + { + Exclude: []*pbauth.ExcludePermissionRule{{PortNames: []string{"bar"}}}, + }, + }, + }, + }, + "destination-rule-invalid-headers-exclude": { + p: &pbauth.Permission{ + Sources: []*pbauth.Source{{IdentityName: "i1"}}, + DestinationRules: []*pbauth.DestinationRule{ + { + Headers: []*pbauth.DestinationRuleHeader{{Name: "auth"}}, + Exclude: []*pbauth.ExcludePermissionRule{{Headers: []*pbauth.DestinationRuleHeader{{}}}}, + }, + }, + }, + expectErr: `invalid element at index 0 of list "destination_rules": invalid element at index 0 of list "exclude_permission_header_rules": invalid element at index 0 of list "exclude_permission_header_rule": header rule must contain header name`, + }, + "destination-rule-mismatched-methods-exclude": { + p: &pbauth.Permission{ + Sources: []*pbauth.Source{{IdentityName: "i1"}}, + DestinationRules: []*pbauth.DestinationRule{ + { + Methods: []string{"post"}, + Exclude: []*pbauth.ExcludePermissionRule{{Methods: []string{"patch"}}}, + }, + }, + }, + expectErr: `invalid element at index 0 of list "destination_rules": invalid element at index 0 of list "exclude_permission_rules": invalid element at index 0 of list "exclude_permission_header_rule": exclude permission rules must select a subset of ports and methods defined in the destination rule`, + }, } } diff --git a/internal/mesh/internal/controllers/sidecarproxy/builder/local_app.go b/internal/mesh/internal/controllers/sidecarproxy/builder/local_app.go index 50fb989990..735d5634b6 100644 --- a/internal/mesh/internal/controllers/sidecarproxy/builder/local_app.go +++ b/internal/mesh/internal/controllers/sidecarproxy/builder/local_app.go @@ -135,60 +135,85 @@ func destinationRulesByPort(allPorts []string, destinationRules []*pbauth.Destin for _, p := range allPorts { out[p] = nil } - return out } for _, destinationRule := range destinationRules { - ports, dr := convertDestinationRule(allPorts, destinationRule) - for _, p := range ports { - if dr == nil { + portRules := convertDestinationRule(allPorts, destinationRule) + for p, pr := range portRules { + if pr.rule == nil { out[p] = nil continue } - out[p] = append(out[p], dr) + out[p] = append(out[p], pr.rule) } } return out } -//nolint:unparam -func convertDestinationRule(allPorts []string, dr *pbauth.DestinationRule) ([]string, *pbproxystate.DestinationRule) { - ports := make(map[string]struct{}) - if len(dr.PortNames) > 0 { - for _, p := range dr.PortNames { - ports[p] = struct{}{} - } - } else { - for _, p := range allPorts { - ports[p] = struct{}{} - } - } +type PortRule struct { + rule *pbproxystate.DestinationRule +} - for _, exclude := range dr.Exclude { - for _, p := range exclude.PortNames { - delete(ports, p) +func convertDestinationRule(allPorts []string, dr *pbauth.DestinationRule) map[string]*PortRule { + portRules := make(map[string]*PortRule) + targetPorts := allPorts + if len(dr.PortNames) > 0 { + targetPorts = dr.PortNames + } + for _, p := range targetPorts { + if dr.PortsOnly() { + portRules[p] = &PortRule{} + for _, exclude := range dr.Exclude { + for _, ep := range exclude.PortNames { + delete(portRules, ep) + } + } + } else { + portRules[p] = makePortRule(dr, p) } } + return portRules +} - var out []string - for p := range ports { - out = append(out, p) - } - - if len(dr.String()) == 0 { - return out, nil - } - +func makePortRule(dr *pbauth.DestinationRule, p string) *PortRule { psdr := &pbproxystate.DestinationRule{ PathExact: dr.PathExact, PathPrefix: dr.PathPrefix, PathRegex: dr.PathRegex, Methods: dr.Methods, } - hrs := make([]*pbproxystate.DestinationRuleHeader, len(dr.Headers)) - for i, hr := range dr.Headers { + psdr.DestinationRuleHeader = destinationRuleHeaders(dr.Headers) + + var excls []*pbproxystate.ExcludePermissionRule + for _, ex := range dr.Exclude { + if len(ex.PortNames) == 0 || listContains(ex.PortNames, p) { + excls = append(excls, &pbproxystate.ExcludePermissionRule{ + PathExact: ex.PathExact, + PathPrefix: ex.PathPrefix, + PathRegex: ex.PathRegex, + Methods: ex.Methods, + Headers: destinationRuleHeaders(ex.Headers), + }) + } + } + psdr.Exclude = excls + return &PortRule{psdr} +} + +func listContains(list []string, str string) bool { + for _, item := range list { + if item == str { + return true + } + } + return false +} + +func destinationRuleHeaders(headers []*pbauth.DestinationRuleHeader) []*pbproxystate.DestinationRuleHeader { + hrs := make([]*pbproxystate.DestinationRuleHeader, len(headers)) + for i, hr := range headers { hrs[i] = &pbproxystate.DestinationRuleHeader{ Name: hr.Name, Present: hr.Present, @@ -199,13 +224,7 @@ func convertDestinationRule(allPorts []string, dr *pbauth.DestinationRule) ([]st Invert: hr.Invert, } } - psdr.DestinationRuleHeader = hrs - - if len(psdr.String()) > 0 { - return out, psdr - } - - return out, nil + return hrs } func makePrincipals(trustDomain string, perm *pbauth.Permission) []*pbproxystate.Principal { diff --git a/internal/mesh/internal/controllers/sidecarproxy/builder/local_app_test.go b/internal/mesh/internal/controllers/sidecarproxy/builder/local_app_test.go index b1399cfa23..2fa095da11 100644 --- a/internal/mesh/internal/controllers/sidecarproxy/builder/local_app_test.go +++ b/internal/mesh/internal/controllers/sidecarproxy/builder/local_app_test.go @@ -704,6 +704,392 @@ func TestBuildTrafficPermissions(t *testing.T) { }, }, }, + "destination rules l7, exclude rules for one port": { + defaultAllow: true, + workloadPorts: map[string]*pbcatalog.WorkloadPort{ + "p1": { + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + "p2": { + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + }, + ctp: &pbauth.ComputedTrafficPermissions{ + AllowPermissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + IdentityName: "foo", + Partition: tenancy.Partition, + Namespace: tenancy.Namespace, + }, + }, + DestinationRules: []*pbauth.DestinationRule{ + { + PathPrefix: "/", + Methods: []string{"get", "post"}, + Exclude: []*pbauth.ExcludePermissionRule{ + { + PathExact: "/secret", + Headers: []*pbauth.DestinationRuleHeader{{Name: "restricted"}}, + PortNames: []string{"p2"}, + }, + }, + }, + }, + }, + }, + }, + expected: map[string]*pbproxystate.TrafficPermissions{ + "p1": { + DefaultAllow: false, + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: &pbproxystate.Spiffe{Regex: fmt.Sprintf("^spiffe://test.consul/ap/%s/ns/%s/identity/foo$", tenancy.Partition, tenancy.Namespace)}, + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/", + Methods: []string{"get", "post"}, + }, + }, + }, + }, + }, + "p2": { + DefaultAllow: false, + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: &pbproxystate.Spiffe{Regex: fmt.Sprintf("^spiffe://test.consul/ap/%s/ns/%s/identity/foo$", tenancy.Partition, tenancy.Namespace)}, + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/", + Methods: []string{"get", "post"}, + Exclude: []*pbproxystate.ExcludePermissionRule{ + { + PathExact: "/secret", + Headers: []*pbproxystate.DestinationRuleHeader{{Name: "restricted"}}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "destination rules l7, rules by port": { + defaultAllow: true, + workloadPorts: map[string]*pbcatalog.WorkloadPort{ + "p1": { + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + "p2": { + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + "p3": { + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + }, + }, + ctp: &pbauth.ComputedTrafficPermissions{ + AllowPermissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + IdentityName: "foo", + Partition: tenancy.Partition, + Namespace: tenancy.Namespace, + }, + }, + DestinationRules: []*pbauth.DestinationRule{ + { + PathPrefix: "/", + Methods: []string{"get", "post"}, + PortNames: []string{"p1", "p2"}, + Exclude: []*pbauth.ExcludePermissionRule{ + { + PathExact: "/secret", + Headers: []*pbauth.DestinationRuleHeader{{Name: "restricted"}}, + PortNames: []string{"p2"}, + }, + }, + }, + { + PortNames: []string{"p3"}, + }, + }, + }, + }, + }, + expected: map[string]*pbproxystate.TrafficPermissions{ + "p1": { + DefaultAllow: false, + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: &pbproxystate.Spiffe{Regex: fmt.Sprintf("^spiffe://test.consul/ap/%s/ns/%s/identity/foo$", tenancy.Partition, tenancy.Namespace)}, + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/", + Methods: []string{"get", "post"}, + }, + }, + }, + }, + }, + "p2": { + DefaultAllow: false, + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: &pbproxystate.Spiffe{Regex: fmt.Sprintf("^spiffe://test.consul/ap/%s/ns/%s/identity/foo$", tenancy.Partition, tenancy.Namespace)}, + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/", + Methods: []string{"get", "post"}, + Exclude: []*pbproxystate.ExcludePermissionRule{ + { + PathExact: "/secret", + Headers: []*pbproxystate.DestinationRuleHeader{{Name: "restricted"}}, + }, + }, + }, + }, + }, + }, + }, + "p3": { + DefaultAllow: false, + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: &pbproxystate.Spiffe{Regex: fmt.Sprintf("^spiffe://test.consul/ap/%s/ns/%s/identity/foo$", tenancy.Partition, tenancy.Namespace)}, + }, + }, + }, + }, + }, + }, + }, + "destination rules l7, exclude infer ports": { + defaultAllow: true, + workloadPorts: map[string]*pbcatalog.WorkloadPort{ + "p1": { + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + "p2": { + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + }, + ctp: &pbauth.ComputedTrafficPermissions{ + AllowPermissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + IdentityName: "foo", + Partition: tenancy.Partition, + Namespace: tenancy.Namespace, + }, + }, + DestinationRules: []*pbauth.DestinationRule{ + { + PathPrefix: "/", + Exclude: []*pbauth.ExcludePermissionRule{ + { + PathExact: "/secret", + }, + }, + }, + }, + }, + }, + }, + expected: map[string]*pbproxystate.TrafficPermissions{ + "p1": { + DefaultAllow: false, + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: &pbproxystate.Spiffe{Regex: fmt.Sprintf("^spiffe://test.consul/ap/%s/ns/%s/identity/foo$", tenancy.Partition, tenancy.Namespace)}, + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/", + Exclude: []*pbproxystate.ExcludePermissionRule{ + { + PathExact: "/secret", + }, + }, + }, + }, + }, + }, + }, + "p2": { + DefaultAllow: false, + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: &pbproxystate.Spiffe{Regex: fmt.Sprintf("^spiffe://test.consul/ap/%s/ns/%s/identity/foo$", tenancy.Partition, tenancy.Namespace)}, + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/", + Exclude: []*pbproxystate.ExcludePermissionRule{ + { + PathExact: "/secret", + }, + }, + }, + }, + }, + }, + }, + }, + }, + "destination rules l7, headers and methods exclude": { + defaultAllow: true, + workloadPorts: map[string]*pbcatalog.WorkloadPort{ + "p1": { + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + "p2": { + Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, + }, + }, + ctp: &pbauth.ComputedTrafficPermissions{ + AllowPermissions: []*pbauth.Permission{ + { + Sources: []*pbauth.Source{ + { + IdentityName: "foo", + Partition: tenancy.Partition, + Namespace: tenancy.Namespace, + }, + }, + DestinationRules: []*pbauth.DestinationRule{ + { + PathPrefix: "/", + Headers: []*pbauth.DestinationRuleHeader{ + {Name: "header1", Present: true}, + }, + Methods: []string{"POST, GET"}, + Exclude: []*pbauth.ExcludePermissionRule{ + { + PathExact: "/secret", + Headers: []*pbauth.DestinationRuleHeader{ + {Name: "header2", Present: true}, + }, + Methods: []string{"POST"}, + }, + { + PathExact: "/config", + Headers: []*pbauth.DestinationRuleHeader{ + {Name: "header3", Present: true}, + }, + Methods: []string{"GET"}, + }, + }, + }, + }, + }, + }, + }, + expected: map[string]*pbproxystate.TrafficPermissions{ + "p1": { + DefaultAllow: false, + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: &pbproxystate.Spiffe{Regex: fmt.Sprintf("^spiffe://test.consul/ap/%s/ns/%s/identity/foo$", tenancy.Partition, tenancy.Namespace)}, + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/", + DestinationRuleHeader: []*pbproxystate.DestinationRuleHeader{ + {Name: "header1", Present: true}, + }, + Methods: []string{"POST, GET"}, + Exclude: []*pbproxystate.ExcludePermissionRule{ + { + PathExact: "/secret", + Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "header2", Present: true}, + }, + Methods: []string{"POST"}, + }, + { + PathExact: "/config", + Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "header3", Present: true}, + }, + Methods: []string{"GET"}, + }, + }, + }, + }, + }, + }, + }, + "p2": { + DefaultAllow: false, + AllowPermissions: []*pbproxystate.Permission{ + { + Principals: []*pbproxystate.Principal{ + { + Spiffe: &pbproxystate.Spiffe{Regex: fmt.Sprintf("^spiffe://test.consul/ap/%s/ns/%s/identity/foo$", tenancy.Partition, tenancy.Namespace)}, + }, + }, + DestinationRules: []*pbproxystate.DestinationRule{ + { + PathPrefix: "/", + DestinationRuleHeader: []*pbproxystate.DestinationRuleHeader{ + {Name: "header1", Present: true}, + }, + Methods: []string{"POST, GET"}, + Exclude: []*pbproxystate.ExcludePermissionRule{ + { + PathExact: "/secret", + Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "header2", Present: true}, + }, + Methods: []string{"POST"}, + }, + { + PathExact: "/config", + Headers: []*pbproxystate.DestinationRuleHeader{ + {Name: "header3", Present: true}, + }, + Methods: []string{"GET"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, } for name, tc := range cases { diff --git a/proto-public/pbauth/v2beta1/traffic_permission_extras_test.go b/proto-public/pbauth/v2beta1/traffic_permission_extras_test.go new file mode 100644 index 0000000000..c35c1b5a12 --- /dev/null +++ b/proto-public/pbauth/v2beta1/traffic_permission_extras_test.go @@ -0,0 +1,63 @@ +package authv2beta1 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTrafficPermissions_PortsOnly(t *testing.T) { + t.Run("nil", func(t *testing.T) { + var dr *DestinationRule + require.False(t, dr.PortsOnly()) + }) + t.Run("empty", func(t *testing.T) { + dr := &DestinationRule{} + require.False(t, dr.PortsOnly()) + }) + t.Run("ports", func(t *testing.T) { + dr := &DestinationRule{ + PortNames: []string{"foo"}, + } + require.True(t, dr.PortsOnly()) + }) + t.Run("excl-ports", func(t *testing.T) { + dr := &DestinationRule{ + Exclude: []*ExcludePermissionRule{{PortNames: []string{"foo"}}}, + } + require.True(t, dr.PortsOnly()) + }) + t.Run("ports-and-excl-ports", func(t *testing.T) { + dr := &DestinationRule{ + PortNames: []string{"foo", "bar"}, + Exclude: []*ExcludePermissionRule{{PortNames: []string{"foo"}}}, + } + require.True(t, dr.PortsOnly()) + }) + t.Run("methods", func(t *testing.T) { + dr := &DestinationRule{ + Methods: []string{"put"}, + } + require.False(t, dr.PortsOnly()) + }) + t.Run("path", func(t *testing.T) { + dr := &DestinationRule{ + PathRegex: "*", + PortNames: []string{"foo"}, + } + require.False(t, dr.PortsOnly()) + }) + t.Run("headers", func(t *testing.T) { + dr := &DestinationRule{ + Exclude: []*ExcludePermissionRule{{Headers: []*DestinationRuleHeader{{Name: "Authorization"}}, PortNames: []string{"foo"}}}, + } + require.False(t, dr.PortsOnly()) + }) + t.Run("path-and-exclports", func(t *testing.T) { + dr := &DestinationRule{ + PathExact: "/", + Exclude: []*ExcludePermissionRule{{PortNames: []string{"foo"}}}, + } + require.False(t, dr.PortsOnly()) + }) +} diff --git a/proto-public/pbauth/v2beta1/traffic_permissions_extras.go b/proto-public/pbauth/v2beta1/traffic_permissions_extras.go new file mode 100644 index 0000000000..2caa6635c3 --- /dev/null +++ b/proto-public/pbauth/v2beta1/traffic_permissions_extras.go @@ -0,0 +1,60 @@ +package authv2beta1 + +// IsEmpty returns true if a destination rule has no fields defined. +func (d *DestinationRule) IsEmpty() bool { + if d == nil { + return true + } + return len(d.PathExact) == 0 && + len(d.PathPrefix) == 0 && + len(d.PathRegex) == 0 && + len(d.Methods) == 0 && + len(d.Headers) == 0 && + len(d.PortNames) == 0 && + len(d.Exclude) == 0 +} + +// IsEmpty returns true if an exclude permission has no fields defined. +func (e *ExcludePermissionRule) IsEmpty() bool { + if e == nil { + return true + } + return len(e.PathExact) == 0 && + len(e.PathPrefix) == 0 && + len(e.PathRegex) == 0 && + len(e.Methods) == 0 && + len(e.Headers) == 0 && + len(e.PortNames) == 0 +} + +// PortsOnly returns true if a destination rule only specifies port criteria +func (d *DestinationRule) PortsOnly() bool { + if d.IsEmpty() { + return false + } + excludePortsOnly := true + for _, e := range d.Exclude { + if !e.PortsOnly() { + excludePortsOnly = false + } + } + return len(d.PathExact) == 0 && + len(d.PathPrefix) == 0 && + len(d.PathRegex) == 0 && + len(d.Methods) == 0 && + len(d.Headers) == 0 && + (len(d.PortNames) != 0 || excludePortsOnly) +} + +// PortsOnly returns true if an exclude rule only specifies port criteria +func (e *ExcludePermissionRule) PortsOnly() bool { + if e == nil { + return false + } + return len(e.PathExact) == 0 && + len(e.PathPrefix) == 0 && + len(e.PathRegex) == 0 && + len(e.Methods) == 0 && + len(e.Headers) == 0 && + len(e.PortNames) != 0 +} diff --git a/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.pb.binary.go b/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.pb.binary.go index ca4199c72a..fe9145c721 100644 --- a/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.pb.binary.go +++ b/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.pb.binary.go @@ -66,3 +66,13 @@ func (msg *DestinationRuleHeader) MarshalBinary() ([]byte, error) { func (msg *DestinationRuleHeader) UnmarshalBinary(b []byte) error { return proto.Unmarshal(b, msg) } + +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *ExcludePermissionRule) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *ExcludePermissionRule) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} diff --git a/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.pb.go b/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.pb.go index d6bb8cc435..4a6b430c57 100644 --- a/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.pb.go +++ b/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.pb.go @@ -269,6 +269,7 @@ type DestinationRule struct { PathRegex string `protobuf:"bytes,3,opt,name=path_regex,json=pathRegex,proto3" json:"path_regex,omitempty"` Methods []string `protobuf:"bytes,4,rep,name=methods,proto3" json:"methods,omitempty"` DestinationRuleHeader []*DestinationRuleHeader `protobuf:"bytes,5,rep,name=destination_rule_header,json=destinationRuleHeader,proto3" json:"destination_rule_header,omitempty"` + Exclude []*ExcludePermissionRule `protobuf:"bytes,6,rep,name=exclude,proto3" json:"exclude,omitempty"` } func (x *DestinationRule) Reset() { @@ -338,6 +339,13 @@ func (x *DestinationRule) GetDestinationRuleHeader() []*DestinationRuleHeader { return nil } +func (x *DestinationRule) GetExclude() []*ExcludePermissionRule { + if x != nil { + return x.Exclude + } + return nil +} + type DestinationRuleHeader struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -433,6 +441,85 @@ func (x *DestinationRuleHeader) GetInvert() bool { return false } +type ExcludePermissionRule struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PathExact string `protobuf:"bytes,1,opt,name=path_exact,json=pathExact,proto3" json:"path_exact,omitempty"` + PathPrefix string `protobuf:"bytes,2,opt,name=path_prefix,json=pathPrefix,proto3" json:"path_prefix,omitempty"` + PathRegex string `protobuf:"bytes,3,opt,name=path_regex,json=pathRegex,proto3" json:"path_regex,omitempty"` + Methods []string `protobuf:"bytes,4,rep,name=methods,proto3" json:"methods,omitempty"` + Headers []*DestinationRuleHeader `protobuf:"bytes,5,rep,name=headers,proto3" json:"headers,omitempty"` +} + +func (x *ExcludePermissionRule) Reset() { + *x = ExcludePermissionRule{} + if protoimpl.UnsafeEnabled { + mi := &file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExcludePermissionRule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExcludePermissionRule) ProtoMessage() {} + +func (x *ExcludePermissionRule) ProtoReflect() protoreflect.Message { + mi := &file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExcludePermissionRule.ProtoReflect.Descriptor instead. +func (*ExcludePermissionRule) Descriptor() ([]byte, []int) { + return file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_rawDescGZIP(), []int{6} +} + +func (x *ExcludePermissionRule) GetPathExact() string { + if x != nil { + return x.PathExact + } + return "" +} + +func (x *ExcludePermissionRule) GetPathPrefix() string { + if x != nil { + return x.PathPrefix + } + return "" +} + +func (x *ExcludePermissionRule) GetPathRegex() string { + if x != nil { + return x.PathRegex + } + return "" +} + +func (x *ExcludePermissionRule) GetMethods() []string { + if x != nil { + return x.Methods + } + return nil +} + +func (x *ExcludePermissionRule) GetHeaders() []*DestinationRuleHeader { + if x != nil { + return x.Headers + } + return nil +} + var File_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto protoreflect.FileDescriptor var file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_rawDesc = []byte{ @@ -486,7 +573,7 @@ var file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_rawDesc = []byte{ 0x0a, 0x06, 0x53, 0x70, 0x69, 0x66, 0x66, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x72, 0x65, 0x67, 0x65, 0x78, 0x12, 0x1d, 0x0a, 0x0a, 0x78, 0x66, 0x63, 0x63, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x78, 0x66, 0x63, 0x63, 0x52, 0x65, 0x67, 0x65, 0x78, 0x22, 0x85, 0x02, + 0x28, 0x09, 0x52, 0x09, 0x78, 0x66, 0x63, 0x63, 0x52, 0x65, 0x67, 0x65, 0x78, 0x22, 0xe2, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x65, 0x78, 0x61, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x74, 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, @@ -503,41 +590,62 @@ var file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_rawDesc = []byte{ 0x6f, 0x78, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x15, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0xb9, 0x01, 0x0a, 0x15, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, - 0x05, 0x65, 0x78, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x78, - 0x61, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x75, 0x66, 0x66, 0x69, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x75, 0x66, - 0x66, 0x69, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x72, 0x65, 0x67, 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x76, - 0x65, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x6e, 0x76, 0x65, 0x72, - 0x74, 0x42, 0xdd, 0x02, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, - 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, - 0x76, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x70, 0x62, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x42, 0x17, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x50, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, - 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f, 0x70, 0x62, 0x6d, 0x65, 0x73, 0x68, - 0x2f, 0x76, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x62, 0x70, 0x72, 0x6f, 0x78, 0x79, - 0x73, 0x74, 0x61, 0x74, 0x65, 0xa2, 0x02, 0x05, 0x48, 0x43, 0x4d, 0x56, 0x50, 0xaa, 0x02, 0x2a, - 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, - 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x2e, 0x56, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x62, - 0x70, 0x72, 0x6f, 0x78, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0xca, 0x02, 0x2a, 0x48, 0x61, 0x73, - 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x4d, 0x65, - 0x73, 0x68, 0x5c, 0x56, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x50, 0x62, 0x70, 0x72, 0x6f, - 0x78, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0xe2, 0x02, 0x36, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x5b, 0x0a, 0x07, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, + 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x70, 0x62, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x2e, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x65, 0x78, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x22, 0xb9, 0x01, 0x0a, 0x15, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x78, + 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x78, 0x61, 0x63, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x75, 0x66, 0x66, + 0x69, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x75, 0x66, 0x66, 0x69, 0x78, + 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x72, 0x65, 0x67, 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x76, 0x65, 0x72, 0x74, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x22, 0xed, + 0x01, 0x0a, 0x15, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x74, 0x68, + 0x5f, 0x65, 0x78, 0x61, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, + 0x74, 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x74, 0x68, 0x5f, + 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, + 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x74, 0x68, + 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, + 0x74, 0x68, 0x52, 0x65, 0x67, 0x65, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x73, 0x12, 0x5b, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x32, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x70, 0x62, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2e, + 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x42, 0xdd, + 0x02, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x32, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x70, 0x62, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x42, 0x17, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x44, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, + 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f, 0x70, 0x62, 0x6d, 0x65, 0x73, 0x68, 0x2f, 0x76, 0x32, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x62, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x73, 0x74, 0x61, + 0x74, 0x65, 0xa2, 0x02, 0x05, 0x48, 0x43, 0x4d, 0x56, 0x50, 0xaa, 0x02, 0x2a, 0x48, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x4d, 0x65, + 0x73, 0x68, 0x2e, 0x56, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x50, 0x62, 0x70, 0x72, 0x6f, + 0x78, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0xca, 0x02, 0x2a, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x4d, 0x65, 0x73, 0x68, 0x5c, 0x56, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x50, 0x62, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0xea, 0x02, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x68, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x3a, 0x3a, 0x50, 0x62, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x61, 0x74, 0x65, 0xe2, 0x02, 0x36, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x4d, 0x65, 0x73, 0x68, 0x5c, 0x56, 0x32, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x5c, 0x50, 0x62, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x2e, + 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x3a, 0x3a, 0x4d, 0x65, 0x73, 0x68, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x3a, 0x3a, 0x50, 0x62, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -552,7 +660,7 @@ func file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_rawDescGZIP() [] return file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_rawDescData } -var file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_goTypes = []interface{}{ (*TrafficPermissions)(nil), // 0: hashicorp.consul.mesh.v2beta1.pbproxystate.TrafficPermissions (*Permission)(nil), // 1: hashicorp.consul.mesh.v2beta1.pbproxystate.Permission @@ -560,6 +668,7 @@ var file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_goTypes = []inter (*Spiffe)(nil), // 3: hashicorp.consul.mesh.v2beta1.pbproxystate.Spiffe (*DestinationRule)(nil), // 4: hashicorp.consul.mesh.v2beta1.pbproxystate.DestinationRule (*DestinationRuleHeader)(nil), // 5: hashicorp.consul.mesh.v2beta1.pbproxystate.DestinationRuleHeader + (*ExcludePermissionRule)(nil), // 6: hashicorp.consul.mesh.v2beta1.pbproxystate.ExcludePermissionRule } var file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_depIdxs = []int32{ 1, // 0: hashicorp.consul.mesh.v2beta1.pbproxystate.TrafficPermissions.allow_permissions:type_name -> hashicorp.consul.mesh.v2beta1.pbproxystate.Permission @@ -569,11 +678,13 @@ var file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_depIdxs = []int32 3, // 4: hashicorp.consul.mesh.v2beta1.pbproxystate.Principal.spiffe:type_name -> hashicorp.consul.mesh.v2beta1.pbproxystate.Spiffe 3, // 5: hashicorp.consul.mesh.v2beta1.pbproxystate.Principal.exclude_spiffes:type_name -> hashicorp.consul.mesh.v2beta1.pbproxystate.Spiffe 5, // 6: hashicorp.consul.mesh.v2beta1.pbproxystate.DestinationRule.destination_rule_header:type_name -> hashicorp.consul.mesh.v2beta1.pbproxystate.DestinationRuleHeader - 7, // [7:7] is the sub-list for method output_type - 7, // [7:7] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 6, // 7: hashicorp.consul.mesh.v2beta1.pbproxystate.DestinationRule.exclude:type_name -> hashicorp.consul.mesh.v2beta1.pbproxystate.ExcludePermissionRule + 5, // 8: hashicorp.consul.mesh.v2beta1.pbproxystate.ExcludePermissionRule.headers:type_name -> hashicorp.consul.mesh.v2beta1.pbproxystate.DestinationRuleHeader + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name } func init() { file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_init() } @@ -654,6 +765,18 @@ func file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_init() { return nil } } + file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExcludePermissionRule); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -661,7 +784,7 @@ func file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_pbmesh_v2beta1_pbproxystate_traffic_permissions_proto_rawDesc, NumEnums: 0, - NumMessages: 6, + NumMessages: 7, NumExtensions: 0, NumServices: 0, }, diff --git a/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.proto b/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.proto index 0d04c09355..41044acbe3 100644 --- a/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.proto +++ b/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions.proto @@ -42,6 +42,7 @@ message DestinationRule { string path_regex = 3; repeated string methods = 4; repeated DestinationRuleHeader destination_rule_header = 5; + repeated ExcludePermissionRule exclude = 6; } message DestinationRuleHeader { @@ -53,3 +54,11 @@ message DestinationRuleHeader { string regex = 6; bool invert = 7; } + +message ExcludePermissionRule { + string path_exact = 1; + string path_prefix = 2; + string path_regex = 3; + repeated string methods = 4; + repeated DestinationRuleHeader headers = 5; +} diff --git a/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions_deepcopy.gen.go b/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions_deepcopy.gen.go index 170a169211..13ea86f52c 100644 --- a/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions_deepcopy.gen.go +++ b/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions_deepcopy.gen.go @@ -130,3 +130,24 @@ func (in *DestinationRuleHeader) DeepCopy() *DestinationRuleHeader { func (in *DestinationRuleHeader) DeepCopyInterface() interface{} { return in.DeepCopy() } + +// DeepCopyInto supports using ExcludePermissionRule within kubernetes types, where deepcopy-gen is used. +func (in *ExcludePermissionRule) DeepCopyInto(out *ExcludePermissionRule) { + proto.Reset(out) + proto.Merge(out, proto.Clone(in)) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExcludePermissionRule. Required by controller-gen. +func (in *ExcludePermissionRule) DeepCopy() *ExcludePermissionRule { + if in == nil { + return nil + } + out := new(ExcludePermissionRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new ExcludePermissionRule. Required by controller-gen. +func (in *ExcludePermissionRule) DeepCopyInterface() interface{} { + return in.DeepCopy() +} diff --git a/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions_json.gen.go b/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions_json.gen.go index 3692fe2a36..ccb771c391 100644 --- a/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions_json.gen.go +++ b/proto-public/pbmesh/v2beta1/pbproxystate/traffic_permissions_json.gen.go @@ -71,6 +71,17 @@ func (this *DestinationRuleHeader) UnmarshalJSON(b []byte) error { return TrafficPermissionsUnmarshaler.Unmarshal(b, this) } +// MarshalJSON is a custom marshaler for ExcludePermissionRule +func (this *ExcludePermissionRule) MarshalJSON() ([]byte, error) { + str, err := TrafficPermissionsMarshaler.Marshal(this) + return []byte(str), err +} + +// UnmarshalJSON is a custom unmarshaler for ExcludePermissionRule +func (this *ExcludePermissionRule) UnmarshalJSON(b []byte) error { + return TrafficPermissionsUnmarshaler.Unmarshal(b, this) +} + var ( TrafficPermissionsMarshaler = &protojson.MarshalOptions{} TrafficPermissionsUnmarshaler = &protojson.UnmarshalOptions{DiscardUnknown: false} diff --git a/test-integ/catalogv2/traffic_permissions_test.go b/test-integ/catalogv2/traffic_permissions_test.go new file mode 100644 index 0000000000..4e97462e33 --- /dev/null +++ b/test-integ/catalogv2/traffic_permissions_test.go @@ -0,0 +1,459 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 +package catalogv2 + +import ( + "context" + "fmt" + "net/http" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/hashicorp/consul/test-integ/topoutil" + libassert "github.com/hashicorp/consul/test/integration/consul-container/libs/assert" + "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" + "github.com/hashicorp/consul/testing/deployer/sprawl/sprawltest" + "github.com/hashicorp/consul/testing/deployer/topology" +) + +type testCase struct { + permissions []*permission + result []*testResult +} + +type permission struct { + allow bool + excludeSource bool + includeSourceTenancy bool + excludeSourceTenancy bool + destRules []*destRules +} + +type destRules struct { + values *ruleValues + excludes []*ruleValues +} + +type ruleValues struct { + portNames []string + path string + pathPref string + pathReg string + headers []string + methods []string +} + +type testResult struct { + fail bool + port string + path string + headers map[string]string +} + +func newTrafficPermissions(p *permission, srcTenancy *pbresource.Tenancy) *pbauth.TrafficPermissions { + sources := []*pbauth.Source{{ + IdentityName: "static-client", + Namespace: srcTenancy.Namespace, + Partition: srcTenancy.Partition, + }} + destinationRules := []*pbauth.DestinationRule{} + if p != nil { + srcId := "static-client" + if p.includeSourceTenancy { + srcId = "" + } + if p.excludeSource { + sources = []*pbauth.Source{{ + IdentityName: srcId, + Namespace: srcTenancy.Namespace, + Partition: srcTenancy.Partition, + Exclude: []*pbauth.ExcludeSource{{ + IdentityName: "static-client", + Namespace: srcTenancy.Namespace, + Partition: srcTenancy.Partition, + }}, + }} + } else { + sources = []*pbauth.Source{{ + IdentityName: srcId, + Namespace: srcTenancy.Namespace, + Partition: srcTenancy.Partition, + }} + } + for _, dr := range p.destRules { + destRule := &pbauth.DestinationRule{} + if dr.values != nil { + destRule.PathExact = dr.values.path + destRule.PathPrefix = dr.values.pathPref + destRule.PathRegex = dr.values.pathReg + destRule.Methods = dr.values.methods + destRule.PortNames = dr.values.portNames + destRule.Headers = []*pbauth.DestinationRuleHeader{} + for _, h := range dr.values.headers { + destRule.Headers = append(destRule.Headers, &pbauth.DestinationRuleHeader{ + Name: h, + Present: true, + }) + } + } + var excludePermissions []*pbauth.ExcludePermissionRule + for _, e := range dr.excludes { + eRule := &pbauth.ExcludePermissionRule{ + PathExact: e.path, + PathPrefix: e.pathPref, + PathRegex: e.pathReg, + Methods: e.methods, + PortNames: e.portNames, + } + eRule.Headers = []*pbauth.DestinationRuleHeader{} + for _, h := range e.headers { + eRule.Headers = append(eRule.Headers, &pbauth.DestinationRuleHeader{ + Name: h, + Present: true, + }) + } + excludePermissions = append(excludePermissions, eRule) + } + destRule.Exclude = excludePermissions + destinationRules = append(destinationRules, destRule) + } + } + action := pbauth.Action_ACTION_ALLOW + if !p.allow { + action = pbauth.Action_ACTION_DENY + } + return &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "static-server", + }, + Action: action, + Permissions: []*pbauth.Permission{{ + Sources: sources, + DestinationRules: destinationRules, + }}, + } + +} + +// This tests runs a gauntlet of traffic permissions updates and validates that the request status codes match the intended rules +func TestL7TrafficPermissions(t *testing.T) { + testcases := map[string]testCase{ + // L4 permissions + "basic": {permissions: []*permission{{allow: true}}, result: []*testResult{{fail: false}}}, + "client-exclude": {permissions: []*permission{{allow: true, includeSourceTenancy: true, excludeSource: true}}, result: []*testResult{{fail: true}}}, + "allow-all-client-in-tenancy": {permissions: []*permission{{allow: true, includeSourceTenancy: true}}, result: []*testResult{{fail: false}}}, + "only-one-port": {permissions: []*permission{{allow: true, destRules: []*destRules{{values: &ruleValues{portNames: []string{"http"}}}}}}, result: []*testResult{{fail: true, port: "http2"}}}, + "exclude-port": {permissions: []*permission{{allow: true, destRules: []*destRules{{excludes: []*ruleValues{{portNames: []string{"http"}}}}}}}, result: []*testResult{{fail: true, port: "http"}}}, + // L7 permissions + "methods": {permissions: []*permission{{allow: true, destRules: []*destRules{{values: &ruleValues{methods: []string{"POST", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", "OPTIONS", "TRACE"}, pathPref: "/"}}}}}, + // fortio fetch2 is configured to GET + result: []*testResult{{fail: true}}}, + "headers": {permissions: []*permission{{allow: true, destRules: []*destRules{{values: &ruleValues{headers: []string{"a", "b"}, pathPref: "/"}}}}}, + result: []*testResult{{fail: true}, {fail: true, headers: map[string]string{"a": "1"}}, {fail: false, headers: map[string]string{"a": "1", "b": "2"}}}}, + "path-prefix-all": {permissions: []*permission{{allow: true, destRules: []*destRules{{values: &ruleValues{pathPref: "/", methods: []string{"GET"}}}}}}, result: []*testResult{{fail: false}}}, + "method-exclude": {permissions: []*permission{{allow: true, destRules: []*destRules{{values: &ruleValues{pathPref: "/"}, excludes: []*ruleValues{{methods: []string{"GET"}}}}}}}, + // fortio fetch2 is configured to GET + result: []*testResult{{fail: true}}}, + "exclude-paths-and-headers": {permissions: []*permission{{allow: true, destRules: []*destRules{ + { + values: &ruleValues{pathPref: "/f", headers: []string{"a"}}, + excludes: []*ruleValues{{headers: []string{"b"}, path: "/foobar"}}, + }}}}, + result: []*testResult{ + {fail: false, path: "foobar", headers: map[string]string{"a": "1"}}, + {fail: false, path: "foo", headers: map[string]string{"a": "1", "b": "2"}}, + {fail: true, path: "foobar", headers: map[string]string{"a": "1", "b": "2"}}, + {fail: false, path: "foo", headers: map[string]string{"a": "1"}}, + {fail: true, path: "foo", headers: map[string]string{"b": "2"}}, + {fail: true, path: "baz", headers: map[string]string{"a": "1"}}, + }}, + "exclude-paths-or-headers": {permissions: []*permission{{allow: true, destRules: []*destRules{ + {values: &ruleValues{pathPref: "/f", headers: []string{"a"}}, excludes: []*ruleValues{{headers: []string{"b"}}, {path: "/foobar"}}}}}}, + result: []*testResult{ + {fail: true, path: "foobar", headers: map[string]string{"a": "1"}}, + {fail: true, path: "foo", headers: map[string]string{"a": "1", "b": "2"}}, + {fail: true, path: "foobar", headers: map[string]string{"a": "1", "b": "2"}}, + {fail: false, path: "foo", headers: map[string]string{"a": "1"}}, + {fail: false, path: "foo", headers: map[string]string{"a": "1"}}, + {fail: true, path: "baz", port: "http", headers: map[string]string{"a": "1"}}, + }}, + "path-or-header": {permissions: []*permission{{allow: true, destRules: []*destRules{{values: &ruleValues{pathPref: "/bar"}}, {values: &ruleValues{headers: []string{"b"}}}}}}, + result: []*testResult{ + {fail: false, path: "bar"}, + {fail: false, path: "foo", headers: map[string]string{"a": "1", "b": "2"}}, + {fail: false, path: "bar", headers: map[string]string{"b": "2"}}, + {fail: true, path: "foo", headers: map[string]string{"a": "1"}}, + }}, + "path-and-header": {permissions: []*permission{{allow: true, destRules: []*destRules{{values: &ruleValues{pathPref: "/bar", headers: []string{"b"}}}}}}, + result: []*testResult{ + {fail: true, path: "bar"}, + {fail: true, path: "foo", headers: map[string]string{"a": "1", "b": "2"}}, + {fail: false, path: "bar", headers: map[string]string{"b": "2"}}, + {fail: true, path: "foo", headers: map[string]string{"a": "1"}}, + }}, + "path-regex-exclude": {permissions: []*permission{{allow: true, destRules: []*destRules{{values: &ruleValues{pathPref: "/"}, excludes: []*ruleValues{{pathReg: ".*dns.*"}}}}}}, + result: []*testResult{{fail: true, path: "fortio/rest/dns"}, {fail: false, path: "fortio/rest/status"}}}, + "header-include-exclude-by-port": {permissions: []*permission{{allow: true, destRules: []*destRules{{values: &ruleValues{pathPref: "/", headers: []string{"experiment1", "experiment2"}}, excludes: []*ruleValues{{portNames: []string{"http2"}, headers: []string{"experiment1"}}}}}}}, + result: []*testResult{{fail: true, port: "http2", headers: map[string]string{"experiment1": "a", "experiment2": "b"}}, + {fail: false, port: "http", headers: map[string]string{"experiment1": "a", "experiment2": "b"}}, + {fail: true, port: "http2", headers: map[string]string{"experiment2": "b"}}, + {fail: true, port: "http", headers: map[string]string{"experiment3": "c"}}, + }}, + "two-tp-or": {permissions: []*permission{{allow: true, destRules: []*destRules{{values: &ruleValues{pathPref: "/bar"}}}}, {allow: true, destRules: []*destRules{{values: &ruleValues{headers: []string{"b"}}}}}}, + result: []*testResult{ + {fail: false, path: "bar"}, + {fail: false, path: "foo", headers: map[string]string{"a": "1", "b": "2"}}, + {fail: false, path: "bar", headers: map[string]string{"b": "2"}}, + {fail: true, path: "foo", headers: map[string]string{"a": "1"}}, + }}, + } + if utils.IsEnterprise() { + // DENY and ALLOW permissions + testcases["deny-cancel-allow"] = testCase{permissions: []*permission{{allow: true}, {allow: false}}, result: []*testResult{{fail: true}}} + testcases["l4-deny-l7-allow"] = testCase{permissions: []*permission{{allow: false}, {allow: true, destRules: []*destRules{{values: &ruleValues{pathPref: "/"}}}}}, result: []*testResult{{fail: true}, {fail: true, path: "test"}}} + testcases["l7-deny-l4-allow"] = testCase{permissions: []*permission{{allow: true}, {allow: true, destRules: []*destRules{{values: &ruleValues{pathPref: "/"}}}}, {allow: false, destRules: []*destRules{{values: &ruleValues{pathPref: "/foo"}}}}}, + result: []*testResult{{fail: false}, {fail: false, path: "test"}, {fail: true, path: "foo-bar"}}} + } + + tenancies := []*pbresource.Tenancy{ + { + Partition: "default", + Namespace: "default", + }, + } + if utils.IsEnterprise() { + tenancies = append(tenancies, &pbresource.Tenancy{ + Partition: "ap1", + Namespace: "ns1", + }) + } + cfg := testL7TrafficPermissionsCreator{tenancies}.NewConfig(t) + targetImage := utils.TargetImages() + imageName := targetImage.Consul + if utils.IsEnterprise() { + imageName = targetImage.ConsulEnterprise + } + t.Log("running with target image: " + imageName) + + sp := sprawltest.Launch(t, cfg) + + asserter := topoutil.NewAsserter(sp) + + topo := sp.Topology() + cluster := topo.Clusters["dc1"] + ships := topo.ComputeRelationships() + + clientV2 := sp.ResourceServiceClientForCluster(cluster.Name) + + // Make sure services exist + for _, tenancy := range tenancies { + for _, name := range []string{ + "static-server", + "static-client", + } { + libassert.CatalogV2ServiceHasEndpointCount(t, clientV2, name, tenancy, len(tenancies)) + } + } + var initialTrafficPerms []*pbresource.Resource + for testName, tc := range testcases { + // Delete old TP and write new one for a new test case + mustDeleteTestResources(t, clientV2, initialTrafficPerms) + initialTrafficPerms = []*pbresource.Resource{} + for _, st := range tenancies { + for _, dt := range tenancies { + for i, p := range tc.permissions { + newTrafficPerms := sprawltest.MustSetResourceData(t, &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbauth.TrafficPermissionsType, + Name: "static-server-perms" + strconv.Itoa(i) + "-" + st.Namespace + "-" + st.Partition, + Tenancy: dt, + }, + }, newTrafficPermissions(p, st)) + mustWriteTestResource(t, clientV2, newTrafficPerms) + initialTrafficPerms = append(initialTrafficPerms, newTrafficPerms) + } + } + } + t.Log(initialTrafficPerms) + // Wait for the resource updates to go through and Envoy to be ready + time.Sleep(1 * time.Second) + // Check the default server workload envoy config for RBAC filters matching testcase criteria + serverWorkload := cluster.WorkloadsByID(topology.ID{ + Partition: "default", + Namespace: "default", + Name: "static-server", + }) + asserter.AssertEnvoyHTTPrbacFiltersContainIntentions(t, serverWorkload[0]) + // Check relationships + for _, ship := range ships { + t.Run("case: "+testName+":"+ship.Destination.PortName+":("+ship.Caller.ID.Partition+"/"+ship.Caller.ID.Namespace+ + ")("+ship.Destination.ID.Partition+"/"+ship.Destination.ID.Namespace+")", func(t *testing.T) { + var ( + wrk = ship.Caller + dest = ship.Destination + ) + for _, res := range tc.result { + if res.port != "" && res.port != ship.Destination.PortName { + continue + } + dest.ID.Name = "static-server" + destClusterPrefix := clusterPrefix(dest.PortName, dest.ID, dest.Cluster) + asserter.DestinationEndpointStatus(t, wrk, destClusterPrefix+".", "HEALTHY", len(tenancies)) + status := http.StatusForbidden + if res.fail == false { + status = http.StatusOK + } + t.Log("Test request:"+res.path, res.headers, status) + asserter.FortioFetch2ServiceStatusCodes(t, wrk, dest, res.path, res.headers, []int{status}) + } + }) + } + } +} + +func mustWriteTestResource(t *testing.T, client pbresource.ResourceServiceClient, res *pbresource.Resource) { + retryer := &retry.Timer{Timeout: time.Minute, Wait: time.Second} + rsp, err := client.Write(context.Background(), &pbresource.WriteRequest{Resource: res}) + require.NoError(t, err) + retry.RunWith(retryer, t, func(r *retry.R) { + readRsp, err := client.Read(context.Background(), &pbresource.ReadRequest{Id: rsp.Resource.Id}) + require.NoError(r, err, "error reading %s", rsp.Resource.Id.Name) + require.NotNil(r, readRsp) + }) + +} + +func mustDeleteTestResources(t *testing.T, client pbresource.ResourceServiceClient, resources []*pbresource.Resource) { + if len(resources) == 0 { + return + } + retryer := &retry.Timer{Timeout: time.Minute, Wait: time.Second} + for _, res := range resources { + retry.RunWith(retryer, t, func(r *retry.R) { + _, err := client.Delete(context.Background(), &pbresource.DeleteRequest{Id: res.Id}) + if status.Code(err) == codes.NotFound { + return + } + if err != nil && status.Code(err) != codes.Aborted { + r.Stop(fmt.Errorf("failed to delete the resource: %w", err)) + return + } + require.NoError(r, err) + }) + } +} + +type testL7TrafficPermissionsCreator struct { + tenancies []*pbresource.Tenancy +} + +func (c testL7TrafficPermissionsCreator) NewConfig(t *testing.T) *topology.Config { + const clusterName = "dc1" + + servers := topoutil.NewTopologyServerSet(clusterName+"-server", 1, []string{clusterName, "wan"}, nil) + + cluster := &topology.Cluster{ + Enterprise: utils.IsEnterprise(), + Name: clusterName, + Nodes: servers, + } + + lastNode := 0 + nodeName := func() string { + lastNode++ + return fmt.Sprintf("%s-box%d", clusterName, lastNode) + } + + for _, st := range c.tenancies { + for _, dt := range c.tenancies { + c.topologyConfigAddNodes(cluster, nodeName, st, dt) + + } + } + + return &topology.Config{ + Images: utils.TargetImages(), + Networks: []*topology.Network{ + {Name: clusterName}, + {Name: "wan", Type: "wan"}, + }, + Clusters: []*topology.Cluster{ + cluster, + }, + } +} + +func (c testL7TrafficPermissionsCreator) topologyConfigAddNodes( + cluster *topology.Cluster, + nodeName func() string, + sourceTenancy *pbresource.Tenancy, + destinationTenancy *pbresource.Tenancy, +) { + clusterName := cluster.Name + + newID := func(name string, tenancy *pbresource.Tenancy) topology.ID { + return topology.ID{ + Partition: tenancy.Partition, + Namespace: tenancy.Namespace, + Name: name, + } + } + + serverNode := &topology.Node{ + Kind: topology.NodeKindDataplane, + Version: topology.NodeVersionV2, + Partition: destinationTenancy.Partition, + Name: nodeName(), + Workloads: []*topology.Workload{ + topoutil.NewFortioWorkloadWithDefaults( + clusterName, + newID("static-server", destinationTenancy), + topology.NodeVersionV2, + nil, + ), + }, + } + + clientNode := &topology.Node{ + Kind: topology.NodeKindDataplane, + Version: topology.NodeVersionV2, + Partition: sourceTenancy.Partition, + Name: nodeName(), + Workloads: []*topology.Workload{ + topoutil.NewFortioWorkloadWithDefaults( + clusterName, + newID("static-client", sourceTenancy), + topology.NodeVersionV2, + func(wrk *topology.Workload) { + wrk.Destinations = append(wrk.Destinations, &topology.Destination{ + ID: newID("static-server", destinationTenancy), + PortName: "http", + LocalAddress: "0.0.0.0", // needed for an assertion + LocalPort: 5000, + }, + &topology.Destination{ + ID: newID("static-server", destinationTenancy), + PortName: "http2", + LocalAddress: "0.0.0.0", // needed for an assertion + LocalPort: 5001, + }, + ) + wrk.WorkloadIdentity = "static-client" + }, + ), + }, + } + + cluster.Nodes = append(cluster.Nodes, + clientNode, + serverNode, + ) +} diff --git a/test-integ/topoutil/asserter.go b/test-integ/topoutil/asserter.go index 597561f9e1..eca1348eaf 100644 --- a/test-integ/topoutil/asserter.go +++ b/test-integ/topoutil/asserter.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "regexp" + "strings" "testing" "time" @@ -127,6 +128,41 @@ func (a *Asserter) AssertEnvoyPresentsCertURIWithClient(t *testing.T, workload * libassert.AssertEnvoyPresentsCertURIWithClient(t, client, addr, workload.ID.Name) } +func (a *Asserter) AssertEnvoyHTTPrbacFiltersContainIntentions(t *testing.T, workload *topology.Workload) { + t.Helper() + client, addr := a.getEnvoyClient(t, workload) + var ( + dump string + err error + ) + failer := func() *retry.Timer { + return &retry.Timer{Timeout: 30 * time.Second, Wait: 1 * time.Second} + } + + retry.RunWith(failer(), t, func(r *retry.R) { + dump, _, err = libassert.GetEnvoyOutputWithClient(client, addr, "config_dump", map[string]string{}) + if err != nil { + r.Fatal("could not fetch envoy configuration") + } + }) + // the steps below validate that the json result from envoy config dump configured active listeners with rbac and http filters + filter := `.configs[2].dynamic_listeners[].active_state.listener | "\(.name) \( .filter_chains[].filters[] | select(.name == "envoy.filters.network.http_connection_manager") | .typed_config.http_filters | map(.name) | join(","))"` + errorStateFilter := `.configs[2].dynamic_listeners[].error_state"` + configErrorStates, _ := utils.JQFilter(dump, errorStateFilter) + require.Nil(t, configErrorStates, "there should not be any error states on listener configuration") + results, err := utils.JQFilter(dump, filter) + require.NoError(t, err, "could not parse envoy configuration") + var filteredResult []string + for _, result := range results { + parts := strings.Split(strings.ReplaceAll(result, `,`, " "), " ") + sanitizedResult := append(parts[:0], parts[1:]...) + filteredResult = append(filteredResult, sanitizedResult...) + } + require.Contains(t, filteredResult, "envoy.filters.http.rbac") + require.Contains(t, filteredResult, "envoy.filters.http.router") + require.Contains(t, dump, "intentions") +} + // HTTPServiceEchoes verifies that a post to the given ip/port combination // returns the data in the response body. Optional path can be provided to // differentiate requests. @@ -243,7 +279,7 @@ func (a *Asserter) fortioFetch2Destination( ) (body []byte, res *http.Response) { t.Helper() - err, res := getFortioFetch2DestinationResponse(t, client, addr, dest, path) + err, res := getFortioFetch2DestinationResponse(t, client, addr, dest, path, nil) require.NoError(t, err) defer res.Body.Close() @@ -258,7 +294,7 @@ func (a *Asserter) fortioFetch2Destination( return body, res } -func getFortioFetch2DestinationResponse(t testutil.TestingTB, client *http.Client, addr string, dest *topology.Destination, path string) (error, *http.Response) { +func getFortioFetch2DestinationResponse(t testutil.TestingTB, client *http.Client, addr string, dest *topology.Destination, path string, headers map[string]string) (error, *http.Response) { var actualURL string if dest.Implied { actualURL = fmt.Sprintf("http://%s--%s--%s.virtual.consul:%d/%s", @@ -279,6 +315,9 @@ func getFortioFetch2DestinationResponse(t testutil.TestingTB, client *http.Clien req, err := http.NewRequest(http.MethodPost, url, nil) require.NoError(t, err) + for h, v := range headers { + req.Header.Set(h, v) + } res, err := client.Do(req) require.NoError(t, err) return err, res @@ -339,13 +378,16 @@ func (a *Asserter) FortioFetch2FortioName( }) } -// FortioFetch2ServiceUnavailable uses the /fortio/fetch2 endpoint to do a header echo check against an destination -// fortio and asserts that the service is unavailable (503) func (a *Asserter) FortioFetch2ServiceUnavailable(t *testing.T, fortioWrk *topology.Workload, dest *topology.Destination) { const kPassphrase = "x-passphrase" const passphrase = "hello" path := (fmt.Sprintf("/?header=%s:%s", kPassphrase, passphrase)) + a.FortioFetch2ServiceStatusCodes(t, fortioWrk, dest, path, nil, []int{http.StatusServiceUnavailable}) +} +// FortioFetch2ServiceStatusCodes uses the /fortio/fetch2 endpoint to do a header echo check against a destination +// fortio and asserts that the returned status code matches the desired one(s) +func (a *Asserter) FortioFetch2ServiceStatusCodes(t *testing.T, fortioWrk *topology.Workload, dest *topology.Destination, path string, headers map[string]string, statuses []int) { var ( node = fortioWrk.Node addr = fmt.Sprintf("%s:%d", node.LocalAddress(), fortioWrk.PortOrDefault(dest.PortName)) @@ -353,9 +395,9 @@ func (a *Asserter) FortioFetch2ServiceUnavailable(t *testing.T, fortioWrk *topol ) retry.RunWith(&retry.Timer{Timeout: 60 * time.Second, Wait: time.Millisecond * 500}, t, func(r *retry.R) { - _, res := getFortioFetch2DestinationResponse(r, client, addr, dest, path) + _, res := getFortioFetch2DestinationResponse(r, client, addr, dest, path, headers) defer res.Body.Close() - require.Equal(r, http.StatusServiceUnavailable, res.StatusCode) + require.Contains(r, statuses, res.StatusCode) }) } diff --git a/test/integration/consul-container/libs/assert/envoy.go b/test/integration/consul-container/libs/assert/envoy.go index 511c778670..95e1297602 100644 --- a/test/integration/consul-container/libs/assert/envoy.go +++ b/test/integration/consul-container/libs/assert/envoy.go @@ -198,41 +198,6 @@ func AssertEnvoyMetricAtLeast(t *testing.T, adminPort int, prefix, metric string }) } -// GetEnvoyHTTPrbacFilters validates that proxy was configured with an http connection manager -// AssertEnvoyHTTPrbacFilters validates that proxy was configured with an http connection manager -// this assertion is currently unused current tests use http protocol -func AssertEnvoyHTTPrbacFilters(t *testing.T, port int) { - var ( - dump string - err error - ) - failer := func() *retry.Timer { - return &retry.Timer{Timeout: 30 * time.Second, Wait: 1 * time.Second} - } - - retry.RunWith(failer(), t, func(r *retry.R) { - dump, _, err = GetEnvoyOutput(port, "config_dump", map[string]string{}) - if err != nil { - r.Fatal("could not fetch envoy configuration") - } - }) - - // the steps below validate that the json result from envoy config dump configured active listeners with rbac and http filters - filter := `.configs[2].dynamic_listeners[].active_state.listener | "\(.name) \( .filter_chains[0].filters[] | select(.name == "envoy.filters.network.http_connection_manager") | .typed_config.http_filters | map(.name) | join(","))"` - results, err := utils.JQFilter(dump, filter) - require.NoError(t, err, "could not parse envoy configuration") - require.Len(t, results, 1, "static-server proxy should have been configured with two listener filters.") - - var filteredResult []string - for _, result := range results { - sanitizedResult := sanitizeResult(result) - filteredResult = append(filteredResult, sanitizedResult...) - } - require.Contains(t, filteredResult, "envoy.filters.http.rbac") - assert.Contains(t, filteredResult, "envoy.filters.http.header_to_metadata") - assert.Contains(t, filteredResult, "envoy.filters.http.router") -} - // AssertEnvoyPresentsCertURI makes GET request to /certs endpoint and validates that // two certificates URI is available in the response func AssertEnvoyPresentsCertURI(t *testing.T, port int, serviceName string) {