go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/network/resources/dnsshake/dnsshake.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package dnsshake
     5  
     6  import (
     7  	"net"
     8  	"os"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/cockroachdb/errors"
    14  	"github.com/miekg/dns"
    15  	"go.mondoo.com/cnquery/utils/multierr"
    16  )
    17  
    18  type DnsClient struct {
    19  	config *dns.ClientConfig
    20  	fqdn   string
    21  	sync   sync.Mutex
    22  }
    23  
    24  type DnsRecord struct {
    25  	// DNS name
    26  	Name string `json:"name"`
    27  	// Time-To-Live (TTL) in seconds
    28  	TTL int64 `json:"ttl"`
    29  	// DNS class
    30  	Class string `json:"class"`
    31  	// DNS type
    32  	Type string `json:"type"`
    33  	// Resource Data
    34  	RData []string `json:"rData"`
    35  	// DNS Response Code
    36  	RCode string `json:"rCode"`
    37  	// Error during dns request
    38  	Error error `json:"error"`
    39  }
    40  
    41  func New(fqdn string) (*DnsClient, error) {
    42  	// use Google DNS for now
    43  	config := &dns.ClientConfig{}
    44  	config.Servers = []string{"8.8.8.8", "8.8.4.4"}
    45  	config.Search = []string{}
    46  	config.Port = "53"
    47  	config.Ndots = 1
    48  	config.Timeout = 5
    49  	config.Attempts = 2
    50  
    51  	// try to load unix dns server
    52  	// TODO: this does not work on windows https://github.com/go-acme/lego/issues/1015
    53  	resolveFile := "/etc/resolv.conf"
    54  	_, err := os.Stat(resolveFile)
    55  	if err == nil {
    56  		rConfig, err := dns.ClientConfigFromFile(resolveFile)
    57  		if err == nil {
    58  			config = rConfig
    59  		}
    60  	}
    61  
    62  	return &DnsClient{
    63  		fqdn:   fqdn,
    64  		config: config,
    65  	}, nil
    66  }
    67  
    68  // stringToType is a map of strings to each RR type.
    69  var stringToType = map[string]uint16{
    70  	"A":          dns.TypeA,
    71  	"AAAA":       dns.TypeAAAA,
    72  	"AFSDB":      dns.TypeAFSDB,
    73  	"ANY":        dns.TypeANY,
    74  	"APL":        dns.TypeAPL,
    75  	"ATMA":       dns.TypeATMA,
    76  	"AVC":        dns.TypeAVC,
    77  	"AXFR":       dns.TypeAXFR,
    78  	"CAA":        dns.TypeCAA,
    79  	"CDNSKEY":    dns.TypeCDNSKEY,
    80  	"CDS":        dns.TypeCDS,
    81  	"CERT":       dns.TypeCERT,
    82  	"CNAME":      dns.TypeCNAME,
    83  	"CSYNC":      dns.TypeCSYNC,
    84  	"DHCID":      dns.TypeDHCID,
    85  	"DLV":        dns.TypeDLV,
    86  	"DNAME":      dns.TypeDNAME,
    87  	"DNSKEY":     dns.TypeDNSKEY,
    88  	"DS":         dns.TypeDS,
    89  	"EID":        dns.TypeEID,
    90  	"EUI48":      dns.TypeEUI48,
    91  	"EUI64":      dns.TypeEUI64,
    92  	"GID":        dns.TypeGID,
    93  	"GPOS":       dns.TypeGPOS,
    94  	"HINFO":      dns.TypeHINFO,
    95  	"HIP":        dns.TypeHIP,
    96  	"HTTPS":      dns.TypeHTTPS,
    97  	"ISDN":       dns.TypeISDN,
    98  	"IXFR":       dns.TypeIXFR,
    99  	"KEY":        dns.TypeKEY,
   100  	"KX":         dns.TypeKX,
   101  	"L32":        dns.TypeL32,
   102  	"L64":        dns.TypeL64,
   103  	"LOC":        dns.TypeLOC,
   104  	"LP":         dns.TypeLP,
   105  	"MAILA":      dns.TypeMAILA,
   106  	"MAILB":      dns.TypeMAILB,
   107  	"MB":         dns.TypeMB,
   108  	"MD":         dns.TypeMD,
   109  	"MF":         dns.TypeMF,
   110  	"MG":         dns.TypeMG,
   111  	"MINFO":      dns.TypeMINFO,
   112  	"MR":         dns.TypeMR,
   113  	"MX":         dns.TypeMX,
   114  	"NAPTR":      dns.TypeNAPTR,
   115  	"NID":        dns.TypeNID,
   116  	"NIMLOC":     dns.TypeNIMLOC,
   117  	"NINFO":      dns.TypeNINFO,
   118  	"NS":         dns.TypeNS,
   119  	"NSEC":       dns.TypeNSEC,
   120  	"NSEC3":      dns.TypeNSEC3,
   121  	"NSEC3PARAM": dns.TypeNSEC3PARAM,
   122  	"NULL":       dns.TypeNULL,
   123  	"NXT":        dns.TypeNXT,
   124  	"None":       dns.TypeNone,
   125  	"OPENPGPKEY": dns.TypeOPENPGPKEY,
   126  	"OPT":        dns.TypeOPT,
   127  	"PTR":        dns.TypePTR,
   128  	"PX":         dns.TypePX,
   129  	"RKEY":       dns.TypeRKEY,
   130  	"RP":         dns.TypeRP,
   131  	"RRSIG":      dns.TypeRRSIG,
   132  	"RT":         dns.TypeRT,
   133  	"Reserved":   dns.TypeReserved,
   134  	"SIG":        dns.TypeSIG,
   135  	"SMIMEA":     dns.TypeSMIMEA,
   136  	"SOA":        dns.TypeSOA,
   137  	"SPF":        dns.TypeSPF,
   138  	"SRV":        dns.TypeSRV,
   139  	"SSHFP":      dns.TypeSSHFP,
   140  	"SVCB":       dns.TypeSVCB,
   141  	"TA":         dns.TypeTA,
   142  	"TALINK":     dns.TypeTALINK,
   143  	"TKEY":       dns.TypeTKEY,
   144  	"TLSA":       dns.TypeTLSA,
   145  	"TSIG":       dns.TypeTSIG,
   146  	"TXT":        dns.TypeTXT,
   147  	"UID":        dns.TypeUID,
   148  	"UINFO":      dns.TypeUINFO,
   149  	"UNSPEC":     dns.TypeUNSPEC,
   150  	"URI":        dns.TypeURI,
   151  	"X25":        dns.TypeX25,
   152  	"ZONEMD":     dns.TypeZONEMD,
   153  	"NSAP-PTR":   dns.TypeNSAPPTR,
   154  }
   155  
   156  func (d *DnsClient) Query(dnsTypes ...string) (map[string]DnsRecord, error) {
   157  	if len(dnsTypes) == 0 {
   158  		for k := range stringToType {
   159  			dnsTypes = append(dnsTypes, k)
   160  		}
   161  	}
   162  
   163  	workers := sync.WaitGroup{}
   164  	var errs multierr.Errors
   165  
   166  	res := map[string]DnsRecord{}
   167  	for i := range dnsTypes {
   168  		dnsType := dnsTypes[i]
   169  
   170  		workers.Add(1)
   171  		go func() {
   172  			defer workers.Done()
   173  
   174  			records, err := d.queryDnsType(d.fqdn, dnsType)
   175  			if err != nil {
   176  				d.sync.Lock()
   177  				errs.Add(err)
   178  				d.sync.Unlock()
   179  				return
   180  			}
   181  
   182  			d.sync.Lock()
   183  			for k := range records {
   184  				res[k] = records[k]
   185  			}
   186  			d.sync.Unlock()
   187  		}()
   188  	}
   189  
   190  	workers.Wait()
   191  	return res, errs.Deduplicate()
   192  }
   193  
   194  func (d *DnsClient) queryDnsType(fqdn string, t string) (map[string]DnsRecord, error) {
   195  	dnsType, ok := stringToType[t]
   196  	if !ok {
   197  		return nil, errors.New("unknown dns type")
   198  	}
   199  	dnsTypText := dns.Type(dnsType).String()
   200  
   201  	res := map[string]DnsRecord{}
   202  
   203  	c := &dns.Client{}
   204  	m := &dns.Msg{}
   205  	m.SetEdns0(4096, false)
   206  	m.SetQuestion(dns.Fqdn(fqdn), dnsType)
   207  	m.RecursionDesired = true
   208  
   209  	r, _, err := c.Exchange(m, net.JoinHostPort(d.config.Servers[0], d.config.Port))
   210  	if err != nil {
   211  		res[dnsTypText] = DnsRecord{
   212  			Type:  dnsTypText,
   213  			Error: err,
   214  		}
   215  		return res, nil
   216  	}
   217  
   218  	if r.Rcode != dns.RcodeSuccess {
   219  		res[dnsTypText] = DnsRecord{
   220  			Type:  dnsTypText,
   221  			RCode: dns.RcodeToString[r.Rcode],
   222  		}
   223  		return res, nil
   224  	}
   225  
   226  	// extract more information if dns request was successful
   227  	for i := range r.Answer {
   228  		a := r.Answer[i]
   229  
   230  		typ := dns.Type(a.Header().Rrtype).String()
   231  
   232  		var rec DnsRecord
   233  
   234  		rec, ok := res[typ]
   235  		if !ok {
   236  			rec = DnsRecord{
   237  				Name:  a.Header().Name,
   238  				Type:  typ,
   239  				Class: dns.Class(a.Header().Class).String(),
   240  				TTL:   int64(a.Header().Ttl),
   241  				RData: []string{},
   242  				RCode: dns.RcodeToString[r.Rcode],
   243  			}
   244  		}
   245  
   246  		switch v := a.(type) {
   247  		case *dns.A:
   248  			rec.RData = append(rec.RData, v.A.String())
   249  		case *dns.NS:
   250  			rec.RData = append(rec.RData, v.Ns)
   251  		case *dns.MD:
   252  			rec.RData = append(rec.RData, v.Md)
   253  		case *dns.MF:
   254  			rec.RData = append(rec.RData, v.Mf)
   255  		case *dns.CNAME:
   256  			rec.RData = append(rec.RData, v.Target)
   257  		case *dns.MB:
   258  			rec.RData = append(rec.RData, v.Mb)
   259  		case *dns.MG:
   260  			rec.RData = append(rec.RData, v.Mg)
   261  		case *dns.MR:
   262  			rec.RData = append(rec.RData, v.Mr)
   263  		case *dns.NULL:
   264  			rec.RData = append(rec.RData, v.Data)
   265  		case *dns.PTR:
   266  			rec.RData = append(rec.RData, v.Ptr)
   267  		case *dns.TXT:
   268  			rec.RData = append(rec.RData, strings.Join(v.Txt, ""))
   269  		case *dns.EID:
   270  			rec.RData = append(rec.RData, v.Endpoint)
   271  		case *dns.NIMLOC:
   272  			rec.RData = append(rec.RData, v.Locator)
   273  		case *dns.SPF:
   274  			rec.RData = append(rec.RData, strings.Join(v.Txt, ""))
   275  		case *dns.UINFO:
   276  			rec.RData = append(rec.RData, v.Uinfo)
   277  		case *dns.UID:
   278  			rec.RData = append(rec.RData, strconv.FormatInt(int64(v.Uid), 10))
   279  		case *dns.GID:
   280  			rec.RData = append(rec.RData, strconv.FormatInt(int64(v.Gid), 10))
   281  		case *dns.EUI48:
   282  			strconv.FormatInt(int64(v.Address), 10)
   283  		case *dns.EUI64:
   284  			strconv.FormatInt(int64(v.Address), 10)
   285  		case *dns.AVC:
   286  			rec.RData = append(rec.RData, strings.Join(v.Txt, ""))
   287  		default:
   288  			rec.RData = append(rec.RData, strings.TrimPrefix(v.String(), v.Header().String()))
   289  		}
   290  
   291  		res[typ] = rec
   292  	}
   293  	return res, nil
   294  }