* Remove list:self from main As per futher discussions, we won't be supporting this, in favor of simply read:self and cancel:self (since listing now already filters out items on which you have no permissions). This doesn't remove list:self entirely from the code base as much of that will simply be converted to read:self and cancel:self -- that will be in a later PR. However, with this removed if we want to release we won't expose an API publicly that isn't going to stick around.
162 lines
4.4 KiB
Go
162 lines
4.4 KiB
Go
package perms
|
|
|
|
/*
|
|
A really useful page to be aware of when looking at ACLs is
|
|
https://hashicorp.atlassian.net/wiki/spaces/ICU/pages/866976600/API+Actions+and+Permissions
|
|
speaking of which: TODO: put that chart in public docs.
|
|
|
|
Anyways, from that page you can see that there are really only a few patterns of
|
|
ACLs that are ever allowed; see the Allowed function for a description along
|
|
with the code.
|
|
|
|
This makes it actually quite simple to perform the ACL checking. Much of ACL
|
|
construction is thus synthesizing something reasonable from a set of Grants.
|
|
*/
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/hashicorp/boundary/internal/types/action"
|
|
"github.com/hashicorp/boundary/internal/types/resource"
|
|
)
|
|
|
|
// ACL provides an entry point into the permissions engine for determining if an
|
|
// action is allowed on a resource based on a principal's (user or group) grants.
|
|
type ACL struct {
|
|
scopeMap map[string][]Grant
|
|
}
|
|
|
|
// ACLResults provides a type for the permission's engine results so that we can
|
|
// pass more detailed information along in the future if we want. It was useful
|
|
// in Vault, may be useful here.
|
|
type ACLResults struct {
|
|
Allowed bool
|
|
|
|
// This is included but unexported for testing/debugging
|
|
scopeMap map[string][]Grant
|
|
}
|
|
|
|
// Resource defines something within boundary that requires authorization
|
|
// capabilities. Resources must have a ScopeId.
|
|
type Resource struct {
|
|
// ScopeId is the scope that contains the Resource.
|
|
ScopeId string
|
|
|
|
// Id is the public id of the resource.
|
|
Id string
|
|
|
|
// Type of resource.
|
|
Type resource.Type
|
|
|
|
// Pin if defined would constrain the resource within the collection of the
|
|
// pin id.
|
|
Pin string
|
|
}
|
|
|
|
// NewACL creates an ACL from the grants provided.
|
|
func NewACL(grants ...Grant) ACL {
|
|
ret := ACL{
|
|
scopeMap: make(map[string][]Grant, len(grants)),
|
|
}
|
|
|
|
for _, grant := range grants {
|
|
ret.scopeMap[grant.scope.Id] = append(ret.scopeMap[grant.scope.Id], grant)
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// Allowed determines if the grants for an ACL allow an action for a resource.
|
|
func (a ACL) Allowed(r Resource, aType action.Type) (results ACLResults) {
|
|
// First, get the grants within the specified scope
|
|
grants := a.scopeMap[r.ScopeId]
|
|
results.scopeMap = a.scopeMap
|
|
|
|
var parentAction action.Type
|
|
split := strings.Split(aType.String(), ":")
|
|
if len(split) == 2 {
|
|
parentAction = action.Map[split[0]]
|
|
}
|
|
// Now, go through and check the cases indicated above
|
|
for _, grant := range grants {
|
|
switch {
|
|
case grant.actions[aType]:
|
|
// We have this action
|
|
case grant.actions[parentAction]:
|
|
// We don't have this action, but it's a subaction and we have the
|
|
// parent action. As an example, if we are looking for "read:self"
|
|
// and have "read", this is sufficient.
|
|
case grant.actions[action.All]:
|
|
// All actions are allowed
|
|
default:
|
|
// No actions in the grant match what we're looking for, so continue
|
|
// with the next grant
|
|
continue
|
|
}
|
|
switch {
|
|
// id=<resource.id>;actions=<action> where ID cannot be a wildcard
|
|
case grant.id == r.Id &&
|
|
grant.id != "" &&
|
|
grant.id != "*" &&
|
|
grant.typ == resource.Unknown &&
|
|
aType != action.List &&
|
|
aType != action.Create:
|
|
|
|
results.Allowed = true
|
|
return
|
|
|
|
// type=<resource.type>;actions=<action> when action is list(:self) or
|
|
// create. Must be a top level collection, otherwise must be one of the
|
|
// two formats specified below.
|
|
case grant.id == "" &&
|
|
r.Id == "" &&
|
|
grant.typ == r.Type &&
|
|
grant.typ != resource.Unknown &&
|
|
topLevelType(r.Type) &&
|
|
(aType == action.List ||
|
|
aType == action.Create):
|
|
|
|
results.Allowed = true
|
|
return
|
|
|
|
// id=*;type=<resource.type>;actions=<action> where type cannot be
|
|
// unknown but can be a wildcard to allow any resource at all
|
|
case grant.id == "*" &&
|
|
grant.typ != resource.Unknown &&
|
|
(grant.typ == r.Type ||
|
|
grant.typ == resource.All):
|
|
|
|
results.Allowed = true
|
|
return
|
|
|
|
// id=<pin>;type=<resource.type>;actions=<action> where type can be a
|
|
// wildcard and this this is operating on a non-top-level type
|
|
case grant.id != "" &&
|
|
grant.id == r.Pin &&
|
|
grant.typ != resource.Unknown &&
|
|
(grant.typ == r.Type || grant.typ == resource.All) &&
|
|
!topLevelType(r.Type):
|
|
|
|
results.Allowed = true
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func topLevelType(typ resource.Type) bool {
|
|
switch typ {
|
|
case resource.AuthMethod,
|
|
resource.AuthToken,
|
|
resource.Group,
|
|
resource.HostCatalog,
|
|
resource.Role,
|
|
resource.Scope,
|
|
resource.Session,
|
|
resource.Target,
|
|
resource.User:
|
|
return true
|
|
}
|
|
return false
|
|
}
|