Merge pull request #188 from moul/dev/moul/fix-166

This commit is contained in:
Manfred Touron 2020-07-08 10:12:52 +02:00 committed by GitHub
commit e43bb55e70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 154 additions and 166 deletions

17
.gitattributes vendored Normal file
View file

@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
# Collapse vendored and generated files on GitHub
AUTHORS linguist-generated
vendor/* linguist-vendored
rules.mk linguist-vendored
*/vendor/* linguist-vendored
*.gen.* linguist-generated
*.pb.go linguist-generated
*.pb.gw.go linguist-generated
go.sum linguist-generated
go.mod linguist-generated
gen.sum linguist-generated
# Reduce conflicts on markdown files
*.md merge=union

3
.gitignore vendored
View file

@ -7,4 +7,5 @@ dist/
/log/
/sshportal
*.db
/data
/data
sshportal.history

View file

@ -1,121 +1,3 @@
# Changelog
## master (unreleased)
* No entry
## v1.10.0 (2019-06-24)
* Bump deps, now using github.com/gliderlabs/ssh upstream
* Fix Windows build ([#101](https://github.com/moul/sshportal/pull/101)) by [@Raerten](https://github.com/Raerten)
* Use environment variables for settings ([#98](https://github.com/moul/sshportal/pull/98)) by [@Raerten](https://github.com/Raerten)
* Fix 'userkey create' ([#111](https://github.com/moul/sshportal/pull/111)) by [@shawn111](https://github.com/shawn111)
* Set log files mode to 440 instead of 640 ([#134](https://github.com/moul/sshportal/pull/134)) by [@jle64](https://github.com/jle64)
* Allow to create a host using an IP as name ([#135](https://github.com/moul/sshportal/pull/135)) by [@jle64](https://github.com/jle64)
* Add username and session ID to session log filename ([#133](https://github.com/moul/sshportal/pull/133)) by [@jle64](https://github.com/jle64)
* Unable to use encrypted SSH private keys ([#124](https://github.com/moul/sshportal/pull/124)) by [@welderpb](https://github.com/welderpb)
* Fix format of ID in new session + closing channel if host is unreachable ([#123](https://github.com/moul/sshportal/pull/123)) by [@vdaviot](https://github.com/vdaviot)
* Refactor the main package with a focus on splitting up into packages ([#113](https://github.com/moul/sshportal/pull/113)) by [@ahamidullah](https://github.com/ahamidullah)
## v1.9.0 (2018-11-18)
* Add `hostgroup update` and `usergroup update` commands ([#58](https://github.com/moul/sshportal/pull/58)) by [@adyxax](https://github.com/adyxax)
* Add socket timeout ([#80](https://github.com/moul/sshportal/pull/80)) by [@ahhx](https://github.com/ahhx)
* Add a flag to list only active sessions ([#76](https://github.com/moul/sshportal/pull/76)) by [@vdaviot](https://github.com/vdaviot)
* Unset hop on host ([#74](https://github.com/moul/sshportal/pull/74)) by [@vdaviot](https://github.com/vdaviot)
* Fix session status and duration display ([#75](https://github.com/moul/sshportal/pull/75)) by [@vdaviot](https://github.com/vdaviot)
* Fix log path and filename on Windows ([#78](https://github.com/moul/sshportal/pull/78)) by [@Raerten](https://github.com/Raerten)
* Admin user is not editable ([#69](https://github.com/moul/sshportal/pull/69)) by [@alenn-m](https://github.com/alenn-m)
* Switch to go modules (go1.11) ([#83](https://github.com/moul/sshportal/pull/83))
* Switch to moul.io/sshportal canonical URL ([#86](https://github.com/moul/sshportal/pull/86))
* Switch to golangci-lint ([#87](https://github.com/moul/sshportal/pull/87))
## v1.8.0 (2018-04-02)
* The default created user now has the same username as the user starting sshportal (was hardcoded "admin")
* Add Telnet support
* Add TTY audit feature ([#23](https://github.com/moul/sshportal/issues/23)) by [@sabban](https://github.com/sabban)
* Fix `--assign-*` commands when using MySQL driver ([#45](https://github.com/moul/sshportal/issues/45))
* Add *HOP* support, an efficient and integrated way of using a jump host transparently ([#47](https://github.com/moul/sshportal/issues/47)) by [@mathieui](https://github.com/mathieui)
* Fix panic on some `ls` commands ([#54](https://github.com/moul/sshportal/pull/54)) by [@jle64](https://github.com/jle64)
* Add tunnels (`direct-tcp`) support with logging ([#44](https://github.com/moul/sshportal/issues/44)) by [@sabban](https://github.com/sabban)
* Add `key import` command ([#52](https://github.com/moul/sshportal/issues/52)) by [@adyxax](https://github.com/adyxax)
* Add 'exec' logging ([#40](https://github.com/moul/sshportal/issues/40)) by [@sabban](https://github.com/sabban)
## v1.7.1 (2018-01-03)
* Return non-null exit-code on authentication error
* **hotfix**: repair invite system (broken in v1.7.0)
## v1.7.0 (2018-01-02)
Breaking changes:
* Use `sshportal server` instead of `sshportal` to start a new server (nothing to change if using the docker image)
* Remove `--config-user` and `--healthcheck-user` global options
Changes:
* Fix connection failure when sending too many environment variables (fix [#22](https://github.com/moul/sshportal/issues/22))
* Fix panic when entering empty command (fix [#13](https://github.com/moul/sshportal/issues/13))
* Add `config backup --ignore-events` option
* Add `sshportal healthcheck [--addr=] [--wait] [--quiet]` cli command
* Add [Docker Healthcheck](https://docs.docker.com/engine/reference/builder/#healthcheck) helper
* Support Putty (fix [#24](https://github.com/moul/sshportal/issues/24))
## v1.6.0 (2017-12-12)
* Add `--latest` and `--quiet` options to `ls` commands
* Add `healthcheck` user
* Add `key show KEY` command
## v1.5.0 (2017-12-02)
* 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)
* Add-back MySQL support (experimental)
* Fix some backup/restore bugs
## v1.4.0 (2017-11-24)
* Add 'key setup' command (easy SSH key installation)
* Add Updated and Created fields in 'ls' commands
* Add `--aes-key` option to encrypt sensitive data
## v1.3.0 (2017-11-23)
* More details in 'ls' commands
* Add 'host update' command (fix [#2](https://github.com/moul/sshportal/issues/2))
* Add 'user update' command (fix [#3](https://github.com/moul/sshportal/issues/3))
* Add 'acl update' command (fix [#4](https://github.com/moul/sshportal/issues/4))
* Allow connecting to the shell mode with the registered username or email (fix [#5](https://github.com/moul/sshportal/issues/5))
* Add 'listhosts' role (fix [#5](https://github.com/moul/sshportal/issues/5))
## v1.2.0 (2017-11-22)
* Support adding multiple `--group` links on `host create` and `user create`
* Use govalidator to perform more consistent input validation
* Use a database migration system
## v1.1.0 (2017-11-15)
* Improve versionning (static VERSION + dynamic GIT_* info)
* Configuration management (backup + restore)
* Implement Exit (fix [#6](https://github.com/moul/sshportal/pull/6))
* Disable mysql support (not fully working right now)
* Set random seed properly
## v1.0.0 (2017-11-14)
Initial version
* Host management
* User management
* User Group management
* Host Group management
* Host Key management
* User Key management
* ACL management
* Connect to host using key or password
* Admin commands can be run directly or in an interactive shell
Here: https://github.com/moul/sshportal/releases

View file

@ -5,6 +5,7 @@ DOCKER_IMAGE ?= moul/sshportal
VERSION ?= `git describe --tags --always`
VCS_REF ?= `git rev-parse --short HEAD`
GO_INSTALL_OPTS = -ldflags="-X main.GitSha=$(VCS_REF) -X main.GitTag=$(VERSION)"
PORT ?= 2222
include rules.mk

View file

@ -326,11 +326,11 @@ event inspect [-h] EVENT...
# host management
host help
host create [-h] [--name=<value>] [--password=<value>] [--comment=<value>] [--key=KEY] [--group=HOSTGROUP...] [--hop=HOST] <username>[:<password>]@<host>[:<port>]
host create [-h] [--name=<value>] [--password=<value>] [--comment=<value>] [--key=KEY] [--group=HOSTGROUP...] [--hop=HOST] [--logging=MODE] <username>[:<password>]@<host>[:<port>]
host inspect [-h] [--decrypt] HOST...
host ls [-h] [--latest] [--quiet]
host rm [-h] HOST...
host update [-h] [--name=<value>] [--comment=<value>] [--key=KEY] [--assign-group=HOSTGROUP...] [--unassign-group=HOSTGROUP...] [--set-hop=HOST] [--unset-hop] HOST...
host update [-h] [--name=<value>] [--comment=<value>] [--key=KEY] [--assign-group=HOSTGROUP...] [--unassign-group=HOSTGROUP...] [--logging-MODE] [--set-hop=HOST] [--unset-hop] HOST...
# hostgroup management
hostgroup help

3
go.mod generated
View file

@ -9,7 +9,7 @@ require (
github.com/docker/docker v1.13.1
github.com/dustin/go-humanize v1.0.0
github.com/gliderlabs/ssh v0.3.0
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/go-sql-driver/mysql v1.5.0
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/jinzhu/gorm v1.9.14
github.com/kr/pty v1.1.8
@ -18,6 +18,7 @@ require (
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
github.com/olekukonko/tablewriter v0.0.4
github.com/pkg/errors v0.9.1
github.com/reiver/go-oi v1.0.0
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e
github.com/sabban/bastion v0.0.0-20180110125408-b9d3c9b1f4d3

8
go.sum generated
View file

@ -43,8 +43,6 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jinzhu/gorm v1.9.2 h1:lCvgEaqe/HVE+tjAR2mt4HbbHAZsQOv3XAZiEZV37iw=
github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q=
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
github.com/jinzhu/gorm v1.9.14 h1:Kg3ShyTPcM6nzVo148fRrcMO6MNKuqtOUwnzqMgVniM=
github.com/jinzhu/gorm v1.9.14/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k=
@ -75,13 +73,14 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM=
@ -108,8 +107,6 @@ golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -128,7 +125,6 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI=
gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw=

View file

@ -6,6 +6,7 @@ import (
"os"
"path"
_ "github.com/go-sql-driver/mysql"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/urfave/cli"

View file

@ -40,7 +40,6 @@ func DBInit(db *gorm.DB) error {
ID: "2",
Migrate: func(tx *gorm.DB) error {
type SSHKey struct {
// FIXME: use uuid for ID
gorm.Model
Name string
Type string
@ -60,7 +59,6 @@ func DBInit(db *gorm.DB) error {
ID: "3",
Migrate: func(tx *gorm.DB) error {
type Host struct {
// FIXME: use uuid for ID
gorm.Model
Name string `gorm:"size:32"`
Addr string
@ -96,7 +94,6 @@ func DBInit(db *gorm.DB) error {
ID: "5",
Migrate: func(tx *gorm.DB) error {
type User struct {
// FIXME: use uuid for ID
gorm.Model
IsAdmin bool
Email string
@ -382,17 +379,16 @@ func DBInit(db *gorm.DB) error {
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 *dbmodels.SSHKey `gorm:"ForeignKey:SSHKeyID"` // SSHKey used to connect by the client
SSHKey *dbmodels.SSHKey `gorm:"ForeignKey:SSHKeyID"`
SSHKeyID uint `gorm:"index"`
HostKey []byte `sql:"size:10000" valid:"optional"`
Groups []*dbmodels.HostGroup `gorm:"many2many:host_host_groups;"`
Fingerprint string `valid:"optional"` // FIXME: replace with hostKey ?
Fingerprint string `valid:"optional"`
Comment string `valid:"optional"`
}
return tx.AutoMigrate(&Host{}).Error
@ -443,7 +439,6 @@ func DBInit(db *gorm.DB) error {
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
@ -465,7 +460,6 @@ func DBInit(db *gorm.DB) error {
ID: "29",
Migrate: func(tx *gorm.DB) error {
type Host struct {
// FIXME: use uuid for ID
gorm.Model
Name string `gorm:"size:32"`
Addr string
@ -485,6 +479,38 @@ func DBInit(db *gorm.DB) error {
Rollback: func(tx *gorm.DB) error {
return fmt.Errorf("not implemented")
},
}, {
ID: "30",
Migrate: func(tx *gorm.DB) error {
type Host struct {
gorm.Model
Name string `gorm:"size:32"`
Addr string
User string
Password string
URL string
SSHKey *dbmodels.SSHKey `gorm:"ForeignKey:SSHKeyID"`
SSHKeyID uint `gorm:"index"`
HostKey []byte `sql:"size:10000"`
Groups []*dbmodels.HostGroup `gorm:"many2many:host_host_groups;"`
Comment string
Hop *dbmodels.Host
Logging string
HopID uint
}
return tx.AutoMigrate(&Host{}).Error
},
Rollback: func(tx *gorm.DB) error {
return fmt.Errorf("not implemented")
},
}, {
ID: "31",
Migrate: func(tx *gorm.DB) error {
return tx.Model(&dbmodels.Host{}).Updates(&dbmodels.Host{Logging: "everything"}).Error
},
Rollback: func(tx *gorm.DB) error {
return fmt.Errorf("not implemented")
},
},
})
if err := m.Migrate(); err != nil {

View file

@ -3,20 +3,23 @@ package bastion // import "moul.io/sshportal/pkg/bastion"
import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"path/filepath"
"time"
"github.com/gliderlabs/ssh"
"github.com/pkg/errors"
"github.com/sabban/bastion/pkg/logchannel"
gossh "golang.org/x/crypto/ssh"
)
type sessionConfig struct {
Addr string
Logs string
LogsLocation string
ClientConfig *gossh.ClientConfig
LoggingMode string
}
func multiChannelHandler(conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context, configs []sessionConfig, sessionID uint) error {
@ -62,7 +65,7 @@ func multiChannelHandler(conn *gossh.ServerConn, newChan gossh.NewChannel, ctx s
actx := ctx.Value(authContextKey).(*authContext)
username := actx.user.Name
// pipe everything
return pipe(lreqs, rreqs, lch, rch, configs[len(configs)-1].Logs, user, username, sessionID, newChan)
return pipe(lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan)
case "direct-tcpip":
lch, lreqs, err := newChan.Accept()
// TODO: defer clean closer
@ -107,7 +110,7 @@ func multiChannelHandler(conn *gossh.ServerConn, newChan gossh.NewChannel, ctx s
actx := ctx.Value(authContextKey).(*authContext)
username := actx.user.Name
// pipe everything
return pipe(lreqs, rreqs, lch, rch, configs[len(configs)-1].Logs, user, username, sessionID, newChan)
return pipe(lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan)
default:
if err := newChan.Reject(gossh.UnknownChannelType, "unsupported channel type"); err != nil {
log.Printf("failed to reject chan: %v", err)
@ -116,7 +119,7 @@ func multiChannelHandler(conn *gossh.ServerConn, newChan gossh.NewChannel, ctx s
}
}
func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, logsLocation string, user string, username string, sessionID uint, newChan gossh.NewChannel) error {
func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, sessConfig sessionConfig, user string, username string, sessionID uint, newChan gossh.NewChannel) error {
defer func() {
_ = lch.Close()
_ = rch.Close()
@ -126,36 +129,51 @@ func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, logsLocati
quit := make(chan string, 1)
channeltype := newChan.ChannelType()
filename := strings.Join([]string{logsLocation, "/", user, "-", username, "-", channeltype, "-", fmt.Sprint(sessionID), "-", time.Now().Format(time.RFC3339)}, "") // get user
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0440)
defer func() {
_ = f.Close()
}()
if err != nil {
log.Fatalf("error: %v", err)
var logWriter io.WriteCloser = newDiscardWriteCloser()
if sessConfig.LoggingMode != "disabled" {
filename := filepath.Join(sessConfig.LogsLocation, fmt.Sprintf("%s-%s-%s-%d-%s", user, username, channeltype, sessionID, time.Now().Format(time.RFC3339)))
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0440)
if err != nil {
return errors.Wrap(err, "open log file")
}
defer func() {
_ = f.Close()
}()
log.Printf("Session %v is recorded in %v", channeltype, filename)
logWriter = f
}
log.Printf("Session %v is recorded in %v", channeltype, filename)
if channeltype == "session" {
wrappedlch := logchannel.New(lch, f)
go func(quit chan string) {
_, _ = io.Copy(wrappedlch, rch)
quit <- "rch"
}(quit)
go func(quit chan string) {
_, _ = io.Copy(rch, lch)
quit <- "lch"
}(quit)
switch sessConfig.LoggingMode {
case "input":
wrappedrch := logchannel.New(rch, logWriter)
go func(quit chan string) {
_, _ = io.Copy(lch, rch)
quit <- "rch"
}(quit)
go func(quit chan string) {
_, _ = io.Copy(wrappedrch, lch)
quit <- "lch"
}(quit)
default: // everything, disabled
wrappedlch := logchannel.New(lch, logWriter)
go func(quit chan string) {
_, _ = io.Copy(wrappedlch, rch)
quit <- "rch"
}(quit)
go func(quit chan string) {
_, _ = io.Copy(rch, lch)
quit <- "lch"
}(quit)
}
}
if channeltype == "direct-tcpip" {
d := logTunnelForwardData{}
if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil {
return err
}
wrappedlch := newLogTunnel(lch, f, d.SourceHost)
wrappedrch := newLogTunnel(rch, f, d.DestinationHost)
wrappedlch := newLogTunnel(lch, logWriter, d.SourceHost)
wrappedrch := newLogTunnel(rch, logWriter, d.DestinationHost)
go func(quit chan string) {
_, _ = io.Copy(wrappedlch, rch)
quit <- "rch"
@ -171,7 +189,7 @@ func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, logsLocati
for req := range lreqs {
b, err := rch.SendRequest(req.Type, req.WantReply, req.Payload)
if req.Type == "exec" {
wrappedlch := logchannel.New(lch, f)
wrappedlch := logchannel.New(lch, logWriter)
command := append(req.Payload, []byte("\n")...)
if _, err := wrappedlch.LogWrite(command); err != nil {
log.Printf("failed to write log: %v", err)
@ -234,3 +252,13 @@ func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, logsLocati
}
}
}
func newDiscardWriteCloser() io.WriteCloser { return &discardWriteCloser{ioutil.Discard} }
type discardWriteCloser struct {
io.Writer
}
func (discardWriteCloser) Close() error {
return nil
}

View file

@ -672,6 +672,7 @@ GLOBAL OPTIONS:
cli.StringFlag{Name: "comment, c"},
cli.StringFlag{Name: "key, k", Usage: "`KEY` to use for authentication"},
cli.StringFlag{Name: "hop, o", Usage: "Hop to use for connecting to the server"},
cli.StringFlag{Name: "logging, l", Usage: "Logging mode (disabled, input, everything)"},
cli.StringSliceFlag{Name: "group, g", Usage: "Assigns the host to `HOSTGROUPS` (default: \"default\")"},
},
Action: func(c *cli.Context) error {
@ -714,6 +715,11 @@ GLOBAL OPTIONS:
if c.String("name") != "" {
host.Name = c.String("name")
}
host.Logging = "everything" // default is everything
if c.String("logging") != "" {
host.Logging = c.String("logging")
}
// FIXME: check if name already exists
if _, err := govalidator.ValidateStruct(host); err != nil {
@ -819,7 +825,7 @@ GLOBAL OPTIONS:
}
table := tablewriter.NewWriter(s)
table.SetHeader([]string{"ID", "Name", "URL", "Key", "Groups", "Updated", "Created", "Comment", "Hop"})
table.SetHeader([]string{"ID", "Name", "URL", "Key", "Groups", "Updated", "Created", "Comment", "Hop", "Logging"})
table.SetBorder(false)
table.SetCaption(true, fmt.Sprintf("Total: %d hosts.", len(hosts)))
for _, host := range hosts {
@ -851,6 +857,7 @@ GLOBAL OPTIONS:
humanize.Time(host.CreatedAt),
host.Comment,
hop,
host.Logging,
//FIXME: add some stats about last access time etc
})
}
@ -882,6 +889,7 @@ GLOBAL OPTIONS:
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.StringFlag{Name: "hop, o", Usage: "Change the hop to use for connecting to the server"},
cli.StringFlag{Name: "logging, l", Usage: "Logging mode (disabled, input, everything)"},
cli.BoolFlag{Name: "unset-hop", Usage: "Remove the hop set for this host"},
cli.StringSliceFlag{Name: "assign-group, g", Usage: "Assign the host to a new `HOSTGROUPS`"},
cli.StringSliceFlag{Name: "unassign-group", Usage: "Unassign the host from a `HOSTGROUPS`"},
@ -944,6 +952,17 @@ GLOBAL OPTIONS:
}
}
// logging
if logging := c.String("logging"); logging != "" {
if !dbmodels.IsValidHostLoggingMode(logging) {
return fmt.Errorf("invalid host logging mode: %q", logging)
}
if err := model.Update("logging", logging).Error; err != nil {
tx.Rollback()
return err
}
}
// remove the hop
if c.Bool("unset-hop") {
var hopHost dbmodels.Host

View file

@ -122,7 +122,8 @@ func ChannelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewCh
sessionConfigs = append([]sessionConfig{{
Addr: currentHost.DialAddr(),
ClientConfig: clientConfig,
Logs: actx.logsLocation,
LogsLocation: actx.logsLocation,
LoggingMode: currentHost.Logging,
}}, sessionConfigs...)
if currentHost.HopID != 0 {
var newHost dbmodels.Host

View file

@ -61,6 +61,7 @@ type Host struct {
HostKey []byte `sql:"size:10000" valid:"optional"`
Groups []*HostGroup `gorm:"many2many:host_host_groups;"`
Comment string `valid:"optional"`
Logging string `valid:"optional,host_logging_mode"`
Hop *Host
HopID uint
}

View file

@ -16,4 +16,18 @@ func InitValidator() {
}
return unixUserRegexp.MatchString(name)
}))
govalidator.CustomTypeTagMap.Set("host_logging_mode", govalidator.CustomTypeValidator(func(i interface{}, context interface{}) bool {
name, ok := i.(string)
if !ok {
return false
}
if name == "" {
return true
}
return IsValidHostLoggingMode(name)
}))
}
func IsValidHostLoggingMode(name string) bool {
return name == "disabled" || name == "input" || name == "everything"
}