From 9df94a233d47615aca14b12ba29ea33f990dafa7 Mon Sep 17 00:00:00 2001 From: Malhar Khimsaria Date: Fri, 27 Aug 2021 14:38:34 -0400 Subject: [PATCH 1/5] fix: Fixed an error caught by static code analysis --- src/AWS.Deploy.CLI/Commands/DeployCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs index 1ba74afc2..51661d56d 100644 --- a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs @@ -43,7 +43,7 @@ public class DeployCommand private readonly ISystemCapabilityEvaluator _systemCapabilityEvaluator; private readonly OrchestratorSession _session; private readonly IDirectoryManager _directoryManager; - private ICDKVersionDetector _cdkVersionDetector; + private readonly ICDKVersionDetector _cdkVersionDetector; public DeployCommand( IToolInteractiveService toolInteractiveService, From d85807eafad4dfc4973643b93a1482f256fed6ee Mon Sep 17 00:00:00 2001 From: aws-sdk-dotnet-automation Date: Thu, 26 Aug 2021 14:29:40 +0000 Subject: [PATCH 2/5] build: version bump to 0.18 --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 0cefc4d15..a4cc51e4d 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.17", + "version": "0.18", "publicReleaseRefSpec": [ ".*" ], From 9a601078c74ba8d57501ead89d22d47bde3f4898 Mon Sep 17 00:00:00 2001 From: Philippe El Asmar Date: Thu, 26 Aug 2021 13:34:19 -0400 Subject: [PATCH 3/5] fix: reusing AES IV could cause potential security risks --- .../Commands/ServerModeCommand.cs | 4 --- .../AwsCredentialsAuthenticationHandler.cs | 28 ++++++++++++++++--- .../Services/AesEncryptionProvider.cs | 6 ++-- .../Services/IEncryptionProvider.cs | 2 +- .../Services/NoEncryptionProvider.cs | 2 +- .../ServerModeHttpClient.cs | 3 +- .../ServerModeSession.cs | 8 ++---- 7 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/AWS.Deploy.CLI/Commands/ServerModeCommand.cs b/src/AWS.Deploy.CLI/Commands/ServerModeCommand.cs index d14b4aa41..58644331f 100644 --- a/src/AWS.Deploy.CLI/Commands/ServerModeCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/ServerModeCommand.cs @@ -104,10 +104,6 @@ private IEncryptionProvider CreateEncryptionProvider() { aes.Key = Convert.FromBase64String(keyInfo.Key); } - if (keyInfo.IV != null) - { - aes.IV = Convert.FromBase64String(keyInfo.IV); - } encryptionProvider = new AesEncryptionProvider(aes); break; diff --git a/src/AWS.Deploy.CLI/ServerMode/AwsCredentialsAuthenticationHandler.cs b/src/AWS.Deploy.CLI/ServerMode/AwsCredentialsAuthenticationHandler.cs index 42703bbd3..ba83907bb 100644 --- a/src/AWS.Deploy.CLI/ServerMode/AwsCredentialsAuthenticationHandler.cs +++ b/src/AWS.Deploy.CLI/ServerMode/AwsCredentialsAuthenticationHandler.cs @@ -82,9 +82,18 @@ protected override Task HandleAuthenticateAsync() public static AuthenticateResult ProcessAuthorizationHeader(string authorizationHeaderValue, IEncryptionProvider encryptionProvider) { var tokens = authorizationHeaderValue.Split(' '); - if (tokens.Length != 2) + if (tokens.Length != 2 && tokens.Length != 3) { - return AuthenticateResult.Fail($"Incorrect format Authorization header. Format should be \"{SchemaName} \""); + var ivPlaceholder = ""; + if (encryptionProvider is AesEncryptionProvider) + { + ivPlaceholder = " "; + } + return AuthenticateResult.Fail($"Incorrect format Authorization header. Format should be \"{SchemaName} {ivPlaceholder}\""); + } + if (tokens.Length == 2 && encryptionProvider is AesEncryptionProvider) + { + return AuthenticateResult.Fail($"Incorrect format Authorization header. Format should be \"{SchemaName} \""); } if (!string.Equals(SchemaName, tokens[0])) { @@ -93,9 +102,20 @@ public static AuthenticateResult ProcessAuthorizationHeader(string authorization try { - var base64Bytes = Convert.FromBase64String(tokens[1]); + byte[]? base64IV; + byte[] base64Bytes; + if (tokens.Length == 2) + { + base64IV = null; + base64Bytes = Convert.FromBase64String(tokens[1]); + } + else + { + base64IV = Convert.FromBase64String(tokens[1]); + base64Bytes = Convert.FromBase64String(tokens[2]); + } - var decryptedBytes = encryptionProvider.Decrypt(base64Bytes); + var decryptedBytes = encryptionProvider.Decrypt(base64Bytes, base64IV); var json = Encoding.UTF8.GetString(decryptedBytes); var authParameters = JsonConvert.DeserializeObject>(json); diff --git a/src/AWS.Deploy.CLI/ServerMode/Services/AesEncryptionProvider.cs b/src/AWS.Deploy.CLI/ServerMode/Services/AesEncryptionProvider.cs index 2fa02f367..e5e13a44b 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Services/AesEncryptionProvider.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Services/AesEncryptionProvider.cs @@ -19,9 +19,9 @@ public AesEncryptionProvider(Aes aes) _aes = aes; } - public byte[] Decrypt(byte[] encryptedData) - { - var decryptor = _aes.CreateDecryptor(_aes.Key, _aes.IV); + public byte[] Decrypt(byte[] encryptedData, byte[]? generatedIV) + { + var decryptor = _aes.CreateDecryptor(_aes.Key, generatedIV); using var inputStream = new MemoryStream(encryptedData); using var decryptStream = new CryptoStream(inputStream, decryptor, CryptoStreamMode.Read); diff --git a/src/AWS.Deploy.CLI/ServerMode/Services/IEncryptionProvider.cs b/src/AWS.Deploy.CLI/ServerMode/Services/IEncryptionProvider.cs index d32fc9de7..ca61a92d9 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Services/IEncryptionProvider.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Services/IEncryptionProvider.cs @@ -10,6 +10,6 @@ namespace AWS.Deploy.CLI.ServerMode.Services { public interface IEncryptionProvider { - byte[] Decrypt(byte[] encryptedData); + byte[] Decrypt(byte[] encryptedData, byte[]? generatedIV); } } diff --git a/src/AWS.Deploy.CLI/ServerMode/Services/NoEncryptionProvider.cs b/src/AWS.Deploy.CLI/ServerMode/Services/NoEncryptionProvider.cs index 0feab1b26..4a14977d4 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Services/NoEncryptionProvider.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Services/NoEncryptionProvider.cs @@ -10,6 +10,6 @@ namespace AWS.Deploy.CLI.ServerMode.Services { public class NoEncryptionProvider : IEncryptionProvider { - public byte[] Decrypt(byte[] encryptedData) => encryptedData; + public byte[] Decrypt(byte[] encryptedData, byte[]? generatedIV) => encryptedData; } } diff --git a/src/AWS.Deploy.ServerMode.Client/ServerModeHttpClient.cs b/src/AWS.Deploy.ServerMode.Client/ServerModeHttpClient.cs index 0f9767c9a..c86fc10f8 100644 --- a/src/AWS.Deploy.ServerMode.Client/ServerModeHttpClient.cs +++ b/src/AWS.Deploy.ServerMode.Client/ServerModeHttpClient.cs @@ -73,6 +73,7 @@ public static void AddAuthorizationHeader(HttpRequestMessage request, ImmutableC string base64; if(aes != null) { + aes.GenerateIV(); var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(json)); @@ -82,7 +83,7 @@ public static void AddAuthorizationHeader(HttpRequestMessage request, ImmutableC inputStream.CopyTo(encryptStream); } - base64 = Convert.ToBase64String(outputStream.ToArray()); + base64 = $"{Convert.ToBase64String(aes.IV)} {Convert.ToBase64String(outputStream.ToArray())}"; } else { diff --git a/src/AWS.Deploy.ServerMode.Client/ServerModeSession.cs b/src/AWS.Deploy.ServerMode.Client/ServerModeSession.cs index a33859fe0..e9ce6c337 100644 --- a/src/AWS.Deploy.ServerMode.Client/ServerModeSession.cs +++ b/src/AWS.Deploy.ServerMode.Client/ServerModeSession.cs @@ -128,12 +128,10 @@ public async Task Start(CancellationToken cancellationToken) { _aes = Aes.Create(); _aes.GenerateKey(); - _aes.GenerateIV(); var keyInfo = new EncryptionKeyInfo( EncryptionKeyInfo.VERSION_1_0, - Convert.ToBase64String(_aes.Key), - Convert.ToBase64String(_aes.IV)); + Convert.ToBase64String(_aes.Key)); var keyInfoStdin = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(keyInfo))); @@ -245,9 +243,9 @@ private class EncryptionKeyInfo public string Version { get; set; } public string Key { get; set; } - public string IV { get; set; } + public string? IV { get; set; } - public EncryptionKeyInfo(string version, string key, string iv) + public EncryptionKeyInfo(string version, string key, string? iv = null) { Version = version; Key = key; From 5b9252ffd968abbfeb588efc804f1d7135f23cd5 Mon Sep 17 00:00:00 2001 From: Philippe El Asmar Date: Fri, 27 Aug 2021 11:09:27 -0400 Subject: [PATCH 4/5] fix: disallow SMB shares from file paths --- src/AWS.Deploy.CLI/AWSUtilities.cs | 7 +++- src/AWS.Deploy.CLI/Commands/CommandFactory.cs | 2 +- src/AWS.Deploy.CLI/Commands/DeployCommand.cs | 4 +- .../DockerExecutionDirectoryCommand.cs | 7 +++- .../TypeHints/TypeHintCommandFactory.cs | 5 ++- src/AWS.Deploy.CLI/ConsoleUtilities.cs | 7 +++- .../Controllers/DeploymentController.cs | 4 +- .../AssumeRoleMfaTokenCodeCallback.cs | 7 +++- src/AWS.Deploy.Common/IO/DirectoryManager.cs | 14 ++++++- src/AWS.Deploy.Common/IO/FileManager.cs | 15 ++++++- .../Utilities/PathUtilities.cs | 30 ++++++++++++++ src/AWS.Deploy.DockerEngine/DockerEngine.cs | 7 +++- src/AWS.Deploy.Orchestration/Orchestrator.cs | 4 +- .../Utilities/ZipFileManager.cs | 11 ++++-- .../ConsoleUtilitiesTests.cs | 39 ++++++++++++------- test/AWS.Deploy.CLI.UnitTests/DockerTests.cs | 6 ++- .../RecommendationTests.cs | 12 +++++- 17 files changed, 139 insertions(+), 42 deletions(-) create mode 100644 src/AWS.Deploy.Common/Utilities/PathUtilities.cs diff --git a/src/AWS.Deploy.CLI/AWSUtilities.cs b/src/AWS.Deploy.CLI/AWSUtilities.cs index 5d0621e5a..66b5c382b 100644 --- a/src/AWS.Deploy.CLI/AWSUtilities.cs +++ b/src/AWS.Deploy.CLI/AWSUtilities.cs @@ -9,6 +9,7 @@ using Amazon.EC2.Model; using System.IO; using AWS.Deploy.CLI.Utilities; +using AWS.Deploy.Common.IO; namespace AWS.Deploy.CLI { @@ -22,11 +23,13 @@ public class AWSUtilities : IAWSUtilities { private readonly IToolInteractiveService _toolInteractiveService; private readonly IConsoleUtilities _consoleUtilities; + private readonly IDirectoryManager _directoryManager; - public AWSUtilities(IToolInteractiveService toolInteractiveService, IConsoleUtilities consoleUtilities) + public AWSUtilities(IToolInteractiveService toolInteractiveService, IConsoleUtilities consoleUtilities, IDirectoryManager directoryManager) { _toolInteractiveService = toolInteractiveService; _consoleUtilities = consoleUtilities; + _directoryManager = directoryManager; } public async Task ResolveAWSCredentials(string? profileName, string? lastUsedProfileName = null) @@ -89,7 +92,7 @@ await CanLoadCredentials(lastUsedCredentials)) if (credentials is AssumeRoleAWSCredentials assumeRoleAWSCredentials) { var assumeOptions = assumeRoleAWSCredentials.Options; - assumeOptions.MfaTokenCodeCallback = new AssumeRoleMfaTokenCodeCallback(_toolInteractiveService, assumeOptions).Execute; + assumeOptions.MfaTokenCodeCallback = new AssumeRoleMfaTokenCodeCallback(_toolInteractiveService, _directoryManager, assumeOptions).Execute; } return credentials; diff --git a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs index 16d043681..60d11511a 100644 --- a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs +++ b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs @@ -190,7 +190,7 @@ private Command BuildDeployCommand() AWSProfileName = input.Profile ?? userDeploymentSettings?.AWSProfile ?? null }; - var dockerEngine = new DockerEngine.DockerEngine(projectDefinition); + var dockerEngine = new DockerEngine.DockerEngine(projectDefinition, _fileManager); var deploy = new DeployCommand( _toolInteractiveService, diff --git a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs index 51661d56d..1e9184a15 100644 --- a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs @@ -390,7 +390,7 @@ private void SetDeploymentBundleOptionSetting(Recommendation recommendation, str switch (optionSettingId) { case "DockerExecutionDirectory": - new DockerExecutionDirectoryCommand(_consoleUtilities).OverrideValue(recommendation, settingValue.ToString() ?? ""); + new DockerExecutionDirectoryCommand(_consoleUtilities, _directoryManager).OverrideValue(recommendation, settingValue.ToString() ?? ""); break; case "DockerBuildArgs": new DockerBuildArgsCommand(_consoleUtilities).OverrideValue(recommendation, settingValue.ToString() ?? ""); @@ -571,7 +571,7 @@ private async Task CreateDeploymentBundle(Orchestrator orchestrator, Recommendat selectedRecommendation.DeploymentBundle.DockerExecutionDirectory, allowEmpty: true); - if (!Directory.Exists(dockerExecutionDirectory)) + if (!_directoryManager.Exists(dockerExecutionDirectory)) continue; selectedRecommendation.DeploymentBundle.DockerExecutionDirectory = dockerExecutionDirectory; diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs index 68e5f1014..38718620c 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/DockerExecutionDirectoryCommand.cs @@ -4,6 +4,7 @@ using System.IO; using System.Threading.Tasks; using AWS.Deploy.Common; +using AWS.Deploy.Common.IO; using AWS.Deploy.Common.Recipes; namespace AWS.Deploy.CLI.Commands.TypeHints @@ -11,10 +12,12 @@ namespace AWS.Deploy.CLI.Commands.TypeHints public class DockerExecutionDirectoryCommand : ITypeHintCommand { private readonly IConsoleUtilities _consoleUtilities; + private readonly IDirectoryManager _directoryManager; - public DockerExecutionDirectoryCommand(IConsoleUtilities consoleUtilities) + public DockerExecutionDirectoryCommand(IConsoleUtilities consoleUtilities, IDirectoryManager directoryManager) { _consoleUtilities = consoleUtilities; + _directoryManager = directoryManager; } public Task Execute(Recommendation recommendation, OptionSettingItem optionSetting) @@ -47,7 +50,7 @@ public void OverrideValue(Recommendation recommendation, string executionDirecto private string ValidateExecutionDirectory(string executionDirectory) { - if (!string.IsNullOrEmpty(executionDirectory) && !Directory.Exists(executionDirectory)) + if (!string.IsNullOrEmpty(executionDirectory) && !_directoryManager.Exists(executionDirectory)) return "The directory specified for Docker execution does not exist."; else return ""; diff --git a/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs b/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs index 3775c664e..8999dd1f4 100644 --- a/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs +++ b/src/AWS.Deploy.CLI/Commands/TypeHints/TypeHintCommandFactory.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using AWS.Deploy.Common; +using AWS.Deploy.Common.IO; using AWS.Deploy.Common.Recipes; using AWS.Deploy.Orchestration.Data; @@ -29,7 +30,7 @@ public class TypeHintCommandFactory : ITypeHintCommandFactory { private readonly Dictionary _commands; - public TypeHintCommandFactory(IToolInteractiveService toolInteractiveService, IAWSResourceQueryer awsResourceQueryer, IConsoleUtilities consoleUtilities) + public TypeHintCommandFactory(IToolInteractiveService toolInteractiveService, IAWSResourceQueryer awsResourceQueryer, IConsoleUtilities consoleUtilities, IDirectoryManager directoryManager) { _commands = new Dictionary { @@ -42,7 +43,7 @@ public TypeHintCommandFactory(IToolInteractiveService toolInteractiveService, IA { OptionSettingTypeHint.DotnetPublishAdditionalBuildArguments, new DotnetPublishArgsCommand(consoleUtilities) }, { OptionSettingTypeHint.DotnetPublishSelfContainedBuild, new DotnetPublishSelfContainedBuildCommand(consoleUtilities) }, { OptionSettingTypeHint.DotnetPublishBuildConfiguration, new DotnetPublishBuildConfigurationCommand(consoleUtilities) }, - { OptionSettingTypeHint.DockerExecutionDirectory, new DockerExecutionDirectoryCommand(consoleUtilities) }, + { OptionSettingTypeHint.DockerExecutionDirectory, new DockerExecutionDirectoryCommand(consoleUtilities, directoryManager) }, { OptionSettingTypeHint.DockerBuildArgs, new DockerBuildArgsCommand(consoleUtilities) }, { OptionSettingTypeHint.ECSCluster, new ECSClusterCommand(awsResourceQueryer, consoleUtilities) }, { OptionSettingTypeHint.ExistingApplicationLoadBalancer, new ExistingApplicationLoadBalancerCommand(awsResourceQueryer, consoleUtilities) }, diff --git a/src/AWS.Deploy.CLI/ConsoleUtilities.cs b/src/AWS.Deploy.CLI/ConsoleUtilities.cs index 57013da44..97476268e 100644 --- a/src/AWS.Deploy.CLI/ConsoleUtilities.cs +++ b/src/AWS.Deploy.CLI/ConsoleUtilities.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using AWS.Deploy.Common; +using AWS.Deploy.Common.IO; namespace AWS.Deploy.CLI { @@ -35,10 +36,12 @@ T AskUserToChoose(IList options, string title, T defaultValue, string? def public class ConsoleUtilities : IConsoleUtilities { private readonly IToolInteractiveService _interactiveService; + private readonly IDirectoryManager _directoryManager; - public ConsoleUtilities(IToolInteractiveService interactiveService) + public ConsoleUtilities(IToolInteractiveService interactiveService, IDirectoryManager directoryManager) { _interactiveService = interactiveService; + _directoryManager = directoryManager; } public Recommendation AskToChooseRecommendation(IList recommendations) @@ -311,7 +314,7 @@ public string AskForEC2KeyPairSaveDirectory(string projectPath) { var keyPairDirectory = _interactiveService.ReadLine(); if (keyPairDirectory != null && - Directory.Exists(keyPairDirectory)) + _directoryManager.Exists(keyPairDirectory)) { var projectFolder = new FileInfo(projectPath).Directory; var keyPairDirectoryInfo = new DirectoryInfo(keyPairDirectory); diff --git a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs index 577135d0b..4be9fd617 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs @@ -515,7 +515,9 @@ private Orchestrator CreateOrchestrator(SessionState state, IServiceProvider? se serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), - new DockerEngine.DockerEngine(session.ProjectDefinition), + new DockerEngine.DockerEngine( + session.ProjectDefinition, + serviceProvider.GetRequiredService()), serviceProvider.GetRequiredService(), new List { RecipeLocator.FindRecipeDefinitionsPath() }, serviceProvider.GetRequiredService() diff --git a/src/AWS.Deploy.CLI/Utilities/AssumeRoleMfaTokenCodeCallback.cs b/src/AWS.Deploy.CLI/Utilities/AssumeRoleMfaTokenCodeCallback.cs index 274c8ea4e..a97677bb8 100644 --- a/src/AWS.Deploy.CLI/Utilities/AssumeRoleMfaTokenCodeCallback.cs +++ b/src/AWS.Deploy.CLI/Utilities/AssumeRoleMfaTokenCodeCallback.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Text; using Amazon.Runtime; +using AWS.Deploy.Common.IO; namespace AWS.Deploy.CLI.Utilities { @@ -15,18 +16,20 @@ internal class AssumeRoleMfaTokenCodeCallback { private readonly AssumeRoleAWSCredentialsOptions _options; private readonly IToolInteractiveService _toolInteractiveService; + private readonly IDirectoryManager _directoryManager; - internal AssumeRoleMfaTokenCodeCallback(IToolInteractiveService toolInteractiveService, AssumeRoleAWSCredentialsOptions options) + internal AssumeRoleMfaTokenCodeCallback(IToolInteractiveService toolInteractiveService, IDirectoryManager directoryManager, AssumeRoleAWSCredentialsOptions options) { _toolInteractiveService = toolInteractiveService; _options = options; + _directoryManager = directoryManager; } internal string Execute() { _toolInteractiveService.WriteLine(); _toolInteractiveService.WriteLine($"Enter MFA code for {_options.MfaSerialNumber}: "); - var consoleUtilites = new ConsoleUtilities(_toolInteractiveService); + var consoleUtilites = new ConsoleUtilities(_toolInteractiveService, _directoryManager); var code = consoleUtilites.ReadSecretFromConsole(); return code; diff --git a/src/AWS.Deploy.Common/IO/DirectoryManager.cs b/src/AWS.Deploy.Common/IO/DirectoryManager.cs index be82af6c3..7228d7028 100644 --- a/src/AWS.Deploy.Common/IO/DirectoryManager.cs +++ b/src/AWS.Deploy.Common/IO/DirectoryManager.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using AWS.Deploy.Common.Utilities; namespace AWS.Deploy.Common.IO { @@ -35,7 +36,7 @@ public class DirectoryManager : IDirectoryManager public DirectoryInfo GetDirectoryInfo(string path) => new DirectoryInfo(path); - public bool Exists(string path) => Directory.Exists(path); + public bool Exists(string path) => IsDirectoryValid(path); public string[] GetFiles(string path, string? searchPattern = null, SearchOption searchOption = SearchOption.TopDirectoryOnly) => Directory.GetFiles(path, searchPattern ?? "*", searchOption); @@ -61,5 +62,16 @@ public string[] GetProjFiles(string path) { return Directory.GetFiles(path).Where(filePath => _projFileExtensions.Contains(Path.GetExtension(filePath).ToLower())).ToArray(); } + + private bool IsDirectoryValid(string directoryPath) + { + if (!PathUtilities.IsPathValid(directoryPath)) + return false; + + if (!Directory.Exists(directoryPath)) + return false; + + return true; + } } } diff --git a/src/AWS.Deploy.Common/IO/FileManager.cs b/src/AWS.Deploy.Common/IO/FileManager.cs index 4689721e2..5bfd88486 100644 --- a/src/AWS.Deploy.Common/IO/FileManager.cs +++ b/src/AWS.Deploy.Common/IO/FileManager.cs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using AWS.Deploy.Common.Utilities; namespace AWS.Deploy.Common.IO { @@ -20,7 +22,7 @@ public interface IFileManager /// public class FileManager : IFileManager { - public bool Exists(string path) => File.Exists(path); + public bool Exists(string path) => IsFileValid(path); public Task ReadAllTextAsync(string path) => File.ReadAllTextAsync(path); @@ -28,5 +30,16 @@ public class FileManager : IFileManager public Task WriteAllTextAsync(string filePath, string contents, CancellationToken cancellationToken) => File.WriteAllTextAsync(filePath, contents, cancellationToken); + + private bool IsFileValid(string filePath) + { + if (!PathUtilities.IsPathValid(filePath)) + return false; + + if (!File.Exists(filePath)) + return false; + + return true; + } } } diff --git a/src/AWS.Deploy.Common/Utilities/PathUtilities.cs b/src/AWS.Deploy.Common/Utilities/PathUtilities.cs new file mode 100644 index 000000000..6804200be --- /dev/null +++ b/src/AWS.Deploy.Common/Utilities/PathUtilities.cs @@ -0,0 +1,30 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.IO; +using System.Linq; + +namespace AWS.Deploy.Common.Utilities +{ + public class PathUtilities + { + public static bool IsPathValid(string path) + { + path = path.Trim(); + + if (string.IsNullOrEmpty(path)) + return false; + + if (path.StartsWith(@"\\")) + return false; + + if (path.Contains("&")) + return false; + + if (Path.GetInvalidPathChars().Any(x => path.Contains(x))) + return false; + + return true; + } + } +} diff --git a/src/AWS.Deploy.DockerEngine/DockerEngine.cs b/src/AWS.Deploy.DockerEngine/DockerEngine.cs index c591d3531..140478a28 100644 --- a/src/AWS.Deploy.DockerEngine/DockerEngine.cs +++ b/src/AWS.Deploy.DockerEngine/DockerEngine.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using AWS.Deploy.Common; +using AWS.Deploy.Common.IO; using Newtonsoft.Json; namespace AWS.Deploy.DockerEngine @@ -31,9 +32,10 @@ public interface IDockerEngine public class DockerEngine : IDockerEngine { private readonly ProjectDefinition _project; + private readonly IFileManager _fileManager; private readonly string _projectPath; - public DockerEngine(ProjectDefinition project) + public DockerEngine(ProjectDefinition project, IFileManager fileManager) { if (project == null) { @@ -42,6 +44,7 @@ public DockerEngine(ProjectDefinition project) _project = project; _projectPath = project.ProjectPath; + _fileManager = fileManager; } /// @@ -146,7 +149,7 @@ public void DetermineDockerExecutionDirectory(Recommendation recommendation) { var projectFilename = Path.GetFileName(recommendation.ProjectPath); var dockerFilePath = Path.Combine(Path.GetDirectoryName(recommendation.ProjectPath) ?? "", "Dockerfile"); - if (File.Exists(dockerFilePath)) + if (_fileManager.Exists(dockerFilePath)) { using (var stream = File.OpenRead(dockerFilePath)) using (var reader = new StreamReader(stream)) diff --git a/src/AWS.Deploy.Orchestration/Orchestrator.cs b/src/AWS.Deploy.Orchestration/Orchestrator.cs index 63d5da0aa..fbc7b71dd 100644 --- a/src/AWS.Deploy.Orchestration/Orchestrator.cs +++ b/src/AWS.Deploy.Orchestration/Orchestrator.cs @@ -106,7 +106,9 @@ public async Task> GenerateRecommendationsFromSavedDeployme { if (_session == null) throw new InvalidOperationException($"{nameof(_session)} is null as part of the orchestartor object"); - if (!Directory.Exists(deploymentProjectPath)) + if (_directoryManager == null) + throw new InvalidOperationException($"{nameof(_directoryManager)} is null as part of the orchestartor object"); + if (!_directoryManager.Exists(deploymentProjectPath)) throw new InvalidCliArgumentException($"The path '{deploymentProjectPath}' does not exists on the file system. Please provide a valid deployment project path and try again."); var engine = new RecommendationEngine.RecommendationEngine(new List { deploymentProjectPath }, _session); diff --git a/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs b/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs index 836f313d2..79ec963e6 100644 --- a/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs +++ b/src/AWS.Deploy.Orchestration/Utilities/ZipFileManager.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using AWS.Deploy.Common.IO; namespace AWS.Deploy.Orchestration.Utilities { @@ -19,10 +20,12 @@ public interface IZipFileManager public class ZipFileManager : IZipFileManager { private readonly ICommandLineWrapper _commandLineWrapper; + private readonly IFileManager _fileManager; - public ZipFileManager(ICommandLineWrapper commandLineWrapper) + public ZipFileManager(ICommandLineWrapper commandLineWrapper, IFileManager fileManager) { _commandLineWrapper = commandLineWrapper; + _fileManager = fileManager; } public async Task CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName) @@ -94,7 +97,7 @@ private IDictionary GetFilesToIncludeInArchive(string publishLoc /// The full path to the command if found otherwise it will return null private string? FindExecutableInPath(string command) { - if (File.Exists(command)) + if (_fileManager.Exists(command)) return Path.GetFullPath(command); Func quoteRemover = x => @@ -112,7 +115,7 @@ private IDictionary GetFilesToIncludeInArchive(string publishLoc try { var fullPath = Path.Combine(quoteRemover(path), command); - if (File.Exists(fullPath)) + if (_fileManager.Exists(fullPath)) return fullPath; } catch (Exception) @@ -121,7 +124,7 @@ private IDictionary GetFilesToIncludeInArchive(string publishLoc } } - if (KNOWN_LOCATIONS.ContainsKey(command) && File.Exists(KNOWN_LOCATIONS[command])) + if (KNOWN_LOCATIONS.ContainsKey(command) && _fileManager.Exists(KNOWN_LOCATIONS[command])) return KNOWN_LOCATIONS[command]; return null; diff --git a/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs b/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs index cfffcab38..76832b2ab 100644 --- a/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/ConsoleUtilitiesTests.cs @@ -9,11 +9,20 @@ using Amazon.Runtime; using AWS.Deploy.CLI.Utilities; using System; +using AWS.Deploy.Common.IO; +using AWS.Deploy.CLI.Common.UnitTests.IO; namespace AWS.Deploy.CLI.UnitTests { public class ConsoleUtilitiesTests { + private readonly IDirectoryManager _directoryManager; + + public ConsoleUtilitiesTests() + { + _directoryManager = new TestDirectoryManager(); + } + private readonly List _options = new List { new() @@ -36,7 +45,7 @@ public void AskUserToChooseOrCreateNew() "3", "CustomNewIdentifier" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var userInputConfiguration = new UserInputConfiguration( option => option.DisplayName, option => option.Identifier.Equals("Identifier2"), @@ -67,7 +76,7 @@ public void AskUserToChooseOrCreateNewPickExisting() { "1" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var userInputConfiguration = new UserInputConfiguration( option => option.DisplayName, option => option.Identifier.Equals("Identifier2"), @@ -94,7 +103,7 @@ public void AskUserToChooseOrCreateNewPickExisting() public void AskUserToChooseStringsPickDefault() { var interactiveServices = new TestToolInteractiveServiceImpl(new List { "" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var selectedValue = consoleUtilities.AskUserToChoose(new List { "Option1", "Option2" }, "Title", "Option2"); Assert.Equal("Option2", selectedValue); @@ -111,7 +120,7 @@ public void AskUserToChooseStringsPickDefault() public void AskUserToChooseStringsPicksNoDefault() { var interactiveServices = new TestToolInteractiveServiceImpl(new List { "1" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var selectedValue = consoleUtilities.AskUserToChoose(new List { "Option1", "Option2" }, "Title", "Option2"); Assert.Equal("Option1", selectedValue); } @@ -120,7 +129,7 @@ public void AskUserToChooseStringsPicksNoDefault() public void AskUserToChooseStringsFirstSelectInvalid() { var interactiveServices = new TestToolInteractiveServiceImpl(new List { "a", "10", "1" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var selectedValue = consoleUtilities.AskUserToChoose(new List { "Option1", "Option2" }, "Title", "Option2"); Assert.Equal("Option1", selectedValue); } @@ -129,7 +138,7 @@ public void AskUserToChooseStringsFirstSelectInvalid() public void AskUserToChooseStringsNoTitle() { var interactiveServices = new TestToolInteractiveServiceImpl(new List { "a", "10", "1" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var selectedValue = consoleUtilities.AskUserToChoose(new List { "Option1", "Option2" }, null, "Option2"); Assert.Equal("Option1", selectedValue); @@ -140,7 +149,7 @@ public void AskUserToChooseStringsNoTitle() public void AskUserForValueCanBeSetToEmptyString() { var interactiveServices = new TestToolInteractiveServiceImpl(new List { "" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var selectedValue = consoleUtilities.AskUserForValue( @@ -155,7 +164,7 @@ public void AskUserForValueCanBeSetToEmptyString() public void AskUserForValueCanBeSetToEmptyStringNoDefault() { var interactiveServices = new TestToolInteractiveServiceImpl(new List { "" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var selectedValue = consoleUtilities.AskUserForValue( @@ -170,7 +179,7 @@ public void AskUserForValueCanBeSetToEmptyStringNoDefault() public void AskYesNoPickDefault() { var interactiveServices = new TestToolInteractiveServiceImpl(new List { string.Empty }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var selectedValue = consoleUtilities.AskYesNoQuestion("Do you want to deploy", YesNo.Yes); Assert.Equal(YesNo.Yes, selectedValue); @@ -181,7 +190,7 @@ public void AskYesNoPickDefault() public void AskYesNoPickNonDefault() { var interactiveServices = new TestToolInteractiveServiceImpl(new List { "n" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var selectedValue = consoleUtilities.AskYesNoQuestion("Do you want to deploy", YesNo.Yes); Assert.Equal(YesNo.No, selectedValue); } @@ -190,7 +199,7 @@ public void AskYesNoPickNonDefault() public void AskYesNoPickNoDefault() { var interactiveServices = new TestToolInteractiveServiceImpl(new List { "n" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var selectedValue = consoleUtilities.AskYesNoQuestion("Do you want to deploy"); Assert.Equal(YesNo.No, selectedValue); @@ -201,7 +210,7 @@ public void AskYesNoPickNoDefault() public void AskYesNoPickInvalidChoice() { var interactiveServices = new TestToolInteractiveServiceImpl(new List { "q", "n" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var selectedValue = consoleUtilities.AskYesNoQuestion("Do you want to deploy", YesNo.Yes); Assert.Equal(YesNo.No, selectedValue); @@ -212,7 +221,7 @@ public void AskYesNoPickInvalidChoice() public void DisplayRow() { var interactiveServices = new TestToolInteractiveServiceImpl(); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); consoleUtilities.DisplayRow(new[] { ("Hello", 10), ("World", 20) }); Assert.Equal("Hello | World ", interactiveServices.OutputMessages[0]); @@ -230,7 +239,7 @@ public void GetMFACode() var interactiveServices = new TestToolInteractiveServiceImpl(); interactiveServices.QueueConsoleInfos(ConsoleKey.A, ConsoleKey.B, ConsoleKey.C, ConsoleKey.Enter); - var callback = new AssumeRoleMfaTokenCodeCallback(interactiveServices, options); + var callback = new AssumeRoleMfaTokenCodeCallback(interactiveServices, _directoryManager, options); var code = callback.Execute(); Assert.Equal("ABC", code); @@ -254,7 +263,7 @@ public void GetMFACodeWithBackspace() var interactiveServices = new TestToolInteractiveServiceImpl(); interactiveServices.QueueConsoleInfos(ConsoleKey.A, ConsoleKey.B, ConsoleKey.C, ConsoleKey.Backspace, ConsoleKey.D, ConsoleKey.Enter); - var callback = new AssumeRoleMfaTokenCodeCallback(interactiveServices, options); + var callback = new AssumeRoleMfaTokenCodeCallback(interactiveServices, _directoryManager, options); var code = callback.Execute(); Assert.Equal("ABD", code); diff --git a/test/AWS.Deploy.CLI.UnitTests/DockerTests.cs b/test/AWS.Deploy.CLI.UnitTests/DockerTests.cs index 1c5205198..12dd1abe5 100644 --- a/test/AWS.Deploy.CLI.UnitTests/DockerTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/DockerTests.cs @@ -27,9 +27,11 @@ public async Task DockerGenerate(string topLevelFolder, string projectName) { var projectPath = ResolvePath(Path.Combine(topLevelFolder, projectName)); - var project = await new ProjectDefinitionParser(new FileManager(), new DirectoryManager()).Parse(projectPath); + var fileManager = new FileManager(); - var engine = new DockerEngine.DockerEngine(project); + var project = await new ProjectDefinitionParser(fileManager, new DirectoryManager()).Parse(projectPath); + + var engine = new DockerEngine.DockerEngine(project, fileManager); engine.GenerateDockerFile(); diff --git a/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.cs b/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.cs index 086659e94..71293599d 100644 --- a/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.cs +++ b/test/AWS.Deploy.CLI.UnitTests/RecommendationTests.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Amazon.Runtime; +using AWS.Deploy.CLI.Common.UnitTests.IO; using AWS.Deploy.CLI.TypeHintResponses; using AWS.Deploy.CLI.UnitTests.Utilities; using AWS.Deploy.Common; @@ -22,6 +23,13 @@ namespace AWS.Deploy.CLI.UnitTests public class RecommendationTests { private OrchestratorSession _session; + private readonly IDirectoryManager _directoryManager; + + public RecommendationTests() + { + _directoryManager = new TestDirectoryManager(); + } + private async Task BuildRecommendationEngine(string testProjectName) { var fullPath = SystemIOUtilities.ResolvePath(testProjectName); @@ -128,7 +136,7 @@ public async Task ResetOptionSettingValue_Int() "" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var engine = await BuildRecommendationEngine("WebAppNoDockerFile"); @@ -155,7 +163,7 @@ public async Task ResetOptionSettingValue_String() { "" }); - var consoleUtilities = new ConsoleUtilities(interactiveServices); + var consoleUtilities = new ConsoleUtilities(interactiveServices, _directoryManager); var engine = await BuildRecommendationEngine("WebAppNoDockerFile"); From 4f9863624d4f1b5986e1330447a67e05a12f4960 Mon Sep 17 00:00:00 2001 From: Malhar Khimsaria Date: Tue, 31 Aug 2021 16:25:47 -0400 Subject: [PATCH 5/5] fix: Fixes the re-deployment experience using the ECS Fargate recipe --- src/AWS.Deploy.Recipes.CDK.Common/CDKRecipeSetup.cs | 9 ++++++++- .../Configurations/AutoScalingConfiguration.cs | 2 +- .../WebAppWithDockerFileTests.cs | 11 +++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/AWS.Deploy.Recipes.CDK.Common/CDKRecipeSetup.cs b/src/AWS.Deploy.Recipes.CDK.Common/CDKRecipeSetup.cs index 06c5e2d3d..6d85bffb4 100644 --- a/src/AWS.Deploy.Recipes.CDK.Common/CDKRecipeSetup.cs +++ b/src/AWS.Deploy.Recipes.CDK.Common/CDKRecipeSetup.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration; using Amazon.CDK; +using System.Text.Json.Serialization; namespace AWS.Deploy.Recipes.CDK.Common { @@ -27,7 +28,13 @@ public static void RegisterStack(Stack stack, IRecipeProps recipeConfigura stack.Tags.SetTag(Constants.CloudFormationIdentifier.STACK_TAG, $"{recipeConfiguration.RecipeId}"); // Serializes all AWS .NET deployment tool settings. - var json = JsonSerializer.Serialize(recipeConfiguration.Settings, new JsonSerializerOptions { WriteIndented = false }); + var json = JsonSerializer.Serialize( + recipeConfiguration.Settings, + new JsonSerializerOptions + { + WriteIndented = false, + Converters = { new JsonStringEnumConverter() } + }); Dictionary metadata; if(stack.TemplateOptions.Metadata?.Count > 0) diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Generated/Configurations/AutoScalingConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Generated/Configurations/AutoScalingConfiguration.cs index fef28be43..d1b887216 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Generated/Configurations/AutoScalingConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/AspNetAppEcsFargate/Generated/Configurations/AutoScalingConfiguration.cs @@ -19,7 +19,7 @@ public class AutoScalingConfiguration public enum ScalingTypeEnum { Cpu, Memory, Request } - public ScalingTypeEnum ScalingType { get; set; } = ScalingTypeEnum.Cpu; + public ScalingTypeEnum? ScalingType { get; set; } diff --git a/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs index cf98e46c6..016ff9de1 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/WebAppWithDockerFileTests.cs @@ -102,6 +102,17 @@ public async Task DefaultConfigurations() var listDeployStdOut = _interactiveService.StdOutReader.ReadAllLines(); Assert.Contains(listDeployStdOut, (deployment) => _stackName.Equals(deployment)); + // Arrange input for re-deployment + await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Select default option settings + await _interactiveService.StdInWriter.FlushAsync(); + + // Perform re-deployment + deployArgs = new[] { "deploy", "--project-path", projectPath, "--stack-name", _stackName, "--diagnostics" }; + var returnCode = await _app.Run(deployArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, returnCode); + Assert.Equal(StackStatus.UPDATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); + Assert.Equal("ACTIVE", cluster.Status); + // Arrange input for delete await _interactiveService.StdInWriter.WriteAsync("y"); // Confirm delete await _interactiveService.StdInWriter.FlushAsync();