From 2a68fc3114f30cc00466f4721b46d4ffcc94d8ab Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Wed, 3 Jan 2018 09:37:37 +0100 Subject: [PATCH] Support having different host.Scheme --- db.go | 137 ++++++++++++++++++++++++++++++++++++++++++------------ dbinit.go | 22 +++++++++ shell.go | 36 +++++++++----- ssh.go | 2 +- 4 files changed, 154 insertions(+), 43 deletions(-) diff --git a/db.go b/db.go index 68c6c9b..03af4ba 100644 --- a/db.go +++ b/db.go @@ -6,6 +6,7 @@ import ( "log" "net/url" "regexp" + "strconv" "strings" "time" @@ -53,9 +54,10 @@ type Host struct { // FIXME: use uuid for ID gorm.Model Name string `gorm:"size:32" valid:"required,length(1|32),unix_user"` - Addr string `valid:"required"` - User string `valid:"optional"` - Password string `valid:"optional"` + Addr string `valid:"optional"` // FIXME: to be removed in a future version in favor of URL + User string `valid:"optional"` // FIXME: to be removed in a future version in favor of URL + Password string `valid:"optional"` // FIXME: to be removed in a future version in favor of URL + URL string `valid:"optional"` SSHKey *SSHKey `gorm:"ForeignKey:SSHKeyID"` // SSHKey used to connect by the client SSHKeyID uint `gorm:"index"` HostKey []byte `sql:"size:10000" valid:"optional"` @@ -168,37 +170,112 @@ func init() { })) } -func (host *Host) URL() string { - return fmt.Sprintf("%s@%s", host.User, host.Addr) -} - -func NewHostFromURL(rawurl string) (*Host, error) { - if !strings.Contains(rawurl, "://") { - rawurl = "ssh://" + rawurl +// Host helpers +func ParseInputURL(input string) (*url.URL, error) { + if !strings.Contains(input, "://") { + input = "ssh://" + input } - u, err := url.Parse(rawurl) + u, err := url.Parse(input) if err != nil { return nil, err } - host := Host{Addr: u.Host} - if !strings.Contains(host.Addr, ":") { - host.Addr += ":22" // add port if not present - } - host.User = "root" // default username - if u.User != nil { - password, _ := u.User.Password() - host.Password = password - host.User = u.User.Username() - } - return &host, nil + return u, nil +} +func (host *Host) DialAddr() string { + return fmt.Sprintf("%s:%d", host.Hostname(), host.Port()) +} +func (host *Host) String() string { + if host.URL != "" { + return host.URL + } else if host.Addr != "" { // to be removed in a future version in favor of URL + if host.Password != "" { + return fmt.Sprintf("ssh://%s:%s@%s", host.User, strings.Repeat("*", 4), host.Addr) + } + return fmt.Sprintf("ssh://%s@%s", host.User, host.Addr) + } + return "" +} +func (host *Host) Scheme() string { + if host.URL != "" { + u, err := url.Parse(host.URL) + if err != nil { + return "ssh" + } + return u.Scheme + } else if host.Addr != "" { + return "ssh" + } + return "" } - func (host *Host) Hostname() string { - return strings.Split(host.Addr, ":")[0] + if host.URL != "" { + u, err := url.Parse(host.URL) + if err != nil { + return "" + } + return u.Hostname() + } else if host.Addr != "" { // to be removed in a future version in favor of URL + return strings.Split(host.Addr, ":")[0] + } + return "" +} +func (host *Host) Username() string { + if host.URL != "" { + u, err := url.Parse(host.URL) + if err != nil { + return "root" + } + if u.User != nil { + return u.User.Username() + } + } else if host.User != "" { // to be removed in a future version in favor of URL + return host.User + } + return "root" +} +func (host *Host) Passwd() string { + if host.URL != "" { + u, err := url.Parse(host.URL) + if err != nil { + return "" + } + if u.User != nil { + password, _ := u.User.Password() + return password + } + } else if host.Password != "" { // to be removed in a future version in favor of URL + return host.Password + } + return "" +} +func (host *Host) Port() uint64 { + var portString string + if host.URL != "" { + u, err := url.Parse(host.URL) + if err != nil { + goto defaultPort + } + portString = u.Port() + } else if host.Addr != "" { // to be removed in a future version in favor of URL + portString = strings.Split(host.Addr, ":")[1] + } + if portString != "" { + port, err := strconv.ParseUint(portString, 10, 64) + if err != nil { + goto defaultPort + } + return port + } +defaultPort: + switch host.Scheme() { + case "ssh": + return 22 + case "telnet": + return 23 + default: + return 0 + } } - -// Host helpers - func HostsPreload(db *gorm.DB) *gorm.DB { return db.Preload("Groups").Preload("SSHKey") } @@ -217,7 +294,7 @@ func HostByName(db *gorm.DB, name string) (*Host, error) { func (host *Host) clientConfig(hk gossh.HostKeyCallback) (*gossh.ClientConfig, error) { config := gossh.ClientConfig{ - User: host.User, + User: host.Username(), HostKeyCallback: hk, Auth: []gossh.AuthMethod{}, } @@ -228,8 +305,8 @@ func (host *Host) clientConfig(hk gossh.HostKeyCallback) (*gossh.ClientConfig, e } config.Auth = append(config.Auth, gossh.PublicKeys(signer)) } - if host.Password != "" { - config.Auth = append(config.Auth, gossh.Password(host.Password)) + if host.Passwd() != "" { + config.Auth = append(config.Auth, gossh.Password(host.Passwd())) } if len(config.Auth) == 0 { return nil, fmt.Errorf("no valid authentication method for host %q", host.Name) diff --git a/dbinit.go b/dbinit.go index 6731f66..a216425 100644 --- a/dbinit.go +++ b/dbinit.go @@ -436,6 +436,28 @@ func dbInit(db *gorm.DB) error { Rollback: func(tx *gorm.DB) error { return fmt.Errorf("not implemented") }, + }, { + ID: "28", + Migrate: func(tx *gorm.DB) error { + type Host struct { + // FIXME: use uuid for ID + gorm.Model + Name string `gorm:"size:32"` + Addr string + User string + Password string + URL string + SSHKey *SSHKey `gorm:"ForeignKey:SSHKeyID"` + SSHKeyID uint `gorm:"index"` + HostKey []byte `sql:"size:10000"` + Groups []*HostGroup `gorm:"many2many:host_host_groups;"` + Comment string + } + return tx.AutoMigrate(&Host{}).Error + }, + Rollback: func(tx *gorm.DB) error { + return fmt.Errorf("not implemented") + }, }, }) if err := m.Migrate(); err != nil { diff --git a/shell.go b/shell.go index 753c390..32f31ce 100644 --- a/shell.go +++ b/shell.go @@ -635,7 +635,7 @@ GLOBAL OPTIONS: { Name: "create", Usage: "Creates a new host", - ArgsUsage: "[:]@[:]", + ArgsUsage: "[scheme://][:]@[:]", Description: "$> host create bart@foo.org\n $> host create bob:marley@example.com:2222", Flags: []cli.Flag{ cli.StringFlag{Name: "name, n", Usage: "Assigns a name to the host"}, @@ -653,10 +653,14 @@ GLOBAL OPTIONS: return err } - host, err := NewHostFromURL(c.Args().First()) + u, err := ParseInputURL(c.Args().First()) if err != nil { return err } + host := &Host{ + URL: u.String(), + Comment: c.String("comment"), + } if c.String("password") != "" { host.Password = c.String("password") } @@ -666,7 +670,6 @@ GLOBAL OPTIONS: host.Name = c.String("name") } // FIXME: check if name already exists - host.Comment = c.String("comment") if _, err := govalidator.ValidateStruct(host); err != nil { return err @@ -773,14 +776,11 @@ GLOBAL OPTIONS: } table := tablewriter.NewWriter(s) - table.SetHeader([]string{"ID", "Name", "URL", "Key", "Pass", "Groups", "Updated", "Created", "Comment"}) + table.SetHeader([]string{"ID", "Name", "URL", "Key", "Groups", "Updated", "Created", "Comment"}) table.SetBorder(false) table.SetCaption(true, fmt.Sprintf("Total: %d hosts.", len(hosts))) for _, host := range hosts { - authKey, authPass := "", "" - if host.Password != "" { - authPass = "yes" - } + authKey := "" if host.SSHKeyID > 0 { var key SSHKey db.Model(&host).Related(&key) @@ -793,9 +793,8 @@ GLOBAL OPTIONS: table.Append([]string{ fmt.Sprintf("%d", host.ID), host.Name, - host.URL(), + host.String(), authKey, - authPass, strings.Join(groupNames, ", "), humanize.Time(host.UpdatedAt), humanize.Time(host.CreatedAt), @@ -827,7 +826,7 @@ GLOBAL OPTIONS: ArgsUsage: "HOST...", Flags: []cli.Flag{ cli.StringFlag{Name: "name, n", Usage: "Rename the host"}, - cli.StringFlag{Name: "password, p", Usage: "Update/set a password, use \"none\" to unset"}, + cli.StringFlag{Name: "url, u", Usage: "Update connection URL"}, cli.StringFlag{Name: "comment, c", Usage: "Update/set a host comment"}, cli.StringFlag{Name: "key, k", Usage: "Link a `KEY` to use for authentication"}, cli.StringSliceFlag{Name: "assign-group, g", Usage: "Assign the host to a new `HOSTGROUPS`"}, @@ -855,7 +854,7 @@ GLOBAL OPTIONS: for _, host := range hosts { model := tx.Model(&host) // simple fields - for _, fieldname := range []string{"name", "comment", "password"} { + for _, fieldname := range []string{"name", "comment"} { if c.String(fieldname) != "" { if err := model.Update(fieldname, c.String(fieldname)).Error; err != nil { tx.Rollback() @@ -864,6 +863,19 @@ GLOBAL OPTIONS: } } + // url + if c.String("url") != "" { + u, err := ParseInputURL(c.String("url")) + if err != nil { + tx.Rollback() + return err + } + if err := model.Update("url", u.String()).Error; err != nil { + tx.Rollback() + return err + } + } + // associations if c.String("key") != "" { var key SSHKey diff --git a/ssh.go b/ssh.go index eadeae0..01efed8 100644 --- a/ssh.go +++ b/ssh.go @@ -128,7 +128,7 @@ func channelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewCh } err = bastionsession.ChannelHandler(srv, conn, newChan, ctx, bastionsession.Config{ - Addr: host.Addr, + Addr: host.DialAddr(), ClientConfig: clientConfig, })