From 1b4e241014f333ca9fc509f6152c7ec16a116828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Thu, 8 Oct 2026 16:36:49 +0300 Subject: [PATCH 1/2] alibaba: add slb, alb, eip and ack services --- PROVIDERS.md | 2 + pkg/providers/alibaba/ack.go | 98 +++++++++++++++++++++++ pkg/providers/alibaba/alb.go | 49 ++++++++++++ pkg/providers/alibaba/alibaba.go | 61 ++++++++++++++- pkg/providers/alibaba/alibaba_test.go | 107 ++++++++++++++++++++++++++ pkg/providers/alibaba/eip.go | 50 ++++++++++++ pkg/providers/alibaba/slb.go | 54 +++++++++++++ 7 files changed, 418 insertions(+), 3 deletions(-) create mode 100644 pkg/providers/alibaba/ack.go create mode 100644 pkg/providers/alibaba/alb.go create mode 100644 pkg/providers/alibaba/alibaba_test.go create mode 100644 pkg/providers/alibaba/eip.go create mode 100644 pkg/providers/alibaba/slb.go diff --git a/PROVIDERS.md b/PROVIDERS.md index fa18154e..2e038959 100644 --- a/PROVIDERS.md +++ b/PROVIDERS.md @@ -491,6 +491,8 @@ Alibaba Cloud can be integrated by using the following configuration block. Alibaba Cloud Access Key ID and Secret can be created by visiting https://ram.console.aliyun.com/manage/ak +Supported services: `instance` (ECS), `slb` (Classic Load Balancer), `alb` (Application Load Balancer), `eip` (Elastic IP), `ack` (Container Service for Kubernetes API server endpoints). The RAM user needs read-only access to each of them, e.g. the `AliyunECSReadOnlyAccess`, `AliyunSLBReadOnlyAccess`, `AliyunALBReadOnlyAccess`, `AliyunVPCReadOnlyAccess` and `AliyunCSReadOnlyAccess` system policies. + References - - https://www.alibabacloud.com/help/faq-detail/142101.htm diff --git a/pkg/providers/alibaba/ack.go b/pkg/providers/alibaba/ack.go new file mode 100644 index 00000000..f8e045c7 --- /dev/null +++ b/pkg/providers/alibaba/ack.go @@ -0,0 +1,98 @@ +package alibaba + +import ( + "context" + "encoding/json" + "net" + "net/url" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/services/cs" + "github.com/projectdiscovery/cloudlist/pkg/schema" +) + +// ackProvider is a Container Service for Kubernetes (ACK) provider for alibaba API +type ackProvider struct { + id string + client *cs.Client +} + +// The cs SDK package leaves ROA response bodies untyped, so the fields we +// need are decoded here. +type ackClustersResponse struct { + Clusters []struct { + // MasterURL is itself a JSON-encoded object. + MasterURL string `json:"master_url"` + } `json:"clusters"` + PageInfo struct { + TotalCount int `json:"total_count"` + } `json:"page_info"` +} + +type ackMasterURL struct { + APIServerEndpoint string `json:"api_server_endpoint"` + IntranetAPIServerEndpoint string `json:"intranet_api_server_endpoint"` +} + +func (d *ackProvider) name() string { + return "ack" +} + +// GetResource returns all the resources in the store for a provider. +func (d *ackProvider) GetResource(ctx context.Context) (*schema.Resources, error) { + list := schema.NewResources() + + request := cs.CreateDescribeClustersV1Request() + request.PageSize = requests.NewInteger(pageSize) + for page := 1; ; page++ { + request.PageNumber = requests.NewInteger(page) + response, err := d.client.DescribeClustersV1(request) + if err != nil { + return nil, err + } + + var clusters ackClustersResponse + if err := json.Unmarshal(response.GetHttpContentBytes(), &clusters); err != nil { + return nil, err + } + + for _, cluster := range clusters.Clusters { + var masterURL ackMasterURL + if err := json.Unmarshal([]byte(cluster.MasterURL), &masterURL); err != nil { + continue + } + d.appendEndpoint(list, masterURL.APIServerEndpoint, true) + d.appendEndpoint(list, masterURL.IntranetAPIServerEndpoint, false) + } + + if len(clusters.Clusters) == 0 || page*pageSize >= clusters.PageInfo.TotalCount { + break + } + } + return list, nil +} + +// appendEndpoint adds the host of an API server URL such as https://47.0.0.1:6443. +func (d *ackProvider) appendEndpoint(list *schema.Resources, endpoint string, public bool) { + parsed, err := url.Parse(endpoint) + if err != nil || parsed.Hostname() == "" { + return + } + host := parsed.Hostname() + + resource := &schema.Resource{ + ID: d.id, + Provider: providerName, + Public: public, + Service: d.name(), + } + switch { + case net.ParseIP(host) == nil: + resource.DNSName = host + case public: + resource.PublicIPv4 = host + default: + resource.PrivateIpv4 = host + } + list.Append(resource) +} diff --git a/pkg/providers/alibaba/alb.go b/pkg/providers/alibaba/alb.go new file mode 100644 index 00000000..b4aac9de --- /dev/null +++ b/pkg/providers/alibaba/alb.go @@ -0,0 +1,49 @@ +package alibaba + +import ( + "context" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/services/alb" + "github.com/projectdiscovery/cloudlist/pkg/schema" +) + +// albProvider is an Application Load Balancer provider for alibaba API +type albProvider struct { + id string + client *alb.Client +} + +func (d *albProvider) name() string { + return "alb" +} + +// GetResource returns all the resources in the store for a provider. +func (d *albProvider) GetResource(ctx context.Context) (*schema.Resources, error) { + list := schema.NewResources() + + request := alb.CreateListLoadBalancersRequest() + request.MaxResults = requests.NewInteger(pageSize) + for { + response, err := d.client.ListLoadBalancers(request) + if err != nil { + return nil, err + } + + for _, lb := range response.LoadBalancers { + list.Append(&schema.Resource{ + ID: d.id, + Provider: providerName, + DNSName: lb.DNSName, + Public: lb.AddressType == "Internet", + Service: d.name(), + }) + } + + if response.NextToken == "" { + break + } + request.NextToken = response.NextToken + } + return list, nil +} diff --git a/pkg/providers/alibaba/alibaba.go b/pkg/providers/alibaba/alibaba.go index 9177e537..9dcd0eae 100644 --- a/pkg/providers/alibaba/alibaba.go +++ b/pkg/providers/alibaba/alibaba.go @@ -3,23 +3,34 @@ package alibaba import ( "context" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials" + "github.com/aliyun/alibaba-cloud-sdk-go/services/alb" + "github.com/aliyun/alibaba-cloud-sdk-go/services/cs" "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" + "github.com/aliyun/alibaba-cloud-sdk-go/services/slb" + "github.com/aliyun/alibaba-cloud-sdk-go/services/vpc" "github.com/projectdiscovery/cloudlist/pkg/schema" ) -var Services = []string{"instance"} +var Services = []string{"instance", "slb", "alb", "eip", "ack"} const ( regionID = "alibaba_region_id" accessKeyID = "alibaba_access_key" accessKeySecret = "alibaba_access_key_secret" providerName = "alibaba" + pageSize = 100 ) // Provider is a data provider for alibaba API type Provider struct { id string ecsClient *ecs.Client + slbClient *slb.Client + albClient *alb.Client + vpcClient *vpc.Client + csClient *cs.Client services schema.ServiceMap } @@ -56,6 +67,31 @@ func New(options schema.OptionBlock) (*Provider, error) { provider.ecsClient = client } + // The SDK defaults to plain HTTP, so the newer clients opt into HTTPS. + config := sdk.NewConfig().WithScheme("HTTPS") + credential := credentials.NewAccessKeyCredential(accessKeyID, accessKeySecret) + var err error + if services.Has("slb") { + if provider.slbClient, err = slb.NewClientWithOptions(regionID, config, credential); err != nil { + return nil, err + } + } + if services.Has("alb") { + if provider.albClient, err = alb.NewClientWithOptions(regionID, config, credential); err != nil { + return nil, err + } + } + if services.Has("eip") { + if provider.vpcClient, err = vpc.NewClientWithOptions(regionID, config, credential); err != nil { + return nil, err + } + } + if services.Has("ack") { + if provider.csClient, err = cs.NewClientWithOptions(regionID, config, credential); err != nil { + return nil, err + } + } + return provider, nil } @@ -77,9 +113,28 @@ func (p *Provider) Services() []string { // Resources returns the provider for an resource deployment source. func (p *Provider) Resources(ctx context.Context) (*schema.Resources, error) { finalResources := schema.NewResources() + + var providers []interface { + GetResource(ctx context.Context) (*schema.Resources, error) + } if p.ecsClient != nil { - ecsprovider := &instanceProvider{client: p.ecsClient, id: p.id} - if resources, err := ecsprovider.GetResource(ctx); err == nil { + providers = append(providers, &instanceProvider{client: p.ecsClient, id: p.id}) + } + if p.slbClient != nil { + providers = append(providers, &slbProvider{client: p.slbClient, id: p.id}) + } + if p.albClient != nil { + providers = append(providers, &albProvider{client: p.albClient, id: p.id}) + } + if p.vpcClient != nil { + providers = append(providers, &eipProvider{client: p.vpcClient, id: p.id}) + } + if p.csClient != nil { + providers = append(providers, &ackProvider{client: p.csClient, id: p.id}) + } + + for _, provider := range providers { + if resources, err := provider.GetResource(ctx); err == nil { finalResources.Merge(resources) } } diff --git a/pkg/providers/alibaba/alibaba_test.go b/pkg/providers/alibaba/alibaba_test.go new file mode 100644 index 00000000..1a69b87f --- /dev/null +++ b/pkg/providers/alibaba/alibaba_test.go @@ -0,0 +1,107 @@ +package alibaba + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials" + "github.com/aliyun/alibaba-cloud-sdk-go/services/alb" + "github.com/aliyun/alibaba-cloud-sdk-go/services/cs" + "github.com/aliyun/alibaba-cloud-sdk-go/services/slb" + "github.com/aliyun/alibaba-cloud-sdk-go/services/vpc" + "github.com/projectdiscovery/cloudlist/pkg/schema" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// fakeAlibabaAPI answers the RPC (Action=...) and ROA (path) calls used by the +// provider. SLB and ALB return two pages to exercise pagination. +func fakeAlibabaAPI(t *testing.T) *httptest.Server { + t.Helper() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.NoError(t, r.ParseForm()) + w.Header().Set("Content-Type", "application/json") + + var body string + switch { + case r.Form.Get("Action") == "DescribeLoadBalancers" && r.Form.Get("PageNumber") == "1": + body = `{"TotalCount":101,"LoadBalancers":{"LoadBalancer":[{"Address":"47.88.1.10","AddressType":"internet"}]}}` + case r.Form.Get("Action") == "DescribeLoadBalancers" && r.Form.Get("PageNumber") == "2": + body = `{"TotalCount":101,"LoadBalancers":{"LoadBalancer":[{"Address":"10.0.0.11","AddressType":"intranet"}]}}` + case r.Form.Get("Action") == "ListLoadBalancers" && r.Form.Get("NextToken") == "": + body = `{"NextToken":"page-2","LoadBalancers":[{"DNSName":"alb-public.cn-hangzhou.alb.aliyuncs.com","AddressType":"Internet"}]}` + case r.Form.Get("Action") == "ListLoadBalancers" && r.Form.Get("NextToken") == "page-2": + body = `{"LoadBalancers":[{"DNSName":"alb-internal.cn-hangzhou.alb.aliyuncs.com","AddressType":"Intranet"}]}` + case r.Form.Get("Action") == "DescribeEipAddresses": + body = `{"TotalCount":1,"EipAddresses":{"EipAddress":[{"IpAddress":"47.88.1.30","Status":"Available"}]}}` + case r.URL.Path == "/api/v1/clusters": + body = `{"clusters":[` + + `{"master_url":"{\"api_server_endpoint\":\"https://47.88.1.40:6443\",\"intranet_api_server_endpoint\":\"https://192.168.0.10:6443\"}"},` + + `{"master_url":""}` + + `],"page_info":{"total_count":2}}` + default: + t.Errorf("unexpected request: %s %s", r.URL.Path, r.Form.Encode()) + w.WriteHeader(http.StatusBadRequest) + return + } + _, _ = w.Write([]byte(body)) + })) + t.Cleanup(server.Close) + return server +} + +func TestResources(t *testing.T) { + server := fakeAlibabaAPI(t) + domain := strings.TrimPrefix(server.URL, "http://") + config := sdk.NewConfig().WithScheme("HTTP") + credential := credentials.NewAccessKeyCredential("test", "test") + + slbClient, err := slb.NewClientWithOptions("cn-hangzhou", config, credential) + require.NoError(t, err) + albClient, err := alb.NewClientWithOptions("cn-hangzhou", config, credential) + require.NoError(t, err) + vpcClient, err := vpc.NewClientWithOptions("cn-hangzhou", config, credential) + require.NoError(t, err) + csClient, err := cs.NewClientWithOptions("cn-hangzhou", config, credential) + require.NoError(t, err) + slbClient.Domain, albClient.Domain, vpcClient.Domain, csClient.Domain = domain, domain, domain, domain + + provider := &Provider{id: "test", slbClient: slbClient, albClient: albClient, vpcClient: vpcClient, csClient: csClient} + resources, err := provider.Resources(context.Background()) + require.NoError(t, err) + + got := map[string]*schema.Resource{} + for _, item := range resources.Items { + for _, value := range []string{item.PublicIPv4, item.PrivateIpv4, item.DNSName} { + if value != "" { + got[value] = item + } + } + } + + expected := map[string]struct { + service string + public bool + }{ + "47.88.1.10": {"slb", true}, + "10.0.0.11": {"slb", false}, + "alb-public.cn-hangzhou.alb.aliyuncs.com": {"alb", true}, + // schema.Resources marks every DNS name public, whatever the provider set. + "alb-internal.cn-hangzhou.alb.aliyuncs.com": {"alb", true}, + "47.88.1.30": {"eip", true}, + "47.88.1.40": {"ack", true}, + "192.168.0.10": {"ack", false}, + } + require.Len(t, got, len(expected)) + for value, want := range expected { + resource, ok := got[value] + require.True(t, ok, "missing %s", value) + assert.Equal(t, want.service, resource.Service, value) + assert.Equal(t, want.public, resource.Public, value) + assert.Equal(t, providerName, resource.Provider, value) + } +} diff --git a/pkg/providers/alibaba/eip.go b/pkg/providers/alibaba/eip.go new file mode 100644 index 00000000..fb34192b --- /dev/null +++ b/pkg/providers/alibaba/eip.go @@ -0,0 +1,50 @@ +package alibaba + +import ( + "context" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/services/vpc" + "github.com/projectdiscovery/cloudlist/pkg/schema" +) + +// eipProvider is an Elastic IP provider for alibaba API. Unlike the instance +// service, it also finds EIPs that are not bound to an ECS instance. +type eipProvider struct { + id string + client *vpc.Client +} + +func (d *eipProvider) name() string { + return "eip" +} + +// GetResource returns all the resources in the store for a provider. +func (d *eipProvider) GetResource(ctx context.Context) (*schema.Resources, error) { + list := schema.NewResources() + + request := vpc.CreateDescribeEipAddressesRequest() + request.PageSize = requests.NewInteger(pageSize) + for page := 1; ; page++ { + request.PageNumber = requests.NewInteger(page) + response, err := d.client.DescribeEipAddresses(request) + if err != nil { + return nil, err + } + + for _, eip := range response.EipAddresses.EipAddress { + list.Append(&schema.Resource{ + ID: d.id, + Provider: providerName, + PublicIPv4: eip.IpAddress, + Public: true, + Service: d.name(), + }) + } + + if len(response.EipAddresses.EipAddress) == 0 || page*pageSize >= response.TotalCount { + break + } + } + return list, nil +} diff --git a/pkg/providers/alibaba/slb.go b/pkg/providers/alibaba/slb.go new file mode 100644 index 00000000..b4b1604e --- /dev/null +++ b/pkg/providers/alibaba/slb.go @@ -0,0 +1,54 @@ +package alibaba + +import ( + "context" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/services/slb" + "github.com/projectdiscovery/cloudlist/pkg/schema" +) + +// slbProvider is a Classic Load Balancer (SLB) provider for alibaba API +type slbProvider struct { + id string + client *slb.Client +} + +func (d *slbProvider) name() string { + return "slb" +} + +// GetResource returns all the resources in the store for a provider. +func (d *slbProvider) GetResource(ctx context.Context) (*schema.Resources, error) { + list := schema.NewResources() + + request := slb.CreateDescribeLoadBalancersRequest() + request.PageSize = requests.NewInteger(pageSize) + for page := 1; ; page++ { + request.PageNumber = requests.NewInteger(page) + response, err := d.client.DescribeLoadBalancers(request) + if err != nil { + return nil, err + } + + for _, lb := range response.LoadBalancers.LoadBalancer { + resource := &schema.Resource{ + ID: d.id, + Provider: providerName, + Service: d.name(), + } + if lb.AddressType == "internet" { + resource.Public = true + resource.PublicIPv4 = lb.Address + } else { + resource.PrivateIpv4 = lb.Address + } + list.Append(resource) + } + + if len(response.LoadBalancers.LoadBalancer) == 0 || page*pageSize >= response.TotalCount { + break + } + } + return list, nil +} From 0c5ecf824a24a456881020edb07eab0a67d9e672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Thu, 8 Oct 2026 18:15:21 +0300 Subject: [PATCH 2/2] alibaba: use https for ecs --- pkg/providers/alibaba/alibaba.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/pkg/providers/alibaba/alibaba.go b/pkg/providers/alibaba/alibaba.go index 9dcd0eae..39219ad1 100644 --- a/pkg/providers/alibaba/alibaba.go +++ b/pkg/providers/alibaba/alibaba.go @@ -55,22 +55,15 @@ func New(options schema.OptionBlock) (*Provider, error) { services := options.ResolveServices(Services) provider.services = services + // The SDK defaults to plain HTTP. + config := sdk.NewConfig().WithScheme("HTTPS") + credential := credentials.NewAccessKeyCredential(accessKeyID, accessKeySecret) + var err error if services.Has("instance") { - client, err := ecs.NewClientWithAccessKey( - regionID, // region ID - accessKeyID, // AccessKey ID - accessKeySecret, // AccessKey secret - ) - if err != nil { + if provider.ecsClient, err = ecs.NewClientWithOptions(regionID, config, credential); err != nil { return nil, err } - provider.ecsClient = client } - - // The SDK defaults to plain HTTP, so the newer clients opt into HTTPS. - config := sdk.NewConfig().WithScheme("HTTPS") - credential := credentials.NewAccessKeyCredential(accessKeyID, accessKeySecret) - var err error if services.Has("slb") { if provider.slbClient, err = slb.NewClientWithOptions(regionID, config, credential); err != nil { return nil, err