2023-03-28 18:39:22 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2023-08-11 13:12:13 +00:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-03-28 18:39:22 +00:00
|
|
|
|
2020-08-27 17:20:58 +00:00
|
|
|
package xds
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
2023-01-06 17:13:40 +00:00
|
|
|
"regexp"
|
2020-08-27 17:20:58 +00:00
|
|
|
"sort"
|
|
|
|
"testing"
|
|
|
|
|
2023-09-13 13:03:42 +00:00
|
|
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
2021-07-15 15:09:00 +00:00
|
|
|
envoy_rbac_v3 "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
|
2024-01-24 02:07:58 +00:00
|
|
|
http_connection_managerv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
2021-07-15 15:09:00 +00:00
|
|
|
envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
|
|
|
|
|
2020-08-27 17:20:58 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
2021-02-22 21:00:15 +00:00
|
|
|
|
2023-09-13 13:03:42 +00:00
|
|
|
"github.com/hashicorp/consul/acl"
|
2021-02-22 21:00:15 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
2023-09-13 13:03:42 +00:00
|
|
|
"github.com/hashicorp/consul/agent/xdsv2"
|
2023-09-22 16:51:15 +00:00
|
|
|
"github.com/hashicorp/consul/proto-public/pbmesh/v2beta1/pbproxystate"
|
2023-02-17 21:14:46 +00:00
|
|
|
"github.com/hashicorp/consul/proto/private/pbpeering"
|
2020-08-27 17:20:58 +00:00
|
|
|
)
|
|
|
|
|
2021-07-15 15:09:00 +00:00
|
|
|
func TestRemoveIntentionPrecedence(t *testing.T) {
|
2022-06-10 21:15:22 +00:00
|
|
|
type ixnOpts struct {
|
|
|
|
src string
|
|
|
|
peer string
|
|
|
|
action structs.IntentionAction
|
|
|
|
}
|
|
|
|
testIntention := func(t *testing.T, opts ixnOpts) *structs.Intention {
|
2021-07-15 15:09:00 +00:00
|
|
|
t.Helper()
|
|
|
|
ixn := structs.TestIntention(t)
|
2022-06-10 21:15:22 +00:00
|
|
|
ixn.SourceName = opts.src
|
|
|
|
ixn.SourcePeer = opts.peer
|
|
|
|
ixn.Action = opts.action
|
|
|
|
|
|
|
|
// Destination is hardcoded, since RBAC rules are generated for a single destination
|
|
|
|
ixn.DestinationName = "api"
|
|
|
|
|
2021-07-15 15:09:00 +00:00
|
|
|
//nolint:staticcheck
|
|
|
|
ixn.UpdatePrecedence()
|
|
|
|
return ixn
|
|
|
|
}
|
2022-06-10 21:15:22 +00:00
|
|
|
testSourceIntention := func(opts ixnOpts) *structs.Intention {
|
|
|
|
return testIntention(t, opts)
|
2021-07-15 15:09:00 +00:00
|
|
|
}
|
|
|
|
testSourcePermIntention := func(src string, perms ...*structs.IntentionPermission) *structs.Intention {
|
2022-06-10 21:15:22 +00:00
|
|
|
opts := ixnOpts{src: src}
|
|
|
|
ixn := testIntention(t, opts)
|
2021-07-15 15:09:00 +00:00
|
|
|
ixn.Permissions = perms
|
|
|
|
return ixn
|
|
|
|
}
|
2023-04-20 16:16:04 +00:00
|
|
|
sorted := func(ixns ...*structs.Intention) structs.SimplifiedIntentions {
|
2021-07-15 15:09:00 +00:00
|
|
|
sort.SliceStable(ixns, func(i, j int) bool {
|
|
|
|
return ixns[j].Precedence < ixns[i].Precedence
|
|
|
|
})
|
2023-04-20 16:16:04 +00:00
|
|
|
return structs.SimplifiedIntentions(ixns)
|
2021-07-15 15:09:00 +00:00
|
|
|
}
|
2022-06-10 21:15:22 +00:00
|
|
|
testPeerTrustBundle := map[string]*pbpeering.PeeringTrustBundle{
|
|
|
|
"peer1": {
|
|
|
|
PeerName: "peer1",
|
|
|
|
TrustDomain: "peer1.domain",
|
|
|
|
ExportedPartition: "part1",
|
|
|
|
},
|
|
|
|
}
|
2022-06-21 02:47:14 +00:00
|
|
|
testTrustDomain := "test.consul"
|
2021-07-15 15:09:00 +00:00
|
|
|
|
|
|
|
var (
|
2022-06-21 02:47:14 +00:00
|
|
|
nameWild = rbacService{ServiceName: structs.NewServiceName("*", nil),
|
|
|
|
TrustDomain: testTrustDomain}
|
|
|
|
nameWeb = rbacService{ServiceName: structs.NewServiceName("web", nil),
|
|
|
|
TrustDomain: testTrustDomain}
|
2022-06-10 21:15:22 +00:00
|
|
|
nameWildPeered = rbacService{ServiceName: structs.NewServiceName("*", nil),
|
|
|
|
Peer: "peer1", TrustDomain: "peer1.domain", ExportedPartition: "part1"}
|
|
|
|
nameWebPeered = rbacService{ServiceName: structs.NewServiceName("web", nil),
|
|
|
|
Peer: "peer1", TrustDomain: "peer1.domain", ExportedPartition: "part1"}
|
2021-07-15 15:09:00 +00:00
|
|
|
permSlashPrefix = &structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
permDenySlashPrefix = &structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
xdsPermSlashPrefix = &envoy_rbac_v3.Permission{
|
|
|
|
Rule: &envoy_rbac_v3.Permission_UrlPath{
|
|
|
|
UrlPath: &envoy_matcher_v3.PathMatcher{
|
|
|
|
Rule: &envoy_matcher_v3.PathMatcher_Path{
|
|
|
|
Path: &envoy_matcher_v3.StringMatcher{
|
|
|
|
MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{
|
|
|
|
Prefix: "/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// NOTE: these default=(allow|deny) wild=(allow|deny) path=(allow|deny)
|
|
|
|
// tests below are meant to verify some of the behaviors work as expected
|
|
|
|
// when the default acl mode changes for the system
|
|
|
|
tests := map[string]struct {
|
|
|
|
intentionDefaultAllow bool
|
|
|
|
http bool
|
2023-04-20 16:16:04 +00:00
|
|
|
intentions structs.SimplifiedIntentions
|
2021-07-15 15:09:00 +00:00
|
|
|
expect []*rbacIntention
|
|
|
|
}{
|
|
|
|
"default-allow-path-allow": {
|
|
|
|
intentionDefaultAllow: true,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permSlashPrefix),
|
|
|
|
),
|
|
|
|
expect: []*rbacIntention{}, // EMPTY, just use the defaults
|
|
|
|
},
|
|
|
|
"default-deny-path-allow": {
|
|
|
|
intentionDefaultAllow: false,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permSlashPrefix),
|
|
|
|
),
|
|
|
|
expect: []*rbacIntention{
|
|
|
|
{
|
|
|
|
Source: nameWeb,
|
|
|
|
Action: intentionActionLayer7,
|
|
|
|
Permissions: []*rbacPermission{
|
|
|
|
{
|
|
|
|
Definition: permSlashPrefix,
|
|
|
|
Action: intentionActionAllow,
|
|
|
|
Perm: xdsPermSlashPrefix,
|
|
|
|
NotPerms: nil,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPermission: xdsPermSlashPrefix,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Precedence: 9,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: idPrincipal(nameWeb),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"default-allow-path-deny": {
|
|
|
|
intentionDefaultAllow: true,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permDenySlashPrefix),
|
|
|
|
),
|
|
|
|
expect: []*rbacIntention{
|
|
|
|
{
|
|
|
|
Source: nameWeb,
|
|
|
|
Action: intentionActionLayer7,
|
|
|
|
Permissions: []*rbacPermission{
|
|
|
|
{
|
|
|
|
Definition: permDenySlashPrefix,
|
|
|
|
Action: intentionActionDeny,
|
|
|
|
Perm: xdsPermSlashPrefix,
|
|
|
|
NotPerms: nil,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPermission: xdsPermSlashPrefix,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Precedence: 9,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: idPrincipal(nameWeb),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"default-deny-path-deny": {
|
|
|
|
intentionDefaultAllow: false,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permDenySlashPrefix),
|
|
|
|
),
|
|
|
|
expect: []*rbacIntention{},
|
|
|
|
},
|
|
|
|
// ========================
|
|
|
|
"default-allow-deny-all-and-path-allow": {
|
|
|
|
intentionDefaultAllow: true,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permSlashPrefix),
|
2022-06-10 21:15:22 +00:00
|
|
|
testSourceIntention(ixnOpts{src: "*", action: structs.IntentionActionDeny}),
|
2021-07-15 15:09:00 +00:00
|
|
|
),
|
|
|
|
expect: []*rbacIntention{
|
|
|
|
{
|
|
|
|
Source: nameWild,
|
2022-06-10 21:15:22 +00:00
|
|
|
NotSources: []rbacService{
|
2021-07-15 15:09:00 +00:00
|
|
|
nameWeb,
|
|
|
|
},
|
|
|
|
Action: intentionActionDeny,
|
|
|
|
Permissions: nil,
|
|
|
|
Precedence: 8,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: andPrincipals(
|
|
|
|
[]*envoy_rbac_v3.Principal{
|
|
|
|
idPrincipal(nameWild),
|
|
|
|
notPrincipal(
|
|
|
|
idPrincipal(nameWeb),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"default-deny-deny-all-and-path-allow": {
|
|
|
|
intentionDefaultAllow: false,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permSlashPrefix),
|
2022-06-10 21:15:22 +00:00
|
|
|
testSourceIntention(ixnOpts{src: "*", action: structs.IntentionActionDeny}),
|
2021-07-15 15:09:00 +00:00
|
|
|
),
|
|
|
|
expect: []*rbacIntention{
|
|
|
|
{
|
|
|
|
Source: nameWeb,
|
|
|
|
Action: intentionActionLayer7,
|
|
|
|
Permissions: []*rbacPermission{
|
|
|
|
{
|
|
|
|
Definition: permSlashPrefix,
|
|
|
|
Action: intentionActionAllow,
|
|
|
|
Perm: xdsPermSlashPrefix,
|
|
|
|
NotPerms: nil,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPermission: xdsPermSlashPrefix,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Precedence: 9,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: idPrincipal(nameWeb),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"default-allow-deny-all-and-path-deny": {
|
|
|
|
intentionDefaultAllow: true,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permDenySlashPrefix),
|
2022-06-10 21:15:22 +00:00
|
|
|
testSourceIntention(ixnOpts{src: "*", action: structs.IntentionActionDeny}),
|
2021-07-15 15:09:00 +00:00
|
|
|
),
|
|
|
|
expect: []*rbacIntention{
|
|
|
|
{
|
|
|
|
Source: nameWeb,
|
|
|
|
Action: intentionActionLayer7,
|
|
|
|
Permissions: []*rbacPermission{
|
|
|
|
{
|
|
|
|
Definition: permDenySlashPrefix,
|
|
|
|
Action: intentionActionDeny,
|
|
|
|
Perm: xdsPermSlashPrefix,
|
|
|
|
NotPerms: nil,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPermission: xdsPermSlashPrefix,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Precedence: 9,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: idPrincipal(nameWeb),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Source: nameWild,
|
2022-06-10 21:15:22 +00:00
|
|
|
NotSources: []rbacService{
|
2021-07-15 15:09:00 +00:00
|
|
|
nameWeb,
|
|
|
|
},
|
|
|
|
Action: intentionActionDeny,
|
|
|
|
Permissions: nil,
|
|
|
|
Precedence: 8,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: andPrincipals(
|
|
|
|
[]*envoy_rbac_v3.Principal{
|
|
|
|
idPrincipal(nameWild),
|
|
|
|
notPrincipal(
|
|
|
|
idPrincipal(nameWeb),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"default-deny-deny-all-and-path-deny": {
|
|
|
|
intentionDefaultAllow: false,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permDenySlashPrefix),
|
2022-06-10 21:15:22 +00:00
|
|
|
testSourceIntention(ixnOpts{src: "*", action: structs.IntentionActionDeny}),
|
2021-07-15 15:09:00 +00:00
|
|
|
),
|
|
|
|
expect: []*rbacIntention{},
|
|
|
|
},
|
|
|
|
// ========================
|
|
|
|
"default-allow-allow-all-and-path-allow": {
|
|
|
|
intentionDefaultAllow: true,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permSlashPrefix),
|
2022-06-10 21:15:22 +00:00
|
|
|
testSourceIntention(ixnOpts{src: "*", action: structs.IntentionActionAllow}),
|
2021-07-15 15:09:00 +00:00
|
|
|
),
|
|
|
|
expect: []*rbacIntention{},
|
|
|
|
},
|
|
|
|
"default-deny-allow-all-and-path-allow": {
|
|
|
|
intentionDefaultAllow: false,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permSlashPrefix),
|
2022-06-10 21:15:22 +00:00
|
|
|
testSourceIntention(ixnOpts{src: "*", action: structs.IntentionActionAllow}),
|
2021-07-15 15:09:00 +00:00
|
|
|
),
|
|
|
|
expect: []*rbacIntention{
|
|
|
|
{
|
|
|
|
Source: nameWeb,
|
|
|
|
Action: intentionActionLayer7,
|
|
|
|
Permissions: []*rbacPermission{
|
|
|
|
{
|
|
|
|
Definition: permSlashPrefix,
|
|
|
|
Action: intentionActionAllow,
|
|
|
|
Perm: xdsPermSlashPrefix,
|
|
|
|
NotPerms: nil,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPermission: xdsPermSlashPrefix,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Precedence: 9,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: idPrincipal(nameWeb),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Source: nameWild,
|
2022-06-10 21:15:22 +00:00
|
|
|
NotSources: []rbacService{
|
2021-07-15 15:09:00 +00:00
|
|
|
nameWeb,
|
|
|
|
},
|
|
|
|
Action: intentionActionAllow,
|
|
|
|
Permissions: nil,
|
|
|
|
Precedence: 8,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: andPrincipals(
|
|
|
|
[]*envoy_rbac_v3.Principal{
|
|
|
|
idPrincipal(nameWild),
|
|
|
|
notPrincipal(
|
|
|
|
idPrincipal(nameWeb),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"default-allow-allow-all-and-path-deny": {
|
|
|
|
intentionDefaultAllow: true,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permDenySlashPrefix),
|
2022-06-10 21:15:22 +00:00
|
|
|
testSourceIntention(ixnOpts{src: "*", action: structs.IntentionActionAllow}),
|
2021-07-15 15:09:00 +00:00
|
|
|
),
|
|
|
|
expect: []*rbacIntention{
|
|
|
|
{
|
|
|
|
Source: nameWeb,
|
|
|
|
Action: intentionActionLayer7,
|
|
|
|
Permissions: []*rbacPermission{
|
|
|
|
{
|
|
|
|
Definition: permDenySlashPrefix,
|
|
|
|
Action: intentionActionDeny,
|
|
|
|
Perm: xdsPermSlashPrefix,
|
|
|
|
NotPerms: nil,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPermission: xdsPermSlashPrefix,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Precedence: 9,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: idPrincipal(nameWeb),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"default-deny-allow-all-and-path-deny": {
|
|
|
|
intentionDefaultAllow: false,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourcePermIntention("web", permDenySlashPrefix),
|
2022-06-10 21:15:22 +00:00
|
|
|
testSourceIntention(ixnOpts{src: "*", action: structs.IntentionActionAllow}),
|
2021-07-15 15:09:00 +00:00
|
|
|
),
|
|
|
|
expect: []*rbacIntention{
|
|
|
|
{
|
|
|
|
Source: nameWild,
|
2022-06-10 21:15:22 +00:00
|
|
|
NotSources: []rbacService{
|
2021-07-15 15:09:00 +00:00
|
|
|
nameWeb,
|
|
|
|
},
|
|
|
|
Action: intentionActionAllow,
|
|
|
|
Permissions: nil,
|
|
|
|
Precedence: 8,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: andPrincipals(
|
|
|
|
[]*envoy_rbac_v3.Principal{
|
|
|
|
idPrincipal(nameWild),
|
|
|
|
notPrincipal(
|
|
|
|
idPrincipal(nameWeb),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-06-10 21:15:22 +00:00
|
|
|
// ========= Sanity check that peers get passed through
|
|
|
|
"default-deny-peered": {
|
|
|
|
intentionDefaultAllow: false,
|
|
|
|
http: true,
|
|
|
|
intentions: sorted(
|
|
|
|
testSourceIntention(ixnOpts{
|
|
|
|
src: "*",
|
|
|
|
action: structs.IntentionActionAllow,
|
|
|
|
peer: "peer1",
|
|
|
|
}),
|
|
|
|
testSourceIntention(ixnOpts{
|
|
|
|
src: "web",
|
|
|
|
action: structs.IntentionActionAllow,
|
|
|
|
peer: "peer1",
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
expect: []*rbacIntention{
|
|
|
|
{
|
|
|
|
Source: nameWebPeered,
|
|
|
|
Action: intentionActionAllow,
|
|
|
|
Permissions: nil,
|
|
|
|
Precedence: 9,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: idPrincipal(nameWebPeered),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Source: nameWildPeered,
|
|
|
|
Action: intentionActionAllow,
|
|
|
|
NotSources: []rbacService{
|
|
|
|
nameWebPeered,
|
|
|
|
},
|
|
|
|
Permissions: nil,
|
|
|
|
Precedence: 8,
|
|
|
|
Skip: false,
|
|
|
|
ComputedPrincipal: andPrincipals(
|
|
|
|
[]*envoy_rbac_v3.Principal{
|
|
|
|
idPrincipal(nameWildPeered),
|
|
|
|
notPrincipal(
|
|
|
|
idPrincipal(nameWebPeered),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-07-15 15:09:00 +00:00
|
|
|
}
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
testLocalInfo := rbacLocalInfo{
|
|
|
|
trustDomain: testTrustDomain,
|
|
|
|
datacenter: "dc1",
|
|
|
|
}
|
|
|
|
|
2021-07-15 15:09:00 +00:00
|
|
|
for name, tt := range tests {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
Use JWT-auth filter in metadata mode & Delegate validation to RBAC filter (#18062)
### Description
<!-- Please describe why you're making this change, in plain English.
-->
- Currently the jwt-auth filter doesn't take into account the service
identity when validating jwt-auth, it only takes into account the path
and jwt provider during validation. This causes issues when multiple
source intentions restrict access to an endpoint with different JWT
providers.
- To fix these issues, rather than use the JWT auth filter for
validation, we use it in metadata mode and allow it to forward the
successful validated JWT token payload to the RBAC filter which will
make the decisions.
This PR ensures requests with and without JWT tokens successfully go
through the jwt-authn filter. The filter however only forwards the data
for successful/valid tokens. On the RBAC filter level, we check the
payload for claims and token issuer + existing rbac rules.
### Testing & Reproduction steps
<!--
* In the case of bugs, describe how to replicate
* If any manual tests were done, document the steps and the conditions
to replicate
* Call out any important/ relevant unit tests, e2e tests or integration
tests you have added or are adding
-->
- This test covers a multi level jwt requirements (requirements at top
level and permissions level). It also assumes you have envoy running,
you have a redis and a sidecar proxy service registered, and have a way
to generate jwks with jwt. I mostly use:
https://www.scottbrady91.com/tools/jwt for this.
- first write your proxy defaults
```
Kind = "proxy-defaults"
name = "global"
config {
protocol = "http"
}
```
- Create two providers
```
Kind = "jwt-provider"
Name = "auth0"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjog....."
}
}
```
```
Kind = "jwt-provider"
Name = "okta"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjogW3...."
}
}
```
- add a service intention
```
Kind = "service-intentions"
Name = "redis"
JWT = {
Providers = [
{
Name = "okta"
},
]
}
Sources = [
{
Name = "*"
Permissions = [{
Action = "allow"
HTTP = {
PathPrefix = "/workspace"
}
JWT = {
Providers = [
{
Name = "okta"
VerifyClaims = [
{
Path = ["aud"]
Value = "my_client_app"
},
{
Path = ["sub"]
Value = "5be86359073c434bad2da3932222dabe"
}
]
},
]
}
},
{
Action = "allow"
HTTP = {
PathPrefix = "/"
}
JWT = {
Providers = [
{
Name = "auth0"
},
]
}
}]
}
]
```
- generate 3 jwt tokens: 1 from auth0 jwks, 1 from okta jwks with
different claims than `/workspace` expects and 1 with correct claims
- connect to your envoy (change service and address as needed) to view
logs and potential errors. You can add: `-- --log-level debug` to see
what data is being forwarded
```
consul connect envoy -sidecar-for redis1 -grpc-addr 127.0.0.1:8502
```
- Make the following requests:
```
curl -s -H "Authorization: Bearer $Auth0_TOKEN" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_wrong_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_correct_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
Successful request
```
### TODO
* [x] Update test coverage
* [ ] update integration tests (follow-up PR)
* [x] appropriate backport labels added
2023-07-17 15:32:49 +00:00
|
|
|
rbacIxns, err := intentionListToIntermediateRBACForm(tt.intentions, testLocalInfo, tt.http, testPeerTrustBundle, nil)
|
2021-07-15 15:09:00 +00:00
|
|
|
intentionDefaultAction := intentionActionFromBool(tt.intentionDefaultAllow)
|
2022-06-29 15:29:54 +00:00
|
|
|
rbacIxns = removeIntentionPrecedence(rbacIxns, intentionDefaultAction, testLocalInfo)
|
2021-07-15 15:09:00 +00:00
|
|
|
|
Use JWT-auth filter in metadata mode & Delegate validation to RBAC filter (#18062)
### Description
<!-- Please describe why you're making this change, in plain English.
-->
- Currently the jwt-auth filter doesn't take into account the service
identity when validating jwt-auth, it only takes into account the path
and jwt provider during validation. This causes issues when multiple
source intentions restrict access to an endpoint with different JWT
providers.
- To fix these issues, rather than use the JWT auth filter for
validation, we use it in metadata mode and allow it to forward the
successful validated JWT token payload to the RBAC filter which will
make the decisions.
This PR ensures requests with and without JWT tokens successfully go
through the jwt-authn filter. The filter however only forwards the data
for successful/valid tokens. On the RBAC filter level, we check the
payload for claims and token issuer + existing rbac rules.
### Testing & Reproduction steps
<!--
* In the case of bugs, describe how to replicate
* If any manual tests were done, document the steps and the conditions
to replicate
* Call out any important/ relevant unit tests, e2e tests or integration
tests you have added or are adding
-->
- This test covers a multi level jwt requirements (requirements at top
level and permissions level). It also assumes you have envoy running,
you have a redis and a sidecar proxy service registered, and have a way
to generate jwks with jwt. I mostly use:
https://www.scottbrady91.com/tools/jwt for this.
- first write your proxy defaults
```
Kind = "proxy-defaults"
name = "global"
config {
protocol = "http"
}
```
- Create two providers
```
Kind = "jwt-provider"
Name = "auth0"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjog....."
}
}
```
```
Kind = "jwt-provider"
Name = "okta"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjogW3...."
}
}
```
- add a service intention
```
Kind = "service-intentions"
Name = "redis"
JWT = {
Providers = [
{
Name = "okta"
},
]
}
Sources = [
{
Name = "*"
Permissions = [{
Action = "allow"
HTTP = {
PathPrefix = "/workspace"
}
JWT = {
Providers = [
{
Name = "okta"
VerifyClaims = [
{
Path = ["aud"]
Value = "my_client_app"
},
{
Path = ["sub"]
Value = "5be86359073c434bad2da3932222dabe"
}
]
},
]
}
},
{
Action = "allow"
HTTP = {
PathPrefix = "/"
}
JWT = {
Providers = [
{
Name = "auth0"
},
]
}
}]
}
]
```
- generate 3 jwt tokens: 1 from auth0 jwks, 1 from okta jwks with
different claims than `/workspace` expects and 1 with correct claims
- connect to your envoy (change service and address as needed) to view
logs and potential errors. You can add: `-- --log-level debug` to see
what data is being forwarded
```
consul connect envoy -sidecar-for redis1 -grpc-addr 127.0.0.1:8502
```
- Make the following requests:
```
curl -s -H "Authorization: Bearer $Auth0_TOKEN" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_wrong_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_correct_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
Successful request
```
### TODO
* [x] Update test coverage
* [ ] update integration tests (follow-up PR)
* [x] appropriate backport labels added
2023-07-17 15:32:49 +00:00
|
|
|
require.NoError(t, err)
|
2021-07-15 15:09:00 +00:00
|
|
|
require.Equal(t, tt.expect, rbacIxns)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-06 22:09:13 +00:00
|
|
|
func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) {
|
2020-08-27 17:20:58 +00:00
|
|
|
testIntention := func(t *testing.T, src, dst string, action structs.IntentionAction) *structs.Intention {
|
|
|
|
t.Helper()
|
|
|
|
ixn := structs.TestIntention(t)
|
|
|
|
ixn.SourceName = src
|
|
|
|
ixn.DestinationName = dst
|
|
|
|
ixn.Action = action
|
2020-10-06 18:24:05 +00:00
|
|
|
//nolint:staticcheck
|
2020-08-27 17:20:58 +00:00
|
|
|
ixn.UpdatePrecedence()
|
|
|
|
return ixn
|
|
|
|
}
|
|
|
|
testSourceIntention := func(src string, action structs.IntentionAction) *structs.Intention {
|
|
|
|
return testIntention(t, src, "api", action)
|
|
|
|
}
|
2022-06-10 21:15:22 +00:00
|
|
|
testIntentionPeered := func(src string, peer string, action structs.IntentionAction) *structs.Intention {
|
|
|
|
ixn := testIntention(t, src, "api", action)
|
|
|
|
ixn.SourcePeer = peer
|
|
|
|
return ixn
|
|
|
|
}
|
2020-10-06 22:09:13 +00:00
|
|
|
testSourcePermIntention := func(src string, perms ...*structs.IntentionPermission) *structs.Intention {
|
|
|
|
ixn := testIntention(t, src, "api", "")
|
|
|
|
ixn.Permissions = perms
|
|
|
|
return ixn
|
|
|
|
}
|
2023-05-30 17:38:33 +00:00
|
|
|
testIntentionWithJWT := func(src string, action structs.IntentionAction, jwt *structs.IntentionJWTRequirement, perms ...*structs.IntentionPermission) *structs.Intention {
|
|
|
|
ixn := testIntention(t, src, "api", action)
|
|
|
|
ixn.JWT = jwt
|
|
|
|
ixn.Action = action
|
|
|
|
if perms != nil {
|
|
|
|
ixn.Permissions = perms
|
|
|
|
ixn.Action = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return ixn
|
|
|
|
}
|
2022-06-21 02:47:14 +00:00
|
|
|
testPeerTrustBundle := []*pbpeering.PeeringTrustBundle{
|
|
|
|
{
|
2022-06-10 21:15:22 +00:00
|
|
|
PeerName: "peer1",
|
|
|
|
TrustDomain: "peer1.domain",
|
|
|
|
ExportedPartition: "part1",
|
|
|
|
},
|
|
|
|
}
|
2022-06-21 02:47:14 +00:00
|
|
|
testTrustDomain := "test.consul"
|
2023-04-20 16:16:04 +00:00
|
|
|
sorted := func(ixns ...*structs.Intention) structs.SimplifiedIntentions {
|
2020-08-27 17:20:58 +00:00
|
|
|
sort.SliceStable(ixns, func(i, j int) bool {
|
|
|
|
return ixns[j].Precedence < ixns[i].Precedence
|
|
|
|
})
|
2023-04-20 16:16:04 +00:00
|
|
|
return structs.SimplifiedIntentions(ixns)
|
2020-08-27 17:20:58 +00:00
|
|
|
}
|
|
|
|
|
2021-07-15 15:09:00 +00:00
|
|
|
var (
|
|
|
|
permSlashPrefix = &structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/",
|
|
|
|
},
|
|
|
|
}
|
2023-05-30 17:38:33 +00:00
|
|
|
oktaWithClaims = structs.IntentionJWTProvider{
|
|
|
|
Name: "okta",
|
|
|
|
VerifyClaims: []*structs.IntentionJWTClaimVerification{
|
|
|
|
{Path: []string{"roles"}, Value: "testing"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
auth0WithClaims = structs.IntentionJWTProvider{
|
|
|
|
Name: "auth0",
|
|
|
|
VerifyClaims: []*structs.IntentionJWTClaimVerification{
|
|
|
|
{Path: []string{"perms", "role"}, Value: "admin"},
|
|
|
|
},
|
|
|
|
}
|
Use JWT-auth filter in metadata mode & Delegate validation to RBAC filter (#18062)
### Description
<!-- Please describe why you're making this change, in plain English.
-->
- Currently the jwt-auth filter doesn't take into account the service
identity when validating jwt-auth, it only takes into account the path
and jwt provider during validation. This causes issues when multiple
source intentions restrict access to an endpoint with different JWT
providers.
- To fix these issues, rather than use the JWT auth filter for
validation, we use it in metadata mode and allow it to forward the
successful validated JWT token payload to the RBAC filter which will
make the decisions.
This PR ensures requests with and without JWT tokens successfully go
through the jwt-authn filter. The filter however only forwards the data
for successful/valid tokens. On the RBAC filter level, we check the
payload for claims and token issuer + existing rbac rules.
### Testing & Reproduction steps
<!--
* In the case of bugs, describe how to replicate
* If any manual tests were done, document the steps and the conditions
to replicate
* Call out any important/ relevant unit tests, e2e tests or integration
tests you have added or are adding
-->
- This test covers a multi level jwt requirements (requirements at top
level and permissions level). It also assumes you have envoy running,
you have a redis and a sidecar proxy service registered, and have a way
to generate jwks with jwt. I mostly use:
https://www.scottbrady91.com/tools/jwt for this.
- first write your proxy defaults
```
Kind = "proxy-defaults"
name = "global"
config {
protocol = "http"
}
```
- Create two providers
```
Kind = "jwt-provider"
Name = "auth0"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjog....."
}
}
```
```
Kind = "jwt-provider"
Name = "okta"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjogW3...."
}
}
```
- add a service intention
```
Kind = "service-intentions"
Name = "redis"
JWT = {
Providers = [
{
Name = "okta"
},
]
}
Sources = [
{
Name = "*"
Permissions = [{
Action = "allow"
HTTP = {
PathPrefix = "/workspace"
}
JWT = {
Providers = [
{
Name = "okta"
VerifyClaims = [
{
Path = ["aud"]
Value = "my_client_app"
},
{
Path = ["sub"]
Value = "5be86359073c434bad2da3932222dabe"
}
]
},
]
}
},
{
Action = "allow"
HTTP = {
PathPrefix = "/"
}
JWT = {
Providers = [
{
Name = "auth0"
},
]
}
}]
}
]
```
- generate 3 jwt tokens: 1 from auth0 jwks, 1 from okta jwks with
different claims than `/workspace` expects and 1 with correct claims
- connect to your envoy (change service and address as needed) to view
logs and potential errors. You can add: `-- --log-level debug` to see
what data is being forwarded
```
consul connect envoy -sidecar-for redis1 -grpc-addr 127.0.0.1:8502
```
- Make the following requests:
```
curl -s -H "Authorization: Bearer $Auth0_TOKEN" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_wrong_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_correct_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
Successful request
```
### TODO
* [x] Update test coverage
* [ ] update integration tests (follow-up PR)
* [x] appropriate backport labels added
2023-07-17 15:32:49 +00:00
|
|
|
testJWTProviderConfigEntry = map[string]*structs.JWTProviderConfigEntry{
|
|
|
|
"okta": {Name: "okta", Issuer: "mytest.okta-issuer"},
|
|
|
|
"auth0": {Name: "auth0", Issuer: "mytest.auth0-issuer"},
|
|
|
|
}
|
2023-05-30 17:38:33 +00:00
|
|
|
jwtRequirement = &structs.IntentionJWTRequirement{
|
|
|
|
Providers: []*structs.IntentionJWTProvider{
|
|
|
|
&oktaWithClaims,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
auth0Requirement = &structs.IntentionJWTRequirement{
|
|
|
|
Providers: []*structs.IntentionJWTProvider{
|
|
|
|
&auth0WithClaims,
|
|
|
|
},
|
|
|
|
}
|
2021-07-15 15:09:00 +00:00
|
|
|
permDenySlashPrefix = &structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-09-15 16:31:22 +00:00
|
|
|
makeSpiffe := func(name string, entMeta *acl.EnterpriseMeta) *pbproxystate.Spiffe {
|
2023-09-13 13:03:42 +00:00
|
|
|
em := *acl.DefaultEnterpriseMeta()
|
|
|
|
if entMeta != nil {
|
|
|
|
em = *entMeta
|
|
|
|
}
|
2023-09-15 16:31:22 +00:00
|
|
|
regex := makeSpiffePattern(rbacService{
|
2023-09-13 13:03:42 +00:00
|
|
|
ServiceName: structs.ServiceName{
|
|
|
|
Name: name,
|
|
|
|
EnterpriseMeta: em,
|
|
|
|
},
|
|
|
|
TrustDomain: testTrustDomain,
|
|
|
|
})
|
2023-09-15 16:31:22 +00:00
|
|
|
return &pbproxystate.Spiffe{Regex: regex}
|
2023-09-13 13:03:42 +00:00
|
|
|
}
|
|
|
|
|
2020-08-27 17:20:58 +00:00
|
|
|
tests := map[string]struct {
|
2024-01-24 02:07:58 +00:00
|
|
|
intentionDefaultAllow bool
|
|
|
|
v1Intentions structs.SimplifiedIntentions
|
|
|
|
v2TrafficPermissions *pbproxystate.TrafficPermissions
|
2020-08-27 17:20:58 +00:00
|
|
|
}{
|
|
|
|
"default-deny-mixed-precedence": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-08-27 17:20:58 +00:00
|
|
|
testIntention(t, "web", "api", structs.IntentionActionAllow),
|
|
|
|
testIntention(t, "*", "api", structs.IntentionActionDeny),
|
|
|
|
testIntention(t, "web", "*", structs.IntentionActionDeny),
|
|
|
|
),
|
2024-01-24 02:07:58 +00:00
|
|
|
v2TrafficPermissions: &pbproxystate.TrafficPermissions{
|
2023-09-15 16:31:22 +00:00
|
|
|
AllowPermissions: []*pbproxystate.Permission{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Principals: []*pbproxystate.Principal{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("web", nil),
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-08-27 17:20:58 +00:00
|
|
|
},
|
|
|
|
"default-deny-service-wildcard-allow": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-08-27 17:20:58 +00:00
|
|
|
testSourceIntention("*", structs.IntentionActionAllow),
|
|
|
|
),
|
2024-01-24 02:07:58 +00:00
|
|
|
v2TrafficPermissions: &pbproxystate.TrafficPermissions{
|
2023-09-15 16:31:22 +00:00
|
|
|
AllowPermissions: []*pbproxystate.Permission{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Principals: []*pbproxystate.Principal{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("*", nil),
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-08-27 17:20:58 +00:00
|
|
|
},
|
|
|
|
"default-allow-service-wildcard-deny": {
|
|
|
|
intentionDefaultAllow: true,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-08-27 17:20:58 +00:00
|
|
|
testSourceIntention("*", structs.IntentionActionDeny),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"default-deny-one-allow": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-08-27 17:20:58 +00:00
|
|
|
testSourceIntention("web", structs.IntentionActionAllow),
|
|
|
|
),
|
2024-01-24 02:07:58 +00:00
|
|
|
v2TrafficPermissions: &pbproxystate.TrafficPermissions{
|
2023-09-15 16:31:22 +00:00
|
|
|
AllowPermissions: []*pbproxystate.Permission{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Principals: []*pbproxystate.Principal{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("web", nil),
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-08-27 17:20:58 +00:00
|
|
|
},
|
|
|
|
"default-allow-one-deny": {
|
|
|
|
intentionDefaultAllow: true,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-08-27 17:20:58 +00:00
|
|
|
testSourceIntention("web", structs.IntentionActionDeny),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"default-deny-allow-deny": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-08-27 17:20:58 +00:00
|
|
|
testSourceIntention("web", structs.IntentionActionDeny),
|
|
|
|
testSourceIntention("*", structs.IntentionActionAllow),
|
|
|
|
),
|
2024-01-24 02:07:58 +00:00
|
|
|
v2TrafficPermissions: &pbproxystate.TrafficPermissions{
|
2023-09-15 16:31:22 +00:00
|
|
|
AllowPermissions: []*pbproxystate.Permission{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Principals: []*pbproxystate.Principal{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("*", nil),
|
|
|
|
ExcludeSpiffes: []*pbproxystate.Spiffe{makeSpiffe("web", nil)},
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-08-27 17:20:58 +00:00
|
|
|
},
|
|
|
|
"default-deny-kitchen-sink": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-08-27 17:20:58 +00:00
|
|
|
// (double exact)
|
|
|
|
testSourceIntention("web", structs.IntentionActionAllow),
|
|
|
|
testSourceIntention("unsafe", structs.IntentionActionDeny),
|
|
|
|
testSourceIntention("cron", structs.IntentionActionAllow),
|
|
|
|
testSourceIntention("*", structs.IntentionActionAllow),
|
|
|
|
),
|
2024-01-24 02:07:58 +00:00
|
|
|
v2TrafficPermissions: &pbproxystate.TrafficPermissions{
|
2023-09-15 16:31:22 +00:00
|
|
|
AllowPermissions: []*pbproxystate.Permission{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Principals: []*pbproxystate.Principal{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("cron", nil),
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("web", nil),
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("*", nil),
|
|
|
|
ExcludeSpiffes: []*pbproxystate.Spiffe{
|
|
|
|
makeSpiffe("web", nil),
|
|
|
|
makeSpiffe("unsafe", nil),
|
|
|
|
makeSpiffe("cron", nil),
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"v2-kitchen-sink": {
|
|
|
|
intentionDefaultAllow: false,
|
2024-01-24 02:07:58 +00:00
|
|
|
v2TrafficPermissions: &pbproxystate.TrafficPermissions{
|
2023-09-15 16:31:22 +00:00
|
|
|
AllowPermissions: []*pbproxystate.Permission{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Principals: []*pbproxystate.Principal{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("api", nil),
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("*", nil),
|
|
|
|
ExcludeSpiffes: []*pbproxystate.Spiffe{
|
|
|
|
makeSpiffe("unsafe", nil),
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Principals: []*pbproxystate.Principal{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("web", nil),
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2023-09-15 16:31:22 +00:00
|
|
|
DenyPermissions: []*pbproxystate.Permission{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Principals: []*pbproxystate.Principal{
|
2023-09-13 13:03:42 +00:00
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("db", nil),
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
{
|
2023-09-15 16:31:22 +00:00
|
|
|
Spiffe: makeSpiffe("cron", nil),
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"v2-default-deny": {
|
2024-01-24 02:07:58 +00:00
|
|
|
intentionDefaultAllow: false,
|
|
|
|
v2TrafficPermissions: &pbproxystate.TrafficPermissions{},
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
|
|
|
"v2-default-allow": {
|
2024-01-24 02:07:58 +00:00
|
|
|
intentionDefaultAllow: true,
|
|
|
|
v2TrafficPermissions: &pbproxystate.TrafficPermissions{},
|
2023-09-13 13:03:42 +00:00
|
|
|
},
|
2023-09-28 19:11:59 +00:00
|
|
|
// This validates that we don't send xDS messages to Envoy that will fail validation.
|
|
|
|
// Traffic permissions validations prevent this from being written to the IR, so the thing
|
|
|
|
// that matters is that the snapshot is valid to Envoy.
|
|
|
|
"v2-ignore-empty-permissions": {
|
2023-10-04 13:58:28 +00:00
|
|
|
intentionDefaultAllow: false,
|
2024-01-24 02:07:58 +00:00
|
|
|
v2TrafficPermissions: &pbproxystate.TrafficPermissions{
|
2023-09-28 19:11:59 +00:00
|
|
|
DenyPermissions: []*pbproxystate.Permission{
|
|
|
|
{},
|
|
|
|
},
|
2023-10-04 13:58:28 +00:00
|
|
|
AllowPermissions: []*pbproxystate.Permission{
|
|
|
|
{},
|
|
|
|
},
|
2023-09-28 19:11:59 +00:00
|
|
|
},
|
|
|
|
},
|
2020-08-27 17:20:58 +00:00
|
|
|
"default-allow-kitchen-sink": {
|
|
|
|
intentionDefaultAllow: true,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-08-27 17:20:58 +00:00
|
|
|
// (double exact)
|
|
|
|
testSourceIntention("web", structs.IntentionActionDeny),
|
|
|
|
testSourceIntention("unsafe", structs.IntentionActionAllow),
|
|
|
|
testSourceIntention("cron", structs.IntentionActionDeny),
|
|
|
|
testSourceIntention("*", structs.IntentionActionDeny),
|
|
|
|
),
|
|
|
|
},
|
2022-06-10 21:15:22 +00:00
|
|
|
"default-deny-peered-kitchen-sink": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2022-06-10 21:15:22 +00:00
|
|
|
testSourceIntention("web", structs.IntentionActionAllow),
|
|
|
|
testIntentionPeered("*", "peer1", structs.IntentionActionAllow),
|
|
|
|
testIntentionPeered("web", "peer1", structs.IntentionActionDeny),
|
|
|
|
),
|
|
|
|
},
|
2021-07-15 15:09:00 +00:00
|
|
|
// ========================
|
|
|
|
"default-allow-path-allow": {
|
|
|
|
intentionDefaultAllow: true,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2021-07-15 15:09:00 +00:00
|
|
|
testSourcePermIntention("web", permSlashPrefix),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"default-deny-path-allow": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2021-07-15 15:09:00 +00:00
|
|
|
testSourcePermIntention("web", permSlashPrefix),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"default-allow-path-deny": {
|
|
|
|
intentionDefaultAllow: true,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2021-07-15 15:09:00 +00:00
|
|
|
testSourcePermIntention("web", permDenySlashPrefix),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"default-deny-path-deny": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2021-07-15 15:09:00 +00:00
|
|
|
testSourcePermIntention("web", permDenySlashPrefix),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
// ========================
|
|
|
|
"default-allow-deny-all-and-path-allow": {
|
|
|
|
intentionDefaultAllow: true,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2021-07-15 15:09:00 +00:00
|
|
|
testSourcePermIntention("web",
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
testSourceIntention("*", structs.IntentionActionDeny),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"default-deny-deny-all-and-path-allow": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2021-07-15 15:09:00 +00:00
|
|
|
testSourcePermIntention("web",
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
testSourceIntention("*", structs.IntentionActionDeny),
|
|
|
|
),
|
2024-01-24 02:07:58 +00:00
|
|
|
v2TrafficPermissions: &pbproxystate.TrafficPermissions{
|
|
|
|
AllowPermissions: []*pbproxystate.Permission{
|
|
|
|
{
|
|
|
|
Principals: []*pbproxystate.Principal{
|
|
|
|
{
|
|
|
|
Spiffe: makeSpiffe("web", nil),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
DestinationRules: []*pbproxystate.DestinationRule{
|
|
|
|
{
|
|
|
|
PathPrefix: "/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-07-15 15:09:00 +00:00
|
|
|
},
|
|
|
|
"default-allow-deny-all-and-path-deny": {
|
|
|
|
intentionDefaultAllow: true,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2021-07-15 15:09:00 +00:00
|
|
|
testSourcePermIntention("web",
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
testSourceIntention("*", structs.IntentionActionDeny),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"default-deny-deny-all-and-path-deny": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2021-07-15 15:09:00 +00:00
|
|
|
testSourcePermIntention("web",
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
testSourceIntention("*", structs.IntentionActionDeny),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
// ========================
|
2020-10-06 22:09:13 +00:00
|
|
|
"default-deny-two-path-deny-and-path-allow": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-10-06 22:09:13 +00:00
|
|
|
testSourcePermIntention("web",
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathExact: "/v1/secret",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathExact: "/v1/admin",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"default-allow-two-path-deny-and-path-allow": {
|
|
|
|
intentionDefaultAllow: true,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-10-06 22:09:13 +00:00
|
|
|
testSourcePermIntention("web",
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathExact: "/v1/secret",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathExact: "/v1/admin",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"default-deny-single-intention-with-kitchen-sink-perms": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-10-06 22:09:13 +00:00
|
|
|
testSourcePermIntention("web",
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathExact: "/v1/secret",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/v1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathRegex: "/v[123]",
|
|
|
|
Methods: []string{"GET", "HEAD", "OPTIONS"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
Header: []structs.IntentionHTTPHeaderPermission{
|
|
|
|
{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},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"default-allow-single-intention-with-kitchen-sink-perms": {
|
|
|
|
intentionDefaultAllow: true,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2020-10-06 22:09:13 +00:00
|
|
|
testSourcePermIntention("web",
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathExact: "/v1/secret",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/v1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathRegex: "/v[123]",
|
|
|
|
Methods: []string{"GET", "HEAD", "OPTIONS"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionDeny,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
Header: []structs.IntentionHTTPHeaderPermission{
|
|
|
|
{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},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
},
|
2023-05-30 17:38:33 +00:00
|
|
|
// ========= JWTAuthn Filter checks
|
|
|
|
"top-level-jwt-no-permissions": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2023-05-30 17:38:33 +00:00
|
|
|
testIntentionWithJWT("web", structs.IntentionActionAllow, jwtRequirement),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"empty-top-level-jwt-with-one-permission": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2023-05-30 17:38:33 +00:00
|
|
|
testIntentionWithJWT("web", structs.IntentionActionAllow, nil, &structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathPrefix: "some-path",
|
|
|
|
},
|
|
|
|
JWT: jwtRequirement,
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"top-level-jwt-with-one-permission": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2023-05-30 17:38:33 +00:00
|
|
|
testIntentionWithJWT("web",
|
|
|
|
structs.IntentionActionAllow,
|
|
|
|
jwtRequirement,
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathExact: "/v1/secret",
|
|
|
|
},
|
|
|
|
JWT: auth0Requirement,
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathExact: "/v1/admin",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"top-level-jwt-with-multiple-permissions": {
|
|
|
|
intentionDefaultAllow: false,
|
2023-09-13 13:03:42 +00:00
|
|
|
v1Intentions: sorted(
|
2023-05-30 17:38:33 +00:00
|
|
|
testIntentionWithJWT("web",
|
|
|
|
structs.IntentionActionAllow,
|
|
|
|
jwtRequirement,
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathExact: "/v1/secret",
|
|
|
|
},
|
|
|
|
JWT: auth0Requirement,
|
|
|
|
},
|
|
|
|
&structs.IntentionPermission{
|
|
|
|
Action: structs.IntentionActionAllow,
|
|
|
|
HTTP: &structs.IntentionHTTPPermission{
|
|
|
|
PathExact: "/v1/admin",
|
|
|
|
},
|
|
|
|
JWT: auth0Requirement,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
},
|
2020-08-27 17:20:58 +00:00
|
|
|
}
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
testLocalInfo := rbacLocalInfo{
|
|
|
|
trustDomain: testTrustDomain,
|
|
|
|
datacenter: "dc1",
|
|
|
|
}
|
|
|
|
|
2020-08-27 17:20:58 +00:00
|
|
|
for name, tt := range tests {
|
|
|
|
tt := tt
|
|
|
|
t.Run(name, func(t *testing.T) {
|
2020-10-06 22:09:13 +00:00
|
|
|
t.Run("network filter", func(t *testing.T) {
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
t.Run("current", func(t *testing.T) {
|
2023-09-13 13:03:42 +00:00
|
|
|
if len(tt.v1Intentions) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
filter, err := makeRBACNetworkFilter(tt.v1Intentions, tt.intentionDefaultAllow, testLocalInfo, testPeerTrustBundle)
|
|
|
|
require.NoError(t, err)
|
2021-02-26 22:23:15 +00:00
|
|
|
gotJSON := protoToJSON(t, filter)
|
2020-10-06 22:09:13 +00:00
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
require.JSONEq(t, goldenSimple(t, filepath.Join("rbac", name), gotJSON), gotJSON)
|
|
|
|
})
|
2023-09-13 13:03:42 +00:00
|
|
|
|
|
|
|
t.Run("v1 vs v2", func(t *testing.T) {
|
2024-01-24 02:07:58 +00:00
|
|
|
if tt.v2TrafficPermissions == nil {
|
2023-09-13 13:03:42 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-24 02:07:58 +00:00
|
|
|
tt.v2TrafficPermissions.DefaultAllow = tt.intentionDefaultAllow
|
2023-10-04 13:58:28 +00:00
|
|
|
|
2024-01-24 02:07:58 +00:00
|
|
|
filters, err := xdsv2.MakeRBACNetworkFilters(tt.v2TrafficPermissions)
|
2023-09-13 13:03:42 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
var gotJSON string
|
|
|
|
if len(filters) == 1 {
|
|
|
|
gotJSON = protoToJSON(t, filters[0])
|
|
|
|
} else {
|
|
|
|
// This is wrapped because protoToJSON won't encode an array of protobufs.
|
|
|
|
chain := &envoy_listener_v3.FilterChain{}
|
|
|
|
chain.Filters = filters
|
|
|
|
gotJSON = protoToJSON(t, chain)
|
|
|
|
}
|
|
|
|
|
|
|
|
require.JSONEq(t, goldenSimple(t, filepath.Join("rbac", name), gotJSON), gotJSON)
|
|
|
|
})
|
2020-10-06 22:09:13 +00:00
|
|
|
})
|
2023-09-13 13:03:42 +00:00
|
|
|
|
2020-10-06 22:09:13 +00:00
|
|
|
t.Run("http filter", func(t *testing.T) {
|
2020-08-27 17:20:58 +00:00
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
t.Run("current", func(t *testing.T) {
|
2024-01-24 02:07:58 +00:00
|
|
|
if len(tt.v1Intentions) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
filter, err := makeRBACHTTPFilter(tt.v1Intentions, tt.intentionDefaultAllow, testLocalInfo, testPeerTrustBundle, testJWTProviderConfigEntry)
|
|
|
|
require.NoError(t, err)
|
2021-02-26 22:23:15 +00:00
|
|
|
gotJSON := protoToJSON(t, filter)
|
|
|
|
|
|
|
|
require.JSONEq(t, goldenSimple(t, filepath.Join("rbac", name+"--httpfilter"), gotJSON), gotJSON)
|
|
|
|
})
|
2024-01-24 02:07:58 +00:00
|
|
|
|
|
|
|
t.Run("v1 vs v2", func(t *testing.T) {
|
|
|
|
if tt.v2TrafficPermissions == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
tt.v2TrafficPermissions.DefaultAllow = tt.intentionDefaultAllow
|
|
|
|
|
|
|
|
filters, err := xdsv2.MakeRBACHTTPFilters(tt.v2TrafficPermissions)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
var gotJSON string
|
|
|
|
if len(filters) == 1 {
|
|
|
|
gotJSON = protoToJSON(t, filters[0])
|
|
|
|
} else {
|
|
|
|
// This is wrapped because protoToJSON won't encode an array of protobufs.
|
|
|
|
manager := &http_connection_managerv3.HttpConnectionManager{}
|
|
|
|
manager.HttpFilters = filters
|
|
|
|
gotJSON = protoToJSON(t, manager)
|
|
|
|
}
|
|
|
|
|
|
|
|
require.JSONEq(t, goldenSimple(t, filepath.Join("rbac", name+"--httpfilter"), gotJSON), gotJSON)
|
|
|
|
})
|
2020-10-06 22:09:13 +00:00
|
|
|
})
|
2020-08-27 17:20:58 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemoveSameSourceIntentions(t *testing.T) {
|
|
|
|
testIntention := func(t *testing.T, src, dst string) *structs.Intention {
|
|
|
|
t.Helper()
|
|
|
|
ixn := structs.TestIntention(t)
|
|
|
|
ixn.SourceName = src
|
|
|
|
ixn.DestinationName = dst
|
2020-10-06 18:24:05 +00:00
|
|
|
//nolint:staticcheck
|
2020-08-27 17:20:58 +00:00
|
|
|
ixn.UpdatePrecedence()
|
|
|
|
return ixn
|
|
|
|
}
|
2022-06-10 21:15:22 +00:00
|
|
|
testIntentionPeered := func(t *testing.T, src, dst, peer string) *structs.Intention {
|
|
|
|
t.Helper()
|
|
|
|
ixn := structs.TestIntention(t)
|
|
|
|
ixn.SourceName = src
|
|
|
|
ixn.SourcePeer = peer
|
|
|
|
ixn.DestinationName = dst
|
|
|
|
//nolint:staticcheck
|
|
|
|
ixn.UpdatePrecedence()
|
|
|
|
return ixn
|
|
|
|
}
|
2023-04-20 16:16:04 +00:00
|
|
|
sorted := func(ixns ...*structs.Intention) structs.SimplifiedIntentions {
|
2020-08-27 17:20:58 +00:00
|
|
|
sort.SliceStable(ixns, func(i, j int) bool {
|
|
|
|
return ixns[j].Precedence < ixns[i].Precedence
|
|
|
|
})
|
2023-04-20 16:16:04 +00:00
|
|
|
return structs.SimplifiedIntentions(ixns)
|
2020-08-27 17:20:58 +00:00
|
|
|
}
|
|
|
|
tests := map[string]struct {
|
2023-04-20 16:16:04 +00:00
|
|
|
in structs.SimplifiedIntentions
|
|
|
|
expect structs.SimplifiedIntentions
|
2020-08-27 17:20:58 +00:00
|
|
|
}{
|
|
|
|
"empty": {},
|
|
|
|
"one": {
|
|
|
|
in: sorted(
|
|
|
|
testIntention(t, "*", "*"),
|
|
|
|
),
|
|
|
|
expect: sorted(
|
|
|
|
testIntention(t, "*", "*"),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"two with no match": {
|
|
|
|
in: sorted(
|
|
|
|
testIntention(t, "*", "foo"),
|
|
|
|
testIntention(t, "bar", "*"),
|
|
|
|
),
|
|
|
|
expect: sorted(
|
|
|
|
testIntention(t, "*", "foo"),
|
|
|
|
testIntention(t, "bar", "*"),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"two with match, exact": {
|
|
|
|
in: sorted(
|
|
|
|
testIntention(t, "bar", "foo"),
|
|
|
|
testIntention(t, "bar", "*"),
|
|
|
|
),
|
|
|
|
expect: sorted(
|
|
|
|
testIntention(t, "bar", "foo"),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
"two with match, wildcard": {
|
|
|
|
in: sorted(
|
|
|
|
testIntention(t, "*", "foo"),
|
|
|
|
testIntention(t, "*", "*"),
|
|
|
|
),
|
|
|
|
expect: sorted(
|
|
|
|
testIntention(t, "*", "foo"),
|
|
|
|
),
|
|
|
|
},
|
2022-06-10 21:15:22 +00:00
|
|
|
"kitchen sink with peers": {
|
|
|
|
in: sorted(
|
|
|
|
testIntention(t, "bar", "foo"),
|
|
|
|
testIntentionPeered(t, "bar", "foo", "peer1"),
|
|
|
|
testIntentionPeered(t, "bar", "*", "peer1"),
|
|
|
|
testIntentionPeered(t, "*", "foo", "peer1"),
|
|
|
|
testIntentionPeered(t, "*", "*", "peer1"),
|
|
|
|
),
|
|
|
|
expect: sorted(
|
|
|
|
testIntention(t, "bar", "foo"),
|
|
|
|
testIntentionPeered(t, "bar", "foo", "peer1"),
|
|
|
|
testIntentionPeered(t, "*", "foo", "peer1"),
|
|
|
|
),
|
|
|
|
},
|
2020-08-27 17:20:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range tests {
|
|
|
|
tc := tc
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
got := removeSameSourceIntentions(tc.in)
|
|
|
|
require.Equal(t, tc.expect, got)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSimplifyNotSourceSlice(t *testing.T) {
|
|
|
|
tests := map[string]struct {
|
|
|
|
in []string
|
|
|
|
expect []string
|
|
|
|
}{
|
|
|
|
"empty": {},
|
|
|
|
"one": {
|
|
|
|
[]string{"bar"},
|
|
|
|
[]string{"bar"},
|
|
|
|
},
|
|
|
|
"two with no match": {
|
|
|
|
[]string{"foo", "bar"},
|
|
|
|
[]string{"foo", "bar"},
|
|
|
|
},
|
|
|
|
"two with match": {
|
|
|
|
[]string{"*", "bar"},
|
|
|
|
[]string{"*"},
|
|
|
|
},
|
|
|
|
"three with two matches down to one": {
|
|
|
|
[]string{"*", "foo", "bar"},
|
|
|
|
[]string{"*"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range tests {
|
|
|
|
tc := tc
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
got := simplifyNotSourceSlice(makeServiceNameSlice(tc.in))
|
|
|
|
require.Equal(t, makeServiceNameSlice(tc.expect), got)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestIxnSourceMatches(t *testing.T) {
|
|
|
|
tests := []struct {
|
2022-06-10 21:15:22 +00:00
|
|
|
tester string
|
|
|
|
testerPeer string
|
|
|
|
against string
|
|
|
|
againstPeer string
|
|
|
|
matches bool
|
2020-08-27 17:20:58 +00:00
|
|
|
}{
|
|
|
|
// identical precedence
|
2022-06-10 21:15:22 +00:00
|
|
|
{"web", "", "api", "", false},
|
|
|
|
{"*", "", "*", "", false},
|
2020-08-27 17:20:58 +00:00
|
|
|
// backwards precedence
|
2022-06-10 21:15:22 +00:00
|
|
|
{"*", "", "web", "", false},
|
2020-08-27 17:20:58 +00:00
|
|
|
// name wildcards
|
2022-06-10 21:15:22 +00:00
|
|
|
{"web", "", "*", "", true},
|
|
|
|
|
|
|
|
// peered cmp peered
|
|
|
|
{"web", "peer1", "api", "peer1", false},
|
|
|
|
{"*", "peer1", "*", "peer1", false},
|
|
|
|
// no match if peer is different
|
|
|
|
{"web", "peer1", "web", "", false},
|
|
|
|
{"*", "peer1", "*", "peer2", false},
|
|
|
|
// name wildcards with peer
|
|
|
|
{"web", "peer1", "*", "peer1", true},
|
2020-08-27 17:20:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range tests {
|
2022-06-10 21:15:22 +00:00
|
|
|
t.Run(fmt.Sprintf("%s%s cmp %s%s", tc.testerPeer, tc.tester, tc.againstPeer, tc.against), func(t *testing.T) {
|
2020-08-27 17:20:58 +00:00
|
|
|
matches := ixnSourceMatches(
|
2022-06-10 21:15:22 +00:00
|
|
|
rbacService{ServiceName: structs.ServiceNameFromString(tc.tester), Peer: tc.testerPeer},
|
|
|
|
rbacService{ServiceName: structs.ServiceNameFromString(tc.against), Peer: tc.againstPeer},
|
2020-08-27 17:20:58 +00:00
|
|
|
)
|
|
|
|
assert.Equal(t, tc.matches, matches)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
func makeServiceNameSlice(slice []string) []rbacService {
|
2020-08-27 17:20:58 +00:00
|
|
|
if len(slice) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2022-06-10 21:15:22 +00:00
|
|
|
var out []rbacService
|
2020-08-27 17:20:58 +00:00
|
|
|
for _, src := range slice {
|
2022-06-10 21:15:22 +00:00
|
|
|
out = append(out, rbacService{ServiceName: structs.ServiceNameFromString(src)})
|
2020-08-27 17:20:58 +00:00
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
2023-01-06 17:13:40 +00:00
|
|
|
|
|
|
|
func TestSpiffeMatcher(t *testing.T) {
|
|
|
|
cases := map[string]struct {
|
|
|
|
xfcc string
|
|
|
|
trustDomain string
|
|
|
|
namespace string
|
|
|
|
partition string
|
|
|
|
datacenter string
|
|
|
|
service string
|
|
|
|
}{
|
|
|
|
"between admin partitions": {
|
|
|
|
xfcc: `By=spiffe://70c72965-291c-d138-e5a6-cfd8a66b395e.consul/ap/ap1/ns/default/dc/primary/svc/s2;Hash=377330adafa619abe52672246b7be7410d74b7497e9d88a8396d641fd6f82ad2;Cert="-----BEGIN%20CERTIFICATE-----%0AMIICGTCCAb%2BgAwIBAgIBCzAKBggqhkjOPQQDAjAwMS4wLAYDVQQDEyVwcmktMTJj%0AOWtvbS5jb25zdWwuY2EuNzBjNzI5NjUuY29uc3VsMB4XDTIyMTIyMjE0MjE1NVoX%0ADTIyMTIyNTE0MjE1NVowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPuJbVdQ%0AYsT8RnvMLT%2FpsuZwltWbCkwxzBR03%2FEC4f7TyLy1Mfe6gm%2Fz5K8Tc29d7W16PBT0%0AR%2B1XPfpigopVanyjgfkwgfYwDgYDVR0PAQH%2FBAQDAgO4MB0GA1UdJQQWMBQGCCsG%0AAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMCkGA1UdDgQiBCBBxpy1QXfp%0AS4V8QFH%2BEfF39VP51Qbhlj75N5gbUSxGajArBgNVHSMEJDAigCCjWP%2BlGhzd4jbD%0A2QI66cvAAgIkLqG0lz0PyzTz76QoOzBfBgNVHREBAf8EVTBThlFzcGlmZmU6Ly83%0AMGM3Mjk2NS0yOTFjLWQxMzgtZTVhNi1jZmQ4YTY2YjM5NWUuY29uc3VsL25zL2Rl%0AZmF1bHQvZGMvcHJpbWFyeS9zdmMvczEwCgYIKoZIzj0EAwIDSAAwRQIhAJxWHplX%0Aqgmd4cRDMllJsCtOmTZ3v%2B6qDnc545tm%2Bg%2FzAiBwWOqqTZ81BtAtzzWpip1XmUFR%0Afv2SYupWQueXYrOjhw%3D%3D%0A-----END%20CERTIFICATE-----%0A";Chain="-----BEGIN%20CERTIFICATE-----%0AMIICGTCCAb%2BgAwIBAgIBCzAKBggqhkjOPQQDAjAwMS4wLAYDVQQDEyVwcmktMTJj%0AOWtvbS5jb25zdWwuY2EuNzBjNzI5NjUuY29uc3VsMB4XDTIyMTIyMjE0MjE1NVoX%0ADTIyMTIyNTE0MjE1NVowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPuJbVdQ%0AYsT8RnvMLT%2FpsuZwltWbCkwxzBR03%2FEC4f7TyLy1Mfe6gm%2Fz5K8Tc29d7W16PBT0%0AR%2B1XPfpigopVanyjgfkwgfYwDgYDVR0PAQH%2FBAQDAgO4MB0GA1UdJQQWMBQGCCsG%0AAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMCkGA1UdDgQiBCBBxpy1QXfp%0AS4V8QFH%2BEfF39VP51Qbhlj75N5gbUSxGajArBgNVHSMEJDAigCCjWP%2BlGhzd4jbD%0A2QI66cvAAgIkLqG0lz0PyzTz76QoOzBfBgNVHREBAf8EVTBThlFzcGlmZmU6Ly83%0AMGM3Mjk2NS0yOTFjLWQxMzgtZTVhNi1jZmQ4YTY2YjM5NWUuY29uc3VsL25zL2Rl%0AZmF1bHQvZGMvcHJpbWFyeS9zdmMvczEwCgYIKoZIzj0EAwIDSAAwRQIhAJxWHplX%0Aqgmd4cRDMllJsCtOmTZ3v%2B6qDnc545tm%2Bg%2FzAiBwWOqqTZ81BtAtzzWpip1XmUFR%0Afv2SYupWQueXYrOjhw%3D%3D%0A-----END%20CERTIFICATE-----%0A";Subject="";URI=spiffe://70c72965-291c-d138-e5a6-cfd8a66b395e.consul/ap/ap9/ns/default/dc/primary/svc/s1`,
|
|
|
|
trustDomain: "70c72965-291c-d138-e5a6-cfd8a66b395e.consul",
|
|
|
|
namespace: "default",
|
|
|
|
partition: "ap9",
|
|
|
|
datacenter: "primary",
|
|
|
|
service: "s1",
|
|
|
|
},
|
|
|
|
"between services": {
|
|
|
|
xfcc: `By=spiffe://f1efe25e-a9b1-1ae1-b580-98000b84a935.consul/ns/default/dc/primary/svc/s2;Hash=c552ee3990fd6e9bb38b1a8bdd28e8358c339d282e6bb92fc86d04915407f47d;Cert="-----BEGIN%20CERTIFICATE-----%0AMIICGjCCAcCgAwIBAgIBCzAKBggqhkjOPQQDAjAxMS8wLQYDVQQDEyZwcmktOGFt%0AMjNueXouY29uc3VsLmNhLmYxZWZlMjVlLmNvbnN1bDAeFw0yMjEyMjIxNTIxMDVa%0AFw0yMjEyMjUxNTIxMDVaMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASrChLh%0AelrBB5e8X78fSvbKxD8yieadFg4XUeJtZh2xwdWckCGDEtT984ihgM8Hu4E%2FGpgD%0AJcExohFnS4H%2BG3uco4H5MIH2MA4GA1UdDwEB%2FwQEAwIDuDAdBgNVHSUEFjAUBggr%0ABgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH%2FBAIwADApBgNVHQ4EIgQghpyuV%2F4g%0Ac6x%2B5jC9uOZQMY4Km2YZwAnSmmTydjjn7qwwKwYDVR0jBCQwIoAgdO0jdTJzfKYq%0ARCYrWbHr7q%2Bq66ispOnMs6HsEwlxV%2F8wXwYDVR0RAQH%2FBFUwU4ZRc3BpZmZlOi8v%0AZjFlZmUyNWUtYTliMS0xYWUxLWI1ODAtOTgwMDBiODRhOTM1LmNvbnN1bC9ucy9k%0AZWZhdWx0L2RjL3ByaW1hcnkvc3ZjL3MxMAoGCCqGSM49BAMCA0gAMEUCIQDTNsze%0AXCj16YvFsX0PUeUBcX4Hh0nmIkMOHCQiPkXTiAIgKJKf038s6muFJw9UQJJ5SSg%2F%0A3RL1wIWXRhsqy1Y89JQ%3D%0A-----END%20CERTIFICATE-----%0A";Chain="-----BEGIN%20CERTIFICATE-----%0AMIICGjCCAcCgAwIBAgIBCzAKBggqhkjOPQQDAjAxMS8wLQYDVQQDEyZwcmktOGFt%0AMjNueXouY29uc3VsLmNhLmYxZWZlMjVlLmNvbnN1bDAeFw0yMjEyMjIxNTIxMDVa%0AFw0yMjEyMjUxNTIxMDVaMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASrChLh%0AelrBB5e8X78fSvbKxD8yieadFg4XUeJtZh2xwdWckCGDEtT984ihgM8Hu4E%2FGpgD%0AJcExohFnS4H%2BG3uco4H5MIH2MA4GA1UdDwEB%2FwQEAwIDuDAdBgNVHSUEFjAUBggr%0ABgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH%2FBAIwADApBgNVHQ4EIgQghpyuV%2F4g%0Ac6x%2B5jC9uOZQMY4Km2YZwAnSmmTydjjn7qwwKwYDVR0jBCQwIoAgdO0jdTJzfKYq%0ARCYrWbHr7q%2Bq66ispOnMs6HsEwlxV%2F8wXwYDVR0RAQH%2FBFUwU4ZRc3BpZmZlOi8v%0AZjFlZmUyNWUtYTliMS0xYWUxLWI1ODAtOTgwMDBiODRhOTM1LmNvbnN1bC9ucy9k%0AZWZhdWx0L2RjL3ByaW1hcnkvc3ZjL3MxMAoGCCqGSM49BAMCA0gAMEUCIQDTNsze%0AXCj16YvFsX0PUeUBcX4Hh0nmIkMOHCQiPkXTiAIgKJKf038s6muFJw9UQJJ5SSg%2F%0A3RL1wIWXRhsqy1Y89JQ%3D%0A-----END%20CERTIFICATE-----%0A";Subject="";URI=spiffe://f1efe25e-a9b1-1ae1-b580-98000b84a935.consul/ns/default/dc/primary/svc/s1`,
|
|
|
|
trustDomain: "f1efe25e-a9b1-1ae1-b580-98000b84a935.consul",
|
|
|
|
namespace: "default",
|
|
|
|
datacenter: "primary",
|
|
|
|
service: "s1",
|
|
|
|
},
|
|
|
|
"between peers": {
|
|
|
|
xfcc: `By=spiffe://ca9857da-71aa-c5be-ec8f-abcd90cae693.consul/gateway/mesh/dc/alpha;Hash=419c850ddc7a32edc752d73bb0f0c6e4c2f5b40feae7cf0cdeeb6f3dd759ed1f;Cert="-----BEGIN%20CERTIFICATE-----%0AMIICGzCCAcCgAwIBAgIBCzAKBggqhkjOPQQDAjAxMS8wLQYDVQQDEyZwcmktcTgw%0AdmcxMXQuY29uc3VsLmNhLmZjOWEwOGVmLmNvbnN1bDAeFw0yMjEyMjIxNTIyNTBa%0AFw0yMjEyMjUxNTIyNTBaMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQnQtQ6%0AFS%2FqjpopxZIaJtYL3pOx%2BgrzoLtKStCS0SUtGbTBmxmTeIX5l5HHD4yqCWk4M1Iv%0AXNflWvKcpw5KS1tLo4H5MIH2MA4GA1UdDwEB%2FwQEAwIDuDAdBgNVHSUEFjAUBggr%0ABgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH%2FBAIwADApBgNVHQ4EIgQg%2B8FyVm2p%0AdpzfijuCYeByJQH5mUkqY6%2FciCC2yScNusQwKwYDVR0jBCQwIoAgy0MyubT%2BMNQv%0A%2BuZGeBqa1yU9Fx9641epfbY%2BuSs7cbowXwYDVR0RAQH%2FBFUwU4ZRc3BpZmZlOi8v%0AZmM5YTA4ZWYtZWZiNC1iYmM5LWIzZWMtYjkzZTc2OGFiZmMyLmNvbnN1bC9ucy9k%0AZWZhdWx0L2RjL3ByaW1hcnkvc3ZjL3MxMAoGCCqGSM49BAMCA0kAMEYCIQDp7hX0%0AJ%2FjrAP71jDt2w3uKQJnfZ93d%2FRub2t%2FRwQfsVAIhAL4VUbk5XUvBzwabuEfMCf4O%0AT5rjXDbCWYNN2m4xZFtt%0A-----END%20CERTIFICATE-----%0A";Chain="-----BEGIN%20CERTIFICATE-----%0AMIICGzCCAcCgAwIBAgIBCzAKBggqhkjOPQQDAjAxMS8wLQYDVQQDEyZwcmktcTgw%0AdmcxMXQuY29uc3VsLmNhLmZjOWEwOGVmLmNvbnN1bDAeFw0yMjEyMjIxNTIyNTBa%0AFw0yMjEyMjUxNTIyNTBaMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQnQtQ6%0AFS%2FqjpopxZIaJtYL3pOx%2BgrzoLtKStCS0SUtGbTBmxmTeIX5l5HHD4yqCWk4M1Iv%0AXNflWvKcpw5KS1tLo4H5MIH2MA4GA1UdDwEB%2FwQEAwIDuDAdBgNVHSUEFjAUBggr%0ABgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH%2FBAIwADApBgNVHQ4EIgQg%2B8FyVm2p%0AdpzfijuCYeByJQH5mUkqY6%2FciCC2yScNusQwKwYDVR0jBCQwIoAgy0MyubT%2BMNQv%0A%2BuZGeBqa1yU9Fx9641epfbY%2BuSs7cbowXwYDVR0RAQH%2FBFUwU4ZRc3BpZmZlOi8v%0AZmM5YTA4ZWYtZWZiNC1iYmM5LWIzZWMtYjkzZTc2OGFiZmMyLmNvbnN1bC9ucy9k%0AZWZhdWx0L2RjL3ByaW1hcnkvc3ZjL3MxMAoGCCqGSM49BAMCA0kAMEYCIQDp7hX0%0AJ%2FjrAP71jDt2w3uKQJnfZ93d%2FRub2t%2FRwQfsVAIhAL4VUbk5XUvBzwabuEfMCf4O%0AT5rjXDbCWYNN2m4xZFtt%0A-----END%20CERTIFICATE-----%0A";Subject="";URI=spiffe://fc9a08ef-efb4-bbc9-b3ec-b93e768abfc2.consul/ns/default/dc/primary/svc/s1,By=spiffe://ca9857da-71aa-c5be-ec8f-abcd90cae693.consul/ns/default/dc/alpha/svc/s2;Hash=1db4ea1e68df1ea0cec7d7ba882ca734d3e1a29a0fe64e73275b6ab796234295;Cert="-----BEGIN%20CERTIFICATE-----%0AMIICEjCCAbmgAwIBAgIBDDAKBggqhkjOPQQDAjAxMS8wLQYDVQQDEyZwcmktMXky%0AZXVpbHkuY29uc3VsLmNhLmNhOTg1N2RhLmNvbnN1bDAeFw0yMjEyMjIxNTIzMDVa%0AFw0yMjEyMjUxNTIzMDVaMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAROaLaT%0A%2BzyYZKfujWX4vOde%2BnnsGP3z0xaEGQFbgi%2BGU%2BrFfMdadzYF1oXDItS%2FpuBADuha%0Ao0iH2i2aRPUbTm4Ko4HyMIHvMA4GA1UdDwEB%2FwQEAwIDuDAdBgNVHSUEFjAUBggr%0ABgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH%2FBAIwADApBgNVHQ4EIgQgWTznn%2BPz%0A4eNoiwdO%2FID3uqbyiBJBFbZFAGs7m5KnoCkwKwYDVR0jBCQwIoAgAdVe5N4m4Qlv%0Afgp9tvw0MGq7puWWuLfiw7qghdr1VDIwWAYDVR0RAQH%2FBE4wTIZKc3BpZmZlOi8v%0AY2E5ODU3ZGEtNzFhYS1jNWJlLWVjOGYtYWJjZDkwY2FlNjkzLmNvbnN1bC9nYXRl%0Ad2F5L21lc2gvZGMvYWxwaGEwCgYIKoZIzj0EAwIDRwAwRAIgJu5Z6O10nQe9HAzk%0ARonRMODgENawDHbErpkQ1q91ZTYCIEHccGIEp3OybkvkmIB9s%2Bu%2FbguUjJ4ZKAiD%0AV0dKf1Ao%0A-----END%20CERTIFICATE-----%0A";Chain="-----BEGIN%20CERTIFICATE-----%0AMIICEjCCAbmgAwIBAgIBDDAKBggqhkjOPQQDAjAxMS8wLQYDVQQDEyZwcmktMXky%0AZXVpbHkuY29uc3VsLmNhLmNhOTg1N2RhLmNvbnN1bDAeFw0yMjEyMjIxNTIzMDVa%0AFw0yMjEyMjUxNTIzMDVaMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAROaLaT%0A%2BzyYZKfujWX4vOde%2BnnsGP3z0xaEGQFbgi%2BGU%2BrFfMdadzYF1oXDItS%2FpuBADuha%0Ao0iH2i2aRPUbTm4Ko4HyMIHvMA4GA1UdDwEB%2FwQEAwIDuDAdBgNVHSUEFjAUBggr%0ABgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH%2FBAIwADApBgNVHQ4EIgQgWTznn%2BPz%0A4eNoiwdO%2FID3uqbyiBJBFbZFAGs7m5KnoCkwKwYDVR0jBCQwIoAgAdVe5N4m4Qlv%0Afgp9tvw0MGq7puWWuLfiw7qghdr1VDIwWAYDVR0RAQH%2FBE4wTIZKc3BpZmZlOi8v%0AY2E5ODU3ZGEtNzFhYS1jNWJlLWVjOGYtYWJjZDkwY2FlNjkzLmNvbnN1bC9nYXRl%0Ad2F5L21lc2gvZGMvYWxwaGEwCgYIKoZIzj0EAwIDRwAwRAIgJu5Z6O10nQe9HAzk%0ARonRMODgENawDHbErpkQ1q91ZTYCIEHccGIEp3OybkvkmIB9s%2Bu%2FbguUjJ4ZKAiD%0AV0dKf1Ao%0A-----END%20CERTIFICATE-----%0A";Subject="";URI=spiffe://ca9857da-71aa-c5be-ec8f-abcd90cae693.consul/gateway/mesh/dc/alpha`,
|
|
|
|
trustDomain: "fc9a08ef-efb4-bbc9-b3ec-b93e768abfc2.consul",
|
|
|
|
namespace: "default",
|
|
|
|
datacenter: "primary",
|
|
|
|
service: "s1",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
re := regexp.MustCompile(downstreamServiceIdentityMatcher)
|
|
|
|
|
|
|
|
for n, c := range cases {
|
|
|
|
t.Run(n, func(t *testing.T) {
|
|
|
|
matches := re.FindAllStringSubmatch(c.xfcc, -1)
|
|
|
|
require.Len(t, matches, 1)
|
|
|
|
|
|
|
|
m := matches[0]
|
|
|
|
require.Equal(t, c.trustDomain, m[1])
|
|
|
|
require.Equal(t, c.partition, m[2])
|
|
|
|
require.Equal(t, c.namespace, m[3])
|
|
|
|
require.Equal(t, c.datacenter, m[4])
|
|
|
|
require.Equal(t, c.service, m[5])
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-05-30 17:38:33 +00:00
|
|
|
|
|
|
|
func TestPathToSegments(t *testing.T) {
|
|
|
|
tests := map[string]struct {
|
|
|
|
key string
|
|
|
|
paths []string
|
|
|
|
expected []*envoy_matcher_v3.MetadataMatcher_PathSegment
|
|
|
|
}{
|
|
|
|
"single-path": {
|
|
|
|
key: "jwt_payload_okta",
|
|
|
|
paths: []string{"perms"},
|
|
|
|
expected: []*envoy_matcher_v3.MetadataMatcher_PathSegment{
|
|
|
|
{
|
|
|
|
Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "jwt_payload_okta"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "perms"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"multi-paths": {
|
|
|
|
key: "jwt_payload_okta",
|
|
|
|
paths: []string{"perms", "roles"},
|
|
|
|
expected: []*envoy_matcher_v3.MetadataMatcher_PathSegment{
|
|
|
|
{
|
|
|
|
Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "jwt_payload_okta"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "perms"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "roles"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tt := range tests {
|
|
|
|
tt := tt
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
segments := pathToSegments(tt.paths, tt.key)
|
|
|
|
require.ElementsMatch(t, segments, tt.expected)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Use JWT-auth filter in metadata mode & Delegate validation to RBAC filter (#18062)
### Description
<!-- Please describe why you're making this change, in plain English.
-->
- Currently the jwt-auth filter doesn't take into account the service
identity when validating jwt-auth, it only takes into account the path
and jwt provider during validation. This causes issues when multiple
source intentions restrict access to an endpoint with different JWT
providers.
- To fix these issues, rather than use the JWT auth filter for
validation, we use it in metadata mode and allow it to forward the
successful validated JWT token payload to the RBAC filter which will
make the decisions.
This PR ensures requests with and without JWT tokens successfully go
through the jwt-authn filter. The filter however only forwards the data
for successful/valid tokens. On the RBAC filter level, we check the
payload for claims and token issuer + existing rbac rules.
### Testing & Reproduction steps
<!--
* In the case of bugs, describe how to replicate
* If any manual tests were done, document the steps and the conditions
to replicate
* Call out any important/ relevant unit tests, e2e tests or integration
tests you have added or are adding
-->
- This test covers a multi level jwt requirements (requirements at top
level and permissions level). It also assumes you have envoy running,
you have a redis and a sidecar proxy service registered, and have a way
to generate jwks with jwt. I mostly use:
https://www.scottbrady91.com/tools/jwt for this.
- first write your proxy defaults
```
Kind = "proxy-defaults"
name = "global"
config {
protocol = "http"
}
```
- Create two providers
```
Kind = "jwt-provider"
Name = "auth0"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjog....."
}
}
```
```
Kind = "jwt-provider"
Name = "okta"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjogW3...."
}
}
```
- add a service intention
```
Kind = "service-intentions"
Name = "redis"
JWT = {
Providers = [
{
Name = "okta"
},
]
}
Sources = [
{
Name = "*"
Permissions = [{
Action = "allow"
HTTP = {
PathPrefix = "/workspace"
}
JWT = {
Providers = [
{
Name = "okta"
VerifyClaims = [
{
Path = ["aud"]
Value = "my_client_app"
},
{
Path = ["sub"]
Value = "5be86359073c434bad2da3932222dabe"
}
]
},
]
}
},
{
Action = "allow"
HTTP = {
PathPrefix = "/"
}
JWT = {
Providers = [
{
Name = "auth0"
},
]
}
}]
}
]
```
- generate 3 jwt tokens: 1 from auth0 jwks, 1 from okta jwks with
different claims than `/workspace` expects and 1 with correct claims
- connect to your envoy (change service and address as needed) to view
logs and potential errors. You can add: `-- --log-level debug` to see
what data is being forwarded
```
consul connect envoy -sidecar-for redis1 -grpc-addr 127.0.0.1:8502
```
- Make the following requests:
```
curl -s -H "Authorization: Bearer $Auth0_TOKEN" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_wrong_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_correct_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
Successful request
```
### TODO
* [x] Update test coverage
* [ ] update integration tests (follow-up PR)
* [x] appropriate backport labels added
2023-07-17 15:32:49 +00:00
|
|
|
func TestJWTClaimsToPrincipals(t *testing.T) {
|
2023-05-30 17:38:33 +00:00
|
|
|
var (
|
|
|
|
firstClaim = structs.IntentionJWTClaimVerification{
|
|
|
|
Path: []string{"perms"},
|
|
|
|
Value: "admin",
|
|
|
|
}
|
|
|
|
secondClaim = structs.IntentionJWTClaimVerification{
|
|
|
|
Path: []string{"passage"},
|
|
|
|
Value: "secret",
|
|
|
|
}
|
|
|
|
payloadKey = "dummy-key"
|
|
|
|
firstPrincipal = envoy_rbac_v3.Principal{
|
|
|
|
Identifier: &envoy_rbac_v3.Principal_Metadata{
|
|
|
|
Metadata: &envoy_matcher_v3.MetadataMatcher{
|
|
|
|
Filter: jwtEnvoyFilter,
|
|
|
|
Path: pathToSegments(firstClaim.Path, payloadKey),
|
|
|
|
Value: &envoy_matcher_v3.ValueMatcher{
|
|
|
|
MatchPattern: &envoy_matcher_v3.ValueMatcher_StringMatch{
|
|
|
|
StringMatch: &envoy_matcher_v3.StringMatcher{
|
|
|
|
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
|
|
|
|
Exact: firstClaim.Value,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
secondPrincipal = envoy_rbac_v3.Principal{
|
|
|
|
Identifier: &envoy_rbac_v3.Principal_Metadata{
|
|
|
|
Metadata: &envoy_matcher_v3.MetadataMatcher{
|
|
|
|
Filter: jwtEnvoyFilter,
|
Use JWT-auth filter in metadata mode & Delegate validation to RBAC filter (#18062)
### Description
<!-- Please describe why you're making this change, in plain English.
-->
- Currently the jwt-auth filter doesn't take into account the service
identity when validating jwt-auth, it only takes into account the path
and jwt provider during validation. This causes issues when multiple
source intentions restrict access to an endpoint with different JWT
providers.
- To fix these issues, rather than use the JWT auth filter for
validation, we use it in metadata mode and allow it to forward the
successful validated JWT token payload to the RBAC filter which will
make the decisions.
This PR ensures requests with and without JWT tokens successfully go
through the jwt-authn filter. The filter however only forwards the data
for successful/valid tokens. On the RBAC filter level, we check the
payload for claims and token issuer + existing rbac rules.
### Testing & Reproduction steps
<!--
* In the case of bugs, describe how to replicate
* If any manual tests were done, document the steps and the conditions
to replicate
* Call out any important/ relevant unit tests, e2e tests or integration
tests you have added or are adding
-->
- This test covers a multi level jwt requirements (requirements at top
level and permissions level). It also assumes you have envoy running,
you have a redis and a sidecar proxy service registered, and have a way
to generate jwks with jwt. I mostly use:
https://www.scottbrady91.com/tools/jwt for this.
- first write your proxy defaults
```
Kind = "proxy-defaults"
name = "global"
config {
protocol = "http"
}
```
- Create two providers
```
Kind = "jwt-provider"
Name = "auth0"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjog....."
}
}
```
```
Kind = "jwt-provider"
Name = "okta"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjogW3...."
}
}
```
- add a service intention
```
Kind = "service-intentions"
Name = "redis"
JWT = {
Providers = [
{
Name = "okta"
},
]
}
Sources = [
{
Name = "*"
Permissions = [{
Action = "allow"
HTTP = {
PathPrefix = "/workspace"
}
JWT = {
Providers = [
{
Name = "okta"
VerifyClaims = [
{
Path = ["aud"]
Value = "my_client_app"
},
{
Path = ["sub"]
Value = "5be86359073c434bad2da3932222dabe"
}
]
},
]
}
},
{
Action = "allow"
HTTP = {
PathPrefix = "/"
}
JWT = {
Providers = [
{
Name = "auth0"
},
]
}
}]
}
]
```
- generate 3 jwt tokens: 1 from auth0 jwks, 1 from okta jwks with
different claims than `/workspace` expects and 1 with correct claims
- connect to your envoy (change service and address as needed) to view
logs and potential errors. You can add: `-- --log-level debug` to see
what data is being forwarded
```
consul connect envoy -sidecar-for redis1 -grpc-addr 127.0.0.1:8502
```
- Make the following requests:
```
curl -s -H "Authorization: Bearer $Auth0_TOKEN" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_wrong_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_correct_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
Successful request
```
### TODO
* [x] Update test coverage
* [ ] update integration tests (follow-up PR)
* [x] appropriate backport labels added
2023-07-17 15:32:49 +00:00
|
|
|
Path: pathToSegments(secondClaim.Path, payloadKey),
|
2023-05-30 17:38:33 +00:00
|
|
|
Value: &envoy_matcher_v3.ValueMatcher{
|
|
|
|
MatchPattern: &envoy_matcher_v3.ValueMatcher_StringMatch{
|
|
|
|
StringMatch: &envoy_matcher_v3.StringMatcher{
|
|
|
|
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
|
|
|
|
Exact: secondClaim.Value,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
tests := map[string]struct {
|
Use JWT-auth filter in metadata mode & Delegate validation to RBAC filter (#18062)
### Description
<!-- Please describe why you're making this change, in plain English.
-->
- Currently the jwt-auth filter doesn't take into account the service
identity when validating jwt-auth, it only takes into account the path
and jwt provider during validation. This causes issues when multiple
source intentions restrict access to an endpoint with different JWT
providers.
- To fix these issues, rather than use the JWT auth filter for
validation, we use it in metadata mode and allow it to forward the
successful validated JWT token payload to the RBAC filter which will
make the decisions.
This PR ensures requests with and without JWT tokens successfully go
through the jwt-authn filter. The filter however only forwards the data
for successful/valid tokens. On the RBAC filter level, we check the
payload for claims and token issuer + existing rbac rules.
### Testing & Reproduction steps
<!--
* In the case of bugs, describe how to replicate
* If any manual tests were done, document the steps and the conditions
to replicate
* Call out any important/ relevant unit tests, e2e tests or integration
tests you have added or are adding
-->
- This test covers a multi level jwt requirements (requirements at top
level and permissions level). It also assumes you have envoy running,
you have a redis and a sidecar proxy service registered, and have a way
to generate jwks with jwt. I mostly use:
https://www.scottbrady91.com/tools/jwt for this.
- first write your proxy defaults
```
Kind = "proxy-defaults"
name = "global"
config {
protocol = "http"
}
```
- Create two providers
```
Kind = "jwt-provider"
Name = "auth0"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjog....."
}
}
```
```
Kind = "jwt-provider"
Name = "okta"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjogW3...."
}
}
```
- add a service intention
```
Kind = "service-intentions"
Name = "redis"
JWT = {
Providers = [
{
Name = "okta"
},
]
}
Sources = [
{
Name = "*"
Permissions = [{
Action = "allow"
HTTP = {
PathPrefix = "/workspace"
}
JWT = {
Providers = [
{
Name = "okta"
VerifyClaims = [
{
Path = ["aud"]
Value = "my_client_app"
},
{
Path = ["sub"]
Value = "5be86359073c434bad2da3932222dabe"
}
]
},
]
}
},
{
Action = "allow"
HTTP = {
PathPrefix = "/"
}
JWT = {
Providers = [
{
Name = "auth0"
},
]
}
}]
}
]
```
- generate 3 jwt tokens: 1 from auth0 jwks, 1 from okta jwks with
different claims than `/workspace` expects and 1 with correct claims
- connect to your envoy (change service and address as needed) to view
logs and potential errors. You can add: `-- --log-level debug` to see
what data is being forwarded
```
consul connect envoy -sidecar-for redis1 -grpc-addr 127.0.0.1:8502
```
- Make the following requests:
```
curl -s -H "Authorization: Bearer $Auth0_TOKEN" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_wrong_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_correct_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
Successful request
```
### TODO
* [x] Update test coverage
* [ ] update integration tests (follow-up PR)
* [x] appropriate backport labels added
2023-07-17 15:32:49 +00:00
|
|
|
claims []*structs.IntentionJWTClaimVerification
|
|
|
|
metadataPayloadKey string
|
|
|
|
expected *envoy_rbac_v3.Principal
|
2023-05-30 17:38:33 +00:00
|
|
|
}{
|
Use JWT-auth filter in metadata mode & Delegate validation to RBAC filter (#18062)
### Description
<!-- Please describe why you're making this change, in plain English.
-->
- Currently the jwt-auth filter doesn't take into account the service
identity when validating jwt-auth, it only takes into account the path
and jwt provider during validation. This causes issues when multiple
source intentions restrict access to an endpoint with different JWT
providers.
- To fix these issues, rather than use the JWT auth filter for
validation, we use it in metadata mode and allow it to forward the
successful validated JWT token payload to the RBAC filter which will
make the decisions.
This PR ensures requests with and without JWT tokens successfully go
through the jwt-authn filter. The filter however only forwards the data
for successful/valid tokens. On the RBAC filter level, we check the
payload for claims and token issuer + existing rbac rules.
### Testing & Reproduction steps
<!--
* In the case of bugs, describe how to replicate
* If any manual tests were done, document the steps and the conditions
to replicate
* Call out any important/ relevant unit tests, e2e tests or integration
tests you have added or are adding
-->
- This test covers a multi level jwt requirements (requirements at top
level and permissions level). It also assumes you have envoy running,
you have a redis and a sidecar proxy service registered, and have a way
to generate jwks with jwt. I mostly use:
https://www.scottbrady91.com/tools/jwt for this.
- first write your proxy defaults
```
Kind = "proxy-defaults"
name = "global"
config {
protocol = "http"
}
```
- Create two providers
```
Kind = "jwt-provider"
Name = "auth0"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjog....."
}
}
```
```
Kind = "jwt-provider"
Name = "okta"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjogW3...."
}
}
```
- add a service intention
```
Kind = "service-intentions"
Name = "redis"
JWT = {
Providers = [
{
Name = "okta"
},
]
}
Sources = [
{
Name = "*"
Permissions = [{
Action = "allow"
HTTP = {
PathPrefix = "/workspace"
}
JWT = {
Providers = [
{
Name = "okta"
VerifyClaims = [
{
Path = ["aud"]
Value = "my_client_app"
},
{
Path = ["sub"]
Value = "5be86359073c434bad2da3932222dabe"
}
]
},
]
}
},
{
Action = "allow"
HTTP = {
PathPrefix = "/"
}
JWT = {
Providers = [
{
Name = "auth0"
},
]
}
}]
}
]
```
- generate 3 jwt tokens: 1 from auth0 jwks, 1 from okta jwks with
different claims than `/workspace` expects and 1 with correct claims
- connect to your envoy (change service and address as needed) to view
logs and potential errors. You can add: `-- --log-level debug` to see
what data is being forwarded
```
consul connect envoy -sidecar-for redis1 -grpc-addr 127.0.0.1:8502
```
- Make the following requests:
```
curl -s -H "Authorization: Bearer $Auth0_TOKEN" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_wrong_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_correct_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
Successful request
```
### TODO
* [x] Update test coverage
* [ ] update integration tests (follow-up PR)
* [x] appropriate backport labels added
2023-07-17 15:32:49 +00:00
|
|
|
"single-claim": {
|
|
|
|
claims: []*structs.IntentionJWTClaimVerification{&firstClaim},
|
|
|
|
metadataPayloadKey: payloadKey,
|
|
|
|
expected: &firstPrincipal,
|
2023-05-30 17:38:33 +00:00
|
|
|
},
|
Use JWT-auth filter in metadata mode & Delegate validation to RBAC filter (#18062)
### Description
<!-- Please describe why you're making this change, in plain English.
-->
- Currently the jwt-auth filter doesn't take into account the service
identity when validating jwt-auth, it only takes into account the path
and jwt provider during validation. This causes issues when multiple
source intentions restrict access to an endpoint with different JWT
providers.
- To fix these issues, rather than use the JWT auth filter for
validation, we use it in metadata mode and allow it to forward the
successful validated JWT token payload to the RBAC filter which will
make the decisions.
This PR ensures requests with and without JWT tokens successfully go
through the jwt-authn filter. The filter however only forwards the data
for successful/valid tokens. On the RBAC filter level, we check the
payload for claims and token issuer + existing rbac rules.
### Testing & Reproduction steps
<!--
* In the case of bugs, describe how to replicate
* If any manual tests were done, document the steps and the conditions
to replicate
* Call out any important/ relevant unit tests, e2e tests or integration
tests you have added or are adding
-->
- This test covers a multi level jwt requirements (requirements at top
level and permissions level). It also assumes you have envoy running,
you have a redis and a sidecar proxy service registered, and have a way
to generate jwks with jwt. I mostly use:
https://www.scottbrady91.com/tools/jwt for this.
- first write your proxy defaults
```
Kind = "proxy-defaults"
name = "global"
config {
protocol = "http"
}
```
- Create two providers
```
Kind = "jwt-provider"
Name = "auth0"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjog....."
}
}
```
```
Kind = "jwt-provider"
Name = "okta"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjogW3...."
}
}
```
- add a service intention
```
Kind = "service-intentions"
Name = "redis"
JWT = {
Providers = [
{
Name = "okta"
},
]
}
Sources = [
{
Name = "*"
Permissions = [{
Action = "allow"
HTTP = {
PathPrefix = "/workspace"
}
JWT = {
Providers = [
{
Name = "okta"
VerifyClaims = [
{
Path = ["aud"]
Value = "my_client_app"
},
{
Path = ["sub"]
Value = "5be86359073c434bad2da3932222dabe"
}
]
},
]
}
},
{
Action = "allow"
HTTP = {
PathPrefix = "/"
}
JWT = {
Providers = [
{
Name = "auth0"
},
]
}
}]
}
]
```
- generate 3 jwt tokens: 1 from auth0 jwks, 1 from okta jwks with
different claims than `/workspace` expects and 1 with correct claims
- connect to your envoy (change service and address as needed) to view
logs and potential errors. You can add: `-- --log-level debug` to see
what data is being forwarded
```
consul connect envoy -sidecar-for redis1 -grpc-addr 127.0.0.1:8502
```
- Make the following requests:
```
curl -s -H "Authorization: Bearer $Auth0_TOKEN" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_wrong_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_correct_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
Successful request
```
### TODO
* [x] Update test coverage
* [ ] update integration tests (follow-up PR)
* [x] appropriate backport labels added
2023-07-17 15:32:49 +00:00
|
|
|
"multiple-claims": {
|
|
|
|
claims: []*structs.IntentionJWTClaimVerification{&firstClaim, &secondClaim},
|
|
|
|
metadataPayloadKey: payloadKey,
|
2023-05-30 17:38:33 +00:00
|
|
|
expected: &envoy_rbac_v3.Principal{
|
Use JWT-auth filter in metadata mode & Delegate validation to RBAC filter (#18062)
### Description
<!-- Please describe why you're making this change, in plain English.
-->
- Currently the jwt-auth filter doesn't take into account the service
identity when validating jwt-auth, it only takes into account the path
and jwt provider during validation. This causes issues when multiple
source intentions restrict access to an endpoint with different JWT
providers.
- To fix these issues, rather than use the JWT auth filter for
validation, we use it in metadata mode and allow it to forward the
successful validated JWT token payload to the RBAC filter which will
make the decisions.
This PR ensures requests with and without JWT tokens successfully go
through the jwt-authn filter. The filter however only forwards the data
for successful/valid tokens. On the RBAC filter level, we check the
payload for claims and token issuer + existing rbac rules.
### Testing & Reproduction steps
<!--
* In the case of bugs, describe how to replicate
* If any manual tests were done, document the steps and the conditions
to replicate
* Call out any important/ relevant unit tests, e2e tests or integration
tests you have added or are adding
-->
- This test covers a multi level jwt requirements (requirements at top
level and permissions level). It also assumes you have envoy running,
you have a redis and a sidecar proxy service registered, and have a way
to generate jwks with jwt. I mostly use:
https://www.scottbrady91.com/tools/jwt for this.
- first write your proxy defaults
```
Kind = "proxy-defaults"
name = "global"
config {
protocol = "http"
}
```
- Create two providers
```
Kind = "jwt-provider"
Name = "auth0"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjog....."
}
}
```
```
Kind = "jwt-provider"
Name = "okta"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjogW3...."
}
}
```
- add a service intention
```
Kind = "service-intentions"
Name = "redis"
JWT = {
Providers = [
{
Name = "okta"
},
]
}
Sources = [
{
Name = "*"
Permissions = [{
Action = "allow"
HTTP = {
PathPrefix = "/workspace"
}
JWT = {
Providers = [
{
Name = "okta"
VerifyClaims = [
{
Path = ["aud"]
Value = "my_client_app"
},
{
Path = ["sub"]
Value = "5be86359073c434bad2da3932222dabe"
}
]
},
]
}
},
{
Action = "allow"
HTTP = {
PathPrefix = "/"
}
JWT = {
Providers = [
{
Name = "auth0"
},
]
}
}]
}
]
```
- generate 3 jwt tokens: 1 from auth0 jwks, 1 from okta jwks with
different claims than `/workspace` expects and 1 with correct claims
- connect to your envoy (change service and address as needed) to view
logs and potential errors. You can add: `-- --log-level debug` to see
what data is being forwarded
```
consul connect envoy -sidecar-for redis1 -grpc-addr 127.0.0.1:8502
```
- Make the following requests:
```
curl -s -H "Authorization: Bearer $Auth0_TOKEN" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_wrong_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_correct_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
Successful request
```
### TODO
* [x] Update test coverage
* [ ] update integration tests (follow-up PR)
* [x] appropriate backport labels added
2023-07-17 15:32:49 +00:00
|
|
|
Identifier: &envoy_rbac_v3.Principal_AndIds{
|
|
|
|
AndIds: &envoy_rbac_v3.Principal_Set{
|
2023-05-30 17:38:33 +00:00
|
|
|
Ids: []*envoy_rbac_v3.Principal{&firstPrincipal, &secondPrincipal},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tt := range tests {
|
|
|
|
tt := tt
|
|
|
|
t.Run(name, func(t *testing.T) {
|
Use JWT-auth filter in metadata mode & Delegate validation to RBAC filter (#18062)
### Description
<!-- Please describe why you're making this change, in plain English.
-->
- Currently the jwt-auth filter doesn't take into account the service
identity when validating jwt-auth, it only takes into account the path
and jwt provider during validation. This causes issues when multiple
source intentions restrict access to an endpoint with different JWT
providers.
- To fix these issues, rather than use the JWT auth filter for
validation, we use it in metadata mode and allow it to forward the
successful validated JWT token payload to the RBAC filter which will
make the decisions.
This PR ensures requests with and without JWT tokens successfully go
through the jwt-authn filter. The filter however only forwards the data
for successful/valid tokens. On the RBAC filter level, we check the
payload for claims and token issuer + existing rbac rules.
### Testing & Reproduction steps
<!--
* In the case of bugs, describe how to replicate
* If any manual tests were done, document the steps and the conditions
to replicate
* Call out any important/ relevant unit tests, e2e tests or integration
tests you have added or are adding
-->
- This test covers a multi level jwt requirements (requirements at top
level and permissions level). It also assumes you have envoy running,
you have a redis and a sidecar proxy service registered, and have a way
to generate jwks with jwt. I mostly use:
https://www.scottbrady91.com/tools/jwt for this.
- first write your proxy defaults
```
Kind = "proxy-defaults"
name = "global"
config {
protocol = "http"
}
```
- Create two providers
```
Kind = "jwt-provider"
Name = "auth0"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjog....."
}
}
```
```
Kind = "jwt-provider"
Name = "okta"
Issuer = "https://ronald.local"
JSONWebKeySet = {
Local = {
JWKS = "eyJrZXlzIjogW3...."
}
}
```
- add a service intention
```
Kind = "service-intentions"
Name = "redis"
JWT = {
Providers = [
{
Name = "okta"
},
]
}
Sources = [
{
Name = "*"
Permissions = [{
Action = "allow"
HTTP = {
PathPrefix = "/workspace"
}
JWT = {
Providers = [
{
Name = "okta"
VerifyClaims = [
{
Path = ["aud"]
Value = "my_client_app"
},
{
Path = ["sub"]
Value = "5be86359073c434bad2da3932222dabe"
}
]
},
]
}
},
{
Action = "allow"
HTTP = {
PathPrefix = "/"
}
JWT = {
Providers = [
{
Name = "auth0"
},
]
}
}]
}
]
```
- generate 3 jwt tokens: 1 from auth0 jwks, 1 from okta jwks with
different claims than `/workspace` expects and 1 with correct claims
- connect to your envoy (change service and address as needed) to view
logs and potential errors. You can add: `-- --log-level debug` to see
what data is being forwarded
```
consul connect envoy -sidecar-for redis1 -grpc-addr 127.0.0.1:8502
```
- Make the following requests:
```
curl -s -H "Authorization: Bearer $Auth0_TOKEN" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_wrong_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
RBAC filter denied
curl -s -H "Authorization: Bearer $Okta_TOKEN_with_correct_claims" --insecure --cert leaf.cert --key leaf.key --cacert connect-ca.pem https://localhost:20000/workspace -v
Successful request
```
### TODO
* [x] Update test coverage
* [ ] update integration tests (follow-up PR)
* [x] appropriate backport labels added
2023-07-17 15:32:49 +00:00
|
|
|
principal := jwtClaimsToPrincipals(tt.claims, tt.metadataPayloadKey)
|
2023-05-30 17:38:33 +00:00
|
|
|
require.Equal(t, principal, tt.expected)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|