Files
cloudpods/pkg/multicloud/openstack/securitygroup.go

460 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}