Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Open Config commands & bug fixes #20

Merged
merged 1 commit into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.");
}
}