Skip to content

Instantly share code, notes, and snippets.

@totoroha
Forked from HSPDev/AllowSSHFromIP.php
Created August 1, 2018 11:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save totoroha/f2dca853397ce16c4a7c549840f26d70 to your computer and use it in GitHub Desktop.
Save totoroha/f2dca853397ce16c4a7c549840f26d70 to your computer and use it in GitHub Desktop.
Complementary code and IAM policy for "You don't need that Bastion host"
<?php
// For laravel 5 based systems
// /path/to/project/app/Console/Commands/AllowSSHFromIP.php
namespace App\Console\Commands;
use Aws\Ec2\Ec2Client;
use Carbon\Carbon;
use Illuminate\Console\Command;
class AllowSSHFromIP extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ssh:allow {ip?}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Allows SSH access from the specified IP';
/**
* Create a new command instance.
*
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$ip = $this->argument('ip');
if(empty($ip))
{
$this->info('No IP Specified, grabbing the current one from api.ipify.org...');
$ip = trim(file_get_contents('https://api.ipify.org/'));
$this->info("Current IP is: {$ip}");
}
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 & FILTER_FLAG_NO_PRIV_RANGE & FILTER_FLAG_NO_RES_RANGE))
{
$this->error("The specified IP was invalid: {$ip}");
return false;
}
$ip = $ip.'/32'; //This specific IP.
$ec2Client = Ec2Client::factory(array(
'version' => '2016-11-15',
'region' => config('aws.ssh.region'),
'credentials' => [
'key' => config('aws.ssh.key'),
'secret' => config('aws.ssh.secret'),
]
));
$securityGroupDescription = $ec2Client->describeSecurityGroups([
'GroupIds' => [config('aws.ssh.group_id')]
]);
$permissions = $securityGroupDescription->get('SecurityGroups');
if(count($permissions) != 1)
{
$this->error("Expected precisely 1 security group, got : ".count($permissions));
return false;
}
$permissions = $permissions[0] ?? [];
$permissions = $permissions['IpPermissions'] ?? [];
if(count($permissions) > 1)
{
$this->error("Expected precisely 1 or 0 permission on group, got : ".count($permissions));
return false;
}
$ipRules = $permissions[0] ?? [];
if(!empty($ipRules) && ($ipRules['FromPort'] !== 22 || $ipRules['ToPort'] !== 22))
{
$this->error("Rule has not been set up correctly and is allowing to other than port 22.");
return false;
}
//The actual CIDR blocks being allowed.
$ipRules = $ipRules['IpRanges'] ?? [];
$ipRulesCount = count($ipRules);
$this->info("Current source rules: {$ipRulesCount}.");
$doesCurrentIpExist = false;
foreach($ipRules as $rule)
{
$loopSource = $rule['CidrIp'] ?? null;
$loopDescription = $rule['Description'] ?? '';
$loopDate = '';
//Parse the date
preg_match('/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/m', $loopDescription, $matches, PREG_OFFSET_CAPTURE, 0);
$date = ($matches[0] ?? [])[0] ?? 'nope-format';
try {
$date = Carbon::createFromFormat('Y-m-d H:i:s', $date);
} catch (\Exception $ex) {
$date = now()->subYears(5);
}
$loopDate = $date->format('Y-m-d H:i');
//Check if older than a week.
$deleteRule = ($date < now()->subWeek());
$this->info("\tSource: {$loopSource} created at: ${loopDate}. Delete: ".($deleteRule ? 'Yes':'No'));
if($loopSource == $ip && !$deleteRule)
{
$this->info("\t\tCurrent IP being asked for found. We won't reinstate it.");
$doesCurrentIpExist = true;
}
if($deleteRule)
{
try {
$ec2Client->revokeSecurityGroupIngress([
'GroupId' => config('aws.ssh.group_id'),
'IpPermissions' => [
[
'IpProtocol' => 'tcp',
'FromPort' => config('aws.ssh.ssh_port'),
'ToPort' => config('aws.ssh.ssh_port'),
'IpRanges' => [
[
'CidrIp' => $loopSource,
'Description' => $loopDescription,
]
],
]
]
]);
$this->warn("\t\tDeleted {$loopSource} OK.");
} catch (\Exception $exception)
{
$this->error("Trying to delete rule: {$loopSource} resulted in error: ".$exception->getMessage());
}
}
}
if($doesCurrentIpExist)
{
$this->info("The IP block {$ip} was found as a rule, we don't need to create it again.");
return true;
}
$result = null;
try {
$result = $ec2Client->authorizeSecurityGroupIngress([
'GroupId' => config('aws.ssh.group_id'),
'IpPermissions' => [
[
'IpProtocol' => 'tcp',
'FromPort' => config('aws.ssh.ssh_port'),
'ToPort' => config('aws.ssh.ssh_port'),
'IpRanges' => [
[
'CidrIp' => $ip,
'Description' => 'Generated from CLI by: '.get_current_user(). ' at '.now()->format('Y-m-d H:i:s'),
]
],
]
]
]);
} catch (\Exception $ex)
{
$this->error("We got an error from AWS: ".$ex->getMessage());
return false;
}
if($result->get('@metadata')['statusCode'] !== 200)
{
$this->error("Something went wrong... Check AWS Web Management...");
return false;
}
$this->info("SSH access to AWS from {$ip} approved. Remember to delete the rule sometime...");
}
}
<?php
// For laravel 5 based systems
// /path/to/project/config/aws.php
return [
'ssh' => [
'region' => 'YOUR_REGION', //e.g. eu-west-1
'key' => 'YOUR_KEY',
'secret' => 'YOUR_SECRET',
'group_id' => 'sg-number_from_security_group_here', //e.g. sg-124532
'ssh_port' => 22, //Be sure it matches IAM policy
]
];
<?php
// For laravel 5 based systems
// /path/to/project/app/Console/Commands/ClearSSHAllowances.php
namespace App\Console\Commands;
use Aws\Ec2\Ec2Client;
use Carbon\Carbon;
use Illuminate\Console\Command;
class ClearSSHAllowances extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ssh:clear';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clears ALL allowed SSH rules.';
/**
* Create a new command instance.
*
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$ec2Client = Ec2Client::factory(array(
'version' => '2016-11-15',
'region' => config('aws.ssh.region'),
'credentials' => [
'key' => config('aws.ssh.key'),
'secret' => config('aws.ssh.secret'),
]
));
$securityGroupDescription = $ec2Client->describeSecurityGroups([
'GroupIds' => [config('aws.ssh.group_id')]
]);
$permissions = $securityGroupDescription->get('SecurityGroups');
if(count($permissions) != 1)
{
$this->error("Expected precisely 1 security group, got : ".count($permissions));
return false;
}
$permissions = $permissions[0] ?? [];
$permissions = $permissions['IpPermissions'] ?? [];
if(count($permissions) > 1)
{
$this->error("Expected precisely 1 or 0 permission on group, got : ".count($permissions));
return false;
}
$ipRules = $permissions[0] ?? [];
if(!empty($ipRules) && ($ipRules['FromPort'] !== 22 || $ipRules['ToPort'] !== 22))
{
$this->error("Rule has not been set up correctly and is allowing to other than port 22.");
return false;
}
//The actual CIDR blocks being allowed.
$ipRules = $ipRules['IpRanges'] ?? [];
$ipRulesCount = count($ipRules);
$this->info("Current source rules: {$ipRulesCount}.");
foreach($ipRules as $rule)
{
$loopSource = $rule['CidrIp'] ?? null;
$loopDescription = $rule['Description'] ?? '';
$this->info("\tSource: {$loopSource}, shall be deleted.");
try {
$ec2Client->revokeSecurityGroupIngress([
'GroupId' => config('aws.ssh.group_id'),
'IpPermissions' => [
[
'IpProtocol' => 'tcp',
'FromPort' => config('aws.ssh.ssh_port'),
'ToPort' => config('aws.ssh.ssh_port'),
'IpRanges' => [
[
'CidrIp' => $loopSource,
'Description' => $loopDescription,
]
],
]
]
]);
$this->warn("\t\tDeleted {$loopSource} OK.");
} catch (\Exception $exception)
{
$this->error("Trying to delete rule: {$loopSource} resulted in error: ".$exception->getMessage());
return false;
}
}
$this->info("All SSH access rules deleted OK!");
}
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ec2:RevokeSecurityGroupIngress",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:UpdateSecurityGroupRuleDescriptionsIngress"
],
"Resource": "arn:aws:ec2:*:*:security-group/sg-number_from_security_group_here"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "ec2:DescribeSecurityGroups",
"Resource": "*"
}
]
}
<?php
// For laravel 5 based systems
// /path/to/project/app/Console/Commands/ListAllSSHAllowances.php
namespace App\Console\Commands;
use Aws\Ec2\Ec2Client;
use Carbon\Carbon;
use Illuminate\Console\Command;
class ListAllSSHAllowances extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ssh:list';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Lists ALL allowed SSH rules.';
/**
* Create a new command instance.
*
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$ec2Client = Ec2Client::factory(array(
'version' => '2016-11-15',
'region' => config('aws.ssh.region'),
'credentials' => [
'key' => config('aws.ssh.key'),
'secret' => config('aws.ssh.secret'),
]
));
$securityGroupDescription = $ec2Client->describeSecurityGroups([
'GroupIds' => [config('aws.ssh.group_id')]
]);
$permissions = $securityGroupDescription->get('SecurityGroups');
if(count($permissions) != 1)
{
$this->error("Expected precisely 1 security group, got : ".count($permissions));
return false;
}
$permissions = $permissions[0] ?? [];
$permissions = $permissions['IpPermissions'] ?? [];
if(count($permissions) > 1)
{
$this->error("Expected precisely 1 or 0 permission on group, got : ".count($permissions));
return false;
}
$ipRules = $permissions[0] ?? [];
if(!empty($ipRules) && ($ipRules['FromPort'] !== 22 || $ipRules['ToPort'] !== 22))
{
$this->error("Rule has not been set up correctly and is allowing to other than port 22.");
return false;
}
//The actual CIDR blocks being allowed.
$ipRules = $ipRules['IpRanges'] ?? [];
$ipRulesCount = count($ipRules);
$this->info("Current source rules: {$ipRulesCount}.");
foreach($ipRules as $rule)
{
$loopSource = $rule['CidrIp'] ?? null;
$loopDescription = $rule['Description'] ?? '';
$loopDate = '';
//Parse the date
preg_match('/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/m', $loopDescription, $matches, PREG_OFFSET_CAPTURE, 0);
$date = ($matches[0] ?? [])[0] ?? 'nope-format';
try {
$date = Carbon::createFromFormat('Y-m-d H:i:s', $date);
$loopDate = $date->diffForHumans();
} catch (\Exception $ex) {
$date = null;
$loopDate = '(unknown)';
}
$this->info("\tSource: {$loopSource}, created: {$loopDate}.");
$this->info("\t\tDescription: \"{$loopDescription}\"");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment