mirror of
https://github.com/fhmq/hmq.git
synced 2026-04-24 10:38:34 +00:00
@@ -9,7 +9,7 @@ const (
|
||||
)
|
||||
|
||||
type Auth interface {
|
||||
CheckACL(action, username, topic string) bool
|
||||
CheckACL(action, clientID, username, ip, topic string) bool
|
||||
CheckConnect(clientID, username, password string) bool
|
||||
}
|
||||
|
||||
|
||||
54
plugins/auth/authfile/Readme.md
Normal file
54
plugins/auth/authfile/Readme.md
Normal file
@@ -0,0 +1,54 @@
|
||||
## ACL Configure
|
||||
```
|
||||
Attention: Acl Type Change, change `pub =1, sub=2` to `sub =1, pub=2`
|
||||
```
|
||||
#### The ACL rules define:
|
||||
~~~
|
||||
Allow | type | value | pubsub | Topics
|
||||
~~~
|
||||
#### ACL Config
|
||||
~~~
|
||||
## type clientid , username, ipaddr
|
||||
##sub 1 , pub 2, pubsub 3
|
||||
## %c is clientid , %u is username
|
||||
allow ip 127.0.0.1 2 $SYS/#
|
||||
allow clientid 0001 3 #
|
||||
allow username admin 3 #
|
||||
allow username joy 3 /test,hello/world
|
||||
allow clientid * 1 toCloud/%c
|
||||
allow username * 1 toCloud/%u
|
||||
deny clientid * 3 #
|
||||
~~~
|
||||
|
||||
~~~
|
||||
#allow local sub $SYS topic
|
||||
allow ip 127.0.0.1 1 $SYS/#
|
||||
~~~
|
||||
~~~
|
||||
#allow client who's id with 0001 or username with admin pub sub all topic
|
||||
allow clientid 0001 3 #
|
||||
allow username admin 3 #
|
||||
~~~
|
||||
~~~
|
||||
#allow client with the username joy can pub sub topic '/test' and 'hello/world'
|
||||
allow username joy 3 /test,hello/world
|
||||
~~~
|
||||
~~~
|
||||
#allow all client pub the topic toCloud/{clientid/username}
|
||||
allow clientid * 2 toCloud/%c
|
||||
allow username * 2 toCloud/%u
|
||||
~~~
|
||||
~~~
|
||||
#deny all client pub sub all topic
|
||||
deny clientid * 3 #
|
||||
~~~
|
||||
Client match acl rule one by one
|
||||
~~~
|
||||
--------- --------- ---------
|
||||
Client -> | Rule1 | --nomatch--> | Rule2 | --nomatch--> | Rule3 | -->
|
||||
--------- --------- ---------
|
||||
| | |
|
||||
match match match
|
||||
\|/ \|/ \|/
|
||||
allow | deny allow | deny allow | deny
|
||||
~~~
|
||||
12
plugins/auth/authfile/acl.conf
Normal file
12
plugins/auth/authfile/acl.conf
Normal file
@@ -0,0 +1,12 @@
|
||||
## sub 1 , pub 2, pubsub 3
|
||||
## %c is clientid , %s is username
|
||||
##auth type value pub/sub topic
|
||||
allow ip 127.0.0.1 2 $SYS/#
|
||||
allow clientid 0001 3 #
|
||||
deny username admin 3 #
|
||||
allow username joy 3 /test,hello/world
|
||||
allow clientid * 1 toCloud/%c
|
||||
allow username * 1 toCloud/%u
|
||||
allow clientid * 2 toDevice/%c
|
||||
allow username * 2 toDevice/%u
|
||||
deny clientid * 3 #
|
||||
23
plugins/auth/authfile/acl.go
Normal file
23
plugins/auth/authfile/acl.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package acl
|
||||
|
||||
type aclAuth struct {
|
||||
config *ACLConfig
|
||||
}
|
||||
|
||||
func Init() *aclAuth {
|
||||
aclConfig, err := AclConfigLoad("./plugins/auth/authfile/acl.conf")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &aclAuth{
|
||||
config: aclConfig,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclAuth) CheckConnect(clientID, username, password string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *aclAuth) CheckACL(action, clientID, username, ip, topic string) bool {
|
||||
return checkTopicAuth(a.config, action, username, ip, clientID, topic)
|
||||
}
|
||||
148
plugins/auth/authfile/aclcheck.go
Normal file
148
plugins/auth/authfile/aclcheck.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package acl
|
||||
|
||||
import "strings"
|
||||
|
||||
func checkTopicAuth(ACLInfo *ACLConfig, action, ip, username, clientid, topic string) bool {
|
||||
for _, info := range ACLInfo.Info {
|
||||
ctyp := info.Typ
|
||||
switch ctyp {
|
||||
case CLIENTID:
|
||||
if match, auth := info.checkWithClientID(action, clientid, topic); match {
|
||||
return auth
|
||||
}
|
||||
case USERNAME:
|
||||
if match, auth := info.checkWithUsername(action, username, topic); match {
|
||||
return auth
|
||||
}
|
||||
case IP:
|
||||
if match, auth := info.checkWithIP(action, ip, topic); match {
|
||||
return auth
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *AuthInfo) checkWithClientID(action, clientid, topic string) (bool, bool) {
|
||||
auth := false
|
||||
match := false
|
||||
if a.Val == "*" || a.Val == clientid {
|
||||
for _, tp := range a.Topics {
|
||||
des := strings.Replace(tp, "%c", clientid, -1)
|
||||
if action == PUB {
|
||||
if pubTopicMatch(topic, des) {
|
||||
match = true
|
||||
auth = a.checkAuth(PUB)
|
||||
}
|
||||
} else if action == SUB {
|
||||
if subTopicMatch(topic, des) {
|
||||
match = true
|
||||
auth = a.checkAuth(SUB)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return match, auth
|
||||
}
|
||||
|
||||
func (a *AuthInfo) checkWithUsername(action, username, topic string) (bool, bool) {
|
||||
auth := false
|
||||
match := false
|
||||
if a.Val == "*" || a.Val == username {
|
||||
for _, tp := range a.Topics {
|
||||
des := strings.Replace(tp, "%u", username, -1)
|
||||
if action == PUB {
|
||||
if pubTopicMatch(topic, des) {
|
||||
match = true
|
||||
auth = a.checkAuth(PUB)
|
||||
}
|
||||
} else if action == SUB {
|
||||
if subTopicMatch(topic, des) {
|
||||
match = true
|
||||
auth = a.checkAuth(SUB)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return match, auth
|
||||
}
|
||||
|
||||
func (a *AuthInfo) checkWithIP(action, ip, topic string) (bool, bool) {
|
||||
auth := false
|
||||
match := false
|
||||
if a.Val == "*" || a.Val == ip {
|
||||
for _, tp := range a.Topics {
|
||||
des := tp
|
||||
if action == PUB {
|
||||
if pubTopicMatch(topic, des) {
|
||||
auth = a.checkAuth(PUB)
|
||||
match = true
|
||||
}
|
||||
} else if action == SUB {
|
||||
if subTopicMatch(topic, des) {
|
||||
auth = a.checkAuth(SUB)
|
||||
match = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return match, auth
|
||||
}
|
||||
|
||||
func (a *AuthInfo) checkAuth(action string) bool {
|
||||
auth := false
|
||||
if action == PUB {
|
||||
if a.Auth == ALLOW && (a.PubSub == PUB || a.PubSub == PUBSUB) {
|
||||
auth = true
|
||||
} else if a.Auth == DENY && a.PubSub == SUB {
|
||||
auth = true
|
||||
}
|
||||
} else if action == SUB {
|
||||
if a.Auth == ALLOW && (a.PubSub == SUB || a.PubSub == PUBSUB) {
|
||||
auth = true
|
||||
} else if a.Auth == DENY && a.PubSub == PUB {
|
||||
auth = true
|
||||
}
|
||||
}
|
||||
return auth
|
||||
}
|
||||
|
||||
func pubTopicMatch(pub, des string) bool {
|
||||
dest, _ := SubscribeTopicSpilt(des)
|
||||
topic, _ := PublishTopicSpilt(pub)
|
||||
for i, t := range dest {
|
||||
if i > len(topic)-1 {
|
||||
return false
|
||||
}
|
||||
if t == "#" {
|
||||
return true
|
||||
}
|
||||
if t == "+" || t == topic[i] {
|
||||
continue
|
||||
}
|
||||
if t != topic[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func subTopicMatch(pub, des string) bool {
|
||||
dest, _ := SubscribeTopicSpilt(des)
|
||||
topic, _ := SubscribeTopicSpilt(pub)
|
||||
for i, t := range dest {
|
||||
if i > len(topic)-1 {
|
||||
return false
|
||||
}
|
||||
if t == "#" {
|
||||
return true
|
||||
}
|
||||
if t == "+" || "+" == topic[i] || t == topic[i] {
|
||||
continue
|
||||
}
|
||||
if t != topic[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
114
plugins/auth/authfile/aclconfig.go
Normal file
114
plugins/auth/authfile/aclconfig.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
SUB = "1"
|
||||
PUB = "2"
|
||||
PUBSUB = "3"
|
||||
CLIENTID = "clientid"
|
||||
USERNAME = "username"
|
||||
IP = "ip"
|
||||
ALLOW = "allow"
|
||||
DENY = "deny"
|
||||
)
|
||||
|
||||
type AuthInfo struct {
|
||||
Auth string
|
||||
Typ string
|
||||
Val string
|
||||
PubSub string
|
||||
Topics []string
|
||||
}
|
||||
|
||||
type ACLConfig struct {
|
||||
File string
|
||||
Info []*AuthInfo
|
||||
}
|
||||
|
||||
func AclConfigLoad(file string) (*ACLConfig, error) {
|
||||
aclconifg := &ACLConfig{
|
||||
File: file,
|
||||
Info: make([]*AuthInfo, 0, 4),
|
||||
}
|
||||
err := aclconifg.Prase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return aclconifg, err
|
||||
}
|
||||
|
||||
func (c *ACLConfig) Prase() error {
|
||||
f, err := os.Open(c.File)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf := bufio.NewReader(f)
|
||||
var parseErr error
|
||||
for {
|
||||
line, err := buf.ReadString('\n')
|
||||
line = strings.TrimSpace(line)
|
||||
if isCommentOut(line) {
|
||||
continue
|
||||
}
|
||||
if line == "" {
|
||||
return parseErr
|
||||
}
|
||||
// fmt.Println(line)
|
||||
tmpArr := strings.Fields(line)
|
||||
if len(tmpArr) != 5 {
|
||||
parseErr = errors.New("\"" + line + "\" format is error")
|
||||
break
|
||||
}
|
||||
if tmpArr[0] != ALLOW && tmpArr[0] != DENY {
|
||||
parseErr = errors.New("\"" + line + "\" format is error")
|
||||
break
|
||||
}
|
||||
if tmpArr[1] != CLIENTID && tmpArr[1] != USERNAME && tmpArr[1] != IP {
|
||||
parseErr = errors.New("\"" + line + "\" format is error")
|
||||
break
|
||||
}
|
||||
if tmpArr[3] != PUB && tmpArr[3] != SUB && tmpArr[3] != PUBSUB {
|
||||
parseErr = errors.New("\"" + line + "\" format is error")
|
||||
break
|
||||
}
|
||||
// var pubsub int
|
||||
// pubsub, err = strconv.Atoi(tmpArr[3])
|
||||
// if err != nil {
|
||||
// parseErr = errors.New("\"" + line + "\" format is error")
|
||||
// break
|
||||
// }
|
||||
topicStr := strings.Replace(tmpArr[4], " ", "", -1)
|
||||
topicStr = strings.Replace(topicStr, "\n", "", -1)
|
||||
topics := strings.Split(topicStr, ",")
|
||||
tmpAuth := &AuthInfo{
|
||||
Auth: tmpArr[0],
|
||||
Typ: tmpArr[1],
|
||||
Val: tmpArr[2],
|
||||
Topics: topics,
|
||||
PubSub: tmpArr[3],
|
||||
}
|
||||
c.Info = append(c.Info, tmpAuth)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
parseErr = err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return parseErr
|
||||
}
|
||||
func isCommentOut(line string) bool {
|
||||
if strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "*") {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
52
plugins/auth/authfile/spilt.go
Normal file
52
plugins/auth/authfile/spilt.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func SubscribeTopicSpilt(topic string) ([]string, error) {
|
||||
subject := []byte(topic)
|
||||
if bytes.IndexByte(subject, '#') != -1 {
|
||||
if bytes.IndexByte(subject, '#') != len(subject)-1 {
|
||||
return nil, errors.New("Topic format error with index of #")
|
||||
}
|
||||
}
|
||||
re := strings.Split(topic, "/")
|
||||
for i, v := range re {
|
||||
if i != 0 && i != (len(re)-1) {
|
||||
if v == "" {
|
||||
return nil, errors.New("Topic format error with index of //")
|
||||
}
|
||||
if strings.Contains(v, "+") && v != "+" {
|
||||
return nil, errors.New("Topic format error with index of +")
|
||||
}
|
||||
} else {
|
||||
if v == "" {
|
||||
re[i] = "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
return re, nil
|
||||
|
||||
}
|
||||
|
||||
func PublishTopicSpilt(topic string) ([]string, error) {
|
||||
subject := []byte(topic)
|
||||
if bytes.IndexByte(subject, '#') != -1 || bytes.IndexByte(subject, '+') != -1 {
|
||||
return nil, errors.New("Publish Topic format error with + and #")
|
||||
}
|
||||
re := strings.Split(topic, "/")
|
||||
for i, v := range re {
|
||||
if v == "" {
|
||||
if i != 0 && i != (len(re)-1) {
|
||||
return nil, errors.New("Topic format error with index of //")
|
||||
} else {
|
||||
re[i] = "/"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return re, nil
|
||||
}
|
||||
@@ -138,7 +138,7 @@ func (a *authHTTP) CheckConnect(clientID, username, password string) bool {
|
||||
// }
|
||||
|
||||
//CheckACL check mqtt connect
|
||||
func (a *authHTTP) CheckACL(action, username, topic string) bool {
|
||||
func (a *authHTTP) CheckACL(action, clientID, username, ip, topic string) bool {
|
||||
|
||||
{
|
||||
aCache := checkCache(action, "", username, "", topic)
|
||||
|
||||
@@ -2,7 +2,7 @@ package auth
|
||||
|
||||
type mockAuth struct{}
|
||||
|
||||
func (m *mockAuth) CheckACL(action, username, topic string) bool {
|
||||
func (m *mockAuth) CheckACL(action, clientID, username, ip, topic string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user