PoshBot icon indicating copy to clipboard operation
PoshBot copied to clipboard

Consider a mechanism for running commands as a different user

Open RamblingCookieMonster opened this issue 7 years ago • 5 comments

Consider a mechanism for running commands as a different user

It may be helpful to include a mechansim to simplify running as a different user.

Currently, if you need to do something with privilege XYZ, there are a few common methods:

  • Run PoshBot as account with privilege XYZ. High risk, particularly if you start doing more and more to poshbot!
  • Add per-poshbot-command handling for running as other accounts. Results in less portable commands, and ugly workarounds

Potential alternative:

Provide some convention or handling for credentials, including an option to use them when starting a job.

Hypothetical, poorly-thought-out example:

Ahead of time, as the account running PoshBot:

# Various implementation details to consider. if ^\w$, assume relative path to configuration folder? etc.
New-PoshBotConfiguration ... -Credential @{
    AD = '\\Path\To\Serialized\ADCredential'
    RT = '\\Path\To\Serialized\RTCredential'
}

Plugins and/or individual commands could be decorated to specify a credential to run with:

[PoshBot.BotCommand(
    CommandName = 'disableuser',
    Permissions = 'write',
    Credential = 'AD'
)]

The last part is tricky. Depending on how PoshBot executes commands (iirc the plan was to use jobs for now?), PoshBot could check the command or plugin config, and if a credential is specified, read the poshbot configuration, deserialize the credential, and use it to launch a job.

Whew!

Does something like this make sense? I don't see it needed in the short term, I'm currently just relying on deserializing credentials with the poshbot plugin / commands themselves, biggest concern is that this makes it a bit tough to share them, and that not all commands offer an easy way to specify credentials (in which case I would need to spawn a job, use a JEA endpoint, or some other workaround)

Cheers!

RamblingCookieMonster avatar May 14 '17 03:05 RamblingCookieMonster

Yes, I think we need some way to manage credentials that commands can run under.

One question is should plugin commands be able to access a global collection of credentials or should PoshBot separate them by plugin and only allow commands to access credentials associated with that plugin? The latter is more secure but potentially more cumbersome to manage. You may have multiple plugins that you've authored for your company and if you want to use the same credentials across plugins, you'd have to specify it in the configuration multiple times.

devblackops avatar Jun 16 '17 04:06 devblackops

Hmm - good question it might make sense to allow both, similar to statefuldata commands?

e.g.

Add-PoshBotRunAsCredential or whatever system stores the credential might have something similar to -Scope, allowing a user to specify if they want a credential to be available to everything, or a specific module.

For example:

  • If -Global switch specified, any plugin can access it
  • If -Module [string[]] specified, any plugin in the list can access it

RamblingCookieMonster avatar Jun 26 '17 19:06 RamblingCookieMonster

oh! if this is up for grabs, would be happy to help! some quick thoughts:

I see two general workflows:

  • PoshBot admin wants to always run command ABC with credential XYZ, doesn't want to revamp internals of the plugin command. Design would likely require a command-to-credential mapping file
  • PoshBot author wants to allow specifying a certain credential. Author will likely need to retrieve a PSCredential. Design would likely benefit from both (1) the option of a command-to-credential map, and (2) potentially the option to allow a user to specify the unique name of a credential to use

Might be over the top, just thinking off the top of my head

  • Where / how to serialize credentials
    • Typical DPAPI serialization to $PoshBotContext.ConfigurationDirectory or a subdirectory might make sense?
  • What other information to include
    • Username
    • Password
    • Optional? Unique Name (default to username, error and require force if existing found regardless of whether Name was provided or not)
    • AllowedModules? Array, use like operator when evaluating whether a plugin can use it (* for global)
    • Roles? Array, user calling command must be in this role to retrieve credential
    • Description? Just for record keeping / details if desired
    • Tags? A way for PoshBot authors to find a credential that their command needs, without forcing a user to specify a certain pre-determined name
  • How to manage associating a command with a credential. Might need a config file mapping commands to credentials

Down the line, this could be used with data about a remote node to execute on, but IMHO it makes sense to keep that separate and let folks mix and match creds/remote details as needed. Or it could be combined. I see benefits / caveats to both...

Cheers!

RamblingCookieMonster avatar Jun 28 '17 02:06 RamblingCookieMonster

We may be able to reuse (with some tweaks) the existing functionality in PoshBot that allows plugin authors to specify that a command parameter by dynamically resolved from the bot configuration. The existing logic could probably be extended for plugin authors to declare that a command be run (as a job) with specific credentials that are stored under the PluginConfiguration property of the bot configuration.

The way it currently works

PoshBot's Get-PoshBotConfiguration and Save-PoshBotConfiguration functions use Joel's Configuration module so it already handles serializing/deserialing credentials and secure strings (with the caveat that the user getting/saving the config must also be the user running PoshBot). To use those values inside your plugin, you would decorate the parameter with [PoshBot.FromConfig()] and PoshBot would look in the appropriate key (plugin name) and subkey (parameter name) and dynamically insert that value as a named parameter when executing the command as a job. This doesn't solve running the entire command as another user but does provide a mechanism to pass secrets and other data to plugin commands that you can't (or don't want to) distribute with the plugin.

PoshBot.psd1
@{
    ###
    # other bot properties omitted
    ###    
    PluginConfiguration = @{
        MyModule = @{
            Credential = (PSCredential "joeuser" "01000000d08c9ddf0115d1118c7a00c04fc297eb0100000060ae863578849c4680a57d65f2994eff00000000020000000000106600000001000020000000cd8c90628fc9f7fd332869a0e30eec41cbje8c531618375f22bfa84a2a53e132000000000e80000000020000200000003b7027c1f5577bd36f7f9c87db7bb427f4808466758eb9a579e36be9bc49b3481000000092223e3261d78e6547ed0f799f5462eb400000008hpddfa3619fcc7b56bb2571b8cc9405740bf266e1fd8fc79b9nj1203ad9058d19a73eb75f5d977ef4478dc9f207f21e19c95affd1d44eca0b405f879e3c98nu")
        }
    }
}
MyModule.psm1
function Get-Foo {
    [cmdletbinding()]
    param(
        [PoshBot.FromConfig()]
        [parameter(mandatory)]
        [string]$Credential
    )

    # Do something useful with $Credential
}

How it could work

This would be a breaking change to how plugin configuration is currently stored but we could modify the structure to resemble:

PoshBot.psd1
@{
    ###
    # other bot properties omitted
    ###    
    PluginConfiguration = @{
        # Global variables that any plugin can reference
        Global = @{
            SharedCred1 = (PSCredential "shared1" "01000000d08c9ddf0115d1118c7a00c04fc297eb0100000060ae863578849c4680a57d65f2994eff00000000020000000000106600000001000020000000cd8c90628fc9f7fd332869a0e30eec41cbje8c531618375f22bfa84a2a53e132000000000e80000000020000200000003b7027c1f5577bd36f7f9c87db7bb427f4808466758eb9a579e36be9bc49b3481000000092223e3261d78e6547ed0f799f5462eb400000008hpddfa3619fcc7b56bb2571b8cc9405740bf266e1fd8fc79b9nj1203ad9058d19a73eb75f5d977ef4478dc9f207f21e19c95affd1d44eca0b405f879e3c98nu")
            SharedCred2 = (PSCredential "shared2" "01000000d08c9ddf0115d1118c7a00c04fc297eb0100000060ae863578849c4680a57d65f2994eff00000000020000000000106600000001000020000000cd8c90628fc9f7fd332869a0e30eec41cbje8c531618375f22bfa84a2a53e132000000000e80000000020000200000003b7027c1f5577bd36f7f9c87db7bb427f4808466758eb9a579e36be9bc49b3481000000092223e3261d78e6547ed0f799f5462eb400000008hpddfa3619fcc7b56bb2571b8cc9405740bf266e1fd8fc79b9nj1203ad9058d19a73eb75f5d977ef4478dc9f207f21e19c95affd1d44eca0b405f879e3c98nu")
        }

        # Variables only accessible to the plugin
        MyModule = @{
            Credential1 = (PSCredential "joeuser" "01000000d08c9ddf0115d1118c7a00c04fc297eb0100000060ae863578849c4680a57d65f2994eff00000000020000000000106600000001000020000000cd8c90628fc9f7fd332869a0e30eec41cbje8c531618375f22bfa84a2a53e132000000000e80000000020000200000003b7027c1f5577bd36f7f9c87db7bb427f4808466758eb9a579e36be9bc49b3481000000092223e3261d78e6547ed0f799f5462eb400000008hpddfa3619fcc7b56bb2571b8cc9405740bf266e1fd8fc79b9nj1203ad9058d19a73eb75f5d977ef4478dc9f207f21e19c95affd1d44eca0b405f879e3c98nu")
            MyOtherCredential = (PSCredential "sallysmith" "01000000d08c9ddf0115d1118c7a00c04fc297eb0100000060ae863578849c4680a57d65f2994eff00000000020000000000106600000001000020000000cd8c90628fc9f7fd332869a0e30eec41cbje8c531618375f22bfa84a2a53e132000000000e80000000020000200000003b7027c1f5577bd36f7f9c87db7bb427f4808466758eb9a579e36be9bc49b3481000000092223e3261d78e6547ed0f799f5462eb400000008hpddfa3619fcc7b56bb2571b8cc9405740bf266e1fd8fc79b9nj1203ad9058d19a73eb75f5d977ef4478dc9f207f21e19c95affd1d44eca0b405f879e3c98nu")
            }
        }
    }
}

You would then reference these variables in your plugin command by using the [PoshBot.BotCommand()] and [PoshBot.FromConfig()] custom attributes.

function Get-Foo {
    # Run this command as a job using the 'SharedCred2' value from
    # the global plugin configuration scope
    [PoshBot.BotCommand(
        CommandName = 'foo',
        RunAs = 'SharedCred2',
        Scope = 'Global'
    )]
    [cmdletbinding()]
    param(
        # Go get the 'Credential1` value from the plugin scope of the bot configuration
        # 'Scope = 'Plugin' would be implicit
        [PoshBot.FromConfig('Credential1')]
        [pscredential]$Credential

        # Go get the 'SharedCred1' value from the global plugin configuration scope
        [PoshBot.FromConfig(
            'SharedCred1',
            Scope = 'Global'
        )]
        [pscredential]$SharedCredential
    )    
}

I like the idea of given the bot admin the ability to specify the credentials a command will run under without mucking with the plugin itself. Perhaps another property could be added to the bot configuration like the following:

PoshBot.psd1

@{
    # Control under what credential plugin commands are run under.
    # Can specify individual commands or all commands in a plugin.
    RunAs = @{
        'MyPlugin:MyCommand' = 'SharedCred1`
        'MyPlugin:Foo' = 'SharedCred2'
        'MyOtherPlugin:*' = 'SharedCred1'
    }
    PluginConfiguration = @{
        # Global variables that any plugin can reference
        Global = @{
            SharedCred1 = (PSCredential "shared1" "01000000d08c9ddf0115d1118c7a00c04fc297eb0100000060ae863578849c4680a57d65f2994eff00000000020000000000106600000001000020000000cd8c90628fc9f7fd332869a0e30eec41cbje8c531618375f22bfa84a2a53e132000000000e80000000020000200000003b7027c1f5577bd36f7f9c87db7bb427f4808466758eb9a579e36be9bc49b3481000000092223e3261d78e6547ed0f799f5462eb400000008hpddfa3619fcc7b56bb2571b8cc9405740bf266e1fd8fc79b9nj1203ad9058d19a73eb75f5d977ef4478dc9f207f21e19c95affd1d44eca0b405f879e3c98nu")
            SharedCred2 = (PSCredential "shared2" "01000000d08c9ddf0115d1118c7a00c04fc297eb0100000060ae863578849c4680a57d65f2994eff00000000020000000000106600000001000020000000cd8c90628fc9f7fd332869a0e30eec41cbje8c531618375f22bfa84a2a53e132000000000e80000000020000200000003b7027c1f5577bd36f7f9c87db7bb427f4808466758eb9a579e36be9bc49b3481000000092223e3261d78e6547ed0f799f5462eb400000008hpddfa3619fcc7b56bb2571b8cc9405740bf266e1fd8fc79b9nj1203ad9058d19a73eb75f5d977ef4478dc9f207f21e19c95affd1d44eca0b405f879e3c98nu")
        }
    }
}

Sorry for the super long response. It's late and I hope this makes sense 😫

I'm totally open to storing this data outside the main bot configuration if that makes sense as well.

devblackops avatar Jun 28 '17 06:06 devblackops

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Aug 23 '19 05:08 stale[bot]