Compare commits

...

5 Commits

Author SHA1 Message Date
Mikhail Bragin
6ae27c9a9b Refactor: support multiple users under the same account (#170)
* feature: add User entity to Account

* test: new file store creation test

* test: add FileStore persist-restore tests

* test: add GetOrCreateAccountByUser Accountmanager test

* refactor: rename account manager users file

* refactor: use userId instead of accountId when handling Management HTTP API

* fix: new account creation for every request

* fix: golint

* chore: add account creator to Account Entity to identify who created the account.

* chore: use xid ID generator for account IDs

* fix: test failures

* test: check that CreatedBy is stored when account is stored

* chore: add account copy method

* test: remove test for non existent GetOrCreateAccount func

* chore: add accounts conversion function

* fix: golint

* refactor: simplify admin user creation

* refactor: move migration script to a separate package
2021-12-27 13:17:15 +01:00
braginini
ff6e369a21 chore: explain why keeping service lib at specific version 2021-12-21 12:10:18 +01:00
braginini
5c3b5e7f40 fix: rollback kardianos pkg 2021-12-21 12:07:14 +01:00
Mikhail Bragin
8c75ef8bef update to go 1.17 (#167)
* chore: update to go 1.17

* fix: update workflows go version

* fix: golint errors/update grpc
2021-12-21 10:02:25 +01:00
Mikhail Bragin
fdc11fff47 update docs (#164) 2021-12-06 13:54:46 +01:00
25 changed files with 1330 additions and 251 deletions

View File

@@ -8,7 +8,7 @@ jobs:
test: test:
strategy: strategy:
matrix: matrix:
go-version: [1.16.x] go-version: [1.17.x]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Install Go - name: Install Go
@@ -24,7 +24,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ windows, linux, darwin ] os: [ windows, linux, darwin ]
go-version: [1.16.x] go-version: [1.17.x]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code

View File

@@ -18,7 +18,7 @@ jobs:
name: Set up Go name: Set up Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.16 go-version: 1.17
- -
name: Cache Go modules name: Cache Go modules
uses: actions/cache@v1 uses: actions/cache@v1

View File

@@ -27,7 +27,7 @@
**Wiretrustee is an open-source VPN platform built on top of WireGuard® making it easy to create secure private networks for your organization or home.** **Wiretrustee is an open-source VPN platform built on top of WireGuard® making it easy to create secure private networks for your organization or home.**
It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, vpn gateways, and so forth. It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
**Wiretrustee automates Wireguard-based networks, offering a management layer with:** **Wiretrustee automates Wireguard-based networks, offering a management layer with:**
* Centralized Peer IP management with a UI dashboard. * Centralized Peer IP management with a UI dashboard.
@@ -56,14 +56,17 @@ Hosted demo version:
### A bit on Wiretrustee internals ### A bit on Wiretrustee internals
* Wiretrustee features a Management Service that offers peer IP management and network updates distribution (e.g. when new peer joins the network). * Wiretrustee features a Management Service that offers peer IP management and network updates distribution (e.g. when a new peer joins the network).
* Wiretrustee uses WebRTC ICE implemented in [pion/ice library](https://github.com/pion/ice) to discover connection candidates when establishing a peer-to-peer connection between devices. * Wiretrustee uses WebRTC ICE implemented in [pion/ice library](https://github.com/pion/ice) to discover connection candidates when establishing a peer-to-peer connection between devices.
* Peers negotiate connection through [Signal Service](signal/). * Peers negotiate connection through [Signal Service](signal/).
* Signal Service uses public Wireguard keys to route messages between peers. * Signal Service uses public Wireguard keys to route messages between peers.
Contents of the messages sent between peers through the signaling server are encrypted with Wireguard keys, making it impossible to inspect them. Contents of the messages sent between peers through the signaling server are encrypted with Wireguard keys, making it impossible to inspect them.
* Occasionally, the NAT-traversal is unsuccessful due to strict NATs (e.g. mobile carrier-grade NAT). * Occasionally, the NAT traversal is unsuccessful due to strict NATs (e.g. mobile carrier-grade NAT). When this occurs the system falls back to the relay server (TURN), and a secure Wireguard tunnel is established via the TURN server. [Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in Wiretrustee setups.
When this occurs the system falls back to relay server (TURN), and a secure Wireguard tunnel is established via TURN server.
[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in Wiretrustee setups. <p float="left" align="middle">
<img src="https://docs.wiretrustee.com/img/architecture/high-level-dia.png" width="700"/>
</p>
### Product Roadmap ### Product Roadmap
- [Public Roadmap](https://github.com/wiretrustee/wiretrustee/projects/2) - [Public Roadmap](https://github.com/wiretrustee/wiretrustee/projects/2)

66
go.mod
View File

@@ -1,28 +1,62 @@
module github.com/wiretrustee/wiretrustee module github.com/wiretrustee/wiretrustee
go 1.16 go 1.17
require ( require (
github.com/cenkalti/backoff/v4 v4.1.0 github.com/cenkalti/backoff/v4 v4.1.2
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang/protobuf v1.5.2 github.com/golang/protobuf v1.5.2
github.com/google/uuid v1.2.0 github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7 github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7 //keep this version otherwise wiretrustee up command breaks
github.com/onsi/ginkgo v1.16.4 github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.13.0 github.com/onsi/gomega v1.17.0
github.com/pion/ice/v2 v2.1.7 github.com/pion/ice/v2 v2.1.17
github.com/rs/cors v1.8.0 github.com/rs/cors v1.8.0
github.com/sirupsen/logrus v1.7.0 github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.1.3 github.com/spf13/cobra v1.3.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netlink v1.1.0
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
golang.zx2c4.com/wireguard/windows v0.4.5 golang.zx2c4.com/wireguard/windows v0.5.1
google.golang.org/grpc v1.32.0 google.golang.org/grpc v1.43.0
google.golang.org/protobuf v1.26.0 google.golang.org/protobuf v1.27.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
) )
require github.com/rs/xid v1.3.0
require (
github.com/BurntSushi/toml v0.4.1 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
github.com/mdlayher/genetlink v1.1.0 // indirect
github.com/mdlayher/netlink v1.4.2 // indirect
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/pion/dtls/v2 v2.0.12 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/mdns v0.0.5 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/stun v0.3.5 // indirect
github.com/pion/transport v0.12.3 // indirect
github.com/pion/turn/v2 v2.0.5 // indirect
github.com/pion/udp v0.1.1 // indirect
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b // indirect
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
golang.org/x/tools v0.1.8 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
honnef.co/go/tools v0.2.2 // indirect
)

677
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/connectivity" "google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
"io" "io"
"time" "time"
@@ -28,7 +29,7 @@ type Client struct {
// NewClient creates a new client to Management service // NewClient creates a new client to Management service
func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*Client, error) { func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*Client, error) {
transportOption := grpc.WithInsecure() transportOption := grpc.WithTransportCredentials(insecure.NewCredentials())
if tlsEnabled { if tlsEnabled {
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})) transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))

View File

@@ -2,6 +2,7 @@ package server
import ( import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/rs/xid"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/wiretrustee/wiretrustee/util" "github.com/wiretrustee/wiretrustee/util"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
@@ -19,10 +20,39 @@ type AccountManager struct {
// Account represents a unique account of the system // Account represents a unique account of the system
type Account struct { type Account struct {
Id string Id string
// User.Id it was created by
CreatedBy string
SetupKeys map[string]*SetupKey SetupKeys map[string]*SetupKey
Network *Network Network *Network
Peers map[string]*Peer Peers map[string]*Peer
Users map[string]*User
}
func (a *Account) Copy() *Account {
peers := map[string]*Peer{}
for id, peer := range a.Peers {
peers[id] = peer.Copy()
}
users := map[string]*User{}
for id, user := range a.Users {
users[id] = user.Copy()
}
setupKeys := map[string]*SetupKey{}
for id, key := range a.SetupKeys {
setupKeys[id] = key.Copy()
}
return &Account{
Id: a.Id,
CreatedBy: a.CreatedBy,
SetupKeys: setupKeys,
Network: a.Network.Copy(),
Peers: peers,
Users: users,
}
} }
// NewManager creates a new AccountManager with a provided Store // NewManager creates a new AccountManager with a provided Store
@@ -125,29 +155,6 @@ func (am *AccountManager) GetAccount(accountId string) (*Account, error) {
return account, nil return account, nil
} }
// GetOrCreateAccount returns an existing account or creates a new one if doesn't exist
func (am *AccountManager) GetOrCreateAccount(accountId string) (*Account, error) {
am.mux.Lock()
defer am.mux.Unlock()
_, err := am.Store.GetAccount(accountId)
if err != nil {
if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
return am.createAccount(accountId)
} else {
// other error
return nil, err
}
}
account, err := am.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) //AccountExists checks whether account exists (returns true) or not (returns false)
func (am *AccountManager) AccountExists(accountId string) (*bool, error) { func (am *AccountManager) AccountExists(accountId string) (*bool, error) {
am.mux.Lock() am.mux.Lock()
@@ -168,18 +175,18 @@ func (am *AccountManager) AccountExists(accountId string) (*bool, error) {
return &res, nil return &res, nil
} }
// AddAccount generates a new Account with a provided accountId and saves to the Store // AddAccount generates a new Account with a provided accountId and userId, saves to the Store
func (am *AccountManager) AddAccount(accountId string) (*Account, error) { func (am *AccountManager) AddAccount(accountId string, userId string) (*Account, error) {
am.mux.Lock() am.mux.Lock()
defer am.mux.Unlock() defer am.mux.Unlock()
return am.createAccount(accountId) return am.createAccount(accountId, userId)
} }
func (am *AccountManager) createAccount(accountId string) (*Account, error) { func (am *AccountManager) createAccount(accountId string, userId string) (*Account, error) {
account, _ := newAccountWithId(accountId) account, _ := newAccountWithId(accountId, userId)
err := am.Store.SaveAccount(account) err := am.Store.SaveAccount(account)
if err != nil { if err != nil {
@@ -190,7 +197,7 @@ func (am *AccountManager) createAccount(accountId string) (*Account, error) {
} }
// newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id // newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id
func newAccountWithId(accountId string) (*Account, *SetupKey) { func newAccountWithId(accountId string, userId string) (*Account, *SetupKey) {
log.Debugf("creating new account") log.Debugf("creating new account")
@@ -204,16 +211,17 @@ func newAccountWithId(accountId string) (*Account, *SetupKey) {
Net: net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}}, Net: net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}},
Dns: ""} Dns: ""}
peers := make(map[string]*Peer) peers := make(map[string]*Peer)
users := make(map[string]*User)
log.Debugf("created new account %s with setup key %s", accountId, defaultKey.Key) log.Debugf("created new account %s with setup key %s", accountId, defaultKey.Key)
return &Account{Id: accountId, SetupKeys: setupKeys, Network: network, Peers: peers}, defaultKey return &Account{Id: accountId, SetupKeys: setupKeys, Network: network, Peers: peers, Users: users, CreatedBy: userId}, defaultKey
} }
// newAccount creates a new Account with a default SetupKey (doesn't store in a Store) // newAccount creates a new Account with a default SetupKey and a provided User.Id of a user who issued account creation (doesn't store in a Store)
func newAccount() (*Account, *SetupKey) { func newAccount(userId string) (*Account, *SetupKey) {
accountId := uuid.New().String() accountId := xid.New().String()
return newAccountWithId(accountId) return newAccountWithId(accountId, userId)
} }
func getAccountSetupKeyById(acc *Account, keyId string) *SetupKey { func getAccountSetupKeyById(acc *Account, keyId string) *SetupKey {

View File

@@ -2,12 +2,36 @@ package server
import ( import (
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net" "net"
"testing" "testing"
) )
func TestAccountManager_GetOrCreateAccountByUser(t *testing.T) {
manager, err := createManager(t)
if err != nil {
t.Fatal(err)
return
}
userId := "test_user"
account, err := manager.GetOrCreateAccountByUser(userId)
if err != nil {
t.Fatal(err)
}
if account == nil {
t.Fatalf("expected to create an account for a user %s", userId)
}
account, err = manager.GetAccountByUser(userId)
if err != nil {
t.Errorf("expected to get existing account after creation, no account was found for a user %s", userId)
}
if account != nil && account.Users[userId] == nil {
t.Fatalf("expected to create an account for a user %s but no user was found after creation udner the account %s", userId, account.Id)
}
}
func TestAccountManager_AddAccount(t *testing.T) { func TestAccountManager_AddAccount(t *testing.T) {
manager, err := createManager(t) manager, err := createManager(t)
if err != nil { if err != nil {
@@ -16,6 +40,7 @@ func TestAccountManager_AddAccount(t *testing.T) {
} }
expectedId := "test_account" expectedId := "test_account"
userId := "account_creator"
expectedPeersSize := 0 expectedPeersSize := 0
expectedSetupKeysSize := 2 expectedSetupKeysSize := 2
expectedNetwork := net.IPNet{ expectedNetwork := net.IPNet{
@@ -23,7 +48,7 @@ func TestAccountManager_AddAccount(t *testing.T) {
Mask: net.IPMask{255, 192, 0, 0}, Mask: net.IPMask{255, 192, 0, 0},
} }
account, err := manager.AddAccount(expectedId) account, err := manager.AddAccount(expectedId, userId)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -45,46 +70,6 @@ func TestAccountManager_AddAccount(t *testing.T) {
} }
} }
func TestAccountManager_GetOrCreateAccount(t *testing.T) {
manager, err := createManager(t)
if err != nil {
t.Fatal(err)
return
}
expectedId := "test_account"
//make sure account doesn't exist
account, err := manager.GetAccount(expectedId)
if err != nil {
errStatus, ok := status.FromError(err)
if !(ok && errStatus.Code() == codes.NotFound) {
t.Fatal(err)
}
}
if account != nil {
t.Fatal("expecting empty account")
}
account, err = manager.GetOrCreateAccount(expectedId)
if err != nil {
t.Fatal(err)
}
if account.Id != expectedId {
t.Fatalf("expected to create an account, got wrong account")
}
account, err = manager.GetOrCreateAccount(expectedId)
if err != nil {
t.Errorf("expected to get existing account after creation, failed")
}
if account.Id != expectedId {
t.Fatalf("expected to create an account, got wrong account")
}
}
func TestAccountManager_AccountExists(t *testing.T) { func TestAccountManager_AccountExists(t *testing.T) {
manager, err := createManager(t) manager, err := createManager(t)
if err != nil { if err != nil {
@@ -93,7 +78,8 @@ func TestAccountManager_AccountExists(t *testing.T) {
} }
expectedId := "test_account" expectedId := "test_account"
_, err = manager.AddAccount(expectedId) userId := "account_creator"
_, err = manager.AddAccount(expectedId, userId)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -117,7 +103,8 @@ func TestAccountManager_GetAccount(t *testing.T) {
} }
expectedId := "test_account" expectedId := "test_account"
account, err := manager.AddAccount(expectedId) userId := "account_creator"
account, err := manager.AddAccount(expectedId, userId)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -154,7 +141,7 @@ func TestAccountManager_AddPeer(t *testing.T) {
return return
} }
account, err := manager.AddAccount("test_account") account, err := manager.AddAccount("test_account", "account_creator")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -20,6 +20,7 @@ type FileStore struct {
Accounts map[string]*Account Accounts map[string]*Account
SetupKeyId2AccountId map[string]string `json:"-"` SetupKeyId2AccountId map[string]string `json:"-"`
PeerKeyId2AccountId map[string]string `json:"-"` PeerKeyId2AccountId map[string]string `json:"-"`
UserId2AccountId map[string]string `json:"-"`
// mutex to synchronise Store read/write operations // mutex to synchronise Store read/write operations
mux sync.Mutex `json:"-"` mux sync.Mutex `json:"-"`
@@ -45,6 +46,7 @@ func restore(file string) (*FileStore, error) {
mux: sync.Mutex{}, mux: sync.Mutex{},
SetupKeyId2AccountId: make(map[string]string), SetupKeyId2AccountId: make(map[string]string),
PeerKeyId2AccountId: make(map[string]string), PeerKeyId2AccountId: make(map[string]string),
UserId2AccountId: make(map[string]string),
storeFile: file, storeFile: file,
} }
@@ -65,6 +67,7 @@ func restore(file string) (*FileStore, error) {
store.storeFile = file store.storeFile = file
store.SetupKeyId2AccountId = make(map[string]string) store.SetupKeyId2AccountId = make(map[string]string)
store.PeerKeyId2AccountId = make(map[string]string) store.PeerKeyId2AccountId = make(map[string]string)
store.UserId2AccountId = make(map[string]string)
for accountId, account := range store.Accounts { for accountId, account := range store.Accounts {
for setupKeyId := range account.SetupKeys { for setupKeyId := range account.SetupKeys {
store.SetupKeyId2AccountId[strings.ToUpper(setupKeyId)] = accountId store.SetupKeyId2AccountId[strings.ToUpper(setupKeyId)] = accountId
@@ -72,6 +75,9 @@ func restore(file string) (*FileStore, error) {
for _, peer := range account.Peers { for _, peer := range account.Peers {
store.PeerKeyId2AccountId[peer.Key] = accountId store.PeerKeyId2AccountId[peer.Key] = accountId
} }
for _, user := range account.Users {
store.UserId2AccountId[user.Id] = accountId
}
} }
return store, nil return store, nil
@@ -168,6 +174,10 @@ func (s *FileStore) SaveAccount(account *Account) error {
s.PeerKeyId2AccountId[peer.Key] = account.Id s.PeerKeyId2AccountId[peer.Key] = account.Id
} }
for _, user := range account.Users {
s.UserId2AccountId[user.Id] = account.Id
}
err := s.persist(s.storeFile) err := s.persist(s.storeFile)
if err != nil { if err != nil {
return err return err
@@ -217,6 +227,18 @@ func (s *FileStore) GetAccount(accountId string) (*Account, error) {
return account, nil return account, nil
} }
func (s *FileStore) GetUserAccount(userId string) (*Account, error) {
s.mux.Lock()
defer s.mux.Unlock()
accountId, accountIdFound := s.UserId2AccountId[userId]
if !accountIdFound {
return nil, status.Errorf(codes.NotFound, "account not found")
}
return s.GetAccount(accountId)
}
func (s *FileStore) GetPeerAccount(peerKey string) (*Account, error) { func (s *FileStore) GetPeerAccount(peerKey string) (*Account, error) {
s.mux.Lock() s.mux.Lock()
defer s.mux.Unlock() defer s.mux.Unlock()

View File

@@ -0,0 +1,171 @@
package server
import (
"github.com/wiretrustee/wiretrustee/util"
"net"
"path/filepath"
"testing"
"time"
)
func TestNewStore(t *testing.T) {
store := newStore(t)
if store.Accounts == nil || len(store.Accounts) != 0 {
t.Errorf("expected to create a new empty Accounts map when creating a new FileStore")
}
if store.SetupKeyId2AccountId == nil || len(store.SetupKeyId2AccountId) != 0 {
t.Errorf("expected to create a new empty SetupKeyId2AccountId map when creating a new FileStore")
}
if store.PeerKeyId2AccountId == nil || len(store.PeerKeyId2AccountId) != 0 {
t.Errorf("expected to create a new empty PeerKeyId2AccountId map when creating a new FileStore")
}
if store.UserId2AccountId == nil || len(store.UserId2AccountId) != 0 {
t.Errorf("expected to create a new empty UserId2AccountId map when creating a new FileStore")
}
}
func TestSaveAccount(t *testing.T) {
store := newStore(t)
account, _ := newAccount("testuser")
account.Users["testuser"] = NewAdminUser("testuser")
setupKey := GenerateDefaultSetupKey()
account.SetupKeys[setupKey.Key] = setupKey
account.Peers["testpeer"] = &Peer{
Key: "peerkey",
SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{},
Name: "peer name",
Status: &PeerStatus{Connected: true, LastSeen: time.Now()},
}
// SaveAccount should trigger persist
err := store.SaveAccount(account)
if err != nil {
return
}
if store.Accounts[account.Id] == nil {
t.Errorf("expecting Account to be stored after SaveAccount()")
}
if store.PeerKeyId2AccountId["peerkey"] == "" {
t.Errorf("expecting PeerKeyId2AccountId index updated after SaveAccount()")
}
if store.UserId2AccountId["testuser"] == "" {
t.Errorf("expecting UserId2AccountId index updated after SaveAccount()")
}
if store.SetupKeyId2AccountId[setupKey.Key] == "" {
t.Errorf("expecting SetupKeyId2AccountId index updated after SaveAccount()")
}
}
func TestStore(t *testing.T) {
store := newStore(t)
account, _ := newAccount("testuser")
account.Users["testuser"] = NewAdminUser("testuser")
account.Peers["testpeer"] = &Peer{
Key: "peerkey",
SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{},
Name: "peer name",
Status: &PeerStatus{Connected: true, LastSeen: time.Now()},
}
// SaveAccount should trigger persist
err := store.SaveAccount(account)
if err != nil {
return
}
restored, err := NewStore(store.storeFile)
if err != nil {
return
}
restoredAccount := restored.Accounts[account.Id]
if restoredAccount == nil {
t.Errorf("failed to restore a FileStore file - missing Account %s", account.Id)
}
if restoredAccount != nil && restoredAccount.Peers["testpeer"] == nil {
t.Errorf("failed to restore a FileStore file - missing Peer testpeer")
}
if restoredAccount != nil && restoredAccount.CreatedBy != "testuser" {
t.Errorf("failed to restore a FileStore file - missing Account CreatedBy")
}
if restoredAccount != nil && restoredAccount.Users["testuser"] == nil {
t.Errorf("failed to restore a FileStore file - missing User testuser")
}
if restoredAccount != nil && restoredAccount.Network == nil {
t.Errorf("failed to restore a FileStore file - missing Network")
}
}
func TestRestore(t *testing.T) {
storeDir := t.TempDir()
err := util.CopyFileContents("testdata/store.json", filepath.Join(storeDir, "store.json"))
if err != nil {
t.Fatal(err)
}
store, err := NewStore(storeDir)
if err != nil {
return
}
account := store.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
if account == nil {
t.Errorf("failed to restore a FileStore file - missing account bf1c8084-ba50-4ce7-9439-34653001fc3b")
}
if account != nil && account.Users["edafee4e-63fb-11ec-90d6-0242ac120003"] == nil {
t.Errorf("failed to restore a FileStore file - missing Account User edafee4e-63fb-11ec-90d6-0242ac120003")
}
if account != nil && account.Users["f4f6d672-63fb-11ec-90d6-0242ac120003"] == nil {
t.Errorf("failed to restore a FileStore file - missing Account User f4f6d672-63fb-11ec-90d6-0242ac120003")
}
if account != nil && account.Network == nil {
t.Errorf("failed to restore a FileStore file - missing Account Network")
}
if account != nil && account.SetupKeys["A2C8E62B-38F5-4553-B31E-DD66C696CEBB"] == nil {
t.Errorf("failed to restore a FileStore file - missing Account SetupKey A2C8E62B-38F5-4553-B31E-DD66C696CEBB")
}
if len(store.UserId2AccountId) != 2 {
t.Errorf("failed to restore a FileStore wrong UserId2AccountId mapping")
}
if len(store.SetupKeyId2AccountId) != 1 {
t.Errorf("failed to restore a FileStore wrong SetupKeyId2AccountId mapping")
}
}
func newStore(t *testing.T) *FileStore {
store, err := NewStore(t.TempDir())
if err != nil {
t.Errorf("failed creating a new store")
}
return store
}

View File

@@ -62,7 +62,13 @@ func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseW
} }
func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) { func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
accountId := extractAccountIdFromRequestContext(r) userId := extractUserIdFromRequestContext(r)
account, err := h.accountManager.GetOrCreateAccountByUser(userId)
if err != nil {
log.Errorf("failed getting account of a user %s: %v", userId, err)
http.Redirect(w, r, "/", http.StatusInternalServerError)
return
}
vars := mux.Vars(r) vars := mux.Vars(r)
peerId := vars["id"] //effectively peer IP address peerId := vars["id"] //effectively peer IP address
if len(peerId) == 0 { if len(peerId) == 0 {
@@ -70,7 +76,7 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
return return
} }
peer, err := h.accountManager.GetPeerByIP(accountId, peerId) peer, err := h.accountManager.GetPeerByIP(account.Id, peerId)
if err != nil { if err != nil {
http.Error(w, "peer not found", http.StatusNotFound) http.Error(w, "peer not found", http.StatusNotFound)
return return
@@ -78,10 +84,10 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case http.MethodDelete: case http.MethodDelete:
h.deletePeer(accountId, peer, w, r) h.deletePeer(account.Id, peer, w, r)
return return
case http.MethodPut: case http.MethodPut:
h.updatePeer(accountId, peer, w, r) h.updatePeer(account.Id, peer, w, r)
return return
case http.MethodGet: case http.MethodGet:
writeJSONObject(w, toPeerResponse(peer)) writeJSONObject(w, toPeerResponse(peer))
@@ -96,11 +102,11 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) { func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
accountId := extractAccountIdFromRequestContext(r) userId := extractUserIdFromRequestContext(r)
//new user -> create a new account //new user -> create a new account
account, err := h.accountManager.GetOrCreateAccount(accountId) account, err := h.accountManager.GetOrCreateAccountByUser(userId)
if err != nil { if err != nil {
log.Errorf("failed getting user account %s: %v", accountId, err) log.Errorf("failed getting account of a user %s: %v", userId, err)
http.Redirect(w, r, "/", http.StatusInternalServerError) http.Redirect(w, r, "/", http.StatusInternalServerError)
return return
} }

View File

@@ -118,7 +118,14 @@ func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.R
} }
func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) { func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
accountId := extractAccountIdFromRequestContext(r) userId := extractUserIdFromRequestContext(r)
account, err := h.accountManager.GetOrCreateAccountByUser(userId)
if err != nil {
log.Errorf("failed getting account of a user %s: %v", userId, err)
http.Redirect(w, r, "/", http.StatusInternalServerError)
return
}
vars := mux.Vars(r) vars := mux.Vars(r)
keyId := vars["id"] keyId := vars["id"]
if len(keyId) == 0 { if len(keyId) == 0 {
@@ -128,10 +135,10 @@ func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case http.MethodPut: case http.MethodPut:
h.updateKey(accountId, keyId, w, r) h.updateKey(account.Id, keyId, w, r)
return return
case http.MethodGet: case http.MethodGet:
h.getKey(accountId, keyId, w, r) h.getKey(account.Id, keyId, w, r)
return return
default: default:
http.Error(w, "", http.StatusNotFound) http.Error(w, "", http.StatusNotFound)
@@ -140,21 +147,20 @@ func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) { func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
accountId := extractAccountIdFromRequestContext(r) userId := extractUserIdFromRequestContext(r)
//new user -> create a new account
account, err := h.accountManager.GetOrCreateAccountByUser(userId)
if err != nil {
log.Errorf("failed getting account of a user %s: %v", userId, err)
http.Redirect(w, r, "/", http.StatusInternalServerError)
return
}
switch r.Method { switch r.Method {
case http.MethodPost: case http.MethodPost:
h.createKey(accountId, w, r) h.createKey(account.Id, w, r)
return return
case http.MethodGet: case http.MethodGet:
//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.WriteHeader(200)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
@@ -165,7 +171,7 @@ func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
err = json.NewEncoder(w).Encode(respBody) err = json.NewEncoder(w).Encode(respBody)
if err != nil { if err != nil {
log.Errorf("failed encoding account peers %s: %v", accountId, err) log.Errorf("failed encoding account peers %s: %v", account.Id, err)
http.Redirect(w, r, "/", http.StatusInternalServerError) http.Redirect(w, r, "/", http.StatusInternalServerError)
return return
} }

View File

@@ -8,8 +8,8 @@ import (
"time" "time"
) )
// extractAccountIdFromRequestContext extracts accountId from the request context previously filled by the JWT token (after auth) // extractUserIdFromRequestContext extracts accountId from the request context previously filled by the JWT token (after auth)
func extractAccountIdFromRequestContext(r *http.Request) string { func extractUserIdFromRequestContext(r *http.Request) string {
token := r.Context().Value("user").(*jwt.Token) token := r.Context().Value("user").(*jwt.Token)
claims := token.Claims.(jwt.MapClaims) claims := token.Claims.(jwt.MapClaims)

View File

@@ -3,6 +3,7 @@ package server_test
import ( import (
"context" "context"
server "github.com/wiretrustee/wiretrustee/management/server" server "github.com/wiretrustee/wiretrustee/management/server"
"google.golang.org/grpc/credentials/insecure"
"io/ioutil" "io/ioutil"
"math/rand" "math/rand"
"net" "net"
@@ -472,7 +473,9 @@ func loginPeerWithValidSetupKey(serverPubKey wgtypes.Key, key wgtypes.Key, clien
func createRawClient(addr string) (mgmtProto.ManagementServiceClient, *grpc.ClientConn) { func createRawClient(addr string) (mgmtProto.ManagementServiceClient, *grpc.ClientConn) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure(),
conn, err := grpc.DialContext(ctx, addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(), grpc.WithBlock(),
grpc.WithKeepaliveParams(keepalive.ClientParameters{ grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 10 * time.Second, Time: 10 * time.Second,

View File

@@ -0,0 +1,13 @@
## Migration from Store v2 to Store v2
Previously Account.Id was an Auth0 user id.
Conversion moves user id to Account.CreatedBy and generates a new Account.Id using xid.
It also adds a User with id = old Account.Id with a role Admin.
To start a conversion simply run the command below providing your current Wiretrustee Management datadir (where store.json file is located)
and a new data directory location (where a converted store.js will be stored):
```shell
./migration --oldDir /var/wiretrustee/datadir --newDir /var/wiretrustee/newdatadir/
```
Afterwards you can run the Management service providing ```/var/wiretrustee/newdatadir/ ``` as a datadir.

View File

@@ -0,0 +1,56 @@
package main
import (
"flag"
"fmt"
"github.com/rs/xid"
"github.com/wiretrustee/wiretrustee/management/server"
)
func main() {
oldDir := flag.String("oldDir", "old store directory", "/var/wiretrustee/datadir")
newDir := flag.String("newDir", "new store directory", "/var/wiretrustee/newdatadir")
flag.Parse()
oldStore, err := server.NewStore(*oldDir)
if err != nil {
panic(err)
}
newStore, err := server.NewStore(*newDir)
if err != nil {
panic(err)
}
err = Convert(oldStore, newStore)
if err != nil {
panic(err)
}
fmt.Println("successfully converted")
}
// Convert converts old store ato a new store
// Previously Account.Id was an Auth0 user id
// Conversion moved user id to Account.CreatedBy and generated a new Account.Id using xid
// It also adds a User with id = old Account.Id with a role Admin
func Convert(oldStore *server.FileStore, newStore *server.FileStore) error {
for _, account := range oldStore.Accounts {
accountCopy := account.Copy()
accountCopy.Id = xid.New().String()
accountCopy.CreatedBy = account.Id
accountCopy.Users[account.Id] = &server.User{
Id: account.Id,
Role: server.UserRoleAdmin,
}
err := newStore.SaveAccount(accountCopy)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,76 @@
package main
import (
"github.com/wiretrustee/wiretrustee/management/server"
"github.com/wiretrustee/wiretrustee/util"
"path/filepath"
"testing"
)
func TestConvertAccounts(t *testing.T) {
storeDir := t.TempDir()
err := util.CopyFileContents("../testdata/storev1.json", filepath.Join(storeDir, "store.json"))
if err != nil {
t.Fatal(err)
}
store, err := server.NewStore(storeDir)
if err != nil {
t.Fatal(err)
}
convertedStore, err := server.NewStore(filepath.Join(storeDir, "converted"))
if err != nil {
t.Fatal(err)
}
err = Convert(store, convertedStore)
if err != nil {
t.Fatal(err)
}
if len(store.Accounts) != len(convertedStore.Accounts) {
t.Errorf("expecting the same number of accounts after conversion")
}
for _, account := range store.Accounts {
convertedAccount, err := convertedStore.GetUserAccount(account.Id)
if err != nil || convertedAccount == nil {
t.Errorf("expecting Account %s to be converted", account.Id)
return
}
if convertedAccount.CreatedBy != account.Id {
t.Errorf("expecting converted Account.CreatedBy field to be equal to the old Account.Id")
return
}
if convertedAccount.Id == account.Id {
t.Errorf("expecting converted Account.Id to be different from Account.Id")
return
}
if len(convertedAccount.Users) != 1 {
t.Errorf("expecting converted Account.Users to be of size 1")
return
}
user := convertedAccount.Users[account.Id]
if user == nil {
t.Errorf("expecting to find a user in converted Account.Users")
return
}
if user.Role != server.UserRoleAdmin {
t.Errorf("expecting to find a user in converted Account.Users with a role Admin")
return
}
for peerId := range account.Peers {
convertedPeer := convertedAccount.Peers[peerId]
if convertedPeer == nil {
t.Errorf("expecting Account Peer of StoreV1 to be found in StoreV2")
return
}
}
}
}

View File

@@ -17,6 +17,14 @@ type Network struct {
Dns string Dns string
} }
func (n *Network) Copy() *Network {
return &Network{
Id: n.Id,
Net: n.Net,
Dns: n.Dns,
}
}
// AllocatePeerIP pics an available IP from an net.IPNet. // AllocatePeerIP pics an available IP from an net.IPNet.
// This method considers already taken IPs and reuses IPs if there are gaps in takenIps // This method considers already taken IPs and reuses IPs if there are gaps in takenIps
// E.g. if ipNet=100.30.0.0/16 and takenIps=[100.30.0.1, 100.30.0.5] then the result would be 100.30.0.2 // E.g. if ipNet=100.30.0.0/16 and takenIps=[100.30.0.1, 100.30.0.5] then the result would be 100.30.0.2

View File

@@ -206,7 +206,6 @@ func (am *AccountManager) GetPeersForAPeer(peerKey string) ([]*Peer, error) {
// Each Account has a list of pre-authorised SetupKey and if no Account has a given key err wit ha code codes.Unauthenticated // 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 // will be returned, meaning the key is invalid
// Each new Peer will be assigned a new next net.IP from the Account.Network and Account.Network.LastIP will be updated (IP's are not reused). // Each new Peer will be assigned a new next net.IP from the Account.Network and Account.Network.LastIP will be updated (IP's are not reused).
// If the specified setupKey is empty then a new Account will be created //todo remove this part
// The peer property is just a placeholder for the Peer properties to pass further // The peer property is just a placeholder for the Peer properties to pass further
func (am *AccountManager) AddPeer(setupKey string, peer Peer) (*Peer, error) { func (am *AccountManager) AddPeer(setupKey string, peer Peer) (*Peer, error) {
am.mux.Lock() am.mux.Lock()
@@ -218,8 +217,8 @@ func (am *AccountManager) AddPeer(setupKey string, peer Peer) (*Peer, error) {
var err error var err error
var sk *SetupKey var sk *SetupKey
if len(upperKey) == 0 { if len(upperKey) == 0 {
// Empty setup key, create a new account for it. // Empty setup key, fail
account, sk = newAccount() return nil, status.Errorf(codes.InvalidArgument, "empty setupKey %s", setupKey)
} else { } else {
account, err = am.Store.GetAccountBySetupKey(upperKey) account, err = am.Store.GetAccountBySetupKey(upperKey)
if err != nil { if err != nil {

View File

@@ -5,6 +5,7 @@ type Store interface {
DeletePeer(accountId string, peerKey string) (*Peer, error) DeletePeer(accountId string, peerKey string) (*Peer, error)
SavePeer(accountId string, peer *Peer) error SavePeer(accountId string, peer *Peer) error
GetAccount(accountId string) (*Account, error) GetAccount(accountId string) (*Account, error)
GetUserAccount(userId string) (*Account, error)
GetAccountPeers(accountId string) ([]*Peer, error) GetAccountPeers(accountId string) ([]*Peer, error)
GetPeerAccount(peerKey string) (*Account, error) GetPeerAccount(peerKey string) (*Account, error)
GetAccountBySetupKey(setupKey string) (*Account, error) GetAccountBySetupKey(setupKey string) (*Account, error)

View File

@@ -22,7 +22,17 @@
}, },
"Dns": null "Dns": null
}, },
"Peers": {} "Peers": {},
"Users": {
"edafee4e-63fb-11ec-90d6-0242ac120003": {
"Id": "edafee4e-63fb-11ec-90d6-0242ac120003",
"Role": "admin"
},
"f4f6d672-63fb-11ec-90d6-0242ac120003": {
"Id": "f4f6d672-63fb-11ec-90d6-0242ac120003",
"Role": "user"
}
}
} }
} }
} }

154
management/server/testdata/storev1.json vendored Normal file
View File

@@ -0,0 +1,154 @@
{
"Accounts": {
"auth0|61bf82ddeab084006aa1bccd": {
"Id": "auth0|61bf82ddeab084006aa1bccd",
"SetupKeys": {
"1B2B50B0-B3E8-4B0C-A426-525EDB8481BD": {
"Id": "831727121",
"Key": "1B2B50B0-B3E8-4B0C-A426-525EDB8481BD",
"Name": "One-off key",
"Type": "one-off",
"CreatedAt": "2021-12-24T16:09:45.926075752+01:00",
"ExpiresAt": "2022-01-23T16:09:45.926075752+01:00",
"Revoked": false,
"UsedTimes": 1,
"LastUsed": "2021-12-24T16:12:45.763424077+01:00"
},
"EB51E9EB-A11F-4F6E-8E49-C982891B405A": {
"Id": "1769568301",
"Key": "EB51E9EB-A11F-4F6E-8E49-C982891B405A",
"Name": "Default key",
"Type": "reusable",
"CreatedAt": "2021-12-24T16:09:45.926073628+01:00",
"ExpiresAt": "2022-01-23T16:09:45.926073628+01:00",
"Revoked": false,
"UsedTimes": 1,
"LastUsed": "2021-12-24T16:13:06.236748538+01:00"
}
},
"Network": {
"Id": "a443c07a-5765-4a78-97fc-390d9c1d0e49",
"Net": {
"IP": "100.64.0.0",
"Mask": "/8AAAA=="
},
"Dns": ""
},
"Peers": {
"oMNaI8qWi0CyclSuwGR++SurxJyM3pQEiPEHwX8IREo=": {
"Key": "oMNaI8qWi0CyclSuwGR++SurxJyM3pQEiPEHwX8IREo=",
"SetupKey": "EB51E9EB-A11F-4F6E-8E49-C982891B405A",
"IP": "100.64.0.2",
"Meta": {
"Hostname": "braginini",
"GoOS": "linux",
"Kernel": "Linux",
"Core": "21.04",
"Platform": "x86_64",
"OS": "Ubuntu",
"WtVersion": ""
},
"Name": "braginini",
"Status": {
"LastSeen": "2021-12-24T16:13:11.244342541+01:00",
"Connected": false
}
},
"xlx9/9D8+ibnRiIIB8nHGMxGOzxV17r8ShPHgi4aYSM=": {
"Key": "xlx9/9D8+ibnRiIIB8nHGMxGOzxV17r8ShPHgi4aYSM=",
"SetupKey": "1B2B50B0-B3E8-4B0C-A426-525EDB8481BD",
"IP": "100.64.0.1",
"Meta": {
"Hostname": "braginini",
"GoOS": "linux",
"Kernel": "Linux",
"Core": "21.04",
"Platform": "x86_64",
"OS": "Ubuntu",
"WtVersion": ""
},
"Name": "braginini",
"Status": {
"LastSeen": "2021-12-24T16:12:49.089339333+01:00",
"Connected": false
}
}
}
},
"google-oauth2|103201118415301331038": {
"Id": "google-oauth2|103201118415301331038",
"SetupKeys": {
"5AFB60DB-61F2-4251-8E11-494847EE88E9": {
"Id": "2485964613",
"Key": "5AFB60DB-61F2-4251-8E11-494847EE88E9",
"Name": "Default key",
"Type": "reusable",
"CreatedAt": "2021-12-24T16:10:02.238476+01:00",
"ExpiresAt": "2022-01-23T16:10:02.238476+01:00",
"Revoked": false,
"UsedTimes": 1,
"LastUsed": "2021-12-24T16:12:05.994307717+01:00"
},
"A72E4DC2-00DE-4542-8A24-62945438104E": {
"Id": "3504804807",
"Key": "A72E4DC2-00DE-4542-8A24-62945438104E",
"Name": "One-off key",
"Type": "one-off",
"CreatedAt": "2021-12-24T16:10:02.238478209+01:00",
"ExpiresAt": "2022-01-23T16:10:02.238478209+01:00",
"Revoked": false,
"UsedTimes": 1,
"LastUsed": "2021-12-24T16:11:27.015741738+01:00"
}
},
"Network": {
"Id": "b6d0b152-364e-40c1-a8a1-fa7bcac2267f",
"Net": {
"IP": "100.64.0.0",
"Mask": "/8AAAA=="
},
"Dns": ""
},
"Peers": {
"6kjbmVq1hmucVzvBXo5OucY5OYv+jSsB1jUTLq291Dw=": {
"Key": "6kjbmVq1hmucVzvBXo5OucY5OYv+jSsB1jUTLq291Dw=",
"SetupKey": "5AFB60DB-61F2-4251-8E11-494847EE88E9",
"IP": "100.64.0.2",
"Meta": {
"Hostname": "braginini",
"GoOS": "linux",
"Kernel": "Linux",
"Core": "21.04",
"Platform": "x86_64",
"OS": "Ubuntu",
"WtVersion": ""
},
"Name": "braginini",
"Status": {
"LastSeen": "2021-12-24T16:12:05.994305438+01:00",
"Connected": false
}
},
"Ok+5QMdt/UjoktNOvicGYj+IX2g98p+0N2PJ3vJ45RI=": {
"Key": "Ok+5QMdt/UjoktNOvicGYj+IX2g98p+0N2PJ3vJ45RI=",
"SetupKey": "A72E4DC2-00DE-4542-8A24-62945438104E",
"IP": "100.64.0.1",
"Meta": {
"Hostname": "braginini",
"GoOS": "linux",
"Kernel": "Linux",
"Core": "21.04",
"Platform": "x86_64",
"OS": "Ubuntu",
"WtVersion": ""
},
"Name": "braginini",
"Status": {
"LastSeen": "2021-12-24T16:11:27.015739803+01:00",
"Connected": false
}
}
}
}
}
}

71
management/server/user.go Normal file
View File

@@ -0,0 +1,71 @@
package server
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
UserRoleAdmin UserRole = "admin"
UserRoleUser UserRole = "user"
)
// UserRole is the role of the User
type UserRole string
// User represents a user of the system
type User struct {
Id string
Role UserRole
}
func (u *User) Copy() *User {
return &User{
Id: u.Id,
Role: u.Role,
}
}
// NewUser creates a new user
func NewUser(id string, role UserRole) *User {
return &User{
Id: id,
Role: role,
}
}
// NewAdminUser creates a new user with role UserRoleAdmin
func NewAdminUser(id string) *User {
return NewUser(id, UserRoleAdmin)
}
// GetOrCreateAccountByUser returns an existing account for a given user id or creates a new one if doesn't exist
func (am *AccountManager) GetOrCreateAccountByUser(userId string) (*Account, error) {
am.mux.Lock()
defer am.mux.Unlock()
account, err := am.Store.GetUserAccount(userId)
if err != nil {
if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
account, _ = newAccount(userId)
account.Users[userId] = NewAdminUser(userId)
err = am.Store.SaveAccount(account)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed creating account")
}
} else {
// other error
return nil, err
}
}
return account, nil
}
// GetAccountByUser returns an existing account for a given user id, NotFound if account couldn't be found
func (am *AccountManager) GetAccountByUser(userId string) (*Account, error) {
am.mux.Lock()
defer am.mux.Unlock()
return am.Store.GetUserAccount(userId)
}

View File

@@ -13,6 +13,7 @@ import (
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/connectivity" "google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
@@ -56,7 +57,7 @@ func (c *Client) Close() error {
// NewClient creates a new Signal client // NewClient creates a new Signal client
func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled bool) (*Client, error) { func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled bool) (*Client, error) {
transportOption := grpc.WithInsecure() transportOption := grpc.WithTransportCredentials(insecure.NewCredentials())
if tlsEnabled { if tlsEnabled {
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})) transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))

View File

@@ -9,6 +9,7 @@ import (
"github.com/wiretrustee/wiretrustee/signal/server" "github.com/wiretrustee/wiretrustee/signal/server"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"net" "net"
@@ -170,7 +171,8 @@ func createSignalClient(addr string, key wgtypes.Key) *Client {
func createRawSignalClient(addr string) sigProto.SignalExchangeClient { func createRawSignalClient(addr string) sigProto.SignalExchangeClient {
ctx := context.Background() ctx := context.Background()
conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure(), conn, err := grpc.DialContext(ctx, addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(), grpc.WithBlock(),
grpc.WithKeepaliveParams(keepalive.ClientParameters{ grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 3 * time.Second, Time: 3 * time.Second,