diff --git a/.gck.sh.swp b/.gck.sh.swp new file mode 100644 index 00000000..a59be63b Binary files /dev/null and b/.gck.sh.swp differ diff --git a/auth/entity/client/client.go b/auth/entity/client/client.go new file mode 100644 index 00000000..c5e980fb --- /dev/null +++ b/auth/entity/client/client.go @@ -0,0 +1,27 @@ +package client + +import "encoding/json" + +type Client struct { + Email string `json:"email,omitempty"` + Code string `json:"code,omitempty"` + Key string `json:"key,omitempty"` + Active bool `json:"active,omitempty"` + LastSessionKey string `json:"lsk,omitempty"` +} + +type Clients = []Client + +func New() *Client { + return &Client{} +} + +func (client *Client) ToJson() ([]byte, error) { + return json.Marshal(client) +} + +func FromJson(data []byte) (client *Client, err error) { + client = New() + err = json.Unmarshal(data, client) + return +} diff --git a/auth/entity/session/session.go b/auth/entity/session/session.go new file mode 100644 index 00000000..887c1f9e --- /dev/null +++ b/auth/entity/session/session.go @@ -0,0 +1,43 @@ +package connection + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/gwuhaolin/livego/auth/utils" +) + +type Session struct { + _Key string + TimeStamp string `json:"time,omitempty"` + Code string `json:"code,omitempty"` + IpAddress string `json:"ip,omitempty"` + Active bool `json:"active,omitempty"` +} + +type Connections = []Session + +func New(code, IpAddress string) *Session { + return &Session{ + _Key: fmt.Sprintf("session-[%s-%s]", utils.RandomNumberString(4), utils.RandomNumberString(5)), + TimeStamp: time.Now().UTC().Format(time.RFC3339Nano), + Code: code, + IpAddress: IpAddress, + Active: true, + } +} + +func (session *Session) Key() string { + return session._Key +} + +func (client *Session) ToJson() ([]byte, error) { + return json.Marshal(client) +} + +func FromJson(data []byte) (conn *Session, err error) { + conn = &Session{} + err = json.Unmarshal(data, conn) + return +} diff --git a/auth/security.go b/auth/security.go new file mode 100644 index 00000000..5265c82f --- /dev/null +++ b/auth/security.go @@ -0,0 +1,141 @@ +package auth + +import ( + "fmt" + "net/url" + "strings" + + "github.com/go-redis/redis/v7" + log "github.com/sirupsen/logrus" + + cli "github.com/gwuhaolin/livego/auth/entity/client" + sess "github.com/gwuhaolin/livego/auth/entity/session" + "github.com/gwuhaolin/livego/configure" +) + +var Auth *Security + +type Security struct { + redisCli *redis.Client + Allow func(string, string) (bool, error) +} + +func init() { + + protected := configure.Config.GetBool("is_protected") + log.Infof("security.init(): %v", protected) + + // Identify if Authentication is enabled: + Auth = New(protected) + // ===================================================== + + log.Infof("redis_addr: %s | redis_pwd: %s | redis_db: %d", + configure.Config.GetString("redis_addr"), + configure.Config.GetString("redis_pwd"), + configure.Config.GetInt("redis_db")) + if len(configure.Config.GetString("redis_addr")) != 0 { + + Auth.redisCli = redis.NewClient(&redis.Options{ + Addr: configure.Config.GetString("redis_addr"), + Password: configure.Config.GetString("redis_pwd"), + DB: configure.Config.GetInt("redis_db"), + }) + _, err := Auth.redisCli.Ping().Result() + if err != nil { + log.Panic("Redis: ", err) + } + log.Info("Redis connected") + + } + +} + +func New(protected bool) (security *Security) { + + security = &Security{} + if protected { + security.Allow = security.allow + } else { + security.Allow = func(string, string) (bool, error) { + log.Debugf("security.Allow(): default true") + return true, nil // Default true + } + } + return + +} + +func (security *Security) allow(ipAddr, value string) (allowed bool, err error) { + + allowed = false + if url, err := url.Parse(value); err == nil { + user := url.Query().Get("u") + key := url.Query().Get("k") + log.Debugf("security.Allow(): [%s]-[%s]", user, len(key) > 0) + + if client, err := security.findClient(user); err == nil { + if client != nil && client.Active { + + lsk := client.LastSessionKey + + session, err := security.findSession(lsk) + if err == redis.Nil || session == nil { + session = sess.New(user, ipAddr) + if err = security.saveSession(session); err == nil { + client.LastSessionKey = session.Key() + if err = security.saveClient(client); err == nil { + log.Debugf("security.Allow(): %+v", session) + allowed = true + } + } + } else { + // Validate the IP Address from source to identify if it is another session to the same user: + allowed = (strings.Split(ipAddr, ":")[0] == strings.Split(session.IpAddress, ":")[0]) + } + } + } + } + if err != nil { + log.Errorf("security.Allow(): %s", err.Error()) + } + return + +} + +func (s *Security) findClient(code string) (*cli.Client, error) { + + if value, err := s.redisCli.Get(fmt.Sprintf("client-%s", code)).Result(); err == nil { + return cli.FromJson([]byte(value)) + } else { + return nil, err + } + +} + +func (s *Security) saveClient(client *cli.Client) (err error) { + + content, _ := client.ToJson() + cmd_val, err := s.redisCli.Set(fmt.Sprintf("client-%s", client.Code), string(content), 0).Result() + log.Debugf("security.saveClient(): %s", cmd_val) + return + +} + +func (s *Security) findSession(key string) (*sess.Session, error) { + + if value, err := s.redisCli.Get(key).Result(); err == nil { + return sess.FromJson([]byte(value)) + } else { + return nil, err + } + +} + +func (s *Security) saveSession(session *sess.Session) (err error) { + + content, _ := session.ToJson() + cmd_val, err := s.redisCli.Set(session.Key(), string(content), 0).Result() + log.Debugf("security.saveSession(): %s", cmd_val) + return + +} diff --git a/auth/utils/utils.go b/auth/utils/utils.go new file mode 100644 index 00000000..50c2d302 --- /dev/null +++ b/auth/utils/utils.go @@ -0,0 +1,22 @@ +package utils + +import ( + "math/rand" + "strconv" + "strings" + "time" +) + +func init() { + rand.Seed(time.Now().Unix()) +} + +func RandomNumberString(length int) string { + + var result strings.Builder + for n := 0; n < length; n++ { + result.WriteString(strconv.Itoa(rand.Intn(9))) + } + return result.String() + +} diff --git a/configure/liveconfig.go b/configure/liveconfig.go index bc2117e4..ab432f81 100644 --- a/configure/liveconfig.go +++ b/configure/liveconfig.go @@ -41,6 +41,7 @@ type JWT struct { } type ServerCfg struct { Level string `mapstructure:"level"` + IsProtected bool `mapstructure:"is_protected"` ConfigFile string `mapstructure:"config_file"` FLVArchive bool `mapstructure:"flv_archive"` FLVDir string `mapstructure:"flv_dir"` @@ -52,6 +53,7 @@ type ServerCfg struct { APIAddr string `mapstructure:"api_addr"` RedisAddr string `mapstructure:"redis_addr"` RedisPwd string `mapstructure:"redis_pwd"` + RedisDb int `mapstructure:"redis_db"` ReadTimeout int `mapstructure:"read_timeout"` WriteTimeout int `mapstructure:"write_timeout"` EnableTLSVerify bool `mapstructure:"enable_tls_verify"` @@ -184,3 +186,9 @@ func GetStaticPushUrlList(appname string) ([]string, bool) { } return nil, false } + +func IsProtected() bool { + c := ServerCfg{} + Config.Unmarshal(&c) + return c.IsProtected +} diff --git a/gck.sh b/gck.sh new file mode 100755 index 00000000..e74aac03 --- /dev/null +++ b/gck.sh @@ -0,0 +1,12 @@ +#/bin/bash + +if [ $# -ne 1 ] +then + CHANNEL="${RANDOM}-${RANDOM}-${RANDOM}" +else + CHANNEL="${1}" +fi + +echo ${CHANNEL} +curl "http://localhost:8090/control/get?room=${CHANNEL}" +echo diff --git a/livego.yaml b/livego.yaml index 6d199016..945b5c9c 100644 --- a/livego.yaml +++ b/livego.yaml @@ -18,6 +18,9 @@ # # API Options # api_addr: ":8090" +is_protected: true +redis_addr: "localhost:6379" +# redis_db: 10 server: - appname: live live: true diff --git a/protocol/rtmp/core/conn_server.go b/protocol/rtmp/core/conn_server.go index 99419498..4553c9c7 100755 --- a/protocol/rtmp/core/conn_server.go +++ b/protocol/rtmp/core/conn_server.go @@ -350,6 +350,7 @@ func (connServer *ConnServer) GetInfo() (app string, name string, url string) { app = connServer.ConnInfo.App name = connServer.PublishInfo.Name url = connServer.ConnInfo.TcUrl + "/" + connServer.PublishInfo.Name + log.Debugf("connServer.GetInfo: [%s]-[%s]-[%s]", app, name, url) return } diff --git a/protocol/rtmp/rtmp.go b/protocol/rtmp/rtmp.go index 6693f4cb..62239b65 100755 --- a/protocol/rtmp/rtmp.go +++ b/protocol/rtmp/rtmp.go @@ -10,6 +10,7 @@ import ( "github.com/gwuhaolin/livego/utils/uid" + "github.com/gwuhaolin/livego/auth" "github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/configure" "github.com/gwuhaolin/livego/container/flv" @@ -91,8 +92,8 @@ func (s *Server) Serve(listener net.Listener) (err error) { return } conn := core.NewConn(netconn, 4*1024) - log.Debug("new client, connect remote: ", conn.RemoteAddr().String(), - "local:", conn.LocalAddr().String()) + log.Debugf("new client, connect remote: %s local: %s", conn.RemoteAddr().String(), + conn.LocalAddr().String()) go s.handleConn(conn) } } @@ -111,7 +112,16 @@ func (s *Server) handleConn(conn *core.Conn) error { return err } - appname, name, _ := connServer.GetInfo() + appname, name, uri := connServer.GetInfo() + + // If is not a publisher we need to validate the credential here...... + if !connServer.IsPublisher() && configure.IsProtected() { + if allow, err := auth.Auth.Allow(conn.RemoteAddr().String(), uri); err != nil || !allow { + conn.Close() + log.Error("handleConn auth err: ", allow, err) + return err + } + } if ret := configure.CheckAppName(appname); !ret { err := fmt.Errorf("application name=%s is not configured", appname)