mirror of
https://github.com/moul/sshportal.git
synced 2025-09-11 23:24:33 +08:00
Support assign multiple groups to hosts and users (#2)
This commit is contained in:
parent
d6a7a6702f
commit
f97c9f2878
5 changed files with 77 additions and 69 deletions
|
@ -2,7 +2,7 @@
|
|||
|
||||
## master (unreleased)
|
||||
|
||||
* No entry
|
||||
* Support adding multiple `--group` links on `host create` and `user create`
|
||||
|
||||
## v1.1.0 (2017-11-15)
|
||||
|
||||
|
|
28
README.md
28
README.md
|
@ -140,9 +140,9 @@ You can enter in interactive mode using this syntax: `ssh admin@portal.example.o
|
|||
# acl management
|
||||
acl help
|
||||
acl create [-h] [--hostgroup=<value>...] [--usergroup=<value>...] [--pattern=<value>] [--comment=<value>] [--action=<value>] [--weight=value]
|
||||
acl inspect [-h] <id> [<id> [<id>...]]
|
||||
acl inspect [-h] <id>...
|
||||
acl ls [-h]
|
||||
acl rm [-h] <id> [<id> [<id>...]]
|
||||
acl rm [-h] <id>...
|
||||
|
||||
# config management
|
||||
config help
|
||||
|
@ -151,38 +151,38 @@ config restore [-h] [--confirm]
|
|||
|
||||
# host management
|
||||
host help
|
||||
host create [-h] [--name=<value>] [--password=<value>] [--fingerprint=<value>] [--comment=<value>] [--key=<value>] [--group=<value>] <user>[:<password>]@<host>[:<port>]
|
||||
host inspect [-h] <id or name> [<id or name> [<id or name>...]]
|
||||
host create [-h] [--name=<value>] [--password=<value>] [--fingerprint=<value>] [--comment=<value>] [--key=<value>] [--group=<value>...] <user>[:<password>]@<host>[:<port>]
|
||||
host inspect [-h] <id or name>...
|
||||
host ls [-h]
|
||||
host rm [-h] <id or name> [<id or name> [<id or name>...]]
|
||||
host rm [-h] <id or name>...
|
||||
|
||||
# hostgroup management
|
||||
hostgroup help
|
||||
hostgroup create [-h] [--name=<value>] [--comment=<value>]
|
||||
hostgroup inspect [-h] <id or name> [<id or name> [<id or name>...]]
|
||||
hostgroup inspect [-h] <id or name>...
|
||||
hostgroup ls [-h]
|
||||
hostgroup rm [-h] <id or name> [<id or name> [<id or name>...]]
|
||||
hostgroup rm [-h] <id or name>...
|
||||
|
||||
# key management
|
||||
key help
|
||||
key create [-h] [--name=<value>] [--type=<value>] [--length=<value>] [--comment=<value>]
|
||||
key inspect [-h] <id or name> [<id or name> [<id or name>...]]
|
||||
key inspect [-h] <id or name>...
|
||||
key ls [-h]
|
||||
key rm [-h] <id or name> [<id or name> [<id or name>...]]
|
||||
key rm [-h] <id or name>...
|
||||
|
||||
# user management
|
||||
user help
|
||||
user invite [-h] [--name=<value>] [--comment=<value>] [--group=<value>] <email>
|
||||
user inspect [-h] <id or email> [<id or email> [<id or email>...]]
|
||||
user invite [-h] [--name=<value>] [--comment=<value>] [--group=<value>...] <email>
|
||||
user inspect [-h] <id or email>...
|
||||
user ls [-h]
|
||||
user rm [-h] <id or email> [<id or email> [<id or email>...]]
|
||||
user rm [-h] <id or email>...
|
||||
|
||||
# usergroup management
|
||||
usergroup help
|
||||
hostgroup create [-h] [--name=<value>] [--comment=<value>]
|
||||
usergroup inspect [-h] <id or name> [<id or name> [<id or name>...]]
|
||||
usergroup inspect [-h] <id or name>...
|
||||
usergroup ls [-h]
|
||||
usergroup rm [-h] <id or name> [<id or name> [<id or name>...]]
|
||||
usergroup rm [-h] <id or name>...
|
||||
|
||||
# other
|
||||
exit [-h]
|
||||
|
|
6
acl.go
6
acl.go
|
@ -2,7 +2,7 @@ package main
|
|||
|
||||
import "sort"
|
||||
|
||||
type ByWeight []ACL
|
||||
type ByWeight []*ACL
|
||||
|
||||
func (a ByWeight) Len() int { return len(a) }
|
||||
func (a ByWeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
@ -10,7 +10,7 @@ func (a ByWeight) Less(i, j int) bool { return a[i].Weight < a[j].Weight }
|
|||
|
||||
func CheckACLs(user User, host Host) (string, error) {
|
||||
// shared ACLs between user and host
|
||||
aclMap := map[uint]ACL{}
|
||||
aclMap := map[uint]*ACL{}
|
||||
for _, userGroup := range user.Groups {
|
||||
for _, userGroupACL := range userGroup.ACLs {
|
||||
for _, hostGroup := range host.Groups {
|
||||
|
@ -30,7 +30,7 @@ func CheckACLs(user User, host Host) (string, error) {
|
|||
}
|
||||
|
||||
// transofrm map to slice and sort it
|
||||
acls := []ACL{}
|
||||
acls := []*ACL{}
|
||||
for _, acl := range aclMap {
|
||||
acls = append(acls, acl)
|
||||
}
|
||||
|
|
50
db.go
50
db.go
|
@ -12,13 +12,13 @@ import (
|
|||
)
|
||||
|
||||
type Config struct {
|
||||
SSHKeys []SSHKey `json:"keys"`
|
||||
Hosts []Host `json:"hosts"`
|
||||
UserKeys []UserKey `json:"user_keys"`
|
||||
Users []User `json:"users"`
|
||||
UserGroups []UserGroup `json:"user_groups"`
|
||||
HostGroups []HostGroup `json:"host_groups"`
|
||||
ACLs []ACL `json:"acls"`
|
||||
SSHKeys []*SSHKey `json:"keys"`
|
||||
Hosts []*Host `json:"hosts"`
|
||||
UserKeys []*UserKey `json:"user_keys"`
|
||||
Users []*User `json:"users"`
|
||||
UserGroups []*UserGroup `json:"user_groups"`
|
||||
HostGroups []*HostGroup `json:"host_groups"`
|
||||
ACLs []*ACL `json:"acls"`
|
||||
Date time.Time `json:"date"`
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ type SSHKey struct {
|
|||
Fingerprint string
|
||||
PrivKey string `sql:"size:10000;"`
|
||||
PubKey string `sql:"size:10000;"`
|
||||
Hosts []Host
|
||||
Hosts []*Host `gorm:"ForeignKey:SSHKeyID"`
|
||||
Comment string
|
||||
}
|
||||
|
||||
|
@ -42,9 +42,9 @@ type Host struct {
|
|||
Addr string
|
||||
User string
|
||||
Password string
|
||||
SSHKey *SSHKey
|
||||
SSHKey *SSHKey `gorm:"ForeignKey:SSHKeyID"`
|
||||
SSHKeyID uint `gorm:"index"`
|
||||
Groups []HostGroup `gorm:"many2many:host_host_groups;"`
|
||||
Groups []*HostGroup `gorm:"many2many:host_host_groups;"`
|
||||
Fingerprint string // FIXME: replace with hostkey ?
|
||||
Comment string
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ type UserKey struct {
|
|||
gorm.Model
|
||||
Key []byte `sql:"size:10000;"`
|
||||
UserID uint
|
||||
User *User
|
||||
User *User `gorm:"ForeignKey:UserID"`
|
||||
Comment string
|
||||
}
|
||||
|
||||
|
@ -63,8 +63,8 @@ type User struct {
|
|||
IsAdmin bool
|
||||
Email string // FIXME: govalidator: email
|
||||
Name string // FIXME: govalidator: min length 3, alphanum
|
||||
Keys []UserKey
|
||||
Groups []UserGroup `gorm:"many2many:user_user_groups;"`
|
||||
Keys []*UserKey `gorm:"ForeignKey:UserID"`
|
||||
Groups []*UserGroup `gorm:"many2many:user_user_groups;"`
|
||||
Comment string
|
||||
InviteToken string
|
||||
}
|
||||
|
@ -72,23 +72,23 @@ type User struct {
|
|||
type UserGroup struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Users []User `gorm:"many2many:user_user_groups;"`
|
||||
ACLs []ACL `gorm:"many2many:user_group_acls;"`
|
||||
Users []*User `gorm:"many2many:user_user_groups;"`
|
||||
ACLs []*ACL `gorm:"many2many:user_group_acls;"`
|
||||
Comment string
|
||||
}
|
||||
|
||||
type HostGroup struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Hosts []Host `gorm:"many2many:host_host_groups;"`
|
||||
ACLs []ACL `gorm:"many2many:host_group_acls;"`
|
||||
Hosts []*Host `gorm:"many2many:host_host_groups;"`
|
||||
ACLs []*ACL `gorm:"many2many:host_group_acls;"`
|
||||
Comment string
|
||||
}
|
||||
|
||||
type ACL struct {
|
||||
gorm.Model
|
||||
HostGroups []HostGroup `gorm:"many2many:host_group_acls;"`
|
||||
UserGroups []UserGroup `gorm:"many2many:user_group_acls;"`
|
||||
HostGroups []*HostGroup `gorm:"many2many:host_group_acls;"`
|
||||
UserGroups []*UserGroup `gorm:"many2many:user_group_acls;"`
|
||||
HostPattern string
|
||||
Action string
|
||||
Weight uint
|
||||
|
@ -165,8 +165,8 @@ func dbInit(db *gorm.DB) error {
|
|||
var defaultHostGroup HostGroup
|
||||
db.Where("name = ?", "default").First(&defaultHostGroup)
|
||||
acl := ACL{
|
||||
UserGroups: []UserGroup{defaultUserGroup},
|
||||
HostGroups: []HostGroup{defaultHostGroup},
|
||||
UserGroups: []*UserGroup{&defaultUserGroup},
|
||||
HostGroups: []*HostGroup{&defaultHostGroup},
|
||||
Action: "allow",
|
||||
//HostPattern: "",
|
||||
//Weight: 0,
|
||||
|
@ -189,7 +189,7 @@ func dbInit(db *gorm.DB) error {
|
|||
Comment: "created by sshportal",
|
||||
IsAdmin: true,
|
||||
InviteToken: RandStringBytes(16),
|
||||
Groups: []UserGroup{defaultUserGroup},
|
||||
Groups: []*UserGroup{&defaultUserGroup},
|
||||
}
|
||||
db.Create(&user)
|
||||
log.Printf("Admin user created, use the user 'invite:%s' to associate a public key with this account", user.InviteToken)
|
||||
|
@ -225,9 +225,9 @@ func dbDemo(db *gorm.DB) error {
|
|||
}
|
||||
|
||||
var (
|
||||
host1 = Host{Name: "sdf", Addr: "sdf.org:22", User: "new", SSHKeyID: key.ID, Groups: []HostGroup{*hostGroup}}
|
||||
host2 = Host{Name: "whoami", Addr: "whoami.filippo.io:22", User: "test", SSHKeyID: key.ID, Groups: []HostGroup{*hostGroup}}
|
||||
host3 = Host{Name: "ssh-chat", Addr: "chat.shazow.net:22", User: "test", SSHKeyID: key.ID, Fingerprint: "MD5:e5:d5:d1:75:90:38:42:f6:c7:03:d7:d0:56:7d:6a:db", Groups: []HostGroup{*hostGroup}}
|
||||
host1 = Host{Name: "sdf", Addr: "sdf.org:22", User: "new", SSHKeyID: key.ID, Groups: []*HostGroup{hostGroup}}
|
||||
host2 = Host{Name: "whoami", Addr: "whoami.filippo.io:22", User: "test", SSHKeyID: key.ID, Groups: []*HostGroup{hostGroup}}
|
||||
host3 = Host{Name: "ssh-chat", Addr: "chat.shazow.net:22", User: "test", SSHKeyID: key.ID, Fingerprint: "MD5:e5:d5:d1:75:90:38:42:f6:c7:03:d7:d0:56:7d:6a:db", Groups: []*HostGroup{hostGroup}}
|
||||
)
|
||||
|
||||
// FIXME: check if hosts exist to avoid `UNIQUE constraint` error
|
||||
|
|
46
shell.go
46
shell.go
|
@ -75,8 +75,8 @@ GLOBAL OPTIONS:
|
|||
acl := ACL{
|
||||
Comment: c.String("comment"),
|
||||
HostPattern: c.String("pattern"),
|
||||
UserGroups: []UserGroup{},
|
||||
HostGroups: []HostGroup{},
|
||||
UserGroups: []*UserGroup{},
|
||||
HostGroups: []*HostGroup{},
|
||||
Weight: c.Uint("weight"),
|
||||
Action: c.String("action"),
|
||||
}
|
||||
|
@ -89,14 +89,14 @@ GLOBAL OPTIONS:
|
|||
if err != nil {
|
||||
return fmt.Errorf("unknown user group %q: %v", name, err)
|
||||
}
|
||||
acl.UserGroups = append(acl.UserGroups, *userGroup)
|
||||
acl.UserGroups = append(acl.UserGroups, userGroup)
|
||||
}
|
||||
for _, name := range c.StringSlice("hostgroup") {
|
||||
hostGroup, err := FindHostGroupByIdOrName(db, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unknown host group %q: %v", name, err)
|
||||
}
|
||||
acl.HostGroups = append(acl.HostGroups, *hostGroup)
|
||||
acl.HostGroups = append(acl.HostGroups, hostGroup)
|
||||
}
|
||||
|
||||
if len(acl.UserGroups) == 0 {
|
||||
|
@ -330,12 +330,12 @@ GLOBAL OPTIONS:
|
|||
ArgsUsage: "<user>[:<password>]@<host>[:<port>]",
|
||||
Description: "$> host create bart@foo.org\n $> host create bob:marley@example.com:2222",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "name", Usage: "Assigns a name to the host"},
|
||||
cli.StringFlag{Name: "password", Usage: "If present, sshportal will use password-based authentication"},
|
||||
cli.StringFlag{Name: "fingerprint", Usage: "SSH host key fingerprint"},
|
||||
cli.StringFlag{Name: "comment"},
|
||||
cli.StringFlag{Name: "key", Usage: "ID or name of the key to use for authentication"},
|
||||
cli.StringFlag{Name: "group", Usage: "Name or ID of the host group", Value: "default"},
|
||||
cli.StringFlag{Name: "name, n", Usage: "Assigns a name to the host"},
|
||||
cli.StringFlag{Name: "password, p", Usage: "If present, sshportal will use password-based authentication"},
|
||||
cli.StringFlag{Name: "fingerprint, f", Usage: "SSH host key fingerprint"},
|
||||
cli.StringFlag{Name: "comment, c"},
|
||||
cli.StringFlag{Name: "key, k", Usage: "ID or name of the key to use for authentication"},
|
||||
cli.StringSliceFlag{Name: "group, g", Usage: "Names or IDs of host groups (default: \"default\")"},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() != 1 {
|
||||
|
@ -373,11 +373,15 @@ GLOBAL OPTIONS:
|
|||
}
|
||||
|
||||
// host group
|
||||
hostGroup, err := FindHostGroupByIdOrName(db, c.String("group"))
|
||||
inputGroups := c.StringSlice("group")
|
||||
if len(inputGroups) == 0 {
|
||||
inputGroups = []string{"default"}
|
||||
}
|
||||
hostGroups, err := FindHostGroupsByIdOrName(db, inputGroups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
host.Groups = []HostGroup{*hostGroup}
|
||||
host.Groups = hostGroups
|
||||
|
||||
if err := db.Create(&host).Error; err != nil {
|
||||
return err
|
||||
|
@ -407,7 +411,7 @@ GLOBAL OPTIONS:
|
|||
Name: "ls",
|
||||
Usage: "Lists hosts",
|
||||
Action: func(c *cli.Context) error {
|
||||
var hosts []Host
|
||||
var hosts []*Host
|
||||
if err := db.Preload("Groups").Find(&hosts).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -515,7 +519,7 @@ GLOBAL OPTIONS:
|
|||
Name: "ls",
|
||||
Usage: "Lists host groups",
|
||||
Action: func(c *cli.Context) error {
|
||||
var hostGroups []HostGroup
|
||||
var hostGroups []*HostGroup
|
||||
if err := db.Preload("ACLs").Preload("Hosts").Find(&hostGroups).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -727,7 +731,7 @@ GLOBAL OPTIONS:
|
|||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "name", Usage: "Assigns a name to the user"},
|
||||
cli.StringFlag{Name: "comment"},
|
||||
cli.StringFlag{Name: "group", Usage: "Name or ID of the user group", Value: "default"},
|
||||
cli.StringSliceFlag{Name: "group, g", Usage: "Names or IDs of user groups (default: \"default\")"},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() != 1 {
|
||||
|
@ -750,11 +754,15 @@ GLOBAL OPTIONS:
|
|||
}
|
||||
|
||||
// user group
|
||||
userGroup, err := FindUserGroupByIdOrName(db, c.String("group"))
|
||||
inputGroups := c.StringSlice("group")
|
||||
if len(inputGroups) == 0 {
|
||||
inputGroups = []string{"default"}
|
||||
}
|
||||
userGroups, err := FindUserGroupsByIdOrName(db, inputGroups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.Groups = []UserGroup{*userGroup}
|
||||
user.Groups = userGroups
|
||||
|
||||
// save the user in database
|
||||
if err := db.Create(&user).Error; err != nil {
|
||||
|
@ -840,7 +848,7 @@ GLOBAL OPTIONS:
|
|||
// 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}
|
||||
userGroup.Users = []*User{&myself}
|
||||
|
||||
if err := db.Create(&userGroup).Error; err != nil {
|
||||
return err
|
||||
|
@ -870,7 +878,7 @@ GLOBAL OPTIONS:
|
|||
Name: "ls",
|
||||
Usage: "Lists user groups",
|
||||
Action: func(c *cli.Context) error {
|
||||
var userGroups []UserGroup
|
||||
var userGroups []*UserGroup
|
||||
if err := db.Preload("ACLs").Preload("Users").Find(&userGroups).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue