Support having different host.Scheme

This commit is contained in:
Manfred Touron 2018-01-03 09:37:37 +01:00
parent 2352a53e6e
commit 2a68fc3114
4 changed files with 154 additions and 43 deletions

137
db.go
View file

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

View file

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

View file

@ -635,7 +635,7 @@ GLOBAL OPTIONS:
{
Name: "create",
Usage: "Creates a new host",
ArgsUsage: "<user>[:<password>]@<host>[:<port>]",
ArgsUsage: "[scheme://]<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, 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

2
ssh.go
View file

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