ssh: Implement authentication for new ssh server

This commit is contained in:
Tom Wiesing 2022-11-11 14:47:10 +01:00
parent 66b397e9da
commit 45f63935cd
No known key found for this signature in database
10 changed files with 259 additions and 1 deletions

54
cmd/ssh.go Normal file
View file

@ -0,0 +1,54 @@
package cmd
import (
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
"github.com/FAU-CDI/wisski-distillery/internal/cli"
"github.com/tkw1536/goprogram/exit"
)
// SSH is the 'ssh' command
var SSH wisski_distillery.Command = ssh{}
type ssh struct {
Bind string `short:"b" long:"bind" description:"address to listen on" default:"127.0.0.1:2223"`
}
func (s ssh) Description() wisski_distillery.Description {
return wisski_distillery.Description{
Requirements: cli.Requirements{
NeedsDistillery: true,
},
Command: "ssh",
Description: "Starts the ssh server to allow clients to connect to this distillery",
}
}
var errSSHListen = exit.Error{
ExitCode: exit.ExitGeneric,
Message: "Unable to listen",
}
func (s ssh) Run(context wisski_distillery.Context) error {
dis := context.Environment
server, err := dis.SSH().Server(dis.Context(), context.IOStream)
if err != nil {
return err
}
context.Printf("Listening on %s\n", s.Bind)
// make a new listener
listener, err := dis.Still.Environment.Listen("tcp", s.Bind)
if err != nil {
return errServerListen.Wrap(err)
}
go func() {
<-dis.Context().Done()
listener.Close()
}()
// and serve that listener
err = server.Serve(listener)
return errServerListen.Wrap(err)
}

View file

@ -56,6 +56,7 @@ func init() {
// servers
wdcli.Register(cmd.Server)
wdcli.Register(cmd.SSH)
}
// an error when no arguments are provided.

4
go.mod
View file

@ -7,10 +7,12 @@ require (
github.com/Showmax/go-fqdn v1.0.0
github.com/alessio/shellescape v1.4.1
github.com/feiin/sqlstring v0.3.0
github.com/gliderlabs/ssh v0.3.5
github.com/go-sql-driver/mysql v1.6.0
github.com/gorilla/websocket v1.5.0
github.com/pkg/errors v0.9.1
github.com/tkw1536/goprogram v0.1.1
github.com/tkw1536/proxyssh v0.2.2
golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
gorm.io/driver/mysql v1.3.6
@ -18,11 +20,13 @@ require (
)
require (
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/gosuri/uilive v0.0.4 // indirect
github.com/jessevdk/go-flags v1.5.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect
)

21
go.sum
View file

@ -4,8 +4,12 @@ github.com/Showmax/go-fqdn v1.0.0 h1:0rG5IbmVliNT5O19Mfuvna9LL7zlHyRfsSvBPZmF9tM
github.com/Showmax/go-fqdn v1.0.0/go.mod h1:SfrFBzmDCtCGrnHhoDjuvFnKsWjEQX/Q9ARZvOrJAko=
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
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/feiin/sqlstring v0.3.0 h1:iyPEFijI2BxpY2M+AuhIvdNManzXa2OwGzuPaEMLUgo=
github.com/feiin/sqlstring v0.3.0/go.mod h1:xpZTjVUw1nD3hMgF9SMRdPiooKSikLf4PS5j2NTn3RI=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
@ -25,16 +29,33 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/tkw1536/goprogram v0.1.1 h1:gamK9OuRqoX2yQlA/nkgfVHHZWd/u2uUj6vJMYrYa70=
github.com/tkw1536/goprogram v0.1.1/go.mod h1:Jqs0sTMzhrAGCX3JQrlEwQ0WRWQACCvuQQkaBDp65pE=
github.com/tkw1536/proxyssh v0.2.2 h1:2NVlTsRFFVfGaQH6B0Ci8n0ura1Qk4uEcZM0cDXAIF4=
github.com/tkw1536/proxyssh v0.2.2/go.mod h1:N6fExNESwKnKV1d0JGT3fvke+Aym22VNCAPEGJTofPQ=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741 h1:fGZugkZk2UgYBxtpKmvub51Yno1LJDeEsRp2xGD+0gY=
golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/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/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w=
golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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=
gorm.io/driver/mysql v1.3.6 h1:BhX1Y/RyALb+T9bZ3t07wLnPZBukt+IRkMn8UZSNbGM=
gorm.io/driver/mysql v1.3.6/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=

View file

@ -0,0 +1,104 @@
package ssh
import (
"bufio"
"context"
"fmt"
"io"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/pkg/sshx"
"github.com/gliderlabs/ssh"
"github.com/tkw1536/goprogram/stream"
"github.com/tkw1536/proxyssh/feature"
)
type SSH struct {
component.Base
Instances *instances.Instances
}
const (
etx rune = 3
eot rune = 4
)
const welcomeMessage = `Welcome to the WissKI SSH Server.
You've successfully authenticated, but we don't provide shell access to the main server.
You may use this connection as part of a proxy jump to connect to your server.
For example:
ssh -J %s:2222 www-data@%s
Press CTRL-C to close this connection.
`
// Server returns an ssh server that implements the main ssh server
func (s *SSH) Server(context context.Context, ios stream.IOStream) (*ssh.Server, error) {
var server ssh.Server
banner := fmt.Sprintf(welcomeMessage, s.Config.DefaultDomain, "example."+s.Config.DefaultDomain)
server.Handle(func(session ssh.Session) {
io.WriteString(session, banner)
buffer := bufio.NewReader(session)
for {
res, _, err := buffer.ReadRune()
if err != nil {
return
}
if res == etx || res == eot {
return
}
}
})
server.PublicKeyHandler = feature.AuthorizeKeys(
slogger{IOStream: ios},
func(ctx ssh.Context) (keys []ssh.PublicKey, err error) {
keys, err = s.GlobalKeys()
if err != nil {
return nil, err
}
instances, err := s.Instances.All()
if err != nil {
return nil, err
}
for _, instance := range instances {
ikeys, err := instance.SSH().Keys()
if err != nil {
continue
}
keys = append(keys, ikeys...)
}
return keys, nil
})
return &server, nil
}
func (s *SSH) GlobalKeys() ([]ssh.PublicKey, error) {
file, err := s.Environment.Open(s.Config.GlobalAuthorizedKeysFile)
if err != nil {
return nil, err
}
bytes, err := io.ReadAll(file)
if err != nil {
return nil, err
}
return sshx.ParseAllKeys(bytes), nil
}
type slogger struct {
stream.IOStream
}
func (s slogger) Print(v ...any) {
fmt.Fprint(s.Stderr, v...)
}
func (s slogger) Printf(format string, v ...any) {
fmt.Fprintf(s.Stderr, format, v...)
}

View file

@ -20,6 +20,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/resolver"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/solr"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/ssh"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/triplestore"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/web"
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
@ -72,6 +73,10 @@ func (dis *Distillery) Resolver() *resolver.Resolver {
func (dis *Distillery) SQL() *sql.SQL {
return export[*sql.SQL](dis)
}
func (dis *Distillery) SSH() *ssh.SSH {
return export[*ssh.SSH](dis)
}
func (dis *Distillery) Triplestore() *triplestore.Triplestore {
return export[*triplestore.Triplestore](dis)
}
@ -119,7 +124,7 @@ func (dis *Distillery) allComponents() []initFunc {
s.PollInterval = time.Second
}),
// instainces
// instances
auto[*instances.Instances],
auto[*meta.Meta],
auto[*malt.Malt],
@ -132,6 +137,9 @@ func (dis *Distillery) allComponents() []initFunc {
auto[*exporter.Filesystem],
auto[*exporter.Pathbuilders],
// ssh server
auto[*ssh.SSH],
// Control server
auto[*control.Control],
auto[*static.Static],

View file

@ -1,6 +1,8 @@
package barrel
import (
"path/filepath"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/mstore"
@ -13,3 +15,11 @@ type Barrel struct {
Locker *locker.Locker
MStore *mstore.MStore
}
func (barrel *Barrel) DataPath() string {
return filepath.Join(barrel.FilesystemBase, "data")
}
func (barrel *Barrel) AuthorizedKeysPath() string {
return filepath.Join(barrel.DataPath(), "authorized_keys")
}

View file

@ -0,0 +1,32 @@
package ssh
import (
"io"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel"
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
"github.com/FAU-CDI/wisski-distillery/pkg/sshx"
"github.com/gliderlabs/ssh"
)
type SSH struct {
ingredient.Base
Barrel *barrel.Barrel
}
func (ssh *SSH) Keys() ([]ssh.PublicKey, error) {
file, err := ssh.Environment.Open(ssh.Barrel.AuthorizedKeysPath())
if environment.IsNotExist(err) {
return nil, nil
}
if err != nil {
return nil, err
}
bytes, err := io.ReadAll(file)
if err != nil {
return nil, err
}
return sshx.ParseAllKeys(bytes), nil
}

View file

@ -8,6 +8,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel/drush"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel/provisioner"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/barrel/ssh"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/bookkeeping"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/info"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/locker"
@ -76,6 +77,10 @@ func (wisski *WissKI) Info() *info.Info {
return export[*info.Info](wisski)
}
func (wisski *WissKI) SSH() *ssh.SSH {
return export[*ssh.SSH](wisski)
}
//
// All components
// THESE SHOULD NEVER BE CALLED DIRECTLY
@ -112,5 +117,7 @@ func (wisski *WissKI) allIngredients() []initFunc {
auto[*drush.Drush],
auto[*reserve.Reserve],
auto[*ssh.SSH],
}
}

17
pkg/sshx/sshx.go Normal file
View file

@ -0,0 +1,17 @@
package sshx
import "github.com/gliderlabs/ssh"
// ParseAllKeys parses all keys from the list of bytes
func ParseAllKeys(bytes []byte) (keys []ssh.PublicKey) {
var key ssh.PublicKey
var err error
for {
key, _, _, bytes, err = ssh.ParseAuthorizedKey(bytes)
if err != nil {
break
}
keys = append(keys, key)
}
return
}