Merge pull request #11024 from hashicorp/partitions/rbac

This commit is contained in:
Freddy 2021-09-14 11:18:19 -06:00 committed by GitHub
commit ce04ce13dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 61 additions and 33 deletions

View File

@ -13,6 +13,7 @@ import (
envoy_network_rbac_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/rbac/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" envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
) )
@ -296,15 +297,16 @@ func (p *rbacPermission) Flatten() *envoy_rbac_v3.Permission {
return andPermissions(parts) return andPermissions(parts)
} }
// 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.
func simplifyNotSourceSlice(notSources []structs.ServiceName) []structs.ServiceName { func simplifyNotSourceSlice(notSources []structs.ServiceName) []structs.ServiceName {
if len(notSources) <= 1 { if len(notSources) <= 1 {
return notSources return notSources
} }
// Collapse NotSources elements together if any element is a subset of
// another.
// Sort, keeping the least wildcarded elements first. // Sort, keeping the least wildcarded elements first.
// More specific elements have a higher precedence over more wildcarded elements.
sort.SliceStable(notSources, func(i, j int) bool { sort.SliceStable(notSources, func(i, j int) bool {
return countWild(notSources[i]) < countWild(notSources[j]) return countWild(notSources[i]) < countWild(notSources[j])
}) })
@ -457,6 +459,16 @@ func makeRBACRules(intentions structs.Intentions, intentionDefaultAllow bool, is
return rbac, nil return rbac, nil
} }
// 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.
func removeSameSourceIntentions(intentions structs.Intentions) structs.Intentions { func removeSameSourceIntentions(intentions structs.Intentions) structs.Intentions {
if len(intentions) < 2 { if len(intentions) < 2 {
return intentions return intentions
@ -493,6 +505,7 @@ func removeSameSourceIntentions(intentions structs.Intentions) structs.Intention
// - (web, *) => true, because "all services" includes "web" // - (web, *) => true, because "all services" includes "web"
// - (default/web, default/*) => true, because "all services in the default NS" includes "default/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/*, */*) => 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"
func ixnSourceMatches(tester, against structs.ServiceName) bool { func ixnSourceMatches(tester, against structs.ServiceName) bool {
// We assume that we can't have the same intention twice before arriving // We assume that we can't have the same intention twice before arriving
// here. // here.
@ -505,13 +518,19 @@ func ixnSourceMatches(tester, against structs.ServiceName) bool {
return false return false
} }
matchesAP := tester.PartitionOrDefault() == against.PartitionOrDefault() || against.PartitionOrDefault() == structs.WildcardSpecifier
matchesNS := tester.NamespaceOrDefault() == against.NamespaceOrDefault() || against.NamespaceOrDefault() == structs.WildcardSpecifier matchesNS := tester.NamespaceOrDefault() == against.NamespaceOrDefault() || against.NamespaceOrDefault() == structs.WildcardSpecifier
matchesName := tester.Name == against.Name || against.Name == structs.WildcardSpecifier matchesName := tester.Name == against.Name || against.Name == structs.WildcardSpecifier
return matchesNS && matchesName return matchesAP && matchesNS && matchesName
} }
// countWild counts the number of wildcard values in the given namespace and name. // countWild counts the number of wildcard values in the given namespace and name.
func countWild(src structs.ServiceName) int { func countWild(src structs.ServiceName) int {
// If Partition is wildcard, panic because it's not supported
if src.PartitionOrDefault() == structs.WildcardSpecifier {
panic("invalid state: intention references wildcard partition")
}
// If NS is wildcard, it must be 2 since wildcards only follow exact // If NS is wildcard, it must be 2 since wildcards only follow exact
if src.NamespaceOrDefault() == structs.WildcardSpecifier { if src.NamespaceOrDefault() == structs.WildcardSpecifier {
return 2 return 2
@ -546,7 +565,7 @@ func notPrincipal(id *envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
} }
func idPrincipal(src structs.ServiceName) *envoy_rbac_v3.Principal { func idPrincipal(src structs.ServiceName) *envoy_rbac_v3.Principal {
pattern := makeSpiffePattern(src.NamespaceOrDefault(), src.Name) pattern := makeSpiffePattern(src.PartitionOrDefault(), src.NamespaceOrDefault(), src.Name)
return &envoy_rbac_v3.Principal{ return &envoy_rbac_v3.Principal{
Identifier: &envoy_rbac_v3.Principal_Authenticated_{ Identifier: &envoy_rbac_v3.Principal_Authenticated_{
@ -560,21 +579,41 @@ func idPrincipal(src structs.ServiceName) *envoy_rbac_v3.Principal {
}, },
} }
} }
func makeSpiffePattern(sourceNS, sourceName string) string {
const ( func makeSpiffePattern(sourceAP, sourceNS, sourceName string) string {
anyPath = `[^/]+` if sourceNS == structs.WildcardSpecifier && sourceName != structs.WildcardSpecifier {
spiffeTemplate = `^spiffe://%s/ns/%s/dc/%s/svc/%s$`
)
switch {
case sourceNS != structs.WildcardSpecifier && sourceName != structs.WildcardSpecifier:
return fmt.Sprintf(spiffeTemplate, anyPath, sourceNS, anyPath, sourceName)
case sourceNS != structs.WildcardSpecifier && sourceName == structs.WildcardSpecifier:
return fmt.Sprintf(spiffeTemplate, anyPath, sourceNS, anyPath, anyPath)
case sourceNS == structs.WildcardSpecifier && sourceName == structs.WildcardSpecifier:
return fmt.Sprintf(spiffeTemplate, anyPath, anyPath, anyPath, anyPath)
default:
panic(fmt.Sprintf("not possible to have a wildcarded namespace %q but an exact service %q", sourceNS, sourceName)) panic(fmt.Sprintf("not possible to have a wildcarded namespace %q but an exact service %q", sourceNS, sourceName))
} }
if sourceAP == structs.WildcardSpecifier {
panic("not possible to have a wildcarded source partition")
}
const anyPath = `[^/]+`
// Match on any namespace or service if it is a wildcard, or on a specific value otherwise.
ns := sourceNS
if sourceNS == structs.WildcardSpecifier {
ns = anyPath
}
svc := sourceName
if sourceName == structs.WildcardSpecifier {
svc = anyPath
}
id := connect.SpiffeIDService{
Namespace: ns,
Service: svc,
// Trust domain and datacenter are not verified by RBAC, so we match on any value.
Host: anyPath,
Datacenter: anyPath,
// Partition can only ever be an exact value.
Partition: sourceAP,
}
return fmt.Sprintf(`^%s://%s%s$`, id.URI().Scheme, id.Host, id.URI().Path)
} }
func anyPermission() *envoy_rbac_v3.Permission { func anyPermission() *envoy_rbac_v3.Permission {

View File

@ -887,14 +887,3 @@ func makeServiceNameSlice(slice []string) []structs.ServiceName {
} }
return out return out
} }
func unmakeServiceNameSlice(slice []structs.ServiceName) []string {
if len(slice) == 0 {
return nil
}
var out []string
for _, src := range slice {
out = append(out, src.String())
}
return out
}