James O'Neill's Blog

April 10, 2020

Transformers for PowerShell parameters which take secrets.

Filed under: Uncategorized — jamesone111 @ 11:17 am

PowerShell Functions often need to accept secure strings as or PowerShell credentials (which contain secure strings) as parameters.  There are people who will advise avoiding use of secure string completely, which isn’t really practical, but one shouldn’t credit Secure String with magic powers it doesn’t have.

  1. It is encoded so that your account on the current machine can decode it at will – any process running under your account can get to the secret.
  2. Another account, or your account on another machine can’t decode it. The main use is securing something in a file on one machine.
  3. In PowerShell 7 (but not PowerShell Core 6 or Windows PowerShell 5) the ConvertFrom-SecureString cmdlet has an -AsPlainText switch which gets the secret back. With older version the standard process is to create a credential object and then ask for the plaintext “network password”.
  4. It is only securing the credential at rest in the file. At some stage the plain text will be in memory (and malware can find it).

A script which looks like the following is obviously bad

$MyUserName = "James"

$MyPassword = ConvertTo-SecureString -force -asPlainText "NotVerySecret"

$Credential = New-Object PSCredential $myUserName,$MyPassword

Better to do

if (Test-path mycred.xml)  {

       $Credential = Import-Clixml myCred.xml

}

else {

       $Credential = Get-Credential

       $Credential | export-CliXML my.cred.xml

}

In the xml file you might see something like this

<Props>

<S N="UserName">james</S>

<SS N="Password">01000000d08c9ddf011 … 395d4060</SS>

</Props>

<S> Denotes a normal string and <ss> a long sequence of digits which become a secure string. There are ways use different keys, and DSC uses one of them to encrypt passwords when making putting them in a .MOF file, which service can then decode, but this simple method uses a personal, local key.

Credentials and secure strings are also useful for things like personal access tokens or API Keys where you might prefer not to save the plain text, but the commands which use them expect plain text here’s a simple case

Function Demo {

    [cmdletBinding()]

    param(

        [string]$ApiKey

    )

    "Your API key is $ApiKey"

}

Because [string] acts as “Convert what you are given to a string”, the function won’t refuse a secure string or a credential – it outputs

Your API key is System.Security.SecureString or Your API key is System.Management.Automation.PSCredential

My usual answer to this that $APIkey shouldn’t be typed and the body of the function should include a check the type of $APIKey and convert it as required; this approach has worked well for me as long as I have been doing PowerShell but there is an option to do it more elegantly.

One Question which came up on GitHub recently went like this:

Some commands take an API key use it as a plain text string so they have a string-typed parameter, but it would be useful to have a parameter attribute which allowed a string declared as a string to accept and convert credential or securestring objects.

PowerShell 5 and upwards can support argument transformers, and and the one below is named “UnSecureString”, it’s declared as an argument transformation attribute, with a single method, transform, which receives the parameter as “Input data” and returns an object. To work on version 5, it converts a credential to a password, and if it is given a secure string, it converts to it to a credential first.

class UnSecureString : System.Management.Automation.ArgumentTransformationAttribute  {

    [object] Transform([System.Management.Automation.EngineIntrinsics]$EngineIntrinsics, [object] $InputData) {

        if ($InputData -is [securestring]) {

            $InputData =  New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'Placeholder', $InputData

        }

        if ($InputData -is  [pscredential]) {

            $InputData =  $InputData.GetNetworkCredential().password

        }

        return ($InputData)

    }

}



With this in place the parameter declaration in the function changes: 

Function Demo {

    [cmdletBinding()]

    param(

        [UnsecureString()] 
        [string]$ApiKey

    )

    "Your API key is $ApiKey"

}

And now it will take string, secure String, or credential

>demo "api12345key"

Your API key is api12345key

>$ss = ConvertTo-SecureString -AsPlainText "api34567key" -Force

>demo $ss

Your API key is api34567key

>$cred = New-Object pscredential "NoRealName", $ss

>demo $cred

Your API key is api34567key

It’s a debatable whether the parameter should still be typed in this case. In another github discussion, someone said they didn’t like to leave the untyped because on-line help shows parameter types and leaving them as the default, [object] skipped a chance to guide the user to provide the right thing. Since PowerShell lets us specify full paths and relative paths as strings, path info objects, or file info objects to identify a file we want to work on, I’ve always tried to accept make a “-Widget” parameter accept a unique widget ID, a Widget Name (possibly with wildcards), or one or more Object(s), the user shouldn’t need to care and I will write help explaining the three options. Without the help I’d be making the user guess.  The help for this example steers the user towards providing a plain text string

NAME

    Demo

   
SYNTAX

    Demo [[-ApiKey] <string>]

My preference is more towards removing the type and assuming the user will read more of the help, but I don’t think these things are absolutes. As supporting older versions of PowerShell becomes less of an issue, using classes and shifting the “first make sure this is the right kind of thing” code can simplify the function body, which is a plus. 
There is a case for changing the parameter type to secure string and having a transformer which turns plain strings to secure ones. But that can lead to writing

Demo –apikey (convertTo-SecureString ….) ,  
which is the horrible insecure method I showed at the start but more annoying to a user – especially if they look inside the function and see the first thing that happens is the secure string is converted back an placed in a request body. Examples should never show that, but should be based on the secure string coming from a file.

Advertisement

April 2, 2020

Including PowerShell classes in modules: a quick round-up

Filed under: Powershell,Uncategorized — jamesone111 @ 5:47 pm
Tags: , ,

Recently I have been adding PowerShell classes to modules and this post is what I’ve learned about where the class code should be located and how different ways of loading it have odd looking side effects. At some point I’ll make a list of people who should get some credit for educating me, because I would not have discovered all of it on my one.

From the start PowerShell has been able to use the Add-Type command to compile a type written in C# (or visual basic) and load it into the PowerShell session like any other class. Windows PowerShell 5 added the ability to code classes in PowerShell and there’s little if any change version 6 and 7. However there are some quite unusual behaviours when they are used in modules, and which don’t seem well documented. The following applies to the classes written in PowerShell (not the ones loaded with Add-Type)

  1. A PowerShell class is only visible outside its own module if either.

    a. It is loaded in the PSM1 file (not dot sourced into the PSM1) AND the module is loaded with the using module directive. OR

    b. It is loaded from a PS1 file using the ScriptsToProcess section of the PSD1 manifest.

  2. If a module is loaded by a script which specifies Using module, classes from its PSM1 are only visible in the script’s scope (inside a ps1, unless the file is Dot sourced.)

    The same scope issues/rules apply if the class is loaded from ScriptsToProcess and the module is loaded with Import-module

  3. If a class used as a parameter attribute in a function is not visible in the scope where that function is called it cannot be abbreviated by dropping the Attribute from its name.

    In other words, a parameter attribute which has been shortened from [ValidateWibbleAttribute()] to [ValidateWibble ()] may cause failures if the module is loaded in the wrong way. [2] above should make it clear that sooner or later any module will be loaded the wrong way.

Based on this

  1. My view of a .PSM1 file as a small “loader” – which I think is a common one – needs to at least be revised enough to allow for Classes to be included in it.

    I’m still not keen on having everything in the PSM1 and will probably continue to keep functions in their own PS1 files, but functions can be successfully dot sourced into the PSM1 and classes cannot.
  2. Using module X is preferable to Import-Module X, where the module has some requirement which means it won’t run on legacy Windows PowerShell.

    In other words,V4 and earlier are still out in the field – and for modules which work with those versions you might prefer import to using. However V4 won’t import a module with classes so with modules which need V5/6/7 sticking to Import-Module doesn’t gain anything and makes classes invisible

  3. I’m no longer trimming ‘attribute’ off the names of parameter attribute class names when declaring parameters. I’m not sure at the moment if there is any reason to use attribute in the class name (it appears not).

Blog at WordPress.com.