-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathSdkUtilities.cs
158 lines (133 loc) · 6.25 KB
/
SdkUtilities.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
using System.Reflection;
using System.Text;
using Amazon.Runtime;
using Microsoft.Extensions.Logging;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
using Aspire.Hosting.ApplicationModel;
using Amazon.Runtime.CredentialManagement;
namespace Aspire.Hosting.AWS;
internal static class SdkUtilities
{
private const string UserAgentHeader = "User-Agent";
private static string? s_userAgentHeader;
private static string GetUserAgentStringSuffix()
{
if (s_userAgentHeader == null)
{
var builder = new StringBuilder($"lib/aspire.hosting.aws#{GetAssemblyVersion()}");
s_userAgentHeader = builder.ToString();
}
return s_userAgentHeader;
}
internal static string GetAssemblyVersion()
{
var attribute = typeof(AWSLifecycleHook).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
return attribute != null ? attribute.InformationalVersion.Split('+')[0] : "Unknown";
}
internal static void ConfigureUserAgentString(object sender, RequestEventArgs e)
{
var suffix = GetUserAgentStringSuffix();
if (e is not WebServiceRequestEventArgs args || !args.Headers.TryGetValue(UserAgentHeader, out var currentValue) || currentValue.Contains(suffix))
{
return;
}
args.Headers[UserAgentHeader] = currentValue + " " + suffix;
}
internal static void ApplySDKConfig(EnvironmentCallbackContext context, IAWSSDKConfig awsSdkConfig, bool force)
{
if (context.Logger != null)
{
// To help debugging do a validation of the SDK config. The results will be logged.
BackgroundSDKConfigValidation(context.Logger, awsSdkConfig);
}
if (!string.IsNullOrEmpty(awsSdkConfig.Profile))
{
if (force || !context.EnvironmentVariables.ContainsKey("AWS__Profile"))
{
// The environment variable that AWSSDK.Extensions.NETCore.Setup will look for via IConfiguration.
context.EnvironmentVariables["AWS__Profile"] = awsSdkConfig.Profile;
// The environment variable the service clients look for service clients created without AWSSDK.Extensions.NETCore.Setup.
context.EnvironmentVariables["AWS_PROFILE"] = awsSdkConfig.Profile;
}
}
if (awsSdkConfig.Region != null)
{
if (force || !context.EnvironmentVariables.ContainsKey("AWS__Region"))
{
// The environment variable that AWSSDK.Extensions.NETCore.Setup will look for via IConfiguration.
context.EnvironmentVariables["AWS__Region"] = awsSdkConfig.Region.SystemName;
// The environment variable the service clients look for service clients created without AWSSDK.Extensions.NETCore.Setup.
context.EnvironmentVariables["AWS_REGION"] = awsSdkConfig.Region.SystemName;
}
}
}
internal static void BackgroundSDKDefaultConfigValidation(ILogger logger)
{
var chain = new Amazon.Runtime.CredentialManagement.CredentialProfileStoreChain();
var profiles = chain.ListProfiles();
// If there are no profiles then the developer is unlikely connecting in the dev environment
// to AWS with the SDK. In this case it doesn't make sense to validate credentials.
if (profiles.Count == 0)
return;
// If there is not a default profile then skip validating it.
var defaultProfile = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AWS_PROFILE")) ? Environment.GetEnvironmentVariable("AWS_PROFILE") : SharedCredentialsFile.DefaultProfileName;
if (profiles.FirstOrDefault(x => string.Equals(defaultProfile, x.Name)) == null)
return;
_ = ValidateSdkConfigAsync(logger, new AWSSDKConfig(), true);
}
internal static void BackgroundSDKConfigValidation(ILogger logger, IAWSSDKConfig config)
{
_ = ValidateSdkConfigAsync(logger, config, false);
}
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
static HashSet<string> _validatedSdkConfigs = new HashSet<string>();
private static async Task ValidateSdkConfigAsync(ILogger logger, IAWSSDKConfig config, bool defaultConfig)
{
// Cache key used to make sure we only validate a SDK configuration once.
var cacheKey = $"Profile:{config.Profile},Region:{config.Region?.SystemName}";
await _semaphore.WaitAsync();
try
{
if (_validatedSdkConfigs.Contains(cacheKey))
{
return;
}
var stsConfig = new AmazonSecurityTokenServiceConfig();
if (config.Region != null)
stsConfig.RegionEndpoint = config.Region;
if (!string.IsNullOrEmpty(config.Profile))
stsConfig.Profile = new Amazon.Profile(config.Profile);
try
{
using var stsClient = new AmazonSecurityTokenServiceClient(stsConfig);
stsClient.BeforeRequestEvent += ConfigureUserAgentString;
// Make an AWS call to an API that doesn't require permissions to confirm
// the sdk config is able to connect to AWS.
var response = await stsClient.GetCallerIdentityAsync(new GetCallerIdentityRequest());
if (defaultConfig)
logger.LogInformation("Default AWS SDK config validated for account: {accountId}", response.Account);
else
logger.LogInformation("AWS SDK config validated for account: {accountId}", response.Account);
_validatedSdkConfigs.Add(cacheKey);
}
catch (Exception)
{
if (defaultConfig)
{
logger.LogWarning("Failed to connect to AWS using default AWS SDK config");
}
else
{
logger.LogError("Failed to connect to AWS using AWS SDK config: {configSettings}", cacheKey);
}
_validatedSdkConfigs.Add(cacheKey);
}
}
finally
{
_semaphore.Release();
}
}
}