Files
cloudpods/pkg/multicloud/google/securitygroup.go
2020-02-10 18:43:47 +08:00

505 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 google
import (
"fmt"
"net"
"sort"
"strconv"
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/regutils"
"yunion.io/x/pkg/util/secrules"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
const (
SECGROUP_TYPE_SERVICE_ACCOUNT = "serviceAccount"
SECGROUP_TYPE_TAG = "tag"
)
type SFirewallAction struct {
IPProtocol string
Ports []string
}
type SFirewall struct {
Id string
CreationTimestamp time.Time
Name string
Description string
Network string
Priority int
SourceRanges []string
DestinationRanges []string
TargetServiceAccounts []string
TargetTags []string
Allowed []SFirewallAction
Denied []SFirewallAction
Direction string
Disabled bool
SelfLink string
Kind string
}
type FirewallSet []SFirewall
func (f FirewallSet) Len() int {
return len(f)
}
func (f FirewallSet) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
func (f FirewallSet) Less(i, j int) bool {
if f[i].Priority != f[j].Priority {
return f[i].Priority < f[j].Priority
}
return len(f[i].Allowed) < len(f[j].Allowed)
}
type SSecurityGroup struct {
vpc *SVpc
ServiceAccount string
Tag string
}
func (region *SRegion) GetFirewalls(network string, maxResults int, pageToken string) ([]SFirewall, error) {
firewalls := []SFirewall{}
params := map[string]string{"filter": "disabled = false"}
resource := "global/firewalls"
if len(network) > 0 {
params["filter"] = fmt.Sprintf(`(disabled = false) AND (network="%s")`, network)
}
return firewalls, region.List(resource, params, maxResults, pageToken, &firewalls)
}
func (region *SRegion) GetFirewall(id string) (*SFirewall, error) {
firewall := &SFirewall{}
return firewall, region.Get(id, firewall)
}
func (firewall *SFirewall) _toRules(action secrules.TSecurityRuleAction) ([]secrules.SecurityRule, error) {
rules := []secrules.SecurityRule{}
list := firewall.Allowed
if action == secrules.SecurityRuleDeny {
list = firewall.Denied
}
for _, allow := range list {
rule := secrules.SecurityRule{
Action: action,
Direction: secrules.DIR_IN,
Description: firewall.SelfLink,
Priority: firewall.Priority,
}
if firewall.Direction == "EGRESS" {
rule.Direction = secrules.DIR_OUT
}
switch allow.IPProtocol {
case "tcp", "udp", "icmp":
rule.Protocol = allow.IPProtocol
case "all":
rule.Protocol = secrules.PROTO_ANY
default:
return nil, fmt.Errorf("unsupport protocol %s", allow.IPProtocol)
}
ipRanges := firewall.SourceRanges
if rule.Direction == secrules.DIR_OUT {
ipRanges = firewall.DestinationRanges
}
for _, ipRange := range ipRanges {
if regutils.MatchCIDR(ipRange) {
_, rule.IPNet, _ = net.ParseCIDR(ipRange)
} else {
rule.IPNet = &net.IPNet{
IP: net.ParseIP(ipRange),
Mask: net.CIDRMask(32, 32),
}
}
ports := []int{}
for _, port := range allow.Ports {
if strings.Index(port, "-") > 0 {
err := rule.ParsePorts(port)
if err != nil {
return nil, errors.Wrapf(err, "Parse port %s", port)
}
rules = append(rules, rule)
} else {
_port, err := strconv.Atoi(port)
if err != nil {
return nil, errors.Wrapf(err, "Atio port %s", port)
}
ports = append(ports, _port)
}
}
if len(ports) > 0 {
rule.Ports = ports
rule.PortStart = -1
rule.PortEnd = -1
rules = append(rules, rule)
}
if len(allow.Ports) == 0 {
rule.Ports = []int{}
rule.PortStart = -1
rule.PortEnd = -1
rules = append(rules, rule)
}
}
}
return rules, nil
}
func (firewall *SFirewall) toRules() ([]secrules.SecurityRule, error) {
rules := []secrules.SecurityRule{}
_rules, err := firewall._toRules(secrules.SecurityRuleAllow)
if err != nil {
return nil, err
}
rules = append(rules, _rules...)
_rules, err = firewall._toRules(secrules.SecurityRuleDeny)
if err != nil {
return nil, err
}
rules = append(rules, _rules...)
return rules, nil
}
func (secgroup *SSecurityGroup) GetId() string {
return secgroup.vpc.globalnetwork.GetGlobalId()
}
func (secgroup *SSecurityGroup) GetGlobalId() string {
if len(secgroup.Tag) > 0 {
return fmt.Sprintf("%s/%s/%s", secgroup.GetId(), SECGROUP_TYPE_TAG, secgroup.Tag)
}
if len(secgroup.ServiceAccount) > 0 {
return fmt.Sprintf("%s/%s/%s", secgroup.GetId(), SECGROUP_TYPE_SERVICE_ACCOUNT, secgroup.ServiceAccount)
}
return secgroup.GetId()
}
func (secgroup *SSecurityGroup) GetDescription() string {
return ""
}
func (secgroup *SSecurityGroup) GetName() string {
if len(secgroup.Tag) > 0 {
return secgroup.Tag
}
if len(secgroup.ServiceAccount) > 0 {
return secgroup.ServiceAccount
}
return secgroup.vpc.globalnetwork.Name
}
func (secgroup *SSecurityGroup) GetMetadata() *jsonutils.JSONDict {
return nil
}
func (secgroup *SSecurityGroup) GetStatus() string {
return ""
}
func (secgroup *SSecurityGroup) IsEmulated() bool {
return false
}
func (secgroup *SSecurityGroup) Refresh() error {
return nil
}
func (secgroup *SSecurityGroup) Delete() error {
rules, err := secgroup.GetRules()
if err != nil {
return errors.Wrap(err, "GetRules")
}
for _, rule := range rules {
err = secgroup.vpc.region.DeleteSecgroupRule(rule.Description, rule)
if err != nil {
return errors.Wrapf(err, "DeleteSecgroupRule(%s)", rule.Description)
}
}
return nil
}
func (secgroup *SSecurityGroup) GetProjectId() string {
return ""
}
func (secgroup *SSecurityGroup) GetVpcId() string {
return secgroup.vpc.GetGlobalId()
}
func (self *SSecurityGroup) GetRules() ([]secrules.SecurityRule, error) {
_firewalls, err := self.vpc.region.GetFirewalls(self.vpc.globalnetwork.SelfLink, 0, "")
if err != nil {
return nil, err
}
firewalls := []SFirewall{}
for _, firewall := range _firewalls {
if len(self.Tag) > 0 && utils.IsInStringArray(self.Tag, firewall.TargetTags) {
firewalls = append(firewalls, firewall)
} else if len(self.ServiceAccount) > 0 && utils.IsInStringArray(self.ServiceAccount, firewall.TargetServiceAccounts) {
firewalls = append(firewalls, firewall)
} else {
if len(self.Tag) == 0 && len(self.ServiceAccount) == 0 && len(firewall.TargetServiceAccounts) == 0 && len(firewall.TargetTags) == 0 {
firewalls = append(firewalls, firewall)
}
}
}
sort.Sort(FirewallSet(firewalls))
rules := []secrules.SecurityRule{}
priority := 100
for _, firewall := range firewalls {
firewall.Priority = priority
if priority > 2 {
priority--
}
_rules, err := firewall.toRules()
if err != nil {
return nil, err
}
rules = append(rules, _rules...)
}
return rules, nil
}
func (region *SRegion) DeleteSecgroupRule(ruleId string, rule secrules.SecurityRule) error {
firwall, err := region.GetFirewall(ruleId)
if err != nil {
return errors.Wrap(err, "region.GetFirewall")
}
currentRule, err := firwall.toRules()
if err != nil {
return errors.Wrap(err, "firwall.toRules")
}
if len(currentRule) > 1 {
for _, _rule := range currentRule {
if _rule.String() != rule.String() {
for _, tag := range firwall.TargetTags {
err = region.CreateSecurityGroupRule(_rule, firwall.Network, tag, "")
if err != nil {
return errors.Wrap(err, "region.CreateSecurityGroupRule")
}
}
for _, serviceAccount := range firwall.TargetServiceAccounts {
err = region.CreateSecurityGroupRule(_rule, firwall.Network, "", serviceAccount)
if err != nil {
return errors.Wrap(err, "region.CreateSecurityGroupRule")
}
}
if len(firwall.TargetTags)+len(firwall.TargetServiceAccounts) == 0 {
err = region.CreateSecurityGroupRule(_rule, firwall.Network, "", "")
if err != nil {
return errors.Wrap(err, "region.CreateSecurityGroupRule")
}
}
}
}
}
return region.Delete(firwall.SelfLink)
}
func (secgroup *SSecurityGroup) SyncRules(rules []secrules.SecurityRule) error {
if len(rules) == 0 {
rules = append(rules, *secrules.MustParseSecurityRule("in:deny any"))
}
currentRule, err := secgroup.GetRules()
if err != nil {
return errors.Wrap(err, "secgroup.GetRules")
}
sort.Sort(secrules.SecurityRuleSet(rules))
region := secgroup.vpc.region
deleteRules := map[string]secrules.SecurityRule{}
addRules := []secrules.SecurityRule{}
i, j := 0, 0
for i < len(rules) || j < len(currentRule) {
if i < len(rules) && j < len(currentRule) {
currentRuleStr := currentRule[j].String()
ruleStr := rules[i].String()
cmp := strings.Compare(currentRuleStr, ruleStr)
if cmp == 0 {
i += 1
j += 1
} else if cmp > 0 {
// delete rule
deleteRules[currentRule[j].Description] = currentRule[j]
j += 1
} else {
rules[i].Priority = 101 - rules[i].Priority
addRules = append(addRules, rules[i])
i += 1
}
} else if i >= len(rules) {
// delete rule
deleteRules[currentRule[j].Description] = currentRule[j]
j += 1
} else if j >= len(currentRule) {
// add rule
rules[i].Priority = 101 - rules[i].Priority
addRules = append(addRules, rules[i])
err = region.CreateSecurityGroupRule(rules[i], secgroup.vpc.globalnetwork.SelfLink, secgroup.Tag, secgroup.ServiceAccount)
if err != nil {
return errors.Wrapf(err, "region.CreateSecurityGroupRule(%s)", rules[i].String())
}
i += 1
}
}
for id, rule := range deleteRules {
err = region.DeleteSecgroupRule(id, rule)
if err != nil {
return errors.Wrapf(err, "DeleteSecgroupRule(%s)", id)
}
}
for _, rule := range addRules {
err = region.CreateSecurityGroupRule(rule, secgroup.vpc.globalnetwork.SelfLink, secgroup.Tag, secgroup.ServiceAccount)
if err != nil {
return errors.Wrapf(err, "CreateSecurityGroupRule(%s)", rule.String())
}
}
return nil
}
func (region *SRegion) GetISecurityGroupById(id string) (cloudprovider.ICloudSecurityGroup, error) {
vpcs, err := region.GetIVpcs()
if err != nil {
return nil, errors.Wrap(err, "GetIVpcs")
}
for _, vpc := range vpcs {
secgroups, err := vpc.GetISecurityGroups()
if err != nil {
return nil, errors.Wrap(err, "GetISecurityGroups")
}
for _, secgroup := range secgroups {
if secgroup.GetGlobalId() == id {
return secgroup, nil
}
}
}
return nil, cloudprovider.ErrNotFound
}
func (region *SRegion) GetISecurityGroupByName(vpcId string, name string) (cloudprovider.ICloudSecurityGroup, error) {
ivpc, err := region.GetIVpcById(vpcId)
if err != nil {
return nil, err
}
secgroups, err := ivpc.GetISecurityGroups()
if err != nil {
return nil, errors.Wrap(err, "ivpc.GetISecurityGroups")
}
for _, secgroup := range secgroups {
if strings.ToLower(secgroup.GetName()) == strings.ToLower(name) {
return secgroup, nil
}
}
return nil, cloudprovider.ErrNotFound
}
func (region *SRegion) CreateSecurityGroupRule(rule secrules.SecurityRule, vpcId string, tag string, serviceAccount string) error {
name := fmt.Sprintf("%s-%d", rule.String(), rule.Priority)
if len(tag) > 0 {
name = fmt.Sprintf("for-tag-%s-%s", tag, name)
}
if len(serviceAccount) > 0 {
name = fmt.Sprintf("for-service-account-%s-%s", serviceAccount, name)
}
body := map[string]interface{}{
"name": strings.ToLower(name),
"priority": rule.Priority,
"network": vpcId,
"direction": "INGRESS",
}
if len(tag) > 0 {
body["targetTags"] = []string{strings.ToLower(tag)}
}
if len(serviceAccount) > 0 {
body["targetServiceAccounts"] = []string{serviceAccount}
}
if rule.Direction == secrules.DIR_OUT {
body["direction"] = "EGRESS"
} else {
body["sourceRanges"] = []string{rule.IPNet.String()}
}
protocol := string(rule.Protocol)
if protocol == secrules.PROTO_ANY {
protocol = "all"
}
ports := []string{}
if len(rule.Ports) > 0 {
for _, port := range rule.Ports {
ports = append(ports, fmt.Sprintf("%d", port))
}
} else if rule.PortStart > 0 && rule.PortEnd > 0 {
if rule.PortStart == rule.PortEnd {
ports = append(ports, fmt.Sprintf("%d", rule.PortStart))
} else {
ports = append(ports, fmt.Sprintf("%d-%d", rule.PortStart, rule.PortEnd))
}
}
actionInfo := []struct {
IPProtocol string
Ports []string
}{
{
IPProtocol: protocol,
Ports: ports,
},
}
if rule.Action == secrules.SecurityRuleDeny {
body["denied"] = actionInfo
} else {
body["allowed"] = actionInfo
}
firwall := &SFirewall{}
err := region.Insert("global/firewalls", jsonutils.Marshal(body), firwall)
if err != nil {
if strings.Index(err.Error(), "already exists") >= 0 {
return nil
}
return errors.Wrap(err, "region.Insert")
}
return nil
}
func (region *SRegion) CreateISecurityGroup(conf *cloudprovider.SecurityGroupCreateInput) (cloudprovider.ICloudSecurityGroup, error) {
conf.VpcId = fmt.Sprintf("%s/%s", region.GetGlobalId(), conf.VpcId)
ivpc, err := region.GetIVpcById(conf.VpcId)
if err != nil {
return nil, errors.Wrapf(err, "region.GetIVpcById(%s)", conf.VpcId)
}
vpc := ivpc.(*SVpc)
secgroup := &SSecurityGroup{vpc: vpc, Tag: strings.ToLower(conf.Name)}
return secgroup, nil
}