Skip to content

Commit

Permalink
feat: support requesting inherited grants (#2423)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssoroka committed Jul 6, 2022
1 parent 64b2f90 commit 0eb7aca
Show file tree
Hide file tree
Showing 14 changed files with 560 additions and 342 deletions.
10 changes: 6 additions & 4 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"runtime"
"strconv"

"github.com/ssoroka/slice"

Expand Down Expand Up @@ -243,10 +244,11 @@ func (c Client) DeleteProvider(id uid.ID) error {

func (c Client) ListGrants(req ListGrantsRequest) (*ListResponse[Grant], error) {
return get[ListResponse[Grant]](c, "/api/grants", Query{
"user": {req.User.String()},
"group": {req.Group.String()},
"resource": {req.Resource},
"privilege": {req.Privilege},
"user": {req.User.String()},
"group": {req.Group.String()},
"resource": {req.Resource},
"privilege": {req.Privilege},
"showInherited": {strconv.FormatBool(req.ShowInherited)},
})
}

Expand Down
9 changes: 5 additions & 4 deletions api/grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ type Grant struct {
}

type ListGrantsRequest struct {
User uid.ID `form:"user" validate:"excluded_with=Group"`
Group uid.ID `form:"group" validate:"excluded_with=User"`
Resource string `form:"resource" example:"production"`
Privilege string `form:"privilege" example:"view"`
User uid.ID `form:"user" validate:"excluded_with=Group"`
Group uid.ID `form:"group" validate:"excluded_with=User"`
Resource string `form:"resource" example:"production"`
Privilege string `form:"privilege" example:"view"`
ShowInherited bool `form:"showInherited" note:"if true, this field includes grants that the user inherits through groups"`
PaginationRequest
}

Expand Down
39 changes: 28 additions & 11 deletions internal/access/grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func GetGrant(c *gin.Context, id uid.ID) (*models.Grant, error) {
return data.GetGrant(db, data.ByID(id))
}

func ListGrants(c *gin.Context, subject uid.PolymorphicID, resource string, privilege string, pg models.Pagination) ([]models.Grant, error) {
func ListGrants(c *gin.Context, subject uid.PolymorphicID, resource string, privilege string, inherited bool, pg models.Pagination) ([]models.Grant, error) {
selectors := []data.SelectorFunc{
data.ByOptionalResource(resource),
data.ByOptionalPrivilege(privilege),
Expand All @@ -29,30 +29,47 @@ func ListGrants(c *gin.Context, subject uid.PolymorphicID, resource string, priv

roles := []string{models.InfraAdminRole, models.InfraViewRole, models.InfraConnectorRole}
db, err := RequireInfraRole(c, roles...)
if err == nil {
selectors = append(selectors, data.ByOptionalSubject(subject))
return data.ListGrants(db, selectors...)
}
err = HandleAuthErr(err, "grants", "list", roles...)

if errors.Is(err, ErrNotAuthorized) {
// Allow an authenticated identity to view their own grants
db := getDB(c)
subjectID, _ := subject.ID()
db = getDB(c)
subjectID, err2 := subject.ID()
if err2 != nil {
// user is only allowed to select their own grants, so if the subject is missing or invalid, this is an access error
return nil, err
}
identity := AuthenticatedIdentity(c)
switch {
case identity == nil:
return nil, err
case subject.IsIdentity() && identity.ID == subjectID:
selectors = append(selectors, data.BySubject(subject))
if inherited {
selectors = append(selectors, data.GrantsInheritedBySubject(subject))
} else {
selectors = append(selectors, data.BySubject(subject))
}
return data.ListGrants(db, selectors...)
case subject.IsGroup() && userInGroup(db, identity.ID, subjectID):
selectors = append(selectors, data.BySubject(subject))
if inherited {
selectors = append(selectors, data.GrantsInheritedBySubject(subject))
} else {
selectors = append(selectors, data.BySubject(subject))
}
return data.ListGrants(db, selectors...)
default:
return nil, err
}
} else if err != nil {
return nil, err
}

if inherited && len(subject) > 0 {
selectors = append(selectors, data.GrantsInheritedBySubject(subject))
} else {
selectors = append(selectors, data.ByOptionalSubject(subject))
}

return nil, err
return data.ListGrants(db, selectors...)
}

func userInGroup(db *gorm.DB, authnUserID uid.ID, groupID uid.ID) bool {
Expand Down
16 changes: 1 addition & 15 deletions internal/cmd/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,25 +67,11 @@ func updateKubeConfig(client *api.Client, id uid.ID) error {
return err
}

grants, err := client.ListGrants(api.ListGrantsRequest{User: id})
grants, err := client.ListGrants(api.ListGrantsRequest{User: id, ShowInherited: true})
if err != nil {
return err
}

groups, err := client.ListGroups(api.ListGroupsRequest{UserID: id})
if err != nil {
return err
}

for _, g := range groups.Items {
groupGrants, err := client.ListGrants(api.ListGrantsRequest{Group: g.ID})
if err != nil {
return err
}

grants.Items = append(grants.Items, groupGrants.Items...)
}

return writeKubeconfig(user, destinations.Items, grants.Items)
}

Expand Down
16 changes: 1 addition & 15 deletions internal/cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,25 +122,11 @@ func getUserDestinationGrants(client *api.Client) (*api.User, *api.ListResponse[
return nil, nil, nil, err
}

grants, err := client.ListGrants(api.ListGrantsRequest{User: config.UserID})
grants, err := client.ListGrants(api.ListGrantsRequest{User: config.UserID, ShowInherited: true})
if err != nil {
return nil, nil, nil, err
}

groups, err := client.ListGroups(api.ListGroupsRequest{UserID: config.UserID})
if err != nil {
return nil, nil, nil, err
}

for _, g := range groups.Items {
groupGrants, err := client.ListGrants(api.ListGrantsRequest{Group: g.ID})
if err != nil {
return nil, nil, nil, err
}

grants.Items = append(grants.Items, groupGrants.Items...)
}

destinations, err := client.ListDestinations(api.ListDestinationsRequest{})
if err != nil {
return nil, nil, nil, err
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ func newConsole(t *testing.T) *expect.Console {
pseudoTY, tty, err := pty.Open()
assert.NilError(t, err, "failed to open pseudo tty")

timeout := 2 * time.Second
timeout := 10 * time.Second
if os.Getenv("CI") != "" || race.Enabled {
// CI and -race take much longer than regular runs, use a much longer timeout
timeout = 30 * time.Second
Expand Down
34 changes: 5 additions & 29 deletions internal/cmd/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,39 +414,15 @@ func createUser(client *api.Client, name string) (*api.CreateUserResponse, error
// check if the user has permissions to reset passwords for another user.
// This might be handy for customizing error messages
func hasAccessToChangePasswordsForOtherUsers(client *api.Client, config *ClientHostConfig) (bool, error) {
// TODO: could really use inherited grants for this
grants, err := client.ListGrants(api.ListGrantsRequest{
User: config.UserID,
Privilege: api.InfraAdminRole,
Resource: "infra",
User: config.UserID,
Privilege: api.InfraAdminRole,
Resource: "infra",
ShowInherited: true,
})
if err != nil {
return false, err
}

if len(grants.Items) > 0 {
return true, nil
}

myGroups, err := client.ListGroups(api.ListGroupsRequest{UserID: config.UserID})
if err != nil {
return false, err
}

for _, group := range myGroups.Items {
grants, err := client.ListGrants(api.ListGrantsRequest{
Group: group.ID,
Privilege: api.InfraAdminRole,
Resource: "infra",
})
if err != nil {
return false, err
}

if len(grants.Items) > 0 {
return true, nil
}
}

return false, nil
return len(grants.Items) > 0, nil
}
1 change: 1 addition & 0 deletions internal/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ func syncWithServer(k8s *kubernetes.Kubernetes, client *api.Client, destination
return
}

// TODO(https://github.com/infrahq/infra/issues/2422): support wildcard resource searches
for _, n := range namespaces {
g, err := client.ListGrants(api.ListGrantsRequest{Resource: fmt.Sprintf("%s.%s", destination.Name, n)})
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions internal/server/apimigrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ func rebuildRequest(c *gin.Context, newReqObj interface{}) {
query.Add(fieldName, fmt.Sprintf("%d", f.Int()))
case reflect.Uint, reflect.Uint64:
query.Add(fieldName, fmt.Sprintf("%d", f.Int()))
case reflect.Bool:
if f.Bool() {
query.Add(fieldName, "1")
} else {
query.Add(fieldName, "0")
}
default:
panic("unhandled reflection kind " + f.Kind().String())
}
Expand Down
31 changes: 31 additions & 0 deletions internal/server/data/grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package data
import (
"gorm.io/gorm"

"github.com/infrahq/infra/internal/logging"
"github.com/infrahq/infra/internal/server/models"
"github.com/infrahq/infra/uid"
)
Expand Down Expand Up @@ -56,6 +57,36 @@ func ByOptionalPrivilege(s string) SelectorFunc {
}
}

func GrantsInheritedBySubject(subjectID uid.PolymorphicID) SelectorFunc {
return func(db *gorm.DB) *gorm.DB {
switch {
case subjectID.IsIdentity():
userID, err := subjectID.ID()
if err != nil {
logging.Errorf("invalid subject id %q", subjectID)
return db.Where("1 = 0")
}
var groupIDs []uid.ID
err = db.Session(&gorm.Session{NewDB: true}).Raw("select distinct group_id from identities_groups where identity_id = ?", userID).Pluck("group_id", &groupIDs).Error
if err != nil {
logging.Errorf("GrantsInheritedByUser: %s", err)
_ = db.AddError(err)
return db.Where("1 = 0")
}

subjects := []string{subjectID.String()}
for _, groupID := range groupIDs {
subjects = append(subjects, uid.NewGroupPolymorphicID(groupID).String())
}
return db.Where("subject in (?)", subjects)
case subjectID.IsGroup():
return BySubject(subjectID)(db)
default:
panic("unhandled subject type")
}
}
}

func ByPrivilege(s string) SelectorFunc {
return func(db *gorm.DB) *gorm.DB {
return db.Where("privilege = ?", s)
Expand Down

0 comments on commit 0eb7aca

Please sign in to comment.