mirror of
https://github.com/moul/sshportal.git
synced 2024-09-20 15:06:07 +08:00
Merge pull request #188 from moul/dev/moul/fix-166
This commit is contained in:
commit
e43bb55e70
17
.gitattributes
vendored
Normal file
17
.gitattributes
vendored
Normal 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
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,3 +8,4 @@ dist/
|
|||
/sshportal
|
||||
*.db
|
||||
/data
|
||||
sshportal.history
|
120
CHANGELOG.md
120
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
|
||||
|
|
1
Makefile
1
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
|
||||
|
||||
|
|
|
@ -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
3
go.mod
generated
|
@ -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
8
go.sum
generated
|
@ -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=
|
||||
|
|
1
main.go
1
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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
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()
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue