pkg/skoop/network/aliyun/assertion.go (755 lines of code) (raw):

package aliyun import ( "fmt" "net" "strconv" "strings" "github.com/alibaba/kubeskoop/pkg/skoop/network" slb "github.com/alibabacloud-go/slb-20140515/v4/client" "github.com/alibaba/kubeskoop/pkg/skoop/infra/aliyun" "github.com/alibaba/kubeskoop/pkg/skoop/model" ecs "github.com/alibabacloud-go/ecs-20140526/v2/client" vpc "github.com/alibabacloud-go/vpc-20160428/v2/client" "github.com/samber/lo" "golang.org/x/exp/slices" "k8s.io/klog/v2" ) type securityPolicyVerdict string const ( securityPolicyVerdictAccept securityPolicyVerdict = "Accept" securityPolicyVerdictDrop securityPolicyVerdict = "Drop" ) type vpcAssertion struct { cloudManager *aliyun.CloudManager } func newVPCAssertion(cloudManager *aliyun.CloudManager) (*vpcAssertion, error) { if cloudManager == nil { return nil, fmt.Errorf("cloud manager must be provided") } return &vpcAssertion{ cloudManager: cloudManager, }, nil } func (a *vpcAssertion) AssertSecurityGroup(srcECS, dstECS string, pkt *model.Packet) ([]model.Suspicion, error) { if srcECS == "" && dstECS == "" { return nil, nil } var suspicions []model.Suspicion if srcECS != "" && dstECS != "" { srcECSInfo, err := a.cloudManager.GetECSInfo(srcECS) if err != nil { return nil, err } dstECSInfo, err := a.cloudManager.GetECSInfo(dstECS) if err != nil { return nil, err } var srcECSSecurityGroup map[string]aliyun.SecurityGroupRule srcENI := a.getENIFromIP(srcECSInfo, pkt.Src.String()) if srcENI != nil { srcECSSecurityGroup = srcENI.SecurityGroups } else { srcECSSecurityGroup = srcECSInfo.Network.SecurityGroups } var dstECSSecurityGroup map[string]aliyun.SecurityGroupRule dstENI := a.getENIFromIP(dstECSInfo, pkt.Dst.String()) if dstENI != nil { dstECSSecurityGroup = dstENI.SecurityGroups } else { dstECSSecurityGroup = dstECSInfo.Network.SecurityGroups } sgIntersection := a.intersection(srcECSSecurityGroup, dstECSSecurityGroup) if len(sgIntersection) == 0 { suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelWarning, Message: fmt.Sprintf("%s(%s) and %s(%s) do not have same security group", srcECS, a.getECSIP(srcECSInfo), dstECS, a.getECSIP(dstECSInfo)), }) } // eni 只能绑定一个类型sg // sg 相交场景,且相交的是普通安全组,默认策略为 Allow // sg 相交场景,且为企业安全组,默认策略 Deny // sg 不相交,默认 Deny sgPolicyVerdict := securityPolicyVerdictDrop if len(sgIntersection) > 0 { if sgIntersection[0].Type == aliyun.SecurityGroupTypeNormal { sgPolicyVerdict = securityPolicyVerdictAccept } checkResult, err := a.checkSourceOut(pkt, srcECSSecurityGroup) if err != nil { return nil, err } if !checkResult { sgIDs := lo.MapToSlice(srcECSSecurityGroup, func(id string, _ aliyun.SecurityGroupRule) string { return id }) suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("%s(%s) security group %v not allow packet to %s:%d", srcECS, a.getECSIP(srcECSInfo), sgIDs, pkt.Dst.String(), pkt.Dport), }) } if !slices.Contains(srcECSInfo.Network.IP, pkt.Src.String()) || len(sgIntersection) == 0 || (len(sgIntersection) > 0 && srcECSSecurityGroup[sgIntersection[0].ID].Type == "enterprise") { checkResult, err := a.checkDestinationIn(pkt, dstECSSecurityGroup, sgPolicyVerdict) if err != nil { return nil, err } if !checkResult { sgIDs := lo.MapToSlice(dstECSSecurityGroup, func(id string, _ aliyun.SecurityGroupRule) string { return id }) suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("%s(%s) security group %v not allow packet from %s to port %d", dstECS, a.getECSIP(dstECSInfo), sgIDs, pkt.Src.String(), pkt.Dport), }) } } } } else if srcECS != "" && dstECS == "" { srcECSInfo, err := a.cloudManager.GetECSInfo(srcECS) if err != nil { return nil, err } srcENI := a.getENIFromIP(srcECSInfo, pkt.Src.String()) var srcECSSecurityGroup map[string]aliyun.SecurityGroupRule if srcENI == nil { srcECSSecurityGroup = srcECSInfo.Network.SecurityGroups } else { srcECSSecurityGroup = srcENI.SecurityGroups } checkResult, err := a.checkSourceOut(pkt, srcECSSecurityGroup) if err != nil { return nil, err } if !checkResult { sgIDs := lo.Keys(srcECSSecurityGroup) suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("%s(%s) security group %v not allow packet from to %s:%d", srcECS, a.getECSIP(srcECSInfo), sgIDs, pkt.Dst.String(), pkt.Dport), }) } } else if srcECS == "" && dstECS != "" { dstECSInfo, err := a.cloudManager.GetECSInfo(dstECS) if err != nil { return nil, err } dstENI := a.getENIFromIP(dstECSInfo, pkt.Dst.String()) var dstECSSecurityGroup map[string]aliyun.SecurityGroupRule if dstENI == nil { dstECSSecurityGroup = dstECSInfo.Network.SecurityGroups } else { dstECSSecurityGroup = dstENI.SecurityGroups } sgs := lo.Values(dstECSSecurityGroup) if len(sgs) > 0 { sgPolicyVerdict := securityPolicyVerdictDrop checkResult, err := a.checkDestinationIn(pkt, dstECSSecurityGroup, sgPolicyVerdict) if err != nil { return nil, err } if !checkResult { sgIDs := lo.MapToSlice(dstECSSecurityGroup, func(id string, _ aliyun.SecurityGroupRule) string { return id }) suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("%s(%s) security group %v not allow packet from %s to port %d", dstECS, a.getECSIP(dstECSInfo), sgIDs, pkt.Src.String(), pkt.Dport), }) } } } return suspicions, nil } func (a *vpcAssertion) AssertRoute(srcECS, dstECS string, pkt *model.Packet, privateIP string) ([]model.Suspicion, error) { var suspicions []model.Suspicion var routeEntries []*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry var srcECSInfo, dstECSInfo *aliyun.ECSInfo var err error if srcECS == "" && dstECS == "" { return nil, nil } if srcECS != "" { srcECSInfo, err = a.cloudManager.GetECSInfo(srcECS) if err != nil { return nil, err } routeEntries = srcECSInfo.Network.RouteTableEntries if !slices.Contains(srcECSInfo.Network.IP, pkt.Src.String()) { routeEntries = srcECSInfo.Network.VpcDefaultRouteTableEntries } lo.ForEach(srcECSInfo.Network.NetworkInterfaces, func(ni *aliyun.ENIInfo, _ int) { lo.ForEach(ni.NetworkInterfaceSet.PrivateIpSets.PrivateIpSet, func(ip *ecs.DescribeNetworkInterfacesResponseBodyNetworkInterfaceSetsNetworkInterfaceSetPrivateIpSetsPrivateIpSet, _ int) { if ip.PrivateIpAddress != nil && *ip.PrivateIpAddress == pkt.Src.String() { routeEntries = ni.RouteTableEntries } }) }) } if dstECS != "" { dstECSInfo, err = a.cloudManager.GetECSInfo(dstECS) if err != nil { return nil, err } if srcECS == "" { routeEntries = dstECSInfo.Network.VpcDefaultRouteTableEntries } } dstRouteEntry, err := routeMatchPacket(pkt.Dst.String(), routeEntries) if err != nil { return nil, err } if dstRouteEntry == nil { suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("no route entry for destination ip %q", pkt.Dst), }) } if dstECS != "" && dstRouteEntry != nil && !slices.Contains(dstECSInfo.Network.IP, pkt.Dst.String()) { // we do not route dst ip in ecs network ips // is there any situation that len(NextHop) == 0? nextHop := dstRouteEntry.NextHops.NextHop[0] if *nextHop.NextHopType != "local" && !(*nextHop.NextHopType == "Instance" && *nextHop.NextHopId == dstECS) { suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("error route next hop for destination ip \"%s\", expect: \"Instance-%s\", actually: \"%s-%s\"", pkt.Dst.String(), dstECS, *nextHop.NextHopType, *nextHop.NextHopId), }) } } // reverse path if srcECS != "" { var srcRouteEntry *vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry if dstECS != "" { if !slices.Contains(dstECSInfo.Network.IP, pkt.Dst.String()) { routeEntries = dstECSInfo.Network.VpcDefaultRouteTableEntries lo.ForEach(dstECSInfo.Network.NetworkInterfaces, func(ni *aliyun.ENIInfo, _ int) { lo.ForEach(ni.NetworkInterfaceSet.PrivateIpSets.PrivateIpSet, func(ip *ecs.DescribeNetworkInterfacesResponseBodyNetworkInterfaceSetsNetworkInterfaceSetPrivateIpSetsPrivateIpSet, _ int) { if ip.PrivateIpAddress != nil && *ip.PrivateIpAddress == pkt.Dst.String() { routeEntries = ni.RouteTableEntries } }) }) } else { routeEntries = dstECSInfo.Network.RouteTableEntries } } else { routeEntries = srcECSInfo.Network.VpcDefaultRouteTableEntries } if privateIP != "" { srcRouteEntry, err = routeMatchPacket(privateIP, routeEntries) if err != nil { return nil, err } } else { srcRouteEntry, err = routeMatchPacket(pkt.Src.String(), routeEntries) } if err != nil { return nil, err } if srcRouteEntry == nil { suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("no route entry for src ip %q", pkt.Src.String()), }) } if srcRouteEntry != nil && !slices.Contains(srcECSInfo.Network.IP, pkt.Src.String()) { nextHop := srcRouteEntry.NextHops.NextHop[0] if nextHop.NextHopRegionId != nil && *nextHop.NextHopRegionId != "local" && !(*nextHop.NextHopType == "Instance" && *nextHop.NextHopId == srcECS) { suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("error route next hop for source ip: %q, expect: \"Instance-%s\", actual: \"%s-%s\"", pkt.Src, srcECSInfo.ID, *nextHop.NextHopType, *nextHop.NextHopId), }) } } } return suspicions, nil } func (a *vpcAssertion) AssertSNAT(srcECS string, pkt *model.Packet, privateIP string) ([]model.Suspicion, error) { var suspicions []model.Suspicion eniInfo, err := a.cloudManager.GetENIInfoFromVPCAndPrivateIP(a.cloudManager.VPC(), privateIP) if err != nil { return nil, err } vswitchID := *eniInfo.VSwitchId klog.V(3).Infof("vswitch id %s", vswitchID) nextHop, err := a.findNextHop(pkt, srcECS) if err != nil { return nil, err } if nextHop == nil { suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("no next hop for destination ip %q", pkt.Dst), }) return suspicions, nil } if *nextHop.NextHopType != "NatGateway" { suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("expect next hop for destination ip %q to be NatGateway, but \"%s-%s\"", pkt.Dst, *nextHop.NextHopType, *nextHop.NextHopId), }) return suspicions, nil } ngwID := *nextHop.NextHopId snatEntry, err := a.findSNATEntry(pkt, ngwID, vswitchID) if err != nil { return nil, err } if snatEntry == nil { suspicions = append(suspicions, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("no snat entry on nat gateway %q for destination ip %q", ngwID, pkt.Dst), }) } return suspicions, nil } func (a *vpcAssertion) getENIFromIP(ecsInfo *aliyun.ECSInfo, dstIP string) *aliyun.ENIInfo { for _, eni := range ecsInfo.Network.NetworkInterfaces { for _, privateIP := range eni.NetworkInterfaceSet.PrivateIpSets.PrivateIpSet { if *privateIP.PrivateIpAddress == dstIP { return eni } } } return nil } func (a *vpcAssertion) intersection(sga, sgb map[string]aliyun.SecurityGroupRule) []aliyun.SecurityGroupRule { existed := map[string]struct{}{} for _, sg := range sga { existed[sg.ID] = struct{}{} } var ret []aliyun.SecurityGroupRule for _, sg := range sgb { if _, ok := existed[sg.ID]; ok { ret = append(ret, sg) } } return ret } func (a *vpcAssertion) getECSIP(ecs *aliyun.ECSInfo) string { if len(ecs.Network.IP) == 0 { return "!NOT FOUND!" } return ecs.Network.IP[0] } func (a *vpcAssertion) checkSourceOut(pkt *model.Packet, sgs map[string]aliyun.SecurityGroupRule) (bool, error) { hasEnterpriseSg := false var outRules []*ecs.DescribeSecurityGroupAttributeResponseBodyPermissionsPermission for _, sg := range sgs { if sg.Type == "enterprise" { hasEnterpriseSg = true } outRules = append(outRules, sg.OutRule.Allows...) outRules = append(outRules, sg.OutRule.Drops...) } if hasEnterpriseSg { return a.packetPassRules(pkt, outRules, securityPolicyVerdictDrop) } return a.packetPassRules(pkt, outRules, securityPolicyVerdictAccept) } func (a *vpcAssertion) checkDestinationIn(pkt *model.Packet, sgs map[string]aliyun.SecurityGroupRule, defaultPolicy securityPolicyVerdict) (bool, error) { var inRules []*ecs.DescribeSecurityGroupAttributeResponseBodyPermissionsPermission for _, sg := range sgs { inRules = append(inRules, sg.InRule.Allows...) inRules = append(inRules, sg.InRule.Drops...) } if defaultPolicy == "" { defaultPolicy = securityPolicyVerdictDrop } return a.packetPassRules(pkt, inRules, defaultPolicy) } func (a *vpcAssertion) packetPassRules(pkt *model.Packet, outRules []*ecs.DescribeSecurityGroupAttributeResponseBodyPermissionsPermission, defaultPolicy securityPolicyVerdict) (bool, error) { var filteredRules []*ecs.DescribeSecurityGroupAttributeResponseBodyPermissionsPermission for _, rule := range outRules { match, err := ruleMatchPacket(pkt, rule) if err != nil { return false, err } if match { filteredRules = append(filteredRules, rule) } } sortSecurityGroupRules(filteredRules) if len(filteredRules) > 0 { return *filteredRules[0].Policy == string(securityPolicyVerdictAccept), nil } klog.V(3).Infof("No rule match for packet, default policy for SecurityGroup is %q", defaultPolicy) return defaultPolicy == securityPolicyVerdictAccept, nil } func (a *vpcAssertion) findNextHop(pkt *model.Packet, srcECS string) (*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntryNextHopsNextHop, error) { ecsInfo, err := a.cloudManager.GetECSInfo(srcECS) if err != nil { return nil, err } routeEntries := ecsInfo.Network.RouteTableEntries if !slices.Contains(ecsInfo.Network.IP, pkt.Src.String()) { routeEntries = ecsInfo.Network.VpcDefaultRouteTableEntries } lo.ForEach(ecsInfo.Network.NetworkInterfaces, func(ni *aliyun.ENIInfo, _ int) { lo.ForEach(ni.NetworkInterfaceSet.PrivateIpSets.PrivateIpSet, func(ip *ecs.DescribeNetworkInterfacesResponseBodyNetworkInterfaceSetsNetworkInterfaceSetPrivateIpSetsPrivateIpSet, _ int) { if ip.PrivateIpAddress != nil && *ip.PrivateIpAddress == pkt.Src.String() { routeEntries = ni.RouteTableEntries } }) }) route, err := routeMatchPacket(pkt.Dst.String(), routeEntries) if err != nil { return nil, err } return route.NextHops.NextHop[0], nil } func (a *vpcAssertion) findSNATEntry(pkt *model.Packet, ngwID, vswitchID string) (*vpc.DescribeSnatTableEntriesResponseBodySnatTableEntriesSnatTableEntry, error) { vswitch, err := a.cloudManager.GetVSwitch(vswitchID) if err != nil { return nil, err } ngw, err := a.cloudManager.GetNatGatewayInfo(ngwID) if err != nil { return nil, err } snatTables := ngw.SnatTableIds.SnatTableId var defaultEntry *vpc.DescribeSnatTableEntriesResponseBodySnatTableEntriesSnatTableEntry for _, t := range snatTables { snatEntries, err := a.cloudManager.GetSNATEntriesBySegment(*t, fmt.Sprintf("%s/32", pkt.Src)) if err != nil { return nil, err } if len(snatEntries) > 0 { defaultEntry = snatEntries[0] break } snatEntries, err = a.cloudManager.GetSNATEntriesBySegment(*t, *vswitch.CidrBlock) if err != nil { return nil, err } if len(snatEntries) > 0 { defaultEntry = snatEntries[0] } } return defaultEntry, nil } type slbAssertion struct { cloudManager *aliyun.CloudManager } func newSLBAssertion(cloudManager *aliyun.CloudManager) (*slbAssertion, error) { return &slbAssertion{ cloudManager: cloudManager, }, nil } func (a *slbAssertion) assertLoadBalancer(lb *slb.DescribeLoadBalancersResponseBodyLoadBalancersLoadBalancer, dport uint16, protocol model.Protocol) ([]model.Suspicion, error) { var sus []model.Suspicion if lb.LoadBalancerStatus != nil && *lb.LoadBalancerStatus != "active" { sus = append(sus, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("loadBalancer(%s) status is %q, not \"active\"", *lb.LoadBalancerId, *lb.LoadBalancerStatus), }) } status, err := a.cloudManager.GetSLBHealthStatus(*lb.LoadBalancerId, int32(dport), string(protocol)) if err != nil { return nil, err } if len(status) == 0 { return sus, nil } s := status[0] if s.ServerHealthStatus != nil && *s.ServerHealthStatus != "normal" { sus = append(sus, model.Suspicion{ Level: model.SuspicionLevelWarning, Message: fmt.Sprintf("backend %s health status for port %d is %q, not \"normal\"", *s.ServerIp, *s.Port, *s.ServerHealthStatus), }) } return sus, nil } func (a *slbAssertion) assertListenerAndServerGroup(lbID string, port int32, protocol string, packet *model.Packet, backends []network.LoadBalancerBackend) ([]model.Suspicion, error) { var sus []model.Suspicion lis, err := a.cloudManager.GetSLBListener(lbID, port, protocol) if err != nil { return nil, err } if lis == nil { sus = append(sus, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("cannot find listener port %d protocol %q for slb %q", port, protocol, lbID), }) return sus, nil } if lis.TCP != nil { if lis.TCP.Status != nil && *lis.TCP.Status != "running" { sus = append(sus, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("status of loadbalancer %q for tcp port %d is %q, not \"running\"", lbID, port, *lis.TCP.Status), }) } if lis.TCP.AclStatus != nil && *lis.TCP.AclStatus == "on" && lis.TCP.AclId != nil { s, err := a.assertACL(*lis.TCP.AclId, *lis.TCP.AclType, packet) if err != nil { return nil, err } sus = append(sus, s...) } if lis.TCP.VServerGroupId != nil && *lis.TCP.VServerGroupId != "" { s, err := a.assertServerGroup(*lis.TCP.VServerGroupId, backends) if err != nil { return nil, err } sus = append(sus, s...) } else { sus = append(sus, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("cannot find vserver group of loadbalancer %q port %d", lbID, port), }) } return sus, nil } if lis.UDP != nil { if lis.UDP.Status != nil && *lis.UDP.Status != "running" { sus = append(sus, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("status of loadbalancer %q for udp port %d is %q, not \"running\"", lbID, port, *lis.TCP.Status), }) } if lis.UDP.AclStatus != nil && *lis.UDP.AclStatus == "on" && lis.TCP.AclId != nil { s, err := a.assertACL(*lis.UDP.AclId, *lis.UDP.AclType, packet) if err != nil { return nil, err } sus = append(sus, s...) } if lis.UDP.VServerGroupId != nil && *lis.UDP.VServerGroupId != "" { s, err := a.assertServerGroup(*lis.UDP.VServerGroupId, backends) if err != nil { return nil, err } sus = append(sus, s...) } else { sus = append(sus, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("cannot find vserver group of loadbalancer %q port %d", lbID, port), }) } return sus, nil } return nil, fmt.Errorf("not supported protocol %s", protocol) } func (a *slbAssertion) assertACL(aclID, t string, packet *model.Packet) ([]model.Suspicion, error) { acl, err := a.cloudManager.GetACLFromID(aclID) if err != nil { return nil, err } if acl == nil { return nil, fmt.Errorf("cannot find acl %q", aclID) } // When there is no entries in an ACL, it will allow all packets to pass. if len(acl.AclEntrys.AclEntry) == 0 { return nil, nil } ipInACL := lo.ContainsBy(acl.AclEntrys.AclEntry, func(i *slb.DescribeAccessControlListAttributeResponseBodyAclEntrysAclEntry) bool { if i == nil || i.AclEntryIP == nil { return false } _, ipCIDR, _ := parseIPOrCIDR(*i.AclEntryIP) return ipCIDR.Contains(packet.Src) }) var sus []model.Suspicion if (t == "white" && !ipInACL) || (t == "black" && ipInACL) { sus = append(sus, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("acl %s type %s on loadbalancer does not allow packet to pass", aclID, t), }) } return sus, nil } func (a *slbAssertion) assertServerGroup(sgID string, backends []network.LoadBalancerBackend) ([]model.Suspicion, error) { sg, err := a.cloudManager.GetSLBVserverGroup(sgID) if err != nil { return nil, err } if sg == nil { return nil, fmt.Errorf("cannot find vserver group %q", sgID) } var sus []model.Suspicion if len(sg.BackendServers.BackendServer) == 0 { sus = append(sus, model.Suspicion{ Level: model.SuspicionLevelFatal, Message: fmt.Sprintf("server group %q has no backend", sgID), }) } if len(sg.BackendServers.BackendServer) != len(backends) { sus = append(sus, model.Suspicion{ Level: model.SuspicionLevelWarning, Message: fmt.Sprintf("backend count %d is not equal to expected backend count %d", len(sg.BackendServers.BackendServer), len(backends)), }) } for _, s := range sg.BackendServers.BackendServer { contains := lo.ContainsBy(backends, func(b network.LoadBalancerBackend) bool { if *s.Port != int32(b.Port) { return false } if s.ServerIp != nil && b.IP != "" { return *s.ServerIp == b.IP } if s.ServerId != nil && b.ID != "" { return *s.ServerId == b.ID } // If we're unable to find backend by the given information, // return true to prevent suspicions. return true }) if !contains { backend := "" if s.ServerIp != nil { backend = fmt.Sprintf("%s:%d", *s.ServerIp, *s.Port) } else if s.ServerId != nil { backend = fmt.Sprintf("%s:%d", *s.ServerId, *s.Port) } else { backend = fmt.Sprintf("(unknown):%d", *s.Port) } sus = append(sus, model.Suspicion{ Level: model.SuspicionLevelWarning, Message: fmt.Sprintf("backend %q in server group %q is unexpected", backend, sgID), }) } // limit suspicions count to not exceed 5 if len(sus) > 5 { break } } return sus, nil } func ruleMatchPacket(pkt *model.Packet, rule *ecs.DescribeSecurityGroupAttributeResponseBodyPermissionsPermission) (bool, error) { if rule.DestCidrIp != nil && *rule.DestCidrIp != "" { _, dstCidrIP, err := parseIPOrCIDR(*rule.DestCidrIp) if err != nil { return false, err } if dstCidrIP.Contains(pkt.Dst) { if *rule.IpProtocol == "ALL" || strings.EqualFold(string(pkt.Protocol), *rule.IpProtocol) { if pkt.Dport == 0 { return true, nil } portRange := strings.Split(*rule.PortRange, "/") // assert len(portRange) == 2 if portRange[0] == "-1" && portRange[1] == "-1" { return true, nil } pStart, err := strconv.Atoi(portRange[0]) if err != nil { return false, err } pEnd, err := strconv.Atoi(portRange[1]) if err != nil { return false, nil } if pStart <= int(pkt.Dport) && pEnd >= int(pkt.Dport) { return true, nil } } } } if rule.SourceCidrIp != nil && *rule.SourceCidrIp != "" { _, srcCidrIP, err := parseIPOrCIDR(*rule.SourceCidrIp) if err != nil { return false, err } if srcCidrIP.Contains(pkt.Src) { if *rule.IpProtocol == "ALL" || strings.EqualFold(string(pkt.Protocol), *rule.IpProtocol) { if pkt.Dport == 0 { return true, nil } portRange := strings.Split(*rule.PortRange, "/") // assert len(portRange) == 2 if portRange[0] == "-1" && portRange[1] == "-1" { return true, nil } pStart, err := strconv.Atoi(portRange[0]) if err != nil { return false, err } pEnd, err := strconv.Atoi(portRange[1]) if err != nil { return false, err } if pStart <= int(pkt.Dport) && pEnd >= int(pkt.Dport) { return true, nil } } } } return false, nil } func sortSecurityGroupRules(sgs []*ecs.DescribeSecurityGroupAttributeResponseBodyPermissionsPermission) { slices.SortStableFunc(sgs, func(a, b *ecs.DescribeSecurityGroupAttributeResponseBodyPermissionsPermission) bool { portRangeA := strings.Split(*a.PortRange, "/") pStartA, _ := strconv.Atoi(portRangeA[0]) pEndA, _ := strconv.Atoi(portRangeA[1]) if pStartA == -1 && pEndA == -1 { pStartA, pEndA = 0, 65535 } portRangeB := strings.Split(*b.PortRange, "/") pStartB, _ := strconv.Atoi(portRangeB[0]) pEndB, _ := strconv.Atoi(portRangeB[1]) if pStartB == -1 && pEndB == -1 { pStartB, pEndB = 0, 65535 } if *a.Priority != *b.Priority { return *a.Priority < *b.Priority } if (a.SourceCidrIp != nil && *a.SourceCidrIp != "") || (b.SourceCidrIp != nil && *b.SourceCidrIp != "") { if (a.SourceCidrIp == nil || *a.SourceCidrIp == "") || (b.SourceCidrIp == nil || *b.SourceCidrIp == "") { return a.SourceCidrIp != nil && *a.SourceCidrIp != "" } _, netA, _ := parseIPOrCIDR(*a.SourceCidrIp) onesA, _ := netA.Mask.Size() _, netB, _ := parseIPOrCIDR(*a.SourceCidrIp) onesB, _ := netB.Mask.Size() if onesA != onesB { return onesA > onesB } } if (a.DestCidrIp != nil && *a.DestCidrIp != "") || (b.DestCidrIp != nil && *b.DestCidrIp != "") { if (a.DestCidrIp == nil || *a.DestCidrIp == "") || (b.DestCidrIp == nil || *b.DestCidrIp == "") { return a.DestCidrIp != nil && *a.DestCidrIp != "" } _, netA, _ := parseIPOrCIDR(*a.DestCidrIp) onesA, _ := netA.Mask.Size() _, netB, _ := parseIPOrCIDR(*a.DestCidrIp) onesB, _ := netB.Mask.Size() if onesA != onesB { return onesA > onesB } } if *a.Policy != *b.Policy { return *a.Policy == string(securityPolicyVerdictDrop) } return (pEndA - pStartA) < (pEndB - pStartB) }) } func routeMatchPacket(ip string, routes []*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry) (*vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry, error) { type routesAndCIDR struct { entry *vpc.DescribeRouteEntryListResponseBodyRouteEntrysRouteEntry cidr *net.IPNet } var filteredRoutes []routesAndCIDR netIP := net.ParseIP(ip) for _, r := range routes { _, cidr, err := parseIPOrCIDR(*r.DestinationCidrBlock) if err != nil { return nil, fmt.Errorf("parse route table %q destination cidr error: %s", *r.RouteTableId, err) } if cidr.Contains(netIP) { filteredRoutes = append(filteredRoutes, routesAndCIDR{entry: r, cidr: cidr}) } } slices.SortFunc(filteredRoutes, func(a, b routesAndCIDR) bool { onesA, _ := a.cidr.Mask.Size() onesB, _ := b.cidr.Mask.Size() return onesA > onesB }) if len(filteredRoutes) > 0 { return filteredRoutes[0].entry, nil } return nil, nil } func parseIPOrCIDR(s string) (net.IP, *net.IPNet, error) { ip, cidr, err := net.ParseCIDR(s) if err == nil { return ip, cidr, err } return net.ParseCIDR(fmt.Sprintf("%s/32", s)) }