acl: some acl authz refactors for nodes (#10909)

This commit is contained in:
R.B. Boyer 2021-08-25 13:43:11 -05:00 committed by GitHub
parent 11b1dc1f97
commit a6d22efb49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 75 additions and 38 deletions

View File

@ -62,6 +62,7 @@ func (a *Agent) vetServiceRegisterWithAuthorizer(authz acl.Authorizer, service *
if service.Kind == structs.ServiceKindConnectProxy {
service.FillAuthzContext(&authzContext)
if authz.ServiceWrite(service.Proxy.DestinationServiceName, &authzContext) != acl.Allow {
// TODO(partitions) fix this to include namespace and partition
return acl.PermissionDenied("Missing service:write on %s", service.Proxy.DestinationServiceName)
}
}
@ -98,7 +99,7 @@ func (a *Agent) vetCheckRegisterWithAuthorizer(authz acl.Authorizer, check *stru
}
} else {
if authz.NodeWrite(a.config.NodeName, &authzContext) != acl.Allow {
return acl.PermissionDenied("Missing node:write on %s", a.config.NodeName)
return acl.PermissionDenied("Missing node:write on %s", structs.NodeNameString(a.config.NodeName, a.agentEnterpriseMeta()))
}
}
@ -110,7 +111,7 @@ func (a *Agent) vetCheckRegisterWithAuthorizer(authz acl.Authorizer, check *stru
}
} else {
if authz.NodeWrite(a.config.NodeName, &authzContext) != acl.Allow {
return acl.PermissionDenied("Missing node:write on %s", a.config.NodeName)
return acl.PermissionDenied("Missing node:write on %s", structs.NodeNameString(a.config.NodeName, a.agentEnterpriseMeta()))
}
}
}
@ -126,11 +127,11 @@ func (a *Agent) vetCheckUpdateWithAuthorizer(authz acl.Authorizer, checkID struc
if existing := a.State.Check(checkID); existing != nil {
if len(existing.ServiceName) > 0 {
if authz.ServiceWrite(existing.ServiceName, &authzContext) != acl.Allow {
return acl.PermissionDenied("Missing service:write on %s", existing.ServiceName)
return acl.PermissionDenied("Missing service:write on %s", structs.ServiceIDString(existing.ServiceName, &existing.EnterpriseMeta))
}
} else {
if authz.NodeWrite(a.config.NodeName, &authzContext) != acl.Allow {
return acl.PermissionDenied("Missing node:write on %s", a.config.NodeName)
return acl.PermissionDenied("Missing node:write on %s", structs.NodeNameString(a.config.NodeName, a.agentEnterpriseMeta()))
}
}
} else {
@ -184,14 +185,12 @@ func (a *Agent) filterChecksWithAuthorizer(authz acl.Authorizer, checks *map[str
var authzContext acl.AuthorizerContext
// Filter out checks based on the node or service policy.
for id, check := range *checks {
check.FillAuthzContext(&authzContext)
if len(check.ServiceName) > 0 {
check.FillAuthzContext(&authzContext)
if authz.ServiceRead(check.ServiceName, &authzContext) == acl.Allow {
continue
}
} else {
// TODO(partition): should this be a Default or Node flavored entmeta?
check.NodeEnterpriseMetaForPartition().FillAuthzContext(&authzContext)
if authz.NodeRead(a.config.NodeName, &authzContext) == acl.Allow {
continue
}

View File

@ -1247,7 +1247,10 @@ func (s *HTTPHandlers) AgentNodeMaintenance(resp http.ResponseWriter, req *http.
if err != nil {
return nil, err
}
if authz.NodeWrite(s.agent.config.NodeName, nil) != acl.Allow {
var authzContext acl.AuthorizerContext
s.agent.agentEnterpriseMeta().FillAuthzContext(&authzContext)
if authz.NodeWrite(s.agent.config.NodeName, &authzContext) != acl.Allow {
return nil, acl.ErrPermissionDenied
}

View File

@ -5150,6 +5150,7 @@ func (tc testCase) run(format string, dataDir string) func(t *testing.T) {
expected.ACLResolverSettings.Datacenter = expected.Datacenter
expected.ACLResolverSettings.ACLsEnabled = expected.ACLsEnabled
expected.ACLResolverSettings.NodeName = expected.NodeName
expected.ACLResolverSettings.EnterpriseMeta = *structs.NodeEnterpriseMetaInPartition(expected.PartitionOrDefault())
assertDeepEqual(t, expected, actual, cmpopts.EquateEmpty())
}
@ -5189,6 +5190,7 @@ func TestLoad_FullConfig(t *testing.T) {
}
defaultEntMeta := structs.DefaultEnterpriseMetaInDefaultPartition()
nodeEntMeta := structs.NodeEnterpriseMetaInDefaultPartition()
expected := &RuntimeConfig{
// non-user configurable values
AEInterval: time.Minute,
@ -5241,6 +5243,7 @@ func TestLoad_FullConfig(t *testing.T) {
ACLsEnabled: true,
Datacenter: "rzo029wg",
NodeName: "otlLxGaI",
EnterpriseMeta: *nodeEntMeta,
ACLDefaultPolicy: "72c2e7a0",
ACLDownPolicy: "03eb2aee",
ACLTokenTTL: 3321 * time.Second,

View File

@ -9,6 +9,7 @@
"ACLTokenTTL": "0s",
"ACLsEnabled": false,
"Datacenter": "",
"EnterpriseMeta": {},
"NodeName": ""
},
"ACLTokenReplication": false,

View File

@ -217,9 +217,10 @@ const aclClientDisabledTTL = 30 * time.Second
// TODO: rename the fields to remove the ACL prefix
type ACLResolverSettings struct {
ACLsEnabled bool
Datacenter string
NodeName string
ACLsEnabled bool
Datacenter string
NodeName string
EnterpriseMeta structs.EnterpriseMeta
// ACLPolicyTTL is used to control the time-to-live of cached ACL policies. This has
// a major impact on performance. By default, it is set to 30 seconds.
@ -301,7 +302,11 @@ type ACLResolver struct {
agentMasterAuthz acl.Authorizer
}
func agentMasterAuthorizer(nodeName string) (acl.Authorizer, error) {
func agentMasterAuthorizer(nodeName string, entMeta *structs.EnterpriseMeta) (acl.Authorizer, error) {
// TODO(partitions,acls): this function likely needs split so that the generated policy can be partitioned appropriately
// TODO(partitions,acls): after this all works, write a test for this function when partitioned
// Build a policy for the agent master token.
// The builtin agent master policy allows reading any node information
// and allows writes to the agent with the node name of the running agent
@ -355,7 +360,7 @@ func NewACLResolver(config *ACLResolverConfig) (*ACLResolver, error) {
return nil, fmt.Errorf("invalid ACL down policy %q", config.Config.ACLDownPolicy)
}
authz, err := agentMasterAuthorizer(config.Config.NodeName)
authz, err := agentMasterAuthorizer(config.Config.NodeName, &config.Config.EnterpriseMeta)
if err != nil {
return nil, fmt.Errorf("failed to initialize the agent master authorizer")
}
@ -1443,7 +1448,7 @@ func (f *aclFilter) filterServiceNodes(nodes *structs.ServiceNodes) {
if f.allowNode(node.Node, &authzContext) && f.allowService(node.ServiceName, &authzContext) {
continue
}
f.logger.Debug("dropping node from result due to ACLs", "node", node.Node)
f.logger.Debug("dropping node from result due to ACLs", "node", structs.NodeNameString(node.Node, &node.EnterpriseMeta))
sn = append(sn[:i], sn[i+1:]...)
i--
}
@ -1457,8 +1462,7 @@ func (f *aclFilter) filterNodeServices(services **structs.NodeServices) {
}
var authzContext acl.AuthorizerContext
// TODO(partitions): put partition into this wildcard?
structs.WildcardEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext)
(*services).Node.FillAuthzContext(&authzContext)
if !f.allowNode((*services).Node.Node, &authzContext) {
*services = nil
return
@ -1482,8 +1486,7 @@ func (f *aclFilter) filterNodeServiceList(services **structs.NodeServiceList) {
}
var authzContext acl.AuthorizerContext
// TODO(partitions): put partition into this wildcard?
structs.WildcardEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext)
(*services).Node.FillAuthzContext(&authzContext)
if !f.allowNode((*services).Node.Node, &authzContext) {
*services = nil
return
@ -1523,7 +1526,7 @@ func (f *aclFilter) filterCheckServiceNodes(nodes *structs.CheckServiceNodes) {
if f.allowNode(node.Node.Node, &authzContext) && f.allowService(node.Service.Service, &authzContext) {
continue
}
f.logger.Debug("dropping node from result due to ACLs", "node", node.Node.Node)
f.logger.Debug("dropping node from result due to ACLs", "node", structs.NodeNameString(node.Node.Node, node.Node.GetEnterpriseMeta()))
csn = append(csn[:i], csn[i+1:]...)
i--
}
@ -1580,15 +1583,14 @@ func (f *aclFilter) filterSessions(sessions *structs.Sessions) {
func (f *aclFilter) filterCoordinates(coords *structs.Coordinates) {
c := *coords
var authzContext acl.AuthorizerContext
// TODO(partitions): put partition into this wildcard?
structs.WildcardEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext)
for i := 0; i < len(c); i++ {
c[i].FillAuthzContext(&authzContext)
node := c[i].Node
if f.allowNode(node, &authzContext) {
continue
}
f.logger.Debug("dropping node from result due to ACLs", "node", node)
f.logger.Debug("dropping node from result due to ACLs", "node", structs.NodeNameString(node, c[i].GetEnterpriseMeta()))
c = append(c[:i], c[i+1:]...)
i--
}
@ -1622,10 +1624,9 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
info := nd[i]
// Filter nodes
// TODO(partitions): put partition into this wildcard?
structs.WildcardEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext)
info.FillAuthzContext(&authzContext)
if node := info.Node; !f.allowNode(node, &authzContext) {
f.logger.Debug("dropping node from result due to ACLs", "node", node)
f.logger.Debug("dropping node from result due to ACLs", "node", structs.NodeNameString(node, info.GetEnterpriseMeta()))
nd = append(nd[:i], nd[i+1:]...)
i--
continue
@ -1691,15 +1692,14 @@ func (f *aclFilter) filterNodes(nodes *structs.Nodes) {
n := *nodes
var authzContext acl.AuthorizerContext
// TODO(partitions): put partition into this wildcard?
structs.WildcardEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext)
for i := 0; i < len(n); i++ {
n[i].FillAuthzContext(&authzContext)
node := n[i].Node
if f.allowNode(node, &authzContext) {
continue
}
f.logger.Debug("dropping node from result due to ACLs", "node", node)
f.logger.Debug("dropping node from result due to ACLs", "node", structs.NodeNameString(node, n[i].GetEnterpriseMeta()))
n = append(n[:i], n[i+1:]...)
i--
}

View File

@ -208,6 +208,7 @@ func (ac *AutoConfig) updateACLsInConfig(opts AutoConfigOptions, resp *pbautocon
Datacenter: ac.config.Datacenter,
},
},
// TODO(partitions): support auto-config in different partitions
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
}

View File

@ -69,7 +69,7 @@ func (c *Client) setupSerf(conf *serf.Config, ch chan serf.Event, path string) (
return nil, err
}
addEnterpriseSerfTags(conf.Tags, c.config.agentEnterpriseMeta())
addEnterpriseSerfTags(conf.Tags, c.config.AgentEnterpriseMeta())
conf.ReconnectTimeoutOverride = libserf.NewReconnectOverride(c.logger)

View File

@ -73,6 +73,13 @@ func testClientWithConfigWithErr(t *testing.T, cb func(c *Config)) (string, *Cli
cb(config)
}
// Apply config to copied fields because many tests only set the old
//values.
config.ACLResolverSettings.ACLsEnabled = config.ACLsEnabled
config.ACLResolverSettings.NodeName = config.NodeName
config.ACLResolverSettings.Datacenter = config.Datacenter
config.ACLResolverSettings.EnterpriseMeta = *config.AgentEnterpriseMeta()
client, err := NewClient(config, newDefaultDeps(t, config))
return dir, client, err
}

View File

@ -4,6 +4,6 @@ package consul
import "github.com/hashicorp/consul/agent/structs"
func (c *Config) agentEnterpriseMeta() *structs.EnterpriseMeta {
func (c *Config) AgentEnterpriseMeta() *structs.EnterpriseMeta {
return structs.NodeEnterpriseMetaInDefaultPartition()
}

View File

@ -186,7 +186,7 @@ func (s *ConnectCA) Sign(
"we are %s", serviceID.Datacenter, s.srv.config.Datacenter)
}
} else if isAgent {
// TODO(partitions): support auto-config in different partitions
structs.DefaultEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext)
if authz.NodeWrite(agentID.Agent, &authzContext) != acl.Allow {
return acl.ErrPermissionDenied

View File

@ -151,7 +151,7 @@ func (c *Coordinate) Update(args *structs.CoordinateUpdateRequest, reply *struct
}
var authzContext acl.AuthorizerContext
args.DefaultEnterpriseMetaForPartition().FillAuthzContext(&authzContext)
args.FillAuthzContext(&authzContext)
if authz.NodeWrite(args.Node, &authzContext) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -174,7 +174,7 @@ func (c *Coordinate) ListDatacenters(args *struct{}, reply *[]structs.Datacenter
return err
}
// TODO(partitions):
// TODO(partitions): should we filter any of this out?
var out []structs.DatacenterMap
@ -248,7 +248,7 @@ func (c *Coordinate) Node(args *structs.NodeSpecificRequest, reply *structs.Inde
}
var authzContext acl.AuthorizerContext
args.WildcardEnterpriseMetaForPartition().FillAuthzContext(&authzContext)
args.FillAuthzContext(&authzContext)
if authz.NodeRead(args.Node, &authzContext) != acl.Allow {
return acl.ErrPermissionDenied
}

View File

@ -47,8 +47,7 @@ func (t *txnResultsFilter) Filter(i int) bool {
result.KV.EnterpriseMeta.FillAuthzContext(&authzContext)
return t.authorizer.KeyRead(result.KV.Key, &authzContext) != acl.Allow
case result.Node != nil:
// TODO(partitions): put partition into this wildcard?
structs.WildcardEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext)
(*structs.Node)(result.Node).FillAuthzContext(&authzContext)
return t.authorizer.NodeRead(result.Node.Node, &authzContext) != acl.Allow
case result.Service != nil:
result.Service.EnterpriseMeta.FillAuthzContext(&authzContext)

View File

@ -1438,6 +1438,7 @@ func (c *CAManager) SignCertificate(csr *x509.CertificateRequest, spiffeID conne
csr.URIs = uris
}
// TODO(partitions): support auto-config in different partitions
entMeta.Merge(structs.DefaultEnterpriseMetaInDefaultPartition())
}

View File

@ -176,7 +176,7 @@ func (s *Server) setupSerf(conf *serf.Config, ch chan serf.Event, path string, w
conf.ReconnectTimeoutOverride = libserf.NewReconnectOverride(s.logger)
addEnterpriseSerfTags(conf.Tags, s.config.agentEnterpriseMeta())
addEnterpriseSerfTags(conf.Tags, s.config.AgentEnterpriseMeta())
if s.config.OverrideInitialSerfTags != nil {
s.config.OverrideInitialSerfTags(conf.Tags)

View File

@ -250,6 +250,7 @@ func testServerWithConfig(t *testing.T, cb func(*Config)) (string, *Server) {
config.ACLResolverSettings.ACLsEnabled = config.ACLsEnabled
config.ACLResolverSettings.NodeName = config.NodeName
config.ACLResolverSettings.Datacenter = config.Datacenter
config.ACLResolverSettings.EnterpriseMeta = *config.AgentEnterpriseMeta()
var err error
srv, err = newServer(t, config)

View File

@ -11,9 +11,10 @@ import (
"strings"
"time"
"golang.org/x/crypto/blake2b"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/lib"
"golang.org/x/crypto/blake2b"
)
type ACLMode string
@ -229,6 +230,7 @@ func (s *ACLNodeIdentity) SyntheticPolicy() *ACLPolicy {
policy.Rules = rules
policy.Syntax = acl.SyntaxCurrent
policy.Datacenters = []string{s.Datacenter}
// TODO(partitions,acls): this needs to be fed the correct partition
policy.EnterpriseMeta = *DefaultEnterpriseMetaInDefaultPartition()
policy.SetHash(true)
return policy

View File

@ -1824,6 +1824,14 @@ type NodeInfo struct {
Checks HealthChecks
}
func (n *NodeInfo) GetEnterpriseMeta() *EnterpriseMeta {
return NodeEnterpriseMetaInPartition(n.Partition)
}
func (n *NodeInfo) PartitionOrDefault() string {
return PartitionOrDefault(n.Partition)
}
// NodeDump is used to dump all the nodes with all their
// associated data. This is currently used for the UI only,
// as it is rather expensive to generate.

View File

@ -128,6 +128,12 @@ func WildcardEnterpriseMetaInPartition(_ string) *EnterpriseMeta {
// FillAuthzContext stub
func (_ *EnterpriseMeta) FillAuthzContext(_ *acl.AuthorizerContext) {}
func (_ *Node) FillAuthzContext(_ *acl.AuthorizerContext) {}
func (_ *Coordinate) FillAuthzContext(_ *acl.AuthorizerContext) {}
func (_ *NodeInfo) FillAuthzContext(_ *acl.AuthorizerContext) {}
func (_ *EnterpriseMeta) Normalize() {}
// FillAuthzContext stub
@ -149,6 +155,10 @@ func (_ *TxnServiceOp) FillAuthzContext(_ *acl.AuthorizerContext) {}
// OSS Stub
func (_ *TxnCheckOp) FillAuthzContext(_ *acl.AuthorizerContext) {}
func NodeNameString(node string, _ *EnterpriseMeta) string {
return node
}
func ServiceIDString(id string, _ *EnterpriseMeta) string {
return id
}

View File

@ -614,6 +614,8 @@ func (s *HTTPHandlers) UIMetricsProxy(resp http.ResponseWriter, req *http.Reques
// This endpoint requires wildcard read on all services and all nodes.
//
// In enterprise it requires this _in all namespaces_ too.
//
// TODO(partitions,acls): need to revisit this
var authzContext acl.AuthorizerContext
entMeta.WildcardEnterpriseMetaForPartition().FillAuthzContext(&authzContext)