ASP.Net Core - EC2 to S3 file upload with Access Denied - asp.net-core

I have developed a .NET Core 3.1 Web API which allows the users to upload their documents to S3 bucket. When I deploy the API to AWS ElasticBeansTalk EC2 instance and call the endpoint which uploads the file to S3, I get an error "Access Denied".
By the way, I have created IAM policy and role to give full access to S3 from my EC2 instance. I have also copied the .aws folder which contains credentials file onto the EC2 instance.
API Controller Action
public async Task<ApiResponse> UpdateProfilePic([FromBody]UploadProfilePicRequest model)
{
using (Stream stream = model.profilePicData.Base64StringToStream(out string header))
{
var tags = new List<KeyValuePair<string, string>>();
var metaData = new List<KeyValuePair<string, string>>();
metaData.Add(new KeyValuePair<string, string>("Content-Disposition", $"attachment; filename=\"{model.filename}\""));
if (_host.IsDevelopment())
{
tags.Add(new KeyValuePair<string, string>("public", "yes"));
}
await AmazonS3Uploader.UploadFileAsync(stream, "myDir/", model.fileId, tags, metaData);
}
}
The AmazonS3Helper class shown below:
using Amazon;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace UploderApp.Services
{
public static class AmazonS3Uploader
{
private static readonly RegionEndpoint bucketRegion = RegionEndpoint.APSouth1;
private static readonly IAmazonS3 s3Client = new AmazonS3Client(GetAwsCredentials(), bucketRegion);
private static readonly string S3Bucket = "abc-test";
private static AWSCredentials GetAwsCredentials()
{
var chain = new CredentialProfileStoreChain();
if (chain.TryGetAWSCredentials("MYPROFILE", out AWSCredentials awsCredentials))
{
return awsCredentials;
}
return null;
}
public static async Task UploadFileAsync(Stream fileStream, string virtualDirectory, string keyName)
{
try
{
using (var fileTransferUtility = new TransferUtility(s3Client))
{
//Upload data from a type of System.IO.Stream.
await fileTransferUtility.UploadAsync(fileStream, S3Bucket, virtualDirectory + keyName).ConfigureAwait(true);
}
}
catch (AmazonS3Exception e)
{
throw new Exception($"Error encountered on server. Message:'{e.Message}' when writing an object");
}
}
public static async Task UploadFileAsync(Stream stream, string virtualDirectory, string keyName, List<KeyValuePair<string, string>> tags = null, List<KeyValuePair<string, string>> metadata = null)
{
try
{
// Specify advanced settings.
var fileTransferUtilityRequest = new TransferUtilityUploadRequest
{
BucketName = S3Bucket,
InputStream = stream,
StorageClass = S3StorageClass.Standard,
Key = virtualDirectory + keyName
};
if (metadata != null)
{
foreach (var item in metadata)
{
fileTransferUtilityRequest.Metadata.Add(item.Key, item.Value);
}
}
if (tags != null)
{
fileTransferUtilityRequest.TagSet = new List<Tag>();
foreach (var tag in tags)
{
fileTransferUtilityRequest.TagSet.Add(new Tag { Key = tag.Key, Value = tag.Value });
}
}
using (var fileTransferUtility = new TransferUtility(s3Client))
{
await fileTransferUtility.UploadAsync(fileTransferUtilityRequest).ConfigureAwait(true);
}
}
catch (AmazonS3Exception e)
{
throw new Exception($"Error encountered on server. Message:'{e.Message}' when writing an object");
}
}
}
}
However, if I create a console application and use the above class without any modifications, it uploads the file from the same EC2 instance.
Code from the Main function of my Console Application.
public static void Main()
{
var file = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "/Screenshot.png";
try
{
var tags = new List<KeyValuePair<string, string>>();
var metaData = new List<KeyValuePair<string, string>>();
metaData.Add(new KeyValuePair<string, string>("Content-Disposition", $"attachment; filename=\"profile-pic.png\""));
using (var stream = new FileStream(file, FileMode.Open))
{
AmazonS3Uploader.UploadFileAsync(stream, "mydir/", "screenshot.png", tags, metaData).GetAwaiter().GetResult();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
This is very strange. Can anybody help me to understand the root cause, please?
Edit:1
Output of the aws s3 ls s3://abc-test is shown below
Edit:2
Uploading the EC2 folder to S3

Related

Files not downloaded showing binay data in ASP.NET core web API

Trying to download multiplefile as zip using System.io.compression from asp.net core web api. But i got binary error text. How can i fix this? below is my code
Error:
Error from backend. {"headers":{"normalizedNames":{},"lazyUpdate":null},"status":200,"statusText":"OK","url":"http://localhost:4000/api/filegeneration/createfile/2023-02-08/1","ok":false,"name":"HttpErrorResponse","
message":"Http failure during parsing for http://localhost:4000/api/filegeneration/createfile/2023-02-08/1",
"error":{"error":{},"text":"PK\u0003\u0004\u0014\u0000\u0000\u0000\b\u0000��HV�
u0010\u0001\u000b\u0000\u0000\u0000\u0003\u0000\u0000\u0000\u001e\u0000\u0000\u0000
Fareshare_Qty_08.02.2023_0.txtz�{?\u0000\u0000\u0000��\u0003\u0000PK\u0001\u0002\u0014
\u0000\u0014\u0000\u0000\u0000\b\u0000��HV�\u0010\u0001\u000b\u0000\u0000\u0000\u0003\u0000\u0000
\u0000\u001e\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000Fareshare_Qty_08.02.2023_0.txtPK\u0005
\u0006\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000L\u0000\u0000\u0000G\u0000\u0000\u0000\u0000\u0000"}}
Controller:
using (var outstream = new MemoryStream())
{
using (var archive = new ZipArchive(outstream,ZipArchiveMode.Create,true))
{
foreach (string filePath in files)
{
var filearcive = archive.CreateEntry(Path.GetFileName(filePath),CompressionLevel.Optimal);
using (var entrystream = filearcive.Open())
{
using (var fileCompressionStream = new MemoryStream(System.IO.File.ReadAllBytes(filePath)))
{
await fileCompressionStream.CopyToAsync(entrystream);
}
}
}
}
outstream.Position = 0;
return File(outstream.ToArray(), "application/zip", "Fareshare.zip");
Below code works for me. Please check it.
public (string fileType, byte[] archiveData, string archiveName) DownloadFiles(string subDirectory)
{
var zipName = $"archive-{DateTime.Now.ToString("yyyy_MM_dd-HH_mm_ss")}.zip";
List<FileInfo> files = new DirectoryInfo(Path.Combine(_hostingEnvironment.ContentRootPath, subDirectory)).GetFiles().ToList();
using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
files.ForEach(file =>
{
var theFile = archive.CreateEntry(file.Name);
using (var streamWriter = new StreamWriter(theFile.Open()))
{
streamWriter.Write(File.ReadAllText(file.FullName));
}
});
}
return ("application/zip", memoryStream.ToArray(), zipName);
}
Test Result

Azure Key Vault Operation get is not allowed on a disabled secret

We have implemented Azure key vault in the .NET core application. Everything is working fine until we disabled the secret from the list - After my application tries to fetch the list again it started giving me the exception
Unhandled exception. Microsoft.Azure.KeyVault.Models.KeyVaultErrorException: Operation get is not allowed on a disabled secret.
at Microsoft.Azure.KeyVault.KeyVaultClient.GetSecretWithHttpMessagesAsync(String vaultBaseUrl, String secretName, String secretVersion, Dictionary`2 customHeaders, CancellationToken cancellationToken)
at Microsoft.Azure.KeyVault.KeyVaultClientExtensions.GetSecretAsync(IKeyVaultClient operations, String secretIdentifier, CancellationToken cancellationToken)
at Microsoft.Extensions.Configuration.AzureKeyVault.AzureKeyVaultConfigurationProvider.LoadAsync()
at Microsoft.Extensions.Configuration.AzureKeyVault.AzureKeyVaultConfigurationProvider.Load()
at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
at Microsoft.Extensions.Hosting.HostBuilder.BuildAppConfiguration()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
at Vodafone.LandingPage.Program.Main(String[] args) in D:\a\1\s\src\LandingPage\Program.cs:line 30
Code I use to connect with Key Vault in program.cs file.
if (ctx.HostingEnvironment.IsProduction())
{
var builtConfig = builder.Build();
var keyVaultEndpoint = $"https://{builtConfig["AppSettings:KeyVaultName"]}.vault.azure.net/";
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
builder.AddAzureKeyVault(keyVaultEndpoint, keyVaultClient, new DefaultKeyVaultSecretManager());
}
How we can restrict the list so that it will not take the disabled secrets together.
I am using "Get" and "List" permission.
After a research I found below solution.
You can use it like this
Problem : Code which read all secret
builder.AddAzureKeyVault(keyVaultEndpoint, keyVaultClient, new DefaultKeyVaultSecretManager());
Solution : Code Which read only enabled secrets
builder.AddAzureKeyVault(keyVaultEndpoint,keyVaultClient,new PrefixKeyVaultSecretManager(keyVaultEndpoint));
Implementation of IKeyVaultSecretManager
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.KeyVault.Models;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureKeyVault;
namespace KeyVaultPOC
{
public class PrefixKeyVaultSecretManager : IKeyVaultSecretManager
{
private readonly IList<string> _overrides = new List<string>();
public PrefixKeyVaultSecretManager(string vaultUrl)
{
Task.Run(() => LoadListOfOverrides(vaultUrl)).Wait();
}
private async Task LoadListOfOverrides(string vaultUrl)
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)
);
var secrets = await keyVaultClient.GetSecretsAsync(vaultUrl);
bool moreSecrets;
do
{
foreach (var secret in secrets)
{
if ((bool)secret.Attributes.Enabled)
{
_overrides.Add(secret.Identifier.Name);
}
}
moreSecrets = !string.IsNullOrEmpty(secrets.NextPageLink);
if (moreSecrets)
{
secrets = await keyVaultClient.GetSecretsNextAsync(secrets.NextPageLink);
}
} while (moreSecrets);
}
public bool Load(SecretItem secret)
{
return true;
}
public string GetKey(SecretBundle secret)
{
var key = secret.SecretIdentifier.Name;
return key;
}
}
}
Ref : https://gist.github.com/davidxcheng/0576659d2c876d299619d979767dcdd6

How to download multiple files at once from S3 using C# AWS SDK

How to download multiple files from s3 buckets. I could not find any better option on SO.
Here is my code for single file download. Given list of Urls, I am looping to download multiple files.
public async Task Download(string url, Stream output)
{
var s3Uri = new AmazonS3Uri(url);
GetObjectRequest getObjectRequest = new GetObjectRequest
{
BucketName = s3Uri.Bucket,
Key = System.Net.WebUtility.UrlDecode(s3Uri.Key)
};
using (var s3Client = new AmazonS3Client(s3Uri.Region))
{
// dispose the underline stream when writing to stream is done
using (var getObjectResponse = await s3Client.GetObjectAsync(getObjectRequest).ConfigureAwait(false))
{
using (var responseStream = getObjectResponse.ResponseStream)
{
await responseStream.CopyToAsync(output);
}
}
}
output.Seek(0L, SeekOrigin.Begin);
}
Download files given s3 urls
var list = new List<Stream>();
foreach(var url in urls)
{
var stream = new MemoryStream();
await Download(url,ms);
list.Add(stream);
}
Is there any better option to download multiple files at once from S3?
I finally decided to implement my own version
public class StreamWrapper
{
public string Url { get; set; }
public Stream Content { get; set; }
public string FileName { get; set; }
}
public async Task Download(IList<StreamWrapper> inout, int maxConcurrentDownloads)
{
if (maxConcurrentDownloads <= 0)
{
maxConcurrentDownloads = 20;
}
if (!inout.HasAny())
return;
var tasks = new List<Task>();
for (int i = 0; i < inout.Count; i++)
{
StreamWrapper wrapper = inout[i];
AmazonS3Uri s3Uri = null;
if (AmazonS3Uri.TryParseAmazonS3Uri(wrapper.Url, out s3Uri))
{
tasks.Add(GetObject(s3Uri, wrapper.Content));
}
if (tasks.Count == maxConcurrentDownloads || i == inout.Count - 1)
{
await Task.WhenAll(tasks);
tasks.Clear();
}
}
}
private async Task GetObject(AmazonS3Uri s3Uri, Stream output)
{
GetObjectRequest getObjectRequest = new GetObjectRequest
{
BucketName = s3Uri.Bucket,
Key = System.Net.WebUtility.UrlDecode(s3Uri.Key)
};
using (var s3Client = new AmazonS3Client(s3Uri.Region))
{
// dispose the underline stream when writing to local file system is done
using (var getObjectResponse = await s3Client.GetObjectAsync(getObjectRequest).ConfigureAwait(false))
{
using (var responseStream = getObjectResponse.ResponseStream)
{
await responseStream.CopyToAsync(output);
}
}
}
output.Seek(0L, SeekOrigin.Begin);
}

A task was canceled Exception when trying to upload file to S3 bucket

A task was canceled exception is thrown when Im trying to call fileTransferUtility.UploadAsync to upload a file i S3. I'm using dot net core 2.0 and trying to Upload file to S3.What is that i'm doing wrong in the below code?
Is is something to do with Timeout? If so how to set time for s3 bucket? or Do I have to set some properties on S3 bucket?
Below is my controller code:
public class UploadController : Controller
{
private IHostingEnvironment _hostingEnvironment;
private AmazonS3Client _s3Client = new AmazonS3Client(RegionEndpoint.APSoutheast1);
private string _bucketName = "fileupload";//this is my Amazon Bucket name
private static string _bucketSubdirectory = String.Empty;
private string uploadWithKeyName = "testFile";
public UploadController(IHostingEnvironment environment)
{
_hostingEnvironment = environment;
}
[HttpPost("UploadExcelData")]
public async Task PostExcelData()
{
var files = Request.Form.Files;
var stringVal = Request.Form.Keys;
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filename = ContentDispositionHeaderValue
.Parse(formFile.ContentDisposition)
.FileName
.TrimStart().ToString();
filename = _hostingEnvironment.WebRootPath + $#"\uploads" + $#"\{formFile.FileName}";
size += formFile.Length;
using (var fs = System.IO.File.Create(filename))
{
formFile.CopyTo(fs);
fs.Flush();
}//these code snippets saves the uploaded files to the project directory
await UploadToS3(filename);//this is the method to upload saved file to S3
}
}
// return Ok();
}
public async Task UploadToS3(string filePath)
{
try
{
TransferUtility fileTransferUtility = new
TransferUtility(_s3Client);
string bucketName;
if (_bucketSubdirectory == "" || _bucketSubdirectory == null)
{
bucketName = _bucketName; //no subdirectory just bucket name
}
else
{ // subdirectory and bucket name
bucketName = _bucketName + #"/" + _bucketSubdirectory;
}
// 1. Upload a file, file name is used as the object key name.
await fileTransferUtility.UploadAsync(filePath, bucketName, uploadWithKeyName).ConfigureAwait(false);
Console.WriteLine("Upload 1 completed");
}
catch (AmazonS3Exception s3Exception)
{
Console.WriteLine(s3Exception.Message,
s3Exception.InnerException);
}
catch (Exception ex)
{
Console.WriteLine("Unknown error", ex.Message);
}
}
}
I forgot to pass the credentials :
private AmazonS3Client _s3Client = new AmazonS3Client(DynamoDbCRUD.Credentials.AccessKey,DynamoDbCRUD.Credentials.SecretKey, RegionEndpoint.APSoutheast1);
This line works fine.

Files uploaded but not appearing on server

I use the code stated here to upload files through a webapi http://bartwullems.blogspot.pe/2013/03/web-api-file-upload-set-filename.html. I also made the following api to list all the files I have :
[HttpPost]
[Route("sharepoint/imageBrowser/listFiles")]
[SharePointContextFilter]
public async Task<HttpResponseMessage> Read()
{
string pathImages = HttpContext.Current.Server.MapPath("~/Content/images");
DirectoryInfo d = new DirectoryInfo(pathImages);//Assuming Test is your Folder
FileInfo[] Files = d.GetFiles(); //Getting Text files
List<object> lst = new List<object>();
foreach (FileInfo f in Files)
{
lst.Add(new
{
name = f.Name,
type = "f",
size = f.Length
});
}
return Request.CreateResponse(HttpStatusCode.OK, lst);
}
When calling this api, all the files uploaded are listed. But when I go to azure I dont see any of them (Content.png is a file I manually uploaded to azure)
Why are the files listed if they dont appear on azure.
According to your description, I suggest you could firstly use azure kudu console to locate the right folder in the azure web portal to see the image file.
Open kudu console:
In the kudu click the debug console and locate the site\wwwroot\yourfilefolder
If you find your file is still doesn't upload successfully, I guess there maybe something wrong with your upload codes. I suggest you could try below codes.
Notice: You need add image folder in the wwwort folder.
{
public class UploadingController : ApiController
{
public async Task<HttpResponseMessage> PostFile()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = Environment.GetEnvironmentVariable("HOME").ToString() + "\\site\\wwwroot\\images";
//string root = HttpContext.Current.Server.MapPath("~/images");
var provider = new FilenameMultipartFormDataStreamProvider(root);
try
{
StringBuilder sb = new StringBuilder(); // Holds the response body
// Read the form data and return an async task.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the form data.
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
sb.Append(string.Format("{0}: {1}\n", key, val));
}
}
// This illustrates how to get the file names for uploaded files.
foreach (var file in provider.FileData)
{
FileInfo fileInfo = new FileInfo(file.LocalFileName);
sb.Append(string.Format("Uploaded file: {0} ({1} bytes)\n", fileInfo.Name, fileInfo.Length));
}
return new HttpResponseMessage()
{
Content = new StringContent(sb.ToString())
};
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
}
public class FilenameMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
public FilenameMultipartFormDataStreamProvider(string path) : base(path)
{
}
public override string GetLocalFileName(System.Net.Http.Headers.HttpContentHeaders headers)
{
var name = !string.IsNullOrWhiteSpace(headers.ContentDisposition.FileName) ? headers.ContentDisposition.FileName : Guid.NewGuid().ToString();
return name.Replace("\"", string.Empty);
}
}
}
Result: