bezpieczne hardocodowanie hasła?
uruchamiając skrypt, najbezpieczniej jest NIE hardcodować hasła. jeśli coś jest w skrypcie, to znaczy, że może być wykorzystane w nieautoryzowany sposób. jednak przykłady z życia pokazują, że czasem taka jest konieczność .. czy po prostu zlecenie (; przykład – obsługa wsparcia, która ma korzystać ze skryptu, który gdzieś tam sobie sięga. to 'gdzieś tam’ to środowisko, w którym nawet owa obsługa nie ma konta.
podstawowe zasady bezpieczeństwa to 3xA (AAA) – Authentication, Authorization & Accounting. jeśli utworzymy konto serwisowe, które ma coś wykonywać w imieniu uruchamiającego, to aby nie złamać zasady AAA i być w stanie stwierdzić kto wykonał daną akcję, trzeba to przesunąć do miejsca, gdzie uruchamia się skrypt/aplikację i umieć to powiązać… dalej mogłoby być o SIEM i tak dalej… ale nie o tym tutaj. ogólnie najważniejsze – wykorzystanie konta serwisowego (w środowisq legacy-domain), czy aplikacji proxy (tak jak działa to w Azure) nie musi koniecznie być złem, ale pozostaje pytanie… *jak zamieścić hasło skrypcie tak, aby nie wyciekło wraz ze skryptem?*
najbezpieczniej byłoby wykorzystać certyfikat… tak to można zrobić np. dla aplikacji Azure. niestety w środowisq klasycznym, gdzie mamy np. wykonać akcję w domenie, nie jest to łatwe. najbezpieczniej byłoby umieszczenie serwisu aka proxy, który działa sobie na serwerze w owej domenie i do tegoż serwisu uwierzytelniać się certyfikatem. spoko ale to developerka i do 'małych projekcików’ czy 'drobnych narzędzi’ – po prostu jest za duża czasochłonność (czyt. koszt), nie wspominając o tym że napisanie własnego serwisu (np. wystawiającego REST API) to nie jest już skryptowanie. to developerka.
no więc zróbmy to, co da się zrobić tak, aby było szybko i *możliwie* bezpiecznie.
…na ile się da
skoro nie jesteśmy w stanie uwierzytelnić się certyfikatem, to przynajmniej zaszyfrujmy hasło certyfikatem. co nam to da:
- dany skrypt będzie działał wyłącznie na tej maszynie gdzie jest certyfikat i dla tych osób, którym na to pozwolimy.
żeby była jasność – jeśli ktoś, kto ma złe intencje będzie tego skryptu używał, to sobie hasło odkoduje. ale po pierwsze – 'pierwszy lepszy’ tego nie zrobi, po drugie – jeśli założylibyśmy takiej osobie konto, to i tak wykorzysta je do niecnych celów. trzeba wiedzieć że to może być rozwiązanie *good enough* i tylko w tym kontekście można nazwać je 'bezpiecznym’. spory disclaimer ale nie chcę przypadkowych czytelników wprowadzić w błąd.
jak już wyjaśniłem co rozumiem przez 'bezpieczne’, recepta:
- załóż konto w domenie, bez żadnych dodatkowych uprawnień
- dobrze byłoby poustawiać 'deny’ dla takiego konta, przykrajając odpowiednio uprawnienia – wedle potrzeby, uznania i paranoia-level
- nadać uprawnienia do tego, do czego będzie wykorzystywane
- wygenerować odpowiedni certyfikat, zainstalować go i odpowiednio nadać uprawnienia
- zaszyfrować hasło certyfikatem
- i voila.. można go wykorzystać w skrypcie
punkty 1-3 pominę, natomiast 4-6 zaraz opiszę.
dzięki takiej konfiguracji – skrypt, nawet skopiowany czy przypadkowo udostępniony, na niewiele się zda. aby wydobyć hasło – trzeba mieć dostęp do klucza prywatnego certyfikatu.
generowanie certyfikatu
super proste, przy czym do tego celu cert musi spełniać odpowiednie funkcje, czyli (nomen-omen) kluczem jest dobry key-usage i document type:
$cert=New-SelfSignedCertificate -DnsName "script automation" -CertStoreLocation "Cert:\CurrentUser\My" -KeyUsage KeyEncipherment,DataEncipherment,KeyAgreement -Type DocumentEncryptionCert $cert|Export-PfxCertificate -FilePath C:\temp\scriptautomation.pfx -Password (ConvertTo-SecureString -String "secret-string" -Force -AsPlainText)
dodatkowo można certyfikat usunąć z własnego komputera tak, aby jedna kopia była w pliq:
$cert|remove-Item
…albo zachować go jako backup a usunąć potem plik. up2u.
instalacja w środowisq docelowym
najłatwiej oczywiście kliknąć sobie na pliq pfx i wybrać opcję 'local machine’. jeśli umieścimy w 'current user’ to będzie dostępny tylko dla nas.
kolejną hiper-istotną kwestią jest *NIE włączanie* możliwości exportu klucza.
jeśli ktoś woli z PowerShell – należy użyć ’import-pfxCertificate’ *bez* parametru -exportable.
Import-PfxCertificate -FilePath C:\temp\scriptautomation.pfx -Password (ConvertTo-SecureString -string "secret-string" -force -AsPlainText) -CertStoreLocation Cert:\LocalMachine\My\
ciekawostka: w PS 5.1 ten commandlet ma dodatkowy parametr, nieopisany w doc, protectPrivateKey, który pozwala wykorzystać Virtual Secure Mode.
to połowa sukcesu – bo do tak zaimportowanego certyfikatu, nikt bez uprawnień administratora nie będzie mógł skorzystać. należy nadać uprawnienia do klucza prywatnego:
- odpal 'certlm.msc’ (zarządzanie certyfikatami komputera)
- wybierz certyfikat i kliknij PGM
- jest tam opcja 'manage private keys…’
4. nadaj uprawnienie READ (nie FC!) dla wybranych osób
kodowanie i odkodowanie hasła
teraz już z górki. przygotowanie środowiska to bardziej wymagający element, ponieważ hasło można zaszyfrować za pomocą polecenia ’protect-cmsMessage’:
C:\_scriptz :))o- Protect-CmsMessage -To 'cn=script automation' -Content 'moje tajne hasło' -----BEGIN CMS----- MIIBtAYJKoZIhvcNAQcDoIIBpTCCAaECAQAxggFMMIIBSAIBADAwMBwxGjAYBgNVBAMMEXNjcmlw dCBhdXRvbWF0aW9uAhB2BdZafxn2nkcWjQNvBi+pMA0GCSqGSIb3DQEBBzAABIIBAFY0TU8LAPl4 4IAWp9tGDOZK/bAEwQpZPix5dz8AoOpaDckpZ8dqatggEKq7vo4VX6wL4sBkImQoZrCZjkT7oCSi N1YFDOIghjD7e2mIvEl/Gq2bReUbsFQ0iw6Wg1K8zVLBzDQAIUHgJvaV2Ssf4nCJ8WfW5d9UzmBB JpHAmy7cdofboQt6hRz0wafGNivD47z2xNSSVyhqHVUzBt6NWmAYouFMGYGsBZBuOHXnQJzh+saG Q5VpWxZGRHzM3igl8IgYDF75R+b/FSfciPu5Se2eeD6xxHrPj9rfLm37KVIab/4+QA/lRFVt4cwA ztjhh8puyPqYgKCE7t0Tp5VNQv4wTAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBKgQQjmYztOm64boC 6XqnH/9kMYAgYTQlOXeGex7qMH5S5gVRKy/JOhfIdqlabP9e/N/6iRk= -----END CMS-----
ciekawostka: przy tym mechaniźmie szyfrowania (Cryptographic Message Syntax), uruchomienie tego polecenia, da za każdym razem inny wynik
jeśli ktoś nie pamięta nazwy certu, to można je szybko wylistować:
C:\_scriptz :))o- ls Cert:\LocalMachine\My\ PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\My Thumbprint Subject ---------- ------- B643393E34BDC10BE2266F307F63A712578416F1 CN=script automation
wkleić sobie ten ciąg do skryptu i kiedy potrzebne jest hasło, użyć unprotect-CMSMessage… np. tak:
[string]$encPass="-----BEGIN CMS----- MIIBtAYJKoZIhvcNAQcDoIIBpTCCAaECAQAxggFMMIIBSAIBADAwMBwxGjAYBgNVBAMMEXNjcmlw dCBhdXRvbWF0aW9uAhB2BdZafxn2nkcWjQNvBi+pMA0GCSqGSIb3DQEBBzAABIIBAFY0TU8LAPl4 4IAWp9tGDOZK/bAEwQpZPix5dz8AoOpaDckpZ8dqatggEKq7vo4VX6wL4sBkImQoZrCZjkT7oCSi N1YFDOIghjD7e2mIvEl/Gq2bReUbsFQ0iw6Wg1K8zVLBzDQAIUHgJvaV2Ssf4nCJ8WfW5d9UzmBB JpHAmy7cdofboQt6hRz0wafGNivD47z2xNSSVyhqHVUzBt6NWmAYouFMGYGsBZBuOHXnQJzh+saG Q5VpWxZGRHzM3igl8IgYDF75R+b/FSfciPu5Se2eeD6xxHrPj9rfLm37KVIab/4+QA/lRFVt4cwA ztjhh8puyPqYgKCE7t0Tp5VNQv4wTAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBKgQQjmYztOm64boC 6XqnH/9kMYAgYTQlOXeGex7qMH5S5gVRKy/JOhfIdqlabP9e/N/6iRk= -----END CMS-----" try { $cert = Get-ChildItem Cert:\LocalMachine\My\B643393E34BDC10BE2266F307F63A712578416F1 -ErrorAction stop } catch { write-log "missing authentication certificate. quitting" -type error retrun $null } if(-not $cert.HasPrivateKey) { write-log "missing decription factor. quitting." -type error return $null } $certCN = $cert.Subject try { [securestring]$secStringPassword = ConvertTo-SecureString (Unprotect-CmsMessage -Content $encPass -To $certCN -ErrorAction stop) -AsPlainText -Force } catch { write-log "error decrypting auth password $($_.exception)" -type error return $null } [pscredential]$creds = New-Object System.Management.Automation.PSCredential ('CORP\SVC-contactCreator', $secStringPassword)
inne scenariusze i metody
jakiś czas temu linkowałem dostęp do EXO certyfikatem. wygodne.
a podczas uruchamiania własnych skryptów, i tam gdzie wystarczą zwykłe credsy, nie chce mi się wpisywać hasła. zapisuję więc je sobie do pliq:
if(test-path $credsFile) { $myCreds = Import-CliXml -Path $credsFile write-log "used saved credentials in $credsFile" -type info } else { $myCreds=Get-Credential if($NULL -eq $myCreds) { write-log 'Cancelled.' -type error exit -3 } if($saveCredentials) { $myCreds | Export-Clixml -Path $credsFile write-log "credentials saved as $credsFile" -type info } }
get-Credential używa DPAPI i w 'zamkniętym’ środowisq (np. na własnym lapq) w zupełności wystarcza. z tego co kojarzę szyfrowane jest kluczem użytkownika i komputera więc taki plik, aby był odczytany, musi być w tym samym środowisq. mimo wszystko w 'otwartym’ środowisq raczej nie należy stosować takich metod.
eN.