This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Text.Json; | |
using System.Threading.Tasks; | |
using Amazon; | |
using Amazon.EC2; | |
using Amazon.EC2.Model; | |
using Amazon.Runtime; | |
using Amazon.SSO; | |
using Amazon.SSO.Model; | |
namespace AWSRetrieveEC2Instances_SSO | |
{ | |
// Class used to group the information of interest for us in the EC2 instances | |
class EC2InstanceLimitedData | |
{ | |
public string InstanceId { get; set; } | |
public string InstanceType { get; set; } | |
public string PrivateIpAddresses { get; set; } | |
public string PublicIpAddresses { get; set; } | |
public string AWSAccountName { get; set; } | |
public string AWSAccountId { get; set; } | |
} | |
class Program | |
{ | |
static async Task Main(string[] args) | |
{ | |
// ! Update these variables using the instructions at | |
// https://mihai-albert.com/2020/12/13/get-the-list-of-all-aws-ec2-instances-with-all-their-private-and-public-ips/#code-for-retrieving-ec2-instances-using-sso-users | |
string providedAccessToken = | |
""; | |
string ssoRoleName = ""; | |
// Connect to the SSO service. This is where we'll get all the | |
// AWS accounts the access token's owner has access to, and the respective roles within | |
// ! Make sure to use the correct zone here, as per | |
// https://mihai-albert.com/2020/12/13/get-the-list-of-all-aws-ec2-instances-with-all-their-private-and-public-ips/#getting-a-token | |
using (AmazonSSOClient amazonSSOClient = | |
new AmazonSSOClient(new AnonymousAWSCredentials(), RegionEndpoint.USEast2)) | |
{ | |
// The list that will contain all the EC2 instances retrieved from all the accounts | |
List<EC2InstanceLimitedData> ec2Instances = new List<EC2InstanceLimitedData>(); | |
// Cycle through all the AWS accounts where the user has permissions | |
foreach (var awsAccount in await GetAWSAccounts(amazonSSOClient, providedAccessToken)) | |
{ | |
Console.WriteLine($"In account {awsAccount.AccountId}"); | |
var credentialsForRole = await GetEC2ReadAccessRoleCredentials(amazonSSOClient, | |
providedAccessToken, awsAccount.AccountId, ssoRoleName); | |
var enabledRegions = await GetEC2EnabledRegions(credentialsForRole); | |
foreach (var enabledRegion in enabledRegions) | |
{ | |
// Build the EC2 client based on the temporary credentials handed back | |
// by STS when assuming the role in the current account | |
using (var ec2Client = new AmazonEC2Client(new SessionAWSCredentials( | |
credentialsForRole.AccessKeyId, credentialsForRole.SecretAccessKey, | |
credentialsForRole.SessionToken), | |
RegionEndpoint.GetBySystemName(enabledRegion.RegionName))) | |
{ | |
var ec2InstancesForCurrentAccountAndRegion = | |
await GetEC2InstancesForAccountAndRegion(ec2Client, awsAccount); | |
Console.WriteLine( | |
$"{ec2InstancesForCurrentAccountAndRegion.Count} instances found in region {ec2Client.Config.RegionEndpoint.DisplayName}"); | |
ec2Instances.AddRange(ec2InstancesForCurrentAccountAndRegion); | |
} | |
} | |
} | |
Console.WriteLine($"{ec2Instances.Count} EC2 instances retrieved overall"); | |
// Serialize the list of EC2 instance data to json | |
var ec2InstancesAsJson = JsonSerializer.Serialize(ec2Instances, | |
new JsonSerializerOptions() | |
{ | |
WriteIndented = true | |
}); | |
System.IO.File.WriteAllText("awsEC2Instances.json", ec2InstancesAsJson); | |
} | |
} | |
private static async Task<List<AccountInfo>> GetAWSAccounts(AmazonSSOClient amazonSSOClient, | |
string providedAccessToken) | |
{ | |
List<AccountInfo> awsAccounts = new List<AccountInfo>(); | |
string nextToken = null; | |
do | |
{ | |
var listAccountsResponse = await amazonSSOClient.ListAccountsAsync( | |
new ListAccountsRequest | |
{ | |
AccessToken = providedAccessToken, | |
NextToken = nextToken | |
}); | |
awsAccounts.AddRange(listAccountsResponse.AccountList); | |
nextToken = listAccountsResponse.NextToken; | |
} while (nextToken != null); | |
return awsAccounts; | |
} | |
private static async Task<RoleCredentials> GetEC2ReadAccessRoleCredentials(AmazonSSOClient amazonSSOClient, | |
string providedAccessToken, string awsAccountId, string ssoRoleName) | |
{ | |
// Assume the designated role in the current AWS account. The result is that | |
// short-term security credentials are provided back from the STS service | |
var getRoleCredentialsResponse = await amazonSSOClient.GetRoleCredentialsAsync( | |
new GetRoleCredentialsRequest() | |
{ | |
AccessToken = providedAccessToken, | |
AccountId = awsAccountId, | |
RoleName = ssoRoleName | |
}); | |
// Extract the credentials from the response | |
return getRoleCredentialsResponse.RoleCredentials; | |
} | |
private static async Task<List<Region>> GetEC2EnabledRegions(RoleCredentials credentialsForTargetRole) | |
{ | |
// Build a throwaway AmazonEC2Client object just to get the enabled regions. Use | |
// a region that is always enabled (us-east-1) | |
var enabledRegionsResponse = await (new AmazonEC2Client(new SessionAWSCredentials( | |
credentialsForTargetRole.AccessKeyId, credentialsForTargetRole.SecretAccessKey, | |
credentialsForTargetRole.SessionToken), | |
RegionEndpoint.USEast1)).DescribeRegionsAsync(); | |
return enabledRegionsResponse.Regions; | |
} | |
public static async Task<List<EC2InstanceLimitedData>> GetEC2InstancesForAccountAndRegion( | |
AmazonEC2Client ec2Client, AccountInfo awsAccount) | |
{ | |
// Don't get fooled by the fact that the AWS account gets passed | |
// through as parameter. It doesn't act as a "filter" for the EC2 | |
// instance data retrieved, as that's done based off the AmazonEC2Client | |
// object, which is also passed as a parameter (and was previously | |
// built against a a specific account using a specific set of | |
// credentials, and also against a specific region). Its sole purpose | |
// is to be able to add information about the account with each | |
// EC2 instance element in our final list, as extracting the account | |
// info directly from the AmazonEC2Client doesn't look possible | |
// In the EC2 world there are reservations (do not confuse with Reserved Instances) | |
// that refer to a launch event. Within a reservation there can be one or more EC2 | |
// instances (the actual VMs); if a launch fired up multiple instances, then all | |
// those instances will belong to the reservation corresponding to the launch. | |
List<EC2InstanceLimitedData> ec2InstancesPerAccountAndRegion = new List<EC2InstanceLimitedData>(); | |
string nextToken = null; | |
do | |
{ | |
var describeInstancesResult = await ec2Client.DescribeInstancesAsync(new DescribeInstancesRequest() | |
{ | |
NextToken = nextToken | |
}); | |
nextToken = describeInstancesResult.NextToken; | |
// Cycle through all the EC2 instances in the current response | |
foreach (var reservation in describeInstancesResult.Reservations) | |
{ | |
foreach (var instance in reservation.Instances) | |
{ | |
Console.WriteLine($"name={instance.InstanceId} (reservation id= {reservation.ReservationId})"); | |
// Add all the private IPs along with their corresponding public IPs; | |
// iterate through network adapters, then one level down through | |
// each private IP | |
List<string> currentPrivateIPsList = new List<string>(); | |
List<string> currentPublicIPsList = new List<string>(); | |
foreach (var eni in instance.NetworkInterfaces) | |
{ | |
foreach (var privateIP in eni.PrivateIpAddresses) | |
{ | |
currentPrivateIPsList.Add(privateIP.PrivateIpAddress); | |
if (privateIP.Association != null) | |
currentPublicIPsList.Add(privateIP.Association.PublicIp); | |
} | |
} | |
var currentPrivateIPsString = String.Join(",", currentPrivateIPsList.ToArray()); | |
var currentPublicIPsString = String.Join(",", currentPublicIPsList.ToArray()); | |
ec2InstancesPerAccountAndRegion.Add( | |
new EC2InstanceLimitedData() | |
{ | |
AWSAccountId = awsAccount.AccountId, | |
AWSAccountName = awsAccount.AccountName, | |
InstanceId = instance.InstanceId, | |
InstanceType = instance.InstanceType, | |
PrivateIpAddresses = currentPrivateIPsString, | |
PublicIpAddresses = currentPublicIPsString | |
}); | |
} | |
} | |
} while (nextToken != null); | |
return ec2InstancesPerAccountAndRegion; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment