WI: allow workloads to use RPCs associated with HTTP API (#15870)

This changeset allows Workload Identities to authenticate to all the RPCs that
support HTTP API endpoints, for use with PR #15864.

* Extends the work done for pre-forwarding authentication to all RPCs that
  support a HTTP API endpoint.
* Consolidates the auth helpers used by the CSI, Service Registration, and Node
  endpoints that are currently used to support both tokens and client secrets.

Intentionally excluded from this changeset:
* The Variables endpoint still has custom handling because of the implicit
  policies. Ideally we'll figure out an efficient way to resolve those into real
  policies and then we can get rid of that custom handling.
* The RPCs that don't currently support auth tokens (i.e. those that don't
  support HTTP endpoints) have not been updated with the new pre-forwarding auth
  We'll be doing this under a separate PR to support RPC rate metrics.
This commit is contained in:
Tim Gross 2023-01-25 14:33:06 -05:00 committed by GitHub
parent 57f8ebfa26
commit f3f64af821
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 811 additions and 324 deletions

3
.changelog/15870.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
identity: Allow workloads to use RPCs associated with HTTP API
```

View File

@ -68,8 +68,16 @@ rules:
authErr := $A.$B.Authenticate($A.ctx, args)
...
if authErr != nil {
return authErr
return $C
}
...
- pattern-not-inside: |
authErr := $A.$B.Authenticate(nil, args)
...
if authErr != nil {
return $C
}
...
- metavariable-pattern:
metavariable: $METHOD
patterns:

View File

@ -156,7 +156,33 @@ func (s *Server) remoteIPFromRPCContext(ctx *RPCContext) (net.IP, error) {
return nil, structs.ErrPermissionDenied
}
func (s *Server) ResolveACL(aclToken *structs.ACLToken) (*acl.ACL, error) {
// ResolveACL is an authentication wrapper which handles resolving both ACL
// tokens and Workload Identities. If both are provided the ACL token is
// preferred, but it is best for the RPC caller to only include the credentials
// for the identity they intend the operation to be performed with.
func (s *Server) ResolveACL(args structs.RequestWithIdentity) (*acl.ACL, error) {
identity := args.GetIdentity()
if !s.config.ACLEnabled || identity == nil {
return nil, nil
}
aclToken := identity.GetACLToken()
if aclToken != nil {
return s.ResolveACLForToken(aclToken)
}
claims := identity.GetClaims()
if claims != nil {
return s.ResolveClaims(claims)
}
return nil, nil
}
// ResolveACLForToken resolves an ACL from a token only. It should be used only
// by Variables endpoints, which have additional implicit policies for their
// claims so we can't wrap them up in ResolveACL.
//
// TODO: figure out a way to the Variables endpoint implicit policies baked into
// their acl.ACL object so that we can avoid using this method.
func (s *Server) ResolveACLForToken(aclToken *structs.ACLToken) (*acl.ACL, error) {
if !s.config.ACLEnabled {
return nil, nil
}
@ -167,6 +193,22 @@ func (s *Server) ResolveACL(aclToken *structs.ACLToken) (*acl.ACL, error) {
return resolveACLFromToken(snap, s.aclCache, aclToken)
}
// ResolveClientOrACL resolves an ACL if the identity has a token or claim, and
// falls back to verifying the client ID if one has been set
func (s *Server) ResolveClientOrACL(args structs.RequestWithIdentity) (*acl.ACL, error) {
identity := args.GetIdentity()
if !s.config.ACLEnabled || identity == nil || identity.ClientID != "" {
return nil, nil
}
aclObj, err := s.ResolveACL(args)
if err != nil {
return nil, err
}
// Returns either the users aclObj, or nil if ACLs are disabled.
return aclObj, nil
}
// 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) {

View File

@ -73,15 +73,19 @@ func (a *ACL) UpsertPolicies(args *structs.ACLPolicyUpsertRequest, reply *struct
if !a.srv.config.ACLEnabled {
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
args.Region = a.srv.config.AuthoritativeRegion
if done, err := a.srv.forward("ACL.UpsertPolicies", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "upsert_policies"}, time.Now())
// Check management level permissions
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -117,15 +121,19 @@ func (a *ACL) DeletePolicies(args *structs.ACLPolicyDeleteRequest, reply *struct
if !a.srv.config.ACLEnabled {
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
args.Region = a.srv.config.AuthoritativeRegion
if done, err := a.srv.forward("ACL.DeletePolicies", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "delete_policies"}, time.Now())
// Check management level permissions
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -152,14 +160,17 @@ func (a *ACL) ListPolicies(args *structs.ACLPolicyListRequest, reply *structs.AC
if !a.srv.config.ACLEnabled {
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward("ACL.ListPolicies", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "list_policies"}, time.Now())
// Check management level permissions
acl, err := a.srv.ResolveToken(args.AuthToken)
acl, err := a.srv.ResolveACL(args)
if err != nil {
return err
} else if acl == nil {
@ -242,13 +253,17 @@ func (a *ACL) GetPolicy(args *structs.ACLPolicySpecificRequest, reply *structs.S
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward("ACL.GetPolicy", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "get_policy"}, time.Now())
// Check management level permissions
acl, err := a.srv.ResolveToken(args.AuthToken)
acl, err := a.srv.ResolveACL(args)
if err != nil {
return err
} else if acl == nil {
@ -513,7 +528,7 @@ func (a *ACL) UpsertTokens(args *structs.ACLTokenUpsertRequest, reply *structs.A
if !a.srv.config.ACLEnabled {
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
// Validate non-zero set of tokens
if len(args.Tokens) == 0 {
return structs.NewErrRPCCoded(http.StatusBadRequest, "must specify as least one token")
@ -545,10 +560,13 @@ func (a *ACL) UpsertTokens(args *structs.ACLTokenUpsertRequest, reply *structs.A
if done, err := a.srv.forward(structs.ACLUpsertTokensRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "upsert_tokens"}, time.Now())
// Check management level permissions
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -674,7 +692,7 @@ func (a *ACL) DeleteTokens(args *structs.ACLTokenDeleteRequest, reply *structs.G
if !a.srv.config.ACLEnabled {
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
// Validate non-zero set of tokens
if len(args.AccessorIDs) == 0 {
return structs.NewErrRPCCoded(400, "must specify as least one token")
@ -683,10 +701,13 @@ func (a *ACL) DeleteTokens(args *structs.ACLTokenDeleteRequest, reply *structs.G
if done, err := a.srv.forward("ACL.DeleteTokens", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "delete_tokens"}, time.Now())
// Check management level permissions
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -753,13 +774,17 @@ func (a *ACL) ListTokens(args *structs.ACLTokenListRequest, reply *structs.ACLTo
if !a.srv.config.ACLEnabled {
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward("ACL.ListTokens", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "list_tokens"}, time.Now())
// Check management level permissions
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -837,12 +862,16 @@ func (a *ACL) GetToken(args *structs.ACLTokenSpecificRequest, reply *structs.Sin
if !a.srv.config.ACLEnabled {
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward("ACL.GetToken", args, args, reply); done {
return err
}
if authErr != nil {
return authErr
}
defer metrics.MeasureSince([]string{"nomad", "acl", "get_token"}, time.Now())
acl, err := a.srv.ResolveToken(args.AuthToken)
acl, err := a.srv.ResolveACL(args)
if err != nil {
return err
}
@ -898,13 +927,17 @@ func (a *ACL) GetTokens(args *structs.ACLTokenSetRequest, reply *structs.ACLToke
if !a.srv.config.ACLEnabled {
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward("ACL.GetTokens", args, args, reply); done {
return err
}
if authErr != nil {
return authErr
}
defer metrics.MeasureSince([]string{"nomad", "acl", "get_tokens"}, time.Now())
// Check management level permissions
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -1134,7 +1167,7 @@ func (a *ACL) UpsertRoles(
if !a.srv.config.ACLEnabled {
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
// This endpoint always forwards to the authoritative region as ACL roles
// are global.
args.Region = a.srv.config.AuthoritativeRegion
@ -1142,6 +1175,9 @@ func (a *ACL) UpsertRoles(
if done, err := a.srv.forward(structs.ACLUpsertRolesRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "upsert_roles"}, time.Now())
// ACL roles can only be used once all servers, in all federated regions
@ -1151,8 +1187,8 @@ func (a *ACL) UpsertRoles(
minACLRoleVersion)
}
// Only tokens with management level permissions can create ACL roles.
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
// Only management level permissions can create ACL roles.
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -1278,6 +1314,8 @@ func (a *ACL) DeleteRolesByID(
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
// This endpoint always forwards to the authoritative region as ACL roles
// are global.
args.Region = a.srv.config.AuthoritativeRegion
@ -1285,6 +1323,9 @@ func (a *ACL) DeleteRolesByID(
if done, err := a.srv.forward(structs.ACLDeleteRolesByIDRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "delete_roles"}, time.Now())
// ACL roles can only be used once all servers, in all federated regions
@ -1294,8 +1335,8 @@ func (a *ACL) DeleteRolesByID(
minACLRoleVersion)
}
// Only tokens with management level permissions can create ACL roles.
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
// Only management level permissions can create ACL roles.
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -1330,13 +1371,17 @@ func (a *ACL) ListRoles(
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward(structs.ACLListRolesRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "list_roles"}, time.Now())
// Resolve the token and ensure it has some form of permissions.
acl, err := a.srv.ResolveToken(args.AuthToken)
acl, err := a.srv.ResolveACL(args)
if err != nil {
return err
} else if acl == nil {
@ -1481,13 +1526,17 @@ func (a *ACL) GetRoleByID(
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward(structs.ACLGetRoleByIDRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "get_role_id"}, time.Now())
// Resolve the token and ensure it has some form of permissions.
acl, err := a.srv.ResolveToken(args.AuthToken)
acl, err := a.srv.ResolveACL(args)
if err != nil {
return err
} else if acl == nil {
@ -1565,14 +1614,17 @@ func (a *ACL) GetRoleByName(
if !a.srv.config.ACLEnabled {
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward(structs.ACLGetRoleByNameRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "get_role_name"}, time.Now())
// Resolve the token and ensure it has some form of permissions.
acl, err := a.srv.ResolveToken(args.AuthToken)
acl, err := a.srv.ResolveACL(args)
if err != nil {
return err
} else if acl == nil {
@ -1706,11 +1758,15 @@ func (a *ACL) UpsertAuthMethods(
if !a.srv.config.ACLEnabled {
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
args.Region = a.srv.config.AuthoritativeRegion
if done, err := a.srv.forward(structs.ACLUpsertAuthMethodsRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "upsert_auth_methods"}, time.Now())
// ACL auth methods can only be used once all servers in all federated
@ -1721,7 +1777,7 @@ func (a *ACL) UpsertAuthMethods(
}
// Check management level permissions
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -1810,10 +1866,14 @@ func (a *ACL) DeleteAuthMethods(
}
args.Region = a.srv.config.AuthoritativeRegion
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward(
structs.ACLDeleteAuthMethodsRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "delete_auth_methods_by_name"}, time.Now())
// ACL auth methods can only be used once all servers in all federated
@ -1824,7 +1884,7 @@ func (a *ACL) DeleteAuthMethods(
}
// Check management level permissions
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -1906,14 +1966,18 @@ func (a *ACL) GetAuthMethod(
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward(
structs.ACLGetAuthMethodRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "get_auth_method_name"}, time.Now())
// Resolve the token and ensure it has some form of permissions.
acl, err := a.srv.ResolveToken(args.AuthToken)
acl, err := a.srv.ResolveACL(args)
if err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
@ -2037,9 +2101,13 @@ func (a *ACL) UpsertBindingRules(
}
args.Region = a.srv.config.AuthoritativeRegion
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward(structs.ACLUpsertBindingRulesRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "upsert_binding_rules"}, time.Now())
// ACL binding rules can only be used once all servers in all federated
@ -2050,7 +2118,7 @@ func (a *ACL) UpsertBindingRules(
}
// Check management level permissions
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -2164,9 +2232,13 @@ func (a *ACL) DeleteBindingRules(
}
args.Region = a.srv.config.AuthoritativeRegion
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward(structs.ACLDeleteBindingRulesRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "delete_binding_rules"}, time.Now())
// ACL binding rules can only be used once all servers in all federated
@ -2177,7 +2249,7 @@ func (a *ACL) DeleteBindingRules(
}
// Check management level permissions.
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -2213,13 +2285,17 @@ func (a *ACL) ListBindingRules(
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward(structs.ACLListBindingRulesRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "list_binding_rules"}, time.Now())
// Check management level permissions.
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -2267,13 +2343,17 @@ func (a *ACL) GetBindingRules(
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward(structs.ACLGetBindingRulesRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "get_rules"}, time.Now())
// Check management level permissions.
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -2317,13 +2397,17 @@ func (a *ACL) GetBindingRule(
return aclDisabled
}
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward(structs.ACLGetBindingRuleRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "acl", "get_binding_rule"}, time.Now())
// Check management level permissions.
if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := a.srv.ResolveACL(args); err != nil {
return err
} else if acl == nil || !acl.IsManagement() {
return structs.ErrPermissionDenied

View File

@ -2234,7 +2234,7 @@ func TestACL_ListRoles(t *testing.T) {
}
var aclRoleResp1 structs.ACLRolesListResponse
err := msgpackrpc.CallWithCodec(codec, structs.ACLListRolesRPCMethod, aclRoleReq1, &aclRoleResp1)
require.ErrorContains(t, err, "ACL token not found")
require.ErrorContains(t, err, structs.ErrPermissionDenied.Error())
// Try listing roles with a valid ACL token.
aclRoleReq2 := &structs.ACLRolesListRequest{

View File

@ -8,7 +8,6 @@ import (
"github.com/armon/go-metrics"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-memdb"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/helper/pointer"
@ -31,15 +30,19 @@ func NewAllocEndpoint(srv *Server, ctx *RPCContext) *Alloc {
// List is used to list the allocations in the system
func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListResponse) error {
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward("Alloc.List", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "alloc", "list"}, time.Now())
namespace := args.RequestNamespace()
// Check namespace read-job permissions
aclObj, err := a.srv.ResolveToken(args.AuthToken)
aclObj, err := a.srv.ResolveACL(args)
if err != nil {
return err
}
@ -136,36 +139,22 @@ func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListRes
// GetAlloc is used to lookup a particular allocation
func (a *Alloc) GetAlloc(args *structs.AllocSpecificRequest,
reply *structs.SingleAllocResponse) error {
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward("Alloc.GetAlloc", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "alloc", "get_alloc"}, time.Now())
// Check namespace read-job permissions before performing blocking query.
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
aclObj, err := a.srv.ResolveToken(args.AuthToken)
aclObj, err := a.srv.ResolveClientOrACL(args)
if err != nil {
// If ResolveToken had an unexpected error return that
if err != structs.ErrTokenNotFound {
return err
}
// Attempt to lookup AuthToken as a Node.SecretID since nodes
// call this endpoint and don't have an ACL token.
node, stateErr := a.srv.fsm.State().NodeBySecretID(nil, args.AuthToken)
if stateErr != nil {
// Return the original ResolveToken error with this err
var merr multierror.Error
merr.Errors = append(merr.Errors, err, stateErr)
return merr.ErrorOrNil()
}
// Not a node or a valid ACL token
if node == nil {
return structs.ErrTokenNotFound
}
}
// Setup the blocking query
opts := blockingOptions{
queryOpts: &args.QueryOptions,
@ -276,9 +265,15 @@ func (a *Alloc) GetAllocs(args *structs.AllocsGetRequest,
// Stop is used to stop an allocation and migrate it to another node.
func (a *Alloc) Stop(args *structs.AllocStopRequest, reply *structs.AllocStopResponse) error {
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward("Alloc.Stop", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "alloc", "stop"}, time.Now())
alloc, err := getAlloc(a.srv.State(), args.AllocID)
@ -288,7 +283,7 @@ func (a *Alloc) Stop(args *structs.AllocStopRequest, reply *structs.AllocStopRes
// Check for namespace alloc-lifecycle permissions.
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityAllocLifecycle)
aclObj, err := a.srv.ResolveToken(args.AuthToken)
aclObj, err := a.srv.ResolveACL(args)
if err != nil {
return err
} else if !allowNsOp(aclObj, alloc.Namespace) {
@ -335,13 +330,18 @@ func (a *Alloc) Stop(args *structs.AllocStopRequest, reply *structs.AllocStopRes
// UpdateDesiredTransition is used to update the desired transitions of an
// allocation.
func (a *Alloc) UpdateDesiredTransition(args *structs.AllocUpdateDesiredTransitionRequest, reply *structs.GenericResponse) error {
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward("Alloc.UpdateDesiredTransition", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "alloc", "update_desired_transition"}, time.Now())
// Check that it is a management token.
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := a.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
@ -370,14 +370,19 @@ func (a *Alloc) GetServiceRegistrations(
args *structs.AllocServiceRegistrationsRequest,
reply *structs.AllocServiceRegistrationsResponse) error {
authErr := a.srv.Authenticate(a.ctx, args)
if done, err := a.srv.forward(structs.AllocServiceRegistrationsRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "alloc", "get_service_registrations"}, time.Now())
// If ACLs are enabled, ensure the caller has the read-job namespace
// capability.
aclObj, err := a.srv.ResolveToken(args.AuthToken)
aclObj, err := a.srv.ResolveACL(args)
if err != nil {
return err
} else if aclObj != nil {

View File

@ -1279,7 +1279,7 @@ func TestAllocEndpoint_List_AllNamespaces_ACL_OSS(t *testing.T) {
Namespace: "*",
Token: uuid.Generate(),
Error: true,
Message: structs.ErrTokenNotFound.Error(),
Message: structs.ErrPermissionDenied.Error(),
},
{
Label: "all namespaces with insufficient token",
@ -1311,7 +1311,7 @@ func TestAllocEndpoint_List_AllNamespaces_ACL_OSS(t *testing.T) {
Namespace: ns1.Name,
Token: uuid.Generate(),
Error: true,
Message: structs.ErrTokenNotFound.Error(),
Message: structs.ErrPermissionDenied.Error(),
},
{
Label: "bad namespace with root token",

View File

@ -35,8 +35,13 @@ func (a *Agent) register() {
}
func (a *Agent) Profile(args *structs.AgentPprofRequest, reply *structs.AgentPprofResponse) error {
authErr := a.srv.Authenticate(nil, args)
if authErr != nil {
return structs.ErrPermissionDenied
}
// Check ACL for agent write
aclObj, err := a.srv.ResolveToken(args.AuthToken)
aclObj, err := a.srv.ResolveACL(args)
if err != nil {
return err
} else if aclObj != nil && !aclObj.AllowAgentWrite() {
@ -128,9 +133,14 @@ func (a *Agent) monitor(conn io.ReadWriteCloser) {
handleStreamResultError(err, pointer.Of(int64(500)), encoder)
return
}
authErr := a.srv.Authenticate(nil, &args)
if authErr != nil {
handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
return
}
// Check agent read permissions
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := a.srv.ResolveACL(&args); err != nil {
handleStreamResultError(err, nil, encoder)
return
} else if aclObj != nil && !aclObj.AllowAgentRead() {
@ -398,8 +408,11 @@ func (a *Agent) forwardProfileClient(args *structs.AgentPprofRequest, reply *str
// Host returns data about the agent's host system for the `debug` command.
func (a *Agent) Host(args *structs.HostDataRequest, reply *structs.HostDataResponse) error {
aclObj, err := a.srv.ResolveToken(args.AuthToken)
authErr := a.srv.Authenticate(nil, args)
if authErr != nil {
return structs.ErrPermissionDenied
}
aclObj, err := a.srv.ResolveACL(args)
if err != nil {
return err
}

View File

@ -39,14 +39,19 @@ func (a *ClientAllocations) GarbageCollectAll(args *structs.NodeSpecificRequest,
// in the forwarding chain.
args.QueryOptions.AllowStale = true
authErr := a.srv.Authenticate(nil, args)
// Potentially forward to a different region.
if done, err := a.srv.forward("ClientAllocations.GarbageCollectAll", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client_allocations", "garbage_collect_all"}, time.Now())
// Check node read permissions
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := a.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNodeWrite() {
return structs.ErrPermissionDenied
@ -85,10 +90,15 @@ func (a *ClientAllocations) Signal(args *structs.AllocSignalRequest, reply *stru
// in the forwarding chain.
args.QueryOptions.AllowStale = true
authErr := a.srv.Authenticate(nil, args)
// Potentially forward to a different region.
if done, err := a.srv.forward("ClientAllocations.Signal", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client_allocations", "signal"}, time.Now())
// Verify the arguments.
@ -108,7 +118,7 @@ func (a *ClientAllocations) Signal(args *structs.AllocSignalRequest, reply *stru
}
// Check namespace alloc-lifecycle permission.
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := a.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
return structs.ErrPermissionDenied
@ -137,10 +147,15 @@ func (a *ClientAllocations) GarbageCollect(args *structs.AllocSpecificRequest, r
// in the forwarding chain.
args.QueryOptions.AllowStale = true
authErr := a.srv.Authenticate(nil, args)
// Potentially forward to a different region.
if done, err := a.srv.forward("ClientAllocations.GarbageCollect", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client_allocations", "garbage_collect"}, time.Now())
// Verify the arguments.
@ -160,7 +175,7 @@ func (a *ClientAllocations) GarbageCollect(args *structs.AllocSpecificRequest, r
}
// Check namespace submit-job permission.
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := a.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied
@ -189,10 +204,15 @@ func (a *ClientAllocations) Restart(args *structs.AllocRestartRequest, reply *st
// in the forwarding chain.
args.QueryOptions.AllowStale = true
authErr := a.srv.Authenticate(nil, args)
// Potentially forward to a different region.
if done, err := a.srv.forward("ClientAllocations.Restart", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client_allocations", "restart"}, time.Now())
// Find the allocation
@ -207,7 +227,7 @@ func (a *ClientAllocations) Restart(args *structs.AllocRestartRequest, reply *st
}
// Check for namespace alloc-lifecycle permissions.
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := a.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
return structs.ErrPermissionDenied
@ -236,10 +256,15 @@ func (a *ClientAllocations) Stats(args *cstructs.AllocStatsRequest, reply *cstru
// in the forwarding chain.
args.QueryOptions.AllowStale = true
authErr := a.srv.Authenticate(nil, args)
// Potentially forward to a different region.
if done, err := a.srv.forward("ClientAllocations.Stats", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client_allocations", "stats"}, time.Now())
// Find the allocation
@ -254,7 +279,7 @@ func (a *ClientAllocations) Stats(args *cstructs.AllocStatsRequest, reply *cstru
}
// Check for namespace read-job permissions.
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := a.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -287,10 +312,15 @@ func (a *ClientAllocations) Checks(args *cstructs.AllocChecksRequest, reply *cst
// hop in the forwarding chain.
args.QueryOptions.AllowStale = true
authErr := a.srv.Authenticate(nil, args)
// Potentially forward to a different region.
if done, err := a.srv.forward("ClientAllocations.Checks", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client_allocations", "checks"}, time.Now())
// Grab the state snapshot, as we need this to perform lookups for a number
@ -308,7 +338,7 @@ func (a *ClientAllocations) Checks(args *cstructs.AllocChecksRequest, reply *cst
}
// Check for namespace read-job permissions.
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := a.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -344,12 +374,18 @@ func (a *ClientAllocations) exec(conn io.ReadWriteCloser) {
return
}
authErr := a.srv.Authenticate(nil, &args)
// Check if we need to forward to a different region
if r := args.RequestRegion(); r != a.srv.Region() {
forwardRegionStreamingRpc(a.srv, conn, encoder, &args, "Allocations.Exec",
args.AllocID, &args.QueryOptions)
return
}
if authErr != nil {
handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
return
}
// Verify the arguments.
if args.AllocID == "" {
@ -375,7 +411,7 @@ func (a *ClientAllocations) exec(conn io.ReadWriteCloser) {
}
// Check node read permissions
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := a.srv.ResolveACL(&args); err != nil {
handleStreamResultError(err, nil, encoder)
return
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocExec) {

View File

@ -106,10 +106,15 @@ func (f *FileSystem) List(args *cstructs.FsListRequest, reply *cstructs.FsListRe
// in the forwarding chain.
args.QueryOptions.AllowStale = true
authErr := f.srv.Authenticate(nil, args)
// Potentially forward to a different region.
if done, err := f.srv.forward("FileSystem.List", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "file_system", "list"}, time.Now())
// Verify the arguments.
@ -130,7 +135,7 @@ func (f *FileSystem) List(args *cstructs.FsListRequest, reply *cstructs.FsListRe
// Check namespace filesystem read permissions
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadFS)
aclObj, err := f.srv.ResolveToken(args.AuthToken)
aclObj, err := f.srv.ResolveACL(args)
if err != nil {
return err
} else if !allowNsOp(aclObj, alloc.Namespace) {
@ -160,10 +165,15 @@ func (f *FileSystem) Stat(args *cstructs.FsStatRequest, reply *cstructs.FsStatRe
// in the forwarding chain.
args.QueryOptions.AllowStale = true
authErr := f.srv.Authenticate(nil, args)
// Potentially forward to a different region.
if done, err := f.srv.forward("FileSystem.Stat", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "file_system", "stat"}, time.Now())
// Verify the arguments.
@ -183,7 +193,7 @@ func (f *FileSystem) Stat(args *cstructs.FsStatRequest, reply *cstructs.FsStatRe
}
// Check filesystem read permissions
if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := f.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadFS) {
return structs.ErrPermissionDenied
@ -221,12 +231,18 @@ func (f *FileSystem) stream(conn io.ReadWriteCloser) {
return
}
authErr := f.srv.Authenticate(nil, &args)
// Check if we need to forward to a different region
if r := args.RequestRegion(); r != f.srv.Region() {
forwardRegionStreamingRpc(f.srv, conn, encoder, &args, "FileSystem.Stream",
args.AllocID, &args.QueryOptions)
return
}
if authErr != nil {
handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
return
}
// Verify the arguments.
if args.AllocID == "" {
@ -252,7 +268,7 @@ func (f *FileSystem) stream(conn io.ReadWriteCloser) {
}
// Check namespace read-fs permissions.
if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := f.srv.ResolveACL(&args); err != nil {
handleStreamResultError(err, nil, encoder)
return
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadFS) {
@ -339,12 +355,18 @@ func (f *FileSystem) logs(conn io.ReadWriteCloser) {
return
}
authErr := f.srv.Authenticate(nil, &args)
// Check if we need to forward to a different region
if r := args.RequestRegion(); r != f.srv.Region() {
forwardRegionStreamingRpc(f.srv, conn, encoder, &args, "FileSystem.Logs",
args.AllocID, &args.QueryOptions)
return
}
if authErr != nil {
handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
return
}
// Verify the arguments.
if args.AllocID == "" {
@ -372,7 +394,7 @@ func (f *FileSystem) logs(conn io.ReadWriteCloser) {
// Check namespace read-logs *or* read-fs permissions.
allowNsOp := acl.NamespaceValidator(
acl.NamespaceCapabilityReadFS, acl.NamespaceCapabilityReadLogs)
aclObj, err := f.srv.ResolveToken(args.AuthToken)
aclObj, err := f.srv.ResolveACL(&args)
if err != nil {
handleStreamResultError(err, nil, encoder)
return

View File

@ -23,19 +23,24 @@ func NewClientStatsEndpoint(srv *Server) *ClientStats {
}
func (s *ClientStats) Stats(args *nstructs.NodeSpecificRequest, reply *structs.ClientStatsResponse) error {
// We only allow stale reads since the only potentially stale information is
// the Node registration and the cost is fairly high for adding another hope
// in the forwarding chain.
args.QueryOptions.AllowStale = true
authErr := s.srv.Authenticate(nil, args)
// Potentially forward to a different region.
if done, err := s.srv.forward("ClientStats.Stats", args, args, reply); done {
return err
}
if authErr != nil {
return nstructs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client_stats", "stats"}, time.Now())
// Check node read permissions
if aclObj, err := s.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := s.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNodeRead() {
return nstructs.ErrPermissionDenied

View File

@ -29,54 +29,6 @@ func NewCSIVolumeEndpoint(srv *Server, ctx *RPCContext) *CSIVolume {
return &CSIVolume{srv: srv, ctx: ctx, logger: srv.logger.Named("csi_volume")}
}
// QueryACLObj looks up the ACL token in the request and returns the acl.ACL object
// - fallback to node secret ids
func (s *Server) QueryACLObj(args *structs.QueryOptions, allowNodeAccess bool) (*acl.ACL, error) {
// Lookup the token
aclObj, err := s.ResolveToken(args.AuthToken)
if err != nil {
// If ResolveToken had an unexpected error return that
if !structs.IsErrTokenNotFound(err) {
return nil, err
}
// If we don't allow access to this endpoint from Nodes, then return token
// not found.
if !allowNodeAccess {
return nil, structs.ErrTokenNotFound
}
ws := memdb.NewWatchSet()
// Attempt to lookup AuthToken as a Node.SecretID since nodes may call
// call this endpoint and don't have an ACL token.
node, stateErr := s.fsm.State().NodeBySecretID(ws, args.AuthToken)
if stateErr != nil {
// Return the original ResolveToken error with this err
var merr multierror.Error
merr.Errors = append(merr.Errors, err, stateErr)
return nil, merr.ErrorOrNil()
}
// We did not find a Node for this ID, so return Token Not Found.
if node == nil {
return nil, structs.ErrTokenNotFound
}
}
// Return either the users aclObj, or nil if ACLs are disabled.
return aclObj, nil
}
// WriteACLObj calls QueryACLObj for a WriteRequest
func (s *Server) WriteACLObj(args *structs.WriteRequest, allowNodeAccess bool) (*acl.ACL, error) {
opts := &structs.QueryOptions{
Region: args.RequestRegion(),
Namespace: args.RequestNamespace(),
AuthToken: args.AuthToken,
}
return s.QueryACLObj(opts, allowNodeAccess)
}
const (
csiVolumeTable = "csi_volumes"
csiPluginTable = "csi_plugins"
@ -99,15 +51,20 @@ func (s *Server) replySetIndex(table string, reply *structs.QueryMeta) error {
// List replies with CSIVolumes, filtered by ACL access
func (v *CSIVolume) List(args *structs.CSIVolumeListRequest, reply *structs.CSIVolumeListResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.List", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIListVolume,
acl.NamespaceCapabilityCSIReadVolume,
acl.NamespaceCapabilityCSIMountVolume,
acl.NamespaceCapabilityListJobs)
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false)
aclObj, err := v.srv.ResolveACL(args)
if err != nil {
return err
}
@ -211,14 +168,19 @@ func (v *CSIVolume) List(args *structs.CSIVolumeListRequest, reply *structs.CSIV
// Get fetches detailed information about a specific volume
func (v *CSIVolume) Get(args *structs.CSIVolumeGetRequest, reply *structs.CSIVolumeGetResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.Get", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
allowCSIAccess := acl.NamespaceValidator(acl.NamespaceCapabilityCSIReadVolume,
acl.NamespaceCapabilityCSIMountVolume,
acl.NamespaceCapabilityReadJob)
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, true)
aclObj, err := v.srv.ResolveClientOrACL(args)
if err != nil {
return err
}
@ -309,12 +271,17 @@ func (v *CSIVolume) controllerValidateVolume(req *structs.CSIVolumeRegisterReque
// again with the right settings. This lets us be as strict with
// validation here as the CreateVolume CSI RPC is expected to be.
func (v *CSIVolume) Register(args *structs.CSIVolumeRegisterRequest, reply *structs.CSIVolumeRegisterResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.Register", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume)
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false)
aclObj, err := v.srv.ResolveACL(args)
if err != nil {
return err
}
@ -400,12 +367,17 @@ func (v *CSIVolume) Register(args *structs.CSIVolumeRegisterRequest, reply *stru
// Deregister removes a set of volumes
func (v *CSIVolume) Deregister(args *structs.CSIVolumeDeregisterRequest, reply *structs.CSIVolumeDeregisterResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.Deregister", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume)
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false)
aclObj, err := v.srv.ResolveACL(args)
if err != nil {
return err
}
@ -437,12 +409,17 @@ func (v *CSIVolume) Deregister(args *structs.CSIVolumeDeregisterRequest, reply *
// Claim submits a change to a volume claim
func (v *CSIVolume) Claim(args *structs.CSIVolumeClaimRequest, reply *structs.CSIVolumeClaimResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.Claim", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIMountVolume)
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, true)
aclObj, err := v.srv.ResolveClientOrACL(args)
if err != nil {
return err
}
@ -619,14 +596,19 @@ func allowCSIMount(aclObj *acl.ACL, namespace string) bool {
// ControllerUnpublish RPCs to the client. It handles errors according to the
// current claim state.
func (v *CSIVolume) Unpublish(args *structs.CSIVolumeUnpublishRequest, reply *structs.CSIVolumeUnpublishResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.Unpublish", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "volume", "unpublish"}, time.Now())
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIMountVolume)
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, true)
aclObj, err := v.srv.ResolveClientOrACL(args)
if err != nil {
return err
}
@ -957,14 +939,17 @@ func (v *CSIVolume) checkpointClaim(vol *structs.CSIVolume, claim *structs.CSIVo
func (v *CSIVolume) Create(args *structs.CSIVolumeCreateRequest, reply *structs.CSIVolumeCreateResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.Create", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "volume", "create"}, time.Now())
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume)
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false)
aclObj, err := v.srv.ResolveACL(args)
if err != nil {
return err
}
@ -1081,14 +1066,18 @@ func (v *CSIVolume) createVolume(vol *structs.CSIVolume, plugin *structs.CSIPlug
}
func (v *CSIVolume) Delete(args *structs.CSIVolumeDeleteRequest, reply *structs.CSIVolumeDeleteResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.Delete", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "volume", "delete"}, time.Now())
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume)
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false)
aclObj, err := v.srv.ResolveACL(args)
if err != nil {
return err
}
@ -1161,16 +1150,20 @@ func (v *CSIVolume) deleteVolume(vol *structs.CSIVolume, plugin *structs.CSIPlug
func (v *CSIVolume) ListExternal(args *structs.CSIVolumeExternalListRequest, reply *structs.CSIVolumeExternalListResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.ListExternal", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "volume", "list_external"}, time.Now())
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIListVolume,
acl.NamespaceCapabilityCSIReadVolume,
acl.NamespaceCapabilityCSIMountVolume,
acl.NamespaceCapabilityListJobs)
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false)
aclObj, err := v.srv.ResolveACL(args)
if err != nil {
return err
}
@ -1221,13 +1214,17 @@ func (v *CSIVolume) ListExternal(args *structs.CSIVolumeExternalListRequest, rep
func (v *CSIVolume) CreateSnapshot(args *structs.CSISnapshotCreateRequest, reply *structs.CSISnapshotCreateResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.CreateSnapshot", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "volume", "create_snapshot"}, time.Now())
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume)
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false)
aclObj, err := v.srv.ResolveACL(args)
if err != nil {
return err
}
@ -1312,13 +1309,17 @@ func (v *CSIVolume) CreateSnapshot(args *structs.CSISnapshotCreateRequest, reply
func (v *CSIVolume) DeleteSnapshot(args *structs.CSISnapshotDeleteRequest, reply *structs.CSISnapshotDeleteResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.DeleteSnapshot", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "volume", "delete_snapshot"}, time.Now())
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume)
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false)
aclObj, err := v.srv.ResolveACL(args)
if err != nil {
return err
}
@ -1372,16 +1373,20 @@ func (v *CSIVolume) DeleteSnapshot(args *structs.CSISnapshotDeleteRequest, reply
func (v *CSIVolume) ListSnapshots(args *structs.CSISnapshotListRequest, reply *structs.CSISnapshotListResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIVolume.ListSnapshots", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "volume", "list_snapshots"}, time.Now())
allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIListVolume,
acl.NamespaceCapabilityCSIReadVolume,
acl.NamespaceCapabilityCSIMountVolume,
acl.NamespaceCapabilityListJobs)
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false)
aclObj, err := v.srv.ResolveACL(args)
if err != nil {
return err
}
@ -1444,21 +1449,24 @@ func NewCSIPluginEndpoint(srv *Server, ctx *RPCContext) *CSIPlugin {
// List replies with CSIPlugins, filtered by ACL access
func (v *CSIPlugin) List(args *structs.CSIPluginListRequest, reply *structs.CSIPluginListResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIPlugin.List", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "plugin", "list"}, time.Now())
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false)
aclObj, err := v.srv.ResolveACL(args)
if err != nil {
return err
}
if !aclObj.AllowPluginList() {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "plugin", "list"}, time.Now())
opts := blockingOptions{
queryOpts: &args.QueryOptions,
queryMeta: &reply.QueryMeta,
@ -1499,15 +1507,20 @@ func (v *CSIPlugin) List(args *structs.CSIPluginListRequest, reply *structs.CSIP
// Get fetches detailed information about a specific plugin
func (v *CSIPlugin) Get(args *structs.CSIPluginGetRequest, reply *structs.CSIPluginGetResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIPlugin.Get", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "plugin", "get"}, time.Now())
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false)
aclObj, err := v.srv.ResolveACL(args)
if err != nil {
return err
}
if !aclObj.AllowPluginRead() {
return structs.ErrPermissionDenied
}
@ -1515,8 +1528,6 @@ func (v *CSIPlugin) Get(args *structs.CSIPluginGetRequest, reply *structs.CSIPlu
withAllocs := aclObj == nil ||
aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob)
defer metrics.MeasureSince([]string{"nomad", "plugin", "get"}, time.Now())
if args.ID == "" {
return fmt.Errorf("missing plugin ID")
}
@ -1564,19 +1575,23 @@ func (v *CSIPlugin) Get(args *structs.CSIPluginGetRequest, reply *structs.CSIPlu
// Delete deletes a plugin if it is unused
func (v *CSIPlugin) Delete(args *structs.CSIPluginDeleteRequest, reply *structs.CSIPluginDeleteResponse) error {
authErr := v.srv.Authenticate(v.ctx, args)
if done, err := v.srv.forward("CSIPlugin.Delete", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "plugin", "delete"}, time.Now())
// Check that it is a management token.
if aclObj, err := v.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := v.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "plugin", "delete"}, time.Now())
if args.ID == "" {
return fmt.Errorf("missing plugin ID")
}

View File

@ -42,7 +42,7 @@ func (d *Deployment) GetDeployment(args *structs.DeploymentSpecificRequest,
// Check namespace read-job permissions
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
aclObj, err := d.srv.ResolveACL(args.GetIdentity().GetACLToken())
aclObj, err := d.srv.ResolveACL(args)
if err != nil {
return err
} else if !allowNsOp(aclObj, args.RequestNamespace()) {
@ -93,9 +93,14 @@ func (d *Deployment) GetDeployment(args *structs.DeploymentSpecificRequest,
// Fail is used to force fail a deployment
func (d *Deployment) Fail(args *structs.DeploymentFailRequest, reply *structs.DeploymentUpdateResponse) error {
authErr := d.srv.Authenticate(d.ctx, args)
if done, err := d.srv.forward("Deployment.Fail", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "deployment", "fail"}, time.Now())
// Validate the arguments
@ -119,7 +124,7 @@ func (d *Deployment) Fail(args *structs.DeploymentFailRequest, reply *structs.De
}
// Check namespace submit-job permissions
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := d.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied
@ -135,9 +140,13 @@ func (d *Deployment) Fail(args *structs.DeploymentFailRequest, reply *structs.De
// Pause is used to pause a deployment
func (d *Deployment) Pause(args *structs.DeploymentPauseRequest, reply *structs.DeploymentUpdateResponse) error {
authErr := d.srv.Authenticate(d.ctx, args)
if done, err := d.srv.forward("Deployment.Pause", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "deployment", "pause"}, time.Now())
// Validate the arguments
@ -161,7 +170,7 @@ func (d *Deployment) Pause(args *structs.DeploymentPauseRequest, reply *structs.
}
// Check namespace submit-job permissions
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := d.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied
@ -181,9 +190,13 @@ func (d *Deployment) Pause(args *structs.DeploymentPauseRequest, reply *structs.
// Promote is used to promote canaries in a deployment
func (d *Deployment) Promote(args *structs.DeploymentPromoteRequest, reply *structs.DeploymentUpdateResponse) error {
authErr := d.srv.Authenticate(d.ctx, args)
if done, err := d.srv.forward("Deployment.Promote", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "deployment", "promote"}, time.Now())
// Validate the arguments
@ -207,7 +220,7 @@ func (d *Deployment) Promote(args *structs.DeploymentPromoteRequest, reply *stru
}
// Check namespace submit-job permissions
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := d.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied
@ -223,9 +236,13 @@ func (d *Deployment) Promote(args *structs.DeploymentPromoteRequest, reply *stru
// Run is used to start a pending deployment
func (d *Deployment) Run(args *structs.DeploymentRunRequest, reply *structs.DeploymentUpdateResponse) error {
authErr := d.srv.Authenticate(d.ctx, args)
if done, err := d.srv.forward("Deployment.Run", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "deployment", "run"}, time.Now())
// Validate the arguments
@ -249,7 +266,7 @@ func (d *Deployment) Run(args *structs.DeploymentRunRequest, reply *structs.Depl
}
// Check namespace submit-job permissions
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := d.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied
@ -265,9 +282,13 @@ func (d *Deployment) Run(args *structs.DeploymentRunRequest, reply *structs.Depl
// Unblock is used to unblock a deployment
func (d *Deployment) Unblock(args *structs.DeploymentUnblockRequest, reply *structs.DeploymentUpdateResponse) error {
authErr := d.srv.Authenticate(d.ctx, args)
if done, err := d.srv.forward("Deployment.Unblock", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "deployment", "unblock"}, time.Now())
// Validate the arguments
@ -291,7 +312,7 @@ func (d *Deployment) Unblock(args *structs.DeploymentUnblockRequest, reply *stru
}
// Check namespace submit-job permissions
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := d.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied
@ -307,9 +328,13 @@ func (d *Deployment) Unblock(args *structs.DeploymentUnblockRequest, reply *stru
// Cancel is used to cancel a deployment
func (d *Deployment) Cancel(args *structs.DeploymentCancelRequest, reply *structs.DeploymentUpdateResponse) error {
authErr := d.srv.Authenticate(d.ctx, args)
if done, err := d.srv.forward("Deployment.Cancel", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "deployment", "cancel"}, time.Now())
// Validate the arguments
@ -333,7 +358,7 @@ func (d *Deployment) Cancel(args *structs.DeploymentCancelRequest, reply *struct
}
// Check namespace submit-job permissions
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := d.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied
@ -350,9 +375,13 @@ func (d *Deployment) Cancel(args *structs.DeploymentCancelRequest, reply *struct
// SetAllocHealth is used to set the health of allocations that are part of the
// deployment.
func (d *Deployment) SetAllocHealth(args *structs.DeploymentAllocHealthRequest, reply *structs.DeploymentUpdateResponse) error {
authErr := d.srv.Authenticate(d.ctx, args)
if done, err := d.srv.forward("Deployment.SetAllocHealth", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "deployment", "set_alloc_health"}, time.Now())
// Validate the arguments
@ -380,7 +409,7 @@ func (d *Deployment) SetAllocHealth(args *structs.DeploymentAllocHealthRequest,
}
// Check namespace submit-job permissions
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := d.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied
@ -396,16 +425,20 @@ func (d *Deployment) SetAllocHealth(args *structs.DeploymentAllocHealthRequest,
// List returns the list of deployments in the system
func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.DeploymentListResponse) error {
authErr := d.srv.Authenticate(d.ctx, args)
if done, err := d.srv.forward("Deployment.List", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "deployment", "list"}, time.Now())
namespace := args.RequestNamespace()
// Check namespace read-job permissions against request namespace since
// results are filtered by request namespace.
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := d.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -483,16 +516,20 @@ func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.De
// Allocations returns the list of allocations that are a part of the deployment
func (d *Deployment) Allocations(args *structs.DeploymentSpecificRequest, reply *structs.AllocListResponse) error {
authErr := d.srv.Authenticate(d.ctx, args)
if done, err := d.srv.forward("Deployment.Allocations", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "deployment", "allocations"}, time.Now())
// Check namespace read-job permissions against the request namespace.
// Must re-check against the alloc namespace when they return to ensure
// there's no namespace mismatch.
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
aclObj, err := d.srv.ResolveToken(args.AuthToken)
aclObj, err := d.srv.ResolveACL(args)
if err != nil {
return err
} else if !allowNsOp(aclObj, args.RequestNamespace()) {

View File

@ -41,14 +41,19 @@ func NewEvalEndpoint(srv *Server, ctx *RPCContext) *Eval {
// GetEval is used to request information about a specific evaluation
func (e *Eval) GetEval(args *structs.EvalSpecificRequest,
reply *structs.SingleEvalResponse) error {
authErr := e.srv.Authenticate(e.ctx, args)
if done, err := e.srv.forward("Eval.GetEval", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "eval", "get_eval"}, time.Now())
// Check for read-job permissions before performing blocking query.
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
aclObj, err := e.srv.ResolveToken(args.AuthToken)
aclObj, err := e.srv.ResolveACL(args)
if err != nil {
return err
} else if !allowNsOp(aclObj, args.RequestNamespace()) {
@ -112,9 +117,6 @@ func (e *Eval) Dequeue(args *structs.EvalDequeueRequest,
reply *structs.EvalDequeueResponse) error {
authErr := e.srv.Authenticate(e.ctx, args)
if authErr != nil {
return authErr
}
// Ensure the connection was initiated by another server if TLS is used.
err := validateTLSCertificateLevel(e.srv, e.ctx, tlsCertificateLevelServer)
@ -125,6 +127,10 @@ func (e *Eval) Dequeue(args *structs.EvalDequeueRequest,
if done, err := e.srv.forward("Eval.Dequeue", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "eval", "dequeue"}, time.Now())
// Ensure there is at least one scheduler
@ -443,14 +449,18 @@ func (e *Eval) Delete(
args *structs.EvalDeleteRequest,
reply *structs.EvalDeleteResponse) error {
authErr := e.srv.Authenticate(e.ctx, args)
if done, err := e.srv.forward(structs.EvalDeleteRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "eval", "delete"}, time.Now())
// This RPC endpoint is very destructive and alters Nomad's core state,
// meaning only those with management tokens can call it.
if aclObj, err := e.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := e.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
@ -629,15 +639,20 @@ func (e *Eval) deleteEvalsByFilter(args *structs.EvalDeleteRequest) (int, uint64
// List is used to get a list of the evaluations in the system
func (e *Eval) List(args *structs.EvalListRequest, reply *structs.EvalListResponse) error {
authErr := e.srv.Authenticate(e.ctx, args)
if done, err := e.srv.forward("Eval.List", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "eval", "list"}, time.Now())
namespace := args.RequestNamespace()
// Check for read-job permissions
aclObj, err := e.srv.ResolveToken(args.AuthToken)
aclObj, err := e.srv.ResolveACL(args)
if err != nil {
return err
}
@ -748,14 +763,19 @@ func (e *Eval) List(args *structs.EvalListRequest, reply *structs.EvalListRespon
// Count is used to get a list of the evaluations in the system
func (e *Eval) Count(args *structs.EvalCountRequest, reply *structs.EvalCountResponse) error {
authErr := e.srv.Authenticate(e.ctx, args)
if done, err := e.srv.forward("Eval.Count", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "eval", "count"}, time.Now())
namespace := args.RequestNamespace()
// Check for read-job permissions
aclObj, err := e.srv.ResolveToken(args.AuthToken)
aclObj, err := e.srv.ResolveACL(args)
if err != nil {
return err
}
@ -847,14 +867,19 @@ func (e *Eval) Count(args *structs.EvalCountRequest, reply *structs.EvalCountRes
// Allocations is used to list the allocations for an evaluation
func (e *Eval) Allocations(args *structs.EvalSpecificRequest,
reply *structs.EvalAllocationsResponse) error {
authErr := e.srv.Authenticate(e.ctx, args)
if done, err := e.srv.forward("Eval.Allocations", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "eval", "allocations"}, time.Now())
// Check for read-job permissions
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
aclObj, err := e.srv.ResolveToken(args.AuthToken)
aclObj, err := e.srv.ResolveACL(args)
if err != nil {
return err
} else if !allowNsOp(aclObj, args.RequestNamespace()) {

View File

@ -37,6 +37,8 @@ func (e *Event) stream(conn io.ReadWriteCloser) {
return
}
authErr := e.srv.Authenticate(nil, &args)
// forward to appropriate region
if args.Region != e.srv.config.Region {
err := e.forwardStreamingRPC(args.Region, "Event.Stream", args, conn)
@ -46,6 +48,10 @@ func (e *Event) stream(conn io.ReadWriteCloser) {
return
}
if authErr != nil {
handleJsonResultError(structs.ErrPermissionDenied, pointer.Of(int64(403)), encoder)
}
// Generate the subscription request
subReq := &stream.SubscribeRequest{
Token: args.AuthToken,

View File

@ -84,9 +84,13 @@ func NewJobEndpoints(s *Server, ctx *RPCContext) *Job {
// Register is used to upsert a job for scheduling
func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegisterResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Register", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "register"}, time.Now())
// Validate the arguments
@ -120,7 +124,7 @@ func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegis
reply.Warnings = structs.MergeMultierrorWarnings(warnings...)
// Check job submission permissions
aclObj, err := j.srv.ResolveToken(args.AuthToken)
aclObj, err := j.srv.ResolveACL(args)
if err != nil {
return err
} else if aclObj != nil {
@ -483,13 +487,17 @@ func getSignalConstraint(signals []string) *structs.Constraint {
// Summary retrieves the summary of a job.
func (j *Job) Summary(args *structs.JobSummaryRequest, reply *structs.JobSummaryResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Summary", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job_summary", "get_job_summary"}, time.Now())
// Check for read-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -531,9 +539,13 @@ func (j *Job) Summary(args *structs.JobSummaryRequest, reply *structs.JobSummary
// Must forward to the leader, because only the leader will have a live Vault
// client with which to validate vault tokens.
func (j *Job) Validate(args *structs.JobValidateRequest, reply *structs.JobValidateResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Validate", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "validate"}, time.Now())
// defensive check; http layer and RPC requester should ensure namespaces are set consistently
@ -548,7 +560,7 @@ func (j *Job) Validate(args *structs.JobValidateRequest, reply *structs.JobValid
args.Job = job
// Check for read-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -578,13 +590,17 @@ func (j *Job) Validate(args *structs.JobValidateRequest, reply *structs.JobValid
// Revert is used to revert the job to a prior version
func (j *Job) Revert(args *structs.JobRevertRequest, reply *structs.JobRegisterResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Revert", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "revert"}, time.Now())
// Check for submit-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied
@ -646,13 +662,17 @@ func (j *Job) Revert(args *structs.JobRevertRequest, reply *structs.JobRegisterR
// Stable is used to mark the job version as stable
func (j *Job) Stable(args *structs.JobStabilityRequest, reply *structs.JobStabilityResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Stable", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "stable"}, time.Now())
// Check for read-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied
@ -692,13 +712,17 @@ func (j *Job) Stable(args *structs.JobStabilityRequest, reply *structs.JobStabil
// Evaluate is used to force a job for re-evaluation
func (j *Job) Evaluate(args *structs.JobEvaluateRequest, reply *structs.JobRegisterResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Evaluate", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "evaluate"}, time.Now())
// Check for read-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -788,13 +812,17 @@ func (j *Job) Evaluate(args *structs.JobEvaluateRequest, reply *structs.JobRegis
// Deregister is used to remove a job the cluster.
func (j *Job) Deregister(args *structs.JobDeregisterRequest, reply *structs.JobDeregisterResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Deregister", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "deregister"}, time.Now())
// Check for submit-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied
@ -901,13 +929,17 @@ func (j *Job) Deregister(args *structs.JobDeregisterRequest, reply *structs.JobD
// BatchDeregister is used to remove a set of jobs from the cluster.
func (j *Job) BatchDeregister(args *structs.JobBatchDeregisterRequest, reply *structs.JobBatchDeregisterResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.BatchDeregister", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "batch_deregister"}, time.Now())
// Resolve the ACL token
aclObj, err := j.srv.ResolveToken(args.AuthToken)
aclObj, err := j.srv.ResolveACL(args)
if err != nil {
return err
}
@ -986,15 +1018,19 @@ func (j *Job) BatchDeregister(args *structs.JobBatchDeregisterRequest, reply *st
// Scale is used to modify one of the scaling targets in the job
func (j *Job) Scale(args *structs.JobScaleRequest, reply *structs.JobRegisterResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Scale", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "scale"}, time.Now())
namespace := args.RequestNamespace()
// Authorize request
aclObj, err := j.srv.ResolveToken(args.AuthToken)
aclObj, err := j.srv.ResolveACL(args)
if err != nil {
return err
}
@ -1168,13 +1204,17 @@ func (j *Job) Scale(args *structs.JobScaleRequest, reply *structs.JobRegisterRes
// GetJob is used to request information about a specific job
func (j *Job) GetJob(args *structs.JobSpecificRequest,
reply *structs.SingleJobResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.GetJob", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "get_job"}, time.Now())
// Check for read-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -1214,13 +1254,17 @@ func (j *Job) GetJob(args *structs.JobSpecificRequest,
// GetJobVersions is used to retrieve all tracked versions of a job.
func (j *Job) GetJobVersions(args *structs.JobVersionsRequest,
reply *structs.JobVersionsResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.GetJobVersions", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "get_job_versions"}, time.Now())
// Check for read-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -1316,15 +1360,19 @@ func registrationsAreAllowed(aclObj *acl.ACL, state *state.StateStore) (bool, er
// List is used to list the jobs registered in the system
func (j *Job) List(args *structs.JobListRequest, reply *structs.JobListResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.List", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "list"}, time.Now())
namespace := args.RequestNamespace()
// Check for list-job permissions
aclObj, err := j.srv.ResolveToken(args.AuthToken)
aclObj, err := j.srv.ResolveACL(args)
if err != nil {
return err
}
@ -1422,13 +1470,17 @@ func (j *Job) List(args *structs.JobListRequest, reply *structs.JobListResponse)
// Allocations is used to list the allocations for a job
func (j *Job) Allocations(args *structs.JobSpecificRequest,
reply *structs.JobAllocationsResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Allocations", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "allocations"}, time.Now())
// Check for read-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -1477,13 +1529,17 @@ func (j *Job) Allocations(args *structs.JobSpecificRequest,
// Evaluations is used to list the evaluations for a job
func (j *Job) Evaluations(args *structs.JobSpecificRequest,
reply *structs.JobEvaluationsResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Evaluations", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "evaluations"}, time.Now())
// Check for read-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -1519,13 +1575,17 @@ func (j *Job) Evaluations(args *structs.JobSpecificRequest,
// Deployments is used to list the deployments for a job
func (j *Job) Deployments(args *structs.JobSpecificRequest,
reply *structs.DeploymentListResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Deployments", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "deployments"}, time.Now())
// Check for read-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -1561,13 +1621,17 @@ func (j *Job) Deployments(args *structs.JobSpecificRequest,
// LatestDeployment is used to retrieve the latest deployment for a job
func (j *Job) LatestDeployment(args *structs.JobSpecificRequest,
reply *structs.SingleDeploymentResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.LatestDeployment", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "latest_deployment"}, time.Now())
// Check for read-job permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
@ -1608,9 +1672,13 @@ func (j *Job) LatestDeployment(args *structs.JobSpecificRequest,
// Plan is used to cause a dry-run evaluation of the Job and return the results
// with a potential diff containing annotations.
func (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Plan", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "plan"}, time.Now())
// Validate the arguments
@ -1629,7 +1697,7 @@ func (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse)
reply.Warnings = structs.MergeMultierrorWarnings(warnings...)
// Check job submission permissions, which we assume is the same for plan
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil {
if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
@ -1823,13 +1891,17 @@ func validateJobUpdate(old, new *structs.Job) error {
// Dispatch a parameterized job.
func (j *Job) Dispatch(args *structs.JobDispatchRequest, reply *structs.JobDispatchResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.Dispatch", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "dispatch"}, time.Now())
// Check for submit-job permissions
aclObj, err := j.srv.ResolveToken(args.AuthToken)
aclObj, err := j.srv.ResolveACL(args)
if err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityDispatchJob) {
@ -2058,13 +2130,17 @@ func validateDispatchRequest(req *structs.JobDispatchRequest, job *structs.Job)
func (j *Job) ScaleStatus(args *structs.JobScaleStatusRequest,
reply *structs.JobScaleStatusResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward("Job.ScaleStatus", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "scale_status"}, time.Now())
// Check for autoscaler permissions
if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := j.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil {
hasReadJob := aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob)
@ -2173,14 +2249,18 @@ func (j *Job) GetServiceRegistrations(
args *structs.JobServiceRegistrationsRequest,
reply *structs.JobServiceRegistrationsResponse) error {
authErr := j.srv.Authenticate(j.ctx, args)
if done, err := j.srv.forward(structs.JobServiceRegistrationsRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "job", "get_service_registrations"}, time.Now())
// If ACLs are enabled, ensure the caller has the read-job namespace
// capability.
aclObj, err := j.srv.ResolveToken(args.AuthToken)
aclObj, err := j.srv.ResolveACL(args)
if err != nil {
return err
} else if aclObj != nil {

View File

@ -27,13 +27,17 @@ func NewKeyringEndpoint(srv *Server, ctx *RPCContext, enc *Encrypter) *Keyring {
}
func (k *Keyring) Rotate(args *structs.KeyringRotateRootKeyRequest, reply *structs.KeyringRotateRootKeyResponse) error {
authErr := k.srv.Authenticate(k.ctx, args)
if done, err := k.srv.forward("Keyring.Rotate", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "keyring", "rotate"}, time.Now())
if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := k.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
@ -95,9 +99,14 @@ func (k *Keyring) Rotate(args *structs.KeyringRotateRootKeyRequest, reply *struc
}
func (k *Keyring) List(args *structs.KeyringListRootKeyMetaRequest, reply *structs.KeyringListRootKeyMetaResponse) error {
authErr := k.srv.Authenticate(k.ctx, args)
if done, err := k.srv.forward("Keyring.List", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "keyring", "list"}, time.Now())
@ -106,7 +115,7 @@ func (k *Keyring) List(args *structs.KeyringListRootKeyMetaRequest, reply *struc
// replication
err := validateTLSCertificateLevel(k.srv, k.ctx, tlsCertificateLevelServer)
if err != nil {
if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := k.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
@ -148,13 +157,18 @@ func (k *Keyring) List(args *structs.KeyringListRootKeyMetaRequest, reply *struc
// Update updates an existing key in the keyring, including both the
// key material and metadata.
func (k *Keyring) Update(args *structs.KeyringUpdateRootKeyRequest, reply *structs.KeyringUpdateRootKeyResponse) error {
authErr := k.srv.Authenticate(k.ctx, args)
if done, err := k.srv.forward("Keyring.Update", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "keyring", "update"}, time.Now())
if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := k.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
@ -290,13 +304,18 @@ func (k *Keyring) Get(args *structs.KeyringGetRootKeyRequest, reply *structs.Key
}
func (k *Keyring) Delete(args *structs.KeyringDeleteRootKeyRequest, reply *structs.KeyringDeleteRootKeyResponse) error {
authErr := k.srv.Authenticate(k.ctx, args)
if done, err := k.srv.forward("Keyring.Delete", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "keyring", "delete"}, time.Now())
if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := k.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied

View File

@ -32,13 +32,13 @@ func (n *Namespace) UpsertNamespaces(args *structs.NamespaceUpsertRequest,
return err
}
if authErr != nil {
return authErr
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "namespace", "upsert_namespaces"}, time.Now())
// Check management permissions
if aclObj, err := n.srv.ResolveACL(args.GetIdentity().GetACLToken()); err != nil {
if aclObj, err := n.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
@ -76,14 +76,19 @@ func (n *Namespace) UpsertNamespaces(args *structs.NamespaceUpsertRequest,
// DeleteNamespaces is used to delete a namespace
func (n *Namespace) DeleteNamespaces(args *structs.NamespaceDeleteRequest, reply *structs.GenericResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
args.Region = n.srv.config.AuthoritativeRegion
if done, err := n.srv.forward("Namespace.DeleteNamespaces", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "namespace", "delete_namespaces"}, time.Now())
// Check management permissions
if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := n.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
@ -225,13 +230,18 @@ func (n *Namespace) namespaceTerminalInRegion(authToken, namespace, region strin
// ListNamespaces is used to list the namespaces
func (n *Namespace) ListNamespaces(args *structs.NamespaceListRequest, reply *structs.NamespaceListResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
if done, err := n.srv.forward("Namespace.ListNamespaces", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "namespace", "list_namespace"}, time.Now())
// Resolve token to acl to filter namespace list
aclObj, err := n.srv.ResolveToken(args.AuthToken)
aclObj, err := n.srv.ResolveACL(args)
if err != nil {
return err
}
@ -286,13 +296,18 @@ func (n *Namespace) ListNamespaces(args *structs.NamespaceListRequest, reply *st
// GetNamespace is used to get a specific namespace
func (n *Namespace) GetNamespace(args *structs.NamespaceSpecificRequest, reply *structs.SingleNamespaceResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
if done, err := n.srv.forward("Namespace.GetNamespace", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "namespace", "get_namespace"}, time.Now())
// Check capabilities for the given namespace permissions
if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := n.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNamespace(args.Name) {
return structs.ErrPermissionDenied
@ -334,13 +349,18 @@ func (n *Namespace) GetNamespace(args *structs.NamespaceSpecificRequest, reply *
// GetNamespaces is used to get a set of namespaces
func (n *Namespace) GetNamespaces(args *structs.NamespaceSetRequest, reply *structs.NamespaceSetResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
if done, err := n.srv.forward("Namespace.GetNamespaces", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "namespace", "get_namespaces"}, time.Now())
// Check management permissions
if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := n.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied

View File

@ -319,9 +319,13 @@ func (n *Node) constructNodeServerInfoResponse(nodeID string, snap *state.StateS
// Deregister is used to remove a client from the cluster. If a client should
// just be made unavailable for scheduling, a status update is preferred.
func (n *Node) Deregister(args *structs.NodeDeregisterRequest, reply *structs.NodeUpdateResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
if done, err := n.srv.forward("Node.Deregister", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client", "deregister"}, time.Now())
if args.NodeID == "" {
@ -341,9 +345,13 @@ func (n *Node) Deregister(args *structs.NodeDeregisterRequest, reply *structs.No
// BatchDeregister is used to remove client nodes from the cluster.
func (n *Node) BatchDeregister(args *structs.NodeBatchDeregisterRequest, reply *structs.NodeUpdateResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
if done, err := n.srv.forward("Node.BatchDeregister", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client", "batch_deregister"}, time.Now())
if len(args.NodeIDs) == 0 {
@ -361,7 +369,7 @@ func (n *Node) deregister(args *structs.NodeBatchDeregisterRequest,
raftApplyFn func() (interface{}, uint64, error),
) error {
// Check request permissions
if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := n.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNodeWrite() {
return structs.ErrPermissionDenied
@ -614,13 +622,18 @@ func nodeStatusTransitionRequiresEval(newStatus, oldStatus string) bool {
// UpdateDrain is used to update the drain mode of a client node
func (n *Node) UpdateDrain(args *structs.NodeUpdateDrainRequest,
reply *structs.NodeDrainUpdateResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
if done, err := n.srv.forward("Node.UpdateDrain", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client", "update_drain"}, time.Now())
// Check node write permissions
if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := n.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNodeWrite() {
return structs.ErrPermissionDenied
@ -708,13 +721,18 @@ func (n *Node) UpdateDrain(args *structs.NodeUpdateDrainRequest,
// UpdateEligibility is used to update the scheduling eligibility of a node
func (n *Node) UpdateEligibility(args *structs.NodeUpdateEligibilityRequest,
reply *structs.NodeEligibilityUpdateResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
if done, err := n.srv.forward("Node.UpdateEligibility", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client", "update_eligibility"}, time.Now())
// Check node write permissions
if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := n.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNodeWrite() {
return structs.ErrPermissionDenied
@ -805,13 +823,18 @@ func (n *Node) UpdateEligibility(args *structs.NodeUpdateEligibilityRequest,
// Evaluate is used to force a re-evaluation of the node
func (n *Node) Evaluate(args *structs.NodeEvaluateRequest, reply *structs.NodeUpdateResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
if done, err := n.srv.forward("Node.Evaluate", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client", "evaluate"}, time.Now())
// Check node write permissions
if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := n.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNodeWrite() {
return structs.ErrPermissionDenied
@ -860,33 +883,22 @@ func (n *Node) Evaluate(args *structs.NodeEvaluateRequest, reply *structs.NodeUp
// GetNode is used to request information about a specific node
func (n *Node) GetNode(args *structs.NodeSpecificRequest,
reply *structs.SingleNodeResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
if done, err := n.srv.forward("Node.GetNode", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client", "get_node"}, time.Now())
// Check node read permissions
if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil {
// If ResolveToken had an unexpected error return that
if err != structs.ErrTokenNotFound {
aclObj, err := n.srv.ResolveClientOrACL(args)
if err != nil {
return err
}
// Attempt to lookup AuthToken as a Node.SecretID since nodes
// call this endpoint and don't have an ACL token.
node, stateErr := n.srv.fsm.State().NodeBySecretID(nil, args.AuthToken)
if stateErr != nil {
// Return the original ResolveToken error with this err
var merr multierror.Error
merr.Errors = append(merr.Errors, err, stateErr)
return merr.ErrorOrNil()
}
// Not a node or a valid ACL token
if node == nil {
return structs.ErrTokenNotFound
}
} else if aclObj != nil && !aclObj.AllowNodeRead() {
if aclObj != nil && !aclObj.AllowNodeRead() {
return structs.ErrPermissionDenied
}
@ -931,13 +943,18 @@ func (n *Node) GetNode(args *structs.NodeSpecificRequest,
// GetAllocs is used to request allocations for a specific node
func (n *Node) GetAllocs(args *structs.NodeSpecificRequest,
reply *structs.NodeAllocsResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
if done, err := n.srv.forward("Node.GetAllocs", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client", "get_allocs"}, time.Now())
// Check node read and namespace job read permissions
aclObj, err := n.srv.ResolveToken(args.AuthToken)
aclObj, err := n.srv.ResolveACL(args)
if err != nil {
return err
}
@ -1431,13 +1448,18 @@ func (n *Node) batchUpdate(future *structs.BatchFuture, updates []*structs.Alloc
// List is used to list the available nodes
func (n *Node) List(args *structs.NodeListRequest,
reply *structs.NodeListResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
if done, err := n.srv.forward("Node.List", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "client", "list"}, time.Now())
// Check node read permissions
if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := n.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNodeRead() {
return structs.ErrPermissionDenied

View File

@ -35,12 +35,17 @@ func (op *Operator) register() {
// RaftGetConfiguration is used to retrieve the current Raft configuration.
func (op *Operator) RaftGetConfiguration(args *structs.GenericRequest, reply *structs.RaftConfigurationResponse) error {
authErr := op.srv.Authenticate(op.ctx, args)
if done, err := op.srv.forward("Operator.RaftGetConfiguration", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
// Check management permissions
if aclObj, err := op.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := op.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
@ -96,12 +101,17 @@ func (op *Operator) RaftGetConfiguration(args *structs.GenericRequest, reply *st
// "IP:port". The reply argument is not used, but it required to fulfill the RPC
// interface.
func (op *Operator) RaftRemovePeerByAddress(args *structs.RaftPeerByAddressRequest, reply *struct{}) error {
authErr := op.srv.Authenticate(op.ctx, args)
if done, err := op.srv.forward("Operator.RaftRemovePeerByAddress", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
// Check management permissions
if aclObj, err := op.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := op.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
@ -148,12 +158,17 @@ REMOVE:
// "IP:port". The reply argument is not used, but is required to fulfill the RPC
// interface.
func (op *Operator) RaftRemovePeerByID(args *structs.RaftPeerByIDRequest, reply *struct{}) error {
authErr := op.srv.Authenticate(op.ctx, args)
if done, err := op.srv.forward("Operator.RaftRemovePeerByID", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
// Check management permissions
if aclObj, err := op.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := op.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
@ -209,12 +224,17 @@ REMOVE:
// AutopilotGetConfiguration is used to retrieve the current Autopilot configuration.
func (op *Operator) AutopilotGetConfiguration(args *structs.GenericRequest, reply *structs.AutopilotConfig) error {
authErr := op.srv.Authenticate(op.ctx, args)
if done, err := op.srv.forward("Operator.AutopilotGetConfiguration", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
// This action requires operator read access.
rule, err := op.srv.ResolveToken(args.AuthToken)
rule, err := op.srv.ResolveACL(args)
if err != nil {
return err
}
@ -238,12 +258,17 @@ func (op *Operator) AutopilotGetConfiguration(args *structs.GenericRequest, repl
// AutopilotSetConfiguration is used to set the current Autopilot configuration.
func (op *Operator) AutopilotSetConfiguration(args *structs.AutopilotSetConfigRequest, reply *bool) error {
authErr := op.srv.Authenticate(op.ctx, args)
if done, err := op.srv.forward("Operator.AutopilotSetConfiguration", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
// This action requires operator write access.
rule, err := op.srv.ResolveToken(args.AuthToken)
rule, err := op.srv.ResolveACL(args)
if err != nil {
return err
}
@ -275,15 +300,20 @@ func (op *Operator) AutopilotSetConfiguration(args *structs.AutopilotSetConfigRe
// ServerHealth is used to get the current health of the servers.
func (op *Operator) ServerHealth(args *structs.GenericRequest, reply *structs.OperatorHealthReply) error {
authErr := op.srv.Authenticate(op.ctx, args)
// This must be sent to the leader, so we fix the args since we are
// re-using a structure where we don't support all the options.
args.AllowStale = false
if done, err := op.srv.forward("Operator.ServerHealth", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
// This action requires operator read access.
rule, err := op.srv.ResolveToken(args.AuthToken)
rule, err := op.srv.ResolveACL(args)
if err != nil {
return err
}
@ -307,12 +337,17 @@ func (op *Operator) ServerHealth(args *structs.GenericRequest, reply *structs.Op
// SchedulerSetConfiguration is used to set the current Scheduler configuration.
func (op *Operator) SchedulerSetConfiguration(args *structs.SchedulerSetConfigRequest, reply *structs.SchedulerSetConfigurationResponse) error {
authErr := op.srv.Authenticate(op.ctx, args)
if done, err := op.srv.forward("Operator.SchedulerSetConfiguration", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
// This action requires operator write access.
rule, err := op.srv.ResolveToken(args.AuthToken)
rule, err := op.srv.ResolveACL(args)
if err != nil {
return err
} else if rule != nil && !rule.AllowOperatorWrite() {
@ -356,12 +391,17 @@ func (op *Operator) SchedulerSetConfiguration(args *structs.SchedulerSetConfigRe
// SchedulerGetConfiguration is used to retrieve the current Scheduler configuration.
func (op *Operator) SchedulerGetConfiguration(args *structs.GenericRequest, reply *structs.SchedulerConfigurationResponse) error {
authErr := op.srv.Authenticate(op.ctx, args)
if done, err := op.srv.forward("Operator.SchedulerGetConfiguration", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
// This action requires operator read access.
rule, err := op.srv.ResolveToken(args.AuthToken)
rule, err := op.srv.ResolveACL(args)
if err != nil {
return err
} else if rule != nil && !rule.AllowOperatorRead() {
@ -429,6 +469,8 @@ func (op *Operator) snapshotSave(conn io.ReadWriteCloser) {
return
}
authErr := op.srv.Authenticate(nil, &args)
// Forward to appropriate region
if args.Region != op.srv.Region() {
err := op.forwardStreamingRPC(args.Region, "Operator.SnapshotSave", args, conn)
@ -455,8 +497,12 @@ func (op *Operator) snapshotSave(conn io.ReadWriteCloser) {
}
}
if authErr != nil {
handleFailure(403, structs.ErrPermissionDenied)
}
// Check agent permissions
if aclObj, err := op.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := op.srv.ResolveACL(&args); err != nil {
code := 500
if err == structs.ErrTokenNotFound {
code = 400
@ -511,6 +557,8 @@ func (op *Operator) snapshotRestore(conn io.ReadWriteCloser) {
return
}
authErr := op.srv.Authenticate(nil, &args)
// Forward to appropriate region
if args.Region != op.srv.Region() {
err := op.forwardStreamingRPC(args.Region, "Operator.SnapshotRestore", args, conn)
@ -535,8 +583,12 @@ func (op *Operator) snapshotRestore(conn io.ReadWriteCloser) {
}
if authErr != nil {
handleFailure(403, structs.ErrPermissionDenied)
}
// Check agent permissions
if aclObj, err := op.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := op.srv.ResolveACL(&args); err != nil {
code := 500
if err == structs.ErrTokenNotFound {
code = 400

View File

@ -664,7 +664,7 @@ func TestOperator_SnapshotSave_ACL(t *testing.T) {
}{
{"root", root.SecretID, 0, nil},
{"no_permission_token", deniedToken.SecretID, 403, structs.ErrPermissionDenied},
{"invalid token", uuid.Generate(), 400, structs.ErrTokenNotFound},
{"invalid token", uuid.Generate(), 403, structs.ErrPermissionDenied},
{"unauthenticated", "", 403, structs.ErrPermissionDenied},
}
@ -886,7 +886,7 @@ func TestOperator_SnapshotRestore_ACL(t *testing.T) {
}{
{"root", 0, nil},
{"no_permission_token", 403, structs.ErrPermissionDenied},
{"invalid token", 400, structs.ErrTokenNotFound},
{"invalid token", 403, structs.ErrPermissionDenied},
{"unauthenticated", 403, structs.ErrPermissionDenied},
}

View File

@ -25,13 +25,18 @@ func NewPeriodicEndpoint(srv *Server, ctx *RPCContext) *Periodic {
// Force is used to force a new instance of a periodic job
func (p *Periodic) Force(args *structs.PeriodicForceRequest, reply *structs.PeriodicForceResponse) error {
authErr := p.srv.Authenticate(p.ctx, args)
if done, err := p.srv.forward("Periodic.Force", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "periodic", "force"}, time.Now())
// Check for write-job permissions
if aclObj, err := p.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := p.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityDispatchJob) && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
return structs.ErrPermissionDenied

View File

@ -28,16 +28,20 @@ func NewScalingEndpoint(srv *Server, ctx *RPCContext) *Scaling {
// ListPolicies is used to list the policies
func (p *Scaling) ListPolicies(args *structs.ScalingPolicyListRequest, reply *structs.ScalingPolicyListResponse) error {
authErr := p.srv.Authenticate(p.ctx, args)
if done, err := p.srv.forward("Scaling.ListPolicies", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "scaling", "list_policies"}, time.Now())
if args.RequestNamespace() == structs.AllNamespacesSentinel {
return p.listAllNamespaces(args, reply)
}
if aclObj, err := p.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := p.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil {
hasListScalingPolicies := aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityListScalingPolicies)
@ -95,13 +99,17 @@ func (p *Scaling) ListPolicies(args *structs.ScalingPolicyListRequest, reply *st
func (p *Scaling) GetPolicy(args *structs.ScalingPolicySpecificRequest,
reply *structs.SingleScalingPolicyResponse) error {
authErr := p.srv.Authenticate(p.ctx, args)
if done, err := p.srv.forward("Scaling.GetPolicy", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "scaling", "get_policy"}, time.Now())
// Check for list-job permissions
if aclObj, err := p.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := p.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil {
hasReadScalingPolicy := aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadScalingPolicy)
@ -144,7 +152,7 @@ func (p *Scaling) GetPolicy(args *structs.ScalingPolicySpecificRequest,
func (p *Scaling) listAllNamespaces(args *structs.ScalingPolicyListRequest, reply *structs.ScalingPolicyListResponse) error {
// Check for list-job permissions
aclObj, err := p.srv.ResolveToken(args.AuthToken)
aclObj, err := p.srv.ResolveACL(args)
if err != nil {
return err
}

View File

@ -548,12 +548,17 @@ func (*Search) silenceError(err error) bool {
// PrefixSearch is used to list matches for a given prefix, and returns
// matching jobs, evaluations, allocations, and/or nodes.
func (s *Search) PrefixSearch(args *structs.SearchRequest, reply *structs.SearchResponse) error {
authErr := s.srv.Authenticate(s.ctx, args)
if done, err := s.srv.forward("Search.PrefixSearch", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "search", "prefix_search"}, time.Now())
aclObj, err := s.srv.ResolveToken(args.AuthToken)
aclObj, err := s.srv.ResolveACL(args)
if err != nil {
return err
}
@ -676,12 +681,17 @@ func sufficientSearchPerms(aclObj *acl.ACL, namespace string, context structs.Co
//
// The results are in descending order starting with strongest match, per Context type.
func (s *Search) FuzzySearch(args *structs.FuzzySearchRequest, reply *structs.FuzzySearchResponse) error {
authErr := s.srv.Authenticate(s.ctx, args)
if done, err := s.srv.forward("Search.FuzzySearch", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "search", "fuzzy_search"}, time.Now())
aclObj, err := s.srv.ResolveToken(args.AuthToken)
aclObj, err := s.srv.ResolveACL(args)
if err != nil {
return err
}

View File

@ -14,7 +14,6 @@ import (
"github.com/hashicorp/go-set"
"github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/state"
"github.com/hashicorp/nomad/nomad/state/paginator"
"github.com/hashicorp/nomad/nomad/structs"
@ -105,9 +104,13 @@ func (s *ServiceRegistration) DeleteByID(
args *structs.ServiceRegistrationDeleteByIDRequest,
reply *structs.ServiceRegistrationDeleteByIDResponse) error {
authErr := s.srv.Authenticate(s.ctx, args)
if done, err := s.srv.forward(structs.ServiceRegistrationDeleteByIDRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "service_registration", "delete_id"}, time.Now())
// Nomad service registrations can only be used once all servers, in the
@ -118,7 +121,7 @@ func (s *ServiceRegistration) DeleteByID(
}
// Perform the ACL token resolution.
aclObj, err := s.srv.ResolveToken(args.AuthToken)
aclObj, err := s.srv.ResolveACL(args)
switch err {
case nil:
@ -198,9 +201,13 @@ func (s *ServiceRegistration) List(
args *structs.ServiceRegistrationListRequest,
reply *structs.ServiceRegistrationListResponse) error {
authErr := s.srv.Authenticate(s.ctx, args)
if done, err := s.srv.forward(structs.ServiceRegistrationListRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "service_registration", "list"}, time.Now())
// If the caller has requested to list services across all namespaces, use
@ -209,9 +216,12 @@ func (s *ServiceRegistration) List(
return s.listAllServiceRegistrations(args, reply)
}
// Perform our mixed auth handling.
if err := s.handleMixedAuthEndpoint(args.QueryOptions, acl.NamespaceCapabilityReadJob); err != nil {
return err
aclObj, err := s.srv.ResolveClientOrACL(args)
if err != nil {
return structs.ErrPermissionDenied
}
if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
}
// Set up and return the blocking query.
@ -270,9 +280,9 @@ func (s *ServiceRegistration) listAllServiceRegistrations(
args *structs.ServiceRegistrationListRequest,
reply *structs.ServiceRegistrationListResponse) error {
// Perform token resolution. The request already goes through forwarding
// Perform ACL resolution. The request already goes through forwarding
// and metrics setup before being called.
aclObj, err := s.srv.ResolveToken(args.AuthToken)
aclObj, err := s.srv.ResolveACL(args)
if err != nil {
return err
}
@ -360,14 +370,21 @@ func (s *ServiceRegistration) GetService(
args *structs.ServiceRegistrationByNameRequest,
reply *structs.ServiceRegistrationByNameResponse) error {
authErr := s.srv.Authenticate(s.ctx, args)
if done, err := s.srv.forward(structs.ServiceRegistrationGetServiceRPCMethod, args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "service_registration", "get_service"}, time.Now())
// Perform our mixed auth handling.
if err := s.handleMixedAuthEndpoint(args.QueryOptions, acl.NamespaceCapabilityReadJob); err != nil {
return err
aclObj, err := s.srv.ResolveClientOrACL(args)
if err != nil {
return structs.ErrPermissionDenied
}
if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
return structs.ErrPermissionDenied
}
// Set up the blocking query.
@ -498,64 +515,3 @@ func (*ServiceRegistration) choose(services []*structs.ServiceRegistration, para
return chosen, nil
}
// handleMixedAuthEndpoint is a helper to handle auth on RPC endpoints that can
// either be called by Nomad nodes, or by external clients.
func (s *ServiceRegistration) handleMixedAuthEndpoint(args structs.QueryOptions, cap string) error {
// Perform the initial token resolution.
aclObj, err := s.srv.ResolveToken(args.AuthToken)
switch err {
case nil:
// Perform our ACL validation. If the object is nil, this means ACLs
// are not enabled, otherwise trigger the allowed namespace function.
if aclObj != nil {
if !aclObj.AllowNsOp(args.RequestNamespace(), cap) {
return structs.ErrPermissionDenied
}
}
default:
// Attempt to verify the token as a JWT with a workload
// identity claim if it's not a secret ID.
// COMPAT(1.4.0): we can remove this conditional in 1.5.0
if !helper.IsUUID(args.AuthToken) {
claims, err := s.srv.VerifyClaim(args.AuthToken)
if err != nil {
return err
}
if claims == nil {
return structs.ErrPermissionDenied
}
return nil
}
// COMPAT(1.4.0): Nomad 1.3.0 shipped with authentication by
// node secret but that's been replaced with workload identity
// in 1.4.0. Leave this here for backwards compatibility
// between clients and servers during cluster upgrades, but
// remove for 1.5.0
// In the event we got any error other than ErrTokenNotFound, consider this
// terminal.
if err != structs.ErrTokenNotFound {
return err
}
// Attempt to lookup AuthToken as a Node.SecretID and
// return any error wrapped along with the original.
node, stateErr := s.srv.fsm.State().NodeBySecretID(nil, args.AuthToken)
if stateErr != nil {
var mErr multierror.Error
mErr.Errors = append(mErr.Errors, err, stateErr)
return mErr.ErrorOrNil()
}
// At this point, we do not have a valid ACL token, nor are we being
// called, or able to confirm via the state store, by a node.
if node == nil {
return structs.ErrTokenNotFound
}
}
return nil
}

View File

@ -70,8 +70,12 @@ func (s *Status) Peers(args *structs.GenericRequest, reply *[]string) error {
// Members return the list of servers in a cluster that a particular server is
// aware of
func (s *Status) Members(args *structs.GenericRequest, reply *structs.ServerMembersResponse) error {
authErr := s.srv.Authenticate(s.ctx, args)
if authErr != nil {
return structs.ErrPermissionDenied
}
// Check node read permissions
if aclObj, err := s.srv.ResolveToken(args.AuthToken); err != nil {
if aclObj, err := s.srv.ResolveACL(args); err != nil {
return err
} else if aclObj != nil && !aclObj.AllowNodeRead() {
return structs.ErrPermissionDenied

View File

@ -22,12 +22,17 @@ func NewSystemEndpoint(srv *Server, ctx *RPCContext) *System {
// GarbageCollect is used to trigger the system to immediately garbage collect nodes, evals
// and jobs.
func (s *System) GarbageCollect(args *structs.GenericRequest, reply *structs.GenericResponse) error {
authErr := s.srv.Authenticate(s.ctx, args)
if done, err := s.srv.forward("System.GarbageCollect", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
// Check management level permissions
if acl, err := s.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := s.srv.ResolveACL(args); err != nil {
return err
} else if acl != nil && !acl.IsManagement() {
return structs.ErrPermissionDenied
@ -46,12 +51,17 @@ func (s *System) GarbageCollect(args *structs.GenericRequest, reply *structs.Gen
// ReconcileJobSummaries reconciles the summaries of all the jobs in the state
// store
func (s *System) ReconcileJobSummaries(args *structs.GenericRequest, reply *structs.GenericResponse) error {
authErr := s.srv.Authenticate(s.ctx, args)
if done, err := s.srv.forward("System.ReconcileJobSummaries", args, args, reply); done {
return err
}
if authErr != nil {
return structs.ErrPermissionDenied
}
// Check management level permissions
if acl, err := s.srv.ResolveToken(args.AuthToken); err != nil {
if acl, err := s.srv.ResolveACL(args); err != nil {
return err
} else if acl != nil && !acl.IsManagement() {
return structs.ErrPermissionDenied

View File

@ -40,7 +40,7 @@ func (sv *Variables) Apply(args *structs.VariablesApplyRequest, reply *structs.V
return err
}
if authErr != nil {
return authErr
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{
@ -114,8 +114,8 @@ func svePreApply(sv *Variables, args *structs.VariablesApplyRequest, vd *structs
canRead = false
var aclObj *acl.ACL
// Perform the ACL token resolution.
if aclObj, err = sv.srv.ResolveToken(args.AuthToken); err != nil {
// Perform the ACL resolution.
if aclObj, err = sv.srv.ResolveACL(args); err != nil {
return
} else if aclObj != nil {
hasPerm := func(perm string) bool {
@ -230,7 +230,7 @@ func (sv *Variables) Read(args *structs.VariablesReadRequest, reply *structs.Var
return err
}
if authErr != nil {
return authErr
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "variables", "read"}, time.Now())
@ -280,7 +280,7 @@ func (sv *Variables) List(
return err
}
if authErr != nil {
return authErr
return structs.ErrPermissionDenied
}
defer metrics.MeasureSince([]string{"nomad", "variables", "list"}, time.Now())
@ -295,7 +295,7 @@ func (sv *Variables) List(
var err error
aclToken := args.GetIdentity().GetACLToken()
if aclToken != nil {
aclObj, err = sv.srv.ResolveACL(args.GetIdentity().GetACLToken())
aclObj, err = sv.srv.ResolveACLForToken(aclToken)
if err != nil {
return err
}
@ -385,7 +385,7 @@ func (sv *Variables) listAllVariables(
var err error
aclToken := args.GetIdentity().GetACLToken()
if aclToken != nil {
aclObj, err = sv.srv.ResolveACL(args.GetIdentity().GetACLToken())
aclObj, err = sv.srv.ResolveACLForToken(aclToken)
if err != nil {
return err
}
@ -499,7 +499,7 @@ func (sv *Variables) handleMixedAuthEndpoint(args structs.QueryOptions, cap, pat
var err error
aclToken := args.GetIdentity().GetACLToken()
if aclToken != nil {
aclObj, err = sv.srv.ResolveACL(args.GetIdentity().GetACLToken())
aclObj, err = sv.srv.ResolveACLForToken(aclToken)
if err != nil {
return nil, nil, err
}