mirror of https://github.com/cbeuw/Cloak
Add user bypass feature
This commit is contained in:
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()
|
||||
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,10 +40,12 @@ func (u *ActiveUser) GetSession(sessionID uint32, obfuscator *mux.Obfuscator, un
|
|||
if sesh = u.sessions[sessionID]; sesh != nil {
|
||||
return sesh, true, nil
|
||||
} else {
|
||||
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
|
||||
return sesh, false, nil
|
||||
|
|
@ -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,6 +32,8 @@ type State struct {
|
|||
|
||||
Now func() time.Time
|
||||
AdminUID []byte
|
||||
|
||||
BypassUID map[[16]byte]struct{}
|
||||
staticPv crypto.PrivateKey
|
||||
|
||||
RedirAddr string
|
||||
|
|
@ -47,6 +50,7 @@ func InitState(bindHost, bindPort string, nowFunc func() time.Time) (*State, err
|
|||
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…
Reference in New Issue