2020-08-27 17:20:58 +00:00
|
|
|
|
package xds
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"sort"
|
2020-10-06 22:09:13 +00:00
|
|
|
|
"strings"
|
2020-08-27 17:20:58 +00:00
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
|
|
|
|
envoy_rbac_v3 "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
|
|
|
|
|
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
|
|
|
|
envoy_http_rbac_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rbac/v3"
|
|
|
|
|
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
|
|
|
|
envoy_network_rbac_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/rbac/v3"
|
|
|
|
|
envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
|
2021-02-22 21:00:15 +00:00
|
|
|
|
|
2021-09-10 00:24:43 +00:00
|
|
|
|
"github.com/hashicorp/consul/agent/connect"
|
2020-08-27 17:20:58 +00:00
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
2022-06-10 21:15:22 +00:00
|
|
|
|
"github.com/hashicorp/consul/proto/pbpeering"
|
2020-08-27 17:20:58 +00:00
|
|
|
|
)
|
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
|
func makeRBACNetworkFilter(
|
|
|
|
|
intentions structs.Intentions,
|
|
|
|
|
intentionDefaultAllow bool,
|
2022-06-29 15:29:54 +00:00
|
|
|
|
localInfo rbacLocalInfo,
|
2022-06-21 02:47:14 +00:00
|
|
|
|
peerTrustBundles []*pbpeering.PeeringTrustBundle,
|
2022-06-10 21:15:22 +00:00
|
|
|
|
) (*envoy_listener_v3.Filter, error) {
|
2022-07-14 18:45:51 +00:00
|
|
|
|
rules := makeRBACRules(intentions, intentionDefaultAllow, localInfo, false, peerTrustBundles)
|
2020-08-27 17:20:58 +00:00
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
cfg := &envoy_network_rbac_v3.RBAC{
|
2020-08-27 17:20:58 +00:00
|
|
|
|
StatPrefix: "connect_authz",
|
|
|
|
|
Rules: rules,
|
|
|
|
|
}
|
2021-02-22 21:00:15 +00:00
|
|
|
|
return makeFilter("envoy.filters.network.rbac", cfg)
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
|
func makeRBACHTTPFilter(
|
|
|
|
|
intentions structs.Intentions,
|
|
|
|
|
intentionDefaultAllow bool,
|
2022-06-29 15:29:54 +00:00
|
|
|
|
localInfo rbacLocalInfo,
|
2022-06-21 02:47:14 +00:00
|
|
|
|
peerTrustBundles []*pbpeering.PeeringTrustBundle,
|
2022-06-10 21:15:22 +00:00
|
|
|
|
) (*envoy_http_v3.HttpFilter, error) {
|
2022-07-14 18:45:51 +00:00
|
|
|
|
rules := makeRBACRules(intentions, intentionDefaultAllow, localInfo, true, peerTrustBundles)
|
2020-08-27 17:20:58 +00:00
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
cfg := &envoy_http_rbac_v3.RBAC{
|
2020-08-27 17:20:58 +00:00
|
|
|
|
Rules: rules,
|
|
|
|
|
}
|
|
|
|
|
return makeEnvoyHTTPFilter("envoy.filters.http.rbac", cfg)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
|
func intentionListToIntermediateRBACForm(
|
|
|
|
|
intentions structs.Intentions,
|
2022-06-29 15:29:54 +00:00
|
|
|
|
localInfo rbacLocalInfo,
|
2022-06-10 21:15:22 +00:00
|
|
|
|
isHTTP bool,
|
|
|
|
|
trustBundlesByPeer map[string]*pbpeering.PeeringTrustBundle,
|
|
|
|
|
) []*rbacIntention {
|
2020-10-06 22:09:13 +00:00
|
|
|
|
sort.Sort(structs.IntentionPrecedenceSorter(intentions))
|
|
|
|
|
|
|
|
|
|
// Omit any lower-precedence intentions that share the same source.
|
|
|
|
|
intentions = removeSameSourceIntentions(intentions)
|
|
|
|
|
|
|
|
|
|
rbacIxns := make([]*rbacIntention, 0, len(intentions))
|
|
|
|
|
for _, ixn := range intentions {
|
2022-06-10 21:15:22 +00:00
|
|
|
|
// trustBundle is only applicable to imported services
|
|
|
|
|
trustBundle, ok := trustBundlesByPeer[ixn.SourcePeer]
|
|
|
|
|
if ixn.SourcePeer != "" && !ok {
|
|
|
|
|
// If the intention defines a source peer, we expect to
|
|
|
|
|
// see a trust bundle. Otherwise the config snapshot may
|
|
|
|
|
// not have yet received the bundles and we fail silently
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
rixn := intentionToIntermediateRBACForm(ixn, localInfo, isHTTP, trustBundle)
|
2020-10-06 22:09:13 +00:00
|
|
|
|
rbacIxns = append(rbacIxns, rixn)
|
|
|
|
|
}
|
|
|
|
|
return rbacIxns
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
func removeSourcePrecedence(rbacIxns []*rbacIntention, intentionDefaultAction intentionAction, localInfo rbacLocalInfo) []*rbacIntention {
|
2020-10-06 22:09:13 +00:00
|
|
|
|
if len(rbacIxns) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove source precedence:
|
|
|
|
|
//
|
|
|
|
|
// First walk backwards and add each intention to all subsequent statements
|
|
|
|
|
// (via AND NOT $x).
|
|
|
|
|
//
|
|
|
|
|
// If it is L4 and has the same action as the default intention action then
|
|
|
|
|
// mark the rule itself for erasure.
|
|
|
|
|
numRetained := 0
|
|
|
|
|
for i := len(rbacIxns) - 1; i >= 0; i-- {
|
|
|
|
|
for j := i + 1; j < len(rbacIxns); j++ {
|
|
|
|
|
if rbacIxns[j].Skip {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// [i] is the intention candidate that we are distributing
|
|
|
|
|
// [j] is the thing to maybe NOT [i] from
|
|
|
|
|
if ixnSourceMatches(rbacIxns[i].Source, rbacIxns[j].Source) {
|
|
|
|
|
rbacIxns[j].NotSources = append(rbacIxns[j].NotSources, rbacIxns[i].Source)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if rbacIxns[i].Action == intentionDefaultAction {
|
|
|
|
|
// Lower precedence intentions that match the default intention
|
|
|
|
|
// action are skipped, since they're handled by the default
|
|
|
|
|
// catch-all.
|
|
|
|
|
rbacIxns[i].Skip = true // mark for deletion
|
|
|
|
|
} else {
|
|
|
|
|
numRetained++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// At this point precedence doesn't matter for the source element.
|
|
|
|
|
|
|
|
|
|
// Remove skipped intentions and also compute the final Principals for each
|
|
|
|
|
// intention.
|
|
|
|
|
out := make([]*rbacIntention, 0, numRetained)
|
|
|
|
|
for _, rixn := range rbacIxns {
|
|
|
|
|
if rixn.Skip {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
rixn.ComputedPrincipal = rixn.FlattenPrincipal(localInfo)
|
2020-10-06 22:09:13 +00:00
|
|
|
|
out = append(out, rixn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
func removeIntentionPrecedence(rbacIxns []*rbacIntention, intentionDefaultAction intentionAction, localInfo rbacLocalInfo) []*rbacIntention {
|
2020-10-06 22:09:13 +00:00
|
|
|
|
// Remove source precedence. After this completes precedence doesn't matter
|
|
|
|
|
// between any two intentions.
|
2022-06-29 15:29:54 +00:00
|
|
|
|
rbacIxns = removeSourcePrecedence(rbacIxns, intentionDefaultAction, localInfo)
|
2020-10-06 22:09:13 +00:00
|
|
|
|
|
2021-07-15 15:09:00 +00:00
|
|
|
|
numRetained := 0
|
2020-10-06 22:09:13 +00:00
|
|
|
|
for _, rbacIxn := range rbacIxns {
|
|
|
|
|
// Remove permission precedence. After this completes precedence
|
|
|
|
|
// doesn't matter between any two permissions on this intention.
|
|
|
|
|
rbacIxn.Permissions = removePermissionPrecedence(rbacIxn.Permissions, intentionDefaultAction)
|
2021-07-15 15:09:00 +00:00
|
|
|
|
if rbacIxn.Action == intentionActionLayer7 && len(rbacIxn.Permissions) == 0 {
|
|
|
|
|
// All of the permissions must have had the default action type and
|
|
|
|
|
// were removed. Mark this for removal below.
|
|
|
|
|
rbacIxn.Skip = true
|
|
|
|
|
} else {
|
|
|
|
|
numRetained++
|
|
|
|
|
}
|
2020-10-06 22:09:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-15 15:09:00 +00:00
|
|
|
|
if numRetained == len(rbacIxns) {
|
|
|
|
|
return rbacIxns
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We previously used the absence of permissions (above) as a signal to
|
|
|
|
|
// mark the entire intention for removal. Now do the deletions.
|
|
|
|
|
out := make([]*rbacIntention, 0, numRetained)
|
|
|
|
|
for _, rixn := range rbacIxns {
|
|
|
|
|
if !rixn.Skip {
|
|
|
|
|
out = append(out, rixn)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out
|
2020-10-06 22:09:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func removePermissionPrecedence(perms []*rbacPermission, intentionDefaultAction intentionAction) []*rbacPermission {
|
|
|
|
|
if len(perms) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// First walk backwards and add each permission to all subsequent
|
|
|
|
|
// statements (via AND NOT $x).
|
|
|
|
|
//
|
|
|
|
|
// If it has the same action as the default intention action then mark the
|
|
|
|
|
// permission itself for erasure.
|
|
|
|
|
numRetained := 0
|
|
|
|
|
for i := len(perms) - 1; i >= 0; i-- {
|
|
|
|
|
for j := i + 1; j < len(perms); j++ {
|
|
|
|
|
if perms[j].Skip {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// [i] is the permission candidate that we are distributing
|
|
|
|
|
// [j] is the thing to maybe NOT [i] from
|
|
|
|
|
perms[j].NotPerms = append(
|
|
|
|
|
perms[j].NotPerms,
|
|
|
|
|
perms[i].Perm,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if perms[i].Action == intentionDefaultAction {
|
|
|
|
|
// Lower precedence permissions that match the default intention
|
|
|
|
|
// action are skipped, since they're handled by the default
|
|
|
|
|
// catch-all.
|
|
|
|
|
perms[i].Skip = true // mark for deletion
|
|
|
|
|
} else {
|
|
|
|
|
numRetained++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove skipped permissions and also compute the final Permissions for each item.
|
|
|
|
|
out := make([]*rbacPermission, 0, numRetained)
|
|
|
|
|
for _, perm := range perms {
|
|
|
|
|
if perm.Skip {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
perm.ComputedPermission = perm.Flatten()
|
|
|
|
|
out = append(out, perm)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
func intentionToIntermediateRBACForm(
|
|
|
|
|
ixn *structs.Intention,
|
|
|
|
|
localInfo rbacLocalInfo,
|
|
|
|
|
isHTTP bool,
|
|
|
|
|
bundle *pbpeering.PeeringTrustBundle,
|
|
|
|
|
) *rbacIntention {
|
2020-10-06 22:09:13 +00:00
|
|
|
|
rixn := &rbacIntention{
|
2022-06-10 21:15:22 +00:00
|
|
|
|
Source: rbacService{
|
|
|
|
|
ServiceName: ixn.SourceServiceName(),
|
|
|
|
|
Peer: ixn.SourcePeer,
|
2022-06-29 15:29:54 +00:00
|
|
|
|
TrustDomain: localInfo.trustDomain,
|
2022-06-10 21:15:22 +00:00
|
|
|
|
},
|
2020-10-06 22:09:13 +00:00
|
|
|
|
Precedence: ixn.Precedence,
|
|
|
|
|
}
|
2022-06-10 21:15:22 +00:00
|
|
|
|
|
|
|
|
|
// imported services will have addition metadata used to override SpiffeID creation
|
|
|
|
|
if bundle != nil {
|
|
|
|
|
rixn.Source.ExportedPartition = bundle.ExportedPartition
|
|
|
|
|
rixn.Source.TrustDomain = bundle.TrustDomain
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-06 22:09:13 +00:00
|
|
|
|
if len(ixn.Permissions) > 0 {
|
|
|
|
|
if isHTTP {
|
|
|
|
|
rixn.Action = intentionActionLayer7
|
|
|
|
|
rixn.Permissions = make([]*rbacPermission, 0, len(ixn.Permissions))
|
|
|
|
|
for _, perm := range ixn.Permissions {
|
|
|
|
|
rixn.Permissions = append(rixn.Permissions, &rbacPermission{
|
|
|
|
|
Definition: perm,
|
|
|
|
|
Action: intentionActionFromString(perm.Action),
|
|
|
|
|
Perm: convertPermission(perm),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// In case L7 intentions slip through to here, treat them as deny intentions.
|
|
|
|
|
rixn.Action = intentionActionDeny
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
rixn.Action = intentionActionFromString(ixn.Action)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rixn
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type intentionAction int
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
intentionActionDeny intentionAction = iota
|
|
|
|
|
intentionActionAllow
|
|
|
|
|
intentionActionLayer7
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func intentionActionFromBool(v bool) intentionAction {
|
|
|
|
|
if v {
|
|
|
|
|
return intentionActionAllow
|
|
|
|
|
} else {
|
|
|
|
|
return intentionActionDeny
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
func intentionActionFromString(s structs.IntentionAction) intentionAction {
|
|
|
|
|
if s == structs.IntentionActionAllow {
|
|
|
|
|
return intentionActionAllow
|
|
|
|
|
}
|
|
|
|
|
return intentionActionDeny
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
|
type rbacService struct {
|
|
|
|
|
structs.ServiceName
|
|
|
|
|
|
|
|
|
|
// Peer, ExportedPartition, and TrustDomain are
|
|
|
|
|
// only applicable to imported services and are
|
|
|
|
|
// used to override SPIFFEID fields.
|
|
|
|
|
Peer string
|
|
|
|
|
ExportedPartition string
|
|
|
|
|
TrustDomain string
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-27 17:20:58 +00:00
|
|
|
|
type rbacIntention struct {
|
2022-06-10 21:15:22 +00:00
|
|
|
|
Source rbacService
|
|
|
|
|
NotSources []rbacService
|
2020-10-06 22:09:13 +00:00
|
|
|
|
Action intentionAction
|
|
|
|
|
Permissions []*rbacPermission
|
|
|
|
|
Precedence int
|
|
|
|
|
|
|
|
|
|
// Skip is field used to indicate that this intention can be deleted in the
|
|
|
|
|
// final pass. Items marked as true should generally not escape the method
|
|
|
|
|
// that marked them.
|
|
|
|
|
Skip bool
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
ComputedPrincipal *envoy_rbac_v3.Principal
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
func (r *rbacIntention) FlattenPrincipal(localInfo rbacLocalInfo) *envoy_rbac_v3.Principal {
|
|
|
|
|
if !localInfo.expectXFCC {
|
|
|
|
|
return r.flattenPrincipalFromCert()
|
|
|
|
|
|
|
|
|
|
} else if r.Source.Peer == "" {
|
|
|
|
|
// NOTE: ixnSourceMatches should enforce that all of Source and NotSources
|
|
|
|
|
// are peered or not-peered, so we only need to look at the Source element.
|
|
|
|
|
return r.flattenPrincipalFromCert() // intention is not relevant to peering
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this intention is an L7 peered one, then it is exclusively resolvable
|
|
|
|
|
// using XFCC, rather than the TLS SAN field.
|
|
|
|
|
fromXFCC := r.flattenPrincipalFromXFCC()
|
|
|
|
|
|
|
|
|
|
// Use of the XFCC one is gated on coming directly from our own gateways.
|
|
|
|
|
gwIDPattern := makeSpiffeMeshGatewayPattern(localInfo.trustDomain, localInfo.partition)
|
|
|
|
|
|
|
|
|
|
return andPrincipals([]*envoy_rbac_v3.Principal{
|
|
|
|
|
authenticatedPatternPrincipal(gwIDPattern),
|
|
|
|
|
fromXFCC,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *rbacIntention) flattenPrincipalFromCert() *envoy_rbac_v3.Principal {
|
2020-08-27 17:20:58 +00:00
|
|
|
|
r.NotSources = simplifyNotSourceSlice(r.NotSources)
|
2020-10-06 22:09:13 +00:00
|
|
|
|
|
|
|
|
|
if len(r.NotSources) == 0 {
|
|
|
|
|
return idPrincipal(r.Source)
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
andIDs := make([]*envoy_rbac_v3.Principal, 0, len(r.NotSources)+1)
|
2020-10-06 22:09:13 +00:00
|
|
|
|
andIDs = append(andIDs, idPrincipal(r.Source))
|
|
|
|
|
for _, src := range r.NotSources {
|
|
|
|
|
andIDs = append(andIDs, notPrincipal(
|
|
|
|
|
idPrincipal(src),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
return andPrincipals(andIDs)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
func (r *rbacIntention) flattenPrincipalFromXFCC() *envoy_rbac_v3.Principal {
|
|
|
|
|
r.NotSources = simplifyNotSourceSlice(r.NotSources)
|
|
|
|
|
|
|
|
|
|
if len(r.NotSources) == 0 {
|
|
|
|
|
return xfccPrincipal(r.Source)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
andIDs := make([]*envoy_rbac_v3.Principal, 0, len(r.NotSources)+1)
|
|
|
|
|
andIDs = append(andIDs, xfccPrincipal(r.Source))
|
|
|
|
|
for _, src := range r.NotSources {
|
|
|
|
|
andIDs = append(andIDs, notPrincipal(
|
|
|
|
|
xfccPrincipal(src),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
return andPrincipals(andIDs)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-06 22:09:13 +00:00
|
|
|
|
type rbacPermission struct {
|
|
|
|
|
Definition *structs.IntentionPermission
|
|
|
|
|
|
|
|
|
|
Action intentionAction
|
2021-02-26 22:23:15 +00:00
|
|
|
|
Perm *envoy_rbac_v3.Permission
|
|
|
|
|
NotPerms []*envoy_rbac_v3.Permission
|
2020-10-06 22:09:13 +00:00
|
|
|
|
|
|
|
|
|
// Skip is field used to indicate that this permission can be deleted in
|
|
|
|
|
// the final pass. Items marked as true should generally not escape the
|
|
|
|
|
// method that marked them.
|
|
|
|
|
Skip bool
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
ComputedPermission *envoy_rbac_v3.Permission
|
2020-10-06 22:09:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
func (p *rbacPermission) Flatten() *envoy_rbac_v3.Permission {
|
2020-10-06 22:09:13 +00:00
|
|
|
|
if len(p.NotPerms) == 0 {
|
|
|
|
|
return p.Perm
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
parts := make([]*envoy_rbac_v3.Permission, 0, len(p.NotPerms)+1)
|
2020-10-06 22:09:13 +00:00
|
|
|
|
parts = append(parts, p.Perm)
|
|
|
|
|
for _, notPerm := range p.NotPerms {
|
|
|
|
|
parts = append(parts, notPermission(notPerm))
|
|
|
|
|
}
|
|
|
|
|
return andPermissions(parts)
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-02 18:33:56 +00:00
|
|
|
|
// simplifyNotSourceSlice will collapse NotSources elements together if any element is
|
|
|
|
|
// a subset of another.
|
|
|
|
|
// For example "default/web" is a subset of "default/*" because it is covered by the wildcard.
|
2022-06-10 21:15:22 +00:00
|
|
|
|
func simplifyNotSourceSlice(notSources []rbacService) []rbacService {
|
2020-08-27 17:20:58 +00:00
|
|
|
|
if len(notSources) <= 1 {
|
|
|
|
|
return notSources
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort, keeping the least wildcarded elements first.
|
2021-09-02 18:33:56 +00:00
|
|
|
|
// More specific elements have a higher precedence over more wildcarded elements.
|
2020-08-27 17:20:58 +00:00
|
|
|
|
sort.SliceStable(notSources, func(i, j int) bool {
|
|
|
|
|
return countWild(notSources[i]) < countWild(notSources[j])
|
|
|
|
|
})
|
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
|
keep := make([]rbacService, 0, len(notSources))
|
2020-08-27 17:20:58 +00:00
|
|
|
|
for i := 0; i < len(notSources); i++ {
|
|
|
|
|
si := notSources[i]
|
|
|
|
|
remove := false
|
|
|
|
|
for j := i + 1; j < len(notSources); j++ {
|
|
|
|
|
sj := notSources[j]
|
|
|
|
|
|
|
|
|
|
if ixnSourceMatches(si, sj) {
|
|
|
|
|
remove = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !remove {
|
|
|
|
|
keep = append(keep, si)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return keep
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
type rbacLocalInfo struct {
|
|
|
|
|
trustDomain string
|
|
|
|
|
datacenter string
|
|
|
|
|
partition string
|
|
|
|
|
expectXFCC bool
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-27 17:20:58 +00:00
|
|
|
|
// makeRBACRules translates Consul intentions into RBAC Policies for Envoy.
|
|
|
|
|
//
|
|
|
|
|
// Consul lets you define up to 9 different kinds of intentions that apply at
|
|
|
|
|
// different levels of precedence (this is limited to 4 if not using Consul
|
|
|
|
|
// Enterprise). Each intention in this flat list (sorted by precedence) can either
|
|
|
|
|
// be an allow rule or a deny rule. Here’s a concrete example of this at work:
|
|
|
|
|
//
|
2022-10-21 19:58:06 +00:00
|
|
|
|
// intern/trusted-app => billing/payment-svc : ALLOW (prec=9)
|
|
|
|
|
// intern/* => billing/payment-svc : DENY (prec=8)
|
|
|
|
|
// */* => billing/payment-svc : ALLOW (prec=7)
|
|
|
|
|
// ::: ACL default policy ::: : DENY (prec=N/A)
|
2020-08-27 17:20:58 +00:00
|
|
|
|
//
|
|
|
|
|
// In contrast, Envoy lets you either configure a filter to be based on an
|
|
|
|
|
// allow-list or a deny-list based on the action attribute of the RBAC rules
|
|
|
|
|
// struct.
|
|
|
|
|
//
|
|
|
|
|
// On the surface it would seem that the configuration model of Consul
|
|
|
|
|
// intentions is incompatible with that of Envoy’s RBAC engine. For any given
|
|
|
|
|
// destination service Consul’s model requires evaluating a list of rules and
|
|
|
|
|
// short circuiting later rules once an earlier rule matches. After a rule is
|
|
|
|
|
// found to match then we decide if it is allow/deny. Envoy on the other hand
|
|
|
|
|
// requires the rules to express all conditions to allow access or all conditions
|
|
|
|
|
// to deny access.
|
|
|
|
|
//
|
|
|
|
|
// Despite the surface incompatibility it is possible to marry these two
|
|
|
|
|
// models. For clarity I’ll rewrite the earlier example intentions in an
|
|
|
|
|
// abbreviated form:
|
|
|
|
|
//
|
2022-10-21 19:58:06 +00:00
|
|
|
|
// A : ALLOW
|
|
|
|
|
// B : DENY
|
|
|
|
|
// C : ALLOW
|
|
|
|
|
// <default> : DENY
|
2020-08-27 17:20:58 +00:00
|
|
|
|
//
|
2022-10-21 19:58:06 +00:00
|
|
|
|
// 1. Given that the overall intention default is set to deny, we start by
|
|
|
|
|
// choosing to build an allow-list in Envoy (this is also the variant that I find
|
|
|
|
|
// easier to think about).
|
|
|
|
|
// 2. Next we traverse the list in precedence order (top down) and any DENY
|
|
|
|
|
// intentions are combined with later intentions using logical operations.
|
|
|
|
|
// 3. Now that all of the intentions result in the same action (allow) we have
|
|
|
|
|
// successfully removed precedence and we can express this in as a set of Envoy
|
|
|
|
|
// RBAC policies.
|
2020-08-27 17:20:58 +00:00
|
|
|
|
//
|
|
|
|
|
// After this the earlier A/B/C/default list becomes:
|
|
|
|
|
//
|
2022-10-21 19:58:06 +00:00
|
|
|
|
// A : ALLOW
|
|
|
|
|
// C AND NOT(B) : ALLOW
|
|
|
|
|
// <default> : DENY
|
2020-08-27 17:20:58 +00:00
|
|
|
|
//
|
|
|
|
|
// Which really is just an allow-list of [A, C AND NOT(B)]
|
2022-06-10 21:15:22 +00:00
|
|
|
|
func makeRBACRules(
|
|
|
|
|
intentions structs.Intentions,
|
|
|
|
|
intentionDefaultAllow bool,
|
2022-06-29 15:29:54 +00:00
|
|
|
|
localInfo rbacLocalInfo,
|
2022-06-10 21:15:22 +00:00
|
|
|
|
isHTTP bool,
|
2022-06-21 02:47:14 +00:00
|
|
|
|
peerTrustBundles []*pbpeering.PeeringTrustBundle,
|
2022-07-14 18:45:51 +00:00
|
|
|
|
) *envoy_rbac_v3.RBAC {
|
2020-08-27 17:20:58 +00:00
|
|
|
|
// TODO(banks,rb): Implement revocation list checking?
|
|
|
|
|
|
2022-06-21 02:47:14 +00:00
|
|
|
|
// TODO(peering): mkeeler asked that these maps come from proxycfg instead of
|
|
|
|
|
// being constructed in xds to save memory allocation and gc pressure. Low priority.
|
|
|
|
|
trustBundlesByPeer := make(map[string]*pbpeering.PeeringTrustBundle, len(peerTrustBundles))
|
|
|
|
|
for _, ptb := range peerTrustBundles {
|
|
|
|
|
trustBundlesByPeer[ptb.PeerName] = ptb
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
if isHTTP && len(peerTrustBundles) > 0 {
|
|
|
|
|
for _, ixn := range intentions {
|
|
|
|
|
if ixn.SourcePeer != "" {
|
|
|
|
|
localInfo.expectXFCC = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-27 17:20:58 +00:00
|
|
|
|
// First build up just the basic principal matches.
|
2022-06-29 15:29:54 +00:00
|
|
|
|
rbacIxns := intentionListToIntermediateRBACForm(intentions, localInfo, isHTTP, trustBundlesByPeer)
|
2020-08-27 17:20:58 +00:00
|
|
|
|
|
|
|
|
|
// Normalize: if we are in default-deny then all intentions must be allows and vice versa
|
2020-10-06 22:09:13 +00:00
|
|
|
|
intentionDefaultAction := intentionActionFromBool(intentionDefaultAllow)
|
2020-08-27 17:20:58 +00:00
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
var rbacAction envoy_rbac_v3.RBAC_Action
|
2020-08-27 17:20:58 +00:00
|
|
|
|
if intentionDefaultAllow {
|
|
|
|
|
// The RBAC policies deny access to principals. The rest is allowed.
|
|
|
|
|
// This is block-list style access control.
|
2021-02-26 22:23:15 +00:00
|
|
|
|
rbacAction = envoy_rbac_v3.RBAC_DENY
|
2020-08-27 17:20:58 +00:00
|
|
|
|
} else {
|
|
|
|
|
// The RBAC policies grant access to principals. The rest is denied.
|
|
|
|
|
// This is safe-list style access control. This is the default type.
|
2021-02-26 22:23:15 +00:00
|
|
|
|
rbacAction = envoy_rbac_v3.RBAC_ALLOW
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-06 22:09:13 +00:00
|
|
|
|
// Remove source and permissions precedence.
|
2022-06-29 15:29:54 +00:00
|
|
|
|
rbacIxns = removeIntentionPrecedence(rbacIxns, intentionDefaultAction, localInfo)
|
2020-08-27 17:20:58 +00:00
|
|
|
|
|
2020-10-06 22:09:13 +00:00
|
|
|
|
// For L4: we should generate one big Policy listing all Principals
|
|
|
|
|
// For L7: we should generate one Policy per Principal and list all of the Permissions
|
2021-02-26 22:23:15 +00:00
|
|
|
|
rbac := &envoy_rbac_v3.RBAC{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
Action: rbacAction,
|
2021-02-26 22:23:15 +00:00
|
|
|
|
Policies: make(map[string]*envoy_rbac_v3.Policy),
|
2020-10-06 22:09:13 +00:00
|
|
|
|
}
|
2020-08-27 17:20:58 +00:00
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
var principalsL4 []*envoy_rbac_v3.Principal
|
2020-10-06 22:09:13 +00:00
|
|
|
|
for i, rbacIxn := range rbacIxns {
|
2021-07-15 15:09:00 +00:00
|
|
|
|
if rbacIxn.Action == intentionActionLayer7 {
|
|
|
|
|
if len(rbacIxn.Permissions) == 0 {
|
|
|
|
|
panic("invalid state: L7 intention has no permissions")
|
|
|
|
|
}
|
2020-10-06 22:09:13 +00:00
|
|
|
|
if !isHTTP {
|
|
|
|
|
panic("invalid state: L7 permissions present for TCP service")
|
|
|
|
|
}
|
2021-07-15 15:09:00 +00:00
|
|
|
|
|
2020-10-06 22:09:13 +00:00
|
|
|
|
// For L7: we should generate one Policy per Principal and list all of the Permissions
|
2021-02-26 22:23:15 +00:00
|
|
|
|
policy := &envoy_rbac_v3.Policy{
|
2022-06-29 15:29:54 +00:00
|
|
|
|
Principals: optimizePrincipals([]*envoy_rbac_v3.Principal{rbacIxn.ComputedPrincipal}),
|
2021-02-26 22:23:15 +00:00
|
|
|
|
Permissions: make([]*envoy_rbac_v3.Permission, 0, len(rbacIxn.Permissions)),
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
2020-10-06 22:09:13 +00:00
|
|
|
|
for _, perm := range rbacIxn.Permissions {
|
|
|
|
|
policy.Permissions = append(policy.Permissions, perm.ComputedPermission)
|
|
|
|
|
}
|
|
|
|
|
rbac.Policies[fmt.Sprintf("consul-intentions-layer7-%d", i)] = policy
|
2020-08-27 17:20:58 +00:00
|
|
|
|
} else {
|
2020-10-06 22:09:13 +00:00
|
|
|
|
// For L4: we should generate one big Policy listing all Principals
|
|
|
|
|
principalsL4 = append(principalsL4, rbacIxn.ComputedPrincipal)
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-06 22:09:13 +00:00
|
|
|
|
if len(principalsL4) > 0 {
|
2021-02-26 22:23:15 +00:00
|
|
|
|
rbac.Policies["consul-intentions-layer4"] = &envoy_rbac_v3.Policy{
|
2022-06-29 15:29:54 +00:00
|
|
|
|
Principals: optimizePrincipals(principalsL4),
|
2021-02-26 22:23:15 +00:00
|
|
|
|
Permissions: []*envoy_rbac_v3.Permission{anyPermission()},
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-06 22:09:13 +00:00
|
|
|
|
if len(rbac.Policies) == 0 {
|
|
|
|
|
rbac.Policies = nil
|
|
|
|
|
}
|
2022-07-14 18:45:51 +00:00
|
|
|
|
return rbac
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
func optimizePrincipals(orig []*envoy_rbac_v3.Principal) []*envoy_rbac_v3.Principal {
|
|
|
|
|
// If they are all ORs, then OR them together.
|
|
|
|
|
var orIds []*envoy_rbac_v3.Principal
|
|
|
|
|
for _, p := range orig {
|
|
|
|
|
or, ok := p.Identifier.(*envoy_rbac_v3.Principal_OrIds)
|
|
|
|
|
if !ok {
|
|
|
|
|
return orig
|
|
|
|
|
}
|
|
|
|
|
orIds = append(orIds, or.OrIds.Ids...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return []*envoy_rbac_v3.Principal{orPrincipals(orIds)}
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-02 18:12:51 +00:00
|
|
|
|
// removeSameSourceIntentions will iterate over intentions and remove any lower precedence
|
|
|
|
|
// intentions that share the same source. Intentions are sorted by descending precedence
|
|
|
|
|
// so once a source has been seen, additional intentions with the same source can be dropped.
|
|
|
|
|
//
|
|
|
|
|
// Example for the default/web service:
|
|
|
|
|
// input: [(backend/* -> default/web), (backend/* -> default/*)]
|
|
|
|
|
// output: [(backend/* -> default/web)]
|
|
|
|
|
//
|
|
|
|
|
// (backend/* -> default/*) was dropped because it is already known that any service
|
|
|
|
|
// in the backend namespace can target default/web.
|
2020-08-27 17:20:58 +00:00
|
|
|
|
func removeSameSourceIntentions(intentions structs.Intentions) structs.Intentions {
|
|
|
|
|
if len(intentions) < 2 {
|
|
|
|
|
return intentions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
out = make(structs.Intentions, 0, len(intentions))
|
|
|
|
|
changed = false
|
2022-06-10 21:15:22 +00:00
|
|
|
|
seenSource = make(map[structs.PeeredServiceName]struct{})
|
2020-08-27 17:20:58 +00:00
|
|
|
|
)
|
|
|
|
|
for _, ixn := range intentions {
|
2022-06-10 21:15:22 +00:00
|
|
|
|
psn := structs.PeeredServiceName{
|
|
|
|
|
ServiceName: ixn.SourceServiceName(),
|
|
|
|
|
Peer: ixn.SourcePeer,
|
|
|
|
|
}
|
|
|
|
|
if _, ok := seenSource[psn]; ok {
|
2020-08-27 17:20:58 +00:00
|
|
|
|
// A higher precedence intention already used this exact source
|
|
|
|
|
// definition with a different destination.
|
|
|
|
|
changed = true
|
|
|
|
|
continue
|
|
|
|
|
}
|
2022-06-10 21:15:22 +00:00
|
|
|
|
seenSource[psn] = struct{}{}
|
2020-08-27 17:20:58 +00:00
|
|
|
|
out = append(out, ixn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !changed {
|
|
|
|
|
return intentions
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
|
// ixnSourceMatches determines if the 'tester' service name is matched by the
|
2020-08-27 17:20:58 +00:00
|
|
|
|
// 'against' service name via wildcard rules.
|
|
|
|
|
//
|
|
|
|
|
// For instance:
|
2021-09-02 16:43:56 +00:00
|
|
|
|
// - (web, api) => false, because these have no wildcards
|
|
|
|
|
// - (web, *) => true, because "all services" includes "web"
|
|
|
|
|
// - (default/web, default/*) => true, because "all services in the default NS" includes "default/web"
|
|
|
|
|
// - (default/*, */*) => true, "any service in any NS" includes "all services in the default NS"
|
|
|
|
|
// - (default/default/*, other/*/*) => false, "any service in "other" partition" does NOT include services in the default partition"
|
2022-06-10 21:15:22 +00:00
|
|
|
|
//
|
|
|
|
|
// Peer and partition must be exact names and cannot be compared with wildcards.
|
|
|
|
|
func ixnSourceMatches(tester, against rbacService) bool {
|
2020-08-27 17:20:58 +00:00
|
|
|
|
// We assume that we can't have the same intention twice before arriving
|
|
|
|
|
// here.
|
|
|
|
|
numWildTester := countWild(tester)
|
|
|
|
|
numWildAgainst := countWild(against)
|
|
|
|
|
|
|
|
|
|
if numWildTester == numWildAgainst {
|
|
|
|
|
return false
|
|
|
|
|
} else if numWildTester > numWildAgainst {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
|
matchesAP := tester.PartitionOrDefault() == against.PartitionOrDefault()
|
|
|
|
|
matchesPeer := tester.Peer == against.Peer
|
2020-08-27 17:20:58 +00:00
|
|
|
|
matchesNS := tester.NamespaceOrDefault() == against.NamespaceOrDefault() || against.NamespaceOrDefault() == structs.WildcardSpecifier
|
|
|
|
|
matchesName := tester.Name == against.Name || against.Name == structs.WildcardSpecifier
|
2022-06-10 21:15:22 +00:00
|
|
|
|
return matchesAP && matchesPeer && matchesNS && matchesName
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// countWild counts the number of wildcard values in the given namespace and name.
|
2022-06-10 21:15:22 +00:00
|
|
|
|
func countWild(src rbacService) int {
|
2021-09-02 16:43:56 +00:00
|
|
|
|
// If Partition is wildcard, panic because it's not supported
|
|
|
|
|
if src.PartitionOrDefault() == structs.WildcardSpecifier {
|
|
|
|
|
panic("invalid state: intention references wildcard partition")
|
|
|
|
|
}
|
2022-06-10 21:15:22 +00:00
|
|
|
|
if src.Peer == structs.WildcardSpecifier {
|
|
|
|
|
panic("invalid state: intention references wildcard peer")
|
|
|
|
|
}
|
2021-09-02 16:43:56 +00:00
|
|
|
|
|
2020-08-27 17:20:58 +00:00
|
|
|
|
// If NS is wildcard, it must be 2 since wildcards only follow exact
|
|
|
|
|
if src.NamespaceOrDefault() == structs.WildcardSpecifier {
|
|
|
|
|
return 2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Same reasoning as above, a wildcard can only follow an exact value
|
|
|
|
|
// and an exact value cannot follow a wildcard, so if name is a wildcard
|
|
|
|
|
// we must have exactly one.
|
|
|
|
|
if src.Name == structs.WildcardSpecifier {
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
func andPrincipals(ids []*envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
|
|
|
|
|
return &envoy_rbac_v3.Principal{
|
|
|
|
|
Identifier: &envoy_rbac_v3.Principal_AndIds{
|
|
|
|
|
AndIds: &envoy_rbac_v3.Principal_Set{
|
2020-08-27 17:20:58 +00:00
|
|
|
|
Ids: ids,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
func orPrincipals(ids []*envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
|
|
|
|
|
return &envoy_rbac_v3.Principal{
|
|
|
|
|
Identifier: &envoy_rbac_v3.Principal_OrIds{
|
|
|
|
|
OrIds: &envoy_rbac_v3.Principal_Set{
|
|
|
|
|
Ids: ids,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
func notPrincipal(id *envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
|
|
|
|
|
return &envoy_rbac_v3.Principal{
|
|
|
|
|
Identifier: &envoy_rbac_v3.Principal_NotId{
|
2020-08-27 17:20:58 +00:00
|
|
|
|
NotId: id,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
|
func idPrincipal(src rbacService) *envoy_rbac_v3.Principal {
|
|
|
|
|
pattern := makeSpiffePattern(src)
|
2022-06-29 15:29:54 +00:00
|
|
|
|
return authenticatedPatternPrincipal(pattern)
|
|
|
|
|
}
|
2020-08-27 17:20:58 +00:00
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
func authenticatedPatternPrincipal(pattern string) *envoy_rbac_v3.Principal {
|
2021-02-26 22:23:15 +00:00
|
|
|
|
return &envoy_rbac_v3.Principal{
|
|
|
|
|
Identifier: &envoy_rbac_v3.Principal_Authenticated_{
|
|
|
|
|
Authenticated: &envoy_rbac_v3.Principal_Authenticated{
|
|
|
|
|
PrincipalName: &envoy_matcher_v3.StringMatcher{
|
|
|
|
|
MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{
|
2020-08-27 17:20:58 +00:00
|
|
|
|
SafeRegex: makeEnvoyRegexMatch(pattern),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-10 00:24:43 +00:00
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
func xfccPrincipal(src rbacService) *envoy_rbac_v3.Principal {
|
|
|
|
|
// Same match we normally would use.
|
|
|
|
|
idPattern := makeSpiffePattern(src)
|
|
|
|
|
|
|
|
|
|
// Remove the leading ^ and trailing $.
|
|
|
|
|
idPattern = idPattern[1 : len(idPattern)-1]
|
|
|
|
|
|
|
|
|
|
// Anchor to the first XFCC component
|
|
|
|
|
pattern := `^[^,]+;URI=` + idPattern + `(?:,.*)?$`
|
|
|
|
|
|
|
|
|
|
// By=spiffe://8c7db6d3-e4ee-aa8c-488c-dbedd3772b78.consul/gateway/mesh/dc/dc2;
|
|
|
|
|
// Hash=2a2db78ac351a05854a0abd350631bf98cc0eb827d21f4ed5935ccd287779eb6;
|
|
|
|
|
// Cert="-----BEGIN%20CERTIFICATE-----<SNIP>";
|
|
|
|
|
// Chain="-----BEGIN%20CERTIFICATE-----<SNIP>";
|
|
|
|
|
// Subject="";
|
|
|
|
|
// URI=spiffe://5583c38e-c1c0-fd1e-2079-170bb2f396ad.consul/ns/default/dc/dc1/svc/pong,
|
|
|
|
|
|
|
|
|
|
return &envoy_rbac_v3.Principal{
|
|
|
|
|
Identifier: &envoy_rbac_v3.Principal_Header{
|
|
|
|
|
Header: &envoy_route_v3.HeaderMatcher{
|
|
|
|
|
Name: "x-forwarded-client-cert",
|
|
|
|
|
HeaderMatchSpecifier: &envoy_route_v3.HeaderMatcher_StringMatch{
|
|
|
|
|
StringMatch: &envoy_matcher_v3.StringMatcher{
|
|
|
|
|
MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{
|
|
|
|
|
SafeRegex: makeEnvoyRegexMatch(pattern),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
|
const anyPath = `[^/]+`
|
|
|
|
|
|
|
|
|
|
func makeSpiffePattern(src rbacService) string {
|
|
|
|
|
var (
|
2022-06-21 02:47:14 +00:00
|
|
|
|
host = src.TrustDomain
|
2022-06-10 21:15:22 +00:00
|
|
|
|
ap = src.PartitionOrDefault()
|
|
|
|
|
ns = src.NamespaceOrDefault()
|
|
|
|
|
svc = src.Name
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Validate proper wildcarding
|
|
|
|
|
if ns == structs.WildcardSpecifier && svc != structs.WildcardSpecifier {
|
|
|
|
|
panic(fmt.Sprintf("not possible to have a wildcarded namespace %q but an exact service %q", ns, svc))
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
2022-06-10 21:15:22 +00:00
|
|
|
|
if ap == structs.WildcardSpecifier {
|
2021-09-10 00:24:43 +00:00
|
|
|
|
panic("not possible to have a wildcarded source partition")
|
|
|
|
|
}
|
2022-06-10 21:15:22 +00:00
|
|
|
|
if src.Peer == structs.WildcardSpecifier {
|
|
|
|
|
panic("not possible to have a wildcarded source peer")
|
|
|
|
|
}
|
2021-09-10 00:24:43 +00:00
|
|
|
|
|
|
|
|
|
// Match on any namespace or service if it is a wildcard, or on a specific value otherwise.
|
2022-06-10 21:15:22 +00:00
|
|
|
|
if ns == structs.WildcardSpecifier {
|
2021-09-10 00:24:43 +00:00
|
|
|
|
ns = anyPath
|
|
|
|
|
}
|
2022-06-10 21:15:22 +00:00
|
|
|
|
if svc == structs.WildcardSpecifier {
|
2021-09-10 00:24:43 +00:00
|
|
|
|
svc = anyPath
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
|
// If service is imported from a peer, the SpiffeID must
|
|
|
|
|
// refer to its remote partition and trust domain.
|
|
|
|
|
if src.Peer != "" {
|
|
|
|
|
ap = src.ExportedPartition
|
|
|
|
|
host = src.TrustDomain
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-10 00:24:43 +00:00
|
|
|
|
id := connect.SpiffeIDService{
|
|
|
|
|
Namespace: ns,
|
|
|
|
|
Service: svc,
|
2022-06-10 21:15:22 +00:00
|
|
|
|
Host: host,
|
2021-09-10 00:24:43 +00:00
|
|
|
|
|
2022-06-10 21:15:22 +00:00
|
|
|
|
// Datacenter is not verified by RBAC, so we match on any value.
|
2021-09-10 00:24:43 +00:00
|
|
|
|
Datacenter: anyPath,
|
|
|
|
|
|
|
|
|
|
// Partition can only ever be an exact value.
|
2022-06-10 21:15:22 +00:00
|
|
|
|
Partition: ap,
|
2021-09-10 00:24:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fmt.Sprintf(`^%s://%s%s$`, id.URI().Scheme, id.Host, id.URI().Path)
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 15:29:54 +00:00
|
|
|
|
func makeSpiffeMeshGatewayPattern(gwTrustDomain, gwPartition string) string {
|
|
|
|
|
id := connect.SpiffeIDMeshGateway{
|
|
|
|
|
Host: gwTrustDomain,
|
|
|
|
|
Partition: gwPartition,
|
|
|
|
|
// Datacenter is not verified by RBAC, so we match on any value.
|
|
|
|
|
Datacenter: anyPath,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fmt.Sprintf(`^%s://%s%s$`, id.URI().Scheme, id.Host, id.URI().Path)
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
func anyPermission() *envoy_rbac_v3.Permission {
|
|
|
|
|
return &envoy_rbac_v3.Permission{
|
|
|
|
|
Rule: &envoy_rbac_v3.Permission_Any{Any: true},
|
2020-08-27 17:20:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-06 22:09:13 +00:00
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
func convertPermission(perm *structs.IntentionPermission) *envoy_rbac_v3.Permission {
|
2020-10-06 22:09:13 +00:00
|
|
|
|
// NOTE: this does not do anything with perm.Action
|
|
|
|
|
if perm.HTTP == nil {
|
|
|
|
|
return anyPermission()
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
var parts []*envoy_rbac_v3.Permission
|
2020-10-06 22:09:13 +00:00
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
case perm.HTTP.PathExact != "":
|
2021-02-26 22:23:15 +00:00
|
|
|
|
parts = append(parts, &envoy_rbac_v3.Permission{
|
|
|
|
|
Rule: &envoy_rbac_v3.Permission_UrlPath{
|
|
|
|
|
UrlPath: &envoy_matcher_v3.PathMatcher{
|
|
|
|
|
Rule: &envoy_matcher_v3.PathMatcher_Path{
|
|
|
|
|
Path: &envoy_matcher_v3.StringMatcher{
|
|
|
|
|
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
Exact: perm.HTTP.PathExact,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
case perm.HTTP.PathPrefix != "":
|
2021-02-26 22:23:15 +00:00
|
|
|
|
parts = append(parts, &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{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
Prefix: perm.HTTP.PathPrefix,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
case perm.HTTP.PathRegex != "":
|
2021-02-26 22:23:15 +00:00
|
|
|
|
parts = append(parts, &envoy_rbac_v3.Permission{
|
|
|
|
|
Rule: &envoy_rbac_v3.Permission_UrlPath{
|
|
|
|
|
UrlPath: &envoy_matcher_v3.PathMatcher{
|
|
|
|
|
Rule: &envoy_matcher_v3.PathMatcher_Path{
|
|
|
|
|
Path: &envoy_matcher_v3.StringMatcher{
|
|
|
|
|
MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
SafeRegex: makeEnvoyRegexMatch(perm.HTTP.PathRegex),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, hdr := range perm.HTTP.Header {
|
2021-02-26 22:23:15 +00:00
|
|
|
|
eh := &envoy_route_v3.HeaderMatcher{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
Name: hdr.Name,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
case hdr.Exact != "":
|
2021-02-26 22:23:15 +00:00
|
|
|
|
eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_ExactMatch{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
ExactMatch: hdr.Exact,
|
|
|
|
|
}
|
|
|
|
|
case hdr.Regex != "":
|
2021-02-26 22:23:15 +00:00
|
|
|
|
eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_SafeRegexMatch{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
SafeRegexMatch: makeEnvoyRegexMatch(hdr.Regex),
|
|
|
|
|
}
|
|
|
|
|
case hdr.Prefix != "":
|
2021-02-26 22:23:15 +00:00
|
|
|
|
eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_PrefixMatch{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
PrefixMatch: hdr.Prefix,
|
|
|
|
|
}
|
|
|
|
|
case hdr.Suffix != "":
|
2021-02-26 22:23:15 +00:00
|
|
|
|
eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_SuffixMatch{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
SuffixMatch: hdr.Suffix,
|
|
|
|
|
}
|
|
|
|
|
case hdr.Present:
|
2021-02-26 22:23:15 +00:00
|
|
|
|
eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_PresentMatch{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
PresentMatch: true,
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
continue // skip this impossible situation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if hdr.Invert {
|
|
|
|
|
eh.InvertMatch = true
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
parts = append(parts, &envoy_rbac_v3.Permission{
|
|
|
|
|
Rule: &envoy_rbac_v3.Permission_Header{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
Header: eh,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(perm.HTTP.Methods) > 0 {
|
|
|
|
|
methodHeaderRegex := strings.Join(perm.HTTP.Methods, "|")
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
eh := &envoy_route_v3.HeaderMatcher{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
Name: ":method",
|
2021-02-26 22:23:15 +00:00
|
|
|
|
HeaderMatchSpecifier: &envoy_route_v3.HeaderMatcher_SafeRegexMatch{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
SafeRegexMatch: makeEnvoyRegexMatch(methodHeaderRegex),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
parts = append(parts, &envoy_rbac_v3.Permission{
|
|
|
|
|
Rule: &envoy_rbac_v3.Permission_Header{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
Header: eh,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NOTE: if for some reason we errantly allow a permission to be defined
|
|
|
|
|
// with a body of "http{}" then we'll end up treating that like "ANY" here.
|
|
|
|
|
return andPermissions(parts)
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
func notPermission(perm *envoy_rbac_v3.Permission) *envoy_rbac_v3.Permission {
|
|
|
|
|
return &envoy_rbac_v3.Permission{
|
|
|
|
|
Rule: &envoy_rbac_v3.Permission_NotRule{NotRule: perm},
|
2020-10-06 22:09:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 22:23:15 +00:00
|
|
|
|
func andPermissions(perms []*envoy_rbac_v3.Permission) *envoy_rbac_v3.Permission {
|
2020-10-06 22:09:13 +00:00
|
|
|
|
switch len(perms) {
|
|
|
|
|
case 0:
|
|
|
|
|
return anyPermission()
|
|
|
|
|
case 1:
|
|
|
|
|
return perms[0]
|
|
|
|
|
default:
|
2021-02-26 22:23:15 +00:00
|
|
|
|
return &envoy_rbac_v3.Permission{
|
|
|
|
|
Rule: &envoy_rbac_v3.Permission_AndRules{
|
|
|
|
|
AndRules: &envoy_rbac_v3.Permission_Set{
|
2020-10-06 22:09:13 +00:00
|
|
|
|
Rules: perms,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|