Added pentest-ec2-manager
This commit is contained in:
parent
be77a20ece
commit
9dcaf5efa5
|
@ -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,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"
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue