Escalating privilege with Bolt

By default, Bolt connects to, and executes on, remote systems as the same user. Sometimes, connection and execution need to be done as separate users. For example, you might need to install a package as the root user on a system that doesn't allow incoming connections as the root user. Bolt has several configuration options for setting which user to execute as, how to escalate to that user, and how to run commands as that user.

Limitations

Bolt will only use the run-as user if:

  • The target shell is Bash

  • The transport is Local or SSH

  • The configured run-as user is different than the connecting user

Escalating privilege must be generalized

Limiting privilege escalation permission to certain commands will not work well when running Bolt tasks, scripts, and some plans. Bolt often uses a temporary directory to copy content to remote systems and execute, which prevents Bolt's remote commands from being predictable. Furthermore, Bolt is an automation tool, and many organizations enforce specific security policies to prevent the kind of automated interaction that Bolt is performing. As a result, you will need to allow arbitrary script execution for any users who need to use Bolt to run tasks or scripts. We recommend either having a dedicated user to run Bolt on target systems and managing access to that user, or using Puppet Enterprise to limit which tasks a user can run on which targets.

Configuring run-as

You can use privilege escalation from the command-line, or set it up using Bolt's configuration files.

Command-line options

--run-as USER

User to run as using privilege escalation.

--sudo-password PASSWORD

Password for privilege escalation.

--sudo-password-prompt

Prompt for user to input escalation password.

--sudo-executable EXEC

Experimental. Specify an executable for running as another user.

Configuration file options

You can configure escalation privilege in your inventory.yaml or bolt-defaults.yaml files. For more information on which file to use, see Configuring Bolt.

run-as

  • Type: String

run-as-command

  • Type: Array

sudo-executable

  • Type: String

sudo-password

  • Type: String

How run-as works

Each time Bolt executes, it builds a command string to run on the remote system. The command is built from many configuration options, including interpreters, task input method, and the run-as configuration. When run-as is set, the following is prepended to the command:

<sudo-executable> -S -H -u <run-as> -p '[sudo] Bolt needs to run as another user, password:'

The default sudo-executable is sudo. If you're running a task that reads parameters from environment variables, -E will be appended.

Note: The command is shell escaped, so each value is interpreted literally. This means you can't set sudo command-line options in the run-as configuration. Use the run-as-command option to specify your own sudo command and command-line options.

You can replace this command wholesale by specifying run-as-command, which will prepend the following to the command being run:

<run-as-command> <run-as>

This enables using any executable and command-line options to change users. Keep in mind that:

  • The run-as-command is non-interactive. If you set run-as-command and the system prompts for a password, Bolt will error instead of supplying the password.

  • Bolt always appends the specified run-as user to the specified run-as-command, which can be limiting for some escalating commands.

  • Bolt will not use the run-as-command option if run-as is not set.

Escalating privilege over WinRM

The WinRM transport does not support run-as. This is because WinRM operates with a session level token and has no functionality to change to another user after authentication. Once you connect with a user, you can only perform actions that you have permission to perform with that user. Instead of switching users, you can connect with one user, and then provide a secondary user's credentials to a program to perform another action. The behavior of providing credentials to a program is not part of WinRM.

For example, to get the processes running on a Windows target, you could connect to the target with WinRM using Invoke-BoltScript -Script .\myscript.ps1 -Targets winrm://mytarget -User userA -Password passwordA and then use a Powershell script to pass credentials to the Get-WmiObject command:

$Cred = New-Object System.Management.Automation.PsCredential('userB',$passwordB)
Get-WmiObject -Class Win32_BIOS -Computer SERVER1 -Credential $Cred

If you want to securely pass credentials into a Powershell script, you have two options:

  • Convert your script into a task and use sensitive task parameters: This is the best option if you need your secret obfuscated in logs. You can either pass the parameters as static values to a task, or compute them per target and use a plan to pass them to the task.

  • Use a remote task: If your secrets are unique to each target and you don't want to write a Bolt plan, you can use a remote task. This option requires you to manage the connection to the target yourself, rather than letting Bolt manage the connection.

If you're not sure where to start, we recommend using a task with sensitive parameters. If your secrets are unique to targets, you must also use an inventory plugin and a Bolt plan. This method is demonstrated in the example below.

If your secrets are the same for every target, you can pass sensitive parameters to a task as static values. If your secrets are unique to each target, or the secrets need to be computed or fetched, set the secrets as data on the target in the inventory. For the purposes of escalating as a secondary user, you can set arbitrary variables on targets in the inventory which you can then access from a Bolt plan.

You can set variables on the target object in your inventory, either statically as plaintext in the inventory file or dynamically using inventory plugins. You can then access variables on the target object in a plan, or using a remote task.

Remote tasks run on localhost and pass the entire target object as JSON to the task as a metaparameter _target. You can write a task to get the secret, and then connect to the remote target and use the secret there. Put another way, if you're using a remote task, you have to manage the connection to the remote target in your task rather than letting Bolt manage it for you. You should only use a remote task if you are comfortable managing connections in a script and don't want to write a plan, or are unable to connect directly to the target using Bolt.

Example

This example uses a task with sensitive parameters, together with a Bolt plan and the PKCS7 inventory plugin.

The inventory file below uses the PKCS7 plugin to set the secondary_user_password variable on two groups of targets:

# inventory.yaml
---
groups:
  - name: databases
    targets:
      - mywindows.com
      - mywindows2.com
    vars:
      secondary_user_pw:
        _plugin: pkcs7
        encrypted_value: |
          ENC[PKCS7, <ENCRYPTED_DATA>]
  - name: load_balancers
    targets:
      - mywindows3.com
      - mywindows4.com
    vars:
      secondary_user_pw:
        _plugin: pkcs7
        encrypted_value: |
          ENC[PKCS7, <OTHER_ENCRYPTED_DATA>]

The example::plan plan collects the variables from the targets and passes them to the example::task task as sensitive parameters:

# This is the example plan that we use to get variables from targets and pass them to the task
plan example::plan(
  TargetSpec $targets
) {
  # This gets all the data for the targets from the inventory and creates
  # Target objects.
  $target_objects = get_targets($targets)

  run_task_with('example::task', $targets) |$target| {
    # You can also pass static values here.
    { 'secondarypassword' => $target.vars['secondary_user_pw'],
      'message' => 'Good bye' }
  }
}

The task's metadata.json file defines secondarypassword as a sensitive parameter:

{
  "input_method": "powershell",
  "parameters": {
    "secondarypassword": {
      "type": "String",
      "description": "The password for the user to run 'GetWMI' as",
      "sensitive": true
    },
    "message": {
      "type": "String",
      "description": "The message to print when finished"
    }
  }
}

The task recieves the sensitive parameters from the plan:

[CmdletBinding()]
param(
  [Parameter(Mandatory = $True)]
  [string]
  $Secondarypassword

  [Parameter(Mandatory = $True)]
  [string]
  $Message
)

$Pw = $Secondarypassword | ConvertTo-SecureString -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PsCredential('bolt', $Pw)
Get-WmiObject -Class Win32_BIOS -Computer SERVER1 -Credential $Cred

Write-Output $Message

📖 Related information