-
Notifications
You must be signed in to change notification settings - Fork 1
/
EC2Access.psm1
272 lines (220 loc) · 11.9 KB
/
EC2Access.psm1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#
# Main code and public interface for EC2Access module
#
#
# Copyright 2021 Cloudsoft Corporation Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
$ErrorActionPreference = 'Stop'
function Convert-RSAEncryptedCipherTextToClearText {
Param (
[Parameter(Mandatory=$true)][Alias('pem')][Alias('p')][string]$pemFile,
[Parameter(Mandatory=$true)][Alias('cipher')][string]$cipherText
)
if (-not (Test-Path -Path $pemFile)) {
# file does not exist - handle error
}
$sr = [System.IO.StreamReader]::new($pemFile)
$pr = [Org.BouncyCastle.OpenSsl.PemReader]::new($sr)
$keyPair = $pr.ReadObject()
$rsa = [Org.BouncyCastle.Security.DotNetUtilities]::ToRSA($keyPair.Private)
$cipherBytes = [System.Convert]::FromBase64String($cipherText)
$clearBytes = $rsa.Decrypt($cipherBytes, $false)
Return [System.Text.Encoding]::ASCII.GetString($clearBytes)
}
<#
.SYNOPSIS
Get the Administrator password for a Windows EC2 instance.
.PARAMETER InstanceId
The AWS EC2 instance ID. This is the string "i-" followed by a series of hexadecimal digits.
.PARAMETER Region
The AWS region containing the instance. If omitted, the region is fetched from the environment or the AWS configuration files.
.PARAMETER PrivateKeyFile
A file containing the private key to decrypt the password. This must be in "PEM" format, as used by SSH and as given by AWS if you use it to create a keypair. If omitted, it will default to ".ssh\id_rsa" from your home directory.
.DESCRIPTION
This function queries the AWS APIs for the encrypted password data of an EC2 instance, and then attempts to decrypt it using a private key stored in a file on your computer. The decrypted password is returned as a SecureString.
AWS EC2 contains a public key store. When starting an EC2 instance, you will be required to choose a public key. For Windows instances, EC2 will generate a random password, assign it to the Administrator user, and then encrypt the password using the chosen public key and stores it in the EC2 control plane.
This function will fetch the encrypted data, then decrypt it using the private key. The private key must be in a file on disk and written in PEM format. This is the standard format using by SSH for storing private keys. If you use EC2's "Create key" function, it will store the public key and then download the private key in PEM format to you (without storing it).
.EXAMPLE
Get-EC2Password -InstanceId i-12345678abcd -Region eu-west-2
.OUTPUTS
A SecureString object containing the password for the EC2 instance.
#>
function Get-EC2Password {
[CmdletBinding()] param(
[Parameter(Mandatory=$true, Position=0)] [string]$InstanceId,
[Parameter(Position=1)] [string]$Region,
[Parameter(Position=2)] [string]$PrivateKeyFile
)
# Verify the private key files exists
if(!($PrivateKeyFile)) {
$PrivateKeyFile = $HOME + '\.ssh\id_rsa'
Write-Verbose "No private key file given - defaulting to $($PrivateKeyFile)"
}
if(-not (Test-Path $PrivateKeyFile)) {
Write-Error "$($PrivateKeyFile) does not exist. Do you need to use -PrivateKeyFile argument?"
}
Write-Verbose "Requesting password data from AWS"
if ($Region) {
$cipherText = Get-EC2PasswordData -InstanceId $InstanceId -Region $Region
} else {
$cipherText = Get-EC2PasswordData -InstanceId $InstanceId
}
Write-Verbose "Decrypting password"
$password = Convert-RSAEncryptedCipherTextToClearText -PemFile $PrivateKeyFile -CipherText $cipherText
return (ConvertTo-SecureString -String $password -AsPlainText -Force)
}
<#
.SYNOPSIS
Start a Remote Desktop session with a Windows EC2 instance on a public IP address.
.PARAMETER InstanceId
The AWS EC2 instance ID. This is the string "i-" followed by a series of hexadecimal digits.
.PARAMETER Region
The AWS region containing the instance. If omitted, the region is fetched from the environment or the AWS configuration files.
.PARAMETER PrivateKeyFile
A file containing the private key to decrypt the password. This must be in "PEM" format, as used by SSH and as given by AWS if you use it to create a keypair. If omitted, it will default to ".ssh\id_rsa" from your home directory.
.DESCRIPTION
Start a Remote Desktop session with an EC2 instance. The instance must have a public IP address and a security group rule that permits access from your public IP address.
This function will retrieve the Windows Administrator password (see Get-EC2Password) and "pre-load" the credentials into the Remote Desktop client, so that you will not need to manually enter credentials into the Remote Desktop client.
If your instance is not reachable by public IP address, but it is configured for Systems Manager Session Manager, see Start-EC2RemoteDesktopViaSessionManager for a way to start Remote Desktop sessions.
.EXAMPLE
Start-DirectEC2RemoteDesktop -InstanceId i-12345678abcd -Region eu-west-2
#>
function Start-DirectEC2RemoteDesktop {
[CmdletBinding(SupportsShouldProcess)] param(
[Parameter(Mandatory=$true, Position=0)] [string]$InstanceId,
[Parameter(Position=1)] [string]$Region,
[Parameter(Position=2)] [string]$PrivateKeyFile
)
$password = Get-EC2Password -InstanceId $InstanceId -Region $Region -PrivateKeyFile $PrivateKeyFile
$Credential = New-Object PSCredential "Administrator",$password
if($Region) {
$response = Get-EC2Instance -InstanceId $InstanceId -Region $Region
} else {
$response = Get-EC2Instance -InstanceId $InstanceId
}
$HostName = $response.Instances[0].PublicIpAddress
Write-Verbose "Instance address is $HostName"
if ($PSCmdlet.ShouldProcess($InstanceId,'Start remote desktop session')) {
Start-RemoteDesktop -HostName $HostName -Credential $Credential
}
}
<#
.SYNOPSIS
Start a Remote Desktop session with a Windows EC2 instance using Systems Manager Session Manager.
.PARAMETER InstanceId
The AWS EC2 instance ID. This is the string "i-" followed by a series of hexadecimal digits.
.PARAMETER Region
The AWS region containing the instance. If omitted, the region is fetched from the environment or the AWS configuration files.
.PARAMETER PrivateKeyFile
A file containing the private key to decrypt the password. This must be in "PEM" format, as used by SSH and as given by AWS if you use it to create a keypair. If omitted, it will default to ".ssh\id_rsa" from your home directory.
.DESCRIPTION
Start a Remote Desktop session with an EC2 instance. The instance must be configured to support Systems Manager Session Manager. This function will configure a Session Manager port forwarding session and invoke the Remote Desktop client through the forwarded port.
This function will retrieve the Windows Administrator password (see Get-EC2Password) and "pre-load" the credentials into the Remote Desktop client, so that you will not need to manually enter credentials into the Remote Desktop client.
.EXAMPLE
Start-EC2RemoteDesktopViaSessionManager -InstanceId i-12345678abcd -Region eu-west-2
#>
function Start-EC2RemoteDesktopViaSessionManager {
[CmdletBinding(SupportsShouldProcess)] param(
[Parameter(Mandatory=$true, Position=0)] [string]$InstanceId,
[Parameter(Position=1)] [string]$Region,
[Parameter(Position=2)] [string]$PrivateKeyFile
)
$password = Get-EC2Password -InstanceId $InstanceId -Region $Region -PrivateKeyFile $PrivateKeyFile
$Credential = New-Object PSCredential "Administrator",$password
$LocalPort = 33389
$PortForwardParams = @{ portNumber=(,"3389"); localPortNumber=(,$LocalPort.ToString()) }
if($Region) {
$session = Start-SSMSession -Target $InstanceId -DocumentName AWS-StartPortForwardingSession -Parameters $PortForwardParams -Region $Region
} else {
$session = Start-SSMSession -Target $InstanceId -DocumentName AWS-StartPortForwardingSession -Parameters $PortForwardParams
}
# We now need to emulate awscli - it invokes session-manager-plugin with the new session information.
# AWS Tools for PowerShell don't do this. Also some of the objects seem to look a bit different, and the
# plugin is pernickety, so we have to jump through some hoops to get all the objects matching up as close
# as we can.
$SessionData = @{
SessionId=$session.SessionID;
StreamUrl=$session.StreamUrl;
TokenValue=$session.TokenValue;
ResponseMetadata=@{
RequestId=$session.ResponseMetadata.RequestId;
HTTPStatusCode=$session.HttpStatusCode;
RetryAttempts=0;
HTTPHeaders=@{
server="server";
"content-type"="application/x-amz-json-1.1";
"content-length"=$session.ContentLength;
connection="keep-alive";
"x-amzn-requestid"=$session.ResponseMetadata.RequestId;
}
}
}
$RequestData = @{
Target=$InstanceId;
DocumentName="AWS-StartPortForwardingSession";
Parameters=$PortForwardParams
}
$Arguments = (
(ConvertTo-Json $SessionData -Compress),
$Region,
"StartSession",
"",
(ConvertTo-Json $RequestData -Compress),
"https://ssm.$($Region).amazonaws.com"
)
# Now we have to do some PowerShell hacking. Start-Process takes an array of arguments, which is great,
# but it doesn't actually do what we expect it to - see https://github.com/PowerShell/PowerShell/issues/5576.
# So instead we have to turn it into an escaped string ourselves...
$EscapedArguments = $Arguments | ForEach-Object { $escaped = $_ -replace "`"", "\`""; "`"$($escaped)`"" }
$ArgumentString = $EscapedArguments -join " "
# Start the Session Manager plugin:
if ($PSCmdlet.ShouldProcess($session.SessionId,'Start Session Manager plugin')) {
try {
$Process = Start-Process -FilePath "session-manager-plugin.exe" -ArgumentList $ArgumentString -NoNewWindow -PassThru
} catch {
Write-Error "Unable to start the process session-manager-plugin.exe. Have you installed the Session Manager Plugin as described in https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html#install-plugin-windows ?"
exit
}
# Wait a moment for it to connect to the session and open up the local ports
Start-Sleep -Seconds 1
# The port should be open now - let's connect
if ($PSCmdlet.ShouldProcess($InstanceId,'Start remote desktop session')) {
Start-RemoteDesktop -HostName "127.0.0.1" -Credential $Credential -Port $LocalPort
}
# Once the desktop session has finished, kill the session manager plugin
$Process.Kill()
}
}
function Start-RemoteDesktop {
[CmdletBinding(SupportsShouldProcess)] param(
[Parameter(Mandatory=$true, Position=0)] [String] $HostName,
[Parameter(Mandatory=$true, Position=1)] [PSCredential] $Credential,
[Parameter()] [Int32] [string]$Port
)
$nwcredential = $Credential.GetNetworkCredential()
if ($PSCmdlet.ShouldProcess($HostName,'Adding credentials to store')) {
Start-Process -FilePath "$($env:SystemRoot)\system32\cmdkey.exe" -ArgumentList ("/generic:TERMSRV/$HostName","/user:$($nwcredential.UserName)","/pass:$($nwcredential.Password)") -WindowStyle Hidden -Wait
}
if ($PSCmdlet.ShouldProcess($HostName,'Connecting mstsc')) {
if ($PSBoundParameters.ContainsKey('Port')) {
$target = "$($HostName):$($Port)"
} else {
$target = $HostName
}
Start-Process -FilePath "$($env:SystemRoot)\system32\mstsc.exe" -ArgumentList ("/v",$target) -NoNewWindow -Wait
}
}
Export-ModuleMember -Function Get-EC2Password
Export-ModuleMember -Function Start-EC2RemoteDesktopViaSessionManager
Export-ModuleMember -Function Start-DirectEC2RemoteDesktop