Skip to content

Commit 922bac8

Browse files
Add suppport for AWS IAM roles (#97)
* Added support for specifying an optional IAM role name Added nullable property RoleName in AWSStorageOptions. Changed AWSStorage.CreateStorageClient to use the IAM role name if it has been supplied, otherwise uses regular basic AWS credentials. * Removed word wrapping for StorageOptions.OriginalOptions Removed the word wrapping for the StorageOptions.OriginalOptions parameter, since I see there are other code lines even longer than these. * Added a comment in CreateStorateClient. * Made role name optional Added support for using instance profile credentials without specifying a role name. * Added missing XML comment for OriginalOptions property * Added XML comment about what the default is * Changed CheckConfiguration to also allow instance profile config Allow that PublicKey and SecretKey is empty if UseInstanceProfileCredentials is true. * Added some AwsConfig tests for instance profile configuration. * Updated LocalStack to latest docker version Updated LocalStack to latest docker version, i.e. 3.5.0. * Updated the Testcontainers NuGet packages Updated the Testcontainers NuGet packages to latest. * Add comment clarifying that IAM instance profile credentials can only be used when running on an EC2 instance. --------- Co-authored-by: ksemenenko <KSemenenko@users.noreply.github.com>
1 parent 6c18b33 commit 922bac8

File tree

6 files changed

+110
-11
lines changed

6 files changed

+110
-11
lines changed

Storages/ManagedCode.Storage.Aws/AWSStorage.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ public override async Task<Result<Stream>> GetStreamAsync(string fileName, Cance
9494

9595
protected override IAmazonS3 CreateStorageClient()
9696
{
97+
// Check if we should use the instance profile credentials.
98+
if (StorageOptions.UseInstanceProfileCredentials)
99+
{
100+
return string.IsNullOrWhiteSpace(StorageOptions.RoleName)
101+
? new AmazonS3Client(new InstanceProfileAWSCredentials(), StorageOptions.OriginalOptions)
102+
: new AmazonS3Client(new InstanceProfileAWSCredentials(StorageOptions.RoleName), StorageOptions.OriginalOptions);
103+
}
104+
105+
// If not, use the basic credentials.
97106
return new AmazonS3Client(new BasicAWSCredentials(StorageOptions.PublicKey, StorageOptions.SecretKey), StorageOptions.OriginalOptions);
98107
}
99108

Storages/ManagedCode.Storage.Aws/Extensions/ServiceCollectionExtensions.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,18 @@ public static IServiceCollection AddAWSStorageAsDefault(this IServiceCollection
4545

4646
private static void CheckConfiguration(AWSStorageOptions options)
4747
{
48-
if (string.IsNullOrEmpty(options.PublicKey))
49-
throw new BadConfigurationException($"{nameof(options.PublicKey)} cannot be empty");
50-
51-
if (string.IsNullOrEmpty(options.SecretKey))
52-
throw new BadConfigurationException($"{nameof(options.SecretKey)} cannot be empty");
53-
48+
// Make sure the bucket name is set.
5449
if (string.IsNullOrEmpty(options.Bucket))
5550
throw new BadConfigurationException($"{nameof(options.Bucket)} cannot be empty");
51+
52+
// If we are using instance profile credentials, we don't need to check for the public and secret keys.
53+
if (!options.UseInstanceProfileCredentials)
54+
{
55+
if (string.IsNullOrEmpty(options.PublicKey))
56+
throw new BadConfigurationException($"{nameof(options.PublicKey)} cannot be empty");
57+
58+
if (string.IsNullOrEmpty(options.SecretKey))
59+
throw new BadConfigurationException($"{nameof(options.SecretKey)} cannot be empty");
60+
}
5661
}
5762
}

Storages/ManagedCode.Storage.Aws/Options/AWSStorageOptions.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,51 @@
33

44
namespace ManagedCode.Storage.Aws.Options;
55

6+
/// <summary>
7+
/// Configuration options for AWS S3 storage.
8+
/// </summary>
69
public class AWSStorageOptions : IStorageOptions
710
{
11+
/// <summary>
12+
/// The public key to access the AWS S3 storage bucket.
13+
/// </summary>
814
public string? PublicKey { get; set; }
15+
16+
/// <summary>
17+
/// The secret key to access the AWS S3 storage bucket.
18+
/// </summary>
919
public string? SecretKey { get; set; }
20+
21+
/// <summary>
22+
/// The name of the IAM role.
23+
/// </summary>
24+
/// <remarks>
25+
/// If this is set, the <see cref="PublicKey"/> and <see cref="SecretKey"/> will be ignored.
26+
/// Note that this can only be used when running on an EC2 instance.
27+
/// </remarks>
28+
public string? RoleName { get; set; }
29+
30+
/// <summary>
31+
/// The name of the bucket to use.
32+
/// </summary>
1033
public string? Bucket { get; set; }
34+
35+
/// <summary>
36+
/// The underlying Amazon S3 configuration.
37+
/// </summary>
1138
public AmazonS3Config? OriginalOptions { get; set; } = new();
1239

40+
/// <summary>
41+
/// Whether to create the container if it does not exist. Default is <c>true</c>.
42+
/// </summary>
1343
public bool CreateContainerIfNotExists { get; set; } = true;
44+
45+
/// <summary>
46+
/// Whether to use the instance profile credentials. Default is <c>false</c>.
47+
/// </summary>
48+
/// <remarks>
49+
/// If this is set to <c>true</c>, the <see cref="PublicKey"/> and <see cref="SecretKey"/> will be ignored.
50+
/// Note that this can only be used when running on an EC2 instance.
51+
/// </remarks>
52+
public bool UseInstanceProfileCredentials { get; set; } = false;
1453
}

Tests/ManagedCode.Storage.Tests/Common/ContainerImages.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ public class ContainerImages
44
{
55
public const string Azurite = "mcr.microsoft.com/azure-storage/azurite:3.30.0";
66
public const string FakeGCSServer = "fsouza/fake-gcs-server:1.49.1";
7-
public const string LocalStack = "localstack/localstack:3.4.0";
7+
public const string LocalStack = "localstack/localstack:3.5.0";
88
}

Tests/ManagedCode.Storage.Tests/ManagedCode.Storage.Tests.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0"/>
3030
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0"/>
3131
<PackageReference Include="System.Linq.Async" Version="6.0.1"/>
32-
<PackageReference Include="Testcontainers" Version="3.8.0"/>
33-
<PackageReference Include="Testcontainers.Azurite" Version="3.8.0"/>
34-
<PackageReference Include="Testcontainers.FakeGcsServer" Version="3.8.0"/>
35-
<PackageReference Include="Testcontainers.LocalStack" Version="3.8.0"/>
32+
<PackageReference Include="Testcontainers" Version="3.9.0" />
33+
<PackageReference Include="Testcontainers.Azurite" Version="3.9.0" />
34+
<PackageReference Include="Testcontainers.FakeGcsServer" Version="3.9.0" />
35+
<PackageReference Include="Testcontainers.LocalStack" Version="3.9.0" />
3636
<PackageReference Include="xunit" Version="2.8.1"/>
3737
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
3838
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

Tests/ManagedCode.Storage.Tests/Storages/AWS/AwsConfigTests.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,52 @@ public void BadConfigurationForStorage_WithoutBucket_ThrowException()
5757
.Throw<BadConfigurationException>();
5858
}
5959

60+
[Fact]
61+
public void BadInstanceProfileConfigurationForStorage_WithoutBucket_ThrowException()
62+
{
63+
var services = new ServiceCollection();
64+
65+
Action action = () => services.AddAWSStorageAsDefault(new AWSStorageOptions
66+
{
67+
RoleName = "my-role-name",
68+
UseInstanceProfileCredentials = true
69+
});
70+
71+
action.Should()
72+
.Throw<BadConfigurationException>();
73+
}
74+
75+
[Fact]
76+
public void ValidInstanceProfileConfigurationForStorage_WithRoleName_DoesNotThrowException()
77+
{
78+
var services = new ServiceCollection();
79+
80+
Action action = () => services.AddAWSStorageAsDefault(new AWSStorageOptions
81+
{
82+
Bucket = "managed-code-bucket",
83+
RoleName = "my-role-name",
84+
UseInstanceProfileCredentials = true
85+
});
86+
87+
action.Should()
88+
.NotThrow<Exception>();
89+
}
90+
91+
[Fact]
92+
public void ValidInstanceProfileConfigurationForStorage_WithoutRoleName_DoesNotThrowException()
93+
{
94+
var services = new ServiceCollection();
95+
96+
Action action = () => services.AddAWSStorageAsDefault(new AWSStorageOptions
97+
{
98+
Bucket = "managed-code-bucket",
99+
UseInstanceProfileCredentials = true
100+
});
101+
102+
action.Should()
103+
.NotThrow<Exception>();
104+
}
105+
60106
[Fact]
61107
public void StorageAsDefaultTest()
62108
{

0 commit comments

Comments
 (0)