aws / amazon-s3-encryption-client-dotnet

An encryption client that allows you to secure your sensitive data before you send it to Amazon S3.
https://aws.github.io/amazon-s3-encryption-client-dotnet/
Apache License 2.0
15 stars 10 forks source link

how to use AmazonS3EncryptionClientV2 with Amazon.Extensions.NETCore.Setup? #28

Closed sunilshahi closed 2 years ago

sunilshahi commented 2 years ago

We were doing S3 client side encryption (AWS KMS + Context) with code that looks like this.

using Amazon;
using Amazon.Extensions.NETCore.Setup;
using Amazon.Extensions.S3.Encryption;
using Amazon.Extensions.S3.Encryption.Primitives;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ServiceStack.IO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;

namespace MyPlayground
{
    class Program
    {
        static async Task Main(string[] args)
        {

            var host = Host.CreateDefaultBuilder()
                .ConfigureServices((context, services) =>
                {
                    services.AddTransient<IAmazonS3>(provider =>
                    {
                        var _s3BucketConfiguration = new
                        {
                            AccessKey = "***Access-Key-Goes-Here***",
                            SecurityKey = "***Security-Key-Goes-Here***",
                            KMSKeyId = "***KMS-ID-Goes-Here***",
                            Region = "us-east-1"
                        };

                        var credential = new BasicAWSCredentials(
                        _s3BucketConfiguration.AccessKey, _s3BucketConfiguration.SecurityKey);

                        var encryptionContext = new Dictionary<string, string>();
                        EncryptionMaterialsV2 encryptionMaterials = new EncryptionMaterialsV2(
                            _s3BucketConfiguration.KMSKeyId, KmsType.KmsContext, encryptionContext);

                        var configuration = new AmazonS3CryptoConfigurationV2(SecurityProfile.V2AndLegacy);
                        configuration.RegionEndpoint = RegionEndpoint.GetBySystemName(_s3BucketConfiguration.Region);

                        return new AmazonS3EncryptionClientV2(credential, configuration, encryptionMaterials);
                    });
                })
                .Build();

            var tester = ActivatorUtilities.GetServiceOrCreateInstance<Tester>(host.Services);
            try
            {
                await tester.TestUpload();
                await tester.TestDownload();
            }
            catch (Exception ex) 
            {
                Console.WriteLine(ex);
            }

            Console.WriteLine("Done");
        }
    }

    public class Tester
    {
        private readonly IAmazonS3 _s3Client;

        public Tester(IAmazonS3 s3Client) => _s3Client = s3Client;

        public async Task TestUpload()
        {
            PutObjectResponse response = await _s3Client.PutObjectAsync(new PutObjectRequest { 
                BucketName = "test-hello-bucket",
                Key = "HelloWorld.txt",
                ContentBody = "Hello World!!",
                ContentType = "text/plain"
            });
        }

        public async Task TestDownload()
        {
            GetObjectResponse response = await _s3Client.GetObjectAsync("test-hello-bucket", "HelloWorld.txt");
            var ms = new MemoryStream();
            response.ResponseStream.CopyTo(ms);
            File.WriteAllBytes("out.txt", ms.ToArray());
        }
    }
}

This is working fine. However, we want to use Amazon.Extensions.NETCore.Setup and not save accessKey and secretKey in our config files like mentioned here.

for example. I can do this if I do not need client side encryption.

var host = Host.CreateDefaultBuilder()
    .ConfigureServices((context, services) =>
    {
        services.AddDefaultAWSOptions(context.Configuration.GetAWSOptions());
        services.AddAWSService<IAmazonS3>();
    })
    .Build();

But how to do this if I need client side encryption using AmazonS3EncryptionClientV2? Is this possible?

Environment

Running in windows 10 with dot net core 3.1. This is my csproj file.

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp3.1</TargetFramework>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.1" />
        <PackageReference Include="AWSSDK.S3" Version="3.7.3.21" />
        <PackageReference Include="Amazon.Extensions.S3.Encryption" Version="2.0.3" />
        <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.20" />
    </ItemGroup>

    <ItemGroup>
        <None Update="appsettings.json">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
    </ItemGroup>

</Project>
ganeshnj commented 2 years ago

AmazonS3EncryptionClientV2 requires explicit config for SecuityProfile which is not possible with plain .AddAWSService call.

However, you don't need to hard-code the credentials. You can use ServiceCollectionServiceExtensions.AddTransient Method extension method to inject the AmazonS3EncryptionClientV2 client.

services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
services.AddTransient<IAmazonS3, AmazonS3EncryptionClientV2>(provider =>
{
    var materials = new EncryptionMaterialsV2("my-kms-key", KmsType.KmsContext, new Dictionary<string, string>());
    var config = new AmazonS3CryptoConfigurationV2(SecurityProfile.V2);
    return new AmazonS3EncryptionClientV2(config, materials);
});

S3 SDK will follow the normal credential resolution process. Tester class code will work without any change.

btw, you don't need AWSSDK.S3 explcitly to use AmazonS3EncryptionClientV2. AWSSDK.S3 will automatically be pulled as transitive dependency.