如何在C#中生成AWS ElastiCache IAM认证的正确签名?
.NET8连接ElastiCache IAM认证失败问题
报错信息
未处理的异常。StackExchange.Redis.RedisConnectionException: 无法连接到Redis服务器。认证失败;请检查密码(或客户端证书)配置是否正确:(RedisServerException) 错误:WRONGPASS 用户名-密码对无效或用户已禁用。
问题背景
- 已通过Java示例成功连接目标ElastiCache集群,但基于sigv4示例自行编写的.NET8代码始终认证失败
- 核心矛盾:使用相同凭证和时间戳生成IAM认证令牌时,.NET代码输出与Java代码结果不一致,怀疑是查询字符串处理或哈希机制存在问题
- 排查发现原代码误用请求头格式处理认证信息,不符合ElastiCache IAM认证要求的查询字符串格式
测试代码
using Amazon.Runtime; using Amazon.Runtime.CredentialManagement; using StackExchange.Redis; using System.Security.Cryptography; using System.Text; using System.Web; var profile = Environment.GetEnvironmentVariable("AWS_PROFILE"); if (string.IsNullOrEmpty(profile) || !new CredentialProfileStoreChain().TryGetAWSCredentials(profile, out var credentials)) { credentials = new InstanceProfileAWSCredentials(); } var username = Environment.GetEnvironmentVariable("USERNAME"); var cacheName = Environment.GetEnvironmentVariable("CACHE_NAME"); var region = Environment.GetEnvironmentVariable("AWS_REGION"); var cacheEndpoint = Environment.GetEnvironmentVariable("CACHE_ENDPOINT"); var cacheChannel = Environment.GetEnvironmentVariable("CACHE_CHANNEL"); var iamAuthTokenRequest = new IAMAuthTokenRequest(username!, cacheName!, region!); var authToken = iamAuthTokenRequest.Get(credentials); Console.WriteLine(authToken); var options = new ConfigurationOptions() { EndPoints = { { cacheEndpoint!, 6379 } }, ChannelPrefix = RedisChannel.Literal(cacheChannel!), User = username, Password = authToken, Ssl = true, }; var redis = ConnectionMultiplexer.Connect(options); IDatabase db = redis.GetDatabase(); string value = "abcdefg"; db.StringSet("mykey", value); public class IAMAuthTokenRequest { private static readonly HttpMethod _requestMethod = HttpMethod.Get; private static readonly string _requestProtocol = "http://", _paramAction = "Action", _paramUser = "User", _actionName = "connect", _amzSecurityToken = "X-Amz-Security-Token", _amzAlgorithm = "X-Amz-Algorithm", _algorithm = "AWS4-HMAC-SHA256", _amzSignedHeaders = "X-Amz-SignedHeaders", _amzExpires = "X-Amz-Expires", _amzCredential = "X-Amz-Credential", _amzDate = "X-Amz-Date", _amzSignature = "X-Amz-Signature", _serviceName = "elasticache"; private static readonly int _expirySeconds = 900; private readonly string _userId, _cacheName, _region; public IAMAuthTokenRequest(string userId, string cacheName, string region) { _userId = userId; _cacheName = cacheName; _region = region; } public string Get(AWSCredentials credentials) { var now = DateTime.UtcNow; var headers = new Dictionary<string, string>() { { "host", _cacheName } }; var immutableCredentials = credentials.GetCredentials(); var uri = GetSignableRequest(); var authorizationrequest = GetAuthorizationRequest( uri, now, _algorithm, headers, CalculateHash(""), immutableCredentials); return authorizationrequest; } private Uri GetSignableRequest() => new($"{_requestProtocol}{_cacheName}"); private string GetAuthorizationRequest( Uri uri, DateTime now, string algorithm, Dictionary<string, string> headers, string payloadHash, ImmutableCredentials credentials) { var amzDate = ToAmzDate(now); var datestamp = now.ToString("yyyyMMdd"); var credentialScope = $"{datestamp}/{_region}/{_serviceName}/aws4_request"; var queryParams = QueryParams( Encode(credentials.Token), Encode($"{credentials.AccessKey}/{credentialScope}"), amzDate); // Create the string to sign var hashedCanonicalRequest = BuildCanonicalRequest(uri.AbsolutePath, queryParams, headers, payloadHash); var stringToSign = CanonicalRequest(algorithm, amzDate, credentialScope, hashedCanonicalRequest); // Sign the string var signingKey = GetSignatureKey(credentials.SecretKey, datestamp, _region, _serviceName); var signature = CalculateHmacHex(signingKey, stringToSign); var query = BuildQueryString( queryParams, signature); // return signing information return $"{uri.ToString().Replace("http://", "")}?{query}"; } private static string Encode(string input) => HttpUtility.UrlEncode(input, Encoding.UTF8); private Dictionary<string, string> QueryParams(string securityToken, string credentials, string amzDate) => new() { { _paramAction, _actionName }, { _paramUser, _userId }, { _amzAlgorithm, _algorithm }, { _amzCredential, credentials }, { _amzDate, amzDate }, { _amzExpires, _expirySeconds.ToString("d") }, { _amzSecurityToken, securityToken }, { _amzSignedHeaders, "host" }, }; private static string BuildQueryString(Dictionary<string, string> queryParams, string signature) { queryParams.Add(_amzSignature, signature); return string.Join("&", queryParams.Select(pair => $"{pair.Key}={pair.Value}")); } private static string BuildCanonicalRequest(string uriPath, Dictionary<string, string> query, Dictionary<string, string> headers, string payloadHash) { var canonicalQuerystring = CanonicalDictionaryToString(query, "&", pair => $"{pair.Key.ToLowerInvariant()}={pair.Value.ToLowerInvariant()}"); var canonicalHeaders = CanonicalDictionaryToString(headers, "\n", header => $"{header.Key.ToLowerInvariant()}:{header.Value.Trim().ToLowerInvariant()}") + "\n"; var signedHeaders = CanonicalDictionaryToString(headers, ";", header => header.Key.ToLowerInvariant()); var canonicalRequest = CanonicalRequest( _requestMethod.Method, uriPath, canonicalQuerystring, canonicalHeaders, signedHeaders, payloadHash); return CalculateHash(canonicalRequest); } private static string CanonicalRequest(params string[] parts) => string.Join("\n", parts); private static string ToAmzDate(DateTime date) => date.ToString("yyyyMMddTHHmmssZ"); private static string CanonicalDictionaryToString(IDictionary<string, string> input, string join, Func<KeyValuePair<string, string>, string> selector) => string.Join(join, new SortedDictionary<string, string>(input, StringComparer.OrdinalIgnoreCase).Select(selector)); private static byte[] GetSignatureKey(string key, string dateStamp, string regionName, string serviceName) { var kSecret = Encoding.UTF8.GetBytes($"AWS4{key}"); var kDate = HmacSha256(kSecret, dateStamp); var kRegion = HmacSha256(kDate, regionName); var kService = HmacSha256(kRegion, serviceName); return HmacSha256(kService, "aws4_request"); } private static string CalculateHmacHex(byte[] key, string data) { var hash = HmacSha256(key, data); return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); } private static byte[] HmacSha256(byte[] key, string data) { using var hmac = new HMACSHA256(key); return hmac.ComputeHash(Encoding.UTF8.GetBytes(data)); } private static string CalculateHash(string data) { var hash = SHA256.HashData(Encoding.UTF8.GetBytes(data)); return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); } }
内容的提问来源于stack exchange,提问作者APW
相关产品推荐
相关产品推荐

