Skip to content
This repository has been archived by the owner on Oct 2, 2022. It is now read-only.

Commit

Permalink
0.9.4: Passing through OnShutdown, Keyboard Interactive.
Browse files Browse the repository at this point in the history
This release fixes a bug where the OnShutdown hook would not be passed through to the backends and adds Keyboard-Interactive authentication support.
  • Loading branch information
Janos Pasztor committed Jan 15, 2021
1 parent 294971e commit 1890028
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 24 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.9.4: Passing through OnShutdown, Keyboard Interactive.

This release fixes a bug where the OnShutdown hook would not be passed through to the backends and adds Keyboard-Interactive authentication support.

## 0.9.3: Bumping version

This release bumps the version to work aroung Go caching.
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down Expand Up @@ -210,6 +211,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
1 change: 0 additions & 1 deletion handler_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
)

// NewHandler creates a new audit logging handler that logs all events as configured, and passes request to a provided backend.
//goland:noinspection GoUnusedExportedFunction
func NewHandler(backend sshserver.Handler, logger auditlog.Logger) sshserver.Handler {
return &handler{
backend: backend,
Expand Down
51 changes: 49 additions & 2 deletions handler_networkconnection.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,64 @@
package auditlogintegration

import (
"context"

"github.com/containerssh/auditlog"
"github.com/containerssh/auditlog/message"
"github.com/containerssh/sshserver"
)

type networkConnectionHandler struct {
sshserver.AbstractNetworkConnectionHandler

backend sshserver.NetworkConnectionHandler
audit auditlog.Connection
}

func (n *networkConnectionHandler) OnAuthKeyboardInteractive(
user string,
challenge func(
instruction string,
questions sshserver.KeyboardInteractiveQuestions,
) (answers sshserver.KeyboardInteractiveAnswers, err error),
) (response sshserver.AuthResponse, reason error) {
return n.backend.OnAuthKeyboardInteractive(
user,
func(
instruction string,
questions sshserver.KeyboardInteractiveQuestions,
) (answers sshserver.KeyboardInteractiveAnswers, err error) {
var auditQuestions []message.KeyboardInteractiveQuestion
for _, q := range questions {
auditQuestions = append(auditQuestions, message.KeyboardInteractiveQuestion{
Question: q.Question,
Echo: q.EchoResponse,
})
}
n.audit.OnAuthKeyboardInteractiveChallenge(user, instruction, auditQuestions)
answers, err = challenge(instruction, questions)
if err != nil {
return answers, err
}
var auditAnswers []message.KeyboardInteractiveAnswer
for _, q := range auditQuestions {
a, err := answers.GetByQuestionText(q.Question)
if err != nil {
return answers, err
}
auditAnswers = append(auditAnswers, message.KeyboardInteractiveAnswer{
Question: q.Question,
Answer: a,
})
}
n.audit.OnAuthKeyboardInteractiveAnswer(user, auditAnswers)
return answers, err
},
)
}

func (n *networkConnectionHandler) OnShutdown(shutdownContext context.Context) {
n.backend.OnShutdown(shutdownContext)
}

func (n *networkConnectionHandler) OnAuthPassword(
username string,
password []byte,
Expand Down
13 changes: 11 additions & 2 deletions handler_sessionchannel.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
package auditlogintegration

import (
"context"

"github.com/containerssh/auditlog"
"github.com/containerssh/sshserver"
)

type sessionChannelHandler struct {
sshserver.AbstractSessionChannelHandler

backend sshserver.SessionChannelHandler
audit auditlog.Channel
session sshserver.SessionChannel
}

func (s *sessionChannelHandler) OnClose() {
s.audit.OnClose()
s.backend.OnClose()
}

func (s *sessionChannelHandler) OnShutdown(shutdownContext context.Context) {
s.backend.OnShutdown(shutdownContext)
}

func (s *sessionChannelHandler) OnUnsupportedChannelRequest(requestID uint64, requestType string, payload []byte) {
s.backend.OnUnsupportedChannelRequest(requestID, requestType, payload)
s.audit.OnRequestUnknown(requestID, requestType, payload)
Expand Down
12 changes: 6 additions & 6 deletions handler_sshconnection.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package auditlogintegration

import (
"context"
"io"

"github.com/containerssh/auditlog"
Expand All @@ -9,12 +10,14 @@ import (
)

type sshConnectionHandler struct {
sshserver.AbstractSSHConnectionHandler

backend sshserver.SSHConnectionHandler
audit auditlog.Connection
}

func (s *sshConnectionHandler) OnShutdown(shutdownContext context.Context) {
s.backend.OnShutdown(shutdownContext)
}

func (s *sshConnectionHandler) OnUnsupportedGlobalRequest(requestID uint64, requestType string, payload []byte) {
//todo audit payload
s.audit.OnGlobalRequestUnknown(requestType)
Expand Down Expand Up @@ -111,9 +114,6 @@ func (s *sessionProxy) CloseWrite() error {
}

func (s *sessionProxy) Close() error {
if s.audit == nil {
panic("BUG: close requested before channel is open")
}
s.audit.OnClose()
// Audit logging is done via the session channel hook.
return s.backend.Close()
}
103 changes: 90 additions & 13 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,59 @@ import (
"github.com/containerssh/auditlogintegration"
)

func TestKeyboardInteractiveAuthentication(t *testing.T) {
logger := log.GetTestLogger(t)

dir, err := ioutil.TempDir("temp", "testcase")
assert.NoError(t, err)
defer func() {
_ = os.RemoveAll(dir)
}()

geoipLookup, err := geoip.New(
geoip.Config{
Provider: "dummy",
},
)
assert.NoError(t, err)

auditLogHandler, err := auditlogintegration.New(
auditlog.Config{
Enable: true,
Format: auditlog.FormatBinary,
Storage: auditlog.StorageFile,
File: file.Config{
Directory: dir,
},
},
&backendHandler{},
geoipLookup,
logger,
)
assert.NoError(t, err)

user := sshserver.NewTestUser("test")
user.AddKeyboardInteractiveChallengeResponse("Challenge", "Response")

srv := sshserver.NewTestServer(auditLogHandler, logger)
srv.Start()
client := sshserver.NewTestClient(srv.GetListen(), srv.GetHostKey(), user, logger)
connection := client.MustConnect()
_ = connection.Close()
srv.Stop(10 * time.Second)

messages, errors, done := getStoredMessages(t, dir, logger)
if done {
return
}
assert.Empty(t, errors)
assert.Equal(t, message.TypeConnect, messages[0].MessageType)
assert.Equal(t, message.TypeAuthKeyboardInteractiveChallenge, messages[1].MessageType)
assert.Equal(t, message.TypeAuthKeyboardInteractiveAnswer, messages[2].MessageType)
assert.Equal(t, message.TypeHandshakeSuccessful, messages[3].MessageType)
assert.Equal(t, message.TypeDisconnect, messages[4].MessageType)
}

func TestConnectMessages(t *testing.T) {
logger := log.GetTestLogger(t)

Expand Down Expand Up @@ -85,6 +138,23 @@ func createTestServer(t *testing.T, dir string, logger log.Logger) (sshserver.Te
}

func checkStoredAuditMessages(t *testing.T, dir string, logger log.Logger) {
messages, errors, done := getStoredMessages(t, dir, logger)
if done {
return
}
assert.Empty(t, errors)
assert.NotEmpty(t, messages)
assert.Equal(t, message.TypeConnect, messages[0].MessageType)
assert.Equal(t, message.TypeAuthPassword, messages[1].MessageType)
assert.Equal(t, message.TypeAuthPasswordSuccessful, messages[2].MessageType)
assert.Equal(t, message.TypeHandshakeSuccessful, messages[3].MessageType)
assert.Equal(t, message.TypeNewChannelSuccessful, messages[4].MessageType)
assert.Equal(t, message.TypeChannelRequestShell, messages[5].MessageType)
assert.Equal(t, message.TypeExit, messages[6].MessageType)
assert.Equal(t, message.TypeDisconnect, messages[7].MessageType)
}

func getStoredMessages(t *testing.T, dir string, logger log.Logger) ([]message.Message, []error, bool) {
storage, err := file.NewStorage(
file.Config{
Directory: dir,
Expand All @@ -102,7 +172,7 @@ func checkStoredAuditMessages(t *testing.T, dir string, logger log.Logger) {
}
assert.NotNil(t, logReader)
if logReader == nil {
return
return nil, nil, true
}

decoder := binary.NewDecoder()
Expand All @@ -124,16 +194,7 @@ loop:
errors = append(errors, err)
}
}
assert.Empty(t, errors)
assert.NotEmpty(t, messages)
assert.Equal(t, message.TypeConnect, messages[0].MessageType)
assert.Equal(t, message.TypeAuthPassword, messages[1].MessageType)
assert.Equal(t, message.TypeAuthPasswordSuccessful, messages[2].MessageType)
assert.Equal(t, message.TypeHandshakeSuccessful, messages[3].MessageType)
assert.Equal(t, message.TypeNewChannelSuccessful, messages[4].MessageType)
assert.Equal(t, message.TypeChannelRequestShell, messages[5].MessageType)
assert.Equal(t, message.TypeExit, messages[6].MessageType)
assert.Equal(t, message.TypeDisconnect, messages[7].MessageType)
return messages, errors, false
}

type backendHandler struct {
Expand All @@ -142,12 +203,28 @@ type backendHandler struct {

func (b *backendHandler) OnAuthKeyboardInteractive(
_ string,
_ func(
challenge func(
instruction string,
questions sshserver.KeyboardInteractiveQuestions,
) (answers sshserver.KeyboardInteractiveAnswers, err error),
) (response sshserver.AuthResponse, reason error) {
return sshserver.AuthResponseUnavailable, fmt.Errorf("not implemented")
answers, err := challenge(
"Test",
sshserver.KeyboardInteractiveQuestions{{
Question: "Challenge",
EchoResponse: true,
}},
)
if err != nil {
return
}
answerText, err := answers.GetByQuestionText("Challenge")
if err == nil {
if answerText != "Response" {
return sshserver.AuthResponseUnavailable, fmt.Errorf("invalid response")
}
}
return sshserver.AuthResponseSuccess, err
}

func (b *backendHandler) OnClose() {
Expand Down

0 comments on commit 1890028

Please sign in to comment.