adassault 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b131722d9575f757c8cdf53cac4e4419d9968d5eeb11a66dabb4ef542fe85a5c
4
- data.tar.gz: bf1de9fd4ddc2dd772980bc93945fd23309e5c7634e79bcb1171530e5b48db9c
3
+ metadata.gz: 1388914e53e2194919173dcbe9cc809e1fff39b3843f8768459343ac68812fa3
4
+ data.tar.gz: 9c81ba10c6f2fc8dc0858b2bea49e3b399515d4d471b2255e469a9430f386425
5
5
  SHA512:
6
- metadata.gz: bf803a032eb88ccc4444eebbf32c8edf94dc724ceec842e82855e6e75943e75e84e01514633f33f09565017dae052698d23ae7973b895863298f9318131eddbe
7
- data.tar.gz: 31826ce2fed8ac57816ecbcfcd3063ea985f94f5dbdc908fcb0f51af5ae89d518866a20daad76e73f78ed1b2b0ecfeaf1ea823b2517591d2c29e8d566fbbba89
6
+ metadata.gz: aabce798d3bf5b4bd1831751ee0416852f2904b439781d1e74fd41a3793dba6c17152ba991b6d334bf6b4507f4e892389697c7a90b8da527a292992faf26472e
7
+ data.tar.gz: d754da4dcf1516025be52d5e49a594f04d117717c8f82058b12899332a651a01c177387aa43a4ff80e7fb1c852a614c2f4f6f787f516e7e2d79f8726502b20b8
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'adassault/cli/dns/duzdu/baseaction'
4
+
5
+ module ADAssault
6
+ class CLI
7
+ module DNS
8
+ module DUZDU
9
+ # command: `ada dns duzdu add`
10
+ class Add < BaseAction
11
+ def run(args)
12
+ res = @duz.addv4(args[0], args[1])
13
+ @duz.display(res, self.class.command_name)
14
+ end
15
+
16
+ class << self
17
+ def description
18
+ 'Add a DNS A record (IPv4) via dynamic updates'
19
+ end
20
+
21
+ def long_description
22
+ '<name>: DNS name, A record. The domain is automatically appended, e.g. test ➡️ test.example.org' \
23
+ "\n\n<ip>: IP address."
24
+ end
25
+
26
+ def arguments
27
+ %i[<name> <ip>]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ # AD Assault lib
4
+ require 'adassault/dns'
5
+ # options / arguments parsing lib
6
+ require 'gli'
7
+
8
+ module ADAssault
9
+ class CLI
10
+ extend GLI::App
11
+
12
+ module DNS
13
+ module DUZDU
14
+ # stuff common to all duzdu's sub-commands
15
+ class BaseAction
16
+ def initialize(options)
17
+ parent_options = options.dig(GLI::Command::PARENT, GLI::Command::PARENT)
18
+ dns_opts = parent_options[:nameserver].nil? ? nil : { nameserver: [parent_options[:nameserver]] }
19
+ @duz = ADAssault::DNS::DUZDU.new(parent_options[:domain], dns_opts)
20
+ end
21
+
22
+ def run(_args)
23
+ raise NotImplementedError, 'Sub-class must implement this method'
24
+ end
25
+
26
+ class << self
27
+ def command_name
28
+ name.split('::').last.downcase
29
+ end
30
+
31
+ def description
32
+ raise NotImplementedError, 'Sub-class must implement this method'
33
+ end
34
+
35
+ def long_description
36
+ raise NotImplementedError, 'Sub-class must implement this method'
37
+ end
38
+
39
+ def arguments
40
+ raise NotImplementedError, 'Sub-class must implement this method'
41
+ end
42
+
43
+ def register(parent_command)
44
+ parent_command.desc description
45
+ parent_command.long_desc long_description unless long_description.empty?
46
+ arguments.each { |argument| parent_command.arg argument } # won't register args if arguments is empty
47
+ parent_command.command command_name.to_sym do |cmd|
48
+ cmd.action do |_global_options, options, args|
49
+ action = new(options)
50
+ action.run(args)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'adassault/cli/dns/duzdu/baseaction'
4
+
5
+ module ADAssault
6
+ class CLI
7
+ module DNS
8
+ module DUZDU
9
+ # command: `ada dns duzdu check`
10
+ class Check < BaseAction
11
+ def run(_args)
12
+ res = @duz.checkv4
13
+ @duz.display(res, self.class.command_name)
14
+ end
15
+
16
+ class << self
17
+ def description
18
+ 'Check if unsecure dynamic updates are allowed (IPv4)'
19
+ end
20
+
21
+ def long_description
22
+ ''
23
+ end
24
+
25
+ def arguments
26
+ []
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'adassault/cli/dns/duzdu/baseaction'
4
+
5
+ module ADAssault
6
+ class CLI
7
+ module DNS
8
+ module DUZDU
9
+ # command: `ada dns duzdu delete`
10
+ class Delete < BaseAction
11
+ def run(args)
12
+ res = @duz.deletev4(args[0])
13
+ @duz.display(res, self.class.command_name)
14
+ end
15
+
16
+ class << self
17
+ def description
18
+ 'Remove a DNS A record (IPv4) via dynamic updates'
19
+ end
20
+
21
+ def long_description
22
+ '<name>: DNS name, A record. The domain is automatically appended, e.g. test ➡️ test.example.org'
23
+ end
24
+
25
+ def arguments
26
+ %i[<name>]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'adassault/cli/dns/duzdu/baseaction'
4
+
5
+ module ADAssault
6
+ class CLI
7
+ module DNS
8
+ module DUZDU
9
+ # command: `ada dns duzdu get`
10
+ class Get < BaseAction
11
+ def run(args)
12
+ name = args.first
13
+ res = @duz.getv4(name)
14
+ @duz.display_record(name, res)
15
+ end
16
+
17
+ class << self
18
+ def description
19
+ 'Get the value(s) of a DNS A record (IPv4) from the domain'
20
+ end
21
+
22
+ def long_description
23
+ '<name>: DNS name, A record. The domain is automatically appended, e.g. test ➡️ test.example.org'
24
+ end
25
+
26
+ def arguments
27
+ %i[<name>]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'adassault/cli/dns/duzdu/baseaction'
4
+
5
+ module ADAssault
6
+ class CLI
7
+ module DNS
8
+ module DUZDU
9
+ # command: `ada dns duzdu replace`
10
+ class Replace < BaseAction
11
+ def run(args)
12
+ res = @duz.replacev4(args[0], args[1])
13
+ @duz.display(res, self.class.command_name)
14
+ end
15
+
16
+ class << self
17
+ def description
18
+ 'Change the value of an existing DNS A record (IPv4) via dynamic updates'
19
+ end
20
+
21
+ def long_description
22
+ '<name>: DNS name, A record. The domain is automatically appended, e.g. test ➡️ test.example.org' \
23
+ "\n\n<ip>: IP address."
24
+ end
25
+
26
+ def arguments
27
+ %i[<name> <ip>]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # AD Assault CLI: DUZDU sub-commands
4
+ require 'adassault/cli/dns/duzdu/add'
5
+ require 'adassault/cli/dns/duzdu/check'
6
+ require 'adassault/cli/dns/duzdu/delete'
7
+ require 'adassault/cli/dns/duzdu/get'
8
+ require 'adassault/cli/dns/duzdu/replace'
9
+
10
+ module ADAssault
11
+ class CLI
12
+ # command: `ada dns`
13
+ module DNS
14
+ # command: `ada dns duzdu`
15
+ module DUZDU
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,47 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'adassault/dns'
3
+ require 'adassault/cli/dns/duzdu'
4
4
  require 'gli'
5
5
 
6
6
  module ADAssault
7
7
  class CLI
8
8
  extend GLI::App
9
9
 
10
- desc 'DNS related commands'
10
+ # dns
11
11
  # since 0.0.1
12
+ desc 'DNS related commands'
12
13
  command :dns do |dns|
13
- dns.desc 'Spot all domain controllers in a Microsoft Active Directory environment'
14
+ dns.flag %i[d domain], desc: 'Active Directory domain.', type: String, required: true, arg_name: 'DOMAIN'
15
+ dns.flag %i[s nameserver name-server],
16
+ desc: 'The IP address of the domain DNS server. If not provided uses your system DNS.',
17
+ type: String, required: false, arg_name: 'IP_ADDRESS'
18
+
19
+ # dns find_dcs
14
20
  # since 0.0.1
21
+ dns.desc 'Spot all domain controllers in a Microsoft Active Directory environment'
15
22
  dns.command :find_dcs do |find_dcs|
16
- find_dcs.flag %i[d domain], desc: 'Active Directory domain.', type: String, required: false
17
- find_dcs.flag %i[s nameserver name-server],
18
- desc: 'The IP address of the domain DNS server. If not provided uses your system DNS.',
19
- type: String
20
23
  find_dcs.action do |_global_options, options, _args|
21
- dns_opts = options[:nameserver].nil? ? nil : { nameserver: [options[:nameserver]] }
22
- dcd = ADAssault::DNS::FindDCs.new(options[:domain], dns_opts)
24
+ parent_options = options[GLI::Command::PARENT]
25
+ dns_opts = parent_options[:nameserver].nil? ? nil : { nameserver: [parent_options[:nameserver]] }
26
+ dcd = ADAssault::DNS::FindDCs.new(parent_options[:domain], dns_opts)
23
27
  dcd.display
24
28
  end
25
29
  end
26
30
 
27
- dns.desc 'TODO'
28
- dns.command :entry_add do |entry_add|
29
- entry_add.action do |_global_options, _options, _args|
30
- puts 'entry_add'
31
- end
32
- end
33
-
34
- dns.desc 'TODO'
35
- dns.command :entry_delete do |entry_delete|
36
- entry_delete.action do |_global_options, _options, _args|
37
- puts 'entry_delete'
38
- end
39
- end
40
-
41
- dns.desc 'TODO'
42
- dns.command :entry_update do |entry_update|
43
- entry_update.action do |_global_options, _options, _args|
44
- puts 'entry_update'
31
+ # dns duzdu
32
+ # since 0.0.2
33
+ dns.desc 'DNS unsecure zone dynamic update (DUZDU)'
34
+ dns.command :duzdu do |duzdu|
35
+ %w[add check delete get replace].each do |sub_command|
36
+ DNS::DUZDU.const_get(sub_command.capitalize).register(duzdu)
45
37
  end
46
38
  end
47
39
  end
data/lib/adassault/cli.rb CHANGED
@@ -14,6 +14,7 @@ module ADAssault
14
14
  extend GLI::App
15
15
 
16
16
  program_desc 'ADAsault desc'
17
+ arguments :strict
17
18
  subcommand_option_handling :normal
18
19
  sort_help :alpha # :manually
19
20
  version ADAssault::VERSION
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ipaddr'
4
+ require 'random/formatter'
5
+
6
+ require 'dnsruby'
7
+ require 'paint'
8
+
9
+ module ADAssault
10
+ module DNS
11
+ # **DNS unsecure zone dynamic update (DUZDU).**
12
+ #
13
+ # On a misconfigured MS DNS zone, one can abuse dynamic updates to perform MiTM attacks in a very stealth way.
14
+ #
15
+ # On a Windows server with the DNS role, the `DSPROPERTY_ZONE_ALLOW_UPDATE` property defines whether dynamic updates are allowed. See [Microsoft - MS-DNSP - 2.3.2.1.1 Property Id](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/3af63871-0cc4-4179-916c-5caade55a8f3).
16
+ # The possible values (`fAllowUpdate`) are (see [Microsoft - MS-DNSP - 2.2.5.2.4.1 DNS_RPC_ZONE_INFO_W2K](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/e8651544-0fbb-4038-8232-375ff2d8a55e)):
17
+ #
18
+ # - `0` (`ZONE_UPDATE_OFF`): No updates are allowed for the zone.
19
+ # - `1` (`ZONE_UPDATE_UNSECURE`): All updates (secure and unsecure) are allowed for the zone.
20
+ # - `2` (`ZONE_UPDATE_SECURE`): The zone only allows secure updates, that is, DNS packet MUST have a TSIG [RFC2845] present in the additional section.
21
+ #
22
+ # One can see the property when connected to the DNS server (near 100% of times on the domain controller), with the command: `dnscmd.exe /ZoneInfo <example: thm.local>` and the value of `update`.
23
+ # Another option with the GUI, is to launch `DNS Manager` on the Windows server, then unfold the tree until the DNS zone, right click on it, select `Properties`, on the `General` tab, see the value of the select fields named `Dynamic updates`.
24
+ # Of course it is also possible to check remotly by trying to create a record. (see {DUZDU#checkv4})
25
+ #
26
+ # References:
27
+ #
28
+ # - [[FR] ANSSI - Points de contrôle Active Directory - Zones DNS mal configurées (vuln_dnszone_bad_prop)](https://www.cert.ssi.gouv.fr/uploads/ad_checklist.html)
29
+ # - [[FR] ANSSI - Bulletin d'alerte du CERT-FR - Multiples vulnérabilités dans Microsoft DNS server - CERTFR-2021-ALE-005](https://www.cert.ssi.gouv.fr/alerte/CERTFR-2021-ALE-005/)
30
+ # - [[EN] RFC 2136 - Dynamic Updates in the Domain Name System (DNS UPDATE)](https://datatracker.ietf.org/doc/html/rfc2136)
31
+ # - [[EN] RFC 2845 - Secret Key Transaction Authentication for DNS (TSIG)](https://datatracker.ietf.org/doc/html/rfc2845)
32
+ # - [[EN] RFC 3007 - Secure Domain Name System (DNS) Dynamic Update](https://datatracker.ietf.org/doc/html/rfc3007)
33
+ # - [[EN] RFC 4033 - DNS Security Introduction and Requirements](https://datatracker.ietf.org/doc/html/rfc4033)
34
+ # - [[EN] RFC 4034 - Resource Records for the DNS Security Extensions](https://datatracker.ietf.org/doc/html/rfc4034)
35
+ # - [[EN] RFC 4035 - Protocol Modifications for the DNS Security Extensions](https://datatracker.ietf.org/doc/html/rfc4035)
36
+ # - [[EN] RFC 6895 - Domain Name System (DNS) IANA Considerations](https://datatracker.ietf.org/doc/html/rfc6895)
37
+ # - [[EN] RFC 8945 - Secret Key Transaction Authentication for DNS (TSIG)](https://datatracker.ietf.org/doc/html/rfc8945)
38
+ # @since 0.0.2
39
+ class DUZDU
40
+ # **Create the DUZDU object**
41
+ # @param ad_domain [String] the Active Directory domain to work on.
42
+ # @param dns_opts [Hash] options for the DNS resolver. See [Resolv::DNS.new](https://ruby-doc.org/3.3.0/stdlibs/resolv/Resolv/DNS.html#method-c-new).
43
+ # @option dns_opts [Array|String] :nameserver the DNS server to contact
44
+ # @example
45
+ # duz = ADAssault::DNS::DUZDU.new('THM.local', nameserver: ['10.10.30.209'])
46
+ def initialize(ad_domain, dns_opts = nil)
47
+ @ad_domain = ad_domain
48
+ @dns_opts = dns_opts
49
+ end
50
+
51
+ # **Change the value of an existing DNS A record (IPv4) via dynamic updates**
52
+ #
53
+ # It will remove and recreate the record.
54
+ #
55
+ # **Warning**: if several entries exist for the same record, they will all be replaced by the new value.
56
+ # @param name [String] DNS name, A record. The domain is automatically appended, e.g. `test` ➡️ `test.example.org`
57
+ # @param ip [String] IP address
58
+ # @return [TrueClass|FalseClass] `true` if the record was changed successfully
59
+ # @example
60
+ # duz.replacev4('noraj', '10.10.56.126') # => true
61
+ def replacev4(name, ip)
62
+ deletev4(name)
63
+ addv4(name, ip)
64
+ end
65
+
66
+ # **Add a DNS A record (IPv4) via dynamic updates**
67
+ #
68
+ # **Warning**: adding 2nd value the same name will result in two entries for the same record, not updating the name (for that use {replacev4}).
69
+ # @param name [String] DNS name, A record. The domain is automatically appended, e.g. `test` ➡️ `test.example.org`
70
+ # @param ip [String] IP address
71
+ # @return [TrueClass|FalseClass] `true` if the record was added successfully
72
+ # @example
73
+ # duz.addv4('noraj', '10.10.56.125') # => true
74
+ def addv4(name, ip)
75
+ update = Dnsruby::Update.new(@ad_domain)
76
+ update.add("#{name}.#{@ad_domain}.", 'A', 300, ip)
77
+ send_update(update)
78
+ end
79
+
80
+ # **Remove a DNS A record (IPv4) via dynamic updates**
81
+ #
82
+ # **Warning**: if several entries exist for the same record, they will all be deleted.
83
+ # @param name [String] DNS name, A record. The domain is automatically appended, e.g. `test` ➡️ `test.example.org`
84
+ # @return [TrueClass|FalseClass] `true` if the record was removed successfully
85
+ # @example
86
+ # duz.deletev4('noraj') # => true
87
+ def deletev4(name)
88
+ update = Dnsruby::Update.new(@ad_domain)
89
+ update.present("#{name}.#{@ad_domain}", 'A')
90
+ update.delete("#{name}.#{@ad_domain}", 'A')
91
+ send_update(update)
92
+ end
93
+
94
+ # rubocop:disable Metrics/AbcSize
95
+
96
+ # **Check if unsecure dynamic updates are allowed (IPv4)**
97
+ #
98
+ # It will try to create a random IPv4 record in the zone and remove it.
99
+ # @return [TrueClass|FalseClass] `true` means unsecure dynamic updates are enabled
100
+ # @example
101
+ # duz.checkv4 # => false
102
+ def checkv4
103
+ networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'].map { |x| IPAddr.new(x) }
104
+ network = networks.sample
105
+ name = Random.uuid_v4
106
+ ip = IPAddr.new(rand(network.to_range.begin.succ.to_i..network.to_range.end.to_i - 1), network.family)
107
+ created = addv4(name, ip)
108
+ # if created
109
+ # deletev4(name)
110
+ # true
111
+ # else
112
+ # false
113
+ # end
114
+ created ? deletev4(name) || true : false
115
+ end
116
+ # rubocop:enable Metrics/AbcSize
117
+
118
+ # **Get the value(s) of a DNS A record (IPv4)**
119
+ # @param name [String] DNS name, A record. The domain is automatically appended, e.g. `test` ➡️ `test.example.org`
120
+ # @return [Array<String>] the IP address(es) as string(s)
121
+ # @example
122
+ # duz.getv4('noraj') # => ["10.10.56.128", "10.10.56.126"]
123
+ def getv4(name)
124
+ Dnsruby::DNS.open(@dns_opts) do |dns|
125
+ ress = dns.getresources("#{name}.#{@ad_domain}", 'A')
126
+ ress.map { |x| x.address.to_s }
127
+ rescue Dnsruby::NXDomain # The requested domain does not exist
128
+ ['']
129
+ end
130
+ end
131
+
132
+ # Display a CLI-friendly output showing if the executed method was successful or not
133
+ # @param success [TrueClass|FalseClass] result of the command
134
+ # @param cmd [String] name of the executed command
135
+ # @return [nil]
136
+ def display(success, cmd)
137
+ # allowed_methods = DUZDU.public_instance_methods(false) - [:display]
138
+ # success = allowed_methods.include?(cmd.to_sym) ? send(cmd) : nil
139
+ message = if success
140
+ Paint["✅ #{cmd} was executed successfully",
141
+ 'green']
142
+ else
143
+ Paint["❌ #{cmd} was unsuccessful", 'red']
144
+ end
145
+ puts message
146
+ end
147
+
148
+ # Display a CLI-friendly output formating the DNS record with its FQDN and IP addresses.
149
+ # @param name [String] record name, e.g. the argument of {getv4}
150
+ # @param ips [Array<String>] list of IP addresses, e.g. the return value of {getv4}
151
+ # @return [nil]
152
+ def display_record(name, ips)
153
+ fqdn = "#{name}.#{@ad_domain}"
154
+ puts "#{Paint[fqdn, 'cyan']} - #{Paint[ips.join(', '), 'aquamarine']}"
155
+ end
156
+
157
+ protected
158
+
159
+ # **Send a DNS update request via dynamic updates**
160
+ # @param update [Dnsruby::Update]
161
+ # @return [TrueClass|FalseClass] `true` if the update succeeded
162
+ # @example see {addv4} and {deletev4}
163
+ def send_update(update)
164
+ res = Dnsruby::Resolver.new(@dns_opts)
165
+ begin
166
+ res.send_message(update)
167
+ true
168
+ rescue Dnsruby::ResolvError, Dnsruby::ResolvTimeout => e
169
+ puts "Update failed: #{e}"
170
+ false
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
data/lib/adassault/dns.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'adassault/dns/duzdu'
3
4
  require 'adassault/dns/find_dcs'
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ADAssault
4
4
  # Version of ADAssault library and app
5
- VERSION = '0.0.1'
5
+ VERSION = '0.0.2'
6
6
  end
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adassault
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandre ZANNI
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-23 00:00:00.000000000 Z
11
+ date: 2024-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dnsruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.72'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.72.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.72'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.72.1
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: gli
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -53,7 +73,15 @@ files:
53
73
  - lib/adassault.rb
54
74
  - lib/adassault/cli.rb
55
75
  - lib/adassault/cli/dns.rb
76
+ - lib/adassault/cli/dns/duzdu.rb
77
+ - lib/adassault/cli/dns/duzdu/add.rb
78
+ - lib/adassault/cli/dns/duzdu/baseaction.rb
79
+ - lib/adassault/cli/dns/duzdu/check.rb
80
+ - lib/adassault/cli/dns/duzdu/delete.rb
81
+ - lib/adassault/cli/dns/duzdu/get.rb
82
+ - lib/adassault/cli/dns/duzdu/replace.rb
56
83
  - lib/adassault/dns.rb
84
+ - lib/adassault/dns/duzdu.rb
57
85
  - lib/adassault/dns/find_dcs.rb
58
86
  - lib/adassault/version.rb
59
87
  homepage: https://noraj.github.io/adassault/