-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3630b8d
commit 3e76ffc
Showing
20 changed files
with
3,486 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# Introduction | ||
|
||
SSHTool is a go application that implements some ssh capabilities in a simple-to-use way. In detail: | ||
|
||
1. Generating ed25519 private/public keys. | ||
2. Exposing a terminal program via an ssh connection. Including pty support (on unix). | ||
3. Exposing one or more directories via **sftp**. Using regular expressions, you can set read and write permissions | ||
separately from the operating system as well as hide files from public view. | ||
For application/operating systems which doesn't support sftp connection, SSHTool additionally can start a **webdav** | ||
server which can be forwarded with ssh to localhost in order to connect to it. | ||
|
||
Note that SSHTool only supports public key authentication. | ||
|
||
# Usage | ||
|
||
## Key generation | ||
|
||
To generate an ed25519 key pair execute | ||
|
||
```bash | ||
sshtool generate sshkey | ||
``` | ||
|
||
It creates the private file under `sshkey` and the corresponding public key named `sshkey.pub`. | ||
|
||
Be aware that you don't need to manually generate the key for SSHTool's remaining commands. | ||
Private keys that were not found will be generated there automatically. | ||
|
||
## Program exposing | ||
|
||
For exposing another program in an ssh session, execute | ||
|
||
```bash | ||
sshtool cmd config.toml | ||
``` | ||
|
||
where `config.toml` is the configuration file containing the details. | ||
A standard configuration file is created if the given filename does not exist. | ||
In the config file you can set the hostname the ssh server is listen to as well as the port. | ||
|
||
### Connecting | ||
|
||
For connecting to the ssh server, execute | ||
|
||
```bash | ||
ssh username@servername -p 2222 | ||
``` | ||
|
||
where `username` can be arbitrary, `servername` is the ip address or dns name of the host and 2222 is the port set in | ||
the configuration. | ||
|
||
### Configuration details | ||
|
||
The `ServerKeyFilename` is an array with different private keys the server offers the client and will sign requests with. | ||
Every key must use a different signature scheme (rsa, ed25519, etc.). | ||
If a key is not found, a random ed25519 key is generated and saved at the location automatically. | ||
|
||
The parameter `MaxNumberOfConnections` is the maximal number of parallel ssh connection accepted by the server. | ||
A value of 0 means no limit. | ||
|
||
`AuthorizedKeys` is a list of public ssh keys accepted from a client. | ||
The format of every entry is the same as in the `authorized_keys` file ssh expects. | ||
E.g. "ssh-ed25519 AAAAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX someone@somewhere". | ||
|
||
`Command` is the path of the application to execute and `CommandArgs` a list of arguments to start it with. | ||
|
||
## SFTP | ||
|
||
For starting the sftp server, call | ||
|
||
```bash | ||
sshtool sftp config.toml | ||
``` | ||
|
||
Note that the `config.toml` is different from the cmd subcommand. | ||
If the config file does not exist, it will be created automatically. | ||
|
||
### Connecting | ||
|
||
Connecting to the sftp server depends on the client you choose. | ||
For Linux there is [sshfs](https://github.com/libfuse/sshfs) and [oxfs](https://github.com/oxfs/oxfs). So you can run | ||
|
||
```bash | ||
sshfs username@servername:/ -p 2222 mount | ||
``` | ||
|
||
to mount the sftp filesystem under "mount". Here 2222 is the port configured, `servername` is ip address or dns name | ||
of the server this app runs on and `username` is the user you want to connect as. | ||
All accepted users along with the accepted authorized keys are listened in the configuration file. | ||
Every user can be served a different filesystem with different read/write permissions. | ||
|
||
This filesystem can also be served as webdav if set in the config. This is done by forwarding the webdav | ||
http port to localhost. | ||
If the server runs in sftp mode, there is no stdin/stdout supported. So you have to forward it with | ||
|
||
```bash | ||
ssh -NT -L 8080:localhost:80 username@servername -p 2222 | ||
``` | ||
|
||
where 80 is the port described in the config and 8080 is the port opened on the client a webdav client | ||
can connect to. The webdav server has no login requirement. | ||
|
||
### Configuration | ||
|
||
Most settings match the one from program exposing. In addition to that we have | ||
|
||
* `WebDavPort` which is the port the webdav server listen to and can be forwarded from. Note that | ||
the server listen on a virtual port and not on an actual port on the operating system. Thus, the only | ||
way to connect to it is by ssh tcp/ip forwarding. | ||
* `Users` is a list of allowed usernames along with configuration for this user. In the default config | ||
a standard user with the name `user` is created. You need to rename this phrase for change the name. | ||
* `CanRead` is a list of regular expression for files that can be read from a client. | ||
* `CanWrite` is a list of regular expression for files that can be written from a client. | ||
* `ShouldHide` is a list of regular expression for files that are hidden from a client. | ||
* `WebDav` enables the webdav server that can be forwarded with ssh if true. It exposes the exact same filesystem sftp does. | ||
* `FileSystem` lists all directories that can be accessed from a client. If a directory has the name "" in the config, | ||
it will be exposed directly. If not, a client sees a virtual filesystem containing every directory with the name in | ||
this config. | ||
* `Root` is the path of the directory to expose. | ||
* `ReadOnly` sets that this directory can only be read and not be written. It has some overlaps with the `CanRead` config. | ||
|
||
# Building | ||
|
||
As SSHTool is written in golang, simple run | ||
|
||
```bash | ||
go build -o sshtool | ||
``` | ||
|
||
for compilation. By setting the "GOOS" and "GOARCH" environment variable, cross compiling is possible. | ||
|
||
# License | ||
|
||
The code is distributed under AGPL-3.0. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module github.com/Entscheider/sshtool | ||
|
||
go 1.16 | ||
|
||
require ( | ||
github.com/BurntSushi/toml v1.0.0 | ||
github.com/creack/pty v1.1.17 | ||
github.com/gliderlabs/ssh v0.3.3 | ||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a | ||
github.com/pkg/sftp v1.13.4 | ||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 | ||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 | ||
golang.org/x/sys v0.0.0-20220325203850-36772127a21f // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= | ||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= | ||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= | ||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= | ||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= | ||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= | ||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/gliderlabs/ssh v0.3.3 h1:mBQ8NiOgDkINJrZtoizkC3nDNYgSaWtxyem6S2XHBtA= | ||
github.com/gliderlabs/ssh v0.3.3/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= | ||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= | ||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= | ||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE= | ||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ= | ||
github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg= | ||
github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8= | ||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | ||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s= | ||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y= | ||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= | ||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.0.0-20220325203850-36772127a21f h1:TrmogKRsSOxRMJbLYGrB4SBbW+LJcEllYBLME5Zk5pU= | ||
golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= | ||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package logger | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"strings" | ||
"sync" | ||
"time" | ||
) | ||
|
||
// Logger is interface for basic logging methods | ||
type Logger interface { | ||
io.Closer | ||
// Warn prints a warning msg under the given tag | ||
Warn(tag string, msg string) | ||
// Err prints an error under the given tag | ||
Err(tag string, msg string) | ||
// Debug prints a debug output under the given tag | ||
Debug(tag string, msg string) | ||
// Info prints a info output under the given tag | ||
Info(tag string, msg string) | ||
} | ||
|
||
// A default implementation of a logger that avoids parallel printing in different thread | ||
type stdLogger struct { | ||
channel chan<- string | ||
waitChannel <-chan bool | ||
wg *sync.WaitGroup | ||
} | ||
|
||
// NewLogger creates a new Logger implementation that writes all outputs to the given writer | ||
func NewLogger(writer io.Writer) Logger { | ||
c := make(chan string) | ||
wc := make(chan bool) | ||
wg := sync.WaitGroup{} | ||
go func() { | ||
defer wg.Done() | ||
defer close(wc) | ||
wg.Add(1) | ||
if closer, ok := writer.(io.WriteCloser); ok { | ||
defer closer.Close() | ||
} | ||
for text := range c { | ||
_, _ = fmt.Fprint(writer, text) | ||
wc <- true | ||
} | ||
}() | ||
return &stdLogger{c, wc, &wg} | ||
} | ||
|
||
func (l *stdLogger) Close() error { | ||
close(l.channel) | ||
l.wg.Wait() | ||
return nil | ||
} | ||
|
||
func (l *stdLogger) print(symbol string, tag string, msg string) { | ||
t := time.Now() | ||
l.channel <- fmt.Sprintf("%s [%s] %s - %s\n", t.Local(), symbol, tag, msg) | ||
<-l.waitChannel | ||
} | ||
|
||
func (l *stdLogger) Warn(tag string, msg string) { | ||
l.print("W", tag, msg) | ||
} | ||
|
||
func (l *stdLogger) Err(tag string, msg string) { | ||
l.print("E", tag, msg) | ||
} | ||
|
||
func (l *stdLogger) Debug(tag string, msg string) { | ||
l.print("D", tag, msg) | ||
} | ||
|
||
func (l *stdLogger) Info(tag string, msg string) { | ||
l.print("I", tag, msg) | ||
} | ||
|
||
// ConnectionInfo contains meta information about a new ssh connection | ||
type ConnectionInfo struct { | ||
IP string | ||
Username string | ||
} | ||
|
||
// AccessLogger is an interface that adds method for logging ssh related actions | ||
type AccessLogger interface { | ||
io.Closer | ||
// NewLogin is called when a new login has happened and was answered with the given status | ||
NewLogin(connection ConnectionInfo, status string) | ||
// Logout is called when a logout happens | ||
Logout(connection ConnectionInfo) | ||
// NewAccess is called when a new file has been requested at the given path | ||
// for the given kind (MkDir, Read, Write, etc.) and was answered with the given status (e.g. granted) | ||
NewAccess(connection ConnectionInfo, path string, kind string, status string) | ||
} | ||
|
||
// AccessLogger that prints all output to an io.Writer and prevents multiple writes from different threads. | ||
type stdAccessLogger struct { | ||
channel chan<- string | ||
waitChannel <-chan bool | ||
wg *sync.WaitGroup | ||
} | ||
|
||
// NewAccessLogger creates a new standard AccessLogger that prints all output to the given writer | ||
func NewAccessLogger(writer io.Writer) AccessLogger { | ||
c := make(chan string) | ||
wc := make(chan bool) | ||
wg := sync.WaitGroup{} | ||
go func() { | ||
defer wg.Done() | ||
defer close(wc) | ||
wg.Add(1) | ||
if closer, ok := writer.(io.WriteCloser); ok { | ||
defer closer.Close() | ||
} | ||
for text := range c { | ||
_, _ = fmt.Fprintf(writer, "%s\n", text) | ||
wc <- true | ||
} | ||
}() | ||
return &stdAccessLogger{c, wc, &wg} | ||
} | ||
|
||
// Collects information about an access log entry | ||
type entry struct { | ||
logType string | ||
connectionInfo ConnectionInfo | ||
path string | ||
kind string | ||
status string | ||
} | ||
|
||
func (l *stdAccessLogger) printStrings(entries ...string) { | ||
t := time.Now() | ||
values := make([]string, len(entries)+1) | ||
values[0] = fmt.Sprintf("\"%s\"", t.Local()) | ||
for i, e := range entries { | ||
values[i+1] = fmt.Sprintf("\"%s\"", strings.ReplaceAll(e, "\"", "\"\"")) | ||
} | ||
l.channel <- strings.Join(values, ",") | ||
<-l.waitChannel | ||
} | ||
|
||
func (l *stdAccessLogger) printEntry(e entry) { | ||
l.printStrings(e.logType, e.connectionInfo.IP, e.connectionInfo.Username, | ||
e.path, e.kind, e.status) | ||
} | ||
|
||
func (l *stdAccessLogger) NewLogin(connection ConnectionInfo, status string) { | ||
l.printEntry(entry{ | ||
logType: "login", | ||
connectionInfo: connection, | ||
status: status, | ||
}) | ||
} | ||
|
||
func (l *stdAccessLogger) Logout(connection ConnectionInfo) { | ||
l.printEntry(entry{ | ||
logType: "logout", | ||
connectionInfo: connection, | ||
}) | ||
} | ||
|
||
func (l *stdAccessLogger) NewAccess(connection ConnectionInfo, path string, kind string, status string) { | ||
l.printEntry(entry{ | ||
logType: "access", | ||
connectionInfo: connection, | ||
path: path, | ||
kind: kind, | ||
status: status, | ||
}) | ||
} | ||
|
||
func (l *stdAccessLogger) Close() error { | ||
close(l.channel) | ||
l.wg.Wait() | ||
return nil | ||
} |
Oops, something went wrong.