Skip to content

Commit

Permalink
New Open Config commands & bug fixes (#20)
Browse files Browse the repository at this point in the history
- Fixes bug when no default credentials are found in AWS file
- Adds an open config command (app and AWS)
  • Loading branch information
vgmello authored Dec 5, 2023
1 parent 8014d5a commit 2ed8758
Show file tree
Hide file tree
Showing 14 changed files with 167 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ csharp_style_unused_value_expression_statement_preference = discard_variable:sil
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true:warning
csharp_style_var_when_type_is_apparent = true:error
csharp_style_prefer_primary_constructors = false:suggestion
csharp_style_prefer_primary_constructors = false:hint

dotnet_style_allow_multiple_blank_lines_experimental = true:silent
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
Expand Down
2 changes: 2 additions & 0 deletions Ellosoft.AwsCredentialsManager.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantNullableFlowAttribute/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantTypeDeclarationBody/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ellosoft/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArgumentsStyleOther/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/CleanupOnSave/@EntryValue">True</s:Boolean>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2023 Ellosoft Limited. All rights reserved.

namespace Ellosoft.AwsCredentialsManager.Commands.Config;

[Name("config")]
[Description("Open config file")]
public class ConfigBranch
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) 2023 Ellosoft Limited. All rights reserved.

using Ellosoft.AwsCredentialsManager.Services;
using Ellosoft.AwsCredentialsManager.Services.IO;

namespace Ellosoft.AwsCredentialsManager.Commands.Config;

[Name("aws")]
[Description("Open AWS credentials file")]
[Examples("aws")]
public class OpenAwsConfig(IFileManager fileManager) : Command
{
public override int Execute(CommandContext context)
{
var awsCredentialsPath = Path.Combine(AppDataDirectory.UserProfileDirectory, ".aws", "credentials");

if (!File.Exists(awsCredentialsPath))
throw new CommandException($"The file {awsCredentialsPath} does not exist");

fileManager.OpenFile(awsCredentialsPath);

return 0;
}
}
22 changes: 22 additions & 0 deletions src/Ellosoft.AwsCredentialsManager/Commands/Config/OpenConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2023 Ellosoft Limited. All rights reserved.

using Ellosoft.AwsCredentialsManager.Services.Configuration;
using Ellosoft.AwsCredentialsManager.Services.IO;

namespace Ellosoft.AwsCredentialsManager.Commands.Config;

[Name("user")]
[Description("Open credentials manager user config file (default)")]
[Examples("user")]
public class OpenConfig(IConfigManager configManager, IFileManager fileManager) : Command
{
public override int Execute(CommandContext context)
{
if (!File.Exists(configManager.AppConfigPath))
configManager.SaveConfig();

fileManager.OpenFile(configManager.AppConfigPath);

return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,12 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
var oktaAppUrl = settings.OktaAppUrl ?? await GetAwsAppUrl(settings.OktaUserProfile);
var awsRole = settings.AwsRoleArn ?? await GetAwsRoleArn(settings.OktaUserProfile, oktaAppUrl);

_credentialsManager.CreateCredential(settings.Name, settings.AwsProfile!, awsRole, oktaAppUrl, settings.OktaUserProfile);
_credentialsManager.CreateCredential(
name: settings.Name,
awsProfile: settings.AwsProfile!,
awsRole: awsRole,
oktaAppUrl: oktaAppUrl,
oktaProfile: settings.OktaUserProfile);

AnsiConsole.MarkupLine($"[bold green]'{settings.Name}' credentials created[/]");

Expand Down
35 changes: 9 additions & 26 deletions src/Ellosoft.AwsCredentialsManager/Program.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
// Copyright (c) 2023 Ellosoft Limited. All rights reserved.

using System.Diagnostics;
using Ellosoft.AwsCredentialsManager;
using Ellosoft.AwsCredentialsManager.Commands;
using Ellosoft.AwsCredentialsManager.Commands.Config;
using Ellosoft.AwsCredentialsManager.Commands.Credentials;
using Ellosoft.AwsCredentialsManager.Commands.Okta;
using Ellosoft.AwsCredentialsManager.Commands.RDS;
using Ellosoft.AwsCredentialsManager.Infrastructure.Cli;
using Ellosoft.AwsCredentialsManager.Infrastructure.Logging;
using Ellosoft.AwsCredentialsManager.Infrastructure.Upgrade;
using Ellosoft.AwsCredentialsManager.Services.AWS;
using Ellosoft.AwsCredentialsManager.Services.AWS.Interactive;
using Ellosoft.AwsCredentialsManager.Services.Configuration;
using Ellosoft.AwsCredentialsManager.Services.Configuration.Interactive;
using Ellosoft.AwsCredentialsManager.Services.Okta;
using Ellosoft.AwsCredentialsManager.Services.Okta.Interactive;
using Microsoft.Extensions.DependencyInjection;
using Serilog.Events;

Expand All @@ -24,26 +20,7 @@

var services = new ServiceCollection()
.SetupLogging(logger)
.AddSingleton<IConfigManager, ConfigManager>()
.AddSingleton<CredentialsManager>()
.AddSingleton<EnvironmentManager>();

// okta related services
services
.AddSingleton<OktaClassicAuthenticator>()
.AddSingleton<OktaClassicAccessTokenProvider>()
.AddSingleton<IOktaLoginService, OktaLoginService>()
.AddSingleton<IOktaMfaFactorSelector, OktaMfaFactorSelector>()
.AddSingleton<AwsOktaSessionManager>()
.AddSingleton<OktaSamlService>();

// aws related services
services
.AddSingleton<RdsTokenGenerator>()
.AddSingleton<AwsSamlService>();

services
.AddKeyedSingleton(nameof(OktaHttpClientFactory), OktaHttpClientFactory.CreateHttpClient());
.RegisterAppServices();

var registrar = new TypeRegistrar(services);
var app = new CommandApp(registrar);
Expand All @@ -68,6 +45,12 @@
{
rds.AddCommand<GetRdsPassword>();
rds.AddCommand<ListRdsProfiles>();
})
.AddBranch<ConfigBranch>(cfg =>
{
cfg.SetDefaultCommand<OpenConfig>();
cfg.AddCommand<OpenConfig>();
cfg.AddCommand<OpenAwsConfig>();
});

config.PropagateExceptions();
Expand Down
43 changes: 43 additions & 0 deletions src/Ellosoft.AwsCredentialsManager/ServiceRegistration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) 2023 Ellosoft Limited. All rights reserved.

using Ellosoft.AwsCredentialsManager.Services.AWS;
using Ellosoft.AwsCredentialsManager.Services.AWS.Interactive;
using Ellosoft.AwsCredentialsManager.Services.Configuration;
using Ellosoft.AwsCredentialsManager.Services.Configuration.Interactive;
using Ellosoft.AwsCredentialsManager.Services.IO;
using Ellosoft.AwsCredentialsManager.Services.Okta;
using Ellosoft.AwsCredentialsManager.Services.Okta.Interactive;
using Microsoft.Extensions.DependencyInjection;

namespace Ellosoft.AwsCredentialsManager;

public static class ServiceRegistration
{
public static IServiceCollection RegisterAppServices(this IServiceCollection services)
{
services
.AddSingleton<IFileManager, FileManager>()
.AddSingleton<IConfigManager, ConfigManager>()
.AddSingleton<CredentialsManager>()
.AddSingleton<EnvironmentManager>();

// okta related services
services
.AddSingleton<OktaClassicAuthenticator>()
.AddSingleton<OktaClassicAccessTokenProvider>()
.AddSingleton<IOktaLoginService, OktaLoginService>()
.AddSingleton<IOktaMfaFactorSelector, OktaMfaFactorSelector>()
.AddSingleton<AwsOktaSessionManager>()
.AddSingleton<OktaSamlService>();

// aws related services
services
.AddSingleton<RdsTokenGenerator>()
.AddSingleton<AwsSamlService>();

services
.AddKeyedSingleton(nameof(OktaHttpClientFactory), OktaHttpClientFactory.CreateHttpClient());

return services;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ internal sealed record ProfileMetadata(string RoleArn, string AccessKey, DateTim
/// Thrown when the SAML authentication assertion fails to meet the requirements
/// or AWS STS is unable to assume the specified role due to invalid parameters.
/// </exception>
/// <remarks>
/// The AssumeRoleWithSAMLAsync issues an HTTP POST request to https://sts.amazonaws.com, which does not require a region,
/// however the region is still required as part of the AmazonSecurityTokenServiceClient constructor validation, therefore USEast2 is being used.
/// </remarks>
public async Task<AwsCredentialsData> GetAwsCredentials(
string samlAssertion,
string roleArn,
string idp,
int expirationInMinutes = 120)
{
using var stsClient = new AmazonSecurityTokenServiceClient(new AnonymousAWSCredentials(), (RegionEndpoint?)null);
using var stsClient = new AmazonSecurityTokenServiceClient(new AnonymousAWSCredentials(), RegionEndpoint.USEast2);

var request = new AssumeRoleWithSAMLRequest
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static class AppDataDirectory
/// </summary>
/// <param name="fileName">The name of the file to get the path for.</param>
/// <returns>The full path to the specified file in the application's data directory.</returns>
public static string GetPath(string fileName) => IOPath.Combine(GetOrCreateAppDataDirectory(), fileName);
public static string GetPath(string fileName) => IOPath.Combine(Path, fileName);

private static string GetOrCreateAppDataDirectory()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,24 @@ public interface IConfigManager
{
AppConfig AppConfig { get; }

string AppConfigPath { get; }

/// <summary>
/// Persist the <see cref="AppConfig" /> state into a file
/// Persist the <see cref="AppConfig" /> state into a file, an empty file will be create if this method is called on a "empty" state
/// </summary>
void SaveConfig();
}

public class ConfigManager : IConfigManager
{
private const string APP_CONFIG_FILE = "aws_cred_mgr.yml";

private static readonly string AppConfigPath = Path.Combine(AppDataDirectory.UserProfileDirectory, APP_CONFIG_FILE);
private static readonly string InternalAppConfigPath = Path.Combine(AppDataDirectory.UserProfileDirectory, APP_CONFIG_FILE);

private readonly ConfigReader _configReader = new();
private readonly ConfigWriter _configWriter = new();

public string AppConfigPath => InternalAppConfigPath;

public ConfigManager() => AppConfig = GetConfiguration();

public AppConfig AppConfig { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,5 +164,5 @@ private static IDeserializer CreateDeserializer() =>
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.Build();

public static string GetYamlName(string value) => UnderscoredNamingConvention.Instance.Apply(value);
private static string GetYamlName(string value) => UnderscoredNamingConvention.Instance.Apply(value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,11 @@

namespace Ellosoft.AwsCredentialsManager.Services.Configuration.Interactive;

public class CredentialsManager
public class CredentialsManager(IConfigManager configManager)
{
private readonly IConfigManager _configManager;

public CredentialsManager(IConfigManager configManager) => _configManager = configManager;

public string GetCredential()
{
var appConfig = _configManager.AppConfig;
var appConfig = configManager.AppConfig;

if (appConfig.Credentials.Count == 0)
throw new CommandException("No AWS credentials found, please use [green]'aws-cred-mgr cred new'[/] to create a new profile");
Expand All @@ -34,7 +30,7 @@ public string GetCredential()

public bool TryGetCredential(string credentialProfile, [NotNullWhen(true)] out CredentialsConfiguration? credentialsConfig)
{
if (_configManager.AppConfig.Credentials.TryGetValue(credentialProfile, out credentialsConfig))
if (configManager.AppConfig.Credentials.TryGetValue(credentialProfile, out credentialsConfig))
{
if (credentialsConfig is { OktaProfile: not null, OktaAppUrl: not null })
return true;
Expand All @@ -57,7 +53,7 @@ public void CreateCredential(string name, string awsProfile, string awsRole, str
OktaProfile = oktaProfile
};

_configManager.AppConfig.Credentials[name] = credential;
_configManager.SaveConfig();
configManager.AppConfig.Credentials[name] = credential;
configManager.SaveConfig();
}
}
33 changes: 33 additions & 0 deletions src/Ellosoft.AwsCredentialsManager/Services/IO/FileManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2023 Ellosoft Limited. All rights reserved.

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Ellosoft.AwsCredentialsManager.Services.IO;

public interface IFileManager
{
void OpenFile(string filePath);
}

public class FileManager : IFileManager
{
public void OpenFile(string filePath)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Process.Start("explorer", $"\"{filePath}\"");

return;
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", $"\"{filePath}\"");

return;
}

throw new InvalidOperationException("Unsupported operating system.");
}
}

0 comments on commit 2ed8758

Please sign in to comment.