From 59c416a5abf2eb802e61e3930e4a0f801e0bd709 Mon Sep 17 00:00:00 2001 From: Tim Birkett Date: Sun, 19 Feb 2017 07:01:12 +0800 Subject: [PATCH] Add ability to specify a server on DNS resources (#170) * Add ability to specify a server on DNS resources Originally, the DNS resource used the golang net library to perform a net.lookupHost on DNS resourcces. net.lookupHost uses the local DNS resolver which includes a host file lookup (still useful) as well as DNS lookup. The following behaviour has been implemented: Without the server attribute set, net.lookupHost is used which is great for testing out host entries or just general hostname resolution. With a server attribute set the github.com/miekg/dns library is used to query the server (on port 53). * Add the ability to query different types of DNS record * Fix attribute name in docs * Add ability to specify a server on DNS resources Originally, the DNS resource used the golang net library to perform a net.lookupHost on DNS resourcces. net.lookupHost uses the local DNS resolver which includes a host file lookup (still useful) as well as DNS lookup. The following behaviour has been implemented: Without the server attribute set, net.lookupHost is used which is great for testing out host entries or just general hostname resolution. With a server attribute set the github.com/miekg/dns library is used to query the server (on port 53). * Add the ability to query different types of DNS record * Fix attribute name in docs * Ensure only the appropriate DNS record types are returned * update tests to use dns records that won't change * Amend DNS integration tests * Fix formatting with goimports * Add retry logic to DNSLookup * Comment out retry code for testing build on Travis * Retry 3 times before returning error * Fix bug in DNS lookup code resolveable / returning error. * Remove unnecessary code * Amend func name in comment * Minor format changes --- add.go | 1 + cmd/goss/goss.go | 4 + docs/manual.md | 42 ++- glide.lock | 36 +-- glide.yaml | 1 + .../goss/alpine3/goss-expected-q.json | 35 +++ .../goss/alpine3/goss-expected.json | 59 +++++ .../goss/centos7/goss-expected-q.json | 35 +++ .../goss/centos7/goss-expected.json | 59 +++++ integration-tests/goss/generate_goss.sh | 14 + integration-tests/goss/goss-shared.json | 74 +++++- .../goss/precise/goss-expected-q.json | 35 +++ .../goss/precise/goss-expected.json | 59 +++++ .../goss/wheezy/goss-expected-q.json | 35 +++ .../goss/wheezy/goss-expected.json | 59 +++++ integration-tests/test.sh | 4 +- resource/dns.go | 16 +- system/dns.go | 246 ++++++++++++++++-- util/config.go | 1 + 19 files changed, 778 insertions(+), 37 deletions(-) diff --git a/add.go b/add.go index b40a9c52b..5ad9b5605 100644 --- a/add.go +++ b/add.go @@ -20,6 +20,7 @@ func AddResources(fileName, resourceName string, keys []string, c *cli.Context) Timeout: int(c.Duration("timeout") / time.Millisecond), AllowInsecure: c.Bool("insecure"), NoFollowRedirects: c.Bool("no-follow-redirects"), + Server: c.String("server"), } var gossConfig GossConfig diff --git a/cmd/goss/goss.go b/cmd/goss/goss.go index 304140098..e0aa09faa 100644 --- a/cmd/goss/goss.go +++ b/cmd/goss/goss.go @@ -227,6 +227,10 @@ func main() { Name: "timeout", Value: 500 * time.Millisecond, }, + cli.StringFlag{ + Name: "server", + Usage: "The IP address of a DNS server to query", + }, }, Action: func(c *cli.Context) error { goss.AddResources(c.GlobalString("gossfile"), "DNS", c.Args(), c) diff --git a/docs/manual.md b/docs/manual.md index d7b6f1a66..992786d7d 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -430,12 +430,51 @@ dns: # required attributes resolveable: true # optional attributes + server: 8.8.8.8 addrs: - 127.0.0.1 - ::1 timeout: 500 # in milliseconds ``` +With the server attribute set, it is possible to validate the following types of DNS record: + +- A +- AAAA +- CNAME +- MX +- NS +- PTR +- SRV +- TXT + +To validate specific DNS address types, prepend the hostname with the type and a colon, a few examples: + +```yaml +dns: + # Validate a CNAME record + CNAME:dnstest.github.io: + resolveable: true + server: 8.8.8.8 + addrs: + - "github.map.fastly.net." + + # Validate a PTR record + PTR:8.8.8.8: + resolveable: true + server: 8.8.8.8 + addrs: + - "google-public-dns-a.google.com." + + # Validate and SRV record + SRV:_https._tcp.dnstest.io: + resolveable: true + server: 8.8.8.8 + addrs: + - "0 5 443 a.dnstest.io." + - "10 10 443 b.dnstest.io." +``` + Please note that if you want `localhost` to **only** resolve `127.0.0.1` you'll need to use [Advanced Matchers](#advanced-matchers) ```yaml @@ -447,7 +486,6 @@ dns: timeout: 500 # in milliseconds ``` - ### file Validates the state of a file @@ -673,6 +711,7 @@ Goss supports advanced matchers by converting json input to [gomega](https://ons ### Examples Validate that user `nobody` has a `uid` that is less than `500` and that they are **only** a member of the `nobody` group. + ```yaml user: nobody: @@ -684,6 +723,7 @@ user: ``` Matchers can be nested for more complex logic, for example you can ensure that you have 3 kernel versions installed and none of them are `4.1.0`: + ```yaml package: kernel: diff --git a/glide.lock b/glide.lock index c65ed2e42..295d37259 100644 --- a/glide.lock +++ b/glide.lock @@ -1,28 +1,32 @@ -hash: 6bbda86455edefb24ebb6fe039ffc6a5f21076c615f69f9cb17c06d3ae0ae404 -updated: 2016-07-28T02:14:47.93162924-04:00 +hash: ee9c9147007d86588eb760fe7985f4017b3798255d99b23d3240c6a0d8b33291 +updated: 2016-11-09T02:23:29.857676716Z imports: - name: github.com/achanda/go-sysctl version: 6be7678c45d2052640e72060e4f5db6165b1ecab - name: github.com/aelsabbahy/GOnetstat version: edf89f784e0876818dc19f7744a16742a0a66f16 - name: github.com/cheekybits/genny - version: 670ed678f7799e3ead194eaf9a0848920a494889 + version: e8e29e67948b15c64e60d6617182c18cf7eead7f subpackages: - generic - name: github.com/docker/docker - version: 0f5c9d301b9b1cca66b3ea0f9dec3b5317d3686d + version: 383a2f046b16c9f79d2fb800844e6550cc784871 subpackages: - pkg/mount - name: github.com/fatih/color - version: 76d423163af754ff6423d2d9be0057fbf03c57c2 + version: bf82308e8c8546dc2b945157173eb8a959ae9505 +- name: github.com/mattn/go-colorable + version: d228849504861217f796da67fae4f6e347643f15 - name: github.com/mattn/go-isatty - version: 7fcbc72f853b92b5720db4a6b8482be612daef24 + version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8 +- name: github.com/miekg/dns + version: 58f52c57ce9df13460ac68200cef30a008b9c468 - name: github.com/mitchellh/go-ps - version: e6c6068076470196af082b1ff896e24a51a87b2a + version: e2d21980687ce16e58469d98dcee92d27fbbd7fb - name: github.com/oleiade/reflections - version: 632977f98cd34d217c4b57d0840ec188b3d3dcaf + version: 0e86b3c98b2ff33e30c85cfe97d9a63d439fe7eb - name: github.com/onsi/gomega - version: c72df929b80ef4930aaa75d5e486887ff2f3e06a + version: ff4bc6b6f9f5affa66635cd04d31d2a7ee21ffd6 subpackages: - types - internal/assertion @@ -36,15 +40,17 @@ imports: - matchers/support/goraph/node - matchers/support/goraph/util - name: github.com/opencontainers/runc - version: 4e6893b05a6aa723daa5f9194361032c6beaca25 + version: 8779fa57eb4a810a7360187dfa5e168a76cf5d21 subpackages: - libcontainer/user - name: github.com/patrickmn/go-cache version: 1881a9bccb818787f68c52bfba648c6cf34c34fa -- name: github.com/shiena/ansicolor - version: d445752f6f66f9cd987722f109149f6799cdf1de - name: github.com/urfave/cli - version: 01857ac33766ce0c93856370626f9799281c14f4 + version: d86a009f5e13f83df65d0d6cee9a2e3f1445f0da +- name: golang.org/x/sys + version: 9a2e24c3733eddc63871eda99f253e2db29bd3b9 + subpackages: + - unix - name: gopkg.in/yaml.v2 - version: f7716cbe52baa25d2e9b0d0da546fcf909fc16b4 -devImports: [] + version: a5b47d31c556af34a302ce5d659e6fea44d90de0 +testImports: [] diff --git a/glide.yaml b/glide.yaml index 12ede9aaa..9c8f01fab 100644 --- a/glide.yaml +++ b/glide.yaml @@ -20,3 +20,4 @@ import: subpackages: - pkg/mount - package: github.com/patrickmn/go-cache +- package: github.com/miekg/dns diff --git a/integration-tests/goss/alpine3/goss-expected-q.json b/integration-tests/goss/alpine3/goss-expected-q.json index de1410f03..4531c0be7 100644 --- a/integration-tests/goss/alpine3/goss-expected-q.json +++ b/integration-tests/goss/alpine3/goss-expected-q.json @@ -82,6 +82,41 @@ } }, "dns": { + "CNAME:c.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "MX:dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "NS:dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "PTR:8.8.8.8": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "SRV:_https._tcp.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "TXT:txt._test.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "ip6.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, "localhost": { "resolveable": true, "timeout": 1000 diff --git a/integration-tests/goss/alpine3/goss-expected.json b/integration-tests/goss/alpine3/goss-expected.json index 5a5e84aa6..1abb4ab90 100644 --- a/integration-tests/goss/alpine3/goss-expected.json +++ b/integration-tests/goss/alpine3/goss-expected.json @@ -98,6 +98,65 @@ } }, "dns": { + "CNAME:c.dnstest.io": { + "resolveable": true, + "addrs": [ + "a.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "MX:dnstest.io": { + "resolveable": true, + "addrs": [ + "10 b.dnstest.io.", + "5 a.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "NS:dnstest.io": { + "resolveable": true, + "addrs": [ + "ns1.dnstest.io.", + "ns2.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "PTR:8.8.8.8": { + "resolveable": true, + "addrs": [ + "google-public-dns-a.google.com." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "SRV:_https._tcp.dnstest.io": { + "resolveable": true, + "addrs": [ + "0 5 443 a.dnstest.io.", + "10 10 443 b.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "TXT:txt._test.dnstest.io": { + "resolveable": true, + "addrs": [ + "Hello DNS" + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "ip6.dnstest.io": { + "resolveable": true, + "addrs": [ + "2404:6800:4001:807::200e" + ], + "timeout": 1000, + "server": "8.8.8.8" + }, "localhost": { "resolveable": true, "addrs": [ diff --git a/integration-tests/goss/centos7/goss-expected-q.json b/integration-tests/goss/centos7/goss-expected-q.json index a41642afb..8b12bfe5c 100644 --- a/integration-tests/goss/centos7/goss-expected-q.json +++ b/integration-tests/goss/centos7/goss-expected-q.json @@ -82,6 +82,41 @@ } }, "dns": { + "CNAME:c.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "MX:dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "NS:dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "PTR:8.8.8.8": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "SRV:_https._tcp.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "TXT:txt._test.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "ip6.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, "localhost": { "resolveable": true, "timeout": 1000 diff --git a/integration-tests/goss/centos7/goss-expected.json b/integration-tests/goss/centos7/goss-expected.json index 2fd5c8b61..052f79bc4 100644 --- a/integration-tests/goss/centos7/goss-expected.json +++ b/integration-tests/goss/centos7/goss-expected.json @@ -105,6 +105,65 @@ } }, "dns": { + "CNAME:c.dnstest.io": { + "resolveable": true, + "addrs": [ + "a.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "MX:dnstest.io": { + "resolveable": true, + "addrs": [ + "10 b.dnstest.io.", + "5 a.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "NS:dnstest.io": { + "resolveable": true, + "addrs": [ + "ns1.dnstest.io.", + "ns2.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "PTR:8.8.8.8": { + "resolveable": true, + "addrs": [ + "google-public-dns-a.google.com." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "SRV:_https._tcp.dnstest.io": { + "resolveable": true, + "addrs": [ + "0 5 443 a.dnstest.io.", + "10 10 443 b.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "TXT:txt._test.dnstest.io": { + "resolveable": true, + "addrs": [ + "Hello DNS" + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "ip6.dnstest.io": { + "resolveable": true, + "addrs": [ + "2404:6800:4001:807::200e" + ], + "timeout": 1000, + "server": "8.8.8.8" + }, "localhost": { "resolveable": true, "addrs": [ diff --git a/integration-tests/goss/generate_goss.sh b/integration-tests/goss/generate_goss.sh index 273206ff7..7dcba2327 100755 --- a/integration-tests/goss/generate_goss.sh +++ b/integration-tests/goss/generate_goss.sh @@ -34,6 +34,20 @@ goss a "${args[@]}" group $user foobar goss a "${args[@]}" command "echo 'hi'" foobar +goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 CNAME:c.dnstest.io + +goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 MX:dnstest.io + +goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 NS:dnstest.io + +goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 PTR:8.8.8.8 + +goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 SRV:_https._tcp.dnstest.io + +goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 TXT:txt._test.dnstest.io + +goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 ip6.dnstest.io + goss a "${args[@]}" dns --timeout 1s localhost goss a "${args[@]}" process $package foobar diff --git a/integration-tests/goss/goss-shared.json b/integration-tests/goss/goss-shared.json index 3606ec0a3..eb3321341 100644 --- a/integration-tests/goss/goss-shared.json +++ b/integration-tests/goss/goss-shared.json @@ -77,13 +77,85 @@ } }, "dns": { + "CNAME:c.dnstest.io": { + "resolveable": true, + "addrs": [ + "a.dnstest.io." + ], + "timeout": 2000, + "server": "8.8.8.8" + }, + "c.dnstest.io": { + "resolveable": true, + "addrs": [ + "192.30.252.153" + ], + "timeout": 2000, + "server": "8.8.8.8" + }, + "MX:dnstest.io": { + "resolveable": true, + "addrs": [ + "10 b.dnstest.io.", + "5 a.dnstest.io." + ], + "timeout": 2000, + "server": "8.8.8.8" + }, + "NS:dnstest.io": { + "resolveable": true, + "addrs": [ + "ns1.dnstest.io.", + "ns2.dnstest.io." + ], + "timeout": 2000, + "server": "8.8.8.8" + }, + "PTR:8.8.8.8": { + "resolveable": true, + "addrs": [ + "google-public-dns-a.google.com." + ], + "timeout": 2000, + "server": "8.8.8.8" + }, + "SRV:_https._tcp.dnstest.io": { + "resolveable": true, + "addrs": [ + "0 5 443 a.dnstest.io.", + "10 10 443 b.dnstest.io." + ], + "timeout": 2000, + "server": "8.8.8.8" + }, + "TXT:txt._test.dnstest.io": { + "resolveable": true, + "addrs": [ + "Hello DNS" + ], + "timeout": 2000, + "server": "8.8.8.8" + }, + "ip6.dnstest.io": { + "resolveable": true, + "addrs": [ + "2404:6800:4001:807::200e" + ], + "timeout": 2000, + "server": "8.8.8.8" + }, "localhost": { "resolveable": true, "addrs": [ "127.0.0.1", "::1" ], - "timeout": 1000 + "timeout": 2000 + }, + "dnstest.io": { + "resolveable": true, + "server": "8.8.8.8", + "timeout": 2000 } }, "process": { diff --git a/integration-tests/goss/precise/goss-expected-q.json b/integration-tests/goss/precise/goss-expected-q.json index a9f7ec9f7..37bad6fef 100644 --- a/integration-tests/goss/precise/goss-expected-q.json +++ b/integration-tests/goss/precise/goss-expected-q.json @@ -82,6 +82,41 @@ } }, "dns": { + "CNAME:c.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "MX:dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "NS:dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "PTR:8.8.8.8": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "SRV:_https._tcp.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "TXT:txt._test.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "ip6.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, "localhost": { "resolveable": true, "timeout": 1000 diff --git a/integration-tests/goss/precise/goss-expected.json b/integration-tests/goss/precise/goss-expected.json index 7834074f9..762941248 100644 --- a/integration-tests/goss/precise/goss-expected.json +++ b/integration-tests/goss/precise/goss-expected.json @@ -105,6 +105,65 @@ } }, "dns": { + "CNAME:c.dnstest.io": { + "resolveable": true, + "addrs": [ + "a.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "MX:dnstest.io": { + "resolveable": true, + "addrs": [ + "10 b.dnstest.io.", + "5 a.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "NS:dnstest.io": { + "resolveable": true, + "addrs": [ + "ns1.dnstest.io.", + "ns2.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "PTR:8.8.8.8": { + "resolveable": true, + "addrs": [ + "google-public-dns-a.google.com." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "SRV:_https._tcp.dnstest.io": { + "resolveable": true, + "addrs": [ + "0 5 443 a.dnstest.io.", + "10 10 443 b.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "TXT:txt._test.dnstest.io": { + "resolveable": true, + "addrs": [ + "Hello DNS" + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "ip6.dnstest.io": { + "resolveable": true, + "addrs": [ + "2404:6800:4001:807::200e" + ], + "timeout": 1000, + "server": "8.8.8.8" + }, "localhost": { "resolveable": true, "addrs": [ diff --git a/integration-tests/goss/wheezy/goss-expected-q.json b/integration-tests/goss/wheezy/goss-expected-q.json index 302fc39b4..a2221a005 100644 --- a/integration-tests/goss/wheezy/goss-expected-q.json +++ b/integration-tests/goss/wheezy/goss-expected-q.json @@ -82,6 +82,41 @@ } }, "dns": { + "CNAME:c.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "MX:dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "NS:dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "PTR:8.8.8.8": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "SRV:_https._tcp.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "TXT:txt._test.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, + "ip6.dnstest.io": { + "resolveable": true, + "timeout": 1000, + "server": "8.8.8.8" + }, "localhost": { "resolveable": true, "timeout": 1000 diff --git a/integration-tests/goss/wheezy/goss-expected.json b/integration-tests/goss/wheezy/goss-expected.json index 64b3a41b4..c30b08607 100644 --- a/integration-tests/goss/wheezy/goss-expected.json +++ b/integration-tests/goss/wheezy/goss-expected.json @@ -105,6 +105,65 @@ } }, "dns": { + "CNAME:c.dnstest.io": { + "resolveable": true, + "addrs": [ + "a.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "MX:dnstest.io": { + "resolveable": true, + "addrs": [ + "10 b.dnstest.io.", + "5 a.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "NS:dnstest.io": { + "resolveable": true, + "addrs": [ + "ns1.dnstest.io.", + "ns2.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "PTR:8.8.8.8": { + "resolveable": true, + "addrs": [ + "google-public-dns-a.google.com." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "SRV:_https._tcp.dnstest.io": { + "resolveable": true, + "addrs": [ + "0 5 443 a.dnstest.io.", + "10 10 443 b.dnstest.io." + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "TXT:txt._test.dnstest.io": { + "resolveable": true, + "addrs": [ + "Hello DNS" + ], + "timeout": 1000, + "server": "8.8.8.8" + }, + "ip6.dnstest.io": { + "resolveable": true, + "addrs": [ + "2404:6800:4001:807::200e" + ], + "timeout": 1000, + "server": "8.8.8.8" + }, "localhost": { "resolveable": true, "addrs": [ diff --git a/integration-tests/test.sh b/integration-tests/test.sh index e564a0f53..39e1c4d87 100755 --- a/integration-tests/test.sh +++ b/integration-tests/test.sh @@ -44,9 +44,9 @@ out=$(docker_exec "/goss/$os/goss-linux-$arch" -g "/goss/$os/goss.json" validate echo "$out" if [[ $os == "arch" ]]; then - egrep -q 'Count: 37, Failed: 0' <<<"$out" + egrep -q 'Count: 54, Failed: 0' <<<"$out" else - egrep -q 'Count: 53, Failed: 0' <<<"$out" + egrep -q 'Count: 70, Failed: 0' <<<"$out" fi if [[ ! $os == "arch" ]]; then diff --git a/resource/dns.go b/resource/dns.go index 329d6cb9b..c91263878 100644 --- a/resource/dns.go +++ b/resource/dns.go @@ -1,6 +1,7 @@ package resource import ( + "strings" "github.com/aelsabbahy/goss/system" "github.com/aelsabbahy/goss/util" ) @@ -12,6 +13,7 @@ type DNS struct { Resolveable matcher `json:"resolveable" yaml:"resolveable"` Addrs matcher `json:"addrs,omitempty" yaml:"addrs,omitempty"` Timeout int `json:"timeout" yaml:"timeout"` + Server string `json:"server,omitempty" yaml:"server,omitempty"` } func (d *DNS) ID() string { return d.Host } @@ -25,7 +27,8 @@ func (d *DNS) Validate(sys *system.System) []TestResult { if d.Timeout == 0 { d.Timeout = 500 } - sysDNS := sys.NewDNS(d.Host, sys, util.Config{Timeout: d.Timeout}) + + sysDNS := sys.NewDNS(d.Host, sys, util.Config{Timeout: d.Timeout, Server: d.Server}) var results []TestResult results = append(results, ValidateValue(d, "resolveable", d.Resolveable, sysDNS.Resolveable, skip)) @@ -39,12 +42,21 @@ func (d *DNS) Validate(sys *system.System) []TestResult { } func NewDNS(sysDNS system.DNS, config util.Config) (*DNS, error) { - host := sysDNS.Host() + var host string + if sysDNS.Qtype() != "" { + host = strings.Join([]string{sysDNS.Qtype(), sysDNS.Host()}, ":") + } else { + host = sysDNS.Host() + } + resolveable, err := sysDNS.Resolveable() + server := sysDNS.Server() + d := &DNS{ Host: host, Resolveable: resolveable, Timeout: config.Timeout, + Server: server, } if !contains(config.IgnoreList, "addrs") { addrs, _ := sysDNS.Addrs() diff --git a/system/dns.go b/system/dns.go index 38551a276..b2d3c6390 100644 --- a/system/dns.go +++ b/system/dns.go @@ -4,9 +4,12 @@ import ( "fmt" "net" "sort" + "strconv" + "strings" "time" "github.com/aelsabbahy/goss/util" + "github.com/miekg/dns" ) type DNS interface { @@ -14,6 +17,8 @@ type DNS interface { Addrs() ([]string, error) Resolveable() (bool, error) Exists() (bool, error) + Server() string + Qtype() string } type DefDNS struct { @@ -23,12 +28,24 @@ type DefDNS struct { Timeout int loaded bool err error + server string + qtype string } func NewDefDNS(host string, system *System, config util.Config) DNS { + var h string + var t string + if len(strings.Split(host, ":")) > 1 { + h = strings.Split(host, ":")[1] + t = strings.Split(host, ":")[0] + } else { + h = host + } return &DefDNS{ - host: host, + host: h, Timeout: config.Timeout, + server: config.Server, + qtype: t, } } @@ -36,27 +53,39 @@ func (d *DefDNS) Host() string { return d.host } +func (d *DefDNS) Server() string { + return d.server +} + +func (d *DefDNS) Qtype() string { + return d.qtype +} + func (d *DefDNS) setup() error { if d.loaded { return d.err } d.loaded = true - addrs, err := lookupHost(d.host, d.Timeout) - if err != nil || len(addrs) == 0 { - d.resolveable = false - d.addrs = []string{} - // DNSError is resolvable == false, ignore error - if _, ok := err.(*net.DNSError); ok { - return nil + for i := 0; i < 3; i++ { + addrs, err := DNSlookup(d.host, d.server, d.qtype, d.Timeout) + if err != nil || len(addrs) == 0 { + d.resolveable = false + d.addrs = []string{} + // DNSError is resolvable == false, ignore error + if _, ok := err.(*net.DNSError); ok { + return nil + } + d.err = err + continue } - d.err = err - return d.err + sort.Strings(addrs) + d.resolveable = true + d.addrs = addrs + d.err = nil + return nil } - sort.Strings(addrs) - d.resolveable = true - d.addrs = addrs - return nil + return d.err } func (d *DefDNS) Addrs() ([]string, error) { @@ -76,12 +105,42 @@ func (d *DefDNS) Exists() (bool, error) { return false, nil } -func lookupHost(host string, timeout int) ([]string, error) { +func DNSlookup(host string, server string, qtype string, timeout int) ([]string, error) { c1 := make(chan []string, 1) e1 := make(chan error, 1) timeoutD := time.Duration(timeout) * time.Millisecond + + var addrs []string + var err error go func() { - addrs, err := net.LookupHost(host) + if server != "" { + c := new(dns.Client) + c.Timeout = timeoutD + m := new(dns.Msg) + + switch qtype { + case "A": + addrs, err = LookupA(host, server, c, m) + case "AAAA": + addrs, err = LookupAAAA(host, server, c, m) + case "PTR": + addrs, err = LookupPTR(host, server, c, m) + case "CNAME": + addrs, err = LookupCNAME(host, server, c, m) + case "MX": + addrs, err = LookupMX(host, server, c, m) + case "NS": + addrs, err = LookupNS(host, server, c, m) + case "SRV": + addrs, err = LookupSRV(host, server, c, m) + case "TXT": + addrs, err = LookupTXT(host, server, c, m) + default: + addrs, err = LookupHost(host, server, c, m) + } + } else { + addrs, err = net.LookupHost(host) + } if err != nil { e1 <- err } @@ -96,3 +155,158 @@ func lookupHost(host string, timeout int) ([]string, error) { return nil, fmt.Errorf("DNS lookup timed out (%s)", timeoutD) } } + +// A and AAAA record lookup - similar to net.LookupHost +func LookupHost(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) { + a, _ := LookupA(host, server, c, m) + aaaa, _ := LookupAAAA(host, server, c, m) + addrs = append(a, aaaa...) + + return +} + +// A record lookup +func LookupA(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) { + m.SetQuestion(dns.Fqdn(host), dns.TypeA) + r, _, err := c.Exchange(m, net.JoinHostPort(server, "53")) + if err != nil { + return nil, err + } + + for _, ans := range r.Answer { + if t, ok := ans.(*dns.A); ok { + addrs = append(addrs, t.A.String()) + } + } + + return +} + +// AAAA (IPv6) record lookup +func LookupAAAA(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) { + m.SetQuestion(dns.Fqdn(host), dns.TypeAAAA) + r, _, err := c.Exchange(m, net.JoinHostPort(server, "53")) + if err != nil { + return nil, err + } + + for _, ans := range r.Answer { + if t, ok := ans.(*dns.AAAA); ok { + addrs = append(addrs, t.AAAA.String()) + } + } + + return +} + +// CNAME record lookup +func LookupCNAME(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) { + m.SetQuestion(dns.Fqdn(host), dns.TypeCNAME) + r, _, err := c.Exchange(m, net.JoinHostPort(server, "53")) + if err != nil { + return nil, err + } + + for _, ans := range r.Answer { + if t, ok := ans.(*dns.CNAME); ok { + addrs = append(addrs, t.Target) + } + } + + return +} + +// MX record lookup +func LookupMX(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) { + m.SetQuestion(dns.Fqdn(host), dns.TypeMX) + r, _, err := c.Exchange(m, net.JoinHostPort(server, "53")) + if err != nil { + return nil, err + } + + for _, ans := range r.Answer { + if t, ok := ans.(*dns.MX); ok { + mxstring := strconv.Itoa(int(t.Preference)) + " " + t.Mx + addrs = append(addrs, mxstring) + } + } + + return +} + +// NS record lookup +func LookupNS(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) { + m.SetQuestion(dns.Fqdn(host), dns.TypeNS) + r, _, err := c.Exchange(m, net.JoinHostPort(server, "53")) + if err != nil { + return nil, err + } + + for _, ans := range r.Answer { + if t, ok := ans.(*dns.NS); ok { + addrs = append(addrs, t.Ns) + } + } + + return +} + +// SRV record lookup +func LookupSRV(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) { + m.SetQuestion(dns.Fqdn(host), dns.TypeSRV) + r, _, err := c.Exchange(m, net.JoinHostPort(server, "53")) + if err != nil { + return nil, err + } + + for _, ans := range r.Answer { + if t, ok := ans.(*dns.SRV); ok { + prio := strconv.Itoa(int(t.Priority)) + weight := strconv.Itoa(int(t.Weight)) + port := strconv.Itoa(int(t.Port)) + srvrec := strings.Join([]string{prio, weight, port, t.Target}, " ") + addrs = append(addrs, srvrec) + } + } + + return +} + +// TXT record lookup +func LookupTXT(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) { + m.SetQuestion(dns.Fqdn(host), dns.TypeTXT) + r, _, err := c.Exchange(m, net.JoinHostPort(server, "53")) + if err != nil { + return nil, err + } + + for _, ans := range r.Answer { + if t, ok := ans.(*dns.TXT); ok { + addrs = append(addrs, t.Txt...) + } + } + + return +} + +// PTR record lookup +func LookupPTR(addr string, server string, c *dns.Client, m *dns.Msg) (name []string, err error) { + + reverse, err := dns.ReverseAddr(addr) + if err != nil { + return nil, err + } + + m.SetQuestion(reverse, dns.TypePTR) + + r, _, err := c.Exchange(m, net.JoinHostPort(server, "53")) + if err != nil { + return nil, err + } + + for _, ans := range r.Answer { + name = append(name, ans.(*dns.PTR).Ptr) + } + + return +} diff --git a/util/config.go b/util/config.go index 8099ffdb1..4229ccb13 100644 --- a/util/config.go +++ b/util/config.go @@ -5,4 +5,5 @@ type Config struct { Timeout int AllowInsecure bool NoFollowRedirects bool + Server string }