Skip to content

Commit

Permalink
Release 1.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
itzikYeret authored and actions-user committed Nov 27, 2021
1 parent 6d708dc commit 425f7bf
Show file tree
Hide file tree
Showing 54 changed files with 890 additions and 341 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ namespace Dome9.CloudGuardOnboarding.Orchestrator
/// <summary>
/// Singleton that executes operations on an AWS CloudFormation client
/// </summary>
public class CloudFormationWrapper : ICloudFormationWrapper, IDisposable
public class CloudFormationWrapper : ICloudFormationWrapper
{
private readonly AmazonCloudFormationClient _client;
private static ICloudFormationWrapper _cfnWrapper = null;
private static readonly object _instanceLock = new object();
private const int STATUS_POLLING_INTERVAL_MILLISECONDS = 500;
private bool _disposed = false;
private bool _disposed = false;

private CloudFormationWrapper()
{
Expand All @@ -42,9 +42,7 @@ public async Task<string> CreateStackAsync(
Dictionary<string, string> parameters,
Action<string> statusUpdate,
int executionTimeoutMinutes)
{

int statusPollCount = 0;
{

var requestParameters = parameters?.Select(p => new Parameter
{
Expand All @@ -63,40 +61,32 @@ public async Task<string> CreateStackAsync(

try
{
StackSummary stackSummary = null;
// we don't check if a stack with the same name already exists (even though we can),
// in case there is - stack create will fail.
var response = await _client.CreateStackAsync(request);
do
if (response?.HttpStatusCode == System.Net.HttpStatusCode.OK)
{
stackSummary = await GetStackSummaryAsync(feature, stackName);
if (!stackSummary.StackStatus.IsFinal())
{
Console.WriteLine($"[INFO] Waiting {STATUS_POLLING_INTERVAL_MILLISECONDS}ms to poll stack status again, {stackSummary.ToDetailedString()}");
statusUpdate($"{stackSummary.StackStatus}");
await Task.Delay(STATUS_POLLING_INTERVAL_MILLISECONDS);
}
StackSummary stackSummary = await PollUntilStackStatusFinal(feature, stackName, statusUpdate, executionTimeoutMinutes);

if(STATUS_POLLING_INTERVAL_MILLISECONDS * ++statusPollCount > executionTimeoutMinutes * 60 * 1000)
if (stackSummary == null || !stackSummary.StackStatus.IsFinal() || stackSummary.StackStatus.IsError())
{
Console.WriteLine("[WARNING] Execution timeout exceeded");
break;
throw new Exception($"Invalid stack status: {stackSummary?.ToDetailedString() ?? $"Unable to get stack summary. Feature='{feature}' StackName='{stackName}', StackTemplateS3Url='{stackTemplateS3Url}'."}");
}

Console.WriteLine($"[INFO] [{nameof(CreateStackAsync)}] Success in creating stack. StackSummary=[{stackSummary.ToDetailedString()}]. StackTemplateS3Url='{stackTemplateS3Url}'.");
return response.StackId;
}
while (stackSummary == null || !stackSummary.StackStatus.IsFinal());

if(stackSummary == null || !stackSummary.StackStatus.IsFinal() || stackSummary.StackStatus.IsError())
else
{
throw new Exception(stackSummary?.ToDetailedString() ?? "Unable to get stack summary");
throw new Exception($"Failed to create stack. Feature='{feature}', StackName='{stackName}', StackTemplateS3Url='{stackTemplateS3Url}', HttpStatusCode='{response.HttpStatusCode}'.");
}

Console.WriteLine($"[INFO] Success, {stackSummary.ToDetailedString()}. StackTemplateS3Url:'{stackTemplateS3Url}'");
return response.StackId;
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] [{nameof(CreateStackAsync)}] Failed to create stack. StackName:'{stackName}', StackTemplateS3Url:'{stackTemplateS3Url}', Error={ex}");
Console.WriteLine($"[ERROR] [{nameof(CreateStackAsync)}] {ex}");
throw new OnboardingException(ex.Message, feature);
}
}
}

public async Task<string> UpdateStackAsync(
Enums.Feature feature,
Expand Down Expand Up @@ -155,25 +145,39 @@ public async Task<StackSummary> GetStackSummaryAsync(Enums.Feature feature, stri
try
{
var nextToken = "";
StackSummary stack = null;
while (stack == null && nextToken != null)
StackSummary stackSummary = null;
while (stackSummary == null && nextToken != null)
{
var response = await _client.ListStacksAsync();
stack = response.StackSummaries.FirstOrDefault(s => s.StackName == stackName);
var listRequest = new ListStacksRequest
{
NextToken = string.IsNullOrWhiteSpace(nextToken) ? null : nextToken,
};

var response = await _client.ListStacksAsync(listRequest);

stackSummary = response.StackSummaries.FirstOrDefault(s => s.StackName == stackName);

nextToken = response.NextToken;
}

return stack;
return stackSummary;

}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] [{nameof(GetStackSummaryAsync)}] Failed to get StackSummary for stack '{stackName}'. Error={ex}");
throw new OnboardingException(ex.Message, feature); ;
}
}

public async Task DeleteStackAsync(Enums.Feature feature, string stackName)
/// <summary>
/// This method will wait until the delete operation is finished, because if it does not wait, and lambda proceeds to exit,
/// the authorization to perform delete actions on the stack will be lost.
/// </summary>
/// <param name="feature"></param>
/// <param name="stackName"></param>
/// <returns></returns>
public async Task DeleteStackAsync(Enums.Feature feature, string stackName, int executionTimeoutMinutes)
{
var request = new DeleteStackRequest
{
Expand All @@ -182,21 +186,69 @@ public async Task DeleteStackAsync(Enums.Feature feature, string stackName)

try
{
await _client.DeleteStackAsync(request);
var stack = await GetStackSummaryAsync(feature, stackName);
if (stack == null)
{
Console.WriteLine($"[INFO] [{nameof(DeleteStackAsync)}] can not get stack, probably stack not exist, will skip stack deletion. Feature={feature}, StackName={stackName}.");
return;
}

var response = await _client.DeleteStackAsync(request);
if (response?.HttpStatusCode == System.Net.HttpStatusCode.OK)
{
StackSummary stackSummary = await PollUntilStackStatusFinal(feature, stackName, (s) => Console.Write(s), executionTimeoutMinutes);

if (stackSummary == null || !stackSummary.StackStatus.IsFinal() || stackSummary.StackStatus.IsError())
{
throw new Exception($"Invalid stack status: {stackSummary?.ToDetailedString() ?? $"Unable to get stack summary. Feature={feature}, StackName={stackName}"}.");
}
Console.WriteLine($"[INFO] [{nameof(DeleteStackAsync)}] Success in deleting stack. Feature={feature}, StackSummary=[{stackSummary.ToDetailedString()}].");

}
else
{
throw new Exception($"Failed to delete stack. Feature={feature}, Stackname='{stackName}' HttpStatusCode='{response?.HttpStatusCode}'.");
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed to delete stack '{stackName}'. Error={ex}");
throw new OnboardingException(ex.Message, feature); ;
Console.WriteLine($"[ERROR] {nameof(DeleteStackAsync)} {ex}");
throw new OnboardingDeleteStackException(ex.Message, stackName, feature); ;
}
}

private async Task<StackSummary> PollUntilStackStatusFinal(Enums.Feature feature, string stackName, Action<string> statusUpdate, int executionTimeoutMinutes)
{
int statusPollCount = 0;
StackSummary stackSummary;

do
{
stackSummary = await GetStackSummaryAsync(feature, stackName);
if (stackSummary == null || !stackSummary.StackStatus.IsFinal())
{
Console.WriteLine($"[INFO] Waiting {STATUS_POLLING_INTERVAL_MILLISECONDS}ms to poll stack status again, {stackSummary?.ToDetailedString()}");
statusUpdate($"{stackSummary.StackStatus}");
await Task.Delay(STATUS_POLLING_INTERVAL_MILLISECONDS);
}

if (STATUS_POLLING_INTERVAL_MILLISECONDS * ++statusPollCount > executionTimeoutMinutes * 60 * 1000)
{
Console.WriteLine($"[WARN] [{nameof(PollUntilStackStatusFinal)}] Execution timeout exceeded. Feature={feature}, StackName={stackName}, ExecutionTimeoutMinutes={executionTimeoutMinutes}.");
break;
}
}
while (stackSummary == null || !stackSummary.StackStatus.IsFinal());

return stackSummary;
}

#region User Based Secrets

public async Task<ApiCredentials> GetCredentialsFromSecretsManager()
public async Task<ApiCredentials> GetCredentialsFromSecretsManager(string resourceName)
{
const string tagKey = "aws:cloudformation:logical-id";
const string tagValue = "CrossAccountUserCredentialsStored";
string tagValue = resourceName;
const string accessKeyId = "ACCESS_KEY";
const string accessKeySecret = "SECRET_KEY";

Expand Down Expand Up @@ -230,6 +282,8 @@ public async Task<ApiCredentials> GetCredentialsFromSecretsManager()
throw new OnboardingException(ex.Message, Enums.Feature.ContinuousCompliance);
}
}



private async Task<List<SecretListEntry>> SecretsManagerListSecrets()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Dome9.CloudGuardOnboarding.Orchestrator
{
public interface ICloudFormationWrapper
public interface ICloudFormationWrapper : IDisposable
{
Task<string> CreateStackAsync(
Enums.Feature feature,
Expand All @@ -27,8 +27,8 @@ Task<string> UpdateStackAsync(

Task<StackSummary> GetStackSummaryAsync(Enums.Feature feature, string stackName);

Task DeleteStackAsync(Enums.Feature feature, string stackName);
Task DeleteStackAsync(Enums.Feature feature, string stackName, int executionTimeoutMinutes);

Task<ApiCredentials> GetCredentialsFromSecretsManager();
Task<ApiCredentials> GetCredentialsFromSecretsManager(string key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,6 @@ public static bool IsError(this StackStatus status)
public static string ToDetailedString(this StackSummary stackSummary)
{
return $"StackName:'{stackSummary.StackName}' Status:'{stackSummary.StackStatus}', Reason:'{stackSummary.StackStatusReason}', LastUpdated:{stackSummary.LastUpdatedTime}";
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@ namespace Dome9.CloudGuardOnboarding.Orchestrator.AwsCloudFormation.StackConfig
{
public class InteligenceStackConfig : OnboardingStackConfig
{

public string CloudtrailS3BucketName { get; set; }
public string CloudGuardRoleName { get; set; }
public InteligenceStackConfig
(string templateS3Url,
string stackName,
List<string> capabilities,
string onboardingId,
string cloudtrailS3BucketName,
string cloudGuardRoleName,
int executionTimeoutMinutes
)
: base(onboardingId, templateS3Url, stackName, capabilities, executionTimeoutMinutes)
: base(onboardingId, templateS3Url, stackName, executionTimeoutMinutes)
{
CloudtrailS3BucketName = cloudtrailS3BucketName;
CloudGuardRoleName = cloudGuardRoleName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ public OnboardingStackConfig(
string onboardingId,
string templateS3Url,
string stackName,
List<string> capabilities,
int executionTimeoutMinutes)
: base(templateS3Url, stackName, capabilities, executionTimeoutMinutes)
: base(templateS3Url, stackName, executionTimeoutMinutes)
{
OnboardingId = onboardingId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ public class PostureStackConfig : OnboardingStackConfig
public PostureStackConfig(
string templateS3Url,
string stackName,
List<string> capabilities,
string onboardingId,
string cloudGuardAwsAccountId,
string cloudGuardExternalTrustSecret,
string cloudGuardExternalTrustSecret,
int executionTimeoutMinutes)
: base(onboardingId, templateS3Url, stackName, capabilities, executionTimeoutMinutes)
: base(onboardingId, templateS3Url, stackName, executionTimeoutMinutes)
{
CloudGuardAwsAccountId = cloudGuardAwsAccountId;
RoleExternalTrustSecret = cloudGuardExternalTrustSecret;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,20 @@

namespace Dome9.CloudGuardOnboarding.Orchestrator
{
public class PostureUserBasedStackConfig : OnboardingStackConfig
public class PostureUserBasedStackConfig : OnboardingStackConfig
{
public string AwsPartition { get; set; }

public PostureUserBasedStackConfig(
string templateS3Url,
string stackName,
List<string> capabilities,
string onboardingId,
string awsPartition,
string onboardingId,
int executionTimeoutMinutes)
: base(onboardingId, templateS3Url, stackName, capabilities, executionTimeoutMinutes)
: base(onboardingId, templateS3Url, stackName, executionTimeoutMinutes)
{
AwsPartition = awsPartition;
}

public override string ToString()
{
return $"{base.ToString()}, {nameof(AwsPartition)}='{AwsPartition}'" ;
return base.ToString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ public class ServerlessStackConfig : OnboardingStackConfig
public ServerlessStackConfig(
string templateS3Url,
string stackName,
List<string> capabilities,
string onboardingId,
int executionTimeoutMinutes)
: base(onboardingId, templateS3Url, stackName, capabilities, executionTimeoutMinutes)
: base(onboardingId, templateS3Url, stackName, executionTimeoutMinutes)
{
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ public class StackConfig
public string TemplateS3Url { get; set; }
public string StackName { get; set; }
public string ExecutionRoleArn { get; set; }
public List<string> Capabilities { get; set; }
public List<string> Capabilities { get; set; } = new List<string> { "CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND" };

public int ExecutionTimeoutMinutes { get; set; }

public StackConfig(string templateS3Url, string stackName, List<string> capabilities, int executionTimeoutMinutes)
public StackConfig(string templateS3Url, string stackName, int executionTimeoutMinutes)
{
TemplateS3Url = templateS3Url;
StackName = stackName;
Capabilities = capabilities;
ExecutionTimeoutMinutes = executionTimeoutMinutes;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ protected override Dictionary<string, string> GetParameters(OnboardingStackConfi
{
if (!(onboardingStackConfig is InteligenceStackConfig))
{
throw new ArgumentException("OnboardingStackConfig must be of type LogicStackConfig");
throw new ArgumentException($"{nameof(onboardingStackConfig)} is not of type {nameof(InteligenceStackConfig)}");
}

InteligenceStackConfig logicStackConfig = onboardingStackConfig as InteligenceStackConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ protected override Dictionary<string, string> GetParameters(OnboardingStackConfi
{
if(!(onboardingStackConfig is PostureStackConfig))
{
throw new ArgumentException("OnboardingStackConfig must be of type PostureStackConfig");
throw new ArgumentException($"{nameof(onboardingStackConfig)} is not of type {nameof(PostureStackConfig)}");
}

PostureStackConfig postureStackConfig = onboardingStackConfig as PostureStackConfig;
return new Dictionary<string, string>
{
{"CloudGuardAwsAccountId", postureStackConfig.CloudGuardAwsAccountId},
{"RoleExternalTrustSecret", postureStackConfig.RoleExternalTrustSecret }
{ "CloudGuardAwsAccountId", postureStackConfig.CloudGuardAwsAccountId },
{ "RoleExternalTrustSecret", postureStackConfig.RoleExternalTrustSecret }
};
}
}
Expand Down
Loading

0 comments on commit 425f7bf

Please sign in to comment.