mirror of https://github.com/status-im/consul.git
add traffic permissions excludes and tests (#20453)
* add traffic permissions tests * review fixes * Update internal/mesh/internal/controllers/sidecarproxy/builder/local_app.go Co-authored-by: John Landa <jonathanlanda@gmail.com> --------- Co-authored-by: John Landa <jonathanlanda@gmail.com>
This commit is contained in:
parent
1bd253021b
commit
57bad0df85
|
@ -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 }} \
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
183
agent/xds/testdata/rbac/v2-single-permission-multiple-destination-rules--httpfilter.golden
vendored
Normal file
183
agent/xds/testdata/rbac/v2-single-permission-multiple-destination-rules--httpfilter.golden
vendored
Normal file
|
@ -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$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
agent/xds/testdata/rbac/v2-single-permission-multiple-destination-rules.golden
vendored
Normal file
8
agent/xds/testdata/rbac/v2-single-permission-multiple-destination-rules.golden
vendored
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
104
agent/xds/testdata/rbac/v2-single-permission-with-excludes--httpfilter.golden
vendored
Normal file
104
agent/xds/testdata/rbac/v2-single-permission-with-excludes--httpfilter.golden
vendored
Normal file
|
@ -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$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
226
agent/xds/testdata/rbac/v2-single-permission-with-kitchen-sink-perms--httpfilter.golden
vendored
Normal file
226
agent/xds/testdata/rbac/v2-single-permission-with-kitchen-sink-perms--httpfilter.golden
vendored
Normal file
|
@ -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$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -226,10 +226,8 @@ 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 != "":
|
||||
|
@ -282,7 +280,11 @@ func permissionsFromDestinationRules(drs []*pbproxystate.DestinationRule) []*env
|
|||
eh := &envoy_route_v3.HeaderMatcher{
|
||||
Name: ":method",
|
||||
HeaderMatchSpecifier: &envoy_route_v3.HeaderMatcher_StringMatch{
|
||||
StringMatch: response.MakeEnvoyStringMatcher(methodHeaderRegex),
|
||||
StringMatch: &envoy_matcher_v3.StringMatcher{
|
||||
MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{
|
||||
SafeRegex: response.MakeEnvoyRegexMatch(methodHeaderRegex),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
perms = append(perms, &envoy_rbac_v3.Permission{
|
||||
|
@ -355,10 +357,48 @@ func permissionsFromDestinationRules(drs []*pbproxystate.DestinationRule) []*env
|
|||
},
|
||||
})
|
||||
}
|
||||
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)
|
||||
|
||||
|
|
|
@ -7,9 +7,11 @@ 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.")
|
||||
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")
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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{})
|
||||
type PortRule struct {
|
||||
rule *pbproxystate.DestinationRule
|
||||
}
|
||||
|
||||
func convertDestinationRule(allPorts []string, dr *pbauth.DestinationRule) map[string]*PortRule {
|
||||
portRules := make(map[string]*PortRule)
|
||||
targetPorts := allPorts
|
||||
if len(dr.PortNames) > 0 {
|
||||
for _, p := range dr.PortNames {
|
||||
ports[p] = struct{}{}
|
||||
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 {
|
||||
for _, p := range allPorts {
|
||||
ports[p] = struct{}{}
|
||||
portRules[p] = makePortRule(dr, p)
|
||||
}
|
||||
}
|
||||
return portRules
|
||||
}
|
||||
|
||||
for _, exclude := range dr.Exclude {
|
||||
for _, p := range exclude.PortNames {
|
||||
delete(ports, p)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue