From 6c52aba4c6636ee16d313530fa880d779f51efb8 Mon Sep 17 00:00:00 2001 From: Kevin Lyda Date: Fri, 27 Oct 2023 12:13:36 +0100 Subject: [PATCH 1/8] First pass at supporting RDAP (#36) Co-authored-by: ParthaI <47887552+ParthaI@users.noreply.github.com> --- go.mod | 3 + go.sum | 6 ++ whois/table_whois_domain.go | 131 +++++++++++++++++++++++++++++++++++- 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 1648c6d..2470cda 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,8 @@ require ( github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/agext/levenshtein v1.2.2 // indirect + github.com/alecthomas/kingpin/v2 v2.3.2 // indirect + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/allegro/bigcache/v3 v3.1.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aws/aws-sdk-go v1.44.122 // indirect @@ -71,6 +73,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oklog/run v1.0.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/openrdap/rdap v0.9.1 // indirect github.com/pegasus-kv/thrift v0.13.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.14.0 // indirect diff --git a/go.sum b/go.sum index fb77ea9..e1fe44b 100644 --- a/go.sum +++ b/go.sum @@ -198,11 +198,15 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU= +github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk= github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -552,6 +556,8 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +github.com/openrdap/rdap v0.9.1 h1:Rv6YbanbiVPsKRvOLdUmlU1AL5+2OFuEFLjFN+mQsCM= +github.com/openrdap/rdap v0.9.1/go.mod h1:vKSiotbsENrjM/vaHXLddXbW8iQkBfa+ldEuYEjyLTQ= github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= diff --git a/whois/table_whois_domain.go b/whois/table_whois_domain.go index 694843d..2b748d9 100644 --- a/whois/table_whois_domain.go +++ b/whois/table_whois_domain.go @@ -8,6 +8,7 @@ import ( "github.com/araddon/dateparse" "github.com/likexian/whois" whoisparser "github.com/likexian/whois-parser" + "github.com/openrdap/rdap" "github.com/sethvargo/go-retry" "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" @@ -60,12 +61,26 @@ func tableWhoisDomain(ctx context.Context) *plugin.Table { func listDomain(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { quals := d.EqualsQuals domain := quals["domain"].GetStringValue() + + // Attempt rdap lookup first. + client := &rdap.Client{} + rdapResult, err := client.QueryDomain(domain) + + if err == nil { + plugin.Logger(ctx).Debug("whois_domain.listDomain", "rdapResult", rdapResult) + mapped := rdapToWhoisDomain(domain, rdapResult) + plugin.Logger(ctx).Debug("whois_domain.listDomain", "mapped.Domain", mapped.Domain) + d.StreamListItem(ctx, mapped) + return nil, nil + } + + // Drop to whois. var whoisRaw string // WHOIS servers are fussy about load, so retry with backoff - b:= retry.NewFibonacci(100 * time.Millisecond) + b := retry.NewFibonacci(100 * time.Millisecond) - err := retry.Do(ctx, retry.WithMaxRetries(10, b), func(ctx context.Context) error { + err = retry.Do(ctx, retry.WithMaxRetries(10, b), func(ctx context.Context) error { var err error whoisRaw, err = whois.Whois(domain) if err != nil { @@ -139,3 +154,115 @@ func containsString(items []string, target string) bool { } return false } + +func rdapToWhoisDomain(passedDomain string, r *rdap.Domain) whoisparser.WhoisInfo { + var nameservers []string + for _, ns := range r.Nameservers { + nameservers = append(nameservers, ns.LDHName) + } + dnssec := false + if r.SecureDNS != nil && r.SecureDNS.DelegationSigned != nil && *r.SecureDNS.DelegationSigned { + dnssec = true + } + createdDate := "" + updatedDate := "" + expireDate := "" + for _, event := range r.Events { + switch event.Action { + case "registration": + createdDate = event.Date + case "expiration": + expireDate = event.Date + case "last changed": + updatedDate = event.Date + } + } + + domain := &whoisparser.Domain{ + ID: r.Handle, + Domain: passedDomain, + Punycode: r.LDHName, + Extension: r.LDHName[strings.LastIndex(r.LDHName, ".")+1:], + WhoisServer: r.Port43, + Status: spacesSpaced(r.Status), + NameServers: nameservers, + DNSSec: dnssec, + CreatedDate: createdDate, + UpdatedDate: updatedDate, + ExpirationDate: expireDate, + } + + registrar := entityToContact("registrar", r.Entities) + registrant := entityToContact("registrant", r.Entities) + admin := entityToContact("admin", r.Entities) + technical := entityToContact("technical", r.Entities) + billing := entityToContact("billing", r.Entities) + + return whoisparser.WhoisInfo{ + Domain: domain, + Registrar: registrar, + Registrant: registrant, + Administrative: admin, + Technical: technical, + Billing: billing, + } +} + +func spacesSpaced(sa []string) (spacelessSA []string) { + for _, s := range sa { + spacelessSA = append(spacelessSA, strings.ReplaceAll(s, " ", "")) + } + return +} + +func assignIfEmpty(target string, newValue string) string { + if target == "" { + return newValue + } + return target +} + +func vcardToContact(contact *whoisparser.Contact, entity rdap.Entity) { + if entity.VCard != nil && entity.VCard.Properties != nil { + for _, property := range entity.VCard.Properties { + switch property.Name { + case "fn": + contact.Name = assignIfEmpty(contact.Name, strings.Join(property.Values(), " ")) + case "org": + contact.Organization = strings.Join(property.Values(), " ") + case "adr": + // TODO: https://datatracker.ietf.org/doc/html/rfc6350#section-6.3.1 + contact.Street = "" + contact.City = "" + contact.Province = "" + contact.PostalCode = "" + contact.Country = "" + case "tel": + // TODO: parse these more to detrimine if it's fax number. + contact.Phone = strings.Join(property.Values(), " ") + contact.Fax = "" + case "email": + contact.Email = strings.Join(property.Values(), " ") + case "url": + contact.ReferralURL = strings.Join(property.Values(), " ") + } + } + + } +} + +func entityToContact(role string, entities []rdap.Entity) *whoisparser.Contact { + contact := &whoisparser.Contact{} + for _, entity := range entities { + if containsString(entity.Roles, role) { + contact.ID = entity.Handle + vcardToContact(contact, entity) + for _, inner := range entity.Entities { + if containsString(inner.Roles, "abuse") { + vcardToContact(contact, inner) + } + } + } + } + return contact +} From 23fc9479500f3dc6357a0575ae8f13e03097320e Mon Sep 17 00:00:00 2001 From: ParthaI Date: Mon, 30 Oct 2023 13:33:58 +0530 Subject: [PATCH 2/8] Added the reqired package --- go.mod | 3 ++- go.sum | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 2470cda..75558f6 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/likexian/whois v1.12.5 github.com/likexian/whois-parser v1.24.0 + github.com/openrdap/rdap v0.9.1 github.com/sethvargo/go-retry v0.2.4 github.com/turbot/steampipe-plugin-sdk/v5 v5.6.2 ) @@ -73,7 +74,6 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oklog/run v1.0.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/openrdap/rdap v0.9.1 // indirect github.com/pegasus-kv/thrift v0.13.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.14.0 // indirect @@ -87,6 +87,7 @@ require ( github.com/tkrajina/go-reflector v0.5.6 // indirect github.com/turbot/go-kit v0.8.0-rc.0 // indirect github.com/ulikunitz/xz v0.5.10 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect github.com/zclconf/go-cty v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel v1.17.0 // indirect diff --git a/go.sum b/go.sum index e1fe44b..adf57a0 100644 --- a/go.sum +++ b/go.sum @@ -463,6 +463,8 @@ github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSAS github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= +github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -556,9 +558,9 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/openrdap/rdap v0.9.1 h1:Rv6YbanbiVPsKRvOLdUmlU1AL5+2OFuEFLjFN+mQsCM= github.com/openrdap/rdap v0.9.1/go.mod h1:vKSiotbsENrjM/vaHXLddXbW8iQkBfa+ldEuYEjyLTQ= -github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4= @@ -649,6 +651,8 @@ github.com/turbot/steampipe-plugin-sdk/v5 v5.6.2 h1:B2gkMJK1m5GYQfmxjO9VREAwdUsG github.com/turbot/steampipe-plugin-sdk/v5 v5.6.2/go.mod h1:u2ubq9W5/5y6wG481LyulS7vuMOTRPmXAUfGLoVmwnA= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From ed75b39c62d16792e70741c0bc90f94bc946292e Mon Sep 17 00:00:00 2001 From: ParthaI Date: Mon, 30 Oct 2023 21:41:07 +0530 Subject: [PATCH 3/8] Splited the table whois_domain to whois_domain and rdap_domain as two separate table --- docs/tables/rdap_domain.md | 142 ++++++++++++++++++++++++++++++++++++ whois/plugin.go | 1 + whois/table_rdap_domain.go | 66 +++++++++++++++++ whois/table_whois_domain.go | 128 +------------------------------- 4 files changed, 210 insertions(+), 127 deletions(-) create mode 100644 docs/tables/rdap_domain.md create mode 100644 whois/table_rdap_domain.go diff --git a/docs/tables/rdap_domain.md b/docs/tables/rdap_domain.md new file mode 100644 index 0000000..bffb869 --- /dev/null +++ b/docs/tables/rdap_domain.md @@ -0,0 +1,142 @@ +# Table: rdap_domain + +An RDAP domain refers to a specific domain name for which you can retrieve registration information using the RDAP (Registration Data Access Protocol) system. RDAP allows users to query domain registration data in a structured and standardized way. The term "RDAP domain" is used to indicate that you are accessing or querying information for a particular domain name through the RDAP protocol. + +**Note:** It's not practical to list all domains in the world, so this table requires a +`domain` qualifier to be passed in the `where` or `join` clause for all queries. + + +## Examples + +### Basic whois info + +```sql +select + domain, + domain_id, + ldh_name, + object_class_name +from + rdap_domain +where + domain = 'steampipe.io'; +``` + +### Get name server information for a domain + +```sql +select + domain, + n ->> 'Handle' as nameserver_handle, + n ->> 'LDHName' as nameserver_ldh_name, + n ->> 'UnicodeName' as nameserver_unicode_name, + n ->> 'Port43' as nameserver_port43, + n -> 'Conformance' as nameserver_conformance, + n -> 'Events' as nameserver_events, + n -> 'Status' as nameserver_status, + n -> 'Entities' as nameserver_entities +from + rdap_domain, + jsonb_array_elements(nameservers) as n +where + domain = 'steampipe.io'; +``` + +### Check domain status codes + +Commonly used protections: + +```sql +select + domain, + s as atatus_code +from + rdap_domain, + jsonb_array_elements_text(status) as s +where + domain = 'steampipe.io'; +``` + +### Get domain variants + +```sql +select + domain, + v ->> 'IDNTable' as idn_table, + v ->> 'Relation' as relation, + v ->> 'VariantNames' as variant_names +from + rdap_domain, + jsonb_array_elements(variants) as v +where + domain = 'steampipe.io'; +``` + +### Get event details of a domain + +```sql +select + domain, + e ->> 'Action' as action, + e ->> 'Actor' as Actor, + e ->> 'Date' as date, + e -> 'Links' as links +from + rdap_domain, + jsonb_array_elements(events) as e +where + domain = 'steampipe.io'; +``` + +### Get entity details of a domain + +```sql +select + domain, + domain_id, + e ->> 'Handle' as handle, + e ->> 'Port43' as port43, + e -> 'AsEventActor' as as_event_actor, + e -> 'VCard' as v_card, + e -> 'Autnums' as autnums, + e -> 'Roles' as roles, + e -> 'Notices' as notices, + e -> 'Remarks' as Remarks, + e -> 'Networks' as Networks +from + rdap_domain, + jsonb_array_elements(entities) as e +where + domain = 'steampipe.io'; +``` + +### Get public IP details of a domain + +```sql +select + domain, + p ->> 'Type' as public_ip_type, + p ->> 'Identifier' as public_ip_identifier +from + rdap_domain, + jsonb_array_elements(public_ids) as p +where + domain = 'steampipe.io'; +``` + +### Working with multiple domains + +```sql +select + domain, + status +from + rdap_domain +where + domain in ( + 'github.com', + 'google.com', + 'steampipe.io', + 'yahoo.com' + ); +``` \ No newline at end of file diff --git a/whois/plugin.go b/whois/plugin.go index 95d8b6b..b32d514 100644 --- a/whois/plugin.go +++ b/whois/plugin.go @@ -13,6 +13,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { DefaultTransform: transform.FromGo().NullIfZero(), TableMap: map[string]*plugin.Table{ "whois_domain": tableWhoisDomain(ctx), + "rdap_domain": tableRdapDomain(ctx), }, } return p diff --git a/whois/table_rdap_domain.go b/whois/table_rdap_domain.go new file mode 100644 index 0000000..320e0b4 --- /dev/null +++ b/whois/table_rdap_domain.go @@ -0,0 +1,66 @@ +package whois + +import ( + "context" + "strings" + + "github.com/openrdap/rdap" + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" +) + +func tableRdapDomain(ctx context.Context) *plugin.Table { + return &plugin.Table{ + Name: "rdap_domain", + Description: "RDAP domain information including expiration, DNS servers and contact details.", + List: &plugin.ListConfig{ + KeyColumns: plugin.SingleColumn("domain"), + Hydrate: getRdapDomain, + }, + Columns: []*plugin.Column{ + // Top columns + {Name: "domain", Type: proto.ColumnType_STRING, Transform: transform.FromQual("domain"), Description: "Domain name the RDAP information relates to."}, + + {Name: "domain_id", Type: proto.ColumnType_STRING, Transform: transform.FromField("Handle").NullIfZero(), Description: "Unique identifier for the domain."}, + {Name: "object_class_name", Type: proto.ColumnType_STRING, Description: "String 'domain' representing the object type in RDAP."}, + {Name: "ldh_name", Type: proto.ColumnType_STRING, Description: "Textual representation of DNS names.", Transform: transform.FromField("LDHName")}, + {Name: "port43", Type: proto.ColumnType_STRING, Description: "Hostname of Registry WHOIS server."}, + + // JSON fields + {Name: "conformance", Type: proto.ColumnType_JSON, Description: "An array of strings, each providing a hint on the used specification."}, + {Name: "entities", Type: proto.ColumnType_JSON, Description: "An array of entities (linked contacts and the designated registrar)."}, + {Name: "events", Type: proto.ColumnType_JSON, Description: "An array of events that have occurred on the domain."}, + {Name: "links", Type: proto.ColumnType_JSON, Description: "Navigation to related on-line resources."}, + {Name: "nameservers", Type: proto.ColumnType_JSON, Description: "An array of nameserver objects."}, + {Name: "network", Type: proto.ColumnType_JSON, Description: "Information about IP address blocks and network allocations."}, + {Name: "notices", Type: proto.ColumnType_JSON, Description: "Information about the service."}, + {Name: "public_ids", Type: proto.ColumnType_JSON, Description: "Public identifiers associated with the domain.", Transform: transform.FromField("PublicIDs")}, + {Name: "secure_dns", Type: proto.ColumnType_JSON, Description: "Secure DNS information.", Transform: transform.FromField("SecureDNS")}, + {Name: "Remarks", Type: proto.ColumnType_JSON, Description: "Additional remarks or notes associated with the domain registration."}, + {Name: "status", Type: proto.ColumnType_JSON, Description: "An array of status flags describing the object state."}, + {Name: "variants", Type: proto.ColumnType_JSON, Description: "The internationalized domain name (IDN) variants."}, + }, + } +} + +func getRdapDomain(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + quals := d.EqualsQuals + domain := quals["domain"].GetStringValue() + + client := &rdap.Client{} + rdapResult, err := client.QueryDomain(domain) + + // Handle not found error + if err != nil { + if strings.Contains(err.Error(), "No RDAP servers found") { + return nil, nil + } + plugin.Logger(ctx).Error("rdap_domain.getRdapDomain", "api_error", err) + return nil, err + } + + d.StreamListItem(ctx, rdapResult) + + return nil, nil +} diff --git a/whois/table_whois_domain.go b/whois/table_whois_domain.go index 2b748d9..5c250a2 100644 --- a/whois/table_whois_domain.go +++ b/whois/table_whois_domain.go @@ -8,7 +8,6 @@ import ( "github.com/araddon/dateparse" "github.com/likexian/whois" whoisparser "github.com/likexian/whois-parser" - "github.com/openrdap/rdap" "github.com/sethvargo/go-retry" "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" @@ -62,25 +61,12 @@ func listDomain(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) quals := d.EqualsQuals domain := quals["domain"].GetStringValue() - // Attempt rdap lookup first. - client := &rdap.Client{} - rdapResult, err := client.QueryDomain(domain) - - if err == nil { - plugin.Logger(ctx).Debug("whois_domain.listDomain", "rdapResult", rdapResult) - mapped := rdapToWhoisDomain(domain, rdapResult) - plugin.Logger(ctx).Debug("whois_domain.listDomain", "mapped.Domain", mapped.Domain) - d.StreamListItem(ctx, mapped) - return nil, nil - } - - // Drop to whois. var whoisRaw string // WHOIS servers are fussy about load, so retry with backoff b := retry.NewFibonacci(100 * time.Millisecond) - err = retry.Do(ctx, retry.WithMaxRetries(10, b), func(ctx context.Context) error { + err := retry.Do(ctx, retry.WithMaxRetries(10, b), func(ctx context.Context) error { var err error whoisRaw, err = whois.Whois(domain) if err != nil { @@ -154,115 +140,3 @@ func containsString(items []string, target string) bool { } return false } - -func rdapToWhoisDomain(passedDomain string, r *rdap.Domain) whoisparser.WhoisInfo { - var nameservers []string - for _, ns := range r.Nameservers { - nameservers = append(nameservers, ns.LDHName) - } - dnssec := false - if r.SecureDNS != nil && r.SecureDNS.DelegationSigned != nil && *r.SecureDNS.DelegationSigned { - dnssec = true - } - createdDate := "" - updatedDate := "" - expireDate := "" - for _, event := range r.Events { - switch event.Action { - case "registration": - createdDate = event.Date - case "expiration": - expireDate = event.Date - case "last changed": - updatedDate = event.Date - } - } - - domain := &whoisparser.Domain{ - ID: r.Handle, - Domain: passedDomain, - Punycode: r.LDHName, - Extension: r.LDHName[strings.LastIndex(r.LDHName, ".")+1:], - WhoisServer: r.Port43, - Status: spacesSpaced(r.Status), - NameServers: nameservers, - DNSSec: dnssec, - CreatedDate: createdDate, - UpdatedDate: updatedDate, - ExpirationDate: expireDate, - } - - registrar := entityToContact("registrar", r.Entities) - registrant := entityToContact("registrant", r.Entities) - admin := entityToContact("admin", r.Entities) - technical := entityToContact("technical", r.Entities) - billing := entityToContact("billing", r.Entities) - - return whoisparser.WhoisInfo{ - Domain: domain, - Registrar: registrar, - Registrant: registrant, - Administrative: admin, - Technical: technical, - Billing: billing, - } -} - -func spacesSpaced(sa []string) (spacelessSA []string) { - for _, s := range sa { - spacelessSA = append(spacelessSA, strings.ReplaceAll(s, " ", "")) - } - return -} - -func assignIfEmpty(target string, newValue string) string { - if target == "" { - return newValue - } - return target -} - -func vcardToContact(contact *whoisparser.Contact, entity rdap.Entity) { - if entity.VCard != nil && entity.VCard.Properties != nil { - for _, property := range entity.VCard.Properties { - switch property.Name { - case "fn": - contact.Name = assignIfEmpty(contact.Name, strings.Join(property.Values(), " ")) - case "org": - contact.Organization = strings.Join(property.Values(), " ") - case "adr": - // TODO: https://datatracker.ietf.org/doc/html/rfc6350#section-6.3.1 - contact.Street = "" - contact.City = "" - contact.Province = "" - contact.PostalCode = "" - contact.Country = "" - case "tel": - // TODO: parse these more to detrimine if it's fax number. - contact.Phone = strings.Join(property.Values(), " ") - contact.Fax = "" - case "email": - contact.Email = strings.Join(property.Values(), " ") - case "url": - contact.ReferralURL = strings.Join(property.Values(), " ") - } - } - - } -} - -func entityToContact(role string, entities []rdap.Entity) *whoisparser.Contact { - contact := &whoisparser.Contact{} - for _, entity := range entities { - if containsString(entity.Roles, role) { - contact.ID = entity.Handle - vcardToContact(contact, entity) - for _, inner := range entity.Entities { - if containsString(inner.Roles, "abuse") { - vcardToContact(contact, inner) - } - } - } - } - return contact -} From 8a7e0a075faede7bdc06453b983ff47dd44507c1 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Tue, 31 Oct 2023 11:41:02 +0530 Subject: [PATCH 4/8] Added more example queries for the table --- docs/tables/rdap_domain.md | 56 ++++++++++++++++++++++++++++++++++++-- whois/table_rdap_domain.go | 2 +- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/docs/tables/rdap_domain.md b/docs/tables/rdap_domain.md index bffb869..ce26d8e 100644 --- a/docs/tables/rdap_domain.md +++ b/docs/tables/rdap_domain.md @@ -5,7 +5,6 @@ An RDAP domain refers to a specific domain name for which you can retrieve regis **Note:** It's not practical to list all domains in the world, so this table requires a `domain` qualifier to be passed in the `where` or `join` clause for all queries. - ## Examples ### Basic whois info @@ -124,6 +123,59 @@ where domain = 'steampipe.io'; ``` +### Get network information of a domain + +```sql +select + domain, + network ->> 'Handle' as network_handle, + network ->> 'ObjectClassName' as network_object_class_name, + network ->> 'StartAddress' as networkStartAddress, + network ->> 'EndAddress' as networkEndAddress, + network ->> 'IPVersion' as networkIPVersion, + network ->> 'Name' as networkName, + network ->> 'Type' as networkType, + network ->> 'Country' as networkCountry, + network ->> 'ParentHandle' as networkParentHandle, + network -> 'Status' as networkStatus +from + rdap_domain +where + domain = 'steampipe.io'; +``` + +### Get secure DNS details of a domain + +```sql +select + domain, + secure_dns ->> 'ZoneSigned' as zone_signed, + secure_dns ->> 'DelegationSigned' as delegation_signed, + secure_dns ->> 'MaxSigLife' as max_sig_life, + secure_dns ->> 'Ds' as data_structure, + secure_dns ->> 'Keys' as keys +from + rdap_domain +where + domain = 'steampipe.io'; +``` + +### Get a domain remarks + +```sql +select + domain, + r ->> 'Title' as title, + r ->> 'Type' as type, + r ->> 'Description' as description, + r ->> 'Links' as links +from + rdap_domain, + jsonb_array_elements(remarks) as r +where + domain = 'steampipe.io'; +``` + ### Working with multiple domains ```sql @@ -139,4 +191,4 @@ where 'steampipe.io', 'yahoo.com' ); -``` \ No newline at end of file +``` diff --git a/whois/table_rdap_domain.go b/whois/table_rdap_domain.go index 320e0b4..26d53fb 100644 --- a/whois/table_rdap_domain.go +++ b/whois/table_rdap_domain.go @@ -37,7 +37,7 @@ func tableRdapDomain(ctx context.Context) *plugin.Table { {Name: "notices", Type: proto.ColumnType_JSON, Description: "Information about the service."}, {Name: "public_ids", Type: proto.ColumnType_JSON, Description: "Public identifiers associated with the domain.", Transform: transform.FromField("PublicIDs")}, {Name: "secure_dns", Type: proto.ColumnType_JSON, Description: "Secure DNS information.", Transform: transform.FromField("SecureDNS")}, - {Name: "Remarks", Type: proto.ColumnType_JSON, Description: "Additional remarks or notes associated with the domain registration."}, + {Name: "remarks", Type: proto.ColumnType_JSON, Description: "Additional remarks or notes associated with the domain registration."}, {Name: "status", Type: proto.ColumnType_JSON, Description: "An array of status flags describing the object state."}, {Name: "variants", Type: proto.ColumnType_JSON, Description: "The internationalized domain name (IDN) variants."}, }, From 92ebfd1ef04829b8a1f185f504dac51c250aa97a Mon Sep 17 00:00:00 2001 From: ParthaI Date: Mon, 27 Nov 2023 10:52:48 +0530 Subject: [PATCH 5/8] Removed the columns for which the column value is always null --- whois/table_rdap_domain.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/whois/table_rdap_domain.go b/whois/table_rdap_domain.go index 26d53fb..34375cd 100644 --- a/whois/table_rdap_domain.go +++ b/whois/table_rdap_domain.go @@ -25,7 +25,6 @@ func tableRdapDomain(ctx context.Context) *plugin.Table { {Name: "domain_id", Type: proto.ColumnType_STRING, Transform: transform.FromField("Handle").NullIfZero(), Description: "Unique identifier for the domain."}, {Name: "object_class_name", Type: proto.ColumnType_STRING, Description: "String 'domain' representing the object type in RDAP."}, {Name: "ldh_name", Type: proto.ColumnType_STRING, Description: "Textual representation of DNS names.", Transform: transform.FromField("LDHName")}, - {Name: "port43", Type: proto.ColumnType_STRING, Description: "Hostname of Registry WHOIS server."}, // JSON fields {Name: "conformance", Type: proto.ColumnType_JSON, Description: "An array of strings, each providing a hint on the used specification."}, @@ -35,9 +34,7 @@ func tableRdapDomain(ctx context.Context) *plugin.Table { {Name: "nameservers", Type: proto.ColumnType_JSON, Description: "An array of nameserver objects."}, {Name: "network", Type: proto.ColumnType_JSON, Description: "Information about IP address blocks and network allocations."}, {Name: "notices", Type: proto.ColumnType_JSON, Description: "Information about the service."}, - {Name: "public_ids", Type: proto.ColumnType_JSON, Description: "Public identifiers associated with the domain.", Transform: transform.FromField("PublicIDs")}, {Name: "secure_dns", Type: proto.ColumnType_JSON, Description: "Secure DNS information.", Transform: transform.FromField("SecureDNS")}, - {Name: "remarks", Type: proto.ColumnType_JSON, Description: "Additional remarks or notes associated with the domain registration."}, {Name: "status", Type: proto.ColumnType_JSON, Description: "An array of status flags describing the object state."}, {Name: "variants", Type: proto.ColumnType_JSON, Description: "The internationalized domain name (IDN) variants."}, }, From a72b94d97b2aed42886bbe77a6e2e59dcbaf285e Mon Sep 17 00:00:00 2001 From: ParthaI Date: Thu, 11 Jan 2024 14:28:56 +0530 Subject: [PATCH 6/8] Updated the column naming and description --- whois/table_rdap_domain.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/whois/table_rdap_domain.go b/whois/table_rdap_domain.go index 34375cd..72eee59 100644 --- a/whois/table_rdap_domain.go +++ b/whois/table_rdap_domain.go @@ -22,8 +22,8 @@ func tableRdapDomain(ctx context.Context) *plugin.Table { // Top columns {Name: "domain", Type: proto.ColumnType_STRING, Transform: transform.FromQual("domain"), Description: "Domain name the RDAP information relates to."}, - {Name: "domain_id", Type: proto.ColumnType_STRING, Transform: transform.FromField("Handle").NullIfZero(), Description: "Unique identifier for the domain."}, - {Name: "object_class_name", Type: proto.ColumnType_STRING, Description: "String 'domain' representing the object type in RDAP."}, + {Name: "handle", Type: proto.ColumnType_STRING, Transform: transform.FromField("Handle").NullIfZero(), Description: "Registry-unique string identifier referencing the domain (domain name)."}, + {Name: "object_class_name", Type: proto.ColumnType_STRING, Description: "The data structure, a member named 'objectClassName', gives the object class name of a particular object as a string. This identifies the type of object being processed. An objectClassName is REQUIRED in all RDAP response objects so that the type of the object can be interpreted."}, {Name: "ldh_name", Type: proto.ColumnType_STRING, Description: "Textual representation of DNS names.", Transform: transform.FromField("LDHName")}, // JSON fields From 2e56075954599f8e6acb5a3676283f2402fdd0d0 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Thu, 11 Jan 2024 14:50:32 +0530 Subject: [PATCH 7/8] Updated the doc for the table --- docs/tables/rdap_domain.md | 232 ++++++++++++++++++++++++++++++++----- 1 file changed, 206 insertions(+), 26 deletions(-) diff --git a/docs/tables/rdap_domain.md b/docs/tables/rdap_domain.md index ce26d8e..ea376de 100644 --- a/docs/tables/rdap_domain.md +++ b/docs/tables/rdap_domain.md @@ -1,18 +1,40 @@ -# Table: rdap_domain +--- +title: "Steampipe Table: rdap_domain - Query Whois Domains using SQL" +description: "Allows users to query RDAP domain registration data in a structured and standardized way." +--- + +# Table: rdap_domain - Query RDAP information for Domains using SQL An RDAP domain refers to a specific domain name for which you can retrieve registration information using the RDAP (Registration Data Access Protocol) system. RDAP allows users to query domain registration data in a structured and standardized way. The term "RDAP domain" is used to indicate that you are accessing or querying information for a particular domain name through the RDAP protocol. -**Note:** It's not practical to list all domains in the world, so this table requires a +## Table Usage Guide + +The `rdap_domain` table provides insights about an RDAP domain query specifically refers to querying information about a domain name. This can include details such as the domain name's registration status, the registrar information, the domain's creation and expiration dates, and contact information associated with the domain. RDAP provides a more secure and standardized way to access this information compared to WHOIS, and it is becoming the preferred method for domain-related queries in the internet infrastructure community. + +**Important Notes** +It's not practical to list all domains in the world, so this table requires a `domain` qualifier to be passed in the `where` or `join` clause for all queries. ## Examples ### Basic whois info -```sql +```sql+postgres +select + domain, + handle, + ldh_name, + object_class_name +from + rdap_domain +where + domain = 'steampipe.io'; +``` + +```sql+sqlite select domain, - domain_id, + handle, ldh_name, object_class_name from @@ -21,9 +43,9 @@ where domain = 'steampipe.io'; ``` -### Get name server information for a domain +### Get nameserver information for a domain -```sql +```sql+postgres select domain, n ->> 'Handle' as nameserver_handle, @@ -41,11 +63,30 @@ where domain = 'steampipe.io'; ``` +```sql+sqlite +select + rdap_domain.domain, + json_extract(n.value, '$.Handle') as nameserver_handle, + json_extract(n.value, '$.LDHName') as nameserver_ldh_name, + json_extract(n.value, '$.UnicodeName') as nameserver_unicode_name, + json_extract(n.value, '$.Port43') as nameserver_port43, + json_extract(n.value, '$.Conformance') as nameserver_conformance, + json_extract(n.value, '$.Events') as nameserver_events, + json_extract(n.value, '$.Status') as nameserver_status, + json_extract(n.value, '$.Entities') as nameserver_entities +from + rdap_domain, + json_each(rdap_domain.nameservers) as n +where + rdap_domain.domain = 'steampipe.io'; + +``` + ### Check domain status codes Commonly used protections: -```sql +```sql+postgres select domain, s as atatus_code @@ -56,9 +97,21 @@ where domain = 'steampipe.io'; ``` +```sql+sqlite +select + rdap_domain.domain, + json_extract(s.value, '$') as status_code +from + rdap_domain, + json_each(rdap_domain.status) as s +where + rdap_domain.domain = 'steampipe.io'; + +``` + ### Get domain variants -```sql +```sql+postgres select domain, v ->> 'IDNTable' as idn_table, @@ -71,9 +124,22 @@ where domain = 'steampipe.io'; ``` +```sql+sqlite +select + rdap_domain.domain, + json_extract(v.value, '$.IDNTable') as idn_table, + json_extract(v.value, '$.Relation') as relation, + json_extract(v.value, '$.VariantNames') as variant_names +from + rdap_domain, + json_each(rdap_domain.variants) as v +where + rdap_domain.domain = 'steampipe.io'; +``` + ### Get event details of a domain -```sql +```sql+postgres select domain, e ->> 'Action' as action, @@ -87,12 +153,27 @@ where domain = 'steampipe.io'; ``` +```sql+sqlite +select + rdap_domain.domain, + json_extract(e.value, '$.Action') as action, + json_extract(e.value, '$.Actor') as actor, + json_extract(e.value, '$.Date') as date, + json_extract(e.value, '$.Links') as links +from + rdap_domain, + json_each(rdap_domain.events) as e +where + rdap_domain.domain = 'steampipe.io'; + +``` + ### Get entity details of a domain -```sql +```sql+postgres select domain, - domain_id, + handle, e ->> 'Handle' as handle, e ->> 'Port43' as port43, e -> 'AsEventActor' as as_event_actor, @@ -100,8 +181,8 @@ select e -> 'Autnums' as autnums, e -> 'Roles' as roles, e -> 'Notices' as notices, - e -> 'Remarks' as Remarks, - e -> 'Networks' as Networks + e -> 'Remarks' as remarks, + e -> 'Networks' as networks from rdap_domain, jsonb_array_elements(entities) as e @@ -109,9 +190,30 @@ where domain = 'steampipe.io'; ``` +```sql+sqlite +select + rdap_domain.domain, + rdap_domain.handle, + json_extract(e.value, '$.Handle') as handle, + json_extract(e.value, '$.Port43') as port43, + json_extract(e.value, '$.AsEventActor') as as_event_actor, + json_extract(e.value, '$.VCard') as v_card, + json_extract(e.value, '$.Autnums') as autnums, + json_extract(e.value, '$.Roles') as roles, + json_extract(e.value, '$.Notices') as notices, + json_extract(e.value, '$.Remarks') as remarks, + json_extract(e.value, '$.Networks') as networks +from + rdap_domain, + json_each(rdap_domain.entities) as e +where + rdap_domain.domain = 'steampipe.io'; + +``` + ### Get public IP details of a domain -```sql +```sql+postgres select domain, p ->> 'Type' as public_ip_type, @@ -123,30 +225,63 @@ where domain = 'steampipe.io'; ``` +```sql+sqlite +select + rdap_domain.domain, + json_extract(p.value, '$.Type') as public_ip_type, + json_extract(p.value, '$.Identifier') as public_ip_identifier +from + rdap_domain, + json_each(rdap_domain.public_ids) as p +where + rdap_domain.domain = 'steampipe.io'; + +``` + ### Get network information of a domain -```sql +```sql+postgres select domain, network ->> 'Handle' as network_handle, network ->> 'ObjectClassName' as network_object_class_name, - network ->> 'StartAddress' as networkStartAddress, - network ->> 'EndAddress' as networkEndAddress, - network ->> 'IPVersion' as networkIPVersion, - network ->> 'Name' as networkName, - network ->> 'Type' as networkType, - network ->> 'Country' as networkCountry, - network ->> 'ParentHandle' as networkParentHandle, - network -> 'Status' as networkStatus + network ->> 'StartAddress' as network_start_address, + network ->> 'EndAddress' as network_end_address, + network ->> 'IPVersion' as network_ip_version, + network ->> 'Name' as network_name, + network ->> 'Type' as network_type, + network ->> 'Country' as network_country, + network ->> 'ParentHandle' as network_parent_handle, + network -> 'Status' as network_status from rdap_domain where domain = 'steampipe.io'; ``` +```sql+sqlite +select + rdap_domain.domain, + json_extract(rdap_domain.network, '$.Handle') as network_handle, + json_extract(rdap_domain.network, '$.ObjectClassName') as network_object_class_name, + json_extract(rdap_domain.network, '$.StartAddress') as network_start_address, + json_extract(rdap_domain.network, '$.EndAddress') as network_end_address, + json_extract(rdap_domain.network, '$.IPVersion') as network_ip_version, + json_extract(rdap_domain.network, '$.Name') as network_name, + json_extract(rdap_domain.network, '$.Type') as network_type, + json_extract(rdap_domain.network, '$.Country') as network_country, + json_extract(rdap_domain.network, '$.ParentHandle') as network_parent_handle, + json_extract(rdap_domain.network, '$.Status') as network_status +from + rdap_domain +where + rdap_domain.domain = 'steampipe.io'; + +``` + ### Get secure DNS details of a domain -```sql +```sql+postgres select domain, secure_dns ->> 'ZoneSigned' as zone_signed, @@ -160,9 +295,24 @@ where domain = 'steampipe.io'; ``` +```sql+sqlite +select + rdap_domain.domain, + json_extract(rdap_domain.secure_dns, '$.ZoneSigned') as zone_signed, + json_extract(rdap_domain.secure_dns, '$.DelegationSigned') as delegation_signed, + json_extract(rdap_domain.secure_dns, '$.MaxSigLife') as max_sig_life, + json_extract(rdap_domain.secure_dns, '$.Ds') as data_structure, + json_extract(rdap_domain.secure_dns, '$.Keys') as keys +from + rdap_domain +where + rdap_domain.domain = 'steampipe.io'; + +``` + ### Get a domain remarks -```sql +```sql+postgres select domain, r ->> 'Title' as title, @@ -176,9 +326,24 @@ where domain = 'steampipe.io'; ``` +```sql+sqlite +select + rdap_domain.domain, + json_extract(r.value, '$.Title') as title, + json_extract(r.value, '$.Type') as type, + json_extract(r.value, '$.Description') as description, + json_extract(r.value, '$.Links') as links +from + rdap_domain, + json_each(rdap_domain.remarks) as r +where + rdap_domain.domain = 'steampipe.io'; + +``` + ### Working with multiple domains -```sql +```sql+postgres select domain, status @@ -192,3 +357,18 @@ where 'yahoo.com' ); ``` + +```sql+sqlite +select + domain, + status +from + rdap_domain +where + domain in ( + 'github.com', + 'google.com', + 'steampipe.io', + 'yahoo.com' + ); +``` \ No newline at end of file From 59dc57a3bb9cca7fe1c48863d5007eebdae61c1b Mon Sep 17 00:00:00 2001 From: ParthaI Date: Wed, 17 Jan 2024 20:53:21 +0530 Subject: [PATCH 8/8] Updated the doc by replacing the word whois to rdap and rearrange the tables in alphabetical order --- docs/tables/rdap_domain.md | 4 ++-- whois/plugin.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tables/rdap_domain.md b/docs/tables/rdap_domain.md index ea376de..e10f96f 100644 --- a/docs/tables/rdap_domain.md +++ b/docs/tables/rdap_domain.md @@ -1,5 +1,5 @@ --- -title: "Steampipe Table: rdap_domain - Query Whois Domains using SQL" +title: "Steampipe Table: rdap_domain - Query RDAP Domains using SQL" description: "Allows users to query RDAP domain registration data in a structured and standardized way." --- @@ -17,7 +17,7 @@ It's not practical to list all domains in the world, so this table requires a ## Examples -### Basic whois info +### Basic RDAP info ```sql+postgres select diff --git a/whois/plugin.go b/whois/plugin.go index b32d514..e3207ec 100644 --- a/whois/plugin.go +++ b/whois/plugin.go @@ -12,8 +12,8 @@ func Plugin(ctx context.Context) *plugin.Plugin { Name: "steampipe-plugin-whois", DefaultTransform: transform.FromGo().NullIfZero(), TableMap: map[string]*plugin.Table{ - "whois_domain": tableWhoisDomain(ctx), "rdap_domain": tableRdapDomain(ctx), + "whois_domain": tableWhoisDomain(ctx), }, } return p