Add user bypass feature

pull/71/head
Qian Wang 5 years ago
parent cc9aaec483
commit 76095bde0f

@ -67,6 +67,10 @@ Then run `make client` or `make server`. Output binary will be in `build` folder
6. [Configure the proxy program.](https://github.com/cbeuw/Cloak/wiki/Underlying-proxy-configuration-guides) Run `sudo ck-server -c <path to ckserver.json>`. ck-server needs root privilege because it binds to a low numbered port (443). Alternatively you can follow https://superuser.com/a/892391 to avoid granting ck-server root privilege unnecessarily. 6. [Configure the proxy program.](https://github.com/cbeuw/Cloak/wiki/Underlying-proxy-configuration-guides) Run `sudo ck-server -c <path to ckserver.json>`. ck-server needs root privilege because it binds to a low numbered port (443). Alternatively you can follow https://superuser.com/a/892391 to avoid granting ck-server root privilege unnecessarily.
#### To add users #### To add users
##### Unrestricted users
Run `ck-server -u` and add the UID into the `BypassUID` field in `ckserver.json`
##### Users subject to bandwidth and credit controls
1. On your client, run `ck-client -s <IP of the server> -l <A local port> -a <AdminUID> -c <path-to-ckclient.json>` to enter admin mode 1. On your client, run `ck-client -s <IP of the server> -l <A local port> -a <AdminUID> -c <path-to-ckclient.json>` to enter admin mode
2. Visit https://cbeuw.github.io/Cloak-panel (Note: this is a static site, there is no backend and all data entered into this site are processed between your browser and the Cloak API endpoint you specified. Alternatively you can download the repo at https://github.com/cbeuw/Cloak-panel and host it on your own web server). 2. Visit https://cbeuw.github.io/Cloak-panel (Note: this is a static site, there is no backend and all data entered into this site are processed between your browser and the Cloak API endpoint you specified. Alternatively you can download the repo at https://github.com/cbeuw/Cloak-panel and host it on your own web server).
3. Type in 127.0.0.1:<the port you entered in step 1> as the API Base, and click `List`. 3. Type in 127.0.0.1:<the port you entered in step 1> as the API Base, and click `List`.

@ -116,7 +116,12 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
} }
} }
user, err := sta.Panel.GetUser(UID) var user *server.ActiveUser
if sta.IsBypass(UID) {
user, err = sta.Panel.GetBypassUser(UID)
} else {
user, err = sta.Panel.GetUser(UID)
}
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"UID": b64(UID), "UID": b64(UID),
@ -129,7 +134,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
sesh, existing, err := user.GetSession(sessionID, obfuscator, util.ReadTLS) sesh, existing, err := user.GetSession(sessionID, obfuscator, util.ReadTLS)
if err != nil { if err != nil {
user.DelSession(sessionID) user.DeleteSession(sessionID, "")
log.Error(err) log.Error(err)
return return
} }
@ -165,7 +170,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
"sessionID": sessionID, "sessionID": sessionID,
"reason": sesh.TerminalMsg(), "reason": sesh.TerminalMsg(),
}).Info("Session closed") }).Info("Session closed")
user.DelSession(sessionID) user.DeleteSession(sessionID, "")
return return
} else { } else {
continue continue

@ -4,6 +4,9 @@
"openvpn": "127.0.0.1:8389", "openvpn": "127.0.0.1:8389",
"tor": "127.0.0.1:9001" "tor": "127.0.0.1:9001"
}, },
"BypassUID": [
"1rmq6Ag1jZJCImLBIL5wzQ=="
],
"RedirAddr": "204.79.197.200:443", "RedirAddr": "204.79.197.200:443",
"PrivateKey": "EN5aPEpNBO+vw+BtFQY2OnK9bQU7rvEj5qmnmgwEtUc=", "PrivateKey": "EN5aPEpNBO+vw+BtFQY2OnK9bQU7rvEj5qmnmgwEtUc=",
"AdminUID": "5nneblJy6lniPJfr81LuYQ==", "AdminUID": "5nneblJy6lniPJfr81LuYQ==",

@ -14,18 +14,22 @@ type ActiveUser struct {
valve *mux.Valve valve *mux.Valve
bypass bool
sessionsM sync.RWMutex sessionsM sync.RWMutex
sessions map[uint32]*mux.Session sessions map[uint32]*mux.Session
} }
func (u *ActiveUser) DelSession(sessionID uint32) { func (u *ActiveUser) DeleteSession(sessionID uint32, reason string) {
u.sessionsM.Lock() u.sessionsM.Lock()
delete(u.sessions, sessionID) sesh, existing := u.sessions[sessionID]
if existing {
delete(u.sessions, sessionID)
sesh.SetTerminalMsg(reason)
sesh.Close()
}
if len(u.sessions) == 0 { if len(u.sessions) == 0 {
u.panel.updateUsageQueueForOne(u) u.panel.DeleteActiveUser(u)
u.panel.activeUsersM.Lock()
delete(u.panel.activeUsers, u.arrUID)
u.panel.activeUsersM.Unlock()
} }
u.sessionsM.Unlock() u.sessionsM.Unlock()
} }
@ -36,9 +40,11 @@ func (u *ActiveUser) GetSession(sessionID uint32, obfuscator *mux.Obfuscator, un
if sesh = u.sessions[sessionID]; sesh != nil { if sesh = u.sessions[sessionID]; sesh != nil {
return sesh, true, nil return sesh, true, nil
} else { } else {
err := u.panel.Manager.AuthoriseNewSession(u.arrUID[:], len(u.sessions)) if !u.bypass {
if err != nil { err := u.panel.Manager.AuthoriseNewSession(u.arrUID[:], len(u.sessions))
return nil, false, err if err != nil {
return nil, false, err
}
} }
sesh = mux.MakeSession(sessionID, u.valve, obfuscator, unitReader) sesh = mux.MakeSession(sessionID, u.valve, obfuscator, unitReader)
u.sessions[sessionID] = sesh u.sessions[sessionID] = sesh
@ -52,12 +58,10 @@ func (u *ActiveUser) Terminate(reason string) {
if reason != "" { if reason != "" {
sesh.SetTerminalMsg(reason) sesh.SetTerminalMsg(reason)
} }
go sesh.Close() sesh.Close()
} }
u.sessionsM.Unlock() u.sessionsM.Unlock()
u.panel.activeUsersM.Lock() u.panel.DeleteActiveUser(u)
delete(u.panel.activeUsers, u.arrUID)
u.panel.activeUsersM.Unlock()
} }
func (u *ActiveUser) NumSession() int { func (u *ActiveUser) NumSession() int {

@ -0,0 +1,118 @@
package server
import (
"encoding/base64"
mux "github.com/cbeuw/Cloak/internal/multiplex"
"github.com/cbeuw/Cloak/internal/server/usermanager"
"os"
"testing"
)
func TestActiveUser_Bypass(t *testing.T) {
manager, err := usermanager.MakeLocalManager(MOCK_DB_NAME)
if err != nil {
t.Error("failed to make local manager", err)
}
panel := MakeUserPanel(manager)
UID, _ := base64.StdEncoding.DecodeString("u97xvcc5YoQA8obCyt9q/w==")
user, _ := panel.GetBypassUser(UID)
obfuscator := &mux.Obfuscator{
nil,
nil,
nil,
}
var sesh0 *mux.Session
var existing bool
var sesh1 *mux.Session
t.Run("get first session", func(t *testing.T) {
sesh0, existing, err = user.GetSession(0, obfuscator, nil)
if err != nil {
t.Error(err)
}
if existing {
t.Error("first session returned as existing")
}
if sesh0 == nil {
t.Error("no session returned")
}
})
t.Run("get first session again", func(t *testing.T) {
seshx, existing, err := user.GetSession(0, obfuscator, nil)
if err != nil {
t.Error(err)
}
if !existing {
t.Error("first session get again returned as not existing")
}
if seshx == nil {
t.Error("no session returned")
}
if seshx != sesh0 {
t.Error("returned a different instance")
}
})
t.Run("get second session", func(t *testing.T) {
sesh1, existing, err = user.GetSession(1, obfuscator, nil)
if err != nil {
t.Error(err)
}
if existing {
t.Error("second session returned as existing")
}
if sesh0 == nil {
t.Error("no session returned")
}
})
t.Run("number of sessions", func(t *testing.T) {
if user.NumSession() != 2 {
t.Error("number of session is not 2")
}
})
t.Run("delete a session", func(t *testing.T) {
user.DeleteSession(0, "")
if user.NumSession() != 1 {
t.Error("number of session is not 1 after deleting one")
}
if !sesh0.IsClosed() {
t.Error("session not closed after deletion")
}
})
t.Run("terminating user", func(t *testing.T) {
user.Terminate("")
if panel.isActive(user.arrUID[:]) {
t.Error("user is still active after termination")
}
if !sesh1.IsClosed() {
t.Error("session not closed after user termination")
}
})
t.Run("get session again after termination", func(t *testing.T) {
seshx, existing, err := user.GetSession(0, obfuscator, nil)
if err != nil {
t.Error(err)
}
if existing {
t.Error("session returned as existing")
}
if seshx == nil {
t.Error("no session returned")
}
if seshx == sesh0 || seshx == sesh1 {
t.Error("get session after termination returned the same instance")
}
})
t.Run("delete last session", func(t *testing.T) {
user.DeleteSession(0, "")
if panel.isActive(user.arrUID[:]) {
t.Error("user still active after last session deleted")
}
})
err = manager.Close()
if err != nil {
t.Error("failed to close localmanager", err)
}
err = os.Remove(MOCK_DB_NAME)
if err != nil {
t.Error("failed to delete mockdb", err)
}
}

@ -15,6 +15,7 @@ import (
type rawConfig struct { type rawConfig struct {
ProxyBook map[string]string ProxyBook map[string]string
BypassUID [][]byte
RedirAddr string RedirAddr string
PrivateKey string PrivateKey string
AdminUID string AdminUID string
@ -31,7 +32,9 @@ type State struct {
Now func() time.Time Now func() time.Time
AdminUID []byte AdminUID []byte
staticPv crypto.PrivateKey
BypassUID map[[16]byte]struct{}
staticPv crypto.PrivateKey
RedirAddr string RedirAddr string
@ -44,9 +47,10 @@ type State struct {
func InitState(bindHost, bindPort string, nowFunc func() time.Time) (*State, error) { func InitState(bindHost, bindPort string, nowFunc func() time.Time) (*State, error) {
ret := &State{ ret := &State{
BindHost: bindHost, BindHost: bindHost,
BindPort: bindPort, BindPort: bindPort,
Now: nowFunc, Now: nowFunc,
BypassUID: make(map[[16]byte]struct{}),
} }
ret.usedRandom = make(map[[32]byte]int64) ret.usedRandom = make(map[[32]byte]int64)
go ret.UsedRandomCleaner() go ret.UsedRandomCleaner()
@ -99,9 +103,24 @@ func (sta *State) ParseConfig(conf string) (err error) {
return errors.New("Failed to decode AdminUID: " + err.Error()) return errors.New("Failed to decode AdminUID: " + err.Error())
} }
sta.AdminUID = adminUID sta.AdminUID = adminUID
var arrUID [16]byte
for _, UID := range preParse.BypassUID {
copy(arrUID[:], UID)
sta.BypassUID[arrUID] = struct{}{}
}
copy(arrUID[:], adminUID)
sta.BypassUID[arrUID] = struct{}{}
return nil return nil
} }
func (sta *State) IsBypass(UID []byte) bool {
var arrUID [16]byte
copy(arrUID[:], UID)
_, exist := sta.BypassUID[arrUID]
return exist
}
// This is the accepting window of the encrypted timestamp from client // This is the accepting window of the encrypted timestamp from client
// we reject the client if the timestamp is outside of this window. // we reject the client if the timestamp is outside of this window.
// This is for replay prevention so that we don't have to save unlimited amount of // This is for replay prevention so that we don't have to save unlimited amount of

@ -195,3 +195,7 @@ func (manager *localManager) UploadStatus(uploads []StatusUpdate) ([]StatusRespo
}) })
return responses, err return responses, err
} }
func (manager *localManager) Close() error {
return manager.db.Close()
}

@ -29,6 +29,26 @@ func MakeUserPanel(manager usermanager.UserManager) *userPanel {
return ret return ret
} }
func (panel *userPanel) GetBypassUser(UID []byte) (*ActiveUser, error) {
panel.activeUsersM.Lock()
var arrUID [16]byte
copy(arrUID[:], UID)
if user, ok := panel.activeUsers[arrUID]; ok {
panel.activeUsersM.Unlock()
return user, nil
}
user := &ActiveUser{
panel: panel,
valve: mux.UNLIMITED_VALVE,
sessions: make(map[uint32]*mux.Session),
bypass: true,
}
copy(user.arrUID[:], UID)
panel.activeUsers[user.arrUID] = user
panel.activeUsersM.Unlock()
return user, nil
}
func (panel *userPanel) GetUser(UID []byte) (*ActiveUser, error) { func (panel *userPanel) GetUser(UID []byte) (*ActiveUser, error) {
panel.activeUsersM.Lock() panel.activeUsersM.Lock()
var arrUID [16]byte var arrUID [16]byte
@ -49,12 +69,20 @@ func (panel *userPanel) GetUser(UID []byte) (*ActiveUser, error) {
valve: valve, valve: valve,
sessions: make(map[uint32]*mux.Session), sessions: make(map[uint32]*mux.Session),
} }
copy(user.arrUID[:], UID) copy(user.arrUID[:], UID)
panel.activeUsers[user.arrUID] = user panel.activeUsers[user.arrUID] = user
panel.activeUsersM.Unlock() panel.activeUsersM.Unlock()
return user, nil return user, nil
} }
func (panel *userPanel) DeleteActiveUser(user *ActiveUser) {
panel.updateUsageQueueForOne(user)
panel.activeUsersM.Lock()
delete(panel.activeUsers, user.arrUID)
panel.activeUsersM.Unlock()
}
func (panel *userPanel) isActive(UID []byte) bool { func (panel *userPanel) isActive(UID []byte) bool {
var arrUID [16]byte var arrUID [16]byte
copy(arrUID[:], UID) copy(arrUID[:], UID)
@ -73,6 +101,10 @@ func (panel *userPanel) updateUsageQueue() {
panel.activeUsersM.Lock() panel.activeUsersM.Lock()
panel.usageUpdateQueueM.Lock() panel.usageUpdateQueueM.Lock()
for _, user := range panel.activeUsers { for _, user := range panel.activeUsers {
if user.bypass {
continue
}
upIncured, downIncured := user.valve.Nullify() upIncured, downIncured := user.valve.Nullify()
if usage, ok := panel.usageUpdateQueue[user.arrUID]; ok { if usage, ok := panel.usageUpdateQueue[user.arrUID]; ok {
atomic.AddInt64(usage.up, upIncured) atomic.AddInt64(usage.up, upIncured)
@ -89,6 +121,9 @@ func (panel *userPanel) updateUsageQueue() {
func (panel *userPanel) updateUsageQueueForOne(user *ActiveUser) { func (panel *userPanel) updateUsageQueueForOne(user *ActiveUser) {
// used when one particular user deactivates // used when one particular user deactivates
if user.bypass {
return
}
upIncured, downIncured := user.valve.Nullify() upIncured, downIncured := user.valve.Nullify()
panel.usageUpdateQueueM.Lock() panel.usageUpdateQueueM.Lock()
if usage, ok := panel.usageUpdateQueue[user.arrUID]; ok { if usage, ok := panel.usageUpdateQueue[user.arrUID]; ok {

@ -0,0 +1,69 @@
package server
import (
"encoding/base64"
"github.com/cbeuw/Cloak/internal/server/usermanager"
"os"
"testing"
)
const MOCK_DB_NAME = "userpanel_test_mock_database.db"
func TestUserPanel_BypassUser(t *testing.T) {
manager, err := usermanager.MakeLocalManager(MOCK_DB_NAME)
if err != nil {
t.Error("failed to make local manager", err)
}
panel := MakeUserPanel(manager)
UID, _ := base64.StdEncoding.DecodeString("u97xvcc5YoQA8obCyt9q/w==")
user, _ := panel.GetBypassUser(UID)
user.valve.AddRx(10)
user.valve.AddTx(10)
t.Run("isActive", func(t *testing.T) {
a := panel.isActive(UID)
if !a {
t.Error("isActive returned ", a)
}
})
t.Run("updateUsageQueue", func(t *testing.T) {
panel.updateUsageQueue()
if user.valve.GetRx() != 10 || user.valve.GetTx() != 10 {
t.Error("user rx or tx info altered")
}
if _, inQ := panel.usageUpdateQueue[user.arrUID]; inQ {
t.Error("user in update queue")
}
})
t.Run("updateUsageQueueForOne", func(t *testing.T) {
panel.updateUsageQueueForOne(user)
if user.valve.GetRx() != 10 || user.valve.GetTx() != 10 {
t.Error("user rx or tx info altered")
}
if _, inQ := panel.usageUpdateQueue[user.arrUID]; inQ {
t.Error("user in update queue")
}
})
t.Run("commitUpdate", func(t *testing.T) {
err := panel.commitUpdate()
if err != nil {
t.Error("commit returned", err)
}
})
t.Run("DeleteActiveUser", func(t *testing.T) {
panel.DeleteActiveUser(user)
if panel.isActive(user.arrUID[:]) {
t.Error("user still active after deletion", err)
}
})
t.Run("Repeated delete", func(t *testing.T) {
panel.DeleteActiveUser(user)
})
err = manager.Close()
if err != nil {
t.Error("failed to close localmanager", err)
}
err = os.Remove(MOCK_DB_NAME)
if err != nil {
t.Error("failed to delete mockdb", err)
}
}
Loading…
Cancel
Save