Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/myst-259 validate session and signature on service provider side #126

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
62d422a
Session id as username and signature as password validation on servic…
tadovas Jan 25, 2018
a647eb9
service consumer side username/password as sessionid and signature im…
tadovas Jan 25, 2018
4ff31ec
Renamed function to better reflect purpose
tadovas Jan 26, 2018
6995606
Renamed from authenticator -> credentials go
tadovas Jan 26, 2018
57aa0ba
Unused code removed, comments according to lint added
tadovas Jan 26, 2018
8b7743d
Lets name things by their real names
tadovas Jan 26, 2018
a378e18
Space removed
tadovas Jan 26, 2018
6c5f3c0
Merged from master, conflicts solved
tadovas Jan 26, 2018
d9b0a31
More clear function type name, tests readability a bit improved
tadovas Jan 29, 2018
55b4fae
Renamed authenticator to credentialsChecker, server now uses real cre…
tadovas Jan 29, 2018
b7b49af
Code structure reorganized to keep related things close
tadovas Jan 30, 2018
2540eda
Prefix is added before session id signing/verifying to avoid abuse of…
tadovas Jan 30, 2018
f493f00
Pulled in from master
tadovas Jan 30, 2018
0b667d3
Provider validates openvpn credentials from consumer
tadovas Jan 30, 2018
dfc74c5
Session manager generator dependency moved outside, mutex introduced …
tadovas Jan 30, 2018
117eaec
Fixes according to PR comments
tadovas Jan 31, 2018
928bdc7
Comments fixed
tadovas Jan 31, 2018
c5f3f7d
More PR comments fixed
tadovas Jan 31, 2018
81f5b63
Removed unused code
tadovas Jan 31, 2018
a7f794b
Better naming
tadovas Feb 1, 2018
a9c88c1
Server auth middleware refactored (a bit)
tadovas Feb 1, 2018
9110836
Merge branch 'master' of github.com:MysteriumNetwork/node into featur…
tadovas Feb 1, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions client_connection/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/mysterium/node/openvpn"
"github.com/mysterium/node/openvpn/middlewares/client/auth"
"github.com/mysterium/node/openvpn/middlewares/client/bytescount"
openvpnSession "github.com/mysterium/node/openvpn/session"
"github.com/mysterium/node/server"
"github.com/mysterium/node/session"
"path/filepath"
Expand Down Expand Up @@ -141,14 +142,16 @@ func ConfigureVpnClientFactory(mysteriumAPIClient server.Client, vpnClientRuntim
return nil, err
}

signer := signerFactory(id)

statsSaver := bytescount.NewSessionStatsSaver(statsKeeper)
statsSender := bytescount.NewSessionStatsSender(mysteriumAPIClient, vpnSession.ID, signerFactory(id))
statsSender := bytescount.NewSessionStatsSender(mysteriumAPIClient, vpnSession.ID, signer)
statsHandler := bytescount.NewCompositeStatsHandler(statsSaver, statsSender)

authenticator := auth.NewAuthenticatorFake()
credentialsProvider := openvpnSession.SignatureCredentialsProvider(vpnSession.ID, signer)
vpnMiddlewares := []openvpn.ManagementMiddleware{
bytescount.NewMiddleware(statsHandler, 1*time.Minute),
auth.NewMiddleware(authenticator),
auth.NewMiddleware(credentialsProvider),
}
return openvpn.NewClient(
vpnConfig,
Expand Down
6 changes: 6 additions & 0 deletions client_connection/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,18 @@ func (foc *fakeOpenvpnClient) resumeAction() {
}

type fakeDialog struct {
peerId identity.Identity
}

func (fd *fakeDialog) CreateDialog(peerID identity.Identity, peerContact dto_discovery.Contact) (communication.Dialog, error) {
fd.peerId = peerID
return fd, nil
}

func (fd *fakeDialog) PeerID() identity.Identity {
return fd.peerId
}

func (fd *fakeDialog) Close() error {
return nil
}
Expand Down
10 changes: 6 additions & 4 deletions cmd/mysterium_server/command_run/command_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ type CommandRun struct {
dialogWaiterFactory func(identity identity.Identity) communication.DialogWaiter
dialogWaiter communication.DialogWaiter

sessionManagerFactory func(serverIP string) session.ManagerInterface
sessionManagerFactory func(serverIP string) session.Manager

vpnServerFactory func() *openvpn.Server
vpnServerFactory func(sessionManager session.Manager) *openvpn.Server
vpnServer *openvpn.Server
}

Expand Down Expand Up @@ -64,12 +64,14 @@ func (cmd *CommandRun) Run() (err error) {
}
proposal := discovery.NewServiceProposalWithLocation(providerID, providerContact, serviceLocation)

dialogHandler := session.NewDialogHandler(proposal.ID, cmd.sessionManagerFactory(vpnServerIP))
sessionManager := cmd.sessionManagerFactory(vpnServerIP)

dialogHandler := session.NewDialogHandler(proposal.ID, sessionManager)
if err := cmd.dialogWaiter.ServeDialogs(dialogHandler); err != nil {
return err
}

cmd.vpnServer = cmd.vpnServerFactory()
cmd.vpnServer = cmd.vpnServerFactory(sessionManager)
if err := cmd.vpnServer.Start(); err != nil {
return err
}
Expand Down
24 changes: 15 additions & 9 deletions cmd/mysterium_server/command_run/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,17 @@ func NewCommandWith(
identity.NewSigner(keystoreInstance, myID),
)
},
sessionManagerFactory: func(vpnServerIP string) session.ManagerInterface {
return openvpn_session.NewManager(openvpn.NewClientConfig(
vpnServerIP,
filepath.Join(options.DirectoryConfig, "ca.crt"),
filepath.Join(options.DirectoryConfig, "ta.key"),
))
sessionManagerFactory: func(vpnServerIP string) session.Manager {
return openvpn_session.NewManager(
openvpn.NewClientConfig(
vpnServerIP,
filepath.Join(options.DirectoryConfig, "ca.crt"),
filepath.Join(options.DirectoryConfig, "ta.key"),
),
&session.UUIDGenerator{},
)
},
vpnServerFactory: func() *openvpn.Server {
vpnServerFactory: func(manager session.Manager) *openvpn.Server {
vpnServerConfig := openvpn.NewServerConfig(
"10.8.0.0", "255.255.255.0",
filepath.Join(options.DirectoryConfig, "ca.crt"),
Expand All @@ -90,9 +93,12 @@ func NewCommandWith(
filepath.Join(options.DirectoryConfig, "crl.pem"),
filepath.Join(options.DirectoryConfig, "ta.key"),
)
authenticator := auth.NewCheckerFake()
sessionValidator := openvpn_session.NewSessionValidator(
manager.FindSession,
identity.NewExtractor(),
)
vpnMiddlewares := []openvpn.ManagementMiddleware{
auth.NewMiddleware(authenticator),
auth.NewMiddleware(sessionValidator),
}
return openvpn.NewServer(vpnServerConfig, options.DirectoryRuntime, vpnMiddlewares...)
},
Expand Down
1 change: 1 addition & 0 deletions communication/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type DialogEstablisher interface {
// Dialog represent established connection between 2 peers in network.
// Enables bidirectional communication with another peer.
type Dialog interface {
PeerID() identity.Identity
Sender
Receiver
Close() error
Expand Down
6 changes: 6 additions & 0 deletions communication/nats/dialog/dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ package dialog

import (
"github.com/mysterium/node/communication"
"github.com/mysterium/node/identity"
)

type dialog struct {
communication.Sender
communication.Receiver
peerID identity.Identity
}

func (dialog *dialog) Close() error {
return nil
}

func (dialog *dialog) PeerID() identity.Identity {
return dialog.peerID
}
4 changes: 3 additions & 1 deletion communication/nats/dialog/dialog_establisher.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (establisher *dialogEstablisher) CreateDialog(
return dialog, err
}

dialog = establisher.newDialogToPeer(peerAddress, peerCodec)
dialog = establisher.newDialogToPeer(peerID, peerAddress, peerCodec)
log.Info(establisherLogPrefix, fmt.Sprintf("Dialog established with: %#v", peerContact))

return dialog, nil
Expand Down Expand Up @@ -99,12 +99,14 @@ func (establisher *dialogEstablisher) newSenderToPeer(
}

func (establisher *dialogEstablisher) newDialogToPeer(
peerID identity.Identity,
peerAddress *discovery.AddressNATS,
peerCodec *codecSecured,
) *dialog {

subTopic := peerAddress.GetTopic() + "." + establisher.myID.Address
return &dialog{
peerID: peerID,
Sender: nats.NewSender(peerAddress.GetConnection(), peerCodec, subTopic),
Receiver: nats.NewReceiver(peerAddress.GetConnection(), peerCodec, subTopic),
}
Expand Down
1 change: 1 addition & 0 deletions communication/nats/dialog/dialog_waiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func (waiter *dialogWaiter) newDialogToPeer(peerID identity.Identity, peerCodec
subTopic := waiter.myAddress.GetTopic() + "." + peerID.Address

return &dialog{
peerID: peerID,
Sender: nats.NewSender(waiter.myAddress.GetConnection(), peerCodec, subTopic),
Receiver: nats.NewReceiver(waiter.myAddress.GetConnection(), peerCodec, subTopic),
}
Expand Down
14 changes: 0 additions & 14 deletions openvpn/middlewares/client/auth/authenticator.go

This file was deleted.

23 changes: 13 additions & 10 deletions openvpn/middlewares/client/auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,28 @@ import (
"regexp"
)

// CredentialsProvider returns client's current auth primitives (i.e. customer identity signature / node's sessionId)
type CredentialsProvider func() (username string, password string, err error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

username, password pair is strongly coupled - it's being passed around the system in many places and we have quite a few methods which returns (username, password string, err error).
Maybe it's time to merge them into struct?

type Credentials struct {
    username, password string
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I.e. we wouldn't have to explain what is "auth primitives" in many places, as we do now - we could do that in one place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's true. but since our middleware packageing is a bit different, we don't have a possibility to share credentials structure between client and server. But good point and it can be improved with separate PR.


type middleware struct {
authenticator Authenticator
connection net.Conn
lastUsername string
lastPassword string
state openvpn.State
fetchCredentials CredentialsProvider
connection net.Conn
lastUsername string
lastPassword string
state openvpn.State
}

// NewMiddleware creates client user_auth challenge authentication middleware
func NewMiddleware(authenticator Authenticator) *middleware {
func NewMiddleware(credentials CredentialsProvider) *middleware {
return &middleware{
authenticator: authenticator,
connection: nil,
fetchCredentials: credentials,
connection: nil,
}
}

func (m *middleware) Start(connection net.Conn) error {
m.connection = connection
log.Info("starting client user-pass authenticator middleware")
log.Info("starting client user-pass provider middleware")
return nil
}

Expand All @@ -43,7 +46,7 @@ func (m *middleware) ConsumeLine(line string) (consumed bool, err error) {
if len(match) > 0 {
m.Reset()
m.state = openvpn.STATE_AUTH
username, password, err := m.authenticator()
username, password, err := m.fetchCredentials()
log.Info("authenticating user ", username, " with pass: ", password)

_, err = m.connection.Write([]byte("password 'Auth' " + password + "\n"))
Expand Down
16 changes: 0 additions & 16 deletions openvpn/middlewares/server/auth/authenticator.go

This file was deleted.

58 changes: 36 additions & 22 deletions openvpn/middlewares/server/auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,24 @@ import (
"strconv"
)

// CredentialsChecker callback checks given auth primitives (i.e. customer identity signature / node's sessionId)
type CredentialsChecker func(username, password string) (bool, error)

type middleware struct {
authenticator AuthenticatorChecker
connection net.Conn
lastUsername string
lastPassword string
clientID int
keyID int
state openvpn.State
checkCredentials CredentialsChecker
connection net.Conn
lastUsername string
lastPassword string
clientID int
keyID int
state openvpn.State
}

// NewMiddleware creates server user_auth challenge authentication middleware
func NewMiddleware(authenticator AuthenticatorChecker) *middleware {
func NewMiddleware(credentialsChecker CredentialsChecker) *middleware {
return &middleware{
authenticator: authenticator,
connection: nil,
checkCredentials: credentialsChecker,
connection: nil,
}
}

Expand Down Expand Up @@ -165,31 +168,42 @@ func (m *middleware) authenticateClient() (consumed bool, err error) {
m.state = openvpn.STATE_UNDEFINED

if m.lastUsername == "" || m.lastPassword == "" {
return false, fmt.Errorf("missing username or password")
denyClientAuthWithMessage(m.connection, m.clientID, m.keyID, "missing username or password")
return true, nil
}

log.Info("authenticating user: ", m.lastUsername, " clientID: ", m.clientID, " keyID: ", m.keyID)

authenticated, err := m.authenticator(m.lastUsername, m.lastPassword)
authenticated, err := m.checkCredentials(m.lastUsername, m.lastPassword)
if err != nil {
return false, err
log.Error("Authentication error: ", err)
denyClientAuthWithMessage(m.connection, m.clientID, m.keyID, "internal error")
return true, nil
}

if authenticated {
_, err = m.connection.Write([]byte("client-auth-nt " + strconv.Itoa(m.clientID) + " " + strconv.Itoa(m.keyID) + "\n"))
if err != nil {
return false, err
}
approveClient(m.connection, m.clientID, m.keyID)
} else {
_, err = m.connection.Write([]byte("client-deny " + strconv.Itoa(m.clientID) + " " + strconv.Itoa(m.keyID) +
" wrong username or password \n"))
if err != nil {
return false, err
}
denyClientAuthWithMessage(m.connection, m.clientID, m.keyID, "wrong username or password")
}
return true, nil
}

func approveClient(conn net.Conn, clientID, keyID int) {
writeStringToConn(conn, "client-auth-nt "+strconv.Itoa(clientID)+" "+strconv.Itoa(keyID)+"\n")
}

func denyClientAuthWithMessage(conn net.Conn, clientID, keyID int, message string) {
writeStringToConn(conn, "client-deny "+strconv.Itoa(clientID)+" "+strconv.Itoa(keyID)+" "+message+"\n")
}

func writeStringToConn(conn net.Conn, message string) {
_, err := conn.Write([]byte(message + "\n"))
if err != nil {
log.Error("Management communication error: ", err)
}
}

func (m *middleware) Reset() {
m.lastUsername = ""
m.lastPassword = ""
Expand Down
39 changes: 39 additions & 0 deletions openvpn/session/authentication.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package session

import (
"github.com/mysterium/node/identity"
client_auth "github.com/mysterium/node/openvpn/middlewares/client/auth"
server_auth "github.com/mysterium/node/openvpn/middlewares/server/auth"
"github.com/mysterium/node/session"
)

const sessionSignaturePrefix = "MystVpnSessionId:"

// SignatureCredentialsProvider returns session id as username and id signed with given signer as password
func SignatureCredentialsProvider(id session.SessionID, signer identity.Signer) client_auth.CredentialsProvider {
return func() (string, string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not fully aware, what do we gain from returning function here instead of credentials itself:

  • The on-demand aspect doesn't really look useful here, because we're not gaining much.
  • We doesn't seem to be mocking this anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was designed with other PR. Might be one of the places for future refactoring

signature, err := signer.Sign([]byte(sessionSignaturePrefix + id))
return string(id), signature.Base64(), err
}
}

type sessionFinder func(session session.SessionID) (session.Session, bool)

// NewSessionValidator provides glue code for openvpn management interface to validate incoming client login request,
// it expects session id as username, and session signature signed by client as password
func NewSessionValidator(findSession sessionFinder, extractor identity.Extractor) server_auth.CredentialsChecker {
return func(sessionString, signatureString string) (bool, error) {
sessionId := session.SessionID(sessionString)
currentSession, found := findSession(sessionId)
if !found {
return false, nil
}

signature := identity.SignatureBase64(signatureString)
extractedIdentity, err := extractor.Extract([]byte(sessionSignaturePrefix+sessionString), signature)
if err != nil {
return false, err
}
return currentSession.ConsumerID == extractedIdentity, nil
}
}
Loading