I am currently working with S3 and need to extract an S3 resource which has a timeout for streaming, so that the client cannot use the URL after a specific amount of time.
I have already used some code provided in the documentation for "Presigned Object URL Using AWS SDK for .NET".
The code will provide a temporary URL which can be used to download an S3 resource by anyone...but within a specific time limit.
I have also used the Amazon S3 Explorer for Visual Studio, but it doesn't support URL generation for resources embedded with AWSKMS key.
Also tried deleting the KMS Key for the S3 folder, but that is throwing an error.
If there is a possible link for deleting KMS keys can you also include it in your answers.
//Code Start
using Amazon;
using Amazon.S3;
using Amazon.S3.Model;
using System;
namespace URLDownload
{
public class Class1
{
private const string bucketName = "some-value";
private const string objectKey = "some-value";
// Specify your bucket region (an example region is shown).
private static readonly RegionEndpoint bucketRegion = RegionEndpoint.USEast1;
private static IAmazonS3 s3Client;
public static void Main()
{
s3Client = new AmazonS3Client(bucketRegion);
string urlString = GeneratePreSignedURL();
Console.WriteLine(urlString);
Console.Read();
}
static string GeneratePreSignedURL()
{
string urlString = "";
try
{
//ServerSideEncryptionMethod ssem = new ServerSideEncryptionMethod("AWSKMS");
GetPreSignedUrlRequest request1 = new GetPreSignedUrlRequest
{
BucketName = bucketName,
Key = objectKey,
Expires = DateTime.Now.AddMinutes(5),
Verb = 0,
ServerSideEncryptionKeyManagementServiceKeyId = "some-value",
ServerSideEncryptionMethod = ServerSideEncryptionMethod.AWSKMS
};
urlString = s3Client.GetPreSignedURL(request1);
}
catch (AmazonS3Exception e)
{
Console.WriteLine("Error encountered on server. Message:'{0}' when writing an object", e.Message);
}
catch (Exception e)
{
Console.WriteLine("Unknown encountered on server. Message:'{0}' when writing an object", e.Message);
}
return urlString;
}
}
}
SignatureDoesNotMatch
The request signature we calculated does not match the signature you provided. Check your key and signing method.
AKIA347A6YXQ3XM4JQ7A
This is the error that I am getting when I am trying to access the generated URL and that is probably because the AWSKMS authentication is having some issue.
I see it's been a couple of years, but did have an answer for this one? One thing that your code snippet seems to be missing is V4 signature flag set to true:
AWSConfigsS3.UseSignatureVersion4 = true;
Sources:
https://aws.amazon.com/blogs/developer/generating-amazon-s3-pre-signed-urls-with-sse-part-1/
https://aws.amazon.com/blogs/developer/generating-amazon-s3-pre-signed-urls-with-sse-kms-part-2/
You also need to make sure you're providing x-amz-server-side-encryption and x-amz-server-side-encryption-aws-kms-key-id headers on your upload request
Related
I have a simple code to get versions of a file from S3 but getting the error below. In the meantime, put and get object requests for the same files are working fine.
var getVrRequest = new ListVersionsRequest()
{
BucketName = bucketName,
MaxKeys = 10
};
ListVersionsResponse response;
try
{
response = await client.ListVersionsAsync(getVrRequest);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
ex.Message = "There is no such thing as the ?versions sub-resource for a key"
Any idea what might be the problem for getting this error?
screenshot
Solved it. For put and get objects, for the request BucketName property I was using the whole path starting with the bucket name and I thought it's the same thing here, but for list versions request, it should be only the name of the bucket.
I'm trying to determine if a folder exists on my Amazon S3 Bucket and if it doesn't I want to create it.
At the moment I can create the folder using the .NET SDK as follows:
public void CreateFolder(string bucketName, string folderName)
{
var folderKey = folderName + "/"; //end the folder name with "/"
var request = new PutObjectRequest();
request.WithBucketName(bucketName);
request.StorageClass = S3StorageClass.Standard;
request.ServerSideEncryptionMethod = ServerSideEncryptionMethod.None;
//request.CannedACL = S3CannedACL.BucketOwnerFullControl;
request.WithKey(folderKey);
request.WithContentBody(string.Empty);
S3Response response = m_S3Client.PutObject(request);
}
Now when I try to see if the folder exists using this code:
public bool DoesFolderExist(string key, string bucketName)
{
try
{
S3Response response = m_S3Client.GetObjectMetadata(new GetObjectMetadataRequest()
.WithBucketName(bucketName)
.WithKey(key));
return true;
}
catch (Amazon.S3.AmazonS3Exception ex)
{
if (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
return false;
//status wasn't not found, so throw the exception
throw;
}
}
It cannot find the folder. The strange thing is if I create the folder using the AWS Management Console, the 'DoesFolderExist' method can see it.
I'm not sure if it's an ACL/IAM thing but am not sure how to resolve this.
Your code actually works for me, but there are a few things you need to be aware off.
As I understand it, Amazon S3 does not have a concept of folders, but individual clients may display the S3 objects as if they did. So if you create an object called A/B , then the client may display it as if it was an object called B inside a folder called A. This is intuitive and seems to have become a standard, but simulating an empty folder does not appear to have a standard.
For example, I used your method to create a folder called Test, then actually end up creating an object called Test/. But I created a folder called Test2 in AWS Explorer (ie the addon to Visual Studio) and it ended up creating an object called Test2/Test2_$folder$
(AWS Explorer will display both Test and Test2 as folders)
Once of the things that this means is that you don't need to create the 'folder' before you can use it, which may mean that you don't need a DoesFolderExist method.
As I mention I tried your code and it works and finds the Test folder it created, but the key had to be tweaked to find the folder created by AWS Explorer , ie
DoesFolderExist("Test/" , bucketName); // Returns true
DoesFolderExist("Test2/" , bucketName); // Returns false
DoesFolderExist("Test2/Test2_$folder$", bucketName); // Returns true
So if you do still want to have a DoesFolderExist method, then it might be safer to just look for any objects that start with folderName + "/" , ie something like
ListObjectsRequest request = new ListObjectsRequest();
request.BucketName = bucketName ;
request.WithPrefix(folderName + "/");
request.MaxKeys = 1;
using (ListObjectsResponse response = m_S3Client.ListObjects(request))
{
return (response.S3Objects.Count > 0);
}
Just refactored above codes to on async method with version 2 of AWS .Net SDK:
public async Task CreateFoldersAsync(string bucketName, string path)
{
path = path.EnsureEndsWith('/');
IAmazonS3 client =
new AmazonS3Client(YOUR.AccessKeyId, YOUR.SecretAccessKey,
RegionEndpoint.EUWest1);
var findFolderRequest = new ListObjectsV2Request();
findFolderRequest.BucketName = bucketName;
findFolderRequest.Prefix = path;
findFolderRequest.MaxKeys = 1;
ListObjectsV2Response findFolderResponse =
await client.ListObjectsV2Async(findFolderRequest);
if (findFolderResponse.S3Objects.Any())
{
return;
}
PutObjectRequest request = new PutObjectRequest()
{
BucketName = bucketName,
StorageClass = S3StorageClass.Standard,
ServerSideEncryptionMethod = ServerSideEncryptionMethod.None,
Key = path,
ContentBody = string.Empty
};
// add try catch in case you have exceptions shield/handling here
PutObjectResponse response = await client.PutObjectAsync(request);
}
ListObjectsRequest findFolderRequest = new ListObjectsRequest();
findFolderRequest.BucketName = bucketName;
findFolderRequest.Prefix = path;
ListObjectsResponse findFolderResponse = s3Client.ListObjects(findFolderRequest);
Boolean folderExists = findFolderResponse.S3Objects.Any();
path can be something like "images/40/".
Using the above code can check if a so-called folder "images/40/" under bucket exists or not.
But Amazon S3 data model does not have the concept of folders. When you try to copy a image or file to certain path, if this co-called folder does not exist it will be created automatically as part of key name of this file or image. Therefore, you actually do not need to check if this folder exists or not.
Very important information from docs.aws.amazon.com : The Amazon S3 data model is a flat structure: you create a bucket, and the bucket stores objects. There is no hierarchy of subbuckets or subfolders; however, you can infer logical hierarchy using key name prefixes and delimiters as the Amazon S3 console does.
http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
I've written an ASP.NET Core 2.0 website in C# and have Facebook authentication enabled, so it requires HTTPS. I'm using the native Kestrel web server to host the site and have a listener set to take the PFX certificate per MS' documentation. I can't seem to find a way for Kestrel to recognize the private key after recall from Key Vault. I know it's present, as I wrote two debug statements that indicate it is, in fact present.
This is the function that I'm using to retrieve the secret, which is working.
public static async Task<X509Certificate2> GetKeyVaultCert()
{
X509Certificate2 pfx;
try
{
var kvClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
var secret = await kvClient
.GetSecretAsync("https://macscampvault.vault.azure.net/secrets/letsencrypt").ConfigureAwait(false);
byte[] bytes;
if(secret.ContentType == "application/x-pkcs12")
bytes = Convert.FromBase64String(secret.Value);
else
{
bytes = new byte[0];
Console.WriteLine("secret is not PFX!!");
throw new ArgumentException("This is not a PFX string!!");
}
var password = new SecureString();
var coll = new X509Certificate2Collection();
coll.Import(bytes, null, X509KeyStorageFlags.Exportable);
pfx = coll[0];
// File output added in case I end up needing to write cert to container
// File.WriteAllBytes(Directory.GetCurrentDirectory().ToString() + "/Macs.pfx", bytes);
Console.WriteLine(pfx.HasPrivateKey);
Console.WriteLine(pfx.GetRSAPrivateKey());
}
catch (Exception ex)
{
Console.WriteLine($"There was a problem during the key vault operation\n{ex.Message}");
throw;
}
return pfx;
}
The debug statements after the assignment call pfx = coll[0]; tell me that this private key exists, but when I try to connect to the website using lynx https://localhost I receive the following exception:
System.NotSupportedException: The server mode SSL must use a certificate with the associated private key.
So, how do I use the private key? Here's a gist to the file in question.
I already was helped by How to serialize and deserialize a PFX certificate in Azure Key Vault? but after following it, I got to this state.
In your gist you have the following code:
var keyVaultCert = GetKeyVaultCert().Result ??
throw new ArgumentNullException("GetKeyVaultCert().Result");
pfx = new X509Certificate2(keyVaultCert.RawData);
The second line there removes the private key, because the RawData property just returns the DER encoded X.509 object.
keyVaultCert is already an X509Certificate2 with a private key, you probably want to just use it.
pfx = GetKeyVaultCert().Result ?? throw etc;
We were upgrading to the new logic specified by ConvertAPI. But the framework used by us is 4 because of which System.Net.Http(HttpResponseMessage, HttpClient ,MultipartFormDataContent ,StreamContent ,StringContent) classes are not being found. Even added System.Net.Http reference. But still the same error persists.
Can someone suggest some workaround for this? How to use ConvertApi in C# for .Net Framework 4?
Just use WebClient instead of HttpClient for quick file upload. To run the demo below you will need to replace file paths and set your secret instead of xxxxx in endpoint url. Also we should set accept:application/octet-stream header to get response as data stream instead of json.
class Program
{
static void Main(string[] args)
{
const string fileToConvert = #"C:\Projects\_temp\test1.docx";
const string fileToSave = #"C:\Projects\_temp\test1.pdf";
try
{
using (var client = new WebClient())
{
client.Headers.Add("accept", "application/octet-stream");
var resultFile = client.UploadFile(new Uri("https://v2.convertapi.com/docx/to/pdf?Secret=xxxxx"), fileToConvert);
File.WriteAllBytes(fileToSave, resultFile );
Console.WriteLine("File converted successfully");
}
}
catch (WebException e)
{
Console.WriteLine("Status Code : {0}", ((HttpWebResponse)e.Response).StatusCode);
Console.WriteLine("Status Description : {0}", ((HttpWebResponse)e.Response).StatusDescription);
Console.WriteLine("Body : {0}", new StreamReader(e.Response.GetResponseStream()).ReadToEnd());
}
Console.ReadLine();
}
}
Trying to make Android chooser to display available actions for user to launch a PDF file which is stored in my local folder.
When I pass the file name like /data/user/0/myappappname/files/output.pdf , (which exsists, of course), I get a nice chooser with all the apps that can accept a pdf file. But when I pick any of them, I get an error (from external app) The document path is not valid. No exception is thrown.
Then I tried (for testing purposes) to set fname to something like /storage/emulated/0/Download/TLCL.pdf (file also exists), and everything works fine.
At first, I thought that this has something to do with file permissions (since first path is private to my app), but then I found flag ActivityFlags.GrantReadUriPermission built exactly for purpose of temporarily granting file access to other apps. Still same results.
Since this is a Xamarin.forms project, I am limited in choice of file creation locations (I use PCLStorage, which always writes to app-private, local folder), so I don't have an option of generating files in /Documents, /Downloads etc.
I am obviously doing something wrong. Any ideas appreciated.
Is there an option to get full path from system, including the /storage/emulated/0 part (or whatever that would be on other devices)? Maybe that would help?
Piece of code:
(mimeType is defined as "application/pdf" earlier)
public async Task<bool> LaunchFile(string fname, string mimeType)
{
var uri = Android.Net.Uri.Parse("file://" + fname );
var intent = new Intent(Intent.ActionView);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.ClearWhenTaskReset | ActivityFlags.NewTask | ActivityFlags.GrantReadUriPermission );
try
{
Forms.Context.StartActivity(Intent.CreateChooser(intent, "ChooseApp"));
return true;
}
catch (Exception ex)
{
Debug.WriteLine("LaunchFile: " + ex.Message);
return false;
}
My solution to this, which may not be exactly what you want, is to generate a file (in my case a zip file), export it to a public folder, and use that file for the chooser.
Using these:
private readonly string PublicDocsPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath + "/AppName";
private readonly string PrivateDocsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData);
and some basic functions:
public Stream GetOutputStream(string destFilePath)
{
string destFolderPath = Path.GetDirectoryName(destFilePath);
if (!Directory.Exists(destFolderPath))
Directory.CreateDirectory(destFolderPath);
return new FileStream(destFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
}
public Stream GetInputStream(string sourceFilePath)
{
if (!File.Exists(sourceFilePath)) throw new FileNotFoundException();
string sourceFolderPath = Path.GetDirectoryName(sourceFilePath);
return new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
You can copy your file to your public folder (or subfolders, you just have to assemble the path) and use that file for your chooser:
public void SendEmail(string subject, string body, string recipient, string mimeType, string attachmentFilePath, string activityTitle)
{
var emailIntent = new Intent(Intent.ActionSendMultiple);
if (string.IsNullOrEmpty(subject)) throw new ArgumentException();
emailIntent.PutExtra(Intent.ExtraSubject, subject);
if (!string.IsNullOrEmpty(recipient))
emailIntent.PutExtra(Intent.ExtraEmail, new[] { recipient });
if (!string.IsNullOrEmpty(body))
emailIntent.PutExtra(Intent.ExtraText, body);
if (!string.IsNullOrEmpty(attachmentFilePath))
{
var file = new Java.IO.File(attachmentFilePath);
file.SetReadable(true, true);
var uri = Android.Net.Uri.FromFile(file);
emailIntent.PutParcelableArrayListExtra(Intent.ExtraStream, new List<IParcelable>(){uri});
}
emailIntent.SetType(mimeType);
_activity.StartActivity(Intent.CreateChooser(emailIntent, activityTitle));
}
This chooser specifically lets the user send their file via email or google drive , but you can assemble it however you want. The attachmentFilePath of this function is the same as the string passed into the GetOutputStream function above.
we're using Acr.IO rather than PCLStorage and I recall that has a property that'll return the fullpath for you.
The code we're using is below, but I wonder if you're simply missing "file://" off the start of your path, as I noticed thats in our code, as well as this previous stackoverflow answer to a similar question as this one, open a PDF in Xamarin.Forms (Android)
We're using a dependency FileService on Android and using this code to open PDFs:
public void OpenNatively(string filePath) {
Android.Net.Uri uri;
if (filePath.StartsWithHTTP()) {
uri = Android.Net.Uri.Parse(filePath);
}
else {
uri = Android.Net.Uri.Parse("file:///" + filePath);
}
Intent intent = new Intent(Intent.ActionView);
var extension = filePath.Substring(filePath.LastIndexOf(".")+1);
if (extension == "ppt" || extension == "pptx") {
extension = "vnd.ms-powerpoint";
}
var docType = "application/" + extension;
intent.SetDataAndType(uri, docType);
intent.SetFlags(ActivityFlags.ClearWhenTaskReset | ActivityFlags.NewTask);
try {
Xamarin.Forms.Forms.Context.StartActivity(intent);
}
catch (Exception e) {
Toast.MakeText(Xamarin.Forms.Forms.Context, "No Application found to view " + extension.ToUpperInvariant() + " files.", ToastLength.Short).Show();
}
}