Pre forwarding authentication (#15417)

Upcoming work to instrument the rate of RPC requests by consumer (and eventually
rate limit) require that we authenticate a RPC request before forwarding. Add a
new top-level `Authenticate` method to the server and have it return an
`AuthenticatedIdentity` struct. RPC handlers will use the relevant fields of
this identity for performing authorization.

This changeset includes:
* The main implementation of `Authenticate`
* Provide a new RPC `ACL.WhoAmI` for debugging authentication. This endpoint
  returns the same `AuthenticatedIdentity` that will be used by RPC handlers. At
  some point we might want to give this an equivalent HTTP endpoint but I didn't
  want to add that to our public API until some of the other Workload Identity
  work is solidified, especially if we don't need it yet.
* A full coverage test of the `Authenticate` method. This sets up two server
  nodes with mTLS and ACLs, some tokens, and some allocations with workload
  identities.
* Wire up an example of using `Authenticate` in the `Namespace.Upsert` RPC and
  see how authorization happens after forwarding.
* A new semgrep rule for `Authenticate`, which we'll need to update once we're
  ready to wire up more RPC endpoints with authorization steps.
This commit is contained in:
Tim Gross 2022-12-06 14:44:03 -05:00 committed by GitHub
parent ce0ffdd077
commit e0fddee386
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 571 additions and 16 deletions

View File

@ -62,6 +62,12 @@ rules:
...
return $A.deregister(...)
...
# Pattern used by Authenticate method.
# TODO: add authorization steps as well.
- pattern-not-inside: |
...
... := $A.$B.Authenticate($A.ctx, args.AuthToken)
...
- metavariable-pattern:
metavariable: $METHOD
patterns:

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIBxaGxJxJXnAXVmb8E3ALsWqva9F01R0cr/1Ap75YyeAoAoGCCqGSM49
AwEHoUQDQgAEXSLJPcA7b9P6y0Ls7zR4997+F3251hwEUn8qR01AEVGjYrAjk/ns
qaq7P9y/w4k9TvhWaq9/L6id468a0/VWCw==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICWTCCAgCgAwIBAgIQOW7/CDB2IhlMyfh16erD/jAKBggqhkjOPQQDAjB4MQsw
CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy
YW5jaXNjbzESMBAGA1UEChMJSGFzaGlDb3JwMQ4wDAYDVQQLEwVOb21hZDEYMBYG
A1UEAxMPbm9tYWQuaGFzaGljb3JwMCAXDTIyMTEyOTE5MjY0MloYDzIxMjIxMTA1
MTkyNjQyWjAhMR8wHQYDVQQDExZjbGllbnQucmVnaW9uRm9vLm5vbWFkMFkwEwYH
KoZIzj0CAQYIKoZIzj0DAQcDQgAEXSLJPcA7b9P6y0Ls7zR4997+F3251hwEUn8q
R01AEVGjYrAjk/nsqaq7P9y/w4k9TvhWaq9/L6id468a0/VWC6OBwDCBvTAOBgNV
HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1Ud
EwEB/wQCMAAwKQYDVR0OBCIEII1J2DmAAcPAaNLFlxFpdBzjhRFRd9E9fedoz9I8
vHPPMB8GA1UdIwQYMBaAFKJkNK006jVs/eYf4w00jciQj2MEMDIGA1UdEQQrMCmC
FmNsaWVudC5yZWdpb25Gb28ubm9tYWSCCWxvY2FsaG9zdIcEfwAAATAKBggqhkjO
PQQDAgNHADBEAiAXzlb98iqyXvtlkThR13ojgjwjP25JBysDKf4vnXjQuwIgFpkB
0B7bPy5VNIAVsw6n5ocvsB7w0rgBPJyS3I2YCi0=
-----END CERTIFICATE-----

View File

@ -1,16 +1,133 @@
package nomad
import (
"errors"
"fmt"
"net"
"time"
metrics "github.com/armon/go-metrics"
lru "github.com/hashicorp/golang-lru"
"github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/state"
"github.com/hashicorp/nomad/nomad/structs"
)
// Authenticate extracts an AuthenticatedIdentity from the request context or
// provided token. The caller can extract an acl.ACL, WorkloadIdentity, or other
// identifying token to use for authorization.
//
// Note: when called on the follower we'll be making stale queries, so it's
// possible if the follower is behind that the leader will get a different value
// if an ACL token or allocation's WI has just been created.
func (s *Server) Authenticate(ctx *RPCContext, secretID string) (*structs.AuthenticatedIdentity, error) {
// Previously-connected clients will have a NodeID set and will be a large
// number of the RPCs sent, so we can fast path this case
if ctx != nil && ctx.NodeID != "" {
return &structs.AuthenticatedIdentity{ClientID: ctx.NodeID}, nil
}
// get the user ACLToken or anonymous token
aclToken, err := s.ResolveSecretToken(secretID)
switch {
case err == nil:
// If ACLs are disabled or we have a non-anonymous token, return that.
if aclToken == nil || aclToken != structs.AnonymousACLToken {
return &structs.AuthenticatedIdentity{ACLToken: aclToken}, nil
}
case errors.Is(err, structs.ErrTokenExpired):
return nil, err
case errors.Is(err, structs.ErrTokenInvalid):
// if it's not a UUID it might be an identity claim
claims, err := s.VerifyClaim(secretID)
if err != nil {
// we already know the token wasn't valid for an ACL in the state
// store, so if we get an error at this point we have an invalid
// token and there are no other options but to bail out
return nil, err
}
return &structs.AuthenticatedIdentity{Claims: claims}, nil
case errors.Is(err, structs.ErrTokenNotFound):
// Check if the secret ID is the leader's secret ID, in which case treat
// it as a management token.
leaderAcl := s.getLeaderAcl()
if leaderAcl != "" && secretID == leaderAcl {
aclToken = structs.LeaderACLToken
} else {
// Otherwise, see if the secret ID belongs to a node. We should
// reach this point only on first connection.
node, err := s.State().NodeBySecretID(nil, secretID)
if err != nil {
// this is a go-memdb error; shouldn't happen
return nil, fmt.Errorf("could not resolve node secret: %w", err)
}
if node != nil {
return &structs.AuthenticatedIdentity{ClientID: node.ID}, nil
}
}
default: // any other error
return nil, fmt.Errorf("could not resolve user: %w", err)
}
// If there's no context we're in a "static" handler which only happens for
// cases where the leader is making RPCs internally (volumewatcher and
// deploymentwatcher)
if ctx == nil {
return &structs.AuthenticatedIdentity{ACLToken: aclToken}, nil
}
// At this point we either have an anonymous token or an invalid one.
// Unlike clients that provide their Node ID on first connection, server
// RPCs don't include an ID for the server so we identify servers by cert
// and IP address.
identity := &structs.AuthenticatedIdentity{ACLToken: aclToken}
if ctx.TLS {
identity.TLSName = ctx.Certificate().Subject.CommonName
}
var remoteAddr *net.TCPAddr
var ok bool
if ctx.Session != nil {
remoteAddr, ok = ctx.Session.RemoteAddr().(*net.TCPAddr)
if !ok {
return nil, errors.New("session address was not a TCP address")
}
}
if remoteAddr == nil && ctx.Conn != nil {
remoteAddr, ok = ctx.Conn.RemoteAddr().(*net.TCPAddr)
if !ok {
return nil, errors.New("session address was not a TCP address")
}
}
if remoteAddr != nil {
identity.RemoteIP = remoteAddr.IP
return identity, nil
}
s.logger.Error("could not authenticate RPC request or determine remote address")
return nil, structs.ErrPermissionDenied
}
func (s *Server) ResolveACL(aclToken *structs.ACLToken) (*acl.ACL, error) {
if !s.config.ACLEnabled {
return nil, nil
}
snap, err := s.fsm.State().Snapshot()
if err != nil {
return nil, err
}
return resolveACLFromToken(snap, s.aclCache, aclToken)
}
// ResolveToken is used to translate an ACL Token Secret ID into
// an ACL object, nil if ACLs are disabled, or an error.
func (s *Server) ResolveToken(secretID string) (*acl.ACL, error) {
@ -106,6 +223,12 @@ func resolveTokenFromSnapshotCache(snap *state.StateSnapshot, cache *lru.TwoQueu
}
}
return resolveACLFromToken(snap, cache, token)
}
func resolveACLFromToken(snap *state.StateSnapshot, cache *lru.TwoQueueCache, token *structs.ACLToken) (*acl.ACL, error) {
// Check if this is a management token
if token.Type == structs.ACLManagementToken {
return acl.ManagementACL, nil
@ -185,27 +308,28 @@ func (s *Server) ResolveSecretToken(secretID string) (*structs.ACLToken, error)
}
defer metrics.MeasureSince([]string{"nomad", "acl", "resolveSecretToken"}, time.Now())
if secretID == "" {
return structs.AnonymousACLToken, nil
}
if !helper.IsUUID(secretID) {
return nil, structs.ErrTokenInvalid
}
snap, err := s.fsm.State().Snapshot()
if err != nil {
return nil, err
}
// Lookup the ACL Token
var token *structs.ACLToken
// Handle anonymous requests
if secretID == "" {
token = structs.AnonymousACLToken
} else {
token, err = snap.ACLTokenBySecretID(nil, secretID)
if err != nil {
return nil, err
}
if token == nil {
return nil, structs.ErrTokenNotFound
}
if token.IsExpired(time.Now().UTC()) {
return nil, structs.ErrTokenExpired
}
token, err := snap.ACLTokenBySecretID(nil, secretID)
if err != nil {
return nil, err
}
if token == nil {
return nil, structs.ErrTokenNotFound
}
if token.IsExpired(time.Now().UTC()) {
return nil, structs.ErrTokenExpired
}
return token, nil

View File

@ -1964,3 +1964,25 @@ func (a *ACL) GetAuthMethods(
}},
)
}
// WhoAmI is a RPC for debugging authentication. This endpoint returns the same
// AuthenticatedIdentity that will be used by RPC handlers.
//
// TODO: At some point we might want to give this an equivalent HTTP endpoint
// once other Workload Identity work is solidified
func (a *ACL) WhoAmI(args *structs.GenericRequest, reply *structs.ACLWhoAmIResponse) error {
identity, err := a.srv.Authenticate(a.ctx, args.AuthToken)
if err != nil {
return err
}
args.SetIdentity(identity)
if done, err := a.srv.forward("ACL.WhoAmI", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"nomad", "acl", "whoami"}, time.Now())
reply.Identity = args.GetIdentity()
return nil
}

View File

@ -1,20 +1,302 @@
package nomad
import (
"path"
"testing"
"time"
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
"github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/nomad/structs/config"
"github.com/hashicorp/nomad/testutil"
"github.com/shoenig/test"
"github.com/shoenig/test/must"
"github.com/stretchr/testify/require"
)
func TestAuthenticate_mTLS(t *testing.T) {
ci.Parallel(t)
// Set up a cluster with mTLS and ACLs
dir := t.TempDir()
tlsCfg := &config.TLSConfig{
EnableHTTP: true,
EnableRPC: true,
VerifyServerHostname: true,
CAFile: "../helper/tlsutil/testdata/ca.pem",
CertFile: "../helper/tlsutil/testdata/nomad-foo.pem",
KeyFile: "../helper/tlsutil/testdata/nomad-foo-key.pem",
}
clientTLSCfg := tlsCfg.Copy()
clientTLSCfg.CertFile = "../helper/tlsutil/testdata/nomad-foo-client.pem"
clientTLSCfg.KeyFile = "../helper/tlsutil/testdata/nomad-foo-client-key.pem"
setCfg := func(name string, bootstrapExpect int) func(*Config) {
return func(c *Config) {
c.Region = "regionFoo"
c.AuthoritativeRegion = "regionFoo"
c.ACLEnabled = true
c.BootstrapExpect = bootstrapExpect
c.NumSchedulers = 0
c.DevMode = false
c.DataDir = path.Join(dir, name)
c.TLSConfig = tlsCfg
}
}
leader, cleanupLeader := TestServer(t, setCfg("node1", 1))
defer cleanupLeader()
testutil.WaitForLeader(t, leader.RPC)
follower, cleanupFollower := TestServer(t, setCfg("node2", 0))
defer cleanupFollower()
TestJoin(t, leader, follower)
testutil.WaitForLeader(t, leader.RPC)
testutil.Wait(t, func() (bool, error) {
keyset, err := follower.encrypter.activeKeySet()
return keyset != nil, err
})
rootToken := uuid.Generate()
var bootstrapResp *structs.ACLTokenUpsertResponse
codec := rpcClientWithTLS(t, follower, tlsCfg)
must.NoError(t, msgpackrpc.CallWithCodec(codec,
"ACL.Bootstrap", &structs.ACLTokenBootstrapRequest{
BootstrapSecret: rootToken,
WriteRequest: structs.WriteRequest{Region: "regionFoo"},
}, &bootstrapResp))
must.NotNil(t, bootstrapResp)
must.Len(t, 1, bootstrapResp.Tokens)
rootAccessor := bootstrapResp.Tokens[0].AccessorID
// create some ACL tokens directly into raft so we can bypass RPC validation
// around expiration times
token1 := mock.ACLToken()
token2 := mock.ACLToken()
expireTime := time.Now().Add(time.Second * -10)
token2.ExpirationTime = &expireTime
_, _, err := leader.raftApply(structs.ACLTokenUpsertRequestType,
&structs.ACLTokenUpsertRequest{Tokens: []*structs.ACLToken{token1, token2}})
must.NoError(t, err)
// create a node so we can test client RPCs
node := mock.Node()
nodeRegisterReq := &structs.NodeRegisterRequest{
Node: node,
WriteRequest: structs.WriteRequest{Region: "regionFoo"},
}
var nodeRegisterResp structs.NodeUpdateResponse
must.NoError(t, msgpackrpc.CallWithCodec(codec,
"Node.Register", nodeRegisterReq, &nodeRegisterResp))
must.NotNil(t, bootstrapResp)
// create some allocations so we can test WorkloadIdentity claims. we'll
// create directly into raft so we can bypass RPC validation and the whole
// eval, plan, etc. workflow.
job := mock.Job()
_, _, err = leader.raftApply(structs.JobRegisterRequestType,
&structs.JobRegisterRequest{Job: job})
must.NoError(t, err)
alloc1 := mock.Alloc()
alloc1.NodeID = node.ID
alloc1.ClientStatus = structs.AllocClientStatusFailed
alloc1.Job = job
alloc1.JobID = job.ID
alloc2 := mock.Alloc()
alloc2.NodeID = node.ID
alloc2.Job = job
alloc2.JobID = job.ID
alloc2.ClientStatus = structs.AllocClientStatusRunning
claims1 := alloc1.ToTaskIdentityClaims(nil, "web")
claims1Token, _, err := leader.encrypter.SignClaims(claims1)
must.NoError(t, err, must.Sprint("could not sign claims"))
claims2 := alloc2.ToTaskIdentityClaims(nil, "web")
claims2Token, _, err := leader.encrypter.SignClaims(claims2)
must.NoError(t, err, must.Sprint("could not sign claims"))
planReq := &structs.ApplyPlanResultsRequest{
AllocUpdateRequest: structs.AllocUpdateRequest{
Alloc: []*structs.Allocation{alloc1, alloc2},
Job: job,
},
}
_, _, err = leader.raftApply(structs.ApplyPlanResultsRequestType, planReq)
must.NoError(t, err)
testutil.WaitForResult(func() (bool, error) {
store := follower.fsm.State()
alloc, err := store.AllocByID(nil, alloc1.ID)
return alloc != nil, err
}, func(err error) {
t.Fatalf("alloc was not replicated via raft: %v", err) // should never happen
})
testCases := []struct {
name string
tlsCfg *config.TLSConfig
stale bool
testToken string
expectAccessor string
expectClientID string
expectAllocID string
expectTLSName string
expectIP string
expectErr string
sendFromPeer *Server
}{
{
name: "root token",
tlsCfg: clientTLSCfg, // TODO: this is a mixed use cert
testToken: rootToken,
expectAccessor: rootAccessor,
},
{
name: "from peer to leader without token", // ex. Eval.Dequeue
tlsCfg: tlsCfg,
expectTLSName: "regionFoo.nomad",
expectAccessor: "anonymous",
expectIP: follower.GetConfig().RPCAddr.IP.String(),
sendFromPeer: follower,
},
{
// note: this test is somewhat bogus because under test all the
// servers share the same IP address with the RPC client
name: "anonymous forwarded from peer to leader",
tlsCfg: tlsCfg,
expectAccessor: "anonymous",
expectTLSName: "regionFoo.nomad",
expectIP: "127.0.0.1",
},
{
name: "invalid token",
tlsCfg: clientTLSCfg,
testToken: uuid.Generate(),
expectTLSName: "regionFoo.nomad",
expectIP: follower.GetConfig().RPCAddr.IP.String(),
},
{
name: "expired token",
tlsCfg: clientTLSCfg,
testToken: uuid.Generate(),
expectTLSName: "regionFoo.nomad",
expectIP: follower.GetConfig().RPCAddr.IP.String(),
},
{
name: "from peer to leader with leader ACL", // ex. core job GC
tlsCfg: tlsCfg,
testToken: leader.getLeaderAcl(),
expectTLSName: "regionFoo.nomad",
expectAccessor: "leader",
expectIP: follower.GetConfig().RPCAddr.IP.String(),
sendFromPeer: follower,
},
{
name: "from client", // ex. Node.GetAllocs
tlsCfg: clientTLSCfg,
testToken: node.SecretID,
expectClientID: node.ID,
},
{
name: "from failed workload", // ex. Variables.List
tlsCfg: clientTLSCfg,
testToken: claims1Token,
expectErr: "rpc error: allocation is terminal",
},
{
name: "from running workload", // ex. Variables.List
tlsCfg: clientTLSCfg,
testToken: claims2Token,
expectAllocID: alloc2.ID,
},
{
name: "valid user token",
tlsCfg: clientTLSCfg,
testToken: token1.SecretID,
expectAccessor: token1.AccessorID,
},
{
name: "expired user token",
tlsCfg: clientTLSCfg,
testToken: token2.SecretID,
expectErr: "rpc error: ACL token expired",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := &structs.GenericRequest{
QueryOptions: structs.QueryOptions{
Region: "regionFoo",
AllowStale: tc.stale,
AuthToken: tc.testToken,
},
}
var resp structs.ACLWhoAmIResponse
var err error
if tc.sendFromPeer != nil {
aclEndpoint := NewACLEndpoint(tc.sendFromPeer, nil)
err = aclEndpoint.WhoAmI(req, &resp)
} else {
err = msgpackrpc.CallWithCodec(codec, "ACL.WhoAmI", req, &resp)
}
if tc.expectErr != "" {
must.EqError(t, err, tc.expectErr)
return
}
must.NoError(t, err)
must.NotNil(t, resp)
must.NotNil(t, resp.Identity)
if tc.expectAccessor != "" {
must.NotNil(t, resp.Identity.ACLToken, must.Sprint("expected ACL token"))
test.Eq(t, tc.expectAccessor, resp.Identity.ACLToken.AccessorID,
must.Sprint("expected ACL token accessor ID"))
}
test.Eq(t, tc.expectClientID, resp.Identity.ClientID,
must.Sprint("expected client ID"))
if tc.expectAllocID != "" {
must.NotNil(t, resp.Identity.Claims, must.Sprint("expected claims"))
test.Eq(t, tc.expectAllocID, resp.Identity.Claims.AllocationID,
must.Sprint("expected workload identity"))
}
test.Eq(t, tc.expectTLSName, resp.Identity.TLSName, must.Sprint("expected TLS name"))
if tc.expectIP == "" {
test.Nil(t, resp.Identity.RemoteIP, must.Sprint("expected no remote IP"))
} else {
test.Eq(t, tc.expectIP, resp.Identity.RemoteIP.String())
}
})
}
}
func TestResolveACLToken(t *testing.T) {
ci.Parallel(t)

View File

@ -25,6 +25,13 @@ func NewNamespaceEndpoint(srv *Server, ctx *RPCContext) *Namespace {
// UpsertNamespaces is used to upsert a set of namespaces
func (n *Namespace) UpsertNamespaces(args *structs.NamespaceUpsertRequest,
reply *structs.GenericResponse) error {
identity, err := n.srv.Authenticate(n.ctx, args.AuthToken)
if err != nil {
return err
}
args.SetIdentity(identity)
args.Region = n.srv.config.AuthoritativeRegion
if done, err := n.srv.forward("Namespace.UpsertNamespaces", args, args, reply); done {
return err
@ -32,7 +39,7 @@ func (n *Namespace) UpsertNamespaces(args *structs.NamespaceUpsertRequest,
defer metrics.MeasureSince([]string{"nomad", "namespace", "upsert_namespaces"}, time.Now())
// Check management permissions
if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := n.srv.ResolveACL(args.GetIdentity().GetACLToken()); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied

View File

@ -31,6 +31,7 @@ import (
"github.com/hashicorp/nomad/testutil"
"github.com/hashicorp/raft"
"github.com/hashicorp/yamux"
"github.com/shoenig/test/must"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -38,6 +39,7 @@ import (
// rpcClient is a test helper method to return a ClientCodec to use to make rpc
// calls to the passed server.
func rpcClient(t *testing.T, s *Server) rpc.ClientCodec {
t.Helper()
addr := s.config.RPCAddr
conn, err := net.DialTimeout("tcp", addr.String(), time.Second)
if err != nil {
@ -48,6 +50,36 @@ func rpcClient(t *testing.T, s *Server) rpc.ClientCodec {
return pool.NewClientCodec(conn)
}
// rpcClientWithTLS is a test helper method to return a ClientCodec to use to
// make RPC calls to the passed server via mTLS
func rpcClientWithTLS(t *testing.T, srv *Server, cfg *config.TLSConfig) rpc.ClientCodec {
t.Helper()
// configure TLS, ignoring client-side validation
tlsConf, err := tlsutil.NewTLSConfiguration(cfg, true, true)
must.NoError(t, err)
outTLSConf, err := tlsConf.OutgoingTLSConfig()
must.NoError(t, err)
outTLSConf.InsecureSkipVerify = true
// make the TCP connection
conn, err := net.DialTimeout("tcp", srv.config.RPCAddr.String(), time.Second)
// write the TLS byte to set the mode
_, err = conn.Write([]byte{byte(pool.RpcTLS)})
must.NoError(t, err)
// connect w/ TLS
tlsConn := tls.Client(conn, outTLSConf)
must.NoError(t, tlsConn.Handshake())
// write the Nomad RPC byte to set the mode
_, err = tlsConn.Write([]byte{byte(pool.RpcNomad)})
must.NoError(t, err)
return pool.NewClientCodec(tlsConn)
}
func TestRPC_forwardLeader(t *testing.T) {
ci.Parallel(t)

View File

@ -843,3 +843,8 @@ type ACLAuthMethodDeleteRequest struct {
type ACLAuthMethodDeleteResponse struct {
WriteMeta
}
type ACLWhoAmIResponse struct {
Identity *AuthenticatedIdentity
QueryMeta
}

View File

@ -13,6 +13,7 @@ const (
errNoRegionPath = "No path to region"
errTokenNotFound = "ACL token not found"
errTokenExpired = "ACL token expired"
errTokenInvalid = "ACL token is invalid" // not a UUID
errPermissionDenied = "Permission denied"
errJobRegistrationDisabled = "Job registration, dispatch, and scale are disabled by the scheduler configuration"
errNoNodeConn = "No path to node"
@ -50,6 +51,7 @@ var (
ErrNoRegionPath = errors.New(errNoRegionPath)
ErrTokenNotFound = errors.New(errTokenNotFound)
ErrTokenExpired = errors.New(errTokenExpired)
ErrTokenInvalid = errors.New(errTokenInvalid)
ErrPermissionDenied = errors.New(errPermissionDenied)
ErrJobRegistrationDisabled = errors.New(errJobRegistrationDisabled)
ErrNoNodeConn = errors.New(errNoNodeConn)

View File

@ -293,6 +293,8 @@ type QueryOptions struct {
// Reverse is used to reverse the default order of list results.
Reverse bool
identity *AuthenticatedIdentity
InternalRpcInfo
}
@ -339,6 +341,14 @@ func (q QueryOptions) AllowStaleRead() bool {
return q.AllowStale
}
func (q *QueryOptions) SetIdentity(identity *AuthenticatedIdentity) {
q.identity = identity
}
func (q QueryOptions) GetIdentity() *AuthenticatedIdentity {
return q.identity
}
// AgentPprofRequest is used to request a pprof report for a given node.
type AgentPprofRequest struct {
// ReqType specifies the profile to use
@ -399,6 +409,8 @@ type WriteRequest struct {
// IdempotencyToken can be used to ensure the write is idempotent.
IdempotencyToken string
identity *AuthenticatedIdentity
InternalRpcInfo
}
@ -435,6 +447,41 @@ func (w WriteRequest) AllowStaleRead() bool {
return false
}
func (w *WriteRequest) SetIdentity(identity *AuthenticatedIdentity) {
w.identity = identity
}
func (w WriteRequest) GetIdentity() *AuthenticatedIdentity {
return w.identity
}
// AuthenticatedIdentity is returned by the Authenticate method on server to
// return a wrapper around the various elements that can be resolved as an
// identity. RPC handlers will use the relevant fields for performing
// authorization.
type AuthenticatedIdentity struct {
ACLToken *ACLToken
Claims *IdentityClaims
ClientID string
ServerID string
TLSName string
RemoteIP net.IP
}
func (ai *AuthenticatedIdentity) GetACLToken() *ACLToken {
if ai == nil {
return nil
}
return ai.ACLToken
}
func (ai *AuthenticatedIdentity) GetClaims() *IdentityClaims {
if ai == nil {
return nil
}
return ai.Claims
}
// QueryMeta allows a query response to include potentially
// useful metadata about a query
type QueryMeta struct {
@ -12086,6 +12133,14 @@ var (
Policies: []string{"anonymous"},
Global: false,
}
// LeaderACLToken is used to represent a leader's own token; this object
// never gets used except on the leader
LeaderACLToken = &ACLToken{
AccessorID: "leader",
Name: "Leader Token",
Type: ACLManagementToken,
}
)
type ACLTokenListStub struct {