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.
#### 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
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`.

@ -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 {
log.WithFields(log.Fields{
"UID": b64(UID),
@ -129,7 +134,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
sesh, existing, err := user.GetSession(sessionID, obfuscator, util.ReadTLS)
if err != nil {
user.DelSession(sessionID)
user.DeleteSession(sessionID, "")
log.Error(err)
return
}
@ -165,7 +170,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
"sessionID": sessionID,
"reason": sesh.TerminalMsg(),
}).Info("Session closed")
user.DelSession(sessionID)
user.DeleteSession(sessionID, "")
return
} else {
continue

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

@ -14,18 +14,22 @@ type ActiveUser struct {
valve *mux.Valve
bypass bool
sessionsM sync.RWMutex
sessions map[uint32]*mux.Session
}
func (u *ActiveUser) DelSession(sessionID uint32) {
func (u *ActiveUser) DeleteSession(sessionID uint32, reason string) {
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 {
u.panel.updateUsageQueueForOne(u)
u.panel.activeUsersM.Lock()
delete(u.panel.activeUsers, u.arrUID)
u.panel.activeUsersM.Unlock()
u.panel.DeleteActiveUser(u)
}
u.sessionsM.Unlock()
}
@ -36,9 +40,11 @@ func (u *ActiveUser) GetSession(sessionID uint32, obfuscator *mux.Obfuscator, un
if sesh = u.sessions[sessionID]; sesh != nil {
return sesh, true, nil
} else {
err := u.panel.Manager.AuthoriseNewSession(u.arrUID[:], len(u.sessions))
if err != nil {
return nil, false, err
if !u.bypass {
err := u.panel.Manager.AuthoriseNewSession(u.arrUID[:], len(u.sessions))
if err != nil {
return nil, false, err
}
}
sesh = mux.MakeSession(sessionID, u.valve, obfuscator, unitReader)
u.sessions[sessionID] = sesh
@ -52,12 +58,10 @@ func (u *ActiveUser) Terminate(reason string) {
if reason != "" {
sesh.SetTerminalMsg(reason)
}
go sesh.Close()
sesh.Close()
}
u.sessionsM.Unlock()
u.panel.activeUsersM.Lock()
delete(u.panel.activeUsers, u.arrUID)
u.panel.activeUsersM.Unlock()
u.panel.DeleteActiveUser(u)
}
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 {
ProxyBook map[string]string
BypassUID [][]byte
RedirAddr string
PrivateKey string
AdminUID string
@ -31,7 +32,9 @@ type State struct {
Now func() time.Time
AdminUID []byte
staticPv crypto.PrivateKey
BypassUID map[[16]byte]struct{}
staticPv crypto.PrivateKey
RedirAddr string
@ -44,9 +47,10 @@ type State struct {
func InitState(bindHost, bindPort string, nowFunc func() time.Time) (*State, error) {
ret := &State{
BindHost: bindHost,
BindPort: bindPort,
Now: nowFunc,
BindHost: bindHost,
BindPort: bindPort,
Now: nowFunc,
BypassUID: make(map[[16]byte]struct{}),
}
ret.usedRandom = make(map[[32]byte]int64)
go ret.UsedRandomCleaner()
@ -99,9 +103,24 @@ func (sta *State) ParseConfig(conf string) (err error) {
return errors.New("Failed to decode AdminUID: " + err.Error())
}
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
}
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
// 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

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

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