From 3c47a3c408dcbf7b5d4fbae161b9935bf95da003 Mon Sep 17 00:00:00 2001 From: Mikhail Bragin Date: Thu, 12 Aug 2021 12:49:10 +0200 Subject: [PATCH] peer management HTTP API (#81) * feature: create account for a newly registered user * feature: finalize user auth flow * feature: create protected API with JWT * chore: cleanup http server * feature: add UI assets * chore: update react UI * refactor: move account not exists -> create to AccountManager * chore: update UI * chore: return only peers on peers endpoint * chore: add UI path to the config * chore: remove ui from management * chore: remove unused Docker comamnds * docs: update management config sample * fix: store creation * feature: introduce peer response to the HTTP api * fix: lint errors * feature: add setup-keys HTTP endpoint * fix: return empty json arrays in HTTP API * feature: add new peer response fields --- go.mod | 6 +- go.sum | 17 +- infrastructure_files/config.json | 8 +- management/cmd/management.go | 12 +- management/server/account.go | 95 ++++++- management/server/config.go | 12 +- management/server/file_store.go | 10 +- management/server/grpc/server.go | 8 +- management/server/http/handler/callback.go | 96 ------- management/server/http/handler/dashboard.go | 44 ---- management/server/http/handler/login.go | 58 ----- management/server/http/handler/logout.go | 48 ---- management/server/http/handler/peers.go | 67 +++++ management/server/http/handler/setupkeys.go | 58 +++++ management/server/http/handler/util.go | 15 ++ management/server/http/middleware/auth.go | 39 --- .../server/http/middleware/authenticated.go | 31 --- management/server/http/middleware/handler.go | 92 +++++++ .../server/http/middleware/jwtmiddleware.go | 237 ++++++++++++++++++ management/server/http/server.go | 46 ++-- management/server/http/template/templates.go | 21 -- management/server/management_test.go | 7 +- 22 files changed, 633 insertions(+), 394 deletions(-) delete mode 100644 management/server/http/handler/callback.go delete mode 100644 management/server/http/handler/dashboard.go delete mode 100644 management/server/http/handler/login.go delete mode 100644 management/server/http/handler/logout.go create mode 100644 management/server/http/handler/peers.go create mode 100644 management/server/http/handler/setupkeys.go create mode 100644 management/server/http/handler/util.go delete mode 100644 management/server/http/middleware/auth.go delete mode 100644 management/server/http/middleware/authenticated.go create mode 100644 management/server/http/middleware/handler.go create mode 100644 management/server/http/middleware/jwtmiddleware.go delete mode 100644 management/server/http/template/templates.go diff --git a/go.mod b/go.mod index f35b34192..d10e7d880 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,15 @@ module github.com/wiretrustee/wiretrustee go 1.16 require ( + github.com/auth0/go-jwt-middleware v1.0.1 github.com/cenkalti/backoff/v4 v4.1.0 github.com/codegangsta/negroni v1.0.0 github.com/coreos/go-oidc v2.2.1+incompatible + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/protobuf v1.5.2 github.com/google/uuid v1.2.0 - github.com/gorilla/mux v1.8.0 + github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/sessions v1.2.1 - github.com/joho/godotenv v1.3.0 github.com/kardianos/service v1.2.0 github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 @@ -20,7 +21,6 @@ require ( github.com/spf13/cobra v1.1.3 github.com/vishvananda/netlink v1.1.0 golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 golang.org/x/sys v0.0.0-20210510120138-977fb7262007 golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210506160403-92e472f520a5 diff --git a/go.sum b/go.sum index 7e1eb60ef..e70fd0f36 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/auth0/go-jwt-middleware v1.0.1 h1:/fsQ4vRr4zod1wKReUH+0A3ySRjGiT9G34kypO/EKwI= +github.com/auth0/go-jwt-middleware v1.0.1/go.mod h1:YSeUX3z6+TF2H+7padiEqNJ73Zy9vXW72U//IgN0BIM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -48,6 +50,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -60,6 +64,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -97,6 +103,9 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= @@ -130,8 +139,6 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA= github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= @@ -145,6 +152,7 @@ github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozI github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g= @@ -254,6 +262,9 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -276,6 +287,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= diff --git a/infrastructure_files/config.json b/infrastructure_files/config.json index b10e06aca..4ef87bff3 100644 --- a/infrastructure_files/config.json +++ b/infrastructure_files/config.json @@ -25,9 +25,9 @@ "HttpConfig": { "LetsEncryptDomain": "", "Address": "0.0.0.0:3000", - "AuthDomain": "", - "AuthClientId": "", - "AuthClientSecret": "", - "AuthCallback": "http://localhost:3000/callback" + "AuthIssuer": "", + "AuthAudience": "", + "AuthKeysLocation": "", + "UIFilesLocation": "/var/lib/wiretrustee/ui/" } } \ No newline at end of file diff --git a/management/cmd/management.go b/management/cmd/management.go index 048c4e413..a158ee11c 100644 --- a/management/cmd/management.go +++ b/management/cmd/management.go @@ -57,6 +57,12 @@ var ( } } + store, err := server.NewStore(config.Datadir) + if err != nil { + log.Fatalf("failed creating a store: %s: %v", config.Datadir, err) + } + accountManager := server.NewManager(store) + var opts []grpc.ServerOption var httpServer *http.Server @@ -65,15 +71,15 @@ var ( transportCredentials := credentials.NewTLS(certManager.TLSConfig()) opts = append(opts, grpc.Creds(transportCredentials)) - httpServer = http.NewHttpsServer(config.HttpConfig, certManager) + httpServer = http.NewHttpsServer(config.HttpConfig, certManager, accountManager) } else { - httpServer = http.NewHttpServer(config.HttpConfig) + httpServer = http.NewHttpServer(config.HttpConfig, accountManager) } opts = append(opts, grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp)) grpcServer := grpc.NewServer(opts...) - server, err := grpc2.NewServer(config) + server, err := grpc2.NewServer(config, accountManager) if err != nil { log.Fatalf("failed creating new server: %v", err) } diff --git a/management/server/account.go b/management/server/account.go index 9fc68e8d2..2ce97ba16 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -82,6 +82,83 @@ func (manager *AccountManager) GetPeersForAPeer(peerKey string) ([]*Peer, error) return res, nil } +//GetAccount returns an existing account or error (NotFound) if doesn't exist +func (manager *AccountManager) GetAccount(accountId string) (*Account, error) { + manager.mux.Lock() + defer manager.mux.Unlock() + + account, err := manager.Store.GetAccount(accountId) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed retrieving account") + } + + return account, nil +} + +// GetOrCreateAccount returns an existing account or creates a new one if doesn't exist +func (manager *AccountManager) GetOrCreateAccount(accountId string) (*Account, error) { + manager.mux.Lock() + defer manager.mux.Unlock() + + _, err := manager.Store.GetAccount(accountId) + if err != nil { + if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { + return manager.createAccount(accountId) + } else { + // other error + return nil, err + } + } + + account, err := manager.Store.GetAccount(accountId) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed retrieving account") + } + + return account, nil +} + +//AccountExists checks whether account exists (returns true) or not (returns false) +func (manager *AccountManager) AccountExists(accountId string) (*bool, error) { + manager.mux.Lock() + defer manager.mux.Unlock() + + var res bool + _, err := manager.Store.GetAccount(accountId) + if err != nil { + if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { + res = false + return &res, nil + } else { + return nil, err + } + } + + res = true + return &res, nil +} + +// AddAccount generates a new Account with a provided accountId and saves to the Store +func (manager *AccountManager) AddAccount(accountId string) (*Account, error) { + + manager.mux.Lock() + defer manager.mux.Unlock() + + return manager.createAccount(accountId) + +} + +func (manager *AccountManager) createAccount(accountId string) (*Account, error) { + account, _ := newAccountWithId(accountId) + + err := manager.Store.SaveAccount(account) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed creating account") + } + + return account, nil +} + // AddPeer adds a new peer to the Store. // Each Account has a list of pre-authorised SetupKey and if no Account has a given key err wit ha code codes.Unauthenticated // will be returned, meaning the key is invalid @@ -96,7 +173,7 @@ func (manager *AccountManager) AddPeer(setupKey string, peerKey string) (*Peer, var sk *SetupKey if len(setupKey) == 0 { // Empty setup key, create a new account for it. - account, sk = manager.newAccount() + account, sk = newAccount() } else { sk = &SetupKey{Key: setupKey} account, err = manager.Store.GetAccountBySetupKey(sk.Key) @@ -129,20 +206,28 @@ func (manager *AccountManager) AddPeer(setupKey string, peerKey string) (*Peer, } -// newAccount creates a new Account with a default SetupKey (doesn't store in a Store) -func (manager *AccountManager) newAccount() (*Account, *SetupKey) { +// newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id +func newAccountWithId(accountId string) (*Account, *SetupKey) { log.Debugf("creating new account") - accountId := uuid.New().String() setupKeyId := uuid.New().String() setupKeys := make(map[string]*SetupKey) setupKey := &SetupKey{Key: setupKeyId} setupKeys[setupKeyId] = setupKey - network := &Network{Id: uuid.New().String(), Net: net.IPNet{}, Dns: ""} + network := &Network{ + Id: uuid.New().String(), + Net: net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}}, + Dns: ""} peers := make(map[string]*Peer) log.Debugf("created new account %s with setup key %s", accountId, setupKeyId) return &Account{Id: accountId, SetupKeys: setupKeys, Network: network, Peers: peers}, setupKey } + +// newAccount creates a new Account with a default SetupKey (doesn't store in a Store) +func newAccount() (*Account, *SetupKey) { + accountId := uuid.New().String() + return newAccountWithId(accountId) +} diff --git a/management/server/config.go b/management/server/config.go index 34042aef9..4b047bbe0 100644 --- a/management/server/config.go +++ b/management/server/config.go @@ -25,10 +25,14 @@ type Config struct { type HttpServerConfig struct { LetsEncryptDomain string Address string - AuthDomain string - AuthClientId string - AuthClientSecret string - AuthCallback string + // AuthAudience identifies the recipients that the JWT is intended for (aud in JWT) + AuthAudience string + // AuthIssuer identifies principal that issued the JWT. + AuthIssuer string + // AuthKeysLocation is a location of JWT key set containing the public keys used to verify JWT + AuthKeysLocation string + // UIFilesLocation is the location of static UI files for management frontend + UIFilesLocation string } // Host represents a Wiretrustee host (e.g. STUN, TURN, Signal) diff --git a/management/server/file_store.go b/management/server/file_store.go index 34e044435..10c1606c1 100644 --- a/management/server/file_store.go +++ b/management/server/file_store.go @@ -41,9 +41,11 @@ func restore(file string) (*FileStore, error) { if _, err := os.Stat(file); os.IsNotExist(err) { // create a new FileStore if previously didn't exist (e.g. first run) s := &FileStore{ - Accounts: make(map[string]*Account), - mux: sync.Mutex{}, - storeFile: file, + Accounts: make(map[string]*Account), + mux: sync.Mutex{}, + SetupKeyId2AccountId: make(map[string]string), + PeerKeyId2AccountId: make(map[string]string), + storeFile: file, } err = s.persist(file) @@ -148,7 +150,7 @@ func (s *FileStore) GetAccount(accountId string) (*Account, error) { account, accountFound := s.Accounts[accountId] if !accountFound { - return nil, status.Errorf(codes.Internal, "account not found") + return nil, status.Errorf(codes.NotFound, "account not found") } return account, nil diff --git a/management/server/grpc/server.go b/management/server/grpc/server.go index 424145ce8..75c7f2a7b 100644 --- a/management/server/grpc/server.go +++ b/management/server/grpc/server.go @@ -34,21 +34,17 @@ type UpdateChannelMessage struct { } // NewServer creates a new Management server -func NewServer(config *server.Config) (*Server, error) { +func NewServer(config *server.Config, accountManager *server.AccountManager) (*Server, error) { key, err := wgtypes.GeneratePrivateKey() if err != nil { return nil, err } - store, err := server.NewStore(config.Datadir) - if err != nil { - return nil, err - } return &Server{ wgKey: key, // peerKey -> event channel peerChannels: make(map[string]chan *UpdateChannelMessage), channelsMux: &sync.Mutex{}, - accountManager: server.NewManager(store), + accountManager: accountManager, config: config, }, nil } diff --git a/management/server/http/handler/callback.go b/management/server/http/handler/callback.go deleted file mode 100644 index 310637ce2..000000000 --- a/management/server/http/handler/callback.go +++ /dev/null @@ -1,96 +0,0 @@ -package handler - -import ( - "context" - "github.com/coreos/go-oidc" - "github.com/gorilla/sessions" - middleware2 "github.com/wiretrustee/wiretrustee/management/server/http/middleware" - "log" - "net/http" -) - -// Callback handler used to receive a callback from the identity provider -type Callback struct { - authenticator *middleware2.Authenticator - sessionStore sessions.Store -} - -func NewCallback(authenticator *middleware2.Authenticator, sessionStore sessions.Store) *Callback { - return &Callback{ - authenticator: authenticator, - sessionStore: sessionStore, - } -} - -// ServeHTTP checks the user session, verifies the state, verifies the token, stores user profile in a session, -// and in case of the successful auth redirects user to the main page -func (h *Callback) ServeHTTP(w http.ResponseWriter, r *http.Request) { - session, err := h.sessionStore.Get(r, "auth-session") - if err != nil { - //todo redirect to the error page stating: "error occurred plz try again later and a link to login" - //http.Error(w, err.Error(), http.StatusInternalServerError) - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - - if r.URL.Query().Get("state") != session.Values["state"] { - //todo redirect to the error page stating: "error authenticating plz try to login once again" - //http.Error(w, "invalid state parameter", http.StatusBadRequest) - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - - token, err := h.authenticator.Config.Exchange(context.TODO(), r.URL.Query().Get("code")) - if err != nil { - log.Printf("no token found: %v", err) - //todo redirect to the error page stating: "error authenticating plz try to login once again" - //w.WriteHeader(http.StatusUnauthorized) - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - - rawIDToken, ok := token.Extra("id_token").(string) - if !ok { - //todo redirect to the error page stating: "error occurred plz try again later and a link to login" - //http.Error(w, "no id_token field in oauth2 token.", http.StatusInternalServerError) - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - - oidcConfig := &oidc.Config{ - ClientID: h.authenticator.Config.ClientID, - } - - idToken, err := h.authenticator.Provider.Verifier(oidcConfig).Verify(context.TODO(), rawIDToken) - - if err != nil { - //todo redirect to the error page stating: "error occurred plz try again later and a link to login" - //http.Error(w, "failed to verify ID Token: "+err.Error(), http.StatusInternalServerError) - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - - // get the userInfo from the token - var profile map[string]interface{} - if err := idToken.Claims(&profile); err != nil { - //todo redirect to the error page stating: "error occurred plz try again later and a link to login" - //http.Error(w, err.Error(), http.StatusInternalServerError) - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - - session.Values["id_token"] = rawIDToken - session.Values["access_token"] = token.AccessToken - session.Values["profile"] = profile - - err = session.Save(r, w) - if err != nil { - //todo redirect to the error page stating: "error occurred plz try again later and a link to login" - //http.Error(w, err.Error(), http.StatusInternalServerError) - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - - // redirect to logged in page - http.Redirect(w, r, "/dashboard", http.StatusSeeOther) -} diff --git a/management/server/http/handler/dashboard.go b/management/server/http/handler/dashboard.go deleted file mode 100644 index fd0cfe455..000000000 --- a/management/server/http/handler/dashboard.go +++ /dev/null @@ -1,44 +0,0 @@ -package handler - -import ( - "fmt" - "github.com/gorilla/sessions" - "io" - "net/http" - "strings" -) - -// Dashboard is a handler of the main page of the app (dashboard) -type Dashboard struct { - sessionStore sessions.Store -} - -func NewDashboard(sessionStore sessions.Store) *Dashboard { - return &Dashboard{ - sessionStore: sessionStore, - } -} - -// ServeHTTP verifies if user is authenticated and returns a user dashboard -func (h *Dashboard) ServeHTTP(w http.ResponseWriter, r *http.Request) { - - session, err := h.sessionStore.Get(r, "auth-session") - if err != nil { - //todo redirect to the error page stating: "error occurred plz try again later and a link to login" - //http.Error(w, err.Error(), http.StatusInternalServerError) - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - - //todo get user account and relevant data to show - profile := session.Values["profile"].(map[string]interface{}) - name := profile["name"] - w.WriteHeader(200) - _, err = io.Copy(w, strings.NewReader("hello "+fmt.Sprintf("%v", name))) - if err != nil { - return - - } - - //template.RenderTemplate(w, "dashboard", session.Values["profile"]) -} diff --git a/management/server/http/handler/login.go b/management/server/http/handler/login.go deleted file mode 100644 index f27752926..000000000 --- a/management/server/http/handler/login.go +++ /dev/null @@ -1,58 +0,0 @@ -package handler - -import ( - "crypto/rand" - "encoding/base64" - "github.com/gorilla/sessions" - middleware2 "github.com/wiretrustee/wiretrustee/management/server/http/middleware" - "io/fs" - "net/http" -) - -// Login handler used to login a user -type Login struct { - authenticator *middleware2.Authenticator - sessionStore sessions.Store -} - -func NewLogin(authenticator *middleware2.Authenticator, sessionStore sessions.Store) *Login { - return &Login{ - authenticator: authenticator, - sessionStore: sessionStore, - } -} - -// ServeHTTP generates a new session state for a user and redirects the user to the auth URL -func (h *Login) ServeHTTP(w http.ResponseWriter, r *http.Request) { - - // Generate random state - b := make([]byte, 32) - _, err := rand.Read(b) - if err != nil { - //todo redirect to the error page stating: "error occurred plz try again later and a link to login" - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - state := base64.StdEncoding.EncodeToString(b) - - session, err := h.sessionStore.Get(r, "auth-session") - if err != nil { - switch err.(type) { - case *fs.PathError: - // a case when session doesn't exist in the store but was sent by the client in the cookie -> create new session ID - // it appears that in this case session is always non empty object - session.ID = "" //nolint - default: - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } - session.Values["state"] = state //nolint - err = session.Save(r, w) //nolint - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - url := h.authenticator.Config.AuthCodeURL(state) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) -} diff --git a/management/server/http/handler/logout.go b/management/server/http/handler/logout.go deleted file mode 100644 index 2144758d4..000000000 --- a/management/server/http/handler/logout.go +++ /dev/null @@ -1,48 +0,0 @@ -package handler - -import ( - "net/http" - "net/url" -) - -// Logout logs out a user -type Logout struct { - authDomain string - authClientId string -} - -func NewLogout(authDomain string, authClientId string) *Logout { - return &Logout{authDomain: authDomain, authClientId: authClientId} -} - -// ServeHTTP redirects user to teh auth identity provider logout URL -func (h *Logout) ServeHTTP(w http.ResponseWriter, r *http.Request) { - - logoutUrl, err := url.Parse("https://" + h.authDomain) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - logoutUrl.Path += "/v2/logout" - parameters := url.Values{} - - var scheme string - if r.TLS == nil { - scheme = "http" - } else { - scheme = "https" - } - - returnTo, err := url.Parse(scheme + "://" + r.Host + "/login") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - parameters.Add("returnTo", returnTo.String()) - parameters.Add("client_id", h.authClientId) - logoutUrl.RawQuery = parameters.Encode() - - http.Redirect(w, r, logoutUrl.String(), http.StatusTemporaryRedirect) -} diff --git a/management/server/http/handler/peers.go b/management/server/http/handler/peers.go new file mode 100644 index 000000000..f590de2d2 --- /dev/null +++ b/management/server/http/handler/peers.go @@ -0,0 +1,67 @@ +package handler + +import ( + "encoding/json" + log "github.com/sirupsen/logrus" + "github.com/wiretrustee/wiretrustee/management/server" + "net/http" + "time" +) + +// Peers is a handler that returns peers of the account +type Peers struct { + accountManager *server.AccountManager +} + +// PeerResponse is a response sent to the client +type PeerResponse struct { + Name string + IP string + Connected bool + LastSeen time.Time + Os string +} + +func NewPeers(accountManager *server.AccountManager) *Peers { + return &Peers{ + accountManager: accountManager, + } +} + +func (h *Peers) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + accountId := extractAccountIdFromRequestContext(r) + //new user -> create a new account + account, err := h.accountManager.GetOrCreateAccount(accountId) + if err != nil { + log.Errorf("failed getting user account %s: %v", accountId, err) + http.Redirect(w, r, "/", http.StatusInternalServerError) + return + } + + w.WriteHeader(200) + w.Header().Set("Content-Type", "application/json") + + respBody := []*PeerResponse{} + for _, peer := range account.Peers { + respBody = append(respBody, &PeerResponse{ + Name: peer.Key, + IP: peer.IP.String(), + LastSeen: time.Now(), + Connected: false, + Os: "Ubuntu 21.04 (Hirsute Hippo)", + }) + } + + err = json.NewEncoder(w).Encode(respBody) + if err != nil { + log.Errorf("failed encoding account peers %s: %v", accountId, err) + http.Redirect(w, r, "/", http.StatusInternalServerError) + return + } + case http.MethodOptions: + default: + http.Error(w, "", http.StatusNotFound) + } +} diff --git a/management/server/http/handler/setupkeys.go b/management/server/http/handler/setupkeys.go new file mode 100644 index 000000000..0c227fdc2 --- /dev/null +++ b/management/server/http/handler/setupkeys.go @@ -0,0 +1,58 @@ +package handler + +import ( + "encoding/json" + log "github.com/sirupsen/logrus" + "github.com/wiretrustee/wiretrustee/management/server" + "net/http" +) + +// SetupKeys is a handler that returns a list of setup keys of the account +type SetupKeys struct { + accountManager *server.AccountManager +} + +// SetupKeyResponse is a response sent to the client +type SetupKeyResponse struct { + Key string +} + +func NewSetupKeysHandler(accountManager *server.AccountManager) *SetupKeys { + return &SetupKeys{ + accountManager: accountManager, + } +} + +func (h *SetupKeys) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + accountId := extractAccountIdFromRequestContext(r) + //new user -> create a new account + account, err := h.accountManager.GetOrCreateAccount(accountId) + if err != nil { + log.Errorf("failed getting user account %s: %v", accountId, err) + http.Redirect(w, r, "/", http.StatusInternalServerError) + return + } + + w.WriteHeader(200) + w.Header().Set("Content-Type", "application/json") + + respBody := []*SetupKeyResponse{} + for _, key := range account.SetupKeys { + respBody = append(respBody, &SetupKeyResponse{ + Key: key.Key, + }) + } + + err = json.NewEncoder(w).Encode(respBody) + if err != nil { + log.Errorf("failed encoding account peers %s: %v", accountId, err) + http.Redirect(w, r, "/", http.StatusInternalServerError) + return + } + case http.MethodOptions: + default: + http.Error(w, "", http.StatusNotFound) + } +} diff --git a/management/server/http/handler/util.go b/management/server/http/handler/util.go new file mode 100644 index 000000000..97c49b78a --- /dev/null +++ b/management/server/http/handler/util.go @@ -0,0 +1,15 @@ +package handler + +import ( + "github.com/golang-jwt/jwt" + "net/http" +) + +// extractAccountIdFromRequestContext extracts accountId from the request context previously filled by the JWT token (after auth) +func extractAccountIdFromRequestContext(r *http.Request) string { + token := r.Context().Value("user").(*jwt.Token) + claims := token.Claims.(jwt.MapClaims) + + //actually a user id but for now we have a 1 to 1 mapping. + return claims["sub"].(string) +} diff --git a/management/server/http/middleware/auth.go b/management/server/http/middleware/auth.go deleted file mode 100644 index 2810ede47..000000000 --- a/management/server/http/middleware/auth.go +++ /dev/null @@ -1,39 +0,0 @@ -package middleware - -import ( - "context" - "golang.org/x/oauth2" - "log" - - "github.com/coreos/go-oidc" -) - -type Authenticator struct { - Provider *oidc.Provider - Config oauth2.Config - Ctx context.Context -} - -func NewAuthenticator(authDomain string, authClientId string, authClientSecret string, authCallback string) (*Authenticator, error) { - ctx := context.Background() - - provider, err := oidc.NewProvider(ctx, "https://"+authDomain+"/") - if err != nil { - log.Printf("failed to get provider: %v", err) - return nil, err - } - - conf := oauth2.Config{ - ClientID: authClientId, - ClientSecret: authClientSecret, - RedirectURL: authCallback, - Endpoint: provider.Endpoint(), - Scopes: []string{oidc.ScopeOpenID, "profile"}, - } - - return &Authenticator{ - Provider: provider, - Config: conf, - Ctx: ctx, - }, nil -} diff --git a/management/server/http/middleware/authenticated.go b/management/server/http/middleware/authenticated.go deleted file mode 100644 index 4d2bd47d4..000000000 --- a/management/server/http/middleware/authenticated.go +++ /dev/null @@ -1,31 +0,0 @@ -package middleware - -import ( - "github.com/gorilla/sessions" - "net/http" -) - -type AuthMiddleware struct { - sessionStore sessions.Store -} - -func NewAuth(sessionStore sessions.Store) *AuthMiddleware { - return &AuthMiddleware{sessionStore: sessionStore} -} - -func (am *AuthMiddleware) IsAuthenticated(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - - session, err := am.sessionStore.Get(r, "auth-session") - if err != nil { - //todo redirect to the error page stating: "error occurred plz try again later and a link to login" - //http.Error(w, err.Error(), http.StatusInternalServerError) - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - - if _, ok := session.Values["profile"]; !ok { - http.Redirect(w, r, "/login", http.StatusSeeOther) - } else { - next(w, r) - } -} diff --git a/management/server/http/middleware/handler.go b/management/server/http/middleware/handler.go new file mode 100644 index 000000000..f9636f3a9 --- /dev/null +++ b/management/server/http/middleware/handler.go @@ -0,0 +1,92 @@ +package middleware + +import ( + "encoding/json" + "errors" + "github.com/golang-jwt/jwt" + "net/http" +) + +//Jwks is a collection of JSONWebKeys obtained from Config.HttpServerConfig.AuthKeysLocation +type Jwks struct { + Keys []JSONWebKeys `json:"keys"` +} + +//JSONWebKeys is a representation of a Jason Web Key +type JSONWebKeys struct { + Kty string `json:"kty"` + Kid string `json:"kid"` + Use string `json:"use"` + N string `json:"n"` + E string `json:"e"` + X5c []string `json:"x5c"` +} + +//NewJwtMiddleware creates new middleware to verify the JWT token sent via Authorization header +func NewJwtMiddleware(issuer string, audience string, keysLocation string) (*JWTMiddleware, error) { + + keys, err := getPemKeys(keysLocation) + if err != nil { + return nil, err + } + + return New(Options{ + ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { + // Verify 'aud' claim + checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(audience, false) + if !checkAud { + return token, errors.New("invalid audience") + } + // Verify 'issuer' claim + checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(issuer, false) + if !checkIss { + return token, errors.New("invalid issuer") + } + + cert, err := getPemCert(token, keys) + if err != nil { + panic(err.Error()) + } + + result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) + return result, nil + }, + SigningMethod: jwt.SigningMethodRS256, + EnableAuthOnOptions: true, + }), nil +} + +func getPemKeys(keysLocation string) (*Jwks, error) { + resp, err := http.Get(keysLocation) + + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jwks = &Jwks{} + err = json.NewDecoder(resp.Body).Decode(jwks) + + if err != nil { + return jwks, err + } + + return jwks, err +} + +func getPemCert(token *jwt.Token, jwks *Jwks) (string, error) { + cert := "" + + for k := range jwks.Keys { + if token.Header["kid"] == jwks.Keys[k].Kid { + cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].X5c[0] + "\n-----END CERTIFICATE-----" + } + } + + if cert == "" { + err := errors.New("unable to find appropriate key") + return cert, err + } + + return cert, nil +} diff --git a/management/server/http/middleware/jwtmiddleware.go b/management/server/http/middleware/jwtmiddleware.go new file mode 100644 index 000000000..f9de79762 --- /dev/null +++ b/management/server/http/middleware/jwtmiddleware.go @@ -0,0 +1,237 @@ +package middleware + +import ( + "context" + "errors" + "fmt" + "github.com/golang-jwt/jwt" + "log" + "net/http" + "strings" +) + +// A function called whenever an error is encountered +type errorHandler func(w http.ResponseWriter, r *http.Request, err string) + +// TokenExtractor is a function that takes a request as input and returns +// either a token or an error. An error should only be returned if an attempt +// to specify a token was found, but the information was somehow incorrectly +// formed. In the case where a token is simply not present, this should not +// be treated as an error. An empty string should be returned in that case. +type TokenExtractor func(r *http.Request) (string, error) + +// Options is a struct for specifying configuration options for the middleware. +type Options struct { + // The function that will return the Key to validate the JWT. + // It can be either a shared secret or a public key. + // Default value: nil + ValidationKeyGetter jwt.Keyfunc + // The name of the property in the request where the user information + // from the JWT will be stored. + // Default value: "user" + UserProperty string + // The function that will be called when there's an error validating the token + // Default value: + ErrorHandler errorHandler + // A boolean indicating if the credentials are required or not + // Default value: false + CredentialsOptional bool + // A function that extracts the token from the request + // Default: FromAuthHeader (i.e., from Authorization header as bearer token) + Extractor TokenExtractor + // Debug flag turns on debugging output + // Default: false + Debug bool + // When set, all requests with the OPTIONS method will use authentication + // Default: false + EnableAuthOnOptions bool + // When set, the middelware verifies that tokens are signed with the specific signing algorithm + // If the signing method is not constant the ValidationKeyGetter callback can be used to implement additional checks + // Important to avoid security issues described here: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/ + // Default: nil + SigningMethod jwt.SigningMethod +} + +type JWTMiddleware struct { + Options Options +} + +func OnError(w http.ResponseWriter, r *http.Request, err string) { + http.Error(w, err, http.StatusUnauthorized) +} + +// New constructs a new Secure instance with supplied options. +func New(options ...Options) *JWTMiddleware { + + var opts Options + if len(options) == 0 { + opts = Options{} + } else { + opts = options[0] + } + + if opts.UserProperty == "" { + opts.UserProperty = "user" + } + + if opts.ErrorHandler == nil { + opts.ErrorHandler = OnError + } + + if opts.Extractor == nil { + opts.Extractor = FromAuthHeader + } + + return &JWTMiddleware{ + Options: opts, + } +} + +func (m *JWTMiddleware) logf(format string, args ...interface{}) { + if m.Options.Debug { + log.Printf(format, args...) + } +} + +// HandlerWithNext is a special implementation for Negroni, but could be used elsewhere. +func (m *JWTMiddleware) HandlerWithNext(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + err := m.CheckJWT(w, r) + + // If there was an error, do not call next. + if err == nil && next != nil { + next(w, r) + } +} + +func (m *JWTMiddleware) Handler(h http.Handler) http.Handler { + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Let secure process the request. If it returns an error, + // that indicates the request should not continue. + err := m.CheckJWT(w, r) + + // If there was an error, do not continue. + if err != nil { + return + } + + h.ServeHTTP(w, r) + }) +} + +// FromAuthHeader is a "TokenExtractor" that takes a give request and extracts +// the JWT token from the Authorization header. +func FromAuthHeader(r *http.Request) (string, error) { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + return "", nil // No error, just no token + } + + // TODO: Make this a bit more robust, parsing-wise + authHeaderParts := strings.Fields(authHeader) + if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" { + return "", errors.New("Authorization header format must be Bearer {token}") + } + + return authHeaderParts[1], nil +} + +// FromParameter returns a function that extracts the token from the specified +// query string parameter +func FromParameter(param string) TokenExtractor { + return func(r *http.Request) (string, error) { + return r.URL.Query().Get(param), nil + } +} + +// FromFirst returns a function that runs multiple token extractors and takes the +// first token it finds +func FromFirst(extractors ...TokenExtractor) TokenExtractor { + return func(r *http.Request) (string, error) { + for _, ex := range extractors { + token, err := ex(r) + if err != nil { + return "", err + } + if token != "" { + return token, nil + } + } + return "", nil + } +} + +func (m *JWTMiddleware) CheckJWT(w http.ResponseWriter, r *http.Request) error { + if !m.Options.EnableAuthOnOptions { + if r.Method == "OPTIONS" { + return nil + } + } + + // Use the specified token extractor to extract a token from the request + token, err := m.Options.Extractor(r) + + // If debugging is turned on, log the outcome + if err != nil { + m.logf("Error extracting JWT: %v", err) + } else { + m.logf("Token extracted: %s", token) + } + + // If an error occurs, call the error handler and return an error + if err != nil { + m.Options.ErrorHandler(w, r, err.Error()) + return fmt.Errorf("Error extracting token: %w", err) + } + + // If the token is empty... + if token == "" { + // Check if it was required + if m.Options.CredentialsOptional { + m.logf(" No credentials found (CredentialsOptional=true)") + // No error, just no token (and that is ok given that CredentialsOptional is true) + return nil + } + + // If we get here, the required token is missing + errorMsg := "Required authorization token not found" + m.Options.ErrorHandler(w, r, errorMsg) + m.logf(" Error: No credentials found (CredentialsOptional=false)") + return fmt.Errorf(errorMsg) + } + + // Now parse the token + parsedToken, err := jwt.Parse(token, m.Options.ValidationKeyGetter) + + // Check if there was an error in parsing... + if err != nil { + m.logf("error parsing token: %v", err) + m.Options.ErrorHandler(w, r, err.Error()) + return fmt.Errorf("Error parsing token: %w", err) + } + + if m.Options.SigningMethod != nil && m.Options.SigningMethod.Alg() != parsedToken.Header["alg"] { + message := fmt.Sprintf("Expected %s signing method but token specified %s", + m.Options.SigningMethod.Alg(), + parsedToken.Header["alg"]) + m.logf("Error validating token algorithm: %s", message) + m.Options.ErrorHandler(w, r, errors.New(message).Error()) + return fmt.Errorf("Error validating token algorithm: %s", message) + } + + // Check if the parsed token is valid... + if !parsedToken.Valid { + m.logf("Token is invalid") + m.Options.ErrorHandler(w, r, "The token isn't valid") + return errors.New("token is invalid") + } + + m.logf("JWT: %v", parsedToken) + + // If we get here, everything worked and we can set the + // user property in context. + newRequest := r.WithContext(context.WithValue(r.Context(), m.Options.UserProperty, parsedToken)) //nolint + // Update the current request with the new context information. + *r = *newRequest + return nil +} diff --git a/management/server/http/server.go b/management/server/http/server.go index 723b891aa..a3cb8d473 100644 --- a/management/server/http/server.go +++ b/management/server/http/server.go @@ -2,40 +2,38 @@ package http import ( "context" - "encoding/gob" log "github.com/sirupsen/logrus" s "github.com/wiretrustee/wiretrustee/management/server" - handler2 "github.com/wiretrustee/wiretrustee/management/server/http/handler" - middleware2 "github.com/wiretrustee/wiretrustee/management/server/http/middleware" + "github.com/wiretrustee/wiretrustee/management/server/http/handler" + "github.com/wiretrustee/wiretrustee/management/server/http/middleware" "golang.org/x/crypto/acme/autocert" "net/http" + "path/filepath" "time" - - "github.com/codegangsta/negroni" - "github.com/gorilla/sessions" ) type Server struct { - server *http.Server - config *s.HttpServerConfig - certManager *autocert.Manager + server *http.Server + config *s.HttpServerConfig + certManager *autocert.Manager + accountManager *s.AccountManager } // NewHttpsServer creates a new HTTPs server (with HTTPS support) // The listening address will be :443 no matter what was specified in s.HttpServerConfig.Address -func NewHttpsServer(config *s.HttpServerConfig, certManager *autocert.Manager) *Server { +func NewHttpsServer(config *s.HttpServerConfig, certManager *autocert.Manager, accountManager *s.AccountManager) *Server { server := &http.Server{ Addr: config.Address, WriteTimeout: time.Second * 15, ReadTimeout: time.Second * 15, IdleTimeout: time.Second * 60, } - return &Server{server: server, config: config, certManager: certManager} + return &Server{server: server, config: config, certManager: certManager, accountManager: accountManager} } // NewHttpServer creates a new HTTP server (without HTTPS) -func NewHttpServer(config *s.HttpServerConfig) *Server { - return NewHttpsServer(config, nil) +func NewHttpServer(config *s.HttpServerConfig, accountManager *s.AccountManager) *Server { + return NewHttpsServer(config, nil, accountManager) } // Stop stops the http server @@ -50,25 +48,23 @@ func (s *Server) Stop(ctx context.Context) error { // Start defines http handlers and starts the http server. Blocks until server is shutdown. func (s *Server) Start() error { - sessionStore := sessions.NewFilesystemStore("", []byte("something-very-secret")) - authenticator, err := middleware2.NewAuthenticator(s.config.AuthDomain, s.config.AuthClientId, s.config.AuthClientSecret, s.config.AuthCallback) + jwtMiddleware, err := middleware.NewJwtMiddleware(s.config.AuthIssuer, s.config.AuthAudience, s.config.AuthKeysLocation) if err != nil { - log.Errorf("failed cerating authentication middleware %v", err) return err } - gob.Register(map[string]interface{}{}) - r := http.NewServeMux() s.server.Handler = r - r.Handle("/login", handler2.NewLogin(authenticator, sessionStore)) - r.Handle("/logout", handler2.NewLogout(s.config.AuthDomain, s.config.AuthClientId)) - r.Handle("/callback", handler2.NewCallback(authenticator, sessionStore)) - r.Handle("/dashboard", negroni.New( - negroni.HandlerFunc(middleware2.NewAuth(sessionStore).IsAuthenticated), - negroni.Wrap(handler2.NewDashboard(sessionStore))), - ) + // serve public website + uiPath := filepath.Clean(s.config.UIFilesLocation) + fs := http.FileServer(http.Dir(uiPath)) + r.Handle("/", fs) + fsStatic := http.FileServer(http.Dir(filepath.Join(uiPath, "static/"))) + r.Handle("/static/", http.StripPrefix("/static/", fsStatic)) + + r.Handle("/api/peers", jwtMiddleware.Handler(handler.NewPeers(s.accountManager))) + r.Handle("/api/setup-keys", jwtMiddleware.Handler(handler.NewSetupKeysHandler(s.accountManager))) http.Handle("/", r) if s.certManager != nil { diff --git a/management/server/http/template/templates.go b/management/server/http/template/templates.go deleted file mode 100644 index ad217e614..000000000 --- a/management/server/http/template/templates.go +++ /dev/null @@ -1,21 +0,0 @@ -package template - -import ( - "html/template" - "net/http" - "os" - "path/filepath" -) - -func RenderTemplate(w http.ResponseWriter, tmpl string, data interface{}) { - cwd, _ := os.Getwd() - t, err := template.ParseFiles(filepath.Join(cwd, "./routes/"+tmpl+"/"+tmpl+".html")) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - err = t.Execute(w, data) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } -} diff --git a/management/server/management_test.go b/management/server/management_test.go index b10f0cc4b..144db1f1a 100644 --- a/management/server/management_test.go +++ b/management/server/management_test.go @@ -426,7 +426,12 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) { lis, err := net.Listen("tcp", ":0") Expect(err).NotTo(HaveOccurred()) s := grpc.NewServer() - mgmtServer, err := grpc2.NewServer(config) + store, err := server.NewStore(config.Datadir) + if err != nil { + log.Fatalf("failed creating a store: %s: %v", config.Datadir, err) + } + accountManager := server.NewManager(store) + mgmtServer, err := grpc2.NewServer(config, accountManager) Expect(err).NotTo(HaveOccurred()) mgmtProto.RegisterManagementServiceServer(s, mgmtServer) go func() {