Puppeteers Blog

Powershell ja Windows hosts-tiedoston muokkaaminen

January 16, 2018 

Windowsissa voidaan käyttää hosts-tiedostoa ip-osoitteiden ja nimien liittämiseen toisiinsa, aivan kuten Linuxissakin. Linuxissa tiedoston sijainti on /etc/hosts, Windowsissa se on c:windowssystem32driversetchosts, tai oikeastaan $env:windirsystem32driversetchosts.

Hosts-tiedoston käyttäminen tulee ajankohtaiseksi silloin kun sovellus tarvitsee DNS-nimen, eikä DNS-palvelinta ole käytettävissä, tai sitä ei voida jostakin syystä käyttää.

Jos Puppet agentille ei ole määritetty palvelinta, se yrittää oletuksena yhteyttä palvelimeen jonka DNS-nimi on 'puppet', eli jos palvelimelle on määritetty domain 'example.com', agent hakee palvelinta jonka DNS-nimi on 'puppet.example.com' Määrittämällä server-asetuksella palvelimen DNS-nimi, voidaan Puppet agent osoittaa haluttuun palvelimeen. Tämä määritetään Linuxeissa tyypillisesti uusimmissa puppet-versioissa tiedostossa /etc/puppetlabs/puppet/puppet.conf osiossa main tai agent.

servername = puppet.example.com

Olemassa olevan Windowsin hosts-tiedoston muokkaaminen esim. Notepad-ohjelmassa sujuu yleensä ongelmitta. Hosts-tiedoston muokkaaminen tai rakentaminen Powershell-koodilla sen sijaan ei välttämättä suju. Muokkaamisen tai rakentamisen jälkeen saatetaan todeta että Puppet agent ei löydä palvelinta koska palvelimen oletusnimi (puppet) tai määritetty nimi eivät käänny ip-osoitteiksi. Seuraava koodi aiheuttaa tämänkaltaisen ongelman.

$hostsFile = "$env:windirSystem32driversetchosts"
 
 if (!(Test-Path "$hostsFile")) {
 New-Item -path $env:windirSystem32driversetc -name hosts -type "file"
 Write-Host "Created new hosts file"
 }
 
 # Remove any lines containing our puppetservername
 $tempfile = $env:temp + "" + (get-date -uformat %s)
 New-Item $tempfile -type file -Force | Out-Null
 Get-Content $HostsFile | Where-Object {$_ -notmatch "$puppetServerName"} | Set-Content $tempfile
 Move-Item -Path $tempfile -Destination $HostsFile -Force
 
 # Insert name, address of puppetserver separated by a tab 
 $fields=@($puppetServerAddress,$puppetServerName)
 $myString=[string]::join("`t", (0,1 | % {$fields[$_]}))
 $found = Get-Content $hostsFile | Where-Object { $_.Contains("$myString") }
 if ([string]::IsNullOrEmpty($found)) {
 [string]::join("`t", (0,1 | % {$fields[$_]})) | Out-File -encoding ASCII -append $hostsFile
 }

Eli ensin määritetään hosts-tiedoston sijainti. Jos tiedostoa ei ole olemassa, se luodaan. Seuraavaksi luodaan väliaikainen tiedosto, jonka sisältö haetaan olemassa olevasta hosts-tiedostosta, samalla poistaen nykyiset rivit jotka sisältävät palvelimen nimen (saatu parametrina). Muokattu tiedosto siirretään sitten oikeaan paikkaan. Tämän jälkeen tiedostoon lisätään palvelimen nimi ja ip-osoite (jotka saatu parametreinä).

Jos nyt tarkastellaan hosts-tiedostoa silmämääräisesti, se vaikuttaa olevan ok. Vaikka Puppet-palvelin on käytettävissä, agentin (ensimmäinen) ajo manuaalisesti tuottaa virheen.

Error: Could not request certificate: getaddrinfo: No such host is known.
 Exiting; failed to retrieve certificate and waitforcert is disabled

Samoin ping tuottaa virheen.

Ping request could not find host puppet.example.com. Please check the name and try again.

Tämä johtuu siitä että nimi puppet.example.com ei ratkea ip-osoitteeksi vaikka se on asianmukaisesti määritetty hosts-tiedostossa. Mistä on kyse?

Windows hosts-tiedoston ongelmat eivät ole aivan harvinaisia, ja näiden ongelmien etsiminen Googlella tuottaa paljon hakutuloksia, esim. tämän. Tarkistettavina asioina esitetään nimien resoluution välimuistia, enkoodausta, ylimääräisiä sarkaimia tai välilyöntejä jne.

Koska tässä artikkelissa on kyse Powershellistä, haluamme selvittää mistä ongelmassa on kyse, ja myös korjata ongelman Powershell-koodilla.

Ongelman syy on siinä että muokkaamalla hosts-tiedostoa edellä mainitulla Powershell-koodilla sen käyttöoikeudet, 'security descriptorit' jäävät vajaiksi. Muokkaamalla oikeudet asianmukaisiksi powershell-koodilla nimet ratkeavat oikein ip-osoitteiksi.

# Create access rule
 $userName = "Users"
 $rule = New-Object System.Security.AccessControl.FileSystemAccessRule("$userName", 'Read', 'Allow')
 
 # Apply access rule
 $acl = Get-ACL $hostsFile
 $acl.SetAccessRule($rule)
 Set-Acl -path $hostsFile -AclObject $acl

Annetaan siis ryhmälle 'Users' lukuoikeus tiedostoon. Tämä oikeus puuttuu, jos hosts-tiedostoa muokataan aiemman Powershell-koodin mukaan. Tämän lukuoikeuden antaminen riittää saattamaan nimiresoluution toimivaksi.

Tässä kokonainen toimiva funktio.

function SetupHostsFile {
 
 param(
 [IPADDRESS]$puppetServerAddress,
 [String]$puppetServerName
 )
 
 if ($debug) {
 write-host ("Now in function {0}." -f $MyInvocation.MyCommand)
 }
 
 
 If (-Not [BOOL]($puppetServerAddress -as [IPADDRESS])) {
 write-host ("{0} is not an IP address" -f $puppetServerAddress)
 break
 }
 
 $fqdnRe='(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-).)+[a-zA-Z]{2,63}$)'
 
 If ($puppetServerName -notmatch $fqdnRe) {
 write-host ("{0} is not a fully qualified name" -f $puppetServerName)
 break
 }
 
 write-host "Setting up hosts file..."
 
 $hostsFile = "$env:windirSystem32driversetchosts"
 
 if (!(Test-Path "$hostsFile")) {
 New-Item -path $env:windirSystem32driversetc -name hosts -type "file"
 Write-Host "Created new hosts file"
 }
 
 # Remove any lines containing our puppetservername
 $tempfile = $env:temp + "" + (get-date -uformat %s)
 New-Item $tempfile -type file -Force | Out-Null
 Get-Content $HostsFile | Where-Object {$_ -notmatch "$puppetServerName"} | Set-Content $tempfile
 Move-Item -Path $tempfile -Destination $HostsFile -Force
 
 # Insert name, address of puppetserver separated by a tab 
 $fields=@($puppetServerAddress,$puppetServerName)
 $myString=[string]::join("`t", (0,1 | % {$fields[$_]}))
 $found = Get-Content $hostsFile | Where-Object { $_.Contains("$myString") }
 if ([string]::IsNullOrEmpty($found)) {
 [string]::join("`t", (0,1 | % {$fields[$_]})) | Out-File -encoding ASCII -append $hostsFile
 }
 
 # Create access rule
 $userName = "Users"
 $rule = New-Object System.Security.AccessControl.FileSystemAccessRule("$userName", 'Read', 'Allow')
 
 # Apply access rule
 $acl = Get-ACL $hostsFile
 $acl.SetAccessRule($rule)
 Set-Acl -path $hostsFile -AclObject $acl
 
 }

Voit lukea lisää Powershellin Set-Acl-cmdletistä täältä

Samuli Seppänen
Petri Lammi
Author archive
menucross-circle