Support assign multiple groups to hosts and users (#2)

This commit is contained in:
Manfred Touron 2017-11-15 09:52:59 +01:00
parent d6a7a6702f
commit f97c9f2878
5 changed files with 77 additions and 69 deletions

View file

@ -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)

View file

@ -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
View file

@ -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
View file

@ -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

View file

@ -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
}