Skip to content

Add Extensibility to Plaster's Constrained Runspace #333

Closed

Description

@bwright86

Plaster is a good platform to scaffold content, and I have used it for building module templates. But recently I have been using it to scaffold application configuration data.

One shortcoming I have found is that Plaster uses constrained runspaces to perform the scaffolding. Correct me if I'm wrong, but this was to provide safety around what is essentially Invoke-Expression. The difficulty comes from trying to write clean code and turn comma separated strings into properly formatted output.

Example template files:

Test.json.txt
(This is the simplest way I could think to join comma separated strings into a JSON array.)

{
    "Key":  "Value",
    "Array":  [
        ""
<%
if ($PLASTER_PARAM_Param1) {
    $PLASTER_PARAM_Param1.split(",").trim().foreach({",`"$_`""})
}
%>
               ]
}

The resulting file is not well formatted, and has a blank element in the first position of the array:
Test.json

{
    "Key":  "Value",
    "Array":  [
        ""
,"Test"
,"Testing"
               ]
}

Another option is to use .Replace, and replace commas with newlines(`n) and double quotes ("). But newlines are not interpolated in the constrained runspace. So this doesn't work:

"`"$($PLASTER_PARAM_Param1.replace(",", "`"`n,`""))`""

Possible Solutions

One solution that I would like to see, is a configuration file that can be placed in a user directory (like $PROFILE), or in the module folder itself. This file contains a list of allowable cmdlets in the scaffolding process, and Invoke-Plaster adds these cmdlets to the runspace. This would provide the extensibility, without having to rebuild the module with a custom list of cmdlets. It also puts the security onus on the user that adds the commands. Either the installing user can add a config file, or only the current user's config file is used during scaffolding.

Another solution, is adding a parameter to the Invoke-Plaster function, like -AllowedCmdlets to add a list of cmdlets to be used during scaffolding. This makes it a little easier for the user to scaffold with the cmdlets they need, but on the flip-side it makes it easier to pass cmdlets required for command injection. So this provides a little less control when scaffolding content.

Either of these solutions would allow the code to be cleaner, like so:

{
    "Key":  "Value",
    "Array":  [
<%
if ($PLASTER_PARAM_Param1) {
    $output = $PLASTER_PARAM_Param1 -split ",") -join "`",`n`""
    "`"$output`""
} else {
    "`"`""
}
%>
               ]
}

Activity

  1. rkeithhill commented on Jun 12, 2018

    @rkeithhill
    Collaborator

    provide safety around what is essentially Invoke-Expression.

    It's a bit more fundamental than that. The idea is that folks can scaffold with any template and not have to worry about templates doing anything other than creating/editing files under the specified destination path. It's a trade-off between trust in Plaster/templates and flexibility. We've err'd on the side of safety/trust.

    One option we could consider is a special parameter to Invoke-Plaster along the lines of -AllowArbitraryScriptExecution, in which case we'd create the runspace as "unconstrained" or just run in the current runspace - whichever is easier to implement alongside the existing constrained runspace.

    Or we could provide an -AllowedCmdlets although there are likely functions and possibly aliases template authors want to use. Or we could allow template authors to declare additional commands in the template and then during execution, we show these to the user and get approval. The only problem with that is that it puts a heck of a burden on the end user to keep themselves safe. IIRC I once tried to add get-command to the constrained runspace only to discover that I could then execute any command.

    Anyway, any changes to "open up" Plaster would likely require the review of the PowerShell Committee.

  2. bwright86 commented on Jun 12, 2018

    @bwright86
    Author

    I think it would be worth it to add the flexibility into Plaster, so that the user can choose. For first use, Plaster could default to using a safety zone for scaffolding, but also have the ability to extend Plaster to do more useful things.

    Maybe some tooling could be done to utilize PSScriptAnalyzer for scanning Plaster Templates to see if potentially hazardous things are being done, or create some Pester tests that can provide a level of safety (i.e. warnings, etc...) to downloaded templates. Even using the zone identifiers on downloaded files could let Plaster know the files came from the "Internet Zone".

    I think a lot of people are currently building their own templates (Probably mostly to learn.), so there is less worry about what they put into their own scripts. But if a central repository of templates comes to fruition, it will be good to already have a safety mechanism in place.

    I have written a very rough module to extract input parameters and TemplateFile variables for Plaster Templates (It still needs a way to extract script blocks from a TemplateFile.) This was more to validate that the correct input is being collected in a manifest for the matrices of TemplateFiles being utilized. If there is interest to extend this into passing the output into PSScriptAnalyzer or Pester Tests, I could build it towards that.
    Link: https://github.com/bwright86/plasterhelper

    Thank you for the response.

  3. JustinGrote commented on Jul 3, 2018

    @JustinGrote
    Contributor

    What I've been doing is basically queueing up anything that doesn't operate inside the runspace and writing it out to a separate script file, then printing a message when the template is done that says "For additional functionality, please run {. $pathtofile} after you have reviewed the contents of the script and determined them safe". Things like git setup, account setup tasks/authorization, etc.

    For my powershell module constructor, I have a step in the invoke-build that I write called "Setup" that also includes these. So after deploying the plaster you can run invoke-build setup for additional items, but like @rkeithhill says the user can trust that Plaster itself won't trash their system.

    I like the idea of keeping Plaster "safe" at the expense of ease of use, because it's pretty easy to just add a second step for the extra stuff, and you can write config items decided during plaster questions to a configuration file that will automate the second-step process.

  4. locked and limited conversation to collaborators on Feb 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions