mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
fix(region): optimized peer secgroup
This commit is contained in:
@@ -57,6 +57,9 @@ const DEFAULT_SRC_RULE_ID = "default_src_rule_id"
|
||||
type SecRuleInfo struct {
|
||||
InDefaultRule SecurityRule
|
||||
OutDefaultRule SecurityRule
|
||||
in SecurityRuleSet
|
||||
out SecurityRuleSet
|
||||
rules SecurityRuleSet
|
||||
Rules SecurityRuleSet
|
||||
MinPriority int
|
||||
MaxPriority int
|
||||
@@ -64,10 +67,26 @@ type SecRuleInfo struct {
|
||||
IsSupportPeerSecgroup bool
|
||||
}
|
||||
|
||||
func (r SecRuleInfo) getOffest(priority int) (int, int) {
|
||||
if r.MinPriority < r.MaxPriority {
|
||||
if priority >= r.MaxPriority {
|
||||
return priority, -1
|
||||
}
|
||||
return priority + 1, 0
|
||||
} else {
|
||||
if priority <= r.MaxPriority {
|
||||
return priority, 1
|
||||
}
|
||||
return priority - 1, 0
|
||||
}
|
||||
}
|
||||
|
||||
func (r SecRuleInfo) AddDefaultRule(d SecRuleInfo, inRules, outRules []SecurityRule, isSrc bool) ([]SecurityRule, []SecurityRule) {
|
||||
min, max := r.MinPriority, r.MaxPriority
|
||||
r.InDefaultRule.Priority = min + 1
|
||||
r.OutDefaultRule.Priority = min + 1
|
||||
r.InDefaultRule.minPriority, r.InDefaultRule.maxPriority = r.MinPriority, r.MaxPriority
|
||||
r.OutDefaultRule.minPriority, r.OutDefaultRule.maxPriority = r.MinPriority, r.MaxPriority
|
||||
if max >= min {
|
||||
r.InDefaultRule.Priority = min - 1
|
||||
r.OutDefaultRule.Priority = min - 1
|
||||
@@ -81,8 +100,21 @@ func (r SecRuleInfo) AddDefaultRule(d SecRuleInfo, inRules, outRules []SecurityR
|
||||
r.OutDefaultRule.ExternalId = DEFAULT_DEST_RULE_ID
|
||||
}
|
||||
|
||||
inRules = append(inRules, r.InDefaultRule)
|
||||
outRules = append(outRules, r.OutDefaultRule)
|
||||
var isWideRule = func(rules []SecurityRule) bool {
|
||||
for _, rule := range rules {
|
||||
if strings.HasSuffix(rule.String(), "allow any") || strings.HasSuffix(rule.String(), "deny any") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if !isWideRule(inRules) {
|
||||
inRules = append(inRules, r.InDefaultRule)
|
||||
}
|
||||
if !isWideRule(outRules) {
|
||||
outRules = append(outRules, r.OutDefaultRule)
|
||||
}
|
||||
return inRules, outRules
|
||||
}
|
||||
|
||||
@@ -101,6 +133,10 @@ type SecurityGroupCreateInput struct {
|
||||
}
|
||||
|
||||
type SecurityRule struct {
|
||||
minPriority int
|
||||
maxPriority int
|
||||
offset int
|
||||
|
||||
secrules.SecurityRule
|
||||
Name string
|
||||
ExternalId string
|
||||
@@ -109,6 +145,11 @@ type SecurityRule struct {
|
||||
PeerSecgroupId string
|
||||
}
|
||||
|
||||
func (self *SecurityRule) addPriority(offset int) {
|
||||
self.Priority += offset
|
||||
self.offset += offset
|
||||
}
|
||||
|
||||
func (r SecurityRule) String() string {
|
||||
if len(r.PeerSecgroupId) == 0 {
|
||||
return r.SecurityRule.String()
|
||||
@@ -118,19 +159,25 @@ func (r SecurityRule) String() string {
|
||||
|
||||
type SecurityRuleSet []SecurityRule
|
||||
|
||||
func (rules SecurityRuleSet) Split(isSupportPeerSecgroup bool) (in, out SecurityRuleSet, isStandardRules bool) {
|
||||
isStandardRules = true
|
||||
for i := 0; i < len(rules); i++ {
|
||||
if len(rules[i].PeerSecgroupId) > 0 {
|
||||
isStandardRules = false
|
||||
func (self *SecRuleInfo) Split() (in, out SecurityRuleSet) {
|
||||
self.rules = SecurityRuleSet{}
|
||||
self.in = SecurityRuleSet{}
|
||||
self.out = SecurityRuleSet{}
|
||||
for i := 0; i < len(self.Rules); i++ {
|
||||
self.rules = append(self.rules, self.Rules[i])
|
||||
if self.Rules[i].Direction == secrules.DIR_IN {
|
||||
self.in = append(self.in, self.Rules[i])
|
||||
} else {
|
||||
self.out = append(self.out, self.Rules[i])
|
||||
}
|
||||
if !isSupportPeerSecgroup && len(rules[i].PeerSecgroupId) > 0 {
|
||||
self.Rules[i].minPriority, self.Rules[i].maxPriority = self.MinPriority, self.MaxPriority
|
||||
if !self.IsSupportPeerSecgroup && len(self.Rules[i].PeerSecgroupId) > 0 {
|
||||
continue
|
||||
}
|
||||
if rules[i].Direction == secrules.DIR_IN {
|
||||
in = append(in, rules[i])
|
||||
if self.Rules[i].Direction == secrules.DIR_IN {
|
||||
in = append(in, self.Rules[i])
|
||||
} else {
|
||||
out = append(out, rules[i])
|
||||
out = append(out, self.Rules[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -145,29 +192,63 @@ func (srs SecurityRuleSet) Swap(i, j int) {
|
||||
}
|
||||
|
||||
func (srs SecurityRuleSet) Less(i, j int) bool {
|
||||
return srs[i].Priority < srs[j].Priority || (srs[i].Priority == srs[j].Priority && srs[i].String() < srs[j].String())
|
||||
if srs[i].Priority < srs[j].Priority {
|
||||
return true
|
||||
}
|
||||
if srs[i].Priority > srs[j].Priority {
|
||||
return false
|
||||
}
|
||||
if len(srs) > 0 {
|
||||
if (srs[0].minPriority <= srs[0].maxPriority && srs[i].String() < srs[j].String()) ||
|
||||
(srs[0].minPriority > srs[0].maxPriority && srs[i].String() > srs[j].String()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (srs SecurityRuleSet) AllowList() secrules.SecurityRuleSet {
|
||||
func (srs SecurityRuleSet) CanBeSplitByProtocol() bool {
|
||||
firstNormalRuleIndex, find := 0, false
|
||||
for idx, r := range srs {
|
||||
if !(strings.HasSuffix(r.String(), "allow any") || strings.HasSuffix(r.String(), "deny any")) && !find {
|
||||
firstNormalRuleIndex = idx
|
||||
find = true
|
||||
} else {
|
||||
if r.ExternalId == DEFAULT_SRC_RULE_ID || r.ExternalId == DEFAULT_DEST_RULE_ID {
|
||||
return true
|
||||
}
|
||||
if idx > firstNormalRuleIndex {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (srs SecurityRuleSet) AllowList() (secrules.SecurityRuleSet, bool) {
|
||||
rules := secrules.SecurityRuleSet{}
|
||||
isOk := true
|
||||
for _, r := range srs {
|
||||
if len(r.PeerSecgroupId) > 0 {
|
||||
isOk = false
|
||||
}
|
||||
rules = append(rules, r.SecurityRule)
|
||||
}
|
||||
return rules.AllowList()
|
||||
return rules.AllowList(), isOk
|
||||
}
|
||||
|
||||
func (srs SecurityRuleSet) Debug() {
|
||||
for i := 0; i < len(srs); i++ {
|
||||
log.Debugf("Name: %s id: %s external_id: %s priority: %d %s", srs[i].Name, srs[i].Id, srs[i].ExternalId, srs[i].Priority, srs[i].String())
|
||||
log.Debugf("Name: %s id: %s external_id: %s min: %d max: %d priority: %d %s", srs[i].Name, srs[i].Id, srs[i].ExternalId, srs[i].minPriority, srs[i].maxPriority, srs[i].Priority, srs[i].String())
|
||||
}
|
||||
}
|
||||
|
||||
func SortSecurityRule(rules SecurityRuleSet, max, min int, isAsc, onlyAllowRules bool) {
|
||||
if (max >= min || onlyAllowRules) && !isAsc {
|
||||
sort.Sort(sort.Reverse(rules))
|
||||
func SortSecurityRule(rules SecurityRuleSet, isAsc bool, info SecRuleInfo) {
|
||||
if (info.MaxPriority > info.MinPriority && isAsc) || (info.MaxPriority < info.MinPriority && !isAsc) || (info.IsOnlySupportAllowRules && isAsc) {
|
||||
sort.Sort(rules)
|
||||
return
|
||||
}
|
||||
sort.Sort(rules)
|
||||
sort.Sort(sort.Reverse(rules))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -183,103 +264,125 @@ func isAllowListEqual(src, dest secrules.SecurityRuleSet) bool {
|
||||
return s1.IsEqual(s2)
|
||||
}
|
||||
|
||||
func isPeerListEqual(src, dest SecurityRuleSet) bool {
|
||||
if len(src) != len(dest) {
|
||||
return false
|
||||
}
|
||||
s1, s2 := set.New(set.ThreadSafe), set.New(set.ThreadSafe)
|
||||
for i := 0; i < len(src); i++ {
|
||||
s1.Add(src[i].String())
|
||||
s2.Add(dest[i].String())
|
||||
}
|
||||
return s1.IsEqual(s2)
|
||||
}
|
||||
|
||||
func CompareRules(src, dest SecRuleInfo, debug bool) (common, inAdds, outAdds, inDels, outDels SecurityRuleSet) {
|
||||
srcInRules, srcOutRules, isSrcStandardRules := src.Rules.Split(src.IsSupportPeerSecgroup)
|
||||
destInRules, destOutRules, isDestStandardRules := dest.Rules.Split(dest.IsSupportPeerSecgroup)
|
||||
srcInRules, srcOutRules := src.Split()
|
||||
destInRules, destOutRules := dest.Split()
|
||||
|
||||
srcInRules, srcOutRules = src.AddDefaultRule(dest, srcInRules, srcOutRules, true)
|
||||
destInRules, destOutRules = dest.AddDefaultRule(src, destInRules, destOutRules, false)
|
||||
|
||||
if debug {
|
||||
log.Debugf("src in rules: ")
|
||||
srcInRules.Debug()
|
||||
// AllowList 需要优先级从高到低排序
|
||||
SortSecurityRule(srcInRules, false, src)
|
||||
SortSecurityRule(srcOutRules, false, src)
|
||||
|
||||
SortSecurityRule(destInRules, false, dest)
|
||||
SortSecurityRule(destOutRules, false, dest)
|
||||
|
||||
isInAllowSplit := srcInRules.CanBeSplitByProtocol() && destInRules.CanBeSplitByProtocol()
|
||||
isOutAllowSplit := srcOutRules.CanBeSplitByProtocol() && destOutRules.CanBeSplitByProtocol()
|
||||
|
||||
srcInAllowList, srcInOk := srcInRules.AllowList()
|
||||
srcOutAllowList, srcOutOk := srcOutRules.AllowList()
|
||||
|
||||
destInAllowList, destInOk := destInRules.AllowList()
|
||||
destOutAllowList, destOutOk := destOutRules.AllowList()
|
||||
inEquals, outEquals, inOk, outOk := isAllowListEqual(srcInAllowList, destInAllowList), isAllowListEqual(srcOutAllowList, destOutAllowList), srcInOk && destInOk, srcOutOk && destOutOk
|
||||
|
||||
if inEquals && outEquals && inOk && outOk {
|
||||
common = dest.rules
|
||||
return
|
||||
}
|
||||
|
||||
if (isSrcStandardRules && isDestStandardRules) || (!src.IsSupportPeerSecgroup && !dest.IsSupportPeerSecgroup) {
|
||||
// AllowList 需要优先级从高到低排序
|
||||
SortSecurityRule(srcInRules, src.MaxPriority, src.MinPriority, false, src.IsOnlySupportAllowRules)
|
||||
SortSecurityRule(srcOutRules, src.MaxPriority, src.MinPriority, false, src.IsOnlySupportAllowRules)
|
||||
if debug {
|
||||
log.Debugf("====desc sort====")
|
||||
log.Debugf("src in rules: ")
|
||||
srcInRules.Debug()
|
||||
log.Debugf("src out rules: ")
|
||||
srcOutRules.Debug()
|
||||
log.Debugf("dest in rules: ")
|
||||
destInRules.Debug()
|
||||
log.Debugf("dest out rules: ")
|
||||
destOutRules.Debug()
|
||||
log.Debugf("====desc sort end====")
|
||||
|
||||
SortSecurityRule(destInRules, dest.MaxPriority, dest.MinPriority, false, dest.IsOnlySupportAllowRules)
|
||||
SortSecurityRule(destOutRules, dest.MaxPriority, dest.MinPriority, false, dest.IsOnlySupportAllowRules)
|
||||
log.Debugf("isInAllowSplit: %v isOutAllowSplit: %v", isInAllowSplit, isOutAllowSplit)
|
||||
|
||||
srcInAllowList := srcInRules.AllowList()
|
||||
srcOutAllowList := srcOutRules.AllowList()
|
||||
log.Debugf("AllowList:")
|
||||
log.Debugf("In: src: %s dest: %s isEquals: %v", srcInAllowList.String(), destInAllowList.String(), inEquals)
|
||||
log.Debugf("Out: src: %s dest: %s isEquals: %v", srcOutAllowList.String(), destOutAllowList.String(), outEquals)
|
||||
}
|
||||
|
||||
destInAllowList := destInRules.AllowList()
|
||||
destOutAllowList := destOutRules.AllowList()
|
||||
inEquals, outEquals := isAllowListEqual(srcInAllowList, destInAllowList), isAllowListEqual(srcOutAllowList, destOutAllowList)
|
||||
|
||||
if inEquals && outEquals {
|
||||
return
|
||||
}
|
||||
|
||||
if debug {
|
||||
log.Debugf("In: src: %s dest: %s result: %v", srcInAllowList.String(), destInAllowList.String(), inEquals)
|
||||
log.Debugf("Out: src: %s dest: %s result: %v", srcOutAllowList.String(), destOutAllowList.String(), outEquals)
|
||||
}
|
||||
|
||||
var tryUseAllowList = func(defaultRule SecurityRule, allowList secrules.SecurityRuleSet, rules SecurityRuleSet, isOnlyAllowList bool) SecurityRuleSet {
|
||||
if len(allowList) < len(rules) || isOnlyAllowList {
|
||||
rules = SecurityRuleSet{}
|
||||
for i := range allowList {
|
||||
rule := SecurityRule{}
|
||||
rule.SecurityRule = allowList[i]
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
if !utils.IsInStringArray(allowList.String(), []string{
|
||||
"",
|
||||
"in:allow any",
|
||||
"out:allow any",
|
||||
"in:deny any",
|
||||
"out:deny any",
|
||||
}) && strings.HasSuffix(defaultRule.SecurityRule.String(), "deny any") {
|
||||
rules = append(rules, defaultRule)
|
||||
}
|
||||
var tryUseAllowList = func(defaultRule SecurityRule, allowList secrules.SecurityRuleSet, rules SecurityRuleSet, isOnlyAllowList bool, isOk bool) SecurityRuleSet {
|
||||
if isOk && (len(allowList) < len(rules) || isOnlyAllowList) {
|
||||
rules = SecurityRuleSet{}
|
||||
for i := range allowList {
|
||||
rule := SecurityRule{}
|
||||
rule.SecurityRule = allowList[i]
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
srcInRules = tryUseAllowList(src.InDefaultRule, srcInAllowList, srcInRules, dest.IsOnlySupportAllowRules)
|
||||
srcOutRules = tryUseAllowList(src.OutDefaultRule, srcOutAllowList, srcOutRules, dest.IsOnlySupportAllowRules)
|
||||
|
||||
if inEquals {
|
||||
srcInRules, destInRules = []SecurityRule{}, []SecurityRule{}
|
||||
}
|
||||
if outEquals {
|
||||
srcOutRules, destOutRules = []SecurityRule{}, []SecurityRule{}
|
||||
if !utils.IsInStringArray(allowList.String(), []string{
|
||||
"",
|
||||
"in:allow any",
|
||||
"out:allow any",
|
||||
"in:deny any",
|
||||
"out:deny any",
|
||||
}) && strings.HasSuffix(defaultRule.SecurityRule.String(), "deny any") {
|
||||
rules = append(rules, defaultRule)
|
||||
}
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
if debug {
|
||||
log.Debugf("src in rules: ")
|
||||
srcInRules.Debug()
|
||||
srcInRules = tryUseAllowList(src.InDefaultRule, srcInAllowList, srcInRules, dest.IsOnlySupportAllowRules, inOk)
|
||||
srcOutRules = tryUseAllowList(src.OutDefaultRule, srcOutAllowList, srcOutRules, dest.IsOnlySupportAllowRules, outOk)
|
||||
|
||||
if inEquals && inOk {
|
||||
common = append(common, dest.in...)
|
||||
srcInRules, destInRules = []SecurityRule{}, []SecurityRule{}
|
||||
}
|
||||
if outEquals && outOk {
|
||||
common = append(common, dest.out...)
|
||||
srcOutRules, destOutRules = []SecurityRule{}, []SecurityRule{}
|
||||
}
|
||||
|
||||
// 默认从优先级低到高比较
|
||||
SortSecurityRule(srcInRules, src.MaxPriority, src.MinPriority, true, src.IsOnlySupportAllowRules)
|
||||
SortSecurityRule(srcOutRules, src.MaxPriority, src.MinPriority, true, src.IsOnlySupportAllowRules)
|
||||
SortSecurityRule(srcInRules, true, src)
|
||||
SortSecurityRule(srcOutRules, true, src)
|
||||
|
||||
SortSecurityRule(destInRules, dest.MaxPriority, dest.MinPriority, true, dest.IsOnlySupportAllowRules)
|
||||
SortSecurityRule(destOutRules, dest.MaxPriority, dest.MinPriority, true, dest.IsOnlySupportAllowRules)
|
||||
SortSecurityRule(destInRules, true, dest)
|
||||
SortSecurityRule(destOutRules, true, dest)
|
||||
|
||||
var addPriority = func(priority int, min, max int, onlyAllowRules bool) int {
|
||||
if onlyAllowRules {
|
||||
return priority
|
||||
}
|
||||
inc := 1
|
||||
if max < min {
|
||||
max, min, inc = min, max, -1
|
||||
}
|
||||
if priority >= max || priority <= min {
|
||||
return priority
|
||||
}
|
||||
return priority + inc
|
||||
if debug {
|
||||
log.Debugf("====asc sort====")
|
||||
log.Debugf("src in rules: ")
|
||||
srcInRules.Debug()
|
||||
log.Debugf("src out rules: ")
|
||||
srcOutRules.Debug()
|
||||
log.Debugf("dest in rules: ")
|
||||
destInRules.Debug()
|
||||
log.Debugf("dest out rules: ")
|
||||
destOutRules.Debug()
|
||||
log.Debugf("====asc sort end====")
|
||||
}
|
||||
|
||||
var _compare = func(srcRules SecurityRuleSet, destRules SecurityRuleSet) (common, add, del SecurityRuleSet) {
|
||||
i, j, priority := 0, 0, (dest.MinPriority-1+dest.MaxPriority)/2
|
||||
i, j, priority := 0, 0, dest.MinPriority
|
||||
if len(destRules) > 0 && (destRules[i].ExternalId != DEFAULT_DEST_RULE_ID) {
|
||||
priority = destRules[0].Priority
|
||||
}
|
||||
for i < len(srcRules) || j < len(destRules) {
|
||||
if i < len(srcRules) && j < len(destRules) {
|
||||
destRuleStr := destRules[j].String()
|
||||
@@ -302,8 +405,20 @@ func CompareRules(src, dest SecRuleInfo, debug bool) (common, inAdds, outAdds, i
|
||||
del = append(del, destRules[j])
|
||||
j++
|
||||
} else {
|
||||
priority = addPriority(priority, dest.MinPriority, dest.MaxPriority, dest.IsOnlySupportAllowRules)
|
||||
offset := 0
|
||||
if !dest.IsOnlySupportAllowRules && i-1 >= 0 && srcRules[i-1].Priority != srcRules[i].Priority {
|
||||
priority, offset = dest.getOffest(priority)
|
||||
}
|
||||
if offset != 0 {
|
||||
for r := range add {
|
||||
add[r].addPriority(offset)
|
||||
}
|
||||
for r := range common {
|
||||
common[r].addPriority(offset)
|
||||
}
|
||||
}
|
||||
srcRules[i].Priority = priority
|
||||
srcRules[i].minPriority, srcRules[i].maxPriority = dest.MinPriority, dest.MaxPriority
|
||||
add = append(add, srcRules[i])
|
||||
i++
|
||||
}
|
||||
@@ -311,8 +426,20 @@ func CompareRules(src, dest SecRuleInfo, debug bool) (common, inAdds, outAdds, i
|
||||
del = append(del, destRules[j])
|
||||
j++
|
||||
} else if j >= len(destRules) {
|
||||
priority = addPriority(priority, dest.MinPriority, dest.MaxPriority, dest.IsOnlySupportAllowRules)
|
||||
offset := 0
|
||||
if !dest.IsOnlySupportAllowRules && i-1 >= 0 && srcRules[i-1].Priority != srcRules[i].Priority {
|
||||
priority, offset = dest.getOffest(priority)
|
||||
}
|
||||
srcRules[i].Priority = priority
|
||||
srcRules[i].minPriority, srcRules[i].maxPriority = dest.MinPriority, dest.MaxPriority
|
||||
if offset != 0 {
|
||||
for r := range add {
|
||||
add[r].addPriority(offset)
|
||||
}
|
||||
for r := range common {
|
||||
common[r].addPriority(offset)
|
||||
}
|
||||
}
|
||||
add = append(add, srcRules[i])
|
||||
i++
|
||||
}
|
||||
@@ -365,8 +492,16 @@ func CompareRules(src, dest SecRuleInfo, debug bool) (common, inAdds, outAdds, i
|
||||
}
|
||||
|
||||
var inCommon, outCommon SecurityRuleSet
|
||||
inCommon, inAdds, inDels = compare(srcInRules, destInRules)
|
||||
outCommon, outAdds, outDels = compare(srcOutRules, destOutRules)
|
||||
if isInAllowSplit {
|
||||
inCommon, inAdds, inDels = compare(srcInRules, destInRules)
|
||||
} else {
|
||||
inCommon, inAdds, inDels = _compare(srcInRules, destInRules)
|
||||
}
|
||||
if isOutAllowSplit {
|
||||
outCommon, outAdds, outDels = compare(srcOutRules, destOutRules)
|
||||
} else {
|
||||
outCommon, outAdds, outDels = _compare(srcOutRules, destOutRules)
|
||||
}
|
||||
|
||||
var handleDefaultRules = func(removed, added []SecurityRule, isOnlyAllowList bool) ([]SecurityRule, []SecurityRule) {
|
||||
ret := []SecurityRule{}
|
||||
@@ -408,7 +543,8 @@ func CompareRules(src, dest SecRuleInfo, debug bool) (common, inAdds, outAdds, i
|
||||
|
||||
inDels, inAdds = handleDefaultRules(inDels, inAdds, dest.IsOnlySupportAllowRules)
|
||||
outDels, outAdds = handleDefaultRules(outDels, outAdds, dest.IsOnlySupportAllowRules)
|
||||
common, _ = handleDefaultRules(append(inCommon, outCommon...), []SecurityRule{}, dest.IsOnlySupportAllowRules)
|
||||
_common, _ := handleDefaultRules(append(inCommon, outCommon...), []SecurityRule{}, dest.IsOnlySupportAllowRules)
|
||||
common = append(common, _common...)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user