Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions pkg/providers/openstack/floatingips.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package openstack

import (
"context"

"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/gophercloud/gophercloud/pagination"
"github.com/projectdiscovery/cloudlist/pkg/schema"
"github.com/projectdiscovery/gologger"
)

// floatingIPProvider lists Neutron floating IPs, including ones not attached
// to an instance, which the compute API never returns.
type floatingIPProvider struct {
id string
client *gophercloud.ServiceClient
}

func (p *floatingIPProvider) name() string {
return "floatingip"
}

// GetResource returns all the floating IPs in the store for a provider.
func (p *floatingIPProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
list := schema.NewResources()

err := floatingips.List(p.client, floatingips.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
ips, err := floatingips.ExtractFloatingIPs(page)
if err != nil {
return false, err
}

for _, ip := range ips {
list.Append(&schema.Resource{
Provider: providerName,
ID: p.id,
PublicIPv4: ip.FloatingIP,
Service: p.name(),
})
}
return true, nil
})

if err != nil {
gologger.Error().Msgf("Couldn't list Openstack floating IPs: %s\n", err)
return list, err
}

return list, nil
}
52 changes: 52 additions & 0 deletions pkg/providers/openstack/loadbalancers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package openstack

import (
"context"

"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers"
"github.com/gophercloud/gophercloud/pagination"
"github.com/projectdiscovery/cloudlist/pkg/schema"
"github.com/projectdiscovery/gologger"
)

// loadBalancerProvider lists Octavia load balancer VIPs
type loadBalancerProvider struct {
id string
client *gophercloud.ServiceClient
}

func (p *loadBalancerProvider) name() string {
return "loadbalancer"
}

// GetResource returns all the load balancers in the store for a provider.
func (p *loadBalancerProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
list := schema.NewResources()

err := loadbalancers.List(p.client, loadbalancers.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
lbs, err := loadbalancers.ExtractLoadBalancers(page)
if err != nil {
return false, err
}

// The VIP is usually private; a public address in front of it is a
// floating IP and is reported by the floatingip service.
for _, lb := range lbs {
list.Append(&schema.Resource{
Provider: providerName,
ID: p.id,
PrivateIpv4: lb.VipAddress,
Service: p.name(),
})
}
return true, nil
})

if err != nil {
gologger.Error().Msgf("Couldn't list Openstack load balancers: %s\n", err)
return list, err
}

return list, nil
}
95 changes: 80 additions & 15 deletions pkg/providers/openstack/openstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ const (
providerName = "openstack"
)

var Services = []string{"instance"}
var Services = []string{"instance", "floatingip", "loadbalancer"}

// Provider is a data provider for Openstack API
type Provider struct {
id string
client *gophercloud.ServiceClient
services schema.ServiceMap
id string
client *gophercloud.ServiceClient
network *gophercloud.ServiceClient
loadBalancer *gophercloud.ServiceClient
services schema.ServiceMap
}

// New creates a new provider client for Openstack API
Expand Down Expand Up @@ -72,17 +74,44 @@ func New(options schema.OptionBlock) (*Provider, error) {
return nil, err
}

client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
Region: "RegionOne",
})
services := options.ResolveServices(Services)
endpointOpts := gophercloud.EndpointOpts{Region: "RegionOne"}
p := &Provider{id: id, services: services}

// Compute is optional when Neutron or Octavia can still answer the
// selected services. A missing Nova endpoint must not fail those.
if services.Has("instance") {
client, clientErr := openstack.NewComputeV2(provider, endpointOpts)
if clientErr != nil && !services.Has("floatingip") && !services.Has("loadbalancer") {
gologger.Error().Msgf("Couldn't use Openstack region: %s\n", clientErr)
return nil, clientErr
}
if clientErr != nil {
gologger.Warning().Msgf("Couldn't use Openstack compute: %s\n", clientErr)
} else {
p.client = client
}
}

if err != nil {
gologger.Error().Msgf("Couldn't use Openstack region: %s\n", err)
return nil, err
// Neutron and Octavia are optional on many clouds, so a missing catalog
// entry only disables that service instead of failing the provider.
if services.Has("floatingip") {
p.network = optionalClient(openstack.NewNetworkV2(provider, endpointOpts))
}
if services.Has("loadbalancer") {
p.loadBalancer = optionalClient(openstack.NewLoadBalancerV2(provider, endpointOpts))
}
return p, nil
}

services := options.ResolveServices(Services)
return &Provider{id: id, client: client, services: services}, nil
// optionalClient drops the client on error, since gophercloud returns a
// non-nil but unusable client when the endpoint is not in the catalog.
func optionalClient(client *gophercloud.ServiceClient, err error) *gophercloud.ServiceClient {
if err != nil {
gologger.Warning().Msgf("Couldn't use Openstack service: %s\n", err)
return nil
}
return client
}

// Name returns the name of the provider
Expand All @@ -103,11 +132,47 @@ func (p *Provider) Services() []string {
// Resources returns the provider for an resource
func (p *Provider) Resources(ctx context.Context) (*schema.Resources, error) {
finalResources := schema.NewResources()
if p.services.Has("instance") {
var failed error

// Neutron owns the floatingip service identity. Nova also reports an
// attached floating address, and the first writer wins deduplication.
if p.network != nil {
provider := &floatingIPProvider{id: p.id, client: p.network}
resources, err := provider.GetResource(ctx)
failed = absorb(finalResources, resources, err)
}
if p.loadBalancer != nil {
provider := &loadBalancerProvider{id: p.id, client: p.loadBalancer}
resources, err := provider.GetResource(ctx)
if err := absorb(finalResources, resources, err); err != nil {
failed = err
}
}
if p.services.Has("instance") && p.client != nil {
provider := &instanceProvider{id: p.id, client: p.client}
if resources, err := provider.GetResource(ctx); err == nil {
finalResources.Merge(resources)
resources, err := provider.GetResource(ctx)
if err := absorb(finalResources, resources, err); err != nil {
failed = err
}
}
if failed != nil && len(finalResources.Items) == 0 {
return nil, failed
}
if failed != nil {
gologger.Warning().Msgf("Couldn't list Openstack resources: %s\n", failed)
}
return finalResources, nil
}

// absorb keeps a partial listing. An empty failed listing is returned so the
// caller can surface it when nothing else was collected.
func absorb(final *schema.Resources, resources *schema.Resources, err error) error {
if resources != nil && len(resources.Items) > 0 {
final.Merge(resources)
if err != nil {
gologger.Warning().Msgf("Couldn't list all Openstack resources: %s\n", err)
}
return nil
}
return err
}
Loading
Loading