Skip to content
Open
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
29 changes: 29 additions & 0 deletions PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,3 +481,32 @@ The `dnssimple_api_token` can be generated from the DNSSimple account settings u
References -
1. https://developer.dnsimple.com/v2/
2. https://support.dnsimple.com/articles/api-access-token/

### OVH

Cloudlist supports fetching DNS records from OVH.

- **Provider key**: `ovh`
- **Services**: `dns`
- **id**: An arbitrary label you choose to tag resources. It helps distinguish multiple OVH accounts/configs.
- **Required auth**:
- `application_key`
- `application_secret`
- `consumer_key`
- **Endpoint**: OVH API endpoint. Defaults to `ovh-eu` if omitted. Common values: `ovh-eu`, `ovh-ca`, `ovh-us`.

Configuration example (from `ovh.yaml`):

```yaml
- provider: ovh
id: ovh-prod
endpoint: ovh-ca
application_key: $OVH_APP_KEY
application_secret: $OVH_APP_SECRET
consumer_key: $OVH_CONSUMER_KEY
```

References -
1. https://eu.api.ovh.com/console/?section=%2Fdomain&branch=v1
2. https://api.ovh.com/createToken/
3. https://help.ovhcloud.com/csm/en-gb-api-getting-started-ovhcloud-api
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ require (
google.golang.org/protobuf v1.36.6
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
Expand Down Expand Up @@ -214,6 +214,7 @@ require (
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/mholt/archives v0.1.0 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78 // indirect
github.com/ovh/go-ovh v1.9.0 // indirect
github.com/projectdiscovery/fastdialer v0.4.3 // indirect
github.com/projectdiscovery/hmap v0.0.92 // indirect
github.com/projectdiscovery/retryabledns v1.0.104 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,8 @@ github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKi
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
Expand Down Expand Up @@ -980,6 +982,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
Expand Down
4 changes: 4 additions & 0 deletions pkg/inventory/inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/projectdiscovery/cloudlist/pkg/providers/namecheap"
"github.com/projectdiscovery/cloudlist/pkg/providers/nomad"
"github.com/projectdiscovery/cloudlist/pkg/providers/openstack"
"github.com/projectdiscovery/cloudlist/pkg/providers/ovh"
"github.com/projectdiscovery/cloudlist/pkg/providers/scaleway"
"github.com/projectdiscovery/cloudlist/pkg/providers/terraform"
"github.com/projectdiscovery/cloudlist/pkg/schema"
Expand Down Expand Up @@ -70,6 +71,7 @@ var Providers = map[string][]string{
"nomad": nomad.Services,
"hetzner": hetzner.Services,
"openstack": openstack.Services,
"ovh": ovh.Services,
"kubernetes": k8s.Services,
"custom": custom.Services,
}
Expand Down Expand Up @@ -125,6 +127,8 @@ func nameToProvider(value string, block schema.OptionBlock) (schema.Provider, er
return hetzner.New(block)
case "openstack":
return openstack.New(block)
case "ovh":
return ovh.New(block)
case "kubernetes":
return k8s.New(block)
case "custom":
Expand Down
87 changes: 87 additions & 0 deletions pkg/providers/ovh/dns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package ovh

import (
"context"
"fmt"
"strings"

"github.com/ovh/go-ovh/ovh"
"github.com/projectdiscovery/cloudlist/pkg/schema"
)

type dnsProvider struct {
id string
client *ovh.Client
}

func (d *dnsProvider) name() string { return "dns" }

type ovhRecord struct {
ID int64 `json:"id"`
FieldType string `json:"fieldType"`
SubDomain string `json:"subDomain"`
Target string `json:"target"`
TTL int `json:"ttl"`
}

// GetResource returns all DNS resources from OVH
func (d *dnsProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
list := schema.NewResources()

var zones []string
if err := d.client.GetWithContext(ctx, "/domain/zone", &zones); err != nil {
return nil, err
}

recordTypes := []string{"A", "AAAA", "CNAME"}
for _, zone := range zones {
for _, rt := range recordTypes {
var ids []int64
path := fmt.Sprintf("/domain/zone/%s/record?fieldType=%s", zone, rt)
if err := d.client.GetWithContext(ctx, path, &ids); err != nil {
continue
}
for _, id := range ids {
var rec ovhRecord
if err := d.client.GetWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record/%d", zone, id), &rec); err != nil {
continue
}

name := strings.TrimSpace(rec.SubDomain)
var fqdn string
if name == "" || name == "@" {
fqdn = zone
} else {
fqdn = name + "." + zone
}

// Append DNS name (public)
list.Append(&schema.Resource{
Public: true,
Provider: providerName,
DNSName: fqdn,
ID: d.id,
Service: d.name(),
})

// For A/AAAA also append IP resource; skip CNAME target to avoid duplicates
res := &schema.Resource{
Public: true,
Provider: providerName,
ID: d.id,
Service: d.name(),
}

if strings.ToUpper(rec.FieldType) == "A" {
res.PublicIPv4 = rec.Target
} else if strings.ToUpper(rec.FieldType) == "AAAA" {
res.PublicIPv6 = rec.Target
}

list.Append(res)
}
Comment on lines +67 to +82
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid appending empty IP resources for CNAME records.

The code currently appends an IP resource even for CNAME records, resulting in an empty resource with no IP addresses set. This creates unnecessary entries in the resource list.

Apply this diff to skip appending empty IP resources:

-				// For A/AAAA also append IP resource; skip CNAME target to avoid duplicates
-				res := &schema.Resource{
-					Public:   true,
-					Provider: providerName,
-					ID:       d.id,
-					Service:  d.name(),
-				}
-
-				if strings.ToUpper(rec.FieldType) == "A" {
-					res.PublicIPv4 = rec.Target
-				} else if strings.ToUpper(rec.FieldType) == "AAAA" {
-					res.PublicIPv6 = rec.Target
-				}
-
-				list.Append(res)
+				// For A/AAAA records, append IP resource
+				if strings.ToUpper(rec.FieldType) == "A" || strings.ToUpper(rec.FieldType) == "AAAA" {
+					res := &schema.Resource{
+						Public:   true,
+						Provider: providerName,
+						ID:       d.id,
+						Service:  d.name(),
+					}
+
+					if strings.ToUpper(rec.FieldType) == "A" {
+						res.PublicIPv4 = rec.Target
+					} else {
+						res.PublicIPv6 = rec.Target
+					}
+
+					list.Append(res)
+				}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// For A/AAAA also append IP resource; skip CNAME target to avoid duplicates
res := &schema.Resource{
Public: true,
Provider: providerName,
ID: d.id,
Service: d.name(),
}
if strings.ToUpper(rec.FieldType) == "A" {
res.PublicIPv4 = rec.Target
} else if strings.ToUpper(rec.FieldType) == "AAAA" {
res.PublicIPv6 = rec.Target
}
list.Append(res)
}
// For A/AAAA records, append IP resource
if strings.ToUpper(rec.FieldType) == "A" || strings.ToUpper(rec.FieldType) == "AAAA" {
res := &schema.Resource{
Public: true,
Provider: providerName,
ID: d.id,
Service: d.name(),
}
if strings.ToUpper(rec.FieldType) == "A" {
res.PublicIPv4 = rec.Target
} else {
res.PublicIPv6 = rec.Target
}
list.Append(res)
}
🤖 Prompt for AI Agents
In pkg/providers/ovh/dns.go around lines 67 to 82, the code constructs and
appends an IP resource for every record including CNAMEs, producing empty
resources; change the logic so you only create and append the resource when the
record is an A or AAAA (or when PublicIPv4/PublicIPv6 would be set). Concretely,
either move the res := &schema.Resource{...} creation inside the A/AAAA branches
or add a guard after setting PublicIPv4/PublicIPv6 that skips appending (e.g.,
continue or return) when neither IP field is populated.

}
}

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

import (
"context"
"net/http"
"strings"
"time"

"github.com/ovh/go-ovh/ovh"
"github.com/projectdiscovery/cloudlist/pkg/schema"
)

const providerName = "ovh"

var Services = []string{"dns"}

type Provider struct {
id string
client *ovh.Client
httpClient *http.Client
services schema.ServiceMap
}

func New(options schema.OptionBlock) (*Provider, error) {
id, _ := options.GetMetadata("id")

// service selection
supported := map[string]struct{}{"dns": {}}
services := make(schema.ServiceMap)
if ss, ok := options.GetMetadata("services"); ok {
for _, s := range strings.Split(ss, ",") {
s = strings.TrimSpace(s)
if _, ok := supported[s]; ok {
services[s] = struct{}{}
}
}
}
if len(services) == 0 {
for _, s := range Services {
services[s] = struct{}{}
}
}

// OVH endpoint (default ovh-eu)
endpoint := "ovh-eu"
if ep, ok := options.GetMetadata("endpoint"); ok && ep != "" {
endpoint = ep
}

// Credentials
appKey, ok := options.GetMetadata("application_key")
if !ok || appKey == "" {
return nil, &schema.ErrNoSuchKey{Name: "application_key"}
}
appSecret, ok := options.GetMetadata("application_secret")
if !ok || appSecret == "" {
return nil, &schema.ErrNoSuchKey{Name: "application_secret"}
}
consumerKey, ok := options.GetMetadata("consumer_key")
if !ok || consumerKey == "" {
return nil, &schema.ErrNoSuchKey{Name: "consumer_key"}
}

cli, err := ovh.NewClient(endpoint, appKey, appSecret, consumerKey)
if err != nil {
return nil, err
}

httpClient := &http.Client{Timeout: 30 * time.Second}
cli.Client = httpClient

return &Provider{
id: id,
client: cli,
httpClient: httpClient,
services: services,
}, nil
}

func (p *Provider) Name() string { return providerName }
func (p *Provider) ID() string { return p.id }
func (p *Provider) Services() []string { return p.services.Keys() }

func (p *Provider) Resources(ctx context.Context) (*schema.Resources, error) {
out := schema.NewResources()
if p.services.Has("dns") {
d := &dnsProvider{id: p.id, client: p.client}
r, err := d.GetResource(ctx)
if err != nil {
return nil, err
}
out.Merge(r)
}
return out, nil
}
Loading