To further extend the hardening of my VMware ESXi hosts, I have been working on scripted ESXi NTP Security Configuration. I wanted to restrict access to the host’s NTP client to the configured NTP Server IPs. Per default, the host firewall is configured to allow all IPs if a service is enabled.
You can add Allowed IP Addresses for an ESXi Host via vSphere Client, vSphere Web Client, vCLI, or PowerCLI. The ESXi Host Client does currently not offer this ability.
The desired ESXi NTP Security Configuration looks like this in the vSphere Client:
But as usual, it is not an option for me to do this configuration manually using the vSphere Client. I will show how to set the desired configuration with Ansible and PowerCLI.
NTP Security Configuration with Ansible
All required Ansible Modules to enforce the NTP Security Configuration are available out of the box and are maintained by the community.
The Ansible Playbook to set the desired configuration for all hosts in the vSphere Cluster is available on GitHub: vmware_secure_ntp_cluster.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
- name: Secure ESXi Host NTP Service and enforce NTP Servers
hosts: localhost
gather_facts: false
strategy: free
vars:
cluster_name: "cluster01"
host_services_query: "host_service_info.*[?key=='ntpd']"
host_firewall_query: "hosts_firewall_info.*[?key=='ntpClient']"
tasks:
- name: Get Services
vmware_host_service_info:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
cluster_name: "{{ cluster_name }}"
validate_certs: no
register: host_services
tags: debug
- name: Debug NTP Service
debug:
var: host_services | json_query(host_services_query)
tags: debug
- name: Get Firewall Rules
vmware_host_firewall_info:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
cluster_name: "{{ cluster_name }}"
validate_certs: no
register: host_firewall
tags: debug
- name: Debug Firewall Rules
debug:
var: host_firewall | json_query(host_firewall_query)
tags: debug
- name: Get NTP Config
vmware_host_ntp_info:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
cluster_name: "{{ cluster_name }}"
validate_certs: no
register: host_ntp
tags: debug
- name: Debug NTP Config
debug:
var: host_ntp.hosts_ntp_info
tags: debug
- name: Manage NTP Firewall Rules
vmware_host_firewall_manager:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
cluster_name: "{{ cluster_name }}"
validate_certs: no
rules:
- name: ntpClient
enabled: True
allowed_hosts:
all_ip: False
ip_address:
- 192.168.20.10
- 192.168.20.11
tags: ntp
- name: Set NTP servers
vmware_host_ntp:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
cluster_name: "{{ cluster_name }}"
validate_certs: no
ntp_servers:
- 192.168.20.10
- 192.168.20.11
tags: ntp
- name: Set NTP Service
vmware_host_service_manager:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
cluster_name: "{{ cluster_name }}"
validate_certs: no
service_name: ntpd
state: start
service_policy: on
tags: ntp
|
NTP Security Configuration with PowerCLI
VMware PowerCLI does currently not offer a native Cmdlet to configure allowed IPs or ranges. If you are also interested in this function, please vote for this idea: Cmdlets to manage VMHost Firewall
Even if no native PowerCLI Cmdlet is available, but the required functionality is available through esxcli, Get-ESXCLI can solve the problem.
The PowerCLI Function to set the desired configuration for one or more ESXi hosts is published on the VMware PowerCLI Example Scripts GitHub Repository: Set-VMHostSecureNTP.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
|
function Set-VMHostSecureNTP {
<#
.NOTES
===========================================================================
Created by: Markus Kraus
===========================================================================
Changelog:
2020.05 ver 1.0 Base Release
===========================================================================
External Code Sources:
-
===========================================================================
Tested Against Environment:
vSphere Version: vSphere 6.7 U3
PowerCLI Version: PowerCLI 11.5
PowerShell Version: 5.1
OS Version: Windows 10
Keyword: ESXi, NTP, Hardening, Security, Firewall
===========================================================================
.DESCRIPTION
This function sets new NTP Servers on given ESXi Hosts and configures the host firewall to only accept NTP connections from these servers.
.Example
Get-VMHost | Set-VMHostSecureNTP -Secure
.Example
Get-VMHost | Set-VMHostSecureNTP -Type SetSecure -NTP 10.100.1.1, 192.168.2.1
.PARAMETER VMHost
Specifies the hosts to configure
.PARAMETER SetSecure
Execute Set and Secure operation for new NTP Servers
.PARAMETER NTP
Specifies a Array of NTP Servers
.PARAMETER Secure
Execute Secure operation for exitsting NTP Servers
#Requires PS -Version 5.1
#Requires -Modules VMware.VimAutomation.Core, @{ModuleName="VMware.VimAutomation.Core";ModuleVersion="11.5.0.0"}
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True, Position=0, HelpMessage = "Specifies the hosts to configure.")]
[ValidateNotNullorEmpty()]
[VMware.VimAutomation.Types.VMHost[]] $VMHost,
[Parameter(Mandatory=$False, ValueFromPipeline=$False, ParameterSetName="SetSecure", HelpMessage = "Execute Set and Secure operation for new NTP Servers")]
[Switch] $SetSecure,
[Parameter(Mandatory=$True, ValueFromPipeline=$False, ParameterSetName="SetSecure", HelpMessage = "Specifies a Array of NTP Servers")]
[ValidateNotNullorEmpty()]
[ipaddress[]] $NTP,
[Parameter(Mandatory=$False, ValueFromPipeline=$False, ParameterSetName="Secure", HelpMessage = "Execute Secure operation for exitsting NTP Servers")]
[Switch] $Secure
)
begin {
function SetNTP ($MyHost) {
## Get NTP Service
"Get NTP Service from VMHost ..."
$NTPService = $MyHost | Get-VMHostService | Where-Object {$_.key -eq "ntpd"}
## Stop NTP Service if running
"Stop NTP Service if running ..."
if($NTPService.Running -eq $True){
Stop-VMHostService -HostService $NTPService -Confirm:$false | Out-Null
}
## Enable NTP Service
"Enable NTP Service if disabled..."
if($NTPService.Policy -ne "on"){
Set-VMHostService -HostService $NTPService -Policy "on" -confirm:$False | Out-Null
}
## Remove all existing NTP Servers
"Remove all existing NTP Servers ..."
try {
$MyHost | Get-VMHostNtpServer | Foreach-Object {
Remove-VMHostNtpServer -VMHost $MyHost -NtpServer $_ -Confirm:$false
}
}
catch [System.Exception] {
Write-Warning "Error during removing existing NTP Servers on Host '$($MyHost.Name)'."
}
## Set New NTP Servers
"Set New NTP Servers ..."
foreach ($myNTP in $NTP) {
$MyHost | Add-VMHostNtpServer -ntpserver $myNTP -confirm:$False | Out-Null
}
## Set Current time on Host
"Set Current time on VMHost ..."
$HostTimeSystem = Get-View $MyHost.ExtensionData.ConfigManager.DateTimeSystem
$HostTimeSystem.UpdateDateTime([DateTime]::UtcNow)
## Start NTP Service
"Start NTP Service ..."
Start-VMHostService -HostService $NTPService -confirm:$False | Out-Null
## Get New NTP Servers
"Get New NTP Servers ..."
$NewNTPServers = $MyHost | Get-VMHostNtpServer
"`tNew NTP Servers: $($NewNTPServers -join ", ")"
}
function SecureNTP ($MyHost) {
## Get NTP Servers
"Get NTP Servers to Secure ..."
[Array]$CurrentNTPServers = $MyHost | Get-VMHostNtpServer
"`tNTP Servers: $($CurrentNTPServers -join ", ")"
## Get ESXCLI -V2
$esxcli = Get-ESXCLI -VMHost $MyHost -v2
## Get NTP Client Firewall
"Get NTP Client Firewall ..."
try {
$FirewallGet = $esxcli.network.firewall.get.Invoke()
}
catch [System.Exception] {
Write-Warning "Error during Rule List. See latest errors..."
}
"`tLoded: $($FirewallGet.Loaded)"
"`tEnabled: $($FirewallGet.Enabled)"
"`tDefaultAction: $($FirewallGet.DefaultAction)"
## Get NTP Client Firewall Rule
"Get NTP Client Firewall RuleSet ..."
$esxcliargs = $esxcli.network.firewall.ruleset.list.CreateArgs()
$esxcliargs.rulesetid = "ntpClient"
try {
$FirewallRuleList = $esxcli.network.firewall.ruleset.list.Invoke($esxcliargs)
}
catch [System.Exception] {
Write-Warning "Error during Rule List. See latest errors..."
}
"`tEnabled: $($FirewallRuleList.Enabled)"
## Set NTP Client Firewall Rule
"Set NTP Client Firewall Rule ..."
$esxcliargs = $esxcli.network.firewall.ruleset.set.CreateArgs()
$esxcliargs.enabled = "true"
$esxcliargs.allowedall = "false"
$esxcliargs.rulesetid = "ntpClient"
try {
$esxcli.network.firewall.ruleset.set.Invoke($esxcliargs)
}
catch [System.Exception] {
$ErrorMessage = $_.Exception.Message
if ($ErrorMessage -ne "Already use allowed ip list") {
Write-Warning "Error during Rule Set. See latest errors..."
}
}
"Get NTP Client Firewall Rule AllowedIP ..."
$esxcliargs = $esxcli.network.firewall.ruleset.allowedip.list.CreateArgs()
$esxcliargs.rulesetid = "ntpClient"
try {
$FirewallRuleAllowedIPList = $esxcli.network.firewall.ruleset.allowedip.list.Invoke($esxcliargs)
}
catch [System.Exception] {
Write-Warning "Error during Rule List. See latest errors..."
}
"`tAllowed IP Addresses: $($FirewallRuleAllowedIPList.AllowedIPAddresses -join ", ")"
## Remove Existing IP from firewall rule
"Remove Existing IP from firewall rule ..."
if ($FirewallRuleAllowedIPList.AllowedIPAddresses -ne "All") {
foreach ($IP in $FirewallRuleAllowedIPList.AllowedIPAddresses) {
$esxcliargs = $esxcli.network.firewall.ruleset.allowedip.remove.CreateArgs()
$esxcliargs.rulesetid = "ntpClient"
$esxcliargs.ipaddress = $IP
try {
$esxcli.network.firewall.ruleset.allowedip.remove.Invoke($esxcliargs)
}
catch [System.Exception] {
Write-Warning "Error during AllowedIP remove. See latest errors..."
}
}
}
## Set NTP Client Firewall Rule AllowedIP
"Set NTP Client Firewall Rule AllowedIP ..."
foreach ($myNTP in $CurrentNTPServers) {
$esxcliargs = $esxcli.network.firewall.ruleset.allowedip.add.CreateArgs()
$esxcliargs.ipaddress = $myNTP
$esxcliargs.rulesetid = "ntpClient"
try {
$esxcli.network.firewall.ruleset.allowedip.add.Invoke($esxcliargs)
}
catch [System.Exception] {
$ErrorMessage = $_.Exception.Message
if ($ErrorMessage -ne "Ip address already exist.") {
Write-Warning "Error during AllowedIP remove. See latest errors..."
}
}
}
## Get New NTP Client Firewall Rule AllowedIP
"Get New NTP Client Firewall Rule AllowedIP ..."
$esxcliargs = $esxcli.network.firewall.ruleset.allowedip.list.CreateArgs()
$esxcliargs.rulesetid = "ntpClient"
try {
$FirewallRuleAllowedIPList = $esxcli.network.firewall.ruleset.allowedip.list.Invoke($esxcliargs)
}
catch [System.Exception] {
Write-Warning "Error during Rule List. See latest errors..."
}
"`tNew Allowed IP Addresses: $($FirewallRuleAllowedIPList.AllowedIPAddresses -join ", ")"
}
}
process {
if ($SetSecure) {
"Execute Set and Secure operation for new NTP Servers ..."
$VMHost | Foreach-Object { Write-Output (SetNTP $_) }
$VMHost | Foreach-Object { Write-Output (SecureNTP $_) }
}
if ($Secure) {
"Execute Secure operation for exitsting NTP Servers ..."
$VMHost | Foreach-Object { Write-Output (SecureNTP $_) }
}
}
}
|
Summary
Ansible was surprisingly well suited to perform this configuration. The vmware_host_firewall_manager Module offered way more possibilities than the VMware PowerCLI. Even when the workaround with Get-ESXCLI works pretty robust, it is still a workaround.
Both examples, the Ansible Playbook, and the PowerCLI Function can be used as a template for even more scripted VMware ESXi security configuration.
I have also merged the ESXi NTP Security Configuration tasks into my existing Ansible Playbook for the additional hardening of VMware ESXi hosts. See also my previous blog post about that topic: ESXi Security Configuration with Ansible