diff --git a/CHANGELOG.md b/CHANGELOG.md index 62536c6..d3cc78d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ * Add 'host update' command (fix [#2](https://github.com/moul/sshportal/issues/2)) * Add 'user update' command (fix [#3](https://github.com/moul/sshportal/issues/3)) * Add 'acl update' command (fix [#4](https://github.com/moul/sshportal/issues/4)) -* Allow connecting to the shell mode with the registered username or email +* Allow connecting to the shell mode with the registered username or email (fix [#5](https://github.com/moul/sshportal/issues/5)) +* Add 'listhosts' role (fix [#5](https://github.com/moul/sshportal/issues/5)) ## v1.2.0 (2017-11-22) diff --git a/db.go b/db.go index 3654db2..4fa7097 100644 --- a/db.go +++ b/db.go @@ -207,6 +207,19 @@ func UserHasRole(user User, name string) bool { } return false } +func UserCheckRoles(user User, names []string) error { + ok := false + for _, name := range names { + if UserHasRole(user, name) { + ok = true + break + } + } + if ok { + return nil + } + return fmt.Errorf("you don't have permission to access this feature (requires any of these roles: '%s')", strings.Join(names, "', '")) +} // ACL helpers func ACLsPreload(db *gorm.DB) *gorm.DB { diff --git a/dbinit.go b/dbinit.go index 75b4d1a..a076c2d 100644 --- a/dbinit.go +++ b/dbinit.go @@ -284,6 +284,14 @@ func dbInit(db *gorm.DB) error { Rollback: func(tx *gorm.DB) error { return fmt.Errorf("not implemented") }, + }, { + ID: "20", + Migrate: func(tx *gorm.DB) error { + return tx.Create(&UserRole{Name: "listhosts"}).Error + }, + Rollback: func(tx *gorm.DB) error { + return tx.Where("name = ?", "listhosts").Delete(&UserRole{}).Error + }, }, }) if err := m.Migrate(); err != nil { diff --git a/main.go b/main.go index 26a52cd..5668f5a 100644 --- a/main.go +++ b/main.go @@ -120,10 +120,6 @@ func server(c *cli.Context) error { switch username := s.User(); { case username == currentUser.Name || username == currentUser.Email || username == c.String("config-user"): - if !UserHasRole(currentUser, "admin") { - fmt.Fprintf(s, "You are not an administrator, permission denied.\n") - return - } if err := shell(c, s, s.Command(), db); err != nil { fmt.Fprintf(s, "error: %v\n", err) } diff --git a/shell.go b/shell.go index fa1ccb6..43afdf1 100644 --- a/shell.go +++ b/shell.go @@ -53,6 +53,8 @@ GLOBAL OPTIONS: app := cli.NewApp() app.Writer = s app.HideVersion = true + + myself := s.Context().Value(userContextKey).(User) app.Commands = []cli.Command{ { Name: "acl", @@ -71,6 +73,9 @@ GLOBAL OPTIONS: cli.UintFlag{Name: "weight, w", Usage: "Assigns the ACL weight (priority)"}, }, Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } acl := ACL{ Comment: c.String("comment"), HostPattern: c.String("pattern"), @@ -118,6 +123,9 @@ GLOBAL OPTIONS: if c.NArg() < 1 { return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } var acls []ACL if err := ACLsPreload(ACLsByIdentifiers(db, c.Args())).Find(&acls).Error; err != nil { @@ -132,6 +140,9 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists acls", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } var acls []ACL if err := db.Preload("UserGroups").Preload("HostGroups").Find(&acls).Error; err != nil { return err @@ -171,6 +182,9 @@ GLOBAL OPTIONS: if c.NArg() < 1 { return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } return ACLsByIdentifiers(db, c.Args()).Delete(&ACL{}).Error }, @@ -192,6 +206,9 @@ GLOBAL OPTIONS: if c.NArg() < 1 { return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } var acls []ACL if err := ACLsByIdentifiers(db, c.Args()).Find(&acls).Error; err != nil { @@ -260,6 +277,10 @@ GLOBAL OPTIONS: }, Description: "ssh admin@portal config backup > sshportal.bkp", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + config := Config{} if err := db.Find(&config.Hosts).Error; err != nil { return err @@ -303,6 +324,10 @@ GLOBAL OPTIONS: cli.BoolFlag{Name: "confirm", Usage: "yes, I want to replace everything with this backup!"}, }, Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + config := Config{} dec := json.NewDecoder(s) @@ -413,6 +438,11 @@ GLOBAL OPTIONS: if c.NArg() != 1 { return cli.ShowSubcommandHelp(c) } + + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + host, err := NewHostFromURL(c.Args().First()) if err != nil { return err @@ -469,8 +499,16 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin", "listhosts"}); err != nil { + return err + } + var hosts []Host - if err := HostsPreload(HostsByIdentifiers(db, c.Args())).Find(&hosts).Error; err != nil { + db = db.Preload("Groups") + if UserHasRole(myself, "admin") { + db = db.Preload("SSHKey") + } + if err := HostsByIdentifiers(db, c.Args()).Find(&hosts).Error; err != nil { return err } @@ -482,6 +520,10 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists hosts", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin", "listhosts"}); err != nil { + return err + } + var hosts []*Host if err := db.Preload("Groups").Find(&hosts).Error; err != nil { return err @@ -528,6 +570,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return HostsByIdentifiers(db, c.Args()).Delete(&Host{}).Error }, }, { @@ -548,6 +594,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var hosts []Host if err := HostsByIdentifiers(db, c.Args()).Find(&hosts).Error; err != nil { return err @@ -615,6 +665,10 @@ GLOBAL OPTIONS: cli.StringFlag{Name: "comment", Usage: "Adds a comment"}, }, Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + hostGroup := HostGroup{ Name: c.String("name"), Comment: c.String("comment"), @@ -642,6 +696,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var hostGroups []HostGroup if err := HostGroupsPreload(HostGroupsByIdentifiers(db, c.Args())).Find(&hostGroups).Error; err != nil { return err @@ -655,6 +713,10 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists host groups", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var hostGroups []*HostGroup if err := db.Preload("ACLs").Preload("Hosts").Find(&hostGroups).Error; err != nil { return err @@ -685,6 +747,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return HostGroupsByIdentifiers(db, c.Args()).Delete(&HostGroup{}).Error }, }, @@ -693,6 +759,10 @@ GLOBAL OPTIONS: Name: "info", Usage: "Shows system-wide information", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + fmt.Fprintf(s, "Debug mode (server): %v\n", globalContext.Bool("debug")) hostname, _ := os.Hostname() fmt.Fprintf(s, "Hostname: %s\n", hostname) @@ -708,7 +778,6 @@ GLOBAL OPTIONS: fmt.Fprintf(s, "Go version (build): %v\n", runtime.Version()) fmt.Fprintf(s, "Uptime: %v\n", time.Since(startTime)) - myself := s.Context().Value(userContextKey).(User) fmt.Fprintf(s, "User email: %v\n", myself.ID) fmt.Fprintf(s, "User email: %s\n", myself.Email) fmt.Fprintf(s, "Version: %s\n", VERSION) @@ -737,6 +806,10 @@ GLOBAL OPTIONS: cli.StringFlag{Name: "comment", Usage: "Adds a comment"}, }, Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + name := namesgenerator.GetRandomName(0) if c.String("name") != "" { name = c.String("name") @@ -770,6 +843,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var keys []SSHKey if err := SSHKeysByIdentifiers(db, c.Args()).Find(&keys).Error; err != nil { return err @@ -783,6 +860,10 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists keys", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var keys []SSHKey if err := db.Preload("Hosts").Find(&keys).Error; err != nil { return err @@ -816,6 +897,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return SSHKeysByIdentifiers(db, c.Args()).Delete(&SSHKey{}).Error }, }, @@ -833,6 +918,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var users []User if err := UsersPreload(UsersByIdentifiers(db, c.Args())).Find(&users).Error; err != nil { return err @@ -857,6 +946,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + // FIXME: validate email email := c.Args().First() @@ -896,6 +989,10 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists users", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var users []User if err := db.Preload("Groups").Preload("Roles").Preload("Keys").Find(&users).Error; err != nil { return err @@ -937,6 +1034,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return UsersByIdentifiers(db, c.Args()).Delete(&User{}).Error }, }, { @@ -956,6 +1057,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + // FIXME: check if unset-admin + user == myself var users []User if err := UsersByIdentifiers(db, c.Args()).Find(&users).Error; err != nil { @@ -1032,6 +1137,10 @@ GLOBAL OPTIONS: cli.StringFlag{Name: "comment", Usage: "Adds a comment"}, }, Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + userGroup := UserGroup{ Name: c.String("name"), Comment: c.String("comment"), @@ -1044,10 +1153,8 @@ GLOBAL OPTIONS: return err } // FIXME: check if name already exists + // FIXME: add myself to the new group - // add myself to the new group - myself := s.Context().Value(userContextKey).(User) - // FIXME: use foreign key with ID to avoid updating the user with the context userGroup.Users = []*User{&myself} if err := db.Create(&userGroup).Error; err != nil { @@ -1065,6 +1172,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var userGroups []UserGroup if err := UserGroupsPreload(UserGroupsByIdentifiers(db, c.Args())).Find(&userGroups).Error; err != nil { return err @@ -1078,6 +1189,10 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists user groups", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var userGroups []*UserGroup if err := db.Preload("ACLs").Preload("Users").Find(&userGroups).Error; err != nil { return err @@ -1108,6 +1223,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return UserGroupsByIdentifiers(db, c.Args()).Delete(&UserGroup{}).Error }, }, @@ -1129,6 +1248,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var user User if err := UsersByIdentifiers(db, c.Args()).First(&user).Error; err != nil { return err @@ -1172,6 +1295,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var userKeys []UserKey if err := UserKeysPreload(UserKeysByIdentifiers(db, c.Args())).Find(&userKeys).Error; err != nil { return err @@ -1185,6 +1312,10 @@ GLOBAL OPTIONS: Name: "ls", Usage: "Lists userkeys", Action: func(c *cli.Context) error { + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + var userkeys []UserKey if err := db.Preload("User").Find(&userkeys).Error; err != nil { return err @@ -1213,6 +1344,10 @@ GLOBAL OPTIONS: return cli.ShowSubcommandHelp(c) } + if err := UserCheckRoles(myself, []string{"admin"}); err != nil { + return err + } + return UserKeysByIdentifiers(db, c.Args()).Delete(&UserKey{}).Error }, },