Donnerstag, 9. Januar 2020

Passwörter in Powershell Scripts

Es gibt immer mal wieder die Notwendigkeit in automatischen Prozessen Konten (Benutzername Passwort) zu verwenden. Die sicherlich dümmste Variante ist es, sie im Script direkt zu hinterlegen. Aber wie jetzt tun?
  • Auf einem sicheren Filesystem ablegen und vor Zugriff schützen.
  • Das Passwort nur verschlüsselt ablegen. Den Key dazu im sicheren Filesystem ablegen und vor Zugriff schützen.
  • Das Script im definierten Userkontext ausführen.
  • Im Code möglichst darauf achten, dass der Speicher wieder gelöscht wird.

Das Passwort verschlüsseln

Man braucht zunächst mal einen definierten Schlüssel um zu verschlüsseln, sonst würde das Cmdlet ConvertFrom-SecureString das Benutzerkonto verwenden. das wäre nicht praktikabel.
So kann man einen zufälligen AES 256 Key (32 Byte) erzeugen:
$pwPath = ".\myPassword.txt"
$aesKeyPath = ".\aesKey.key"
$AESKey = New-Object Byte[] 32
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey)
if (-not (Test-Path $aesKeyPath)) {Set-Content $aesKeyPath $AESKey}
Ich mag ja Einzeiler, hier eine Variante um das Passwort verschlüsselt abzulegen:
"password" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString -Key $AESKey| Out-File $pwPath

Das Passwort entschlüsseln und verwenden

Das Passwort kann man "umgekehrt" wieder einlesen, aber dabei entsteht zunächst ein SecureString, dieser ist so nicht darstellbar und in vielen Applikationen auch nicht verwendbar..
$SecureCredential = Get-Content $pwPath | ConvertTo-SecureString -Key $AESKey
Zur Darstellung kann man das PSCredential Object verwenden (alternativ auch die .net Klasse [Runtime.InteropServices.Marshal]).
(New-Object PSCredential "username",$SecureCredential).GetNetworkCredential().Password
Dabei verlangt das PSCredential Objekt einen Benutzernamen, der mit dem Passwort aber primär gar keine Verbindung haben muss.
Entweder nimmt man hier irgendeinen "Begriff" oder man nimmt den Benutzer, den man in der weiteren Verarbeitung verwenden kann/will (z.B. Mailaccount)
$Account = (New-Object PSCredential "username",$SecureCredential).GetNetworkCredential()
$Account.Password
$Account.UserName

Das Passwort an externe Programme übergeben

Die Verwendung der Kommandozeile in Powershell kann einen manchmal verzweifeln lassen.
Einige eingebaute Alias Definitionen (Get-Alias) ersetzen bekannten Windows Kommandozeilen Befehle durch Cmdlets. Deren Aufruf Syntax ist nie 100% kompatibel!
Parameter/Argumente werden von der Powershell "analysiert" und "umgedeutet".

Im Zweifelsfall hilft nur probieren und suchen!

Leerzeichen und Sonderzeichen in den Strings machen einen das Leben zusätzlich schwer. Bestimmte Zeichen muss man mit dem backtick ` schützen (Artikel in der WinPro).
Es bieten sich ein paar Möglichkeiten zum Aufruf externer Programme an:
  • Command Shell cmd verwenden
  • Aufruf mit & Zeichen am Zeilenanfang
  • Alias setzen Set-Alias
  • Das Parsing der Argumente durch Einfügen von --% abbrechen.
Hier ein paar Beispiele für die Verwendung von Programmaufrufen in Powershell Console oder Script:
& cmd /c dir /q
set-alias sz "$env:ProgramFiles\7-Zip\7z.exe"
sz a -tzip "ArchivNameOhneEndung" "PfadName" -mem=AES256 "-p$($Account.Password)" -v2g
icacls .\Archiv1.zip.001 /grant Benutzer:`(R`)
icacls .\Archiv1.zip.001 --% /grant Benutzer:(R)
Will man die Argumente noch in eine Variable packen, wird das nochmal schwierig.
Hier hilft dann eventuell ein Array (siehe Beispiele weiter unten).

Die Dateien vor Zugriff schützen

Die Absicherung der Datei unter Windows kann mit verschiedenen Methoden erfolgen, die grafische Variante mit dem Explorer will ich nicht extra beschreiben.
Schlussendlich muss der Benutzer, der das Script ausführt die Datei lesen können!
Achtung: Der folgenden icacls Syntax funktioniert in einer Powershell Umgebung, aber besser immer icacls --% verwenden!
Beispiel: Rechte Anzeigen, Vererbung löschen, Administratoren und SYSTEM entfernen:
icacls w.txt
icacls w.txt /inheritance:d
icacls w.txt /remove Administratoren
icacls w.txt /remove SYSTEM
Mit Clear-Variable und Remove-Variable kann man sicherstellen, das Variable (mit sensiblen Inhalt) zur Laufzeit wieder gelöscht werden und nicht erst mit der "Müllabfuhr" nach Beenden des Scripts. Die Variable wird ohne $ angegeben!
Clear-Variable Account

Dinge die man nicht verstehen muss

Hier mal 3 Varianten wie man den 7Zip Befehl (Alias sz) fehlerfrei ausführen kann.
$Archiv="Archiv1"
$Pfad="c:\temp"
# Alles in einer Aufrufzeile
sz a -tzip "$Archiv" "$Pfad" -mem=AES256 "-p$($Account.Password)" -v2g
# die gesamte Aufrufzeile in eine Stringvariable verpackt
$befehl = "sz a -tzip `"$Archiv`" `"$Pfad`" -mem=AES256 -p$($Account.Password) -v2g"
Invoke-Expression $befehl
# Alle Argumente als Elemente eines Arrays
$Arguments = "a","-tzip","`"$Archiv`"","`"$Pfad`"","-mem=AES256","-p$($Account.Password)","-v2g"
sz $Arguments
So funktioniert es nicht:
$Argument="a -tzip `"$Archiv`" `"$Pfad`" -mem=AES256 -p$($Account.Password) -v2g"
sz $Argument

ToDo?

Code Block

Code Block
Und hier noch ein umfassender Beitrag zu dem Thema.