github.com/someshkoli/terratest@v0.41.1/modules/dns-helper/dns_helper.go (about)

     1  // Package dns_helper contains helpers to interact with the Domain Name System.
     2  package dns_helper
     3  
     4  import (
     5  	"fmt"
     6  	"net"
     7  	"reflect"
     8  	"sort"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/gruntwork-io/terratest/modules/logger"
    13  	"github.com/gruntwork-io/terratest/modules/retry"
    14  	"github.com/gruntwork-io/terratest/modules/testing"
    15  	"github.com/miekg/dns"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  // DNSFindNameservers tries to find the NS record for the given FQDN, iterating down the domain hierarchy
    20  // until it founds the NS records and returns it. Fails if there's any error or no NS record is found up to the apex domain.
    21  func DNSFindNameservers(t testing.TestingT, fqdn string, resolvers []string) []string {
    22  	nameservers, err := DNSFindNameserversE(t, fqdn, resolvers)
    23  	require.NoError(t, err)
    24  	return nameservers
    25  }
    26  
    27  // DNSFindNameserversE tries to find the NS record for the given FQDN, iterating down the domain hierarchy
    28  // until it founds the NS records and returns it. Returns the last error if the apex domain is reached with no result.
    29  func DNSFindNameserversE(t testing.TestingT, fqdn string, resolvers []string) ([]string, error) {
    30  	var lookupFunc func(domain string) ([]string, error)
    31  
    32  	if resolvers == nil {
    33  		lookupFunc = func(domain string) ([]string, error) {
    34  			var nameservers []string
    35  			res, err := net.LookupNS(domain)
    36  			for _, ns := range res {
    37  				nameservers = append(nameservers, ns.Host)
    38  			}
    39  			return nameservers, err
    40  		}
    41  	} else {
    42  		lookupFunc = func(domain string) ([]string, error) {
    43  			var nameservers []string
    44  			res, err := DNSLookupE(t, DNSQuery{"NS", domain}, resolvers)
    45  			for _, r := range res {
    46  				if r.Type == "NS" {
    47  					nameservers = append(nameservers, r.Value)
    48  				}
    49  			}
    50  			return nameservers, err
    51  		}
    52  	}
    53  
    54  	parts := strings.Split(fqdn, ".")
    55  
    56  	var domain string
    57  	for i := range parts[:len(parts)-1] {
    58  		domain = strings.Join(parts[i:], ".")
    59  		res, err := lookupFunc(domain)
    60  
    61  		if len(res) > 0 {
    62  			var nameservers []string
    63  
    64  			for _, ns := range res {
    65  				nameservers = append(nameservers, strings.TrimSuffix(ns, "."))
    66  			}
    67  
    68  			logger.Logf(t, "FQDN %s belongs to domain %s, found NS record: %s", fqdn, domain, nameservers)
    69  			return nameservers, nil
    70  		}
    71  
    72  		if err != nil {
    73  			logger.Logf(t, err.Error())
    74  		}
    75  	}
    76  
    77  	err := &NSNotFoundError{fqdn, domain}
    78  	return nil, err
    79  }
    80  
    81  // DNSLookupAuthoritative gets authoritative answers for the specified record and type.
    82  // If resolvers are defined, uses them instead of the default system ones to find the authoritative nameservers.
    83  // Fails on any error from DNSLookupAuthoritativeE.
    84  func DNSLookupAuthoritative(t testing.TestingT, query DNSQuery, resolvers []string) DNSAnswers {
    85  	res, err := DNSLookupAuthoritativeE(t, query, resolvers)
    86  	require.NoError(t, err)
    87  	return res
    88  }
    89  
    90  // DNSLookupAuthoritativeE gets authoritative answers for the specified record and type.
    91  // If resolvers are defined, uses them instead of the default system ones to find the authoritative nameservers.
    92  // Returns NotFoundError when no answer found in any authoritative nameserver.
    93  // Returns any underlying error from individual lookups.
    94  func DNSLookupAuthoritativeE(t testing.TestingT, query DNSQuery, resolvers []string) (DNSAnswers, error) {
    95  	nameservers, err := DNSFindNameserversE(t, query.Name, resolvers)
    96  
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	return DNSLookupE(t, query, nameservers)
   102  }
   103  
   104  // DNSLookupAuthoritativeWithRetry repeatedly gets authoritative answers for the specified record and type
   105  // until ANY of the authoritative nameservers found replies with non-empty answer matching the expectedAnswers,
   106  // or until max retries has been exceeded.
   107  // If resolvers are defined, uses them instead of the default system ones to find the authoritative nameservers.
   108  // Fails on any error from DNSLookupAuthoritativeWithRetryE.
   109  func DNSLookupAuthoritativeWithRetry(t testing.TestingT, query DNSQuery, resolvers []string, maxRetries int, sleepBetweenRetries time.Duration) DNSAnswers {
   110  	res, err := DNSLookupAuthoritativeWithRetryE(t, query, resolvers, maxRetries, sleepBetweenRetries)
   111  	require.NoError(t, err)
   112  	return res
   113  }
   114  
   115  // DNSLookupAuthoritativeWithRetryE repeatedly gets authoritative answers for the specified record and type
   116  // until ANY of the authoritative nameservers found replies with non-empty answer matching the expectedAnswers,
   117  // or until max retries has been exceeded.
   118  // If resolvers are defined, uses them instead of the default system ones to find the authoritative nameservers.
   119  func DNSLookupAuthoritativeWithRetryE(t testing.TestingT, query DNSQuery, resolvers []string, maxRetries int, sleepBetweenRetries time.Duration) (DNSAnswers, error) {
   120  	res, err := retry.DoWithRetryInterfaceE(
   121  		t, fmt.Sprintf("DNSLookupAuthoritativeE %s record for %s using authoritative nameservers", query.Type, query.Name),
   122  		maxRetries, sleepBetweenRetries,
   123  		func() (interface{}, error) {
   124  			return DNSLookupAuthoritativeE(t, query, resolvers)
   125  		})
   126  
   127  	return res.(DNSAnswers), err
   128  }
   129  
   130  // DNSLookupAuthoritativeAll gets authoritative answers for the specified record and type.
   131  // All the authoritative nameservers found must give the same answers.
   132  // If resolvers are defined, uses them instead of the default system ones to find the authoritative nameservers.
   133  // Fails on any error from DNSLookupAuthoritativeAllE.
   134  func DNSLookupAuthoritativeAll(t testing.TestingT, query DNSQuery, resolvers []string) DNSAnswers {
   135  	res, err := DNSLookupAuthoritativeAllE(t, query, resolvers)
   136  	require.NoError(t, err)
   137  	return res
   138  }
   139  
   140  // DNSLookupAuthoritativeAllE gets authoritative answers for the specified record and type.
   141  // All the authoritative nameservers found must give the same answers.
   142  // If resolvers are defined, uses them instead of the default system ones to find the authoritative nameservers.
   143  // Returns InconsistentAuthoritativeError when any authoritative nameserver gives a different answer.
   144  // Returns any underlying error.
   145  func DNSLookupAuthoritativeAllE(t testing.TestingT, query DNSQuery, resolvers []string) (DNSAnswers, error) {
   146  	nameservers, err := DNSFindNameserversE(t, query.Name, resolvers)
   147  
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	var answers DNSAnswers
   153  
   154  	for _, ns := range nameservers {
   155  		res, err := DNSLookupE(t, query, []string{ns})
   156  
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  
   161  		if len(answers) > 0 {
   162  			if !reflect.DeepEqual(answers, res) {
   163  				err := &InconsistentAuthoritativeError{Query: query, Answers: res, Nameserver: ns, PreviousAnswers: answers}
   164  				return nil, err
   165  			}
   166  		} else {
   167  			answers = res
   168  		}
   169  	}
   170  
   171  	return answers, nil
   172  }
   173  
   174  // DNSLookupAuthoritativeAllWithRetry repeatedly sends DNS requests for the specified record and type,
   175  // until ALL authoritative nameservers reply with the exact same non-empty answers or until max retries has been exceeded.
   176  // If defined, uses the given resolvers instead of the default system ones to find the authoritative nameservers.
   177  // Fails when max retries has been exceeded.
   178  func DNSLookupAuthoritativeAllWithRetry(t testing.TestingT, query DNSQuery, resolvers []string, maxRetries int, sleepBetweenRetries time.Duration) {
   179  	_, err := DNSLookupAuthoritativeAllWithRetryE(t, query, resolvers, maxRetries, sleepBetweenRetries)
   180  	require.NoError(t, err)
   181  }
   182  
   183  // DNSLookupAuthoritativeAllWithRetryE repeatedly sends DNS requests for the specified record and type,
   184  // until ALL authoritative nameservers reply with the exact same non-empty answers or until max retries has been exceeded.
   185  // If defined, uses the given resolvers instead of the default system ones to find the authoritative nameservers.
   186  func DNSLookupAuthoritativeAllWithRetryE(t testing.TestingT, query DNSQuery, resolvers []string, maxRetries int, sleepBetweenRetries time.Duration) (DNSAnswers, error) {
   187  	res, err := retry.DoWithRetryInterfaceE(
   188  		t, fmt.Sprintf("DNSLookupAuthoritativeAllE %s record for %s using authoritative nameservers", query.Type, query.Name),
   189  		maxRetries, sleepBetweenRetries,
   190  		func() (interface{}, error) {
   191  			return DNSLookupAuthoritativeAllE(t, query, resolvers)
   192  		})
   193  
   194  	return res.(DNSAnswers), err
   195  }
   196  
   197  // DNSLookupAuthoritativeAllWithValidation gets authoritative answers for the specified record and type.
   198  // All the authoritative nameservers found must give the same answers and match the expectedAnswers.
   199  // If resolvers are defined, uses them instead of the default system ones to find the authoritative nameservers.
   200  // Fails on any underlying error from DNSLookupAuthoritativeAllWithValidationE.
   201  func DNSLookupAuthoritativeAllWithValidation(t testing.TestingT, query DNSQuery, resolvers []string, expectedAnswers DNSAnswers) {
   202  	err := DNSLookupAuthoritativeAllWithValidationE(t, query, resolvers, expectedAnswers)
   203  	require.NoError(t, err)
   204  }
   205  
   206  // DNSLookupAuthoritativeAllWithValidationE gets authoritative answers for the specified record and type.
   207  // All the authoritative nameservers found must give the same answers and match the expectedAnswers.
   208  // If resolvers are defined, uses them instead of the default system ones to find the authoritative nameservers.
   209  // Returns ValidationError when expectedAnswers differ from the obtained ones.
   210  // Returns any underlying error from DNSLookupAuthoritativeAllE.
   211  func DNSLookupAuthoritativeAllWithValidationE(t testing.TestingT, query DNSQuery, resolvers []string, expectedAnswers DNSAnswers) error {
   212  	expectedAnswers.Sort()
   213  
   214  	answers, err := DNSLookupAuthoritativeAllE(t, query, resolvers)
   215  
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	if !reflect.DeepEqual(answers, expectedAnswers) {
   221  		err := &ValidationError{Query: query, Answers: answers, ExpectedAnswers: expectedAnswers}
   222  		return err
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  // DNSLookupAuthoritativeAllWithValidationRetry repeatedly gets authoritative answers for the specified record and type
   229  // until ALL the authoritative nameservers found give the same answers and match the expectedAnswers,
   230  // or until max retries has been exceeded.
   231  // If resolvers are defined, uses them instead of the default system ones to find the authoritative nameservers.
   232  // Fails when max retries has been exceeded.
   233  func DNSLookupAuthoritativeAllWithValidationRetry(t testing.TestingT, query DNSQuery, resolvers []string, expectedAnswers DNSAnswers, maxRetries int, sleepBetweenRetries time.Duration) {
   234  	err := DNSLookupAuthoritativeAllWithValidationRetryE(t, query, resolvers, expectedAnswers, maxRetries, sleepBetweenRetries)
   235  	require.NoError(t, err)
   236  }
   237  
   238  // DNSLookupAuthoritativeAllWithValidationRetryE repeatedly gets authoritative answers for the specified record and type
   239  // until ALL the authoritative nameservers found give the same answers and match the expectedAnswers,
   240  // or until max retries has been exceeded.
   241  // If resolvers are defined, uses them instead of the default system ones to find the authoritative nameservers.
   242  func DNSLookupAuthoritativeAllWithValidationRetryE(t testing.TestingT, query DNSQuery, resolvers []string, expectedAnswers DNSAnswers, maxRetries int, sleepBetweenRetries time.Duration) error {
   243  	_, err := retry.DoWithRetryInterfaceE(
   244  		t, fmt.Sprintf("DNSLookupAuthoritativeAllWithValidationRetryE %s record for %s using authoritative nameservers", query.Type, query.Name),
   245  		maxRetries, sleepBetweenRetries,
   246  		func() (interface{}, error) {
   247  			return nil, DNSLookupAuthoritativeAllWithValidationE(t, query, resolvers, expectedAnswers)
   248  		})
   249  
   250  	return err
   251  }
   252  
   253  // DNSLookup sends a DNS query for the specified record and type using the given resolvers.
   254  // Fails on any error.
   255  // Supported record types: A, AAAA, CNAME, MX, NS, TXT
   256  func DNSLookup(t testing.TestingT, query DNSQuery, resolvers []string) DNSAnswers {
   257  	res, err := DNSLookupE(t, query, resolvers)
   258  	require.NoError(t, err)
   259  	return res
   260  }
   261  
   262  // DNSLookupE sends a DNS query for the specified record and type using the given resolvers.
   263  // Returns QueryTypeError when record type is not supported.
   264  // Returns any underlying error.
   265  // Supported record types: A, AAAA, CNAME, MX, NS, TXT
   266  func DNSLookupE(t testing.TestingT, query DNSQuery, resolvers []string) (DNSAnswers, error) {
   267  	if len(resolvers) == 0 {
   268  		err := &NoResolversError{}
   269  		return nil, err
   270  	}
   271  
   272  	var dnsAnswers DNSAnswers
   273  	var err error
   274  	for _, resolver := range resolvers {
   275  		dnsAnswers, err = dnsLookup(t, query, resolver)
   276  
   277  		if err == nil {
   278  			return dnsAnswers, nil
   279  		}
   280  	}
   281  
   282  	return nil, err
   283  }
   284  
   285  // dnsLookup sends a DNS query for the specified record and type using the given resolver.
   286  // Returns DNSAnswers to the DNSQuery.
   287  // If no records found, returns NotFoundError.
   288  func dnsLookup(t testing.TestingT, query DNSQuery, resolver string) (DNSAnswers, error) {
   289  	switch query.Type {
   290  	case "A", "AAAA", "CNAME", "MX", "NS", "TXT":
   291  	default:
   292  		err := &QueryTypeError{query.Type}
   293  		return nil, err
   294  	}
   295  
   296  	qType, ok := dns.StringToType[strings.ToUpper(query.Type)]
   297  	if !ok {
   298  		err := &QueryTypeError{query.Type}
   299  		return nil, err
   300  	}
   301  
   302  	if strings.LastIndex(resolver, ":") <= strings.LastIndex(resolver, "]") {
   303  		resolver += ":53"
   304  	}
   305  
   306  	c := new(dns.Client)
   307  	m := new(dns.Msg)
   308  	m.SetQuestion(dns.Fqdn(query.Name), qType)
   309  
   310  	in, _, err := c.Exchange(m, resolver)
   311  	if err != nil {
   312  		logger.Logf(t, "Error sending DNS query %s: %s", query, err)
   313  		return nil, err
   314  	}
   315  
   316  	if len(in.Answer) == 0 {
   317  		err := &NotFoundError{query, resolver}
   318  		return nil, err
   319  	}
   320  
   321  	var dnsAnswers DNSAnswers
   322  
   323  	for _, a := range in.Answer {
   324  		switch at := a.(type) {
   325  		case *dns.A:
   326  			dnsAnswers = append(dnsAnswers, DNSAnswer{"A", at.A.String()})
   327  		case *dns.AAAA:
   328  			dnsAnswers = append(dnsAnswers, DNSAnswer{"AAAA", at.AAAA.String()})
   329  		case *dns.CNAME:
   330  			dnsAnswers = append(dnsAnswers, DNSAnswer{"CNAME", at.Target})
   331  		case *dns.NS:
   332  			dnsAnswers = append(dnsAnswers, DNSAnswer{"NS", at.Ns})
   333  		case *dns.MX:
   334  			dnsAnswers = append(dnsAnswers, DNSAnswer{"MX", fmt.Sprintf("%d %s", at.Preference, at.Mx)})
   335  		case *dns.TXT:
   336  			for _, txt := range at.Txt {
   337  				dnsAnswers = append(dnsAnswers, DNSAnswer{"TXT", fmt.Sprintf(`"%s"`, txt)})
   338  			}
   339  		}
   340  	}
   341  
   342  	dnsAnswers.Sort()
   343  
   344  	return dnsAnswers, nil
   345  }
   346  
   347  // DNSQuery type
   348  type DNSQuery struct {
   349  	Type, Name string
   350  }
   351  
   352  // DNSAnswer type
   353  type DNSAnswer struct {
   354  	Type, Value string
   355  }
   356  
   357  func (a DNSAnswer) String() string {
   358  	return fmt.Sprintf("%s %s", a.Type, a.Value)
   359  }
   360  
   361  // DNSAnswers type
   362  type DNSAnswers []DNSAnswer
   363  
   364  // Sort sorts the answers by type and value
   365  func (a DNSAnswers) Sort() {
   366  	sort.Slice(a, func(i, j int) bool {
   367  		return a[i].Type < a[j].Type || a[i].Value < a[j].Value
   368  	})
   369  }