using System;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Amazon;
using Amazon.SecretsManager;
using Amazon.SecretsManager.Model;
using Serilog;
// Possible AWS Regions
// https://docs.aws.amazon.com/general/latest/gr/rande.html
namespace CoinbaseEngine
{
public class AWSSecretKeys
{
public string ApiKey { get; set; }
public string ApiSecret { get; set; }
public string ApiPassPhrase { get; set; }
}
public sealed partial class AWSSecretManager
{
private static readonly Lazy<AWSSecretManager> lazy = new Lazy<AWSSecretManager>(() => new AWSSecretManager());
public static AWSSecretManager Instance { get { return lazy.Value; } }
private static readonly string AwsAccessKeyId = CoinbaseConfig.AwsAccessKeyId;
private static readonly string AwsSecretAccessKey = CoinbaseConfig.AwsSecretAccessKey;
private static readonly string AwsRegionEndPoint = CoinbaseConfig.AwsRegionEndPoint;
private IAmazonSecretsManager client = null;
private AWSSecretManager()
{
var regionEP = RegionEndpoint.GetBySystemName(AwsRegionEndPoint);
if (string.IsNullOrEmpty(AwsAccessKeyId))
client = new AmazonSecretsManagerClient(regionEP); // AccessKeys assumed set as environment variables
else
client = new AmazonSecretsManagerClient(AwsAccessKeyId, AwsSecretAccessKey, regionEP);
if (client != null)
{
Log.Information("AmazonSecretsManagerClient instantiation OK.");
}
}
/// <summary>
/// The main method initializes the necessary values and then calls
/// the GetSecretAsync and DecodeString methods to get the decoded
/// secret value for the secret named in secretName.
/// </summary>
public async Task<string> GetSecretKey(string secretName)
{
var response = await GetSecretAsync(client, secretName);
if (response is not null)
{
var secret = DecodeString(response);
if (!string.IsNullOrEmpty(secret))
return secret;
}
return string.Empty;
}
/// <summary>
/// Retrieves the secret value given the name of the secret to
/// retrieve.
/// </summary>
/// <param name="client">The client object used to retrieve the secret
/// value for the given secret name.</param>
/// <param name="secretName">The name of the secret value to retrieve.</param>
/// <returns>The GetSecretValueReponse object returned by
/// GetSecretValueAsync.</returns>
private async Task<GetSecretValueResponse> GetSecretAsync(IAmazonSecretsManager client, string secretName)
{
GetSecretValueRequest request = new();
request.SecretId = secretName;
request.VersionStage = "AWSCURRENT"; // VersionStage defaults to AWSCURRENT if unspecified.
GetSecretValueResponse response = null;
// For the sake of simplicity, this example handles only the most
// general SecretsManager exception.
try
{
response = await client.GetSecretValueAsync(request);
}
catch (AmazonSecretsManagerException e)
{
Log.Debug($"AWSSecretManager.GetSecretAsync: {e.Message}");
}
return response;
}
/// <summary>
/// Decodes the secret returned by the call to GetSecretValueAsync and
/// returns it to the calling program.
/// </summary>
/// <param name="response">A GetSecretValueResponse object containing
/// the requested secret value returned by GetSecretValueAsync.</param>
/// <returns>A string representing the decoded secret value.</returns>
private string DecodeString(GetSecretValueResponse response)
{
try
{
// Decrypts secret using the associated AWS Key Management Service
// Customer Master Key (CMK.) Depending on whether the secret is a
// string or binary value, one of these fields will be populated.
MemoryStream memoryStream = new();
if (response.SecretString is not null)
{
var secret = response.SecretString;
return secret;
}
else if (response.SecretBinary is not null)
{
memoryStream = response.SecretBinary;
StreamReader reader = new StreamReader(memoryStream);
string decodedBinarySecret = Encoding.UTF8.GetString(Convert.FromBase64String(reader.ReadToEnd()));
return decodedBinarySecret;
}
}
catch (Exception ex)
{
Log.Debug($"AWSSecretManager.DecodeString {ex.Message}");
}
return string.Empty;
}
}
}