Cloud infrastructure can be powerful—but also complex to manage. If you’ve ever needed to quickly allow or revoke SSH access to your EC2 instances, you know how easy it is to make mistakes in the AWS Console.
Today, I’ll show you how to build a simple yet powerful C# console application that lets you:
✅ Add SSH rules to a security group
✅ Remove SSH rules safely with confirmation
✅ List all current SSH rules
All from your terminal, in seconds.
This is a perfect tool for DevOps teams, system administrators, or anyone managing EC2 infrastructure.
What You’ll Build
Features:
- Add Rule: Specify port, CIDR, and description.
- Remove Rule: Confirm before deletion.
- List Rules: See all SSH rules in one command.
Prerequisites
✅ .NET 8 SDK installed
✅ AWS IAM user credentials with EC2 permissions
✅ Visual Studio 2022 or later
Step 1: Create a .NET Console App
Open Visual Studio.
Create a new Console App targeting .NET 8.
Name it AwsSecurityGroupManager
.
Step 2: Install NuGet Packages
From the Package Manager Console, run:
Install-Package AWSSDK.EC2
Install-Package Microsoft.Extensions.Configuration
Install-Package Microsoft.Extensions.Configuration.Json
Install-Package Microsoft.Extensions.Configuration.Binder
Install-Package Microsoft.Extensions.Configuration.FileExtensions
You can also right-click on your dependencies, from the solution explorer and choose Manage NuGet.
Step 3: Create appsettings.json
Create a file named appsettings.json
in your project root:
{
"AWS": {
"Region": "us-west-2",
"AccessKey": "",
"SecretKey": ""
},
"SecurityGroup": {
"GroupId": "sg-xxxxxxxxxxxxxxx",
"Protocol": "tcp"
}
}
Important:
Never commit real access keys to version control.
Keep AccessKey
and SecretKey
empty in public repositories.
Step 4: Add the Code
Replace Program.cs
with the following:
using System;
using System.IO;
using System.Threading.Tasks;
using Amazon;
using Amazon.EC2;
using Amazon.EC2.Model;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using System.Linq;
namespace AwsSecurityGroupManager
{
class Program
{
static bool IsValidCidr(string cidr)
{
if (string.IsNullOrWhiteSpace(cidr))
return false;
var parts = cidr.Split('/');
if (parts.Length != 2)
return false;
if (!System.Net.IPAddress.TryParse(parts[0], out _))
return false;
if (!int.TryParse(parts[1], out int prefix))
return false;
return prefix >= 0 && prefix <= 32;
}
static void ShowUsage()
{
Console.WriteLine("Usage:");
Console.WriteLine(" To ADD a rule:");
Console.WriteLine(" AwsSecurityGroupManager.exe <Port> <CIDR> [Description]");
Console.WriteLine();
Console.WriteLine(" To REMOVE a rule:");
Console.WriteLine(" AwsSecurityGroupManager.exe --remove <Port> <CIDR>");
Console.WriteLine();
Console.WriteLine(" To LIST rules:");
Console.WriteLine(" AwsSecurityGroupManager.exe --list");
Console.WriteLine();
Console.WriteLine("Examples:");
Console.WriteLine(" Add SSH: AwsSecurityGroupManager.exe 22 192.168.1.10/32 \"SSH Access\"");
Console.WriteLine(" Remove SSH: AwsSecurityGroupManager.exe --remove 22 192.168.1.10/32");
Console.WriteLine(" List SSH: AwsSecurityGroupManager.exe --list");
}
static async Task Main(string[] args)
{
if (args.Length == 0)
{
ShowUsage();
return;
}
bool isRemove = false;
bool isList = false;
int argIndex = 0;
if (args[0].Equals("--remove", StringComparison.OrdinalIgnoreCase))
{
isRemove = true;
argIndex = 1;
if (args.Length < 3)
{
ShowUsage();
return;
}
}
else if (args[0].Equals("--list", StringComparison.OrdinalIgnoreCase))
{
isList = true;
}
var config = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
var awsConfig = config.GetSection("AWS");
var sgConfig = config.GetSection("SecurityGroup");
var region = RegionEndpoint.GetBySystemName(awsConfig["Region"]);
var accessKey = awsConfig["AccessKey"];
var secretKey = awsConfig["SecretKey"];
var groupId = sgConfig["GroupId"];
var protocol = sgConfig["Protocol"];
if (string.IsNullOrWhiteSpace(groupId))
{
Console.WriteLine("❌ GroupId is not configured in appsettings.json.");
return;
}
var ec2Client = new AmazonEC2Client(accessKey, secretKey, region);
if (isList)
{
Console.WriteLine("🔍 Fetching current SSH rules...");
var describeResponse = await ec2Client.DescribeSecurityGroupsAsync(new DescribeSecurityGroupsRequest
{
GroupIds = new List<string> { groupId }
});
var securityGroup = describeResponse.SecurityGroups?.FirstOrDefault();
if (securityGroup == null)
{
Console.WriteLine("❌ Security group not found.");
return;
}
var sshRules = securityGroup.IpPermissions
.Where(p => p.IpProtocol == protocol)
.ToList();
if (!sshRules.Any())
{
Console.WriteLine("ℹ️ No SSH rules found.");
}
else
{
Console.WriteLine($"✅ Found {sshRules.Count} rule(s):");
foreach (var perm in sshRules)
{
foreach (var range in perm.Ipv4Ranges)
{
Console.WriteLine($" Port: {perm.FromPort}");
Console.WriteLine($" CIDR: {range.CidrIp}");
Console.WriteLine($" Description: {range.Description}");
Console.WriteLine();
}
}
}
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
return;
}
if (!int.TryParse(args[argIndex], out int port))
{
Console.WriteLine("❌ Invalid port argument.");
return;
}
var cidrIp = args[argIndex + 1];
if (!IsValidCidr(cidrIp))
{
Console.WriteLine("❌ Invalid CIDR format. Use format x.x.x.x/y, e.g., 192.168.1.10/32.");
return;
}
var description = args.Length > argIndex + 2 ? args[argIndex + 2] : "SSH rule added by AwsSecurityGroupManager";
if (isRemove)
{
Console.WriteLine("🔍 Fetching current rules...");
var describeResponse = await ec2Client.DescribeSecurityGroupsAsync(new DescribeSecurityGroupsRequest
{
GroupIds = new List<string> { groupId }
});
var securityGroup = describeResponse.SecurityGroups?.FirstOrDefault();
if (securityGroup == null)
{
Console.WriteLine("❌ Security group not found.");
return;
}
var matchingPermissions = securityGroup.IpPermissions
.Where(p =>
p.IpProtocol == protocol &&
p.FromPort == port &&
p.ToPort == port &&
p.Ipv4Ranges.Any(r => r.CidrIp != null && r.CidrIp == cidrIp))
.ToList();
if (!matchingPermissions.Any())
{
Console.WriteLine("❌ No matching rule found for the specified port and CIDR.");
return;
}
Console.WriteLine($"⚠️ You are about to REMOVE the following rule(s):");
foreach (var perm in matchingPermissions)
{
foreach (var range in perm.Ipv4Ranges.Where(r => r.CidrIp != null && r.CidrIp == cidrIp))
{
Console.WriteLine($" Protocol: {perm.IpProtocol}");
Console.WriteLine($" Port: {perm.FromPort}");
Console.WriteLine($" CIDR: {range.CidrIp}");
Console.WriteLine($" Description: {range.Description}");
Console.WriteLine();
}
}
Console.Write("Are you sure you want to proceed? (y/n): ");
var confirm = Console.ReadLine();
if (!string.Equals(confirm, "y", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("❌ Operation cancelled.");
return;
}
var revokeRequest = new RevokeSecurityGroupIngressRequest
{
GroupId = groupId,
IpPermissions = matchingPermissions.Select(p =>
{
var matchingCidrIps = p.Ipv4Ranges
.Select(r => r.CidrIp)
.Where(cidr => cidr != null && cidr == cidrIp)
.ToList();
return new IpPermission
{
IpProtocol = p.IpProtocol,
FromPort = p.FromPort,
ToPort = p.ToPort,
Ipv4Ranges = matchingCidrIps
.Select(cidr => new IpRange { CidrIp = cidr! })
.ToList()
};
}).ToList()
};
try
{
await ec2Client.RevokeSecurityGroupIngressAsync(revokeRequest);
Console.WriteLine($"✅ Rule(s) removed successfully.");
}
catch (AmazonEC2Exception ex)
{
Console.WriteLine($"❌ Error removing rule: {ex.Message}");
}
}
else
{
Console.WriteLine($"🔹 Adding inbound SSH rule:");
Console.WriteLine($" Port: {port}");
Console.WriteLine($" CIDR: {cidrIp}");
Console.WriteLine($" Description: {description}");
var authorizeRequest = new AuthorizeSecurityGroupIngressRequest
{
GroupId = groupId,
IpPermissions = new List<IpPermission>
{
new IpPermission
{
IpProtocol = protocol,
FromPort = port,
ToPort = port,
Ipv4Ranges = new List<IpRange>
{
new IpRange
{
CidrIp = cidrIp,
Description = description
}
}
}
}
};
try
{
await ec2Client.AuthorizeSecurityGroupIngressAsync(authorizeRequest);
Console.WriteLine($"✅ Rule added successfully: TCP {port} from {cidrIp}");
}
catch (AmazonEC2Exception ex)
{
if (ex.ErrorCode == "InvalidPermission.Duplicate")
{
Console.WriteLine("⚠️ Rule already exists.");
}
else
{
Console.WriteLine($"❌ Error adding rule: {ex.Message}");
}
}
}
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
}
}
}
To see a list of commands run the executable file without any switch:

Here you go!
You can add to this and build something that meets your requirements.