mirror of
https://github.com/fhmq/hmq.git
synced 2026-04-26 19:48:34 +00:00
'acl'
This commit is contained in:
@@ -54,6 +54,8 @@ $ go run main.go
|
|||||||
|
|
||||||
* TLS/SSL Support
|
* TLS/SSL Support
|
||||||
|
|
||||||
|
* Flexible ACL
|
||||||
|
|
||||||
### QUEUE SUBSCRIBE
|
### QUEUE SUBSCRIBE
|
||||||
~~~
|
~~~
|
||||||
| Prefix | Examples |
|
| Prefix | Examples |
|
||||||
|
|||||||
29
broker/auth.go
Normal file
29
broker/auth.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package broker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hmq/lib/acl"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PUB = 1
|
||||||
|
SUB = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *client) CheckTopicAuth(typ int, topic string) bool {
|
||||||
|
if !c.broker.config.Acl {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(topic, "$queue/") {
|
||||||
|
topic = string([]byte(topic)[7:])
|
||||||
|
if topic == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ip := c.info.remoteIP
|
||||||
|
username := string(c.info.username)
|
||||||
|
clientid := string(c.info.clientID)
|
||||||
|
aclInfo := c.broker.AclConfig
|
||||||
|
return acl.CheckTopicAuth(aclInfo, typ, ip, username, clientid, topic)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package broker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"hmq/lib/acl"
|
||||||
"hmq/lib/message"
|
"hmq/lib/message"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -18,6 +19,7 @@ type Broker struct {
|
|||||||
cid uint64
|
cid uint64
|
||||||
config *Config
|
config *Config
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
|
AclConfig *acl.ACLConfig
|
||||||
clients cMap
|
clients cMap
|
||||||
routes cMap
|
routes cMap
|
||||||
remotes cMap
|
remotes cMap
|
||||||
@@ -45,10 +47,22 @@ func NewBroker(config *Config) *Broker {
|
|||||||
}
|
}
|
||||||
b.tlsConfig = tlsconfig
|
b.tlsConfig = tlsconfig
|
||||||
}
|
}
|
||||||
|
if b.config.Acl {
|
||||||
|
aclconfig, err := acl.AclConfigLoad(b.config.AclConf)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Load acl conf error: ", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
b.AclConfig = aclconfig
|
||||||
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Broker) Start() {
|
func (b *Broker) Start() {
|
||||||
|
if b == nil {
|
||||||
|
log.Error("broker is null")
|
||||||
|
return
|
||||||
|
}
|
||||||
if b.config.Port != "" {
|
if b.config.Port != "" {
|
||||||
go b.StartListening(CLIENT)
|
go b.StartListening(CLIENT)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,11 +146,16 @@ func (c *client) ProcessPublish(buf []byte) {
|
|||||||
c.Close()
|
c.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
topic := msg.Topic()
|
||||||
|
|
||||||
|
if c.typ != CLIENT || !c.CheckTopicAuth(PUB, string(topic)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
c.ProcessPublishMessage(buf, msg)
|
c.ProcessPublishMessage(buf, msg)
|
||||||
|
|
||||||
if msg.Retain() {
|
if msg.Retain() {
|
||||||
if b := c.broker; b != nil {
|
if b := c.broker; b != nil {
|
||||||
err := b.rl.Insert(msg.Topic(), buf)
|
err := b.rl.Insert(topic, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Insert Retain Message error: ", err)
|
log.Error("Insert Retain Message error: ", err)
|
||||||
}
|
}
|
||||||
@@ -246,12 +251,13 @@ func (c *client) ProcessSubscribe(buf []byte) {
|
|||||||
for i, t := range topics {
|
for i, t := range topics {
|
||||||
topic := string(t)
|
topic := string(t)
|
||||||
//check topic auth for client
|
//check topic auth for client
|
||||||
// if !c.CheckTopicAuth(topic, SUB) {
|
if c.typ == CLIENT {
|
||||||
// log.Error("CheckSubAuth failed")
|
if !c.CheckTopicAuth(SUB, topic) {
|
||||||
// retcodes = append(retcodes, message.QosFailure)
|
log.Error("CheckSubAuth failed")
|
||||||
// continue
|
retcodes = append(retcodes, message.QosFailure)
|
||||||
// }
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, exist := c.subs[topic]; !exist {
|
if _, exist := c.subs[topic]; !exist {
|
||||||
queue := false
|
queue := false
|
||||||
if strings.HasPrefix(topic, "$queue/") {
|
if strings.HasPrefix(topic, "$queue/") {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package broker
|
package broker
|
||||||
|
|
||||||
import "sync"
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
type RetainList struct {
|
type RetainList struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|||||||
12
conf/acl.conf
Normal file
12
conf/acl.conf
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
## pub 1 , sub 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 #
|
||||||
114
lib/acl/acl.go
Normal file
114
lib/acl/acl.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PUB = 1
|
||||||
|
SUB = 2
|
||||||
|
PUBSUB = 3
|
||||||
|
CLIENTID = "clientid"
|
||||||
|
USERNAME = "username"
|
||||||
|
IP = "ip"
|
||||||
|
ALLOW = "allow"
|
||||||
|
DENY = "deny"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthInfo struct {
|
||||||
|
Auth string
|
||||||
|
Typ string
|
||||||
|
Val string
|
||||||
|
PubSub int
|
||||||
|
Topics []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ACLConfig struct {
|
||||||
|
File string
|
||||||
|
Info []*AuthInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func AclConfigLoad(file string) (*ACLConfig, error) {
|
||||||
|
if file == "" {
|
||||||
|
file = "./conf/acl.conf"
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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: pubsub,
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
148
lib/acl/aclcheck.go
Normal file
148
lib/acl/aclcheck.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func CheckTopicAuth(ACLInfo *ACLConfig, typ int, ip, username, clientid, topic string) bool {
|
||||||
|
for _, info := range ACLInfo.Info {
|
||||||
|
ctyp := info.Typ
|
||||||
|
switch ctyp {
|
||||||
|
case CLIENTID:
|
||||||
|
if match, auth := info.checkWithClientID(typ, clientid, topic); match {
|
||||||
|
return auth
|
||||||
|
}
|
||||||
|
case USERNAME:
|
||||||
|
if match, auth := info.checkWithUsername(typ, username, topic); match {
|
||||||
|
return auth
|
||||||
|
}
|
||||||
|
case IP:
|
||||||
|
if match, auth := info.checkWithIP(typ, ip, topic); match {
|
||||||
|
return auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthInfo) checkWithClientID(typ int, 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 typ == PUB {
|
||||||
|
if pubTopicMatch(topic, des) {
|
||||||
|
match = true
|
||||||
|
auth = a.checkAuth(PUB)
|
||||||
|
}
|
||||||
|
} else if typ == SUB {
|
||||||
|
if subTopicMatch(topic, des) {
|
||||||
|
match = true
|
||||||
|
auth = a.checkAuth(SUB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match, auth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthInfo) checkWithUsername(typ int, 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 typ == PUB {
|
||||||
|
if pubTopicMatch(topic, des) {
|
||||||
|
match = true
|
||||||
|
auth = a.checkAuth(PUB)
|
||||||
|
}
|
||||||
|
} else if typ == SUB {
|
||||||
|
if subTopicMatch(topic, des) {
|
||||||
|
match = true
|
||||||
|
auth = a.checkAuth(SUB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match, auth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthInfo) checkWithIP(typ int, ip, topic string) (bool, bool) {
|
||||||
|
auth := false
|
||||||
|
match := false
|
||||||
|
if a.Val == "*" || a.Val == ip {
|
||||||
|
for _, tp := range a.Topics {
|
||||||
|
des := tp
|
||||||
|
if typ == PUB {
|
||||||
|
if pubTopicMatch(topic, des) {
|
||||||
|
auth = a.checkAuth(PUB)
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
} else if typ == SUB {
|
||||||
|
if subTopicMatch(topic, des) {
|
||||||
|
auth = a.checkAuth(SUB)
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match, auth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthInfo) checkAuth(typ int) bool {
|
||||||
|
auth := false
|
||||||
|
if typ == 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 typ == 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
|
||||||
|
}
|
||||||
52
lib/acl/spilt.go
Normal file
52
lib/acl/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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user