From 678e584119597cd3a4a610b48f61121a90bce878 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sat, 2 Mar 2024 23:16:43 -0500 Subject: [PATCH 1/2] add PSWSMAN and remoting examples --- .../powershell-runtime/Dockerfile | 10 +- .../powershell-runtime/readme.md | 151 ++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 examples/demo-container-images-shared/powershell-runtime/readme.md diff --git a/examples/demo-container-images-shared/powershell-runtime/Dockerfile b/examples/demo-container-images-shared/powershell-runtime/Dockerfile index 50ca814..770cde6 100644 --- a/examples/demo-container-images-shared/powershell-runtime/Dockerfile +++ b/examples/demo-container-images-shared/powershell-runtime/Dockerfile @@ -31,7 +31,15 @@ RUN rm -Rf $ARTIFACTS_DIR/modules/Private # Make bootstrap script executable RUN chmod +x /var/runtime/bootstrap +# https://github.com/jborean93/PSWSMan +# This fixes WSMan/OMI so that PowerShell remoting is possible to Windows hosts. +RUN DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 /opt/powershell/pwsh -C '\ + Install-PSResource -Name PSWSMAN -Version "[2.3.1,3.0.0]" -Scope AllUsers -TrustRepository ; \ + Install-WSMan -Verbose ; \ + Get-WSManVersion | Format-List \ + ' + ## Build final image FROM public.ecr.aws/lambda/provided:al2023 ## Copy PowerShell runtime files -COPY --from=runtime-files . / \ No newline at end of file +COPY --from=runtime-files . / diff --git a/examples/demo-container-images-shared/powershell-runtime/readme.md b/examples/demo-container-images-shared/powershell-runtime/readme.md new file mode 100644 index 0000000..438fe3c --- /dev/null +++ b/examples/demo-container-images-shared/powershell-runtime/readme.md @@ -0,0 +1,151 @@ +# About PowerShell remoting + +With the help of [`PSWSMAN`](https://github.com/jborean93/PSWSMan) PowerShell remoting into Active Directory domain-joined Windows hosts is possible, using Kerberos authentication. You will need credentials that have permission to connect to the host and session configuration. + +## Lambda considerations + +### Network access + +Since your lambda needs to have access to your domain, it will need network access to perform the authentication and remoting, so it will usually need to be in a VPC and given a security group that allows it connect to those resources. + +### Memory + +Recommend at least 512 MB, as it seems to use just over 256 even without doing much of anything. + +### Timeout + +Consider that the authentication and connection process over WinRM/PSRP is kind of slow. On a cold start, it could take ~30 seconds until your code gets to the point where a command is executing against the remote host. On a warm start, that will be less but not instant. Consider starting with a timeout measured in minutes and pare down after you get a feel for how long things are taking. It will be even slower with less than 512 MB of memory. + +## Examples + +### Credential + +You **must** use UPN format (`user@REALM`), and the realm **is case sensitive**. + +For the Actice Directory domain `ad.contoso.com` and the user `account`, you must set the user name to `account@AD.CONTOSO.COM`. + +For all of the following examples, we'll assume the use of a `PSCredential` object in the variable `$credential`. + +Here's an example of retrieving it from AWS Secrets Manager, but it could come from anywhere. It is not recommended to accept Active Directory credentials as direct function input since it is not encrypted and likely to be logged. + +This example assumes that the lambda already has IAM permission to read from the secret, and the ARN is set via environment variable `ConnectorSecretARN`. + +It assumes that the secret is formatted as JSON with the following fields: + +```json +{ + "user": "account", + "domain_dns": "ad.contoso.com", + "password": "correct horse battery staple" +} +``` + +```powershell +Import-Module -Name AWS.Tools.SecretsManager + +$connector_secret = Get-SECSecretValue -SecretId $env:ConnectorSecretARN | ConvertFrom-Json +$user = "{0}@{1}" -f $connector_secret.user, $connector_secret.domain_dns.ToUpper() +$pass = ConvertTo-SecureString -String $connector_secret.password -AsPlainText -Force +$credential = [PSCredential]::new($user, $pass) +``` + +### Simple use + +Using `Invoke-Command` implicitly creates a PSSession to run a script in and removes it (closes it) when the script is done executing. + +```powershell +Invoke-Command -ComputerName server01.ad.contoso.com -Credential $credential -ScriptBlock { + $env:COMPUTERNAME + & whoami.exe + Get-ChildItem +} +``` + +### Passing data to a remote session + +Variables in the local scope are not available in the remote scope. There are two ways to pass values into the remote command. + +#### The `using:` scope modifier + +See also: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes#the-using-scope-modifier + +The `using:` method is a little more straightforward in many cases where you want to use the same variable names in the local and remote side. + +```powershell +$a, $b, $c = @(1, 2, 3) + +Invoke-Command -ComputerName server01.ad.contoso.com -Credential $credential -ScriptBlock { + Write-Host $using:a + Write-Host $using:b + Write-Host $using:c +} +``` + +### Arguments + +The arguments method requires declaring a `param()` block in the scriptblock, but that can also be versatile. It allows full parameter declaration like `[Parameter()]` and validation attributes, and it lets you give different names to the variables for use in the remote scriptblock which can help distinguish them. + +However there is no way to pass them in by name, so you have to match up your values to your parameters positionally. + +```powershell +$a, $b, $c = @(1, 2, 3) + +Invoke-Command -ComputerName server01.ad.contoso.com -Credential $credential -ScriptBlock { + param($x, $y, $z) + + Write-Host $x + Write-Host $y + Write-Host $z +} -ArgumentList @($a, $b, $c) + +# Passing a single object requires wrapping it in a single element list +Invoke-Command -ComputerName server01.ad.contoso.com -Credential $credential -ScriptBlock { + param($x) + + Write-Host $x +} -ArgumentList @(,$a) +``` + +### Reusing a session + +Creating the session object separately lets you use it for several commands at different points in the execution. Be sure to close the session when finished otherwise it may stay open on the remote end. + +```powershell +$session = New-PSSession -ComputerName server01.ad.contoso.com -Credential $credential + +try { + $remote_output = Invoke-Command -Session $session -ScriptBlock { Invoke-CustomFunction } + $converted_data = $remote_output | Convert-DataToSomething + Invoke-Command -Session $session -ScriptBlock { Update-AppData -Data $using:converted_data } +} +finally { + if ($session) { + Remove-PSSession -InputObject $session -ErrorAction SilentlyContinue + } +} +``` + +### Using JEA and custom session configurations + +Using [Just Enough Administration](https://learn.microsoft.com/en-us/powershell/scripting/learn/remoting/jea/overview) you can provide very restricted endpoints and remote sessions, with different local and remote identities. The documentation above has the details on how to set this up on the remote (Windows) side. For the purposes of a connecting lambda, all you need to know is the name of the Session Configuration to connect to. + +```powershell +Invoke-Command ` + -ComputerName server01.ad.contoso.com ` + -Credential $credential ` + -SessionConfiguration My.Custom.Configuration ` + -ScriptBlock { Get-Command -Module Microsoft.PowerShell.Archive } +``` + +```powershell +$session = New-PSSession -ComputerName server01.ad.contoso.com -Credential $credential -SessionConfiguration My.Custom.Configuration + +try { + Invoke-Command -Session $session -ScriptBlock { Invoke-CustomFunction } +} +finally { + if ($session) { + Remove-PSSession -InputObject $session -ErrorAction SilentlyContinue + } +} +``` From b9cfde3d1707c27aa549c82c10215b7d138b7b21 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sat, 2 Mar 2024 23:17:20 -0500 Subject: [PATCH 2/2] add (BROKEN?) NTLM support --- .../demo-container-images-shared/Dockerfile | 6 ++- .../powershell-remoting-ntlm/Dockerfile | 47 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 examples/demo-container-images-shared/powershell-remoting-ntlm/Dockerfile diff --git a/examples/demo-container-images-shared/Dockerfile b/examples/demo-container-images-shared/Dockerfile index 734490f..27b41b0 100644 --- a/examples/demo-container-images-shared/Dockerfile +++ b/examples/demo-container-images-shared/Dockerfile @@ -1,6 +1,8 @@ #FROM public.ecr.aws/lambda/provided:al2023 ## INSTALL POWERSHELL RUNTIME FROM 978558897928.dkr.ecr.us-east-1.amazonaws.com/powershell-runtime:latest as runtime-files +## Install gss-ntlmssp and related packages for NTLM authentication +# FROM 978558897928.dkr.ecr.us-east-1.amazonaws.com/powershell-remoting-ntlm:latest as remoting-files ## INSTALL AWS SDK FROM 978558897928.dkr.ecr.us-east-1.amazonaws.com/powershell-modules-aws-tools:latest as module-files @@ -8,6 +10,8 @@ FROM 978558897928.dkr.ecr.us-east-1.amazonaws.com/powershell-modules-aws-tools:l FROM public.ecr.aws/lambda/provided:al2023 ## Copy PowerShell runtime files COPY --from=runtime-files . / +## Copy NTLM auth files +# COPY --from=remoting-files . / ## Copy Module files COPY --from=module-files . / ## Function files @@ -15,4 +19,4 @@ COPY /function/ /var/task ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 WORKDIR /var/task ENTRYPOINT [ "/var/runtime/bootstrap" ] -CMD [ "examplehandler.ps1::handler" ] \ No newline at end of file +CMD [ "examplehandler.ps1::handler" ] diff --git a/examples/demo-container-images-shared/powershell-remoting-ntlm/Dockerfile b/examples/demo-container-images-shared/powershell-remoting-ntlm/Dockerfile new file mode 100644 index 0000000..6cc73b3 --- /dev/null +++ b/examples/demo-container-images-shared/powershell-remoting-ntlm/Dockerfile @@ -0,0 +1,47 @@ +# syntax=docker/dockerfile:1 + +# This image is based on the existing powershell-runtime image. +# Set any of the arguments as needed in case you customized the image details. +ARG REGISTRY=.dkr.ecr..amazonaws.com +ARG RUNTIME_IMAGE=${REGISTRY}/powershell-remoting +ARG RUNTIME_TAG=latest +ARG RUNTIME=${RUNTIME_IMAGE}:${RUNTIME_TAG} + +FROM ${RUNTIME} as build + +WORKDIR /tmp + +# These build dependencies are documented here: +# https://github.com/gssapi/gss-ntlmssp/blob/main/contrib/gssntlmssp.spec.in#L13 +RUN dnf install -y \ + autoconf automake docbook-style-xsl doxygen findutils krb5-devel \ + libtool libxml2 libxslt libunistring-devel m4 pkgconfig openssl-devel + +# These build dependencies are also needed but not in the spec file. +# It's easier to iterate on these packages when they are in their own RUN step. +RUN dnf install -y rpm-build gettext-devel libwbclient-devel zlib-devel + +# Build gssntlmssp +RUN <