Allow DatabasePath to be empty if user info database is never used

This commit is contained in:
Andy Wang 2020-12-21 15:06:46 +00:00
parent 298e6249e6
commit 0d3f8dd27f
No known key found for this signature in database
GPG Key ID: 181B49F9F38F3374
7 changed files with 106 additions and 40 deletions

View File

@ -103,15 +103,13 @@ Example:
`PrivateKey` is the static curve25519 Diffie-Hellman private key encoded in base64. `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 `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 `AdminUID` is the UID of the admin user in base64. You can leave this empty if you only ever add users to `BypassUID`.
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 `DatabasePath` is the path to `userinfo.db`, which is used to store user usage information and restrictions. Cloak will
leave it as default then Cloak will attempt to create userinfo.db under /, which it doesn't have the permission to do so create the file automatically if it doesn't exist. You can leave this empty if you only ever add users to `BypassUID`.
and will raise an error. See Issue #13.** 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 `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). 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 ##### 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 <IP of the server> -l <A local port> -a <AdminUID> -c <path-to-ckclient.json>` to 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 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 2. Visit https://cbeuw.github.io/Cloak-panel (Note: this is a pure-js static site, there is no backend and all data

View File

@ -143,9 +143,14 @@ func InitState(preParse RawConfig, worldState common.WorldState) (sta *State, er
err = errors.New("command & control mode not implemented") err = errors.New("command & control mode not implemented")
return return
} else { } else {
manager, err := usermanager.MakeLocalManager(preParse.DatabasePath, worldState) var manager usermanager.UserManager
if err != nil { if len(preParse.AdminUID) == 0 || preParse.DatabasePath == "" {
return sta, err manager = &usermanager.Voidmanager{}
} else {
manager, err = usermanager.MakeLocalManager(preParse.DatabasePath, worldState)
if err != nil {
return sta, err
}
} }
sta.Panel = MakeUserPanel(manager) sta.Panel = MakeUserPanel(manager)
} }

View File

@ -40,6 +40,7 @@ const (
var ErrUserNotFound = errors.New("UID does not correspond to a user") var ErrUserNotFound = errors.New("UID does not correspond to a user")
var ErrSessionsCapReached = errors.New("Sessions cap has reached") 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 ErrNoUpCredit = errors.New("No upload credit left")
var ErrNoDownCredit = errors.New("No download credit left") var ErrNoDownCredit = errors.New("No download credit left")

View File

@ -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
}

View File

@ -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)
}

View File

@ -185,6 +185,9 @@ func (panel *userPanel) commitUpdate() error {
panel.usageUpdateQueue = make(map[[16]byte]*usagePair) panel.usageUpdateQueue = make(map[[16]byte]*usagePair)
panel.usageUpdateQueueM.Unlock() panel.usageUpdateQueueM.Unlock()
if len(statuses) == 0 {
return nil
}
responses, err := panel.Manager.UploadStatus(statuses) responses, err := panel.Manager.UploadStatus(statuses)
if err != nil { if err != nil {
return err return err

View File

@ -12,10 +12,8 @@ import (
"github.com/cbeuw/connutil" "github.com/cbeuw/connutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"io" "io"
"io/ioutil"
"math/rand" "math/rand"
"net" "net"
"os"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -24,8 +22,6 @@ import (
) )
const numConns = 200 // -race option limits the number of goroutines to 8192 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) { func serveTCPEcho(l net.Listener) {
for { for {
@ -137,17 +133,15 @@ func generateClientConfigs(rawConfig client.RawConfig, state common.WorldState)
return lcl, rmt, auth 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{ var serverConfig = server.RawConfig{
ProxyBook: map[string][]string{"shadowsocks": {"tcp", "fake.com:9999"}, "openvpn": {"udp", "fake.com:9999"}}, ProxyBook: map[string][]string{"shadowsocks": {"tcp", "fake.com:9999"}, "openvpn": {"udp", "fake.com:9999"}},
BindAddr: []string{"fake.com:9999"}, BindAddr: []string{"fake.com:9999"},
BypassUID: [][]byte{bypassUID[:]}, BypassUID: [][]byte{bypassUID[:]},
RedirAddr: "fake.com:9999", RedirAddr: "fake.com:9999",
PrivateKey: privateKey, PrivateKey: privateKey,
AdminUID: nil, KeepAlive: 15,
DatabasePath: db.Name(), CncMode: false,
KeepAlive: 15,
CncMode: false,
} }
state, err := server.InitState(serverConfig, ws) state, err := server.InitState(serverConfig, ws)
if err != nil { if err != nil {
@ -258,13 +252,11 @@ func runEchoTest(t *testing.T, conns []net.Conn, maxMsgLen int) {
} }
func TestUDP(t *testing.T) { func TestUDP(t *testing.T) {
var tmpDB, _ = ioutil.TempFile("", "ck_user_info")
defer os.Remove(tmpDB.Name())
log.SetLevel(log.ErrorLevel) log.SetLevel(log.ErrorLevel)
worldState := common.WorldOfTime(time.Unix(10, 0)) worldState := common.WorldOfTime(time.Unix(10, 0))
lcc, rcc, ai := generateClientConfigs(basicUDPConfig, worldState) lcc, rcc, ai := generateClientConfigs(basicUDPConfig, worldState)
sta := basicServerState(worldState, tmpDB) sta := basicServerState(worldState)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta) proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta)
if err != nil { if err != nil {
@ -319,9 +311,7 @@ func TestTCPSingleplex(t *testing.T) {
log.SetLevel(log.ErrorLevel) log.SetLevel(log.ErrorLevel)
worldState := common.WorldOfTime(time.Unix(10, 0)) worldState := common.WorldOfTime(time.Unix(10, 0))
lcc, rcc, ai := generateClientConfigs(singleplexTCPConfig, worldState) lcc, rcc, ai := generateClientConfigs(singleplexTCPConfig, worldState)
var tmpDB, _ = ioutil.TempFile("", "ck_user_info") sta := basicServerState(worldState)
defer os.Remove(tmpDB.Name())
sta := basicServerState(worldState, tmpDB)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta) proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -381,9 +371,7 @@ func TestTCPMultiplex(t *testing.T) {
worldState := common.WorldOfTime(time.Unix(10, 0)) worldState := common.WorldOfTime(time.Unix(10, 0))
lcc, rcc, ai := generateClientConfigs(basicTCPConfig, worldState) lcc, rcc, ai := generateClientConfigs(basicTCPConfig, worldState)
var tmpDB, _ = ioutil.TempFile("", "ck_user_info") sta := basicServerState(worldState)
defer os.Remove(tmpDB.Name())
sta := basicServerState(worldState, tmpDB)
proxyToCkClientD, proxyFromCkServerL, netToCkServerD, redirFromCkServerL, err := establishSession(lcc, rcc, ai, sta) proxyToCkClientD, proxyFromCkServerL, netToCkServerD, redirFromCkServerL, err := establishSession(lcc, rcc, ai, sta)
if err != nil { if err != nil {
@ -456,11 +444,8 @@ func TestClosingStreamsFromProxy(t *testing.T) {
clientConfig := clientConfig clientConfig := clientConfig
clientConfigName := clientConfigName clientConfigName := clientConfigName
t.Run(clientConfigName, func(t *testing.T) { 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) lcc, rcc, ai := generateClientConfigs(clientConfig, worldState)
sta := basicServerState(worldState, tmpDB) sta := basicServerState(worldState)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta) proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -519,12 +504,10 @@ func TestClosingStreamsFromProxy(t *testing.T) {
} }
func BenchmarkThroughput(b *testing.B) { func BenchmarkThroughput(b *testing.B) {
var tmpDB, _ = ioutil.TempFile("", "ck_user_info")
defer os.Remove(tmpDB.Name())
log.SetLevel(log.ErrorLevel) log.SetLevel(log.ErrorLevel)
worldState := common.WorldOfTime(time.Unix(10, 0)) worldState := common.WorldOfTime(time.Unix(10, 0))
lcc, rcc, ai := generateClientConfigs(basicTCPConfig, worldState) lcc, rcc, ai := generateClientConfigs(basicTCPConfig, worldState)
sta := basicServerState(worldState, tmpDB) sta := basicServerState(worldState)
const bufSize = 16 * 1024 const bufSize = 16 * 1024
encryptionMethods := map[string]byte{ encryptionMethods := map[string]byte{