mirror of https://github.com/hashicorp/boundary
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
608 lines
17 KiB
608 lines
17 KiB
// Copyright IBM Corp. 2020, 2025
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package iam
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/hashicorp/boundary/globals"
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/db/timestamp"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/hashicorp/boundary/internal/iam/store"
|
|
"github.com/hashicorp/boundary/internal/oplog"
|
|
"github.com/hashicorp/boundary/internal/types/action"
|
|
"github.com/hashicorp/boundary/internal/types/resource"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
const (
|
|
defaultGlobalRoleTableName = "iam_role_global"
|
|
defaultOrgRoleTableName = "iam_role_org"
|
|
defaultProjectRoleTableName = "iam_role_project"
|
|
)
|
|
|
|
// roleGrantScopeUpdater represents an internal scope type specific role structs that
|
|
// support grant scope columns. Currently this only applies to globalRole and orgRole
|
|
// this is used in SetRoleGrantScope, AddRoleGrantScope, DeleteRoleGrantScope
|
|
type roleGrantScopeUpdater interface {
|
|
// setVersion sets value of `Version` of this role. This is used in
|
|
// `repository_grant_scope` operations where version column of the associated role
|
|
// has to increase when grant scopes list is changed (add/remove) which is done in a different table.
|
|
// This version bump cannot be done by automatically with trigger and is being handled in the application code
|
|
setVersion(version uint32)
|
|
|
|
// setThisGrantScope sets value of `GrantThisRoleScope` of this role which control
|
|
// whether this role has 'this' scope granted to it
|
|
setGrantThisRoleScope(grantThis bool)
|
|
|
|
// setGrantScope sets value of `GrantScope` column of this role. The allowed values depends on the scope
|
|
// that the role is in
|
|
// - global-role: ['descendants', 'children']
|
|
// - org-role: ['children']
|
|
// - project-role: [] (None)
|
|
// This value controls whether hierarchical grant scope is granted to this role
|
|
// This method may return error when role does not support hierarchical grant scope (project role)
|
|
setGrantScope(ctx context.Context, specialGrant string) error
|
|
|
|
// removeHierarchicalGrantScope removes all hierarchical grant scopes from a role ['children', 'descendants']
|
|
// and sets 'grant_scope' column to 'individual' if required
|
|
// - global-role: may remove ['descendants', 'children']
|
|
// - org-role: may remove ['children']
|
|
// - project-role: no-op as hierarchical grant scope isn't supported for project roles
|
|
removeGrantScope()
|
|
|
|
// GrantThisRoleScope return value of `GrantScopeThis` column as *RoleGrantScope.
|
|
// Prior to the grants refactor, `this` grant scope is granted to a role by
|
|
// inserting a row to 'role_grant_scope' table, but we've moved on to storing
|
|
// 'this' grant as a dedicated column in the type-specific role tables
|
|
grantThisRoleScope() (*RoleGrantScope, bool)
|
|
|
|
// grantScope returns hierarchical grant scopes ['descendants', 'children'] if available.
|
|
// returns nil, false if grant_scope is 'individual'
|
|
grantScope() (*RoleGrantScope, bool)
|
|
}
|
|
|
|
// Roles are granted permissions and assignable to Users and Groups.
|
|
type Role struct {
|
|
PublicId string
|
|
ScopeId string
|
|
Name string
|
|
Description string
|
|
CreateTime *timestamp.Timestamp
|
|
UpdateTime *timestamp.Timestamp
|
|
Version uint32
|
|
GrantScopes []*RoleGrantScope `gorm:"-"`
|
|
}
|
|
|
|
func (role *Role) GetPublicId() string {
|
|
if role == nil {
|
|
return ""
|
|
}
|
|
return role.PublicId
|
|
}
|
|
|
|
func (role *Role) GetScopeId() string {
|
|
if role == nil {
|
|
return ""
|
|
}
|
|
return role.ScopeId
|
|
}
|
|
|
|
func (role *Role) GetName() string {
|
|
if role == nil {
|
|
return ""
|
|
}
|
|
return role.Name
|
|
}
|
|
|
|
func (role *Role) GetDescription() string {
|
|
if role == nil {
|
|
return ""
|
|
}
|
|
return role.Description
|
|
}
|
|
|
|
func (role *Role) GetCreateTime() *timestamp.Timestamp {
|
|
if role == nil {
|
|
return nil
|
|
}
|
|
return role.CreateTime
|
|
}
|
|
|
|
func (role *Role) GetUpdateTime() *timestamp.Timestamp {
|
|
if role == nil {
|
|
return nil
|
|
}
|
|
return role.UpdateTime
|
|
}
|
|
|
|
func (role *Role) GetVersion() uint32 {
|
|
if role == nil {
|
|
return 0
|
|
}
|
|
return role.Version
|
|
}
|
|
|
|
// ensure that Role implements the interfaces of: Resource, Cloneable, and db.VetForWriter.
|
|
var (
|
|
_ roleGrantScopeUpdater = (*globalRole)(nil)
|
|
_ Resource = (*globalRole)(nil)
|
|
_ Cloneable = (*globalRole)(nil)
|
|
_ db.VetForWriter = (*globalRole)(nil)
|
|
_ oplog.ReplayableMessage = (*globalRole)(nil)
|
|
|
|
_ roleGrantScopeUpdater = (*orgRole)(nil)
|
|
_ Resource = (*orgRole)(nil)
|
|
_ Cloneable = (*orgRole)(nil)
|
|
_ db.VetForWriter = (*orgRole)(nil)
|
|
_ oplog.ReplayableMessage = (*orgRole)(nil)
|
|
|
|
_ roleGrantScopeUpdater = (*projectRole)(nil)
|
|
_ Resource = (*projectRole)(nil)
|
|
_ Cloneable = (*projectRole)(nil)
|
|
_ db.VetForWriter = (*projectRole)(nil)
|
|
_ oplog.ReplayableMessage = (*projectRole)(nil)
|
|
)
|
|
|
|
// NewRole creates a new in memory role with a scope (project/org)
|
|
// allowed options include: withDescription, WithName.
|
|
func NewRole(ctx context.Context, scopeId string, opt ...Option) (*Role, error) {
|
|
const op = "iam.NewRole"
|
|
if scopeId == "" {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
|
}
|
|
opts := getOpts(opt...)
|
|
r := &Role{
|
|
ScopeId: scopeId,
|
|
Name: opts.withName,
|
|
Description: opts.withDescription,
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// VetForWrite implements db.VetForWrite() interface.
|
|
func (role *Role) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
|
|
const op = "iam.(Role).VetForWrite"
|
|
if role.PublicId == "" {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "missing public id")
|
|
}
|
|
if err := validateScopeForWrite(ctx, r, role, opType, opt...); err != nil {
|
|
return errors.Wrap(ctx, err, op)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (role *Role) getResourceType() resource.Type {
|
|
return resource.Role
|
|
}
|
|
|
|
// GetScope returns the scope for the Role.
|
|
func (role *Role) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
|
|
return LookupScope(ctx, r, role)
|
|
}
|
|
|
|
// GetResourceType returns the type of the Role.
|
|
func (*Role) GetResourceType() resource.Type { return resource.Role }
|
|
|
|
// Actions returns the available actions for Role.
|
|
func (*Role) Actions() map[string]action.Type {
|
|
ret := CrudlActions()
|
|
ret[action.AddGrants.String()] = action.AddGrants
|
|
ret[action.RemoveGrants.String()] = action.RemoveGrants
|
|
ret[action.SetGrants.String()] = action.SetGrants
|
|
ret[action.AddPrincipals.String()] = action.AddPrincipals
|
|
ret[action.RemovePrincipals.String()] = action.RemovePrincipals
|
|
ret[action.SetPrincipals.String()] = action.SetPrincipals
|
|
return ret
|
|
}
|
|
|
|
type deletedRole struct {
|
|
PublicId string `gorm:"primary_key"`
|
|
DeleteTime *timestamp.Timestamp
|
|
}
|
|
|
|
// TableName returns the tablename to override the default gorm table name
|
|
func (s *deletedRole) TableName() string {
|
|
return "iam_role_deleted"
|
|
}
|
|
|
|
// globalRole is a type embedding store.GlobalRole used to interact with iam_role_global table which contains
|
|
// all iam_role entries that are created in global-level scopes through gorm.
|
|
type globalRole struct {
|
|
*store.GlobalRole
|
|
GrantScopes []*RoleGrantScope `gorm:"-"`
|
|
tableName string `gorm:"-"`
|
|
}
|
|
|
|
func (g *globalRole) removeGrantScope() {
|
|
if g != nil {
|
|
g.GrantScope = globals.GrantScopeIndividual
|
|
}
|
|
}
|
|
|
|
func (g *globalRole) setVersion(version uint32) {
|
|
if g == nil {
|
|
return
|
|
}
|
|
g.Version = version
|
|
}
|
|
|
|
func (g *globalRole) setGrantThisRoleScope(grantThis bool) {
|
|
if g == nil {
|
|
return
|
|
}
|
|
g.GrantThisRoleScope = grantThis
|
|
}
|
|
|
|
func (g *globalRole) setGrantScope(ctx context.Context, specialGrant string) error {
|
|
if g != nil {
|
|
g.GrantScope = specialGrant
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *globalRole) grantThisRoleScope() (*RoleGrantScope, bool) {
|
|
if g == nil {
|
|
return &RoleGrantScope{}, false
|
|
}
|
|
if !g.GrantThisRoleScope {
|
|
return &RoleGrantScope{}, false
|
|
}
|
|
return &RoleGrantScope{
|
|
CreateTime: g.GrantThisRoleScopeUpdateTime,
|
|
RoleId: g.PublicId,
|
|
ScopeIdOrSpecial: globals.GrantScopeThis,
|
|
}, true
|
|
}
|
|
|
|
func (g *globalRole) grantScope() (*RoleGrantScope, bool) {
|
|
if g == nil {
|
|
return nil, false
|
|
}
|
|
if g.GrantScope == globals.GrantScopeIndividual {
|
|
return nil, false
|
|
}
|
|
return &RoleGrantScope{
|
|
CreateTime: g.GrantScopeUpdateTime,
|
|
RoleId: g.PublicId,
|
|
ScopeIdOrSpecial: g.GrantScope,
|
|
}, true
|
|
}
|
|
|
|
func (g *globalRole) TableName() string {
|
|
if g.tableName != "" {
|
|
return g.tableName
|
|
}
|
|
return defaultGlobalRoleTableName
|
|
}
|
|
|
|
func (g *globalRole) SetTableName(n string) {
|
|
g.tableName = n
|
|
}
|
|
|
|
func (g *globalRole) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
|
|
const op = "iam.(globalRole).VetForWrite"
|
|
if g.PublicId == "" {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "missing public id")
|
|
}
|
|
if err := validateScopeForWrite(ctx, r, g, opType, opt...); err != nil {
|
|
return errors.Wrap(ctx, err, op)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func allocGlobalRole() globalRole {
|
|
return globalRole{
|
|
GlobalRole: &store.GlobalRole{},
|
|
}
|
|
}
|
|
|
|
func (g *globalRole) Clone() any {
|
|
cp := proto.Clone(g.GlobalRole)
|
|
ret := &globalRole{
|
|
GlobalRole: cp.(*store.GlobalRole),
|
|
}
|
|
for _, grantScope := range g.GrantScopes {
|
|
ret.GrantScopes = append(ret.GrantScopes, grantScope.Clone().(*RoleGrantScope))
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (g *globalRole) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
|
|
return LookupScope(ctx, r, g)
|
|
}
|
|
func (g *globalRole) GetResourceType() resource.Type { return resource.Role }
|
|
func (g *globalRole) getResourceType() resource.Type { return resource.Role }
|
|
func (g *globalRole) Actions() map[string]action.Type {
|
|
ret := CrudlActions()
|
|
ret[action.AddGrants.String()] = action.AddGrants
|
|
ret[action.RemoveGrants.String()] = action.RemoveGrants
|
|
ret[action.SetGrants.String()] = action.SetGrants
|
|
ret[action.AddPrincipals.String()] = action.AddPrincipals
|
|
ret[action.RemovePrincipals.String()] = action.RemovePrincipals
|
|
ret[action.SetPrincipals.String()] = action.SetPrincipals
|
|
return ret
|
|
}
|
|
|
|
func (g *globalRole) toRole() *Role {
|
|
if g == nil {
|
|
return nil
|
|
}
|
|
ret := &Role{
|
|
PublicId: g.GetPublicId(),
|
|
ScopeId: g.GetScopeId(),
|
|
Name: g.GetName(),
|
|
Description: g.GetDescription(),
|
|
CreateTime: g.GetCreateTime(),
|
|
UpdateTime: g.GetUpdateTime(),
|
|
Version: g.GetVersion(),
|
|
}
|
|
for _, grantScope := range g.GrantScopes {
|
|
ret.GrantScopes = append(ret.GrantScopes, grantScope.Clone().(*RoleGrantScope))
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// orgRole is a type embedding store.OrgRole used to interact with iam_role_org table which contains
|
|
// all iam_role entries that are created in org-level scopes through gorm.
|
|
type orgRole struct {
|
|
*store.OrgRole
|
|
GrantScopes []*RoleGrantScope `gorm:"-"`
|
|
tableName string `gorm:"-"`
|
|
}
|
|
|
|
func (o *orgRole) removeGrantScope() {
|
|
if o != nil {
|
|
o.GrantScope = globals.GrantScopeIndividual
|
|
}
|
|
}
|
|
|
|
func (o *orgRole) setVersion(version uint32) {
|
|
if o == nil {
|
|
return
|
|
}
|
|
o.Version = version
|
|
}
|
|
|
|
func (o *orgRole) setGrantThisRoleScope(grantThis bool) {
|
|
if o == nil {
|
|
return
|
|
}
|
|
o.GrantThisRoleScope = grantThis
|
|
}
|
|
|
|
func (o *orgRole) setGrantScope(ctx context.Context, specialGrant string) error {
|
|
if o != nil {
|
|
o.GrantScope = specialGrant
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (o *orgRole) grantThisRoleScope() (*RoleGrantScope, bool) {
|
|
if o == nil {
|
|
return &RoleGrantScope{}, false
|
|
}
|
|
if !o.GrantThisRoleScope {
|
|
return &RoleGrantScope{}, false
|
|
}
|
|
return &RoleGrantScope{
|
|
CreateTime: o.GrantThisRoleScopeUpdateTime,
|
|
RoleId: o.PublicId,
|
|
ScopeIdOrSpecial: globals.GrantScopeThis,
|
|
}, true
|
|
}
|
|
|
|
func (o *orgRole) grantScope() (*RoleGrantScope, bool) {
|
|
if o == nil {
|
|
return nil, false
|
|
}
|
|
if o.GrantScope == globals.GrantScopeIndividual {
|
|
return nil, false
|
|
}
|
|
return &RoleGrantScope{
|
|
CreateTime: o.GrantScopeUpdateTime,
|
|
RoleId: o.PublicId,
|
|
ScopeIdOrSpecial: o.GrantScope,
|
|
}, true
|
|
}
|
|
|
|
func (o *orgRole) TableName() string {
|
|
if o.tableName != "" {
|
|
return o.tableName
|
|
}
|
|
return defaultOrgRoleTableName
|
|
}
|
|
|
|
func (o *orgRole) SetTableName(n string) {
|
|
o.tableName = n
|
|
}
|
|
|
|
func (o *orgRole) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
|
|
const op = "iam.(orgRole).VetForWrite"
|
|
if o.PublicId == "" {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "missing public id")
|
|
}
|
|
if err := validateScopeForWrite(ctx, r, o, opType, opt...); err != nil {
|
|
return errors.Wrap(ctx, err, op)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func allocOrgRole() orgRole {
|
|
return orgRole{
|
|
OrgRole: &store.OrgRole{},
|
|
}
|
|
}
|
|
|
|
func (o *orgRole) Clone() any {
|
|
cp := proto.Clone(o.OrgRole)
|
|
ret := &orgRole{
|
|
OrgRole: cp.(*store.OrgRole),
|
|
}
|
|
for _, grantScope := range o.GrantScopes {
|
|
ret.GrantScopes = append(ret.GrantScopes, grantScope.Clone().(*RoleGrantScope))
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (o *orgRole) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
|
|
return LookupScope(ctx, r, o)
|
|
}
|
|
func (o *orgRole) GetResourceType() resource.Type { return resource.Role }
|
|
func (o *orgRole) getResourceType() resource.Type { return resource.Role }
|
|
func (o *orgRole) Actions() map[string]action.Type {
|
|
ret := CrudlActions()
|
|
ret[action.AddGrants.String()] = action.AddGrants
|
|
ret[action.RemoveGrants.String()] = action.RemoveGrants
|
|
ret[action.SetGrants.String()] = action.SetGrants
|
|
ret[action.AddPrincipals.String()] = action.AddPrincipals
|
|
ret[action.RemovePrincipals.String()] = action.RemovePrincipals
|
|
ret[action.SetPrincipals.String()] = action.SetPrincipals
|
|
return ret
|
|
}
|
|
|
|
func (o *orgRole) toRole() *Role {
|
|
if o == nil {
|
|
return nil
|
|
}
|
|
ret := &Role{
|
|
PublicId: o.GetPublicId(),
|
|
ScopeId: o.GetScopeId(),
|
|
Name: o.GetName(),
|
|
Description: o.GetDescription(),
|
|
CreateTime: o.GetCreateTime(),
|
|
UpdateTime: o.GetUpdateTime(),
|
|
Version: o.GetVersion(),
|
|
}
|
|
for _, grantScope := range o.GrantScopes {
|
|
ret.GrantScopes = append(ret.GrantScopes, grantScope.Clone().(*RoleGrantScope))
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// projectRole is a type embedding store.ProjectRole used to interact with iam_role_project table which contains
|
|
// all iam_role entries that are created in project-level scopes through gorm.
|
|
type projectRole struct {
|
|
*store.ProjectRole
|
|
GrantScopes []*RoleGrantScope `gorm:"-"`
|
|
tableName string `gorm:"-"`
|
|
}
|
|
|
|
func (p *projectRole) removeGrantScope() {
|
|
// no-op since hierarchical isn't supported by project roles
|
|
return
|
|
}
|
|
|
|
func (p *projectRole) setGrantScope(ctx context.Context, specialGrant string) error {
|
|
const op = "iam.(projectRole).setGrantScope"
|
|
return errors.New(ctx, errors.InvalidParameter, op, "hierarchical grant scope is not allowed for project role")
|
|
}
|
|
|
|
func (p *projectRole) grantScope() (*RoleGrantScope, bool) {
|
|
return &RoleGrantScope{}, false
|
|
}
|
|
|
|
func (p *projectRole) setVersion(version uint32) {
|
|
if p == nil {
|
|
return
|
|
}
|
|
p.Version = version
|
|
}
|
|
|
|
func (p *projectRole) setGrantThisRoleScope(grantThis bool) {
|
|
if p == nil {
|
|
return
|
|
}
|
|
p.GrantThisRoleScope = grantThis
|
|
}
|
|
|
|
func (p *projectRole) grantThisRoleScope() (*RoleGrantScope, bool) {
|
|
if p == nil {
|
|
return &RoleGrantScope{}, false
|
|
}
|
|
if !p.GrantThisRoleScope {
|
|
return &RoleGrantScope{}, false
|
|
}
|
|
return &RoleGrantScope{
|
|
CreateTime: p.GrantThisRoleScopeUpdateTime,
|
|
RoleId: p.PublicId,
|
|
ScopeIdOrSpecial: globals.GrantScopeThis,
|
|
}, true
|
|
}
|
|
|
|
func (p *projectRole) TableName() string {
|
|
if p.tableName != "" {
|
|
return p.tableName
|
|
}
|
|
return defaultProjectRoleTableName
|
|
}
|
|
|
|
func (p *projectRole) SetTableName(n string) {
|
|
p.tableName = n
|
|
}
|
|
|
|
func (p *projectRole) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
|
|
const op = "iam.(projectRole).VetForWrite"
|
|
if p.PublicId == "" {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "missing public id")
|
|
}
|
|
if err := validateScopeForWrite(ctx, r, p, opType, opt...); err != nil {
|
|
return errors.Wrap(ctx, err, op)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func allocProjectRole() projectRole {
|
|
return projectRole{
|
|
ProjectRole: &store.ProjectRole{},
|
|
}
|
|
}
|
|
|
|
func (p *projectRole) Clone() any {
|
|
cp := proto.Clone(p.ProjectRole)
|
|
ret := &projectRole{
|
|
ProjectRole: cp.(*store.ProjectRole),
|
|
}
|
|
for _, grantScope := range p.GrantScopes {
|
|
ret.GrantScopes = append(ret.GrantScopes, grantScope.Clone().(*RoleGrantScope))
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (p *projectRole) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
|
|
return LookupScope(ctx, r, p)
|
|
}
|
|
func (p *projectRole) GetResourceType() resource.Type { return resource.Role }
|
|
func (p *projectRole) getResourceType() resource.Type { return resource.Role }
|
|
func (p *projectRole) Actions() map[string]action.Type {
|
|
ret := CrudlActions()
|
|
ret[action.AddGrants.String()] = action.AddGrants
|
|
ret[action.RemoveGrants.String()] = action.RemoveGrants
|
|
ret[action.SetGrants.String()] = action.SetGrants
|
|
ret[action.AddPrincipals.String()] = action.AddPrincipals
|
|
ret[action.RemovePrincipals.String()] = action.RemovePrincipals
|
|
ret[action.SetPrincipals.String()] = action.SetPrincipals
|
|
return ret
|
|
}
|
|
|
|
func (p *projectRole) toRole() *Role {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
ret := &Role{
|
|
PublicId: p.GetPublicId(),
|
|
ScopeId: p.GetScopeId(),
|
|
Name: p.GetName(),
|
|
Description: p.GetDescription(),
|
|
CreateTime: p.GetCreateTime(),
|
|
UpdateTime: p.GetUpdateTime(),
|
|
Version: p.GetVersion(),
|
|
}
|
|
for _, grantScope := range p.GrantScopes {
|
|
ret.GrantScopes = append(ret.GrantScopes, grantScope.Clone().(*RoleGrantScope))
|
|
}
|
|
return ret
|
|
}
|