Skip to content

Commit

Permalink
Support TLS transfer and user authorization between server and client.
Browse files Browse the repository at this point in the history
  • Loading branch information
mstmdev committed Dec 5, 2021
1 parent a28d49a commit 9a90528
Show file tree
Hide file tree
Showing 29 changed files with 522 additions and 198 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ gofs -src=./src -target=./target -daemon -daemon_pid

Start a file server for src path and target path.
The file server is use HTTPS default, set the `tls_cert_file` and `tls_key_file` flags to customize the cert file and key file.
You can disable the HTTPS by set the `server_tls` flag to `false` if you don't need it.
You should set `rand_server_user` flag to auto generate some random users or set `server_users` flag to custom file server users for security reasons.
The server users will output to log if you set the `rand_server_user` flag greater than zero.
You can disable the HTTPS by set the `tls` flag to `false` if you don't need it.
You should set `rand_user_count` flag to auto generate some random users or set `users` flag to custom server users for security reasons.
The server users will output to log if you set the `rand_user_count` flag greater than zero.

```bash
gofs -src=./src -target=./target -server -rand_server_user=3
gofs -src=./src -target=./target -server -rand_user_count=3
```

Start a remote disk server as a remote file source.

```bash
gofs -src="rs://127.0.0.1:9016?mode=server&local_sync_disabled=true&path=./src&fs_server=https://127.0.0.1" -target=./target -rand_server_user=3
gofs -src="rs://127.0.0.1:9016?mode=server&local_sync_disabled=true&path=./src&fs_server=https://127.0.0.1" -target=./target -rand_user_count=3
```

Start a remote disk client to sync files from remote disk server.
Expand Down
28 changes: 28 additions & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package auth

import (
"fmt"
"github.com/no-src/gofs/contract"
)

// ParseAuthCommandData parse auth command request data
func ParseAuthCommandData(data []byte) (user *HashUser, err error) {
authCmdLen := len(contract.AuthCommand)
length := authCmdLen + 16 + 16
if len(data) != length {
return nil, fmt.Errorf("auth command data is invalid => [%s]", string(data))
}
user = &HashUser{
UserNameHash: string(data[authCmdLen : authCmdLen+16]),
PasswordHash: string(data[authCmdLen+16 : authCmdLen+32]),
}
return user, nil
}

// GenerateAuthCommandData generate auth command request data
func GenerateAuthCommandData(userNameHash, passwordHash string) []byte {
authData := contract.AuthCommand
authData = append(authData, []byte(userNameHash)...)
authData = append(authData, []byte(passwordHash)...)
return authData
}
24 changes: 24 additions & 0 deletions auth/hash_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package auth

// HashUser store the hash info of User
type HashUser struct {
// UserNameHash a 16 bytes hash of username
UserNameHash string
// PasswordHash a 16 bytes hash of password
PasswordHash string
}

// ToHashUserList convert User list to HashUser list
func ToHashUserList(users []*User) (hashUsers []*HashUser, err error) {
if len(users) == 0 {
return hashUsers, nil
}
for _, user := range users {
hashUser, err := user.ToHashUser()
if err != nil {
return nil, err
}
hashUsers = append(hashUsers, hashUser)
}
return hashUsers, nil
}
24 changes: 24 additions & 0 deletions auth/session_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package auth

import "encoding/gob"

type SessionUser struct {
UserId int
UserName string
Password string
}

func MapperToSessionUser(user *User) *SessionUser {
if user == nil {
return nil
}
return &SessionUser{
UserId: user.UserId(),
UserName: user.UserName(),
Password: user.Password(),
}
}

func init() {
gob.Register(SessionUser{})
}
48 changes: 25 additions & 23 deletions contract/user.go → auth/user.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package contract
package auth

import (
"encoding/gob"
"errors"
"fmt"
"github.com/no-src/gofs/util"
"strings"
)

// User a login user info
type User struct {
userId int
userName string
Expand All @@ -30,6 +30,23 @@ func (user *User) Password() string {
return user.password
}

// ToHashUser convert User to HashUser
func (user *User) ToHashUser() (hashUser *HashUser, err error) {
hashUser = &HashUser{}
hashUser.UserNameHash, err = util.MD5(user.userName)
if err != nil {
return nil, err
}
hashUser.PasswordHash, err = util.MD5(user.password)
if err != nil {
return nil, err
}
hashUser.UserNameHash = hashUser.UserNameHash[:16]
hashUser.PasswordHash = hashUser.PasswordHash[:16]
return hashUser, err
}

// NewUser create a new user
func NewUser(userId int, userName string, password string) (*User, error) {
if userId <= 0 {
return nil, errors.New("userId must greater than zero")
Expand All @@ -46,6 +63,7 @@ func NewUser(userId int, userName string, password string) (*User, error) {
return user, nil
}

// isValidUser check username and password is valid or not
func isValidUser(user User) error {
if len(user.UserName()) == 0 {
return errors.New("userName can't be empty")
Expand Down Expand Up @@ -90,6 +108,10 @@ func ParseUsers(userStr string) (users []*User, err error) {
return users, nil
}

// RandomUser generate some user with random username and password
// count is user count you want
// userLen is the length of random username, max length is 20
// pwdLen is the length of random password, max length is 20
func RandomUser(count, userLen, pwdLen int) []*User {
var users []*User
for i := 1; i <= count; i++ {
Expand All @@ -103,6 +125,7 @@ func RandomUser(count, userLen, pwdLen int) []*User {
return users
}

// ParseStringUsers parse user list to user string
func ParseStringUsers(users []*User) (userStr string, err error) {
if len(users) == 0 {
return userStr, nil
Expand All @@ -121,24 +144,3 @@ func ParseStringUsers(users []*User) (userStr string, err error) {
userStr = strings.Join(userResultList, ",")
return userStr, nil
}

type SessionUser struct {
UserId int
UserName string
Password string
}

func MapperToSessionUser(user *User) *SessionUser {
if user == nil {
return nil
}
return &SessionUser{
UserId: user.UserId(),
UserName: user.UserName(),
Password: user.Password(),
}
}

func init() {
gob.Register(SessionUser{})
}
80 changes: 49 additions & 31 deletions cmd/gofs/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package main
import (
"flag"
"fmt"
"github.com/no-src/gofs/contract"
"github.com/no-src/gofs/auth"
"github.com/no-src/gofs/core"
"github.com/no-src/gofs/daemon"
"github.com/no-src/gofs/server"
"github.com/no-src/gofs/util"
"github.com/no-src/log"
"os"
"time"
Expand All @@ -16,7 +17,7 @@ var (
sourceVFS core.VFS
targetVFS core.VFS
logLevel int
fileLogger bool
enableFileLogger bool
logDir string
logFlush bool
logFlushInterval time.Duration
Expand All @@ -32,14 +33,14 @@ var (
daemonMonitorDelay time.Duration
killPPid bool
isSubprocess bool
fileServer bool
enableFileServer bool
fileServerAddr string
fileServerTLS bool
certFile string
keyFile string
fileServerUsers string
enableTLS bool
tlsCertFile string
tlsKeyFile string
fileServerTemplate string
randomServerUser int
users string
randomUserCount int
randomUserNameLen int
randomPasswordLen int
)
Expand All @@ -51,16 +52,16 @@ func parseFlags() {
os.Args = append(os.Args, "-h")
}

flag.BoolVar(&printVersion, "v", false, "print version info")
core.VFSVar(&sourceVFS, "src", core.NewEmptyVFS(), "source path by monitor")
core.VFSVar(&targetVFS, "target", core.NewEmptyVFS(), "target path to backup")
flag.BoolVar(&printVersion, "v", false, "print the version info")
core.VFSVar(&sourceVFS, "src", core.NewEmptyVFS(), "the source path by monitor")
core.VFSVar(&targetVFS, "target", core.NewEmptyVFS(), "the target path to backup")
flag.IntVar(&logLevel, "log_level", int(log.InfoLevel), "set log level, default is INFO. DEBUG=0 INFO=1 WARN=2 ERROR=3")
flag.BoolVar(&fileLogger, "log_file", false, "enable file logger")
flag.StringVar(&logDir, "log_dir", "./logs/", "set log file's dir")
flag.BoolVar(&enableFileLogger, "log_file", false, "enable the file logger")
flag.StringVar(&logDir, "log_dir", "./logs/", "set the directory of log file")
flag.BoolVar(&logFlush, "log_flush", false, "enable auto flush log with interval")
flag.DurationVar(&logFlushInterval, "log_flush_interval", time.Second*3, "set log flush interval duration, you need to enable log_flush first")
flag.IntVar(&retryCount, "retry_count", 15, "if execute failed, then retry to work retry_count times")
flag.DurationVar(&retryWait, "retry_wait", time.Second*5, "if retry to work, wait retry_wait time then do")
flag.DurationVar(&logFlushInterval, "log_flush_interval", time.Second*3, "set the log flush interval duration, you need to enable -log_flush first")
flag.IntVar(&retryCount, "retry_count", 15, "if execute failed, then retry to work -retry_count times")
flag.DurationVar(&retryWait, "retry_wait", time.Second*5, "if retry to work, wait -retry_wait time then do")
flag.BoolVar(&retryAsync, "retry_async", false, "execute retry asynchronously")
flag.IntVar(&bufSize, "buf_size", 1024*1024, "read and write buffer byte size")
flag.BoolVar(&syncOnce, "sync_once", false, "sync src directory to target directory once")
Expand All @@ -70,42 +71,59 @@ func parseFlags() {
flag.DurationVar(&daemonMonitorDelay, "daemon_monitor_delay", time.Second*3, "daemon monitor work interval, wait to check subprocess state")
flag.BoolVar(&killPPid, "kill_ppid", false, "try to kill the parent process when it's running")
flag.BoolVar(&isSubprocess, daemon.SubprocessTag, false, "tag current process is subprocess")
flag.BoolVar(&fileServer, "server", false, "start a file server to browse source directory and target directory")
flag.BoolVar(&enableFileServer, "server", false, "start a file server to browse source directory and target directory")
flag.StringVar(&fileServerAddr, "server_addr", server.DefaultAddrHttps, "a file server binding address")
flag.BoolVar(&fileServerTLS, "server_tls", true, fmt.Sprintf("enable https for file server, if disable it, server_addr is \"%s\" default", server.DefaultAddrHttp))
flag.StringVar(&fileServerUsers, "server_users", "", "the file server accounts, the file server allows anonymous access if there is no effective account, format like this, user1|password1,user2|password2")
flag.StringVar(&fileServerTemplate, "server_tmpl", "./template/*.html", "the file server template pattern")
flag.IntVar(&randomServerUser, "rand_server_user", 0, "the number of random server accounts, if it is greater than zero, random generate some accounts for server_users")
flag.StringVar(&users, "users", "", "the server accounts, the server allows anonymous access if there is no effective account, format like this, user1|password1,user2|password2")
flag.IntVar(&randomUserCount, "rand_user_count", 0, "the number of random server accounts, if it is greater than zero, random generate some accounts for -users")
flag.IntVar(&randomUserNameLen, "rand_user_len", 6, "the length of the random user's username")
flag.IntVar(&randomPasswordLen, "rand_pwd_len", 10, "the length of the random user's password")
flag.StringVar(&certFile, "tls_cert_file", "gofs.pem", "cert file for https connections")
flag.StringVar(&keyFile, "tls_key_file", "gofs.key", "key file for https connections")
flag.BoolVar(&enableTLS, "tls", true, fmt.Sprintf("enable the tls connections, if disable it, server_addr is \"%s\" default", server.DefaultAddrHttp))
flag.StringVar(&tlsCertFile, "tls_cert_file", "gofs.pem", "cert file for tls connections")
flag.StringVar(&tlsKeyFile, "tls_key_file", "gofs.key", "key file for tls connections")
flag.Parse()
}

// initFlags init flags default value
func initFlags() error {
if !fileServerTLS && fileServerAddr == server.DefaultAddrHttps {
if !enableTLS && fileServerAddr == server.DefaultAddrHttps {
fileServerAddr = server.DefaultAddrHttp
}

// if start a remote server monitor, auto enable file server
if sourceVFS.Server() {
fileServer = true
enableFileServer = true
}

if randomServerUser > 0 && fileServer {
serverUsers := contract.RandomUser(randomServerUser, randomUserNameLen, randomPasswordLen)
randUserStr, err := contract.ParseStringUsers(serverUsers)
if randomUserCount > 0 && enableFileServer {
userList := auth.RandomUser(randomUserCount, randomUserNameLen, randomPasswordLen)
randUserStr, err := auth.ParseStringUsers(userList)
if err != nil {
return err
} else {
if len(fileServerUsers) > 0 {
fileServerUsers = fmt.Sprintf("%s,%s", fileServerUsers, randUserStr)
if len(users) > 0 {
users = fmt.Sprintf("%s,%s", users, randUserStr)
} else {
fileServerUsers = randUserStr
users = randUserStr
}
log.Info("generate random file server users success => [%s]", fileServerUsers)
log.Info("generate random users success => [%s]", users)
}
}

if enableTLS && (sourceVFS.Server() || enableFileServer) {
exist, err := util.FileExist(tlsCertFile)
if err != nil {
return err
}
if !exist {
return fmt.Errorf("cert file is not found for tls => %s", tlsCertFile)
}
exist, err = util.FileExist(tlsKeyFile)
if err != nil {
return err
}
if !exist {
return fmt.Errorf("key file is not found for tls => %s", tlsKeyFile)
}
}

Expand Down
Loading

0 comments on commit 9a90528

Please sign in to comment.