Files
nomad/client/meta_endpoint.go
Tim Gross cbd7248248 auth: use ACLsDisabledACL when ACLs are disabled (#18754)
The RPC handlers expect to see `nil` ACL objects whenever ACLs are disabled. By
using `nil` as a sentinel value, we have the risk of nil pointer exceptions and
improper handling of `nil` when returned from our various auth methods that can
lead to privilege escalation bugs. This is the final patch in a series to
eliminate the use of `nil` ACLs as a sentinel value for when ACLs are disabled.

This patch adds a new virtual ACL policy field for when ACLs are disabled and
updates our authentication logic to use it. Included:

* Extends auth package tests to demonstrate that nil ACLs are treated as failed
  auth and disabled ACLs succeed auth.
* Adds a new `AllowDebug` ACL check for the weird special casing we have for
  pprof debugging when ACLs are disabled.
* Removes the remaining unexported methods (and repeated tests) from the
  `nomad/acl.go` file.
* Update the semgrep rules to detect improper nil ACL checking and remove the
  old invalid ACL checks.
* Update the contributing guide for RPC authentication.

Ref: https://github.com/hashicorp/nomad-enterprise/pull/1218
Ref: https://github.com/hashicorp/nomad/pull/18703
Ref: https://github.com/hashicorp/nomad/pull/18715
Ref: https://github.com/hashicorp/nomad/pull/16799
Ref: https://github.com/hashicorp/nomad/pull/18730
Ref: https://github.com/hashicorp/nomad/pull/18744
2023-10-16 09:30:24 -04:00

110 lines
2.6 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package client
import (
"net/http"
"time"
"github.com/armon/go-metrics"
"github.com/hashicorp/nomad/nomad/structs"
"golang.org/x/exp/maps"
)
type NodeMeta struct {
c *Client
}
func newNodeMetaEndpoint(c *Client) *NodeMeta {
n := &NodeMeta{c: c}
return n
}
func (n *NodeMeta) Apply(args *structs.NodeMetaApplyRequest, reply *structs.NodeMetaResponse) error {
defer metrics.MeasureSince([]string{"client", "node_meta", "apply"}, time.Now())
// Check node write permissions
if aclObj, err := n.c.ResolveToken(args.AuthToken); err != nil {
return err
} else if !aclObj.AllowNodeWrite() {
return structs.ErrPermissionDenied
}
if err := args.Validate(); err != nil {
return structs.NewErrRPCCoded(http.StatusBadRequest, err.Error())
}
var stateErr error
var dyn map[string]*string
newNode := n.c.UpdateNode(func(node *structs.Node) {
// First update the Client's state store. This must be done
// atomically with updating the metadata inmemory to avoid
// bad interleaving between concurrent updates.
dyn = maps.Clone(n.c.metaDynamic)
maps.Copy(dyn, args.Meta)
// Delete null values from the dynamic metadata if they are also not
// static. Static null values must be kept so their removal is
// persisted in client state.
for k, v := range args.Meta {
_, static := n.c.metaStatic[k]
if v == nil && !static {
delete(dyn, k)
}
}
if stateErr = n.c.stateDB.PutNodeMeta(dyn); stateErr != nil {
return
}
// Apply updated dynamic metadata to client and node now that the part of
// the operation that can fail succeeded (persistence). Must clone as dyn
// is read outside of UpdateNode.
n.c.metaDynamic = maps.Clone(dyn)
for k, v := range args.Meta {
if v == nil {
delete(node.Meta, k)
continue
}
node.Meta[k] = *v
}
})
if stateErr != nil {
return stateErr
}
// Trigger an async node update
n.c.updateNode()
reply.Meta = newNode.Meta
reply.Dynamic = dyn
reply.Static = n.c.metaStatic
return nil
}
func (n *NodeMeta) Read(args *structs.NodeSpecificRequest, reply *structs.NodeMetaResponse) error {
defer metrics.MeasureSince([]string{"client", "node_meta", "read"}, time.Now())
// Check node read permissions
if aclObj, err := n.c.ResolveToken(args.AuthToken); err != nil {
return err
} else if !aclObj.AllowNodeRead() {
return structs.ErrPermissionDenied
}
// Must acquire configLock to ensure reads aren't interleaved with
// writes
n.c.configLock.Lock()
defer n.c.configLock.Unlock()
reply.Meta = n.c.config.Node.Meta
reply.Dynamic = maps.Clone(n.c.metaDynamic)
reply.Static = n.c.metaStatic
return nil
}