mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-05-19 17:03:21 +08:00
460 lines
13 KiB
Go
460 lines
13 KiB
Go
// Copyright 2019 Yunion
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
package openstack
|
||
|
||
import (
|
||
"fmt"
|
||
"net"
|
||
"sort"
|
||
"strings"
|
||
"time"
|
||
|
||
"yunion.io/x/jsonutils"
|
||
"yunion.io/x/log"
|
||
"yunion.io/x/pkg/errors"
|
||
"yunion.io/x/pkg/util/secrules"
|
||
"yunion.io/x/pkg/utils"
|
||
|
||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||
"yunion.io/x/onecloud/pkg/util/httputils"
|
||
)
|
||
|
||
const (
|
||
SECGROUP_NOT_SUPPORT = "openstack_skip_security_group"
|
||
)
|
||
|
||
type SSecurityGroupRule struct {
|
||
Direction string
|
||
Ethertype string
|
||
ID string
|
||
PortRangeMax int
|
||
PortRangeMin int
|
||
Protocol string
|
||
RemoteGroupID string
|
||
RemoteIpPrefix string
|
||
SecurityGroupID string
|
||
ProjectID string
|
||
RevisionNumber int
|
||
Tags []string
|
||
TenantID string
|
||
CreatedAt time.Time
|
||
UpdatedAt time.Time
|
||
Description string
|
||
}
|
||
|
||
type SSecurityGroup struct {
|
||
region *SRegion
|
||
|
||
Description string
|
||
ID string
|
||
Name string
|
||
SecurityGroupRules []SSecurityGroupRule
|
||
ProjectID string
|
||
RevisionNumber int
|
||
CreatedAt time.Time
|
||
UpdatedAt time.Time
|
||
Tags []string
|
||
TenantID string
|
||
}
|
||
|
||
type SecurigyGroupRuleSet []SSecurityGroupRule
|
||
|
||
func (v SecurigyGroupRuleSet) Len() int {
|
||
return len(v)
|
||
}
|
||
|
||
func (v SecurigyGroupRuleSet) Swap(i, j int) {
|
||
v[i], v[j] = v[j], v[i]
|
||
}
|
||
|
||
func (v SecurigyGroupRuleSet) Less(i, j int) bool {
|
||
return strings.Compare(v[i].String(), v[j].String()) <= 0
|
||
}
|
||
|
||
func (region *SRegion) GetSecurityGroup(secgroupId string) (*SSecurityGroup, error) {
|
||
_, resp, err := region.Get("network", "/v2.0/security-groups/"+secgroupId, "", nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
secgroup := &SSecurityGroup{region: region}
|
||
return secgroup, resp.Unmarshal(secgroup, "security_group")
|
||
}
|
||
|
||
func (region *SRegion) GetSecurityGroups(name string) ([]SSecurityGroup, error) {
|
||
url := "/v2.0/security-groups"
|
||
if len(name) > 0 {
|
||
url = fmt.Sprintf("%s?name=%s", url, name)
|
||
}
|
||
secgroups := []SSecurityGroup{}
|
||
for len(url) > 0 {
|
||
_, resp, err := region.List("network", url, "", nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
_secgroups := []SSecurityGroup{}
|
||
err = resp.Unmarshal(&_secgroups, "security_groups")
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, `resp.Unmarshal(&_secgroups, "security_groups")`)
|
||
}
|
||
secgroups = append(secgroups, _secgroups...)
|
||
url = ""
|
||
if resp.Contains("security_groups_links") {
|
||
nextLink := []SNextLink{}
|
||
err = resp.Unmarshal(&nextLink, "security_groups_links")
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, `resp.Unmarshal(&nextLink, "security_groups_links")`)
|
||
}
|
||
for _, next := range nextLink {
|
||
if next.Rel == "next" {
|
||
url = next.Href
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
for i := range secgroups {
|
||
secgroups[i].region = region
|
||
}
|
||
|
||
return secgroups, nil
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) GetMetadata() *jsonutils.JSONDict {
|
||
return nil
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) GetVpcId() string {
|
||
return "normal"
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) GetId() string {
|
||
return secgroup.ID
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) GetGlobalId() string {
|
||
return secgroup.ID
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) GetDescription() string {
|
||
return secgroup.Description
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) GetName() string {
|
||
if len(secgroup.Name) > 0 {
|
||
return secgroup.Name
|
||
}
|
||
return secgroup.ID
|
||
}
|
||
|
||
func (secgroup *SSecurityGroupRule) String() string {
|
||
rules := secgroup.toRules()
|
||
result := []string{}
|
||
for _, rule := range rules {
|
||
result = append(result, rule.String())
|
||
}
|
||
return strings.Join(result, ";")
|
||
}
|
||
|
||
func (secgrouprule *SSecurityGroupRule) toRules() []secrules.SecurityRule {
|
||
rules := []secrules.SecurityRule{}
|
||
// 暂时忽略IPv6安全组规则,忽略远端也是安全组的规则
|
||
if secgrouprule.Ethertype != "IPv4" || len(secgrouprule.RemoteGroupID) > 0 {
|
||
return rules
|
||
}
|
||
rule := secrules.SecurityRule{
|
||
Direction: secrules.DIR_IN,
|
||
Action: secrules.SecurityRuleAllow,
|
||
Description: secgrouprule.Description,
|
||
Priority: 1,
|
||
}
|
||
if utils.IsInStringArray(secgrouprule.Protocol, []string{"", "0", "any"}) {
|
||
rule.Protocol = secrules.PROTO_ANY
|
||
} else if utils.IsInStringArray(secgrouprule.Protocol, []string{"6", "tcp"}) {
|
||
rule.Protocol = secrules.PROTO_TCP
|
||
} else if utils.IsInStringArray(secgrouprule.Protocol, []string{"17", "udp"}) {
|
||
rule.Protocol = secrules.PROTO_UDP
|
||
} else if utils.IsInStringArray(secgrouprule.Protocol, []string{"1", "icmp"}) {
|
||
rule.Protocol = secrules.PROTO_ICMP
|
||
} else {
|
||
return rules
|
||
}
|
||
if secgrouprule.Direction == "egress" {
|
||
rule.Direction = secrules.DIR_OUT
|
||
}
|
||
if len(secgrouprule.RemoteIpPrefix) == 0 {
|
||
secgrouprule.RemoteIpPrefix = "0.0.0.0/0"
|
||
}
|
||
_, ipnet, err := net.ParseCIDR(secgrouprule.RemoteIpPrefix)
|
||
if err != nil {
|
||
return rules
|
||
}
|
||
rule.IPNet = ipnet
|
||
if secgrouprule.PortRangeMax > 0 && secgrouprule.PortRangeMin > 0 {
|
||
if secgrouprule.PortRangeMax == secgrouprule.PortRangeMin {
|
||
rule.Ports = []int{secgrouprule.PortRangeMax}
|
||
} else {
|
||
rule.PortStart = secgrouprule.PortRangeMin
|
||
rule.PortEnd = secgrouprule.PortRangeMax
|
||
}
|
||
}
|
||
if err := rule.ValidateRule(); err != nil {
|
||
return rules
|
||
}
|
||
return []secrules.SecurityRule{rule}
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) GetRules() ([]secrules.SecurityRule, error) {
|
||
rules := []secrules.SecurityRule{}
|
||
priority := 100
|
||
for _, rule := range secgroup.SecurityGroupRules {
|
||
if priority < 2 {
|
||
priority = 2
|
||
}
|
||
subRules := rule.toRules()
|
||
for _, subRule := range subRules {
|
||
subRule.Priority = priority
|
||
rules = append(rules, subRule)
|
||
}
|
||
}
|
||
defaultDenyRule := secrules.MustParseSecurityRule("out:deny any")
|
||
defaultDenyRule.Priority = 1
|
||
rules = append(rules, *defaultDenyRule)
|
||
return rules, nil
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) GetStatus() string {
|
||
return ""
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) IsEmulated() bool {
|
||
return false
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) Refresh() error {
|
||
new, err := secgroup.region.GetSecurityGroup(secgroup.ID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return jsonutils.Update(secgroup, new)
|
||
}
|
||
|
||
func (region *SRegion) SyncSecurityGroup(secgroupId string, vpcId string, name string, desc string, rules []secrules.SecurityRule) (string, error) {
|
||
if len(secgroupId) > 0 {
|
||
_, err := region.GetSecurityGroup(secgroupId)
|
||
if err != nil {
|
||
if err != cloudprovider.ErrNotFound {
|
||
return "", err
|
||
}
|
||
secgroupId = ""
|
||
}
|
||
}
|
||
if len(secgroupId) == 0 {
|
||
secgroups, err := region.GetSecurityGroups("")
|
||
if err != nil {
|
||
// 若返回 cloudprovider.ErrNotFound, 表明不支持安全组或者未安装安全组相关组件
|
||
if err == cloudprovider.ErrNotFound {
|
||
return SECGROUP_NOT_SUPPORT, nil
|
||
}
|
||
log.Errorf("failed to get secgroups: %v", err)
|
||
return "", err
|
||
}
|
||
|
||
secgroupNames := []string{}
|
||
for _, secgroup := range secgroups {
|
||
secgroupNames = append(secgroupNames, strings.ToLower(secgroup.Name))
|
||
}
|
||
|
||
uniqName := strings.ToLower(name)
|
||
if utils.IsInStringArray(uniqName, secgroupNames) {
|
||
for i := 0; i < 20; i++ {
|
||
uniqName = fmt.Sprintf("%s-%d", strings.ToLower(name), i)
|
||
if !utils.IsInStringArray(uniqName, secgroupNames) {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
log.Errorf("create secgroup %s", uniqName)
|
||
secgroup, err := region.CreateSecurityGroup(uniqName, desc)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
secgroupId = secgroup.ID
|
||
}
|
||
return region.syncSecgroupRules(secgroupId, rules)
|
||
}
|
||
|
||
func (region *SRegion) syncSecgroupRules(secgroupId string, rules []secrules.SecurityRule) (string, error) {
|
||
secgroup, err := region.GetSecurityGroup(secgroupId)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// OpenStack仅支持allow规则添加,需要将规则全转换为allow rules
|
||
inRules, outRules := secrules.SecurityRuleSet{}, secrules.SecurityRuleSet{}
|
||
for i := 0; i < len(rules); i++ {
|
||
if rules[i].Direction == secrules.DIR_IN {
|
||
inRules = append(inRules, rules[i])
|
||
} else {
|
||
outRules = append(outRules, rules[i])
|
||
}
|
||
}
|
||
|
||
// OpenStack Out方向默认是禁止所有流量,需要给本地安全组规则加一条优先级最低的allow any规则,和OpenStack规则语义保持一致
|
||
defaultAllow := secrules.MustParseSecurityRule("out:allow any")
|
||
defaultAllow.Priority = 0
|
||
outRules = append(outRules, *defaultAllow)
|
||
|
||
rules = inRules.AllowList()
|
||
rules = append(rules, outRules.AllowList()...)
|
||
|
||
sort.Sort(secrules.SecurityRuleSet(rules))
|
||
sort.Sort(SecurigyGroupRuleSet(secgroup.SecurityGroupRules))
|
||
|
||
delSecgroupRuleIds := []string{}
|
||
addSecgroupRules := []secrules.SecurityRule{}
|
||
addSecgroupRuleStrings := []string{}
|
||
|
||
i, j := 0, 0
|
||
for i < len(rules) || j < len(secgroup.SecurityGroupRules) {
|
||
if i < len(rules) && j < len(secgroup.SecurityGroupRules) {
|
||
secruleStr := secgroup.SecurityGroupRules[j].String()
|
||
ruleStr := rules[i].String()
|
||
cmp := strings.Compare(secruleStr, ruleStr)
|
||
if cmp == 0 {
|
||
i++
|
||
j++
|
||
} else if cmp > 0 {
|
||
delSecgroupRuleIds = append(delSecgroupRuleIds, secgroup.SecurityGroupRules[j].ID)
|
||
j++
|
||
} else {
|
||
if !utils.IsInStringArray(ruleStr, addSecgroupRuleStrings) {
|
||
addSecgroupRules = append(addSecgroupRules, rules[i])
|
||
addSecgroupRuleStrings = append(addSecgroupRuleStrings, ruleStr)
|
||
}
|
||
i++
|
||
}
|
||
} else if i >= len(rules) {
|
||
delSecgroupRuleIds = append(delSecgroupRuleIds, secgroup.SecurityGroupRules[j].ID)
|
||
j++
|
||
} else if j >= len(secgroup.SecurityGroupRules) {
|
||
ruleStr := rules[i].String()
|
||
if !utils.IsInStringArray(ruleStr, addSecgroupRuleStrings) {
|
||
addSecgroupRules = append(addSecgroupRules, rules[i])
|
||
addSecgroupRuleStrings = append(addSecgroupRuleStrings, ruleStr)
|
||
}
|
||
i++
|
||
}
|
||
}
|
||
|
||
for _, ruleId := range delSecgroupRuleIds {
|
||
if err := region.delSecurityGroupRule(ruleId); err != nil {
|
||
log.Errorf("delSecurityGroupRule error %v", err)
|
||
return "", err
|
||
}
|
||
}
|
||
for i := 0; i < len(addSecgroupRules); i++ {
|
||
if err := region.addSecurityGroupRules(secgroupId, &addSecgroupRules[i]); err != nil {
|
||
if jsonError, ok := err.(*httputils.JSONClientError); ok {
|
||
if jsonError.Class == "SecurityGroupRuleExists" {
|
||
continue
|
||
}
|
||
}
|
||
log.Errorf("addSecurityGroupRule error %v", rules[i])
|
||
return "", err
|
||
}
|
||
}
|
||
|
||
return secgroupId, nil
|
||
}
|
||
|
||
func (region *SRegion) delSecurityGroupRule(ruleId string) error {
|
||
_, err := region.Delete("network", "/v2.0/security-group-rules/"+ruleId, "")
|
||
return err
|
||
}
|
||
|
||
func (region *SRegion) addSecurityGroupRules(secgroupId string, rule *secrules.SecurityRule) error {
|
||
if rule.Action == secrules.SecurityRuleDeny {
|
||
// openstack 不支持deny规则
|
||
return nil
|
||
}
|
||
direction := "ingress"
|
||
if rule.Direction == secrules.SecurityRuleEgress {
|
||
direction = "egress"
|
||
}
|
||
|
||
if rule.Protocol == secrules.PROTO_ANY {
|
||
rule.Protocol = "0"
|
||
}
|
||
|
||
params := map[string]map[string]interface{}{
|
||
"security_group_rule": {
|
||
"direction": direction,
|
||
"protocol": rule.Protocol,
|
||
"security_group_id": secgroupId,
|
||
"remote_ip_prefix": rule.IPNet.String(),
|
||
},
|
||
}
|
||
if len(rule.Ports) > 0 {
|
||
for _, port := range rule.Ports {
|
||
params["security_group_rule"]["port_range_max"] = port
|
||
params["security_group_rule"]["port_range_min"] = port
|
||
_, _, err := region.Post("network", "/v2.0/security-group-rules", "", jsonutils.Marshal(params))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
if rule.PortEnd > 0 && rule.PortStart > 0 {
|
||
params["security_group_rule"]["port_range_min"] = rule.PortStart
|
||
params["security_group_rule"]["port_range_max"] = rule.PortEnd
|
||
}
|
||
_, _, err := region.Post("network", "/v2.0/security-group-rules", "", jsonutils.Marshal(params))
|
||
return err
|
||
}
|
||
|
||
func (region *SRegion) DeleteSecurityGroup(secGroupId string) error {
|
||
_, err := region.Delete("network", "/v2.0/security-groups/"+secGroupId, "")
|
||
return err
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) Delete() error {
|
||
return secgroup.region.DeleteSecurityGroup(secgroup.ID)
|
||
}
|
||
|
||
func (region *SRegion) CreateSecurityGroup(name, description string) (*SSecurityGroup, error) {
|
||
params := map[string]map[string]interface{}{
|
||
"security_group": {
|
||
"name": name,
|
||
"description": description,
|
||
},
|
||
}
|
||
_, resp, err := region.Post("network", "/v2.0/security-groups", "", jsonutils.Marshal(params))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
secgroup := &SSecurityGroup{region: region}
|
||
return secgroup, resp.Unmarshal(secgroup, "security_group")
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) GetProjectId() string {
|
||
return secgroup.TenantID
|
||
}
|
||
|
||
func (secgroup *SSecurityGroup) SyncRules(rules []secrules.SecurityRule) error {
|
||
_, err := secgroup.region.syncSecgroupRules(secgroup.ID, rules)
|
||
return err
|
||
}
|