Wednesday, November 28, 2018

Launching EC2 Instance with an IAM Role – Part 1 of 2


IAM role is a secure mechanism to delegate access to users,
applications, or services. The role defines both who can assume the permissions
and what these permissions are. These are defined as two separate JSON policy
documents. When an IAM role is associated with the EC2 instance, EC2 makes
temporary credentials available to the instance, and periodically rotates them.
The applications running inside the instance can retrieve and use these temporary
credentials. The role creation for EC2 is a multistep process



a) Create
the role with policy document that defines who can assume this role.



b) Write
the permission policy document to grant specific permissions.



c) Create
the instance profile. Role cannot be directly associated with the instance, but
only through instance profile. The
profile container provides extra level of indirection. That is why a profile is
created first and then the role is added to it. As a convention, profile name
is the same as the role name. AWS Console follows the same convention as well.



d) Add
role to the instance profile



e) Finally,
associate instance profile to the instance as part of the launch.




Policy document defines “who” has access. In this case, permissions
are granted to the agent running inside the EC2 instance (ec2config).







$assumePolicy
= @"





"Version":"2012-10-17",




"Statement":[





"Sid":"",




"Effect":"Allow",




"Principal":"Service":"ec2.amazonaws.com",




"Action":"sts:AssumeRole"




]




"@





Below JSON document defines which API actions and resources, the
application or user is allowed after assuming the role. The ssm:*,
ec2messages:* APIs listed below are needed for ec2config to interact with the
SSM service. The ds:CreateComputer is needed for auto domain join, logs:* and
cloudwatch:* are needed for the CloudWatch plugin, s3:* is for storing Run
Command output in s3.



Please follow the security best practices in granting minimal
permissions needed. (e.g.) If you are not using CloudWatch, remove that part.
Likewise, restrict to specific resources instead of “*”.




#Define “what” access (specific APIs and resource)



$policy
= @"




"Version":
"2012-10-17",



"Statement": [




"Effect":
"Allow",



"Action":
[




"cloudwatch:PutMetricData",



"ds:CreateComputer",




"ec2messages:*",




"logs:CreateLogGroup",




"logs:CreateLogStream",




"logs:DescribeLogGroups",




"logs:DescribeLogStreams",




"logs:PutLogEvents",




"s3:PutObject",




"s3:GetObject",



"s3:AbortMultipartUpload",




"s3:ListMultipartUploadParts",




"s3:ListBucketMultipartUploads",




"ssm:DescribeAssociation",




"ssm:ListAssociations",




"ssm:GetDocument",




"ssm:UpdateAssociationStatus",



"ssm:UpdateInstanceInformation",




"ec2:DescribeInstanceStatus"



],




"Resource": "*"




]




"@





Below script performs the above defined four steps to create a role
and add it to the instance profile.





function
SSMCreateRole ([string]$RoleName = 'ssm-demo-role')




#Skips if the
role is already present



if (Get-IAMRoles






This function cleans up all the objects created by SSMCreateRole.
This takes name of the role as input, removes the role from the instance
profile and deletes instance profile. Then, it deletes the policy associated
with the role, finally it deletes the role itself.



function
SSMRemoveRole ([string]$RoleName = 'ssm-demo-role')




if (!(Get-IAMRoles






A security group acts as a virtual firewall that controls the
traffic for one or more instances. AWS offers layers of security for defense in
depth and security group is one among them. Security best practice is to allow
only the expected traffic and from known sources. The script below allows
network traffic only from one source, where the script is executed. For
example, if you launch the EC2 instance from your home laptop, the security
group is configured to allow traffic only from the public IP address associated
with your home router. It uses http://checkip.amazonaws.com/ to get
the source IP address as seen by AWS. The source IP has to be a public IP
address. Private IP address behind the NAT or proxy server will not work. The code
below retrieves the source IP address and creates a security group to allow
RDP, PS remoting, port 80, ICMP traffic from this source IP. Change this
function to suit your needs.




if (Get-EC2SecurityGroup




function
SSMRemoveSecurityGroup ([string]$SecurityGroupName
= 'ssm-demo-sg')




$securityGroupId
= (Get-EC2SecurityGroup






EC2 key pair is based on private key and public key. The key pair
is used to encrypt password in Windows and ssh access on Linux. EC2 generates
random password and stores in an encrypted fashion, using the public key
associated with key name defined during the instance launch. EC2 does not save
the unencrypted password and also does not have access to private key. Since the
EC2 customer exclusively has access to the private key, only the customer can
decrypt. Creating a key pair is simple. The private key associated with the key
pair can only be retrieved at the time of creating. If the private key is lost,
then the password cannot be retrieved. Script to create a key pair and retrieve
the associated private key is shown below. The retrieved private key is saved in
the file name specified. Safe guarding this private key is very important from
security point of view and once lost, it is gone!




[string]$KeyName = 'ssm-demo-key',



[string]$KeyFile = "c:keys$keyName.$((Get-DefaultAWSRegion).Region).pem"



)



Out-File -encoding ascii -filepath
$keyfile





function
SSMRemoveKeypair (



[string]$KeyName = 'ssm-demo-key',



[string]$KeyFile = "c:keys$keyName.$((Get-DefaultAWSRegion).Region).pem"



)




#delete
keypair



del $keyfile -ea 0



Remove-EC2KeyPair
-KeyName $keyName
-Force



Write-Verbose
"Removed keypair=$keypair, keyfile=$keyfile"







Now that we have created all the dependent resources like IAM role,
security group, and key pair, the instance can be created.





[string]$ImageName =
'WINDOWS_2012R2_BASE',



[string]$SecurityGroupName =
'ssm-demo-sg',



[string]$InstanceType =
'm4.large',



[string]$Tag = 'ssm-demo',



[string]$KeyName = 'ssm-demo-key',



[string]$KeyFile = "c:keys$keyName.$((Get-DefaultAWSRegion).Region).pem",



[string]$RoleName = 'ssm-demo-role'



)



`



? $_.GroupName
-eq $SecurityGroupName
).GroupId



if (! $securityGroupId)



throw
"Security Group $SecurityGroupName not
found"





#Get the
latest R2 base image



$image
= Get-EC2ImageByName
$ImageName




#User Data to
enable PowerShell remoting on port 80



#User data
must be passed in as 64bit encoding.



$userdata
= @"




Enable-NetFirewallRule
FPS-ICMP4-ERQ-In



Set-NetFirewallRule
-Name WINRM-HTTP-In-TCP-PUBLIC -RemoteAddress Any



New-NetFirewallRule
-Name "WinRM80" -DisplayName "WinRM80" -Protocol TCP
-LocalPort 80



Set-Item
WSMan:localhostServiceEnableCompatibilityHttpListener -Value true




"@



$utf8 = [System.Text.Encoding]::UTF8.GetBytes($userdata)



$userdataBase64Encoded
= [System.Convert]::ToBase64String($utf8)




#Launch EC2
Instance with the role, firewall group created



# and on the
right subnet



$instance
= (New-EC2Instance
-ImageId $image.ImageId `



-InstanceProfile_Id
$RoleName `



-AssociatePublicIp
$true `



-SecurityGroupId
$securityGroupId `



-KeyName
$keyName `



-UserData
$userdataBase64Encoded `



-InstanceType
$InstanceType).Instances[0]




#Wait to
retrieve password



$cmd =



$password
= Get-EC2PasswordData
-InstanceId $instance.InstanceId `



-PemFile
$keyfile -Decrypt



$password
-ne $null




SSMWait $cmd 'Password
Generation' 600




$password
= Get-EC2PasswordData
-InstanceId $instance.InstanceId `



-PemFile
$keyfile -Decrypt



$securepassword
= ConvertTo-SecureString
$Password -AsPlainText
-Force



$creds
= New-Object
System.Management.Automation.PSCredential
("Administrator", $securepassword)




#update the
instance to get the public IP Address



$instance
= (Get-EC2Instance
$instance.InstanceId).Instances[0]




#Wait for
remote PS connection



$cmd =



icm $instance.PublicIpAddress

-Credential $creds
-Port 80




SSMWait $cmd 'Remote
Connection' 450




New-EC2Tag -ResourceId $instance.InstanceId -Tag
@



$instance.InstanceId





function
SSMRemoveInstance (



[string]$Tag = 'ssm-demo',



[string]$KeyName = 'ssm-demo-key',



[string]$KeyFile = "c:keys$keyName.$((Get-DefaultAWSRegion).Region).pem"



)




$filter1
= @



$filter2
= @



$instance
= Get-EC2Instance
-Filter @($filter1, $filter2)




if ($instance)



$instanceId
= $instance.Instances[0].InstanceId




$null
= Stop-EC2Instance
-Instance $instanceId
-Force -Terminate




Write-Verbose
"Terminated instance $instanceId"



else



Write-Verbose
"Skipping as instance with name=$Tag not found"







Instance creation is an async operation (i.e.) New-EC2Instance API
just initiates the instance creation process, and we need to periodically poll
in a loop for completion. We will use “Wait” function explained in a previous blog
located here. Waiting
for instance creation is achieved in two steps. First one is to poll for
password generation. Once the password is generated, keep trying until the
remote connection is established successfully. Establishing remote connection
is used as a proxy check for instance readiness.


#Wait function



function
SSMWait (



[ScriptBlock] $Cmd,



[string] $Message,



[int] $RetrySeconds,



[int] $SleepTimeInMilliSeconds = 5000)




$_msg = "Waiting for $Message to
succeed"



$_t1 = Get-Date



Write-Verbose
"$_msg in $RetrySeconds seconds"



while ($true)




$_t2
= Get-Date



try



select -Last 1



if
($? -and
$_result)




Write-Verbose("Succeeded $Message in " + `



"$_([int]($_t2-$_t1).TotalSeconds)
Seconds, Result=$_result")



break;





catch





$_t
= [int]($_t2 - $_t1).TotalSeconds



if
($_t -gt
$RetrySeconds)




throw
"Timeout - $Message after $RetrySeconds seconds, " + `



"Current
result=$_result"



break




Write-Verbose
"$_msg ($_t/$RetrySeconds) Seconds."



Sleep -Milliseconds $SleepTimeInMilliSeconds











Explore
& Enjoy!



/Siva




Source

No comments:

Post a Comment