From 0d3f8dd27f05f71bdc42abfbf59db2d44affe89b Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Mon, 21 Dec 2020 15:06:46 +0000 Subject: [PATCH] Allow DatabasePath to be empty if user info database is never used --- README.md | 14 +++--- internal/server/state.go | 11 +++-- internal/server/usermanager/usermanager.go | 1 + internal/server/usermanager/voidmanager.go | 31 +++++++++++++ .../server/usermanager/voidmanager_test.go | 43 +++++++++++++++++++ internal/server/userpanel.go | 3 ++ internal/test/integration_test.go | 43 ++++++------------- 7 files changed, 106 insertions(+), 40 deletions(-) create mode 100644 internal/server/usermanager/voidmanager.go create mode 100644 internal/server/usermanager/voidmanager_test.go diff --git a/README.md b/README.md index 9b50dfc..285575f 100644 --- a/README.md +++ b/README.md @@ -103,15 +103,13 @@ Example: `PrivateKey` is the static curve25519 Diffie-Hellman private key encoded in base64. -`AdminUID` is the UID of the admin user in base64. - `BypassUID` is a list of UIDs that are authorised without any bandwidth or credit limit restrictions -`DatabasePath` is the path to `userinfo.db`. If `userinfo.db` doesn't exist in this directory, Cloak will create one -automatically. **If Cloak is started as a Shadowsocks plugin and Shadowsocks is started with its working directory as -/ (e.g. starting ss-server with systemctl), you need to set this field as an absolute path to a desired folder. If you -leave it as default then Cloak will attempt to create userinfo.db under /, which it doesn't have the permission to do so -and will raise an error. See Issue #13.** +`AdminUID` is the UID of the admin user in base64. You can leave this empty if you only ever add users to `BypassUID`. + +`DatabasePath` is the path to `userinfo.db`, which is used to store user usage information and restrictions. Cloak will +create the file automatically if it doesn't exist. You can leave this empty if you only ever add users to `BypassUID`. +This field also has no effect if `AdminUID` isn't a valid UID or is empty. `KeepAlive` is the number of seconds to tell the OS to wait after no activity before sending TCP KeepAlive probes to the upstream proxy server. Zero or negative value disables it. Default is 0 (disabled). @@ -184,6 +182,8 @@ Run `ck-server -uid` and add the UID into the `BypassUID` field in `ckserver.jso ##### Users subject to bandwidth and credit controls +0. First make sure you have `AdminUID` generated and set in `ckserver.json`, along with a path to `userinfo.db` + in `DatabasePath` (Cloak will create this file for you if it didn't already exist). 1. On your client, run `ck-client -s -l -a -c ` to enter admin mode 2. Visit https://cbeuw.github.io/Cloak-panel (Note: this is a pure-js static site, there is no backend and all data diff --git a/internal/server/state.go b/internal/server/state.go index 576e326..03d9298 100644 --- a/internal/server/state.go +++ b/internal/server/state.go @@ -143,9 +143,14 @@ func InitState(preParse RawConfig, worldState common.WorldState) (sta *State, er err = errors.New("command & control mode not implemented") return } else { - manager, err := usermanager.MakeLocalManager(preParse.DatabasePath, worldState) - if err != nil { - return sta, err + var manager usermanager.UserManager + if len(preParse.AdminUID) == 0 || preParse.DatabasePath == "" { + manager = &usermanager.Voidmanager{} + } else { + manager, err = usermanager.MakeLocalManager(preParse.DatabasePath, worldState) + if err != nil { + return sta, err + } } sta.Panel = MakeUserPanel(manager) } diff --git a/internal/server/usermanager/usermanager.go b/internal/server/usermanager/usermanager.go index bfd5cc4..7bf84d5 100644 --- a/internal/server/usermanager/usermanager.go +++ b/internal/server/usermanager/usermanager.go @@ -40,6 +40,7 @@ const ( var ErrUserNotFound = errors.New("UID does not correspond to a user") var ErrSessionsCapReached = errors.New("Sessions cap has reached") +var ErrMangerIsVoid = errors.New("cannot perform operation with user manager as database path is not specified") var ErrNoUpCredit = errors.New("No upload credit left") var ErrNoDownCredit = errors.New("No download credit left") diff --git a/internal/server/usermanager/voidmanager.go b/internal/server/usermanager/voidmanager.go new file mode 100644 index 0000000..a20ab3c --- /dev/null +++ b/internal/server/usermanager/voidmanager.go @@ -0,0 +1,31 @@ +package usermanager + +type Voidmanager struct{} + +func (v *Voidmanager) AuthenticateUser(bytes []byte) (int64, int64, error) { + return 0, 0, ErrMangerIsVoid +} + +func (v *Voidmanager) AuthoriseNewSession(bytes []byte, info AuthorisationInfo) error { + return ErrMangerIsVoid +} + +func (v *Voidmanager) UploadStatus(updates []StatusUpdate) ([]StatusResponse, error) { + return nil, ErrMangerIsVoid +} + +func (v *Voidmanager) ListAllUsers() ([]UserInfo, error) { + return nil, ErrMangerIsVoid +} + +func (v *Voidmanager) GetUserInfo(UID []byte) (UserInfo, error) { + return UserInfo{}, ErrMangerIsVoid +} + +func (v *Voidmanager) WriteUserInfo(info UserInfo) error { + return ErrMangerIsVoid +} + +func (v *Voidmanager) DeleteUser(UID []byte) error { + return ErrMangerIsVoid +} diff --git a/internal/server/usermanager/voidmanager_test.go b/internal/server/usermanager/voidmanager_test.go new file mode 100644 index 0000000..55ab2b4 --- /dev/null +++ b/internal/server/usermanager/voidmanager_test.go @@ -0,0 +1,43 @@ +package usermanager + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +var v = &Voidmanager{} + +func Test_Voidmanager_AuthenticateUser(t *testing.T) { + _, _, err := v.AuthenticateUser([]byte{}) + assert.Equal(t, ErrMangerIsVoid, err) +} + +func Test_Voidmanager_AuthoriseNewSession(t *testing.T) { + err := v.AuthoriseNewSession([]byte{}, AuthorisationInfo{}) + assert.Equal(t, ErrMangerIsVoid, err) +} + +func Test_Voidmanager_DeleteUser(t *testing.T) { + err := v.DeleteUser([]byte{}) + assert.Equal(t, ErrMangerIsVoid, err) +} + +func Test_Voidmanager_GetUserInfo(t *testing.T) { + _, err := v.GetUserInfo([]byte{}) + assert.Equal(t, ErrMangerIsVoid, err) +} + +func Test_Voidmanager_ListAllUsers(t *testing.T) { + _, err := v.ListAllUsers() + assert.Equal(t, ErrMangerIsVoid, err) +} + +func Test_Voidmanager_UploadStatus(t *testing.T) { + _, err := v.UploadStatus([]StatusUpdate{}) + assert.Equal(t, ErrMangerIsVoid, err) +} + +func Test_Voidmanager_WriteUserInfo(t *testing.T) { + err := v.WriteUserInfo(UserInfo{}) + assert.Equal(t, ErrMangerIsVoid, err) +} diff --git a/internal/server/userpanel.go b/internal/server/userpanel.go index 453ff39..953179e 100644 --- a/internal/server/userpanel.go +++ b/internal/server/userpanel.go @@ -185,6 +185,9 @@ func (panel *userPanel) commitUpdate() error { panel.usageUpdateQueue = make(map[[16]byte]*usagePair) panel.usageUpdateQueueM.Unlock() + if len(statuses) == 0 { + return nil + } responses, err := panel.Manager.UploadStatus(statuses) if err != nil { return err diff --git a/internal/test/integration_test.go b/internal/test/integration_test.go index db58b39..5812ba2 100644 --- a/internal/test/integration_test.go +++ b/internal/test/integration_test.go @@ -12,10 +12,8 @@ import ( "github.com/cbeuw/connutil" "github.com/stretchr/testify/assert" "io" - "io/ioutil" "math/rand" "net" - "os" "sync" "testing" "time" @@ -24,8 +22,6 @@ import ( ) const numConns = 200 // -race option limits the number of goroutines to 8192 -const delayBeforeTestingConnClose = 500 * time.Millisecond -const connCloseRetries = 3 func serveTCPEcho(l net.Listener) { for { @@ -137,17 +133,15 @@ func generateClientConfigs(rawConfig client.RawConfig, state common.WorldState) return lcl, rmt, auth } -func basicServerState(ws common.WorldState, db *os.File) *server.State { +func basicServerState(ws common.WorldState) *server.State { var serverConfig = server.RawConfig{ - ProxyBook: map[string][]string{"shadowsocks": {"tcp", "fake.com:9999"}, "openvpn": {"udp", "fake.com:9999"}}, - BindAddr: []string{"fake.com:9999"}, - BypassUID: [][]byte{bypassUID[:]}, - RedirAddr: "fake.com:9999", - PrivateKey: privateKey, - AdminUID: nil, - DatabasePath: db.Name(), - KeepAlive: 15, - CncMode: false, + ProxyBook: map[string][]string{"shadowsocks": {"tcp", "fake.com:9999"}, "openvpn": {"udp", "fake.com:9999"}}, + BindAddr: []string{"fake.com:9999"}, + BypassUID: [][]byte{bypassUID[:]}, + RedirAddr: "fake.com:9999", + PrivateKey: privateKey, + KeepAlive: 15, + CncMode: false, } state, err := server.InitState(serverConfig, ws) if err != nil { @@ -258,13 +252,11 @@ func runEchoTest(t *testing.T, conns []net.Conn, maxMsgLen int) { } func TestUDP(t *testing.T) { - var tmpDB, _ = ioutil.TempFile("", "ck_user_info") - defer os.Remove(tmpDB.Name()) log.SetLevel(log.ErrorLevel) worldState := common.WorldOfTime(time.Unix(10, 0)) lcc, rcc, ai := generateClientConfigs(basicUDPConfig, worldState) - sta := basicServerState(worldState, tmpDB) + sta := basicServerState(worldState) proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta) if err != nil { @@ -319,9 +311,7 @@ func TestTCPSingleplex(t *testing.T) { log.SetLevel(log.ErrorLevel) worldState := common.WorldOfTime(time.Unix(10, 0)) lcc, rcc, ai := generateClientConfigs(singleplexTCPConfig, worldState) - var tmpDB, _ = ioutil.TempFile("", "ck_user_info") - defer os.Remove(tmpDB.Name()) - sta := basicServerState(worldState, tmpDB) + sta := basicServerState(worldState) proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta) if err != nil { t.Fatal(err) @@ -381,9 +371,7 @@ func TestTCPMultiplex(t *testing.T) { worldState := common.WorldOfTime(time.Unix(10, 0)) lcc, rcc, ai := generateClientConfigs(basicTCPConfig, worldState) - var tmpDB, _ = ioutil.TempFile("", "ck_user_info") - defer os.Remove(tmpDB.Name()) - sta := basicServerState(worldState, tmpDB) + sta := basicServerState(worldState) proxyToCkClientD, proxyFromCkServerL, netToCkServerD, redirFromCkServerL, err := establishSession(lcc, rcc, ai, sta) if err != nil { @@ -456,11 +444,8 @@ func TestClosingStreamsFromProxy(t *testing.T) { clientConfig := clientConfig clientConfigName := clientConfigName t.Run(clientConfigName, func(t *testing.T) { - var tmpDB, _ = ioutil.TempFile("", "ck_user_info") - defer os.Remove(tmpDB.Name()) - lcc, rcc, ai := generateClientConfigs(clientConfig, worldState) - sta := basicServerState(worldState, tmpDB) + sta := basicServerState(worldState) proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta) if err != nil { t.Fatal(err) @@ -519,12 +504,10 @@ func TestClosingStreamsFromProxy(t *testing.T) { } func BenchmarkThroughput(b *testing.B) { - var tmpDB, _ = ioutil.TempFile("", "ck_user_info") - defer os.Remove(tmpDB.Name()) log.SetLevel(log.ErrorLevel) worldState := common.WorldOfTime(time.Unix(10, 0)) lcc, rcc, ai := generateClientConfigs(basicTCPConfig, worldState) - sta := basicServerState(worldState, tmpDB) + sta := basicServerState(worldState) const bufSize = 16 * 1024 encryptionMethods := map[string]byte{