Published on 23 January 2018 by

With any luck, you’ve seen all the reports about the vulnerabilities recently discovered called Spectre and Meltdown (CVE-2017-5715, CVE-2017-5753, and CVE-2017-5754) and know how serious they are.

And odds are, you’re already planning the rollout of the patches and BIOS updates needed to bring your environment into compliance, and you have a good idea of how many nodes need to be touched, but you don’t have an easy way of verifying the current state of things. But you need a quick way to determine what the current state is, and for that way to be repeatable so you can verify that your patching is successful.

Microsoft recently published a blog post on verifying Spectre/Meltdown using a new PowerShell module that was released to determine Spectre/Meltdown compliance. In this blog post we’ll go through the steps on how to accomplish the same thing using Puppet Bolt and Puppet Enterprise Task Management.

We’ll start by just issuing the commands needed to get the information we want, move on to how to make this a reusable script, then end with a way to make this a distributable Puppet task that can be run at any time.

Setup

To get up and running with Puppet Bolt, we need to install it on our workstation. A more complete walkthrough can be found here.

Note: Be sure to have ruby installed first.

PS C:\> gem install bolt

That's all there is to installing Bolt!

The last thing we need is the SpeculationControl PowerShell module. We can install the module on our target nodes using the PowerShell Gallery. Alternately, we could avoid having to install anything on our target nodes by copying the content of the module into our commands or script. There are reasons for either approach, and your environment will largely decide the approach you take. We’re going to take the approach of installing it on target nodes in this post. To do so, we’ll run the following commands using bolt:

PS C:\> bolt command run 'PackageManagement\Get-PackageProvider -Name NuGet -Force | Out-Null;Install-Module -Name SpeculationControl -Force | Out-Null' --nodes winrm://192.168.0.12 --user jpogran --password ********** --insecure
Started on 192.168.0.12...
Finished on 192.168.0.12:

We first ran the 'PackageManagement\Get-PackageProvider cmdlet in order to ensure that NuGet is installed on the target node. If we hadn’t then the Install-Module cmdlet would prompt for confirmation to install the NuGet binary, which would block our bolt command (interactive prompts are not supported in bolt). We send the results of installing the module to Out-Null so we don’t pollute our result stream, and we could use the color of ‘Finished on’ to indicate success (green) or failure (red).

Now that we are setup, let’s start querying our target nodes.

Running the Puppet Bolt command

Let's see what kind of output we're going to get. We'll connect to the remote hosts using the WinRM transport with bolt, and run the Get-SpeculationControlSettings function.

Note: We’re using the --insecure option here because this example is running against a test lab environment without WinRM setup to use SSL. If your nodes have SSL setup and running, you can skip using the --insecure flag. We could also skip specifying some transport options by using a bolt configuration file, but for this post we will use all the command line options. For more information see the bolt documentation

PS C:\> bolt command run 'Get-SpeculationControlSettings' --nodes winrm://192.168.0.12 --user jpogran --password ********** --insecure
Started on 192.168.0.12...
Finished on 192.168.0.12:
  STDOUT:
    Speculation control settings for CVE-2017-5715 [branch target injection]

    Hardware support for branch target injection mitigation is present: False
    Windows OS support for branch target injection mitigation is present: False
    Windows OS support for branch target injection mitigation is enabled: False

    Speculation control settings for CVE-2017-5754 [rogue data cache load]

    Hardware requires kernel VA shadowing: True
    Windows OS support for kernel VA shadow is present: False
    Windows OS support for kernel VA shadow is enabled: False

    Suggested actions

     * Install BIOS/firmware update provided by your device OEM that enables hardware support for the branch target injection mitigation.
     * Install the latest available updates for Windows with support for speculation control mitigations.
     * Follow the guidance for enabling Windows Server support for speculation control mitigations described in https://support.microsoft.com/help/4072698


    BTIHardwarePresent             : False
    BTIWindowsSupportPresent       : False
    BTIWindowsSupportEnabled       : False
    BTIDisabledBySystemPolicy      : False
    BTIDisabledByNoHardwareSupport : False
    KVAShadowRequired              : True
    KVAShadowWindowsSupportPresent : False
    KVAShadowWindowsSupportEnabled : False
    KVAShadowPcidEnabled           : False



Ran on 1 node in 1.68 seconds

That works, but the output is not ideal. There's a bunch of text that explains the report but it's mixed in with the PSObject so our output isn't parseable. That's because the Get-SpeculationControlSettings function uses Write-Host instead of Write-Verbose or another information stream method. Besides requesting that the Get-SpeculationControlSettings function be updated, there are a few ways around this. In this case we will get around this by overriding the Write-Host function.

PS C:\> bolt command run 'function global:Write-Host(){};Get-SpeculationControlSettings' --nodes winrm://192.168.0.12 --user jpogran --password ********** --insecure
Started on 192.168.0.12...
Finished on 192.168.0.12:
  STDOUT:


    BTIHardwarePresent             : False
    BTIWindowsSupportPresent       : False
    BTIWindowsSupportEnabled       : False
    BTIDisabledBySystemPolicy      : False
    BTIDisabledByNoHardwareSupport : False
    KVAShadowRequired              : True
    KVAShadowWindowsSupportPresent : False
    KVAShadowWindowsSupportEnabled : False
    KVAShadowPcidEnabled           : False



Ran on 1 node in 1.64 seconds

That's better, we now have only the PSObject with the data we need to start reporting on status of our target nodes. There's one more problem though, it's just unstructured text. We can use PowerShell's formatting capabilities and turn the unformatted text to JSON so we can parse it later.

PS C:\> command run 'function global:Write-Host(){};Get-SpeculationControlSettings | ConvertTo-JSON' --nodes winrm://192.168.0.12 --user jpogran --password ********** --insecure
Started on 192.168.0.12...
Finished on 192.168.0.12:
  STDOUT:
    {
        "BTIHardwarePresent":  false,
        "BTIWindowsSupportPresent":  false,
        "BTIWindowsSupportEnabled":  false,
        "BTIDisabledBySystemPolicy":  false,
        "BTIDisabledByNoHardwareSupport":  false,
        "KVAShadowRequired":  true,
        "KVAShadowWindowsSupportPresent":  false,
        "KVAShadowWindowsSupportEnabled":  false,
        "KVAShadowPcidEnabled":  false
    }
Ran on 1 node in 1.62 seconds

We now have parseable text that we can use to report on our environment. But our command string is getting a little long, and it's not accounting for installing the module on our target hosts. Running this as a command would be too unwieldy. We need to turn this into a script that can handle these requirements. It's time to move to a Puppet Bolt script.

Running the Puppet Bolt script

We can run scripts using Bolt by issuing the bolt script run subcommand. We'll first create a file called spectre_meltdown.ps1 and add the code we already worked through, while also cleaning it up a little.

if(!(Get-Module -Name SpeculationControl)){
  # bootstrap nuget so install-module works without prompting 
  PackageManagement\Get-PackageProvider -Name NuGet -Force | Out-Null
  # install speculationcontrol module
  Install-Module -Name SpeculationControl -Force | Out-Null
}

# overwrite write-host because Get-SpeculationControlSettings uses it instead of
# returning just an object
function global:Write-Host() {}

# interrogate system
$settings = Get-SpeculationControlSettings

# determine compliance
$compliance = $false
if ($settings.KVAShadowRequired -eq $False) {
  $compliance = $True
}elseif ($settings.KVAShadowRequired -eq $True -and `
  $settings.KVAShadowWindowsSupportPresent -eq $True -and `
  $settings.KVAShadowWindowsSupportEnabled -eq $True -and `
  $settings.KVAShadowPcidEnabled -eq $True) {
  $compliance = $True
}

# format output
[PSCustomObject]@{
  Compliant                      = $compliance
  BTIHardwarePresent             = $settings.BTIHardwarePresent
  BTIWindowsSupportPresent       = $settings.BTIWindowsSupportPresent
  BTIWindowsSupportEnabled       = $settings.BTIWindowsSupportEnabled
  BTIDisabledBySystemPolicy      = $settings.BTIDisabledBySystemPolicy
  BTIDisabledByNoHardwareSupport = $settings.BTIDisabledByNoHardwareSupport
  KVAShadowRequired              = $settings.KVAShadowRequired
  KVAShadowWindowsSupportPresent = $settings.KVAShadowWindowsSupportPresent
  KVAShadowWindowsSupportEnabled = $settings.KVAShadowWindowsSupportEnabled
  KVAShadowPcidEnabled           = $settings.KVAShadowPcidEnabled
} | ConvertTo-Json

In the script we add the typical PowerShell module installation boilerplate to get the SpeculationControl module on our target node, then use our Write-Host trick to squash the informational messages we don't want.

Note: Our Write-Host trick here is temporary. Ideally the function would not pollute the result stream and instead use one of the information streams, but we have to work with what we have right now. Long term, we should look at contributing a fix to the project for this module or using the save/restore pattern to ensure we don’t trip up and break a Write-Host used down the line. Since this is exploratory work, we are fine for now.

After running Get-SpeculationControlSettings we use the results to determine compliance, using the recommendations from the Microsoft security team that wrote the module. In the end, we output a PSCustomObject and pipe it to the ConvertTo-Json cmdlet to report on the status.

PS C:\> bolt script run ./spectre_meltdown.ps1 --nodes winrm://192.168.0.12 --user jpogran --password ********** --insecure

So now we have a reusable script that we can run against all the nodes in our environment. Running this from the command line yourself is quick and easy, but what about a week from now? Or having someone else run this? Time to move to Puppet Tasks.

Running the Puppet Bolt task

There are a few simple steps to creating Puppet Tasks that are outlined here and a lot of examples in our hands on lab Github Repo. For the purposes of our task today, we'll use Puppet Development Kit (PDK) to get us started.

Creating a Puppet task is easy with PDK. Since a Puppet task lives inside a Puppet module, we'll start by creating a module for our task using PDK.

$ pdk new module spectre_meltdown
PS C:\src\experiments> pdk new module spectre_meltdown
pdk (INFO): Creating new module: spectre_meltdown
We need to create a metadata.json file for this module, so we're going to ask you 9 questions.
If the question is not applicable to this module, accept the default option shown after each question. You can modify any answers at any time by manually updating the metadata.json file.
[Q 1/9] If you have a Puppet Forge username, add it here.
We can use this to upload your module to the Forge when it's complete.
--> jpogran
[Q 2/9] What version is this module?
Puppet uses Semantic Versioning (semver.org) to version modules.
--> 0.1.0
[Q 3/9] Who wrote this module?
This is used to credit the module's author.
--> jpogran
[Q 4/9] What license does this module code fall under?
This should be an identifier from https://spdx.org/licenses/. Common values are "Apache-2.0", "MIT", or "proprietary".
--> Apache-2.0
[Q 5/9] What operating systems does this module support?
Use the up and down keys to move between the choices, space to select and enter to continue.
--> RedHat based Linux, Debian based Linux, Windows (Use arrow or number (1-7) keys, press Space to select and Enter to
--> RedHat based Linux, Debian based Linux, Windows
[Q 6/9] Summarize the purpose of this module in a single sentence.
This helps other Puppet users understand what the module does.
--> detect spectre/meltdown
[Q 7/9] If there is a source code repository for this module, enter the URL here.
Skip this if no repository exists yet. You can update this later in the metadata.json.
-->
[Q 8/9] If there is a URL where others can learn more about this module, enter it here.
Optional. You can update this later in the metadata.json.
-->
[Q 9/9] If there is a public issue tracker for this module, enter its URL here.
Optional. You can update this later in the metadata.json.
-->
Metadata will be generated based on this information, continue? Yes
pdk (INFO): Module 'spectre_meltdown' generated at path 'C:/src/experiments/spectre_meltdown', from template 'file://C:
/PROGRA~1/PUPPET~1/DevelopmentKit/share/cache/pdk-templates.git'.
pdk (INFO): In your module directory, add classes with the 'pdk new class' command.
PS C:\src\experiments>

PDK then walks us through an interview to help us create the Puppet module with all the plumbing and information we need, without us having to remember how to do it. Creating the task is just as simple:

PS C:\src\experiments> cd .\spectre_meltdown\
PS C:\src\experiments\spectre_meltdown> pdk new task detect
pdk (INFO): Creating 'C:/src/experiments/spectre_meltdown/tasks/detect.sh' from template.
pdk (INFO): Creating 'C:/src/experiments/spectre_meltdown/tasks/detect.json' from template.

We're now ready to start making our Puppet task. Open up the module in any editor you prefer, here we use Visual Studio Code with the Puppet VSCode extension installed.

Visual Studio Code with Puppet VSCode extension

PDK created the module skeleton for us, as well as some example files for our task. We'll keep detect.json, but rename detect.sh to detect.ps1 instead.

detect.json

Inside detect.ps1 we'll add the code we already worked through when we made our Puppet Bolt script. A Puppet task can use the script as is, we don’t have to adjust the script or change anything to get it working. If we needed parameters or other metadata inside the script, we could add them now, but it’s not needed to get running.

detect.ps1

Now our Puppet task is ready to be tested out on our target nodes. Let's run it against a single node to see what output we get back. We'll use Bolt just like before, but use the task subcommand.

PS C:\> bolt task run --nodes winrm://192.168.0.12 --user jpogran --password ******* --modulepath ../ spectre_meltdown::detect
Started on 192.168.33.12...
Finished on 192.168.33.12:
  {
    "Compliant": false,
    "BTIHardwarePresent": false,
    "BTIWindowsSupportPresent": false,
    "BTIWindowsSupportEnabled": false,
    "BTIDisabledBySystemPolicy": false,
    "BTIDisabledByNoHardwareSupport": false,
    "KVAShadowRequired": true,
    "KVAShadowWindowsSupportPresent": false,
    "KVAShadowWindowsSupportEnabled": false,
    "KVAShadowPcidEnabled": false
  }
Ran on 1 node in 6.92 seconds

Note: For eagle-eyed readers you may notice the runtime increased in this try compared to others in the article. The total runtime of the commands will vary depending on if the SpeculationControl PowerShell module is installed on the target node. If it’s not then the time will increase based on how long it takes to install the module on the target node.

Works like a charm. Notice we specified the modulepath since we're actively developing this. You wouldn't need to do this with a published module that is installed in your modulepath.

Working against one node is fine, how about against multiple nodes:

PS C:\> task run --nodes winrm://192.168.0.12,winrm://192.168.0.13 --user jpogran --password *******  --modulepath ../ spectre_meltdown::detect
Started on 192.168.0.12...
Started on 192.168.0.13...
Finished on 192.168.0.12:
  {
    "Compliant": false,
    "BTIHardwarePresent": false,
    "BTIWindowsSupportPresent": false,
    "BTIWindowsSupportEnabled": false,
    "BTIDisabledBySystemPolicy": false,
    "BTIDisabledByNoHardwareSupport": false,
    "KVAShadowRequired": true,
    "KVAShadowWindowsSupportPresent": false,
    "KVAShadowWindowsSupportEnabled": false,
    "KVAShadowPcidEnabled": false
  }
Finished on 192.168.0.13:
  {
    "Compliant": false,
    "BTIHardwarePresent": false,
    "BTIWindowsSupportPresent": false,
    "BTIWindowsSupportEnabled": false,
    "BTIDisabledBySystemPolicy": false,
    "BTIDisabledByNoHardwareSupport": false,
    "KVAShadowRequired": true,
    "KVAShadowWindowsSupportPresent": false,
    "KVAShadowWindowsSupportEnabled": false,
    "KVAShadowPcidEnabled": false
  }
Ran on 2 nodes in 1.62 seconds

Success! Now that we have a Puppet task, we can add this to our Puppet Enterprise install and use it to report on our hosts.

James Pogran is a senior software engineer at Puppet.

Learn more

Share via:
Posted in:
The content of this field is kept private and will not be shown publicly.

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.