1 #!powershell
2 
3 # Copyright: (c) 2020 VMware, Inc. All Rights Reserved.
4 # SPDX-License-Identifier: GPL-3.0-only
5 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6 
7 #AnsibleRequires -CSharpUtil Ansible.Basic
8 
9 $spec = @{
10     options = @{
11         name = @{ type = "str"; required = $true }
12         type = @{ type = "str"; choices = "primary", "secondary", "forwarder", "stub" }
13         replication = @{ type = "str"; choices = "forest", "domain", "legacy", "none" }
14         dynamic_update = @{ type = "str"; choices = "secure", "none", "nonsecureandsecure" }
15         state = @{ type = "str"; choices = "absent", "present"; default = "present" }
16         forwarder_timeout = @{ type = "int" }
17         dns_servers = @{ type = "list"; elements = "str" }
18     }
19     supports_check_mode = $true
20 }
21 
22 $module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
23 $check_mode = $module.CheckMode
24 
25 $name = $module.Params.name
26 $type = $module.Params.type
27 $replication = $module.Params.replication
28 $dynamic_update = $module.Params.dynamic_update
29 $state = $module.Params.state
30 $dns_servers = $module.Params.dns_servers
31 $forwarder_timeout = $module.Params.forwarder_timeout
32 
33 $parms = @{ name = $name }
34 
Get-DnsZoneObject()35 Function Get-DnsZoneObject {
36     Param([PSObject]$Object)
37     $parms = @{
38         name = $Object.ZoneName.toLower()
39         type = $Object.ZoneType.toLower()
40         paused = $Object.IsPaused
41         shutdown = $Object.IsShutdown
42     }
43 
44     if ($Object.DynamicUpdate) { $parms.dynamic_update = $Object.DynamicUpdate.toLower() }
45     if ($Object.IsReverseLookupZone) { $parms.reverse_lookup = $Object.IsReverseLookupZone }
46     if ($Object.ZoneType -like 'forwarder' ) { $parms.forwarder_timeout = $Object.ForwarderTimeout }
47     if ($Object.MasterServers) { $parms.dns_servers = $Object.MasterServers.IPAddressToString }
48     if (-not $Object.IsDsIntegrated) {
49         $parms.replication = "none"
50         $parms.zone_file = $Object.ZoneFile
51     } else {
52         $parms.replication = $Object.ReplicationScope.toLower()
53     }
54 
55     return $parms | Sort-Object
56 }
57 
Compare-DnsZonenull58 Function Compare-DnsZone {
59     Param(
60         [PSObject]$Original,
61         [PSObject]$Updated)
62 
63     if ($Original -eq $false) { return $false }
64     $props = @('ZoneType', 'DynamicUpdate', 'IsDsIntegrated', 'MasterServers', 'ForwarderTimeout', 'ReplicationScope')
65     $x = Compare-Object $Original $Updated -Property $props
66     $x.Count -eq 0
67 }
68 
69 # attempt import of module
70 Try { Import-Module DnsServer }
71 Catch { $module.FailJson("The DnsServer module failed to load properly: $($_.Exception.Message)", $_) }
72 
73 Try {
74     # determine current zone state
75     $current_zone = Get-DnsServerZone -name $name
76     $module.Diff.before = Get-DnsZoneObject -Object $current_zone
77     if (-not $type) { $type = $current_zone.ZoneType.toLower() }
78     if ($current_zone.ZoneType -like $type) { $current_zone_type_match = $true }
79     # check for fast fails
80     if ($current_zone.ReplicationScope -like 'none' -and $replication -in @('legacy', 'forest', 'domain')) {
81         $module.FailJson("Converting a file backed DNS zone to Active Directory integrated zone is unsupported")
82     }
83     if ($current_zone.ReplicationScope -in @('legacy', 'forest', 'domain') -and $replication -like 'none') {
84         $module.FailJson("Converting Active Directory integrated zone to a file backed DNS zone is unsupported")
85     }
86     if ($current_zone.IsDsIntegrated -eq $false -and $parms.DynamicUpdate -eq 'secure') {
87         $module.FailJson("The secure dynamic update option is only available for Active Directory integrated zones")
88     }
89 } Catch {
90     $module.Diff.before = ""
91     $current_zone = $false
92 }
93 
94 if ($state -eq "present") {
95     # parse replication/zonefile
96     if (-not $replication -and $current_zone) {
97         $parms.ReplicationScope = $current_zone.ReplicationScope
98     } elseif ((($replication -eq 'none') -or (-not $replication)) -and (-not $current_zone)) {
99         $parms.ZoneFile = "$name.dns"
100     } elseif (($replication -eq 'none') -and ($current_zone)) {
101         $parms.ZoneFile = "$name.dns"
102     } else {
103         $parms.ReplicationScope = $replication
104     }
105     # parse param
106     if ($dynamic_update) { $parms.DynamicUpdate = $dynamic_update }
107     if ($dns_servers) { $parms.MasterServers = $dns_servers }
108     if ($type -in @('stub','forwarder','secondary') -and -not $current_zone -and -not $dns_servers) {
109         $module.FailJson("The dns_servers param is required when creating new stub, forwarder or secondary zones")
110     }
111     switch ($type) {
112         "primary" {
113             # remove irrelevant params
114             $parms.Remove('MasterServers')
115             if ($parms.ZoneFile -and ($dynamic_update -in @('secure','nonsecureandsecure'))) {
116                 $parms.Remove('DynamicUpdate')
117                 $module.Warn("Secure DNS updates are available only for Active Directory-integrated zones")
118             }
119             if (-not $current_zone) {
120                 # create zone
121                 Try { Add-DnsServerPrimaryZone @parms -WhatIf:$check_mode }
122                 Catch { $module.FailJson("Failed to add $type zone $($name): $($_.Exception.Message)", $_) }
123             } else {
124                 # update zone
125                 if (-not $current_zone_type_match) {
126                     Try {
127                         if ($current_zone.ReplicationScope) { $parms.ReplicationScope = $current_zone.ReplicationScope } else { $parms.Remove('ReplicationScope') }
128                         if ($current_zone.ZoneFile) { $parms.ZoneFile = $current_zone.ZoneFile } else { $parms.Remove('ReplicationScope') }
129                         if ($current_zone.IsShutdown) { $module.FailJson("Failed to convert DNS zone $($name): this zone is shutdown and cannot be modified") }
130                         ConvertTo-DnsServerPrimaryZone @parms -Force -WhatIf:$check_mode
131                     }
132                     Catch { $module.FailJson("Failed to convert DNS zone $($name): $($_.Exception.Message)", $_) }
133                 }
134                 Try {
135                     if (-not $parms.ZoneFile) { Set-DnsServerPrimaryZone -Name $name -ReplicationScope $parms.ReplicationScope -WhatIf:$check_mode }
136                     if ($dynamic_update) { Set-DnsServerPrimaryZone -Name $name -DynamicUpdate $parms.DynamicUpdate -WhatIf:$check_mode }
137                 }
138                 Catch { $module.FailJson("Failed to set properties on the zone $($name): $($_.Exception.Message)", $_) }
139             }
140         }
141         "secondary" {
142             # remove irrelevant params
143             $parms.Remove('ReplicationScope')
144             $parms.Remove('DynamicUpdate')
145             if (-not $current_zone) {
146                 # enforce param
147                 $parms.ZoneFile = "$name.dns"
148                 # create zone
149                 Try { Add-DnsServerSecondaryZone @parms -WhatIf:$check_mode }
150                 Catch { $module.FailJson("Failed to add $type zone $($name): $($_.Exception.Message)", $_) }
151             } else {
152                 # update zone
153                 if (-not $current_zone_type_match) {
154                     $parms.MasterServers = $current_zone.MasterServers
155                     $parms.ZoneFile = $current_zone.ZoneFile
156                     if ($current_zone.IsShutdown) { $module.FailJson("Failed to convert DNS zone $($name): this zone is shutdown and cannot be modified") }
157                     Try { ConvertTo-DnsServerSecondaryZone @parms -Force -WhatIf:$check_mode }
158                     Catch { $module.FailJson("Failed to convert DNS zone $($name): $($_.Exception.Message)", $_) }
159                 }
160                 Try { if ($dns_servers) { Set-DnsServerSecondaryZone -Name $name -MasterServers $dns_servers -WhatIf:$check_mode } }
161                 Catch { $module.FailJson("Failed to set properties on the zone $($name): $($_.Exception.Message)", $_) }
162             }
163         }
164         "stub" {
165             $parms.Remove('DynamicUpdate')
166             if (-not $current_zone) {
167                 # create zone
168                 Try { Add-DnsServerStubZone @parms -WhatIf:$check_mode }
169                 Catch { $module.FailJson("Failed to add $type zone $($name): $($_.Exception.Message)", $_) }
170             } else {
171                 # update zone
172                 if (-not $current_zone_type_match) { $module.FailJson("Failed to convert DNS zone $($name) to $type, unsupported conversion") }
173                 Try {
174                     if ($parms.ReplicationScope) { Set-DnsServerStubZone -Name $name -ReplicationScope $parms.ReplicationScope -WhatIf:$check_mode }
175                     if ($forwarder_timeout) { Set-DnsServerStubZone -Name $name -ForwarderTimeout $forwarder_timeout -WhatIf:$check_mode }
176                     if ($dns_servers) { Set-DnsServerStubZone -Name $name -MasterServers $dns_servers -WhatIf:$check_mode }
177                 }
178                 Catch { $module.FailJson("Failed to set properties on the zone $($name): $($_.Exception.Message)", $_) }
179             }
180         }
181         "forwarder" {
182             # remove irrelevant params
183             $parms.Remove('DynamicUpdate')
184             $parms.Remove('ZoneFile')
185             if ($forwarder_timeout -and ($forwarder_timeout -in 0..15)) {
186                 $parms.ForwarderTimeout = $forwarder_timeout
187             }
188             if ($forwarder_timeout -and -not ($forwarder_timeout -in 0..15)) {
189                 $module.Warn("The forwarder_timeout param must be an integer value between 0 and 15")
190             }
191             if ($parms.ReplicationScope -eq 'none'){$parms.Remove('ReplicationScope')}
192             if (-not $current_zone) {
193                 # create zone
194                 Try { Add-DnsServerConditionalForwarderZone @parms -WhatIf:$check_mode }
195                 Catch { $module.FailJson("Failed to add $type zone $($name): $($_.Exception.Message)", $_) }
196             } else {
197                 # update zone
198                 if (-not $current_zone_type_match) { $module.FailJson("Failed to convert DNS zone $($name) to $type, unsupported conversion") }
199                 Try {
200                     if ($parms.ReplicationScope) { Set-DnsServerConditionalForwarderZone -Name $name -ReplicationScope $parms.ReplicationScope -WhatIf:$check_mode }
201                     if ($forwarder_timeout) { Set-DnsServerConditionalForwarderZone -Name $name -ForwarderTimeout $forwarder_timeout -WhatIf:$check_mode }
202                     if ($dns_servers) { Set-DnsServerConditionalForwarderZone -Name $name -MasterServers $dns_servers -WhatIf:$check_mode }
203                 }
204                 Catch { $module.FailJson("Failed to set properties on the zone $($name): $($_.Exception.Message)", $_) }
205             }
206         }
207     }
208 }
209 
210 if ($state -eq "absent") {
211     if ($current_zone -and -not $check_mode) {
212         Try {
213             Remove-DnsServerZone -Name $name -Force -WhatIf:$check_mode
214             $module.Result.changed = $true
215             $module.Diff.after = ""
216         } Catch {
217             $module.FailJson("Failed to remove DNS zone: $($_.Exception.Message)", $_)
218         }
219     }
220     $module.ExitJson()
221 }
222 
223 # determine if a change was made
224 Try {
225     $new_zone = Get-DnsServerZone -Name $name
226     if (-not (Compare-DnsZone -Original $current_zone -Updated $new_zone)) {
227         $module.Result.changed = $true
228         $module.Result.zone = Get-DnsZoneObject -Object $new_zone
229         $module.Diff.after = Get-DnsZoneObject -Object $new_zone
230     }
231 
232     # simulate changes if check mode
233     if ($check_mode) {
234         $new_zone = @{}
235         $current_zone.PSObject.Properties | ForEach-Object {
236             if($parms[$_.Name]) {
237                 $new_zone[$_.Name] = $parms[$_.Name]
238             } else {
239                 $new_zone[$_.Name] = $_.Value
240             }
241         }
242         $module.Diff.after = Get-DnsZoneObject -Object $new_zone
243     }
244 } Catch {
245     $module.FailJson("Failed to lookup new zone $($name): $($_.Exception.Message)", $_)
246 }
247 
248 $module.ExitJson()