From 64ba179cc77070b2582f600c6d2c1a74ca821839 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Sat, 4 Jul 2020 21:32:59 +0200 Subject: [PATCH 1/3] chore: add .gitattributes Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com> --- .gitattributes | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8226a5e --- /dev/null +++ b/.gitattributes @@ -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 From 54128beb124604ccbaf28951bcb564f49072e1e9 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Sat, 4 Jul 2020 21:53:15 +0200 Subject: [PATCH 2/3] chore: point CHANGELOG.md to releases page Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com> --- CHANGELOG.md | 120 +-------------------------------------------------- 1 file changed, 1 insertion(+), 119 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff57998..5df7aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 From 763ced752456a48b7954965f8780d83e7f374084 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Sat, 4 Jul 2020 21:31:28 +0200 Subject: [PATCH 3/3] feat: host logging modes (disabled, commands, everything) Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com> --- .gitignore | 3 +- Makefile | 1 + README.md | 4 +- go.mod | 3 +- go.sum | 8 +--- main.go | 1 + pkg/bastion/dbinit.go | 42 ++++++++++++++++---- pkg/bastion/session.go | 82 ++++++++++++++++++++++++++------------- pkg/bastion/shell.go | 21 +++++++++- pkg/bastion/ssh.go | 3 +- pkg/dbmodels/dbmodels.go | 1 + pkg/dbmodels/validator.go | 14 +++++++ 12 files changed, 136 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index b0e1bc9..0eeb82e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ dist/ /log/ /sshportal *.db -/data \ No newline at end of file +/data +sshportal.history \ No newline at end of file diff --git a/Makefile b/Makefile index 4f3fd21..679f302 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 025a46e..08c381f 100644 --- a/README.md +++ b/README.md @@ -326,11 +326,11 @@ event inspect [-h] EVENT... # host management host help -host create [-h] [--name=] [--password=] [--comment=] [--key=KEY] [--group=HOSTGROUP...] [--hop=HOST] [:]@[:] +host create [-h] [--name=] [--password=] [--comment=] [--key=KEY] [--group=HOSTGROUP...] [--hop=HOST] [--logging=MODE] [:]@[:] host inspect [-h] [--decrypt] HOST... host ls [-h] [--latest] [--quiet] host rm [-h] HOST... -host update [-h] [--name=] [--comment=] [--key=KEY] [--assign-group=HOSTGROUP...] [--unassign-group=HOSTGROUP...] [--set-hop=HOST] [--unset-hop] HOST... +host update [-h] [--name=] [--comment=] [--key=KEY] [--assign-group=HOSTGROUP...] [--unassign-group=HOSTGROUP...] [--logging-MODE] [--set-hop=HOST] [--unset-hop] HOST... # hostgroup management hostgroup help diff --git a/go.mod b/go.mod index 675d97f..b5b6974 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index e881843..d70f02d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/main.go b/main.go index f5f20be..a0daf7e 100644 --- a/main.go +++ b/main.go @@ -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" diff --git a/pkg/bastion/dbinit.go b/pkg/bastion/dbinit.go index 0ff46cc..6be4773 100644 --- a/pkg/bastion/dbinit.go +++ b/pkg/bastion/dbinit.go @@ -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 { diff --git a/pkg/bastion/session.go b/pkg/bastion/session.go index fb40731..8a4c6a3 100644 --- a/pkg/bastion/session.go +++ b/pkg/bastion/session.go @@ -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 +} diff --git a/pkg/bastion/shell.go b/pkg/bastion/shell.go index 6d12893..24b8531 100644 --- a/pkg/bastion/shell.go +++ b/pkg/bastion/shell.go @@ -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 diff --git a/pkg/bastion/ssh.go b/pkg/bastion/ssh.go index c81cc02..33b4a6f 100644 --- a/pkg/bastion/ssh.go +++ b/pkg/bastion/ssh.go @@ -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 diff --git a/pkg/dbmodels/dbmodels.go b/pkg/dbmodels/dbmodels.go index d5198cb..7e51d35 100644 --- a/pkg/dbmodels/dbmodels.go +++ b/pkg/dbmodels/dbmodels.go @@ -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 } diff --git a/pkg/dbmodels/validator.go b/pkg/dbmodels/validator.go index 7dd0efa..e9cbd4d 100644 --- a/pkg/dbmodels/validator.go +++ b/pkg/dbmodels/validator.go @@ -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" }