Added pentest-ec2-manager

This commit is contained in:
mgeeky 2019-04-04 15:29:43 +02:00
parent be77a20ece
commit 9dcaf5efa5
7 changed files with 1071 additions and 0 deletions

View File

@ -96,3 +96,5 @@ Afterwards, one should see following logs in CloudWatch traces for planted Lambd
- **`exfiltrateLambdaTasksDirectory.py`** - Script that creates an in-memory ZIP file from the entire directory `$LAMBDA_TASK_ROOT` (typically `/var/task`) and sends it out in a form of HTTP(S) POST request, within an `exfil` parameter. To be used for exfiltrating AWS Lambda's entire source code.
- **`identifyS3Bucket.rb`** - This script attempts to identify passed name whether it resolves to a valid AWS S3 Bucket via different means. This script may come handy when revealing S3 buckets hidden behind HTTP proxies.
- **`pentest-ec2-instance`** - A set of utilities for quick starting, ssh-ing and stopping of a single temporary EC2 instance intended to be used for Web out-of-band tests (SSRF, reverse-shells, dns/http/other daemons).

0
clouds/aws/identifyS3Bucket.rb Normal file → Executable file
View File

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
gem "aws-sdk-ec2"

View File

@ -0,0 +1,97 @@
# pentest-ec2-manager
A set of utilities for quick starting, ssh-ing and stopping of temporary EC2 instances intended to be used for Web out-of-band tests (SSRF, reverse-shells, dns/http/other daemons).
Those scripts are useful for managing *single* EC2 instance, which is picked based on specific characteristics: `key-name`, `image-id`, `security-group-name`, `instance-type`.
Most common use case:
- _Performing SSRF tests._ - When you want to quickly assert there is out-of-band request going over to attacker-controlled machine
**CAUTION NOTE**: Files in this repository are preconfigured/hardcoded with some intial specific configurations. Those configurations are placed on top of each script file, one can change them easily.
### Installation
Installation is pretty straightforward assuming you have AWS account already and AWS Access Key (AKIA...) and AWS Secret Key with you.
If you have AWS account, installation can be started using `init.sh` script. This script assumes you can use `sudo` to pull in prerequisities.
```
bash $ ./init.sh
----------------------------------------------
:: AWS EC2 single-instance management utilities installation script.
This script is going to:
- Update your repos & install packages such as: ssh, cron, jq, ruby, rubygems, awscli, gem bundler, gem 'aws-sdk-ec2'
- Configure your AWS credentials
- Create AWS security groups, EC2 key pairs
- Integrate EC2 management aliases into the end of your .bashrc
- Add a cron job that will notify you every two hours if your EC2 machine is up and running
----------------------------------------------
Would you like to proceed? [Y/n]
```
After hitting `Y` it shall configure AWS CLI for you, pull `aws-sdk-ec2` for ruby and then create Security Groups, Key Pairs and append things to your bashrc.
### Usage
After script installation is over, your bash will get preloaded (in `~/.bashrc`) with following aliases:
* `startpentestec2` - Starts EC2 Instance if it exists, otherwise creates it
* `stoppentestec2` - Stops EC2 Instance
* `terminatepentestec2` - Terminates EC2 Instance (which means also deletes that instance's Elastic Block Store / hdd)
* `sshpentestec2` - Attemtps to SSH into managed EC2 instance.
* `getpentestec2` - Obtains IPv4 address of managed EC2 instance.
* `checkpentestec2` - Prints out EC2 Instance status (running, stopped, notcreated, pending, etc)
If you do not want to use bash aliases, or prefer having it your own way, you can call the `aws-manager.rb` script directly:
```
bash $ ruby aws-manager.rb --help
Usage: aws-manager.rb [options] <func> <name>
Available 'func' values:
- start Starts an EC2 instance. If it does not exist, it is to be created
- stop Stops the EC2 instance. It does not terminate it.
- restart Restarts the EC2 instance
- terminate Terminates the EC2 instance.
- address Gets an IPv4 address of the EC2 instance. If verbose options is set, will return more FQDN also.
- status Checks what is a status of picked EC2 instance.
- ssh Opens a ssh connection with specified instance. If it is not running, it is to be created and started.
- notify Sends gnome notification using "notify-send" with running instance uptime.
Options:
-h, --help Display this screen
-q, --quiet Surpress informative output.
-v, --verbose Turn on verbose logging.
--debug Turn on debug logging.
-d, --aws-path=PATH Path to shared AWS credentials file. Default value that will be used: $AWS_PATH/credentials
--profile=NAME AWS credentials profile to use. Should no option is given, "default" is used.
-p, --region=REGION AWS regoin to use. Default one: "us-east-1".
-i, --image-id=ID AWS image ID to create an EC2 from. Default: 'ami-07360d1b1c9e13198
-k, --key-name=KEY AWS EC2 Key Name to use. Default: 'ec2-pentest-key
-s, --security-group-name=NAME AWS EC2 Security Group name to use. Default: 'ec2-pentest-usage
-t, --instance-type=TYPE Instance type to spin. Default: 't2.micro
-u, --user=USER SSH user to log into when doing 'ssh'. Default: 'ec2-user
```
Typical usage boils down to issuing `func` operations. Eventually one would like to observe what happens under the hood using one of those flags:
* `-v`
* `--debug`
On the other hand, it is possible to surpress script's output nearly entirely using:
* `-q`
flag.
### TODO
* Test, bug fixes
* Support different Regions. Currently scripts are fixed on using one region.
* Support more than one instance. Very far aim to reach.

View File

@ -0,0 +1,631 @@
#!/usr/bin/env ruby
#
# This script is intended to manage a single AWS EC2 instance for use during
# pentests. The script offers following functionalities:
#
# start - Starts an EC2 instance. If it does not exist, it is to be created
# stop - Stops the EC2 instance. It does not terminate it.
# restart - Restarts the EC2 instance
# terminate - Terminates the EC2 instance.
# address - Gets an IPv4 address of the EC2 instance. If verbose options is set, will return more FQDN also.
# status - Checks what is a status of picked EC2 instance.
# ssh - Opens a ssh connection with specified instance. If it is not running, it is to be created and started.
# notify - Sends gnome notification using "notify-send" with running instance uptime
#
# The basic use case for this script is to have yourself launched an EC2 instance for quick
# verification of Web Application vulnerabilities like out-of-bound communication: blind-XXE for instance.
# Everytime you will be in position of needing a machine with public IPv4 address - take this script for a spin,
# and it will provide you with your own EC2 instance.
#
# Requirements:
# - gem "aws-sdk-ec2"
#
# Author: Mariusz B., '19, <mb@binary-offensive.com>
#
require 'aws-sdk-ec2'
require 'base64'
require 'optparse'
VERSION = '0.1'
$aws_config_path = File.join((ENV['OS'] == 'Windows_NT') ? ENV['UserProfile'] : File.expand_path('~'), '.aws')
$config = {
#
# Notably interesting configuration to fill up
#
:image_id => 'ami-07360d1b1c9e13198',
:key_name => 'ec2-pentest-key',
:sg_name => 'ec2-pentest-usage',
:instance_name => 'pentestec2',
:instance_type => 't2.micro',
:ssh_user => 'ec2-user',
:ssh_identity_file => File.join($aws_config_path, 'ec2-pentest-key.pem'),
# ----
:verbose => false,
:quiet => false,
:debug => false,
:aws_path => "",
:aws_profile => 'default',
:region => 'us-east-1',
}
$supported_funcs = {
'start' => 'Starts an EC2 instance. If it does not exist, it is to be created',
'stop' => 'Stops the EC2 instance. It does not terminate it.',
'restart' => 'Restarts the EC2 instance',
'terminate' => 'Terminates the EC2 instance.',
'address' => 'Gets an IPv4 address of the EC2 instance. If verbose options is set, will return more FQDN also.',
'status' => 'Checks what is a status of picked EC2 instance.',
'ssh' => 'Opens a ssh connection with specified instance. If it is not running, it is to be created and started.',
'notify' => 'Sends gnome notification using "notify-send" with running instance uptime.'
}
class Logger
def Logger._out(x)
if not $config[:quiet] and ($config[:verbose] or $config[:debug])
STDOUT.write(x + "\n")
end
end
def Logger.dbg(x)
if $config[:debug]
Logger._out("[dbg] #{x}")
end
end
def Logger.info(x)
Logger._out("[.] #{x}")
end
def Logger.fatal(x)
unless $config[:quiet]
STDOUT.write(x + "\n")
end
exit 1
end
def Logger.fail(x)
Logger._out("[-] #{x}")
end
def Logger.ok(x)
Logger._out("[+] #{x}")
end
end
class AwsEc2Manager
attr_reader :instance_name
def initialize(func, instance_name, config)
@instance_name = instance_name
@checked = false
@func = func
@config = config
@instance_id = ""
@instance_id_file = File.join($aws_config_path, instance_name+'_id')
try_to_load_instance_id
@aws_access_key_id = ""
@aws_secret_access_key = ""
if @config[:aws_profile] != 'default' or not @config[:aws_path].to_s.empty?
path = @config[:aws_path].to_s.empty? ? $aws_config_path : @config[:aws_path]
Logger.dbg("Initializing AWS config with creds from path: #{path} - profile: #{@config[:aws_profile]}")
shared_creds = Aws::SharedCredentials.new(
path: path,
profile_name: @config[:aws_profile]
)
Aws.config.update({
credentials: shared_creds
})
end
Aws.config.update({region: @config[:region]})
@ec2_client = Aws::EC2::Client.new(region: @config[:region])
@ec2 = Aws::EC2::Resource.new(client: @ec2_client)
end
def try_to_load_instance_id
if File.file? @instance_id_file
File.open(@instance_id_file) do |f|
@instance_id = f.gets.strip
Logger.dbg("Using instance ID: #{@instance_id}")
end
else
Logger.fail("No instance id file: #{@instance_id_file}")
end
end
def get_security_group_id
group_id = ""
@ec2_client.describe_security_groups do |security_group|
if security_group.group_name == @config[:sg_name]
group_id = security_group.group_id
break
end
end
group_id
end
def try_to_find_instance_id
Logger.dbg("Trying to find that specific instance online...")
insts = @ec2.instances({
filters: [
{
name: 'image-id',
values: [@config[:image_id]]
},
{
name: 'instance-type',
values: [@config[:instance_type]]
},
{
name: 'tag:name',
values: [@config[:instance_name]]
},
{
name: 'key-name',
values: [@config[:key_name]]
},
]
})
insts.each do |instance|
Logger.dbg("Checking instance with ID: #{instance.id}")
if instance.state.code == 48
Logger.fail("Instance is terminated. Leaving id file empty.")
File.open(@instance_id_file, 'w') do |f|
f.puts("")
end
@instance_id = ""
break
end
Logger.dbg("Found proper instance: #{instance.id} / #{instance.image_id}. Clobberring id file...")
@instance_id = instance.id
File.open(@instance_id_file, 'w') do |f|
f.puts(instance.id)
end
break
end
if not insts.any?
Logger.fail("Did not found any instance matching our criterias.")
end
@instance_id
end
def get_instance_id
if @checked and not @instance_id.to_s.empty?
Logger.dbg("Returning cached checked instance id.")
elsif not @instance_id.to_s.empty?
Logger.dbg("Checking if instance with ID = #{@instance_id} still exists.")
i = @ec2.instance(@instance_id)
if i.exists?
Logger.dbg("Instance still exists.")
@checked = true
else
Logger.dbg("Instance does not exist, clearing cached instance id in file.")
File.open(@instance_id_file, 'w') do |f|
f.puts("")
end
try_to_find_instance_id
end
end
Logger.info("Working on instance: #{@instance_id}")
@instance_id
end
def create(wait: false)
# group_id = get_security_group_id
# unless group_id
# Logger.fatal("Could not obtain EC2 Security Group ID by name: #{@config[:sg_name]}")
# end
Logger.dbg(%Q(Creating an instance:
AMI Image ID: #{@config[:image_id]}
EC2 Key Name: #{@config[:key_name]}
Security Group Name: #{@config[:sg_name]}
))
# Security Group ID: #{group_id}
instance = @ec2.create_instances({
image_id: @config[:image_id],
min_count: 1,
max_count: 1,
key_name: @config[:key_name],
security_groups: [@config[:sg_name]],
instance_type: @config[:instance_type],
tag_specifications: [
{
resource_type: 'instance',
tags: [
key: 'name',
value: @config[:instance_name],
],
},
],
})
if instance.any?
Logger.ok("Instance created. Waiting for it to get into running state...")
else
Logger.fail("Could not spin up an instance! Something went wrong but will proceed anyway...")
end
# Wait for the instance to be created, running, and passed status checks
inst = instance.first
if wait
puts "Waiting for instance to get it up & running. This might take a couple of minutes."
@ec2.client.wait_until(:instance_status_ok, {instance_ids: [inst.id]})
else
Logger.ok("Instance up & initializing.")
end
File.open(@instance_id_file, 'w') do |f|
f.puts(inst.id)
end
if @config[:quiet]
puts "created"
elsif @config[:verbose] or @config[:debug]
puts "Created instance: inst.id"
end
end
def start(wait: false)
state = status(raw: true)
Logger.info("Instance is in state: #{state}")
if state == 'notcreated'
create(wait: wait)
else
if get_instance_id.to_s.empty?
Logger.fatal("No instance that could be started.")
end
i = @ec2.instance(@instance_id)
if i.exists?
case i.state.code
when 0 # pending
puts "#{i.id} is pending, so it will be running in a bit"
when 16 # started
puts "#{i.id} is already started"
when 48 # terminated
puts "#{i.id} is terminated, gotta create another one."
create(wait: wait)
else
puts "Started instance. Please wait couple of minutes before doing SSH."
begin
i.start
return true
rescue Aws::EC2::Errors::IncorrectInstanceState => e
if e.include? "is not in a state from which it can be started."
Logger.fatal("EC2 instance is in a state from which it can't be started right now. Try later.")
else
Logger.fatal("Could not start EC2 instance: #{e}")
end
end
end
end
end
return false
end
def stop
if get_instance_id.to_s.empty?
Logger.fatal("No instance that could be stopped.")
end
i = @ec2.instance(@instance_id)
if i.exists?
case i.state.code
when 48 # terminated
puts "#{i.id} is already stopped."
when 64 # stopping
puts "#{i.id} is stopping, so it become stopped in a while."
when 80 # stopped
puts "#{i.id} is already stopped."
else
puts "Stopped an instance."
i.stop
end
end
end
def restart
if get_instance_id.to_s.empty?
Logger.fatal("No instance that could be restarted.")
end
i = @ec2.instance(@instance_id)
if i.exists?
case i.state.code
when 48 # terminated
start
else
puts "Issued instance reboot signal."
i.reboot
end
end
end
def terminate
if get_instance_id.to_s.empty?
Logger.fatal("No instance that could be terminated.")
end
i = @ec2.instance(@instance_id)
if i.exists?
case i.state.code
when 48 # terminated
puts "#{i.id} is already terminated."
else
puts "Terminated instance. Cleared instance id file."
i.terminate
File.open(@instance_id_file, 'w') do |f|
f.puts("")
end
end
end
end
def address(raw: false)
if get_instance_id.to_s.empty?
if @config[:quiet]
exit 1
end
Logger.fatal("No instance id to operate on. Instance will need to be created first.")
end
addr = ""
i = @ec2.instance(@instance_id)
if i.exists?
Logger.dbg("Instance found.")
addr = i.public_ip_address
if not raw
if @config[:verbose]
puts "Public IPv4:\t#{i.public_ip_address}"
puts "Public FQDN:\t#{i.public_dns_name}"
puts "Private IPv4:\t#{i.private_ip_address}"
puts "Private FQDN:\t#{i.private_dns_name}"
else
puts i.public_ip_address
end
end
end
addr
end
def status(raw: false, quitOnFailure: true)
get_instance_id
if @instance_id.to_s.empty?
Logger.fail("No instance id stored locally. Will try to look up online.")
if not try_to_find_instance_id
puts("No instance created.")
exit 1
end
end
if @instance_id.to_s.empty? and quitOnFailure
state = "notcreated"
if not raw
puts "State: notcreated"
end
return state
end
state = ""
inst = @ec2.instance(@instance_id)
if inst.exists?
state = inst.state.name
if not raw
if @config[:verbose] or @config[:debug]
puts "State:\t\tInstance is #{inst.state.name} (#{inst.state.code})"
if inst.state.code == 16
dif = ((Time.now - inst.launch_time) / 60).ceil
t = inst.launch_time
stime = t.strftime("%Y-%m-%d %I:%M%P UTC")
puts "Launched time:\t#{stime}; #{dif.to_s} minutes ago."
end
else
puts inst.state.name
end
end
else
state = "notcreated"
if not raw
puts "State: notcreated"
end
end
state
end
def ssh
state = status(raw: true)
if state == 'stopped'
puts "Instance is stopped. Creating it first."
if not start(wait: true)
Logger.fatal("Could not create EC2 instance.")
end
state = 'running'
end
if state == 'running'
addr = address(raw: true)
cmd = "ssh -i #{@config[:ssh_identity_file]} -o ConnectTimeout=10 -oStrictHostKeyChecking=no #{@config[:ssh_user]}@#{addr}"
Logger.dbg("Running command: #{cmd}")
puts "Attempting to ssh #{@config[:ssh_user]}@#{addr} ...\n\n"
exec(cmd)
end
raise "Unsupported EC2 machine state: #{state}"
end
def notify
get_instance_id
if @instance_id.to_s.empty?
if not try_to_find_instance_id
exit 1
end
end
inst = @ec2.instance(@instance_id)
if inst.exists?
Logger.dbg('Instance exists.')
if inst.state.code == 16
Logger.dbg('Instance is running.')
minutes = ((Time.now - inst.launch_time) / 60).ceil
title = "EC2 Instance #{@config[:instance_name]} is running."
body = ""
if minutes < 60
body = "Your instance has been running #{minutes} minutes by now. Consider stopping it."
else
hours = (minutes / 60).floor
restm = minutes % 60
body = "Your instance's been running #{hours}h and #{restm}mins by now. Consider stopping it."
end
cmd = "notify-send '#{title}' '#{body}'"
Logger.dbg("Executing notification command: #{cmd}")
exec(cmd)
puts cmd
else
Logger.dbg("Instance is not running.")
end
end
end
end
def parse_options
options = {}
parser = OptionParser.new do |opts|
funcs = ""
$supported_funcs.each do |k, v|
funcs += " - #{k}\t\t\t#{v}\n"
end
opts.banner = %Q(
Usage: aws-manager.rb [options] <func> <name>
Available 'func' values:
#{funcs}
Options:
)
opts.on('-h', '--help', 'Display this screen' ) do
puts opts
exit
end
opts.on('-q', '--quiet', 'Surpress informative output.') do |q|
$config[:quiet] = q
end
opts.on('-v', '--verbose', 'Turn on verbose logging.') do |v|
$config[:verbose] = v
end
opts.on('--debug', 'Turn on debug logging.') do |d|
$config[:debug] = d
end
opts.on('-dPATH', '--aws-path=PATH', "Path to shared AWS credentials file. Default value that will be used: $AWS_PATH/credentials ") do |p|
$config[:aws_path] = p
end
opts.on('-pNAME', '--profile=NAME', 'AWS credentials profile to use. Should no option is given, "default" is used.') do |n|
$config[:aws_profile] = n
end
opts.on('-pREGION', '--region=REGION', 'AWS regoin to use. Default one: "us-east-1".') do |n|
$config[:region] = n
end
opts.on('-iID', '--image-id=ID', "AWS image ID to create an EC2 from. Default: '#{$config[:image_id]}") do |i|
$config[:image_id] = i
end
opts.on('-kKEY', '--key-name=KEY', "AWS EC2 Key Name to use. Default: '#{$config[:key_name]}") do |k|
$config[:key_name] = k
end
opts.on('-sNAME', '--security-group-name=NAME', "AWS EC2 Security Group name to use. Default: '#{$config[:sg_name]}") do |s|
$config[:sg_name] = s
end
opts.on('-tTYPE', '--instance-type=TYPE', "Instance type to spin. Default: '#{$config[:instance_type]}") do |s|
$config[:instance_type] = s
end
opts.on('-uUSER', '--user=USER', "SSH user to log into when doing 'ssh'. Default: '#{$config[:ssh_user]}") do |s|
$config[:ssh_user] = s
end
end
args = parser.parse!
func = args.shift.downcase
raise "Need to specify <func> parameter to invoke." unless func
unless $supported_funcs.keys.include? func
Logger.fatal("Unsupported function specified. You need to pick on of these: #{$supported_funcs.keys.join(', ')}")
end
instance_name = args.shift.downcase
$config[:instance_name] = instance_name
raise "Need to specify <name> parameter to invoke." unless instance_name
raise "EC2 Security Group name must be specified." unless $config[:sg_name]
raise "EC2 Key pair name must be specified." unless $config[:key_name]
raise "EC2 image id (AMI) must be specified." unless $config[:image_id]
raise "EC2 instance type must be specified." unless $config[:instance_type]
raise "EC2 instance name must be specified." unless $config[:instance_name]
raise "EC2 instance name must be specified." unless $config[:ssh_user]
return func, instance_name
end
def main
func, instance_name = parse_options
Logger.dbg("Using AWS configuration path: #{$aws_config_path}")
Logger.dbg("Action to take: #{func} #{instance_name}")
manager = AwsEc2Manager.new(func, instance_name, $config)
manager.send func
end
main

View File

@ -0,0 +1,55 @@
#!/bin/bash
EC2_NAME=pentestec2
#
# Will set the following aliases:
# - ssh<vm> alias for quick ssh-connection
# - get<vm> alias for quick vm's ip resolution
# - start<vm> alias for starting up particular vm
# - stop<vm> alias for stopping particular vm
# - is<vm> alias for checking whether the vm is running.
#
# For instance, when EC2_NAME=pentestec2 - the following aliases will be defined:
# sshpentestec2, getpentestec2, ispentestec2, and so on...
#
function setup_aliases() {
name=${EC2_NAME,,}
if [ -z $name ]; then
echo "[!] You must set the EC2_NAME variable within that script first!"
return 1
fi
alias start$name="startec2"
alias stop$name="stopec2"
alias terminate$name="terminateec2"
alias ssh$name="sshec2"
alias get$name="getec2"
alias check$name="checkec2"
}
function startec2() {
ruby ./aws-manager.rb "$@" start $EC2_NAME
}
function stopec2() {
ruby ./aws-manager.rb "$@" stop $EC2_NAME
}
function terminateec2() {
ruby ./aws-manager.rb "$@" terminate $EC2_NAME
}
function sshec2() {
ruby ./aws-manager.rb "$@" ssh $EC2_NAME
}
function getec2() {
ruby ./aws-manager.rb "$@" address $EC2_NAME
}
function checkec2() {
ruby ./aws-manager.rb "$@" status $EC2_NAME
}
setup_aliases

View File

@ -0,0 +1,281 @@
#!/bin/bash
AWS_REGION=us-east-1
EC2_NAME=pentestec2
EC2_KEY_NAME=ec2-pentest-key
EC2_SECURITY_GROUP_NAME=ec2-pentest-usage
MANAGEMENT_SCRIPT=ec2-utils.sh
install_prerequisities() {
if [[ ! -z "$(which ruby)" ]] && [[ ! -z "$(which gem)" ]] && [[ ! -z "$(which bundle)" ]]; then
echo "[.] Prerequisities already installed."
return
fi
sudo=sudo
if [ $EUID -eq 0 ] || [[ ! -z "$(which sudo)" ]]; then
sudo=
fi
if [[ ! -z "$(which dnf)" ]]; then
$sudo dnf update
$sudo dnf install -y ssh cron ruby rubygems awscli jq
elif [[ ! -z "$(which apt-get)" ]]; then
$sudo apt-get update
$sudo apt-get install -y ssh cron ruby rubygems awscli jq
elif [[ ! -z "$(which yum)" ]]; then
$sudo yum update
$sudo yum install -y ssh cron ruby rubygems awscli jq
else
echo "[!] Please install following packages: ssh, cron, ruby, rubygems, awscli, jq yourself first."
exit 1
fi
gem install bundler
bundle install
aw=$(which aws)
if [ -z "$aw" ]; then
echo "[!] Something went wrong. There is no 'aws' after installing 'awscli'."
exit 1
fi
}
configure_credentials() {
if [ -e ~/.aws/credentials ]; then
con=$(cat ~/.aws/credentials)
if echo $con | grep -q AKIA; then
echo "[.] AWS CLI already configured."
return
fi
fi
if [ ! -d "~/.aws" ]; then
mkdir -p ~/.aws
fi
access_key=
secret_key=
read -p 'AWS Access Key (AKIA...): ' access_key
read -s -p "AWS Secret Access Key: " secret_key
if [ "$secret_key" == "" ] || [ "$access_key" == "" ]; then
echo "[!] You must specify both access key and secret key."
exit 1
fi
touch ~/.aws/config
touch ~/.aws/credentials
cat <<EOT > ~/.aws/config
[default]
region = $AWS_REGION
output = json
EOT
cat <<EOT > ~/.aws/credentials
[default]
aws_access_key_id = $access_key
aws_secret_access_key = $secret_key
EOT
}
create_security_group() {
groupId=$(aws ec2 create-security-group --group-name $EC2_SECURITY_GROUP_NAME --description "Security Group with allow any any for inbound and outbound." 2>&1 )
if echo $groupId | grep -q "InvalidGroup.Duplicate"; then
echo "[.] Security Group already exists."
return
elif echo $groupId | grep -q "An error occurred" ; then
echo "[!] Failed: $groupId"
exit 1
fi
groupId=$(echo $groupId | jq -r .GroupId)
echo "[>] Security Group ID: $groupId"
aws ec2 authorize-security-group-ingress --group-id $groupId --ip-permissions IpProtocol=tcp,FromPort=0,ToPort=65535,IpRanges='[{CidrIp=0.0.0.0/0,Description="allow any any"}]'
aws ec2 authorize-security-group-ingress --group-id $groupId --ip-permissions IpProtocol=udp,FromPort=0,ToPort=65535,IpRanges='[{CidrIp=0.0.0.0/0,Description="allow any any"}]'
aws ec2 authorize-security-group-egress --group-id $groupId --ip-permissions IpProtocol=tcp,FromPort=0,ToPort=65535,IpRanges='[{CidrIp=0.0.0.0/0,Description="allow any any"}]'
aws ec2 authorize-security-group-egress --group-id $groupId --ip-permissions IpProtocol=udp,FromPort=0,ToPort=65535,IpRanges='[{CidrIp=0.0.0.0/0,Description="allow any any"}]'
echo "[>] Created inbound/outbound allow any any rules"
}
delete_key_pair_and_instances() {
echo "[.] Deleting key pair..."
out=$(aws ec2 delete-key-pair --key-name $EC2_KEY_NAME 2>&1 )
if echo $out | grep -q "An error occurred" ; then
echo "[!] Deleting key pair failed: $out"
exit 1
fi
echo "[.] Terminating related EC2 instances..."
out=$(aws ec2 terminate-instances --instance-ids $(aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' --filters "Name=key-name,Values=$EC2_KEY_NAME" --output text) 2>&1 )
if echo $out | grep -q "An error occurred" ; then
echo "[!] Terminating instances failed: $out"
exit 1
fi
}
create_ec2_key_pair() {
keymaterial=
out=$(aws ec2 describe-key-pairs --key-name $EC2_KEY_NAME 2>&1 )
if echo $out | jq -r .KeyPairs[0].KeyName | grep -q $EC2_KEY_NAME ; then
echo "[.] Key pair already created in your AWS account."
filefinger=
if [ -f "~/.aws/$EC2_KEY_NAME.pem" ] ; then
filefinger=$(openssl pkcs8 -in ~/.aws/$EC2_KEY_NAME.pem -inform PEM -outform DER -topk8 -nocrypt | openssl sha1 -c | cut -d= -f2 | tr -d ' ')
fi
awsfinger=$(echo $out | jq -r .KeyPairs[0].KeyFingerprint)
if [ ! -e ~/.aws/$EC2_KEY_NAME.pem ]; then
echo
echo "[!] ERROR: It looks like you don't have ~/.aws/$EC2_KEY_NAME.pem file with your EC2 Key-Pair"
echo "[!] This means you do have the key pair on your AWS account but not physically in a file."
echo "[!] In such case you will be unable to SSH into previously created EC2 instances without it, so "
echo "[!] you are left with creating a new key pair and then terminating existing EC2 instances."
echo
read -p "Do you want to remove your $EC2_NAME EC2 instance and recreate key pairs? [y/N] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
delete_key_pair_and_instances
else
echo "[-] Unable to continue. You must sort this out yourself."
return
fi
elif [ "$filefinger" != "" ] && [ "$awsfinger" != "" ] && [ "$filefinger" != "$awsfinger" ]; then
echo
echo "[!] ERROR: It looks like your EC2 Key Pair located at ~/.aws/$EC2_KEY_NAME.pem"
echo "[!] has differrent fingerprint than the Key Pair on your AWS account."
echo "[!] In such case you will be unable to SSH into previously created EC2 instances using it, so "
echo "[!] you are left with creating a new key pair and then terminating existing EC2 instances."
echo
echo "Your locally stored PEM fingerprint: $filefinger"
echo "Your on AWS stored PEM fingerprint: $awsfinger"
echo
read -p "Do you want to remove your $EC2_NAME EC2 instance and recreate key pairs? [y/N] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
delete_key_pair_and_instances
else
echo "[-] Unable to continue. You must sort this out yourself."
return
fi
else
echo "[+] EC2 Key pair already created. PEM stored at: ~/.aws/$EC2_KEY_NAME.pem"
return
fi
fi
keymaterial=$(aws ec2 create-key-pair --key-name $EC2_KEY_NAME 2>&1 )
if echo $keymaterial | grep -q "An error occurred" ; then
echo "[!] Creating key pair failed: $keymaterial"
exit 1
fi
touch ~/.aws/$EC2_KEY_NAME.pem
echo $keymaterial | jq -r .KeyMaterial > ~/.aws/$EC2_KEY_NAME.pem
chmod 400 ~/.aws/$EC2_KEY_NAME.pem
echo "[+] EC2 Key pair created. PEM stored at: ~/.aws/$EC2_KEY_NAME.pem"
}
integrate_with_bashrc() {
if cat ~/.bashrc | grep -q $MANAGEMENT_SCRIPT ; then
echo "[.] Script is already integrated."
return
elif cat ~/.bashrc | grep -q "Adding aliases for EC2 single-instance management" ; then
echo "[.] Script is already integrated."
return
fi
sed -i -e "s@ruby ./aws-manager.rb@ruby $PWD/aws-manager.rb@" ./$MANAGEMENT_SCRIPT
source $PWD/$MANAGEMENT_SCRIPT
cat <<EOT >> ~/.bashrc
# Adding aliases for EC2 single-instance management.
source $PWD/$MANAGEMENT_SCRIPT
EOT
}
add_cron_job() {
crontab -l > /tmp/.cr 2> /dev/null
cp /tmp/.cr /tmp/.cr.bak
if cat /tmp/.cr | grep -q "aws-manager.rb notify "; then
echo "[.] Crontab job already scheduled."
return
fi
cmd="$(which ruby) $PWD/aws-manager.rb notify $EC2_NAME"
job=" * */2 * * * $USER $cmd"
echo -e "[*] Adding following line to crontab:\n\n\t\t$job\n"
echo $job >> /tmp/.cr
crontab /tmp/.cr
if [ $? -ne 0 ]; then
echo "[!] Updating crontab failed. Restoring original one (you can find it at: /tmp/.cr.bak) "
crontab /tmp/.cr.bak
fi
rm /tmp/.cr
#rm /tmp/.cr.bak
}
echo
echo "----------------------------------------------"
echo
echo ":: AWS EC2 single-instance management utilities installation script."
echo
echo "This script is going to:"
echo -e "\t- Update your repos & install packages such as: ssh, cron, jq, ruby, rubygems, awscli, gem bundler, gem 'aws-sdk-ec2'"
echo -e "\t- Configure your AWS credentials"
echo -e "\t- Create AWS security groups, EC2 key pairs"
echo -e "\t- Integrate EC2 management aliases into the end of your .bashrc"
echo -e "\t- Add a cron job that will notify you every two hours if your EC2 machine is up and running"
echo
echo "----------------------------------------------"
echo
read -p "Would you like to proceed? [Y/n] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
echo -e "\n[.] Step 1: Installing prerequisities first."
install_prerequisities
echo -e "\n[.] Step 2: Configuring credentials."
configure_credentials
echo -e "\n\n[.] Step 3: Creating Security Group."
create_security_group
echo -e "\n[.] Step 4: Creating EC2 Key Pairs."
create_ec2_key_pair
echo -e "\n[.] Step 5: Integrating scripts with your .bashrc"
integrate_with_bashrc
echo -e "\n[.] Step 6: Adding notification cron job"
add_cron_job
echo -e "\n[+] Ready to roll."
echo
echo "----------------------------------------------"
echo
echo -e "You can now try out our shiny new bash aliases:"
echo -e "\t- check$EC2_NAME - To check an instance status"
echo -e "\t- get$EC2_NAME - To get the instance IPv4 address."
echo -e "\t- start$EC2_NAME - To start the instance."
echo -e "\t- stop$EC2_NAME - To stop the instance."
echo -e "\t- terminate$EC2_NAME - To terminate the instance."
echo -e "\t- ssh$EC2_NAME - To ssh into that instance"
echo
echo "Go ahead, check them out. Start with creating EC2 first using 'start$EC2_NAME'"
bash
fi