internal async Task <AwsIamAuthRequest> BuildAws4SignedRequest(byte[] nonce = null) { AssertAuthenticatedToService(); var nowUtc = DateTime.UtcNow; using var httpBody = new ByteArrayContent( Encoding.UTF8.GetBytes(AwsIamConstants.AwsIamRequestContent)); httpBody.Headers.ContentType = MediaTypeHeaderValue.Parse( AwsIamConstants.AwsIamRequestContentMediaType); var httpRequ = new HttpRequestMessage(HttpMethod.Post, AwsIamConstants.AwsIamRequestEndpoint) { Content = httpBody, }; var httpClient = new HttpClient(); var sig = await AwsSignatureVersion4.Private.Signer.SignAsync( httpClient, httpRequ, nowUtc, AwsIamConstants.AwsIamRequestRegion, AwsIamConstants.AwsIamRequestService, _awsCreds); var authRequ = new AwsIamAuthRequest { StsAmzIso8601Date = nowUtc.ToString(AwsIamConstants.ISO8601DateTimeFormat), StsAuthorization = sig.AuthorizationHeader, Nonce = (nonce?.Length ?? 0) == 0 ? ByteString.Empty : ByteString.CopyFrom(nonce) }; foreach (var h in httpRequ.Headers) { if (h.Key == "Authorization") { // Skip this as it's treated special below continue; } authRequ.StsAdditionalHeaders.Add(h.Key, new Server.AwsIamAuthRequest.Types.HeaderValues { Values = { h.Value, }, }); } return(authRequ); }
public override async Task <AwsIamAuthReply> AwsIamAuth(AwsIamAuthRequest request, ServerCallContext context) { if (_config.AwsIamMapping == null) { throw new Exception("missing AWS IAM configuration -- AWS IAM mapping is unsupported"); } var nowUtc = DateTime.UtcNow; var amzDateMin = nowUtc.AddMinutes(-5); var amzDateMax = nowUtc.AddMinutes(5); var amzDate = DateTime.ParseExact(request.StsAmzIso8601Date, AwsIamConstants.ISO8601DateTimeFormat, null).ToUniversalTime(); if (amzDate < amzDateMin || amzDate > amzDateMax) { throw new Exception("AMZ Date outside of valid range"); } using var httpBody = new StringContent( AwsIamConstants.AwsIamRequestContent, AwsIamConstants.AwsIamRequestContentEncoding, AwsIamConstants.AwsIamRequestContentMediaType); using var httpRequ = new HttpRequestMessage( AwsIamConstants.AwsIamRequestHttpMethod, AwsIamConstants.AwsIamRequestEndpoint) { Content = httpBody, }; foreach (var h in request.StsAdditionalHeaders) { httpRequ.Headers.Add(h.Key, h.Value.Values); } if (!httpRequ.Headers.TryAddWithoutValidation("Authorization", request.StsAuthorization)) { throw new Exception("could not add AWSv4 Authorization header"); } using var http = new HttpClient(); using var httpResp = await http.SendAsync(httpRequ); httpResp.EnsureSuccessStatusCode(); using var httpRespStream = await httpResp.Content.ReadAsStreamAsync(); var httpRespResult = AwsStsGetCallerIdentityResponse.ParseXml(httpRespStream); var identityArn = httpRespResult?.GetCallerIdentityResult?.Arn; if (string.IsNullOrEmpty(identityArn)) { throw new Exception("could not authenticate or resolve IAM Identity ARN"); } var userMap = _config.AwsIamMapping.Users.FirstOrDefault(u => u.Arn == identityArn || (u.Arn.EndsWith("*") && identityArn.StartsWith(u.Arn.Substring(0, u.Arn.Length - 1)))); if (userMap == null) { throw new Exception("user has no mapping"); } ByteString sig = ByteString.Empty; if (!(request.Nonce?.IsEmpty ?? true)) { var nonce = request.Nonce.ToByteArray(); var nkeys = Nkeys.FromSeed(userMap.NKey); sig = ByteString.CopyFrom(nkeys.Sign(nonce)); } return(new AwsIamAuthReply { Jwt = userMap.JWT, NonceSigned = sig, IdentityArn = httpRespResult.GetCallerIdentityResult?.Arn, }); }