Starting/stopping Amazon EC2 instances using CLI and Python SDK. It’s a very good practice to scan your perimeter from the outside of your network, simulating an attacker. However, you will need to deploy the scanners somewhere to do this. Hosting on Amazon EC2 can be a good and cost-effective option, especially if you start instances with vulnerability scanners only when it’s necessary and keep them stopped at other time.
So, in this post I will give some examples of how to manage Amazon instances automatically using the AWS CLI or Python SDK (boto3): start/stop the instance and get public ip address.
Let’s say we have a corporate account at aws.amazon.com
We login at https://[corporation].signin.aws.amazon.com/console
And at https://eu-west-2.console.aws.amazon.com/ec2/v2/home?region=eu-west-2#Instances:sort=instanceId page (where eu-west-2 is our region), we see some of our instances:
To automate the work with Amazon from CLI and python, we need:
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
You can generate them here: https://console.aws.amazon.com/iam/home?#security_credential
Let’s say you got AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. Next, we can try to turning on and off instances with CLI.
AWS CLI
Installing AWS CLI:
$ pip install awscli --upgrade --user
Check that it works:
$ aws --version
aws-cli/1.11.167 Python/2.7.5 Linux/3.10.0-693.2.2.el7.x86_64 botocore/1.7.XX
Configuring:
$ aws configure
AWS Access Key ID [None]: DERNRG34WHROIW$HT41
AWS Secret Access Key [None]: CCUz01piG+xRx532fhM+7vfEFr86UCQoGDq/fHY3
Default region name [None]: eu-west-2
Default output format [None]: json
Verify that authentication works. If you have a problem with permissions you will get:
$ aws ec2 describe-instances
An error occurred (UnauthorizedOperation) when calling the DescribeInstances operation: You are not authorized to perform this operation.
If everything is ok, this command will return a long json describing instances. You can see the instance id here:
$ aws ec2 describe-instances
{ "Reservations": [ { "Instances": [ { "Monitoring": { "State": "disabled" }, "PublicDnsName": "", "StateReason": { "Message": "Client.UserInitiatedShutdown: User initiated shutdown", "Code": "Client.UserInitiatedShutdown" }, "State": { "Code": 80, "Name": "stopped" }, "EbsOptimized": false, "LaunchTime": "2017-XX-XXXXX:XX:XX.XXXX", "PrivateIpAddress": "XXX.XX.XX.XX", "ProductCodes": [], "VpcId": "vpc-XXXX", "StateTransitionReason": "User initiated (XXXX-XX-XX XX:XX:XX GMT)", "InstanceId": "i-wieituqp938tyqp94", "EnaSupport": true, "ImageId": "ami-XXXX", "PrivateDnsName": "ip-XXX-XX-XX-XX.eu-west-2.compute.internal", "KeyName": "EntryPoint", "SecurityGroups": [ { "GroupName": "launch-wizard-2", "GroupId": "sg-XXXX" } ], "ClientToken": "XXXX", "SubnetId": "subnet-XXXX", "InstanceType": "XXXX", "NetworkInterfaces": [ { "Status": "in-use", "MacAddress": "0a:eb:33:4d:aa:f1", "SourceDestCheck": true, "VpcId": "vpc-XXXX", "Description": "", "NetworkInterfaceId": "eni-XXXX", "PrivateIpAddresses": [ { "PrivateDnsName": "ip-XXX-XX-XX-XX.eu-west-2.compute.internal", "Primary": true, "PrivateIpAddress": "XXX.XX.XX.XX" } ], "PrivateDnsName": "ip-XXX-XX-XX-XX.eu-west-2.compute.internal", "Attachment": { "Status": "attached", "DeviceIndex": 0, "DeleteOnTermination": true, "AttachmentId": "eni-attach-XXXX", "AttachTime": "XXXX-XX-XXXXX:XX:XX.XXXX" }, "Groups": [ { "GroupName": "launch-wizard-2", "GroupId": "sg-XXXXXXX" } ], "Ipv6Addresses": [], "OwnerId": "XXXXXXX", "SubnetId": "subnet-XXXXXXX", "PrivateIpAddress": "XXX.XX.XX.XX" } ], "SourceDestCheck": true, "Placement": { "Tenancy": "default", "GroupName": "", "AvailabilityZone": "eu-west-2b" }, "Hypervisor": "xen", "BlockDeviceMappings": [ { "DeviceName": "/dev/sda1", "Ebs": { "Status": "attached", "DeleteOnTermination": true, "VolumeId": "vol-XXXXXXX", "AttachTime": "XXXX-XX-XXXXX:XX:XX.XXXX" } } ], "Architecture": "x86_64", "RootDeviceType": "ebs", "RootDeviceName": "/dev/sda1", "VirtualizationType": "hvm", "Tags": [ { "Value": "linuxXX", "Key": "Name" } ], "AmiLaunchIndex": 0 } ], "ReservationId": "r-XXXXXXX", "Groups": [], "OwnerId": "XXXXXXX" }, ...
Starting instance using this id:
$ aws ec2 start-instances --instance-ids i-wieituqp938tyqp94
{ "StartingInstances": [ { "InstanceId": "i-wieituqp938tyqp94", "CurrentState": { "Code": 0, "Name": "pending" }, "PreviousState": { "Code": 80, "Name": "stopped" } } ] }
Checking the status:
$ aws ec2 describe-instance-status --instance-id i-wieituqp938tyqp94
{ "InstanceStatuses": [ { "InstanceId": "i-wieituqp938tyqp94", "InstanceState": { "Code": 16, "Name": "running" }, "AvailabilityZone": "eu-west-2b", "SystemStatus": { "Status": "initializing", "Details": [ { "Status": "initializing", "Name": "reachability" } ] }, "InstanceStatus": { "Status": "initializing", "Details": [ { "Status": "initializing", "Name": "reachability" } ] } } ] }
Great! It’s running.
We look at all the instances once again and see that an external IP address has appeared:
$ aws ec2 describe-instances
"NetworkInterfaces": [ { "Status": "in-use", "MacAddress": "0a:eb:33:4d:aa:f1", "SourceDestCheck": true, "VpcId": "vpc-XXXX", "Description": "", "NetworkInterfaceId": "eni-XXXX", "PrivateIpAddresses": [ { "PrivateDnsName": "ip-XXX-XX-XX-XX.eu-west-2.compute.internal", "PrivateIpAddress": "XXX.XX.XX.XX" "Primary": true, "Association": { "PublicIp": "35.176.43.150", "PublicDnsName": "ec2-XXX-XXX-XXX-XXX.eu-west-2.compute.amazonaws.com", "IpOwnerId": "amazon" } } ], ... ],
And now, when the work with instance is finnished, we are stopping the instance by id:
$ aws ec2 stop-instances --instance-ids i-wieituqp938tyqp94
{ "StoppingInstances": [ { "InstanceId": "i-wieituqp938tyqp94", "CurrentState": { "Code": 64, "Name": "stopping" }, "PreviousState": { "Code": 16, "Name": "running" } } ] }
Amazon AWS Python SDK
Now let’s try the same thing using Python
Installing SDK:
sudo pip install -U boto
Looking for stopped instances:
import boto3
ec2 = boto3.resource(
'ec2',
aws_access_key_id="DERNRG34WHROIW$HT41",
aws_secret_access_key="CCUz01piG+xRx532fhM+7vfEFr86UCQoGDq/fHY3",
region_name='eu-west-2'
)
instances = ec2.instances.filter(
Filters=[{'Name': 'instance-state-name', 'Values': ['stopped']}])
for instance in instances:
print(instance.id)
Here they are:
i-quae4itvyqn738ty3 i-wieituqp938tyqp94
Ok, we see our instance i-wieituqp938tyqp94 let’s try to start it:
ids = ["i-wieituqp938tyqp94"]
status = ec2.instances.filter(InstanceIds=ids).start()
print(status)
Output:
[{u'StartingInstances': [{u'InstanceId': 'i-wieituqp938tyqp94', u'CurrentState': {u'Code': 0, u'Name': 'pending'}, u'PreviousState': {u'Code': 80, u'Name': 'stopped'}}], 'ResponseMetadata': {'RetryAttempts': 0, 'HTTPStatusCode': 200, 'RequestId': 'XXXX', 'HTTPHeaders': {'transfer-encoding': 'chunked', 'vary': 'Accept-Encoding', 'server': 'AmazonEC2', 'content-type': 'text/xml;charset=UTF-8', 'date': 'XXXX'}}}]
Ok, looks like it’s started. Let’s try to find it among the running instances and get out the external IP-address:
instances = ec2.instances.filter(
Filters=[{'Name': 'instance-state-name', 'Values': ['running']}])
for instance in instances:
print(instance.id)
print(instance.public_ip_address)
Output:
i-wieituqp938tyqp94 35.176.43.151
Finally, we stop this instance:
ids = ["i-wieituqp938tyqp94"]
status = ec2.instances.filter(InstanceIds=ids).stop()
print(status)
Output:
[{u'StoppingInstances': [{u'InstanceId': 'i-wieituqp938tyqp94', u'CurrentState': {u'Code': 64, u'Name': 'stopping'}, u'PreviousState': {u'Code': 16, u'Name': 'running'}}], 'ResponseMetadata': {'RetryAttempts': 0, 'HTTPStatusCode': 200, 'RequestId': 'XXXX', 'HTTPHeaders': {'transfer-encoding': 'chunked', 'vary': 'Accept-Encoding', 'server': 'AmazonEC2', 'content-type': 'text/xml;charset=UTF-8', 'date': 'XXXX'}}}]
Hi! My name is Alexander and I am a Vulnerability Management specialist. You can read more about me here. Currently, the best way to follow me is my Telegram channel @avleonovcom. I update it more often than this site. If you haven’t used Telegram yet, give it a try. It’s great. You can discuss my posts or ask questions at @avleonovchat.
А всех русскоязычных я приглашаю в ещё один телеграмм канал @avleonovrus, первым делом теперь пишу туда.