mirror of
https://github.com/moul/sshportal.git
synced 2025-09-06 20:54:27 +08:00
Host key checking shared across users
This commit is contained in:
parent
017ee2ab39
commit
511470087b
8 changed files with 80 additions and 21 deletions
|
@ -5,6 +5,7 @@
|
|||
* Create Session objects on each connections (history)
|
||||
* Connection history
|
||||
* Audit log
|
||||
* Add dynamic strict host key checking (learning on the first time, strict on the next ones)
|
||||
|
||||
## v1.4.0 (2017-11-24)
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ Jump host/Jump server without the jump, a.k.a Transparent SSH bastion
|
|||
* Sensitive data encryption
|
||||
* Session management
|
||||
* Audit log
|
||||
* Host Keys verifications shared across users
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -153,11 +154,11 @@ event inspect [-h] EVENT...
|
|||
|
||||
# host management
|
||||
host help
|
||||
host create [-h] [--name=<value>] [--password=<value>] [--fingerprint=<value>] [--comment=<value>] [--key=KEY] [--group=HOSTGROUP...] <username>[:<password>]@<host>[:<port>]
|
||||
host create [-h] [--name=<value>] [--password=<value>] [--comment=<value>] [--key=KEY] [--group=HOSTGROUP...] <username>[:<password>]@<host>[:<port>]
|
||||
host inspect [-h] [--decrypt] HOST...
|
||||
host ls [-h]
|
||||
host rm [-h] HOST...
|
||||
host update [-h] [--name=<value>] [--comment=<value>] [--fingerprint=<value>] [--key=KEY] [--assign-group=HOSTGROUP...] [--unassign-group=HOSTGROUP...] HOST...
|
||||
host update [-h] [--name=<value>] [--comment=<value>] [--key=KEY] [--assign-group=HOSTGROUP...] [--unassign-group=HOSTGROUP...] HOST...
|
||||
|
||||
# hostgroup management
|
||||
hostgroup help
|
||||
|
|
20
db.go
20
db.go
|
@ -34,6 +34,7 @@ type Setting struct {
|
|||
Value string `valid:"required"`
|
||||
}
|
||||
|
||||
// SSHKey defines a ssh client key (used by sshportal to connect to remote hosts)
|
||||
type SSHKey struct {
|
||||
// FIXME: use uuid for ID
|
||||
gorm.Model
|
||||
|
@ -50,17 +51,18 @@ type SSHKey struct {
|
|||
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"`
|
||||
SSHKey *SSHKey `gorm:"ForeignKey:SSHKeyID"`
|
||||
SSHKeyID uint `gorm:"index"`
|
||||
Groups []*HostGroup `gorm:"many2many:host_host_groups;"`
|
||||
Fingerprint string `valid:"optional"` // FIXME: replace with hostKey ?
|
||||
Comment string `valid:"optional"`
|
||||
Name string `gorm:"size:32" valid:"required,length(1|32),unix_user"`
|
||||
Addr string `valid:"required"`
|
||||
User string `valid:"optional"`
|
||||
Password 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"`
|
||||
Groups []*HostGroup `gorm:"many2many:host_host_groups;"`
|
||||
Comment string `valid:"optional"`
|
||||
}
|
||||
|
||||
// UserKey defines a user public key used by sshportal to identify the user
|
||||
type UserKey struct {
|
||||
gorm.Model
|
||||
Key []byte `sql:"size:10000" valid:"required,length(1|10000)"`
|
||||
|
|
22
dbinit.go
22
dbinit.go
|
@ -370,6 +370,28 @@ func dbInit(db *gorm.DB) error {
|
|||
Rollback: func(tx *gorm.DB) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
},
|
||||
}, {
|
||||
ID: "25",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
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"`
|
||||
SSHKey *SSHKey `gorm:"ForeignKey:SSHKeyID"` // SSHKey used to connect by the client
|
||||
SSHKeyID uint `gorm:"index"`
|
||||
HostKey []byte `sql:"size:10000" valid:"optional"`
|
||||
Groups []*HostGroup `gorm:"many2many:host_host_groups;"`
|
||||
Fingerprint string `valid:"optional"` // FIXME: replace with hostKey ?
|
||||
Comment string `valid:"optional"`
|
||||
}
|
||||
return tx.AutoMigrate(&Host{}).Error
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
},
|
||||
},
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
|
|
2
main.go
2
main.go
|
@ -167,7 +167,7 @@ func server(c *cli.Context) error {
|
|||
fmt.Fprintf(s, "error: %v\n", err)
|
||||
return
|
||||
}
|
||||
err := proxy(s, host)
|
||||
err := proxy(s, host, DynamicHostKey(db, host))
|
||||
sessUpdate := Session{}
|
||||
if err != nil {
|
||||
fmt.Fprintf(s, "error: %v\n", err)
|
||||
|
|
10
proxy.go
10
proxy.go
|
@ -10,8 +10,8 @@ import (
|
|||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func proxy(s ssh.Session, host *Host) error {
|
||||
config, err := host.ClientConfig(s)
|
||||
func proxy(s ssh.Session, host *Host, hk gossh.HostKeyCallback) error {
|
||||
config, err := host.ClientConfig(s, hk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ func proxy(s ssh.Session, host *Host) error {
|
|||
return err
|
||||
}
|
||||
|
||||
log.Println("SSH Connectin established")
|
||||
log.Println("SSH Connection established")
|
||||
return pipe(s.MaskedReqs(), rreqs, s, rch)
|
||||
}
|
||||
|
||||
|
@ -76,10 +76,10 @@ func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (host *Host) ClientConfig(_ ssh.Session) (*gossh.ClientConfig, error) {
|
||||
func (host *Host) ClientConfig(_ ssh.Session, hk gossh.HostKeyCallback) (*gossh.ClientConfig, error) {
|
||||
config := gossh.ClientConfig{
|
||||
User: host.User,
|
||||
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
|
||||
HostKeyCallback: hk,
|
||||
Auth: []gossh.AuthMethod{},
|
||||
}
|
||||
if host.SSHKey != nil {
|
||||
|
|
5
shell.go
5
shell.go
|
@ -539,7 +539,6 @@ GLOBAL OPTIONS:
|
|||
Flags: []cli.Flag{
|
||||
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: "`KEY` to use for authentication"},
|
||||
cli.StringSliceFlag{Name: "group, g", Usage: "Assigns the host to `HOSTGROUPS` (default: \"default\")"},
|
||||
|
@ -560,7 +559,6 @@ GLOBAL OPTIONS:
|
|||
if c.String("password") != "" {
|
||||
host.Password = c.String("password")
|
||||
}
|
||||
host.Fingerprint = c.String("fingerprint")
|
||||
host.Name = strings.Split(host.Hostname(), ".")[0]
|
||||
|
||||
if c.String("name") != "" {
|
||||
|
@ -708,7 +706,6 @@ GLOBAL OPTIONS:
|
|||
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: "fingerprint, f", Usage: "Update/set a host fingerprint, use \"none\" to unset"},
|
||||
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`"},
|
||||
|
@ -736,7 +733,7 @@ GLOBAL OPTIONS:
|
|||
for _, host := range hosts {
|
||||
model := tx.Model(&host)
|
||||
// simple fields
|
||||
for _, fieldname := range []string{"name", "comment", "password", "fingerprint"} {
|
||||
for _, fieldname := range []string{"name", "comment", "password"} {
|
||||
if c.String(fieldname) != "" {
|
||||
if err := model.Update(fieldname, c.String(fieldname)).Error; err != nil {
|
||||
tx.Rollback()
|
||||
|
|
36
ssh.go
Normal file
36
ssh.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type dynamicHostKey struct {
|
||||
db *gorm.DB
|
||||
host *Host
|
||||
}
|
||||
|
||||
func (d *dynamicHostKey) check(hostname string, remote net.Addr, key gossh.PublicKey) error {
|
||||
if len(d.host.HostKey) == 0 {
|
||||
log.Println("Discovering host fingerprint...")
|
||||
return d.db.Model(d.host).Update("HostKey", key.Marshal()).Error
|
||||
}
|
||||
|
||||
if !bytes.Equal(d.host.HostKey, key.Marshal()) {
|
||||
return fmt.Errorf("ssh: host key mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DynamicHostKey returns a function for use in
|
||||
// ClientConfig.HostKeyCallback to dynamically learn or accept host key.
|
||||
func DynamicHostKey(db *gorm.DB, host *Host) gossh.HostKeyCallback {
|
||||
// FIXME: forward interactively the host key checking
|
||||
hk := &dynamicHostKey{db, host}
|
||||
return hk.check
|
||||
}
|
Loading…
Add table
Reference in a new issue