// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Http;
using System.Net.Http.Headers;
using Amazon.Runtime;
using System.Threading.Tasks;
using System.Threading;
using System.Security.Cryptography;
using System.IO;
using System.Globalization;
namespace AWS.Deploy.ServerMode.Client
{
///
/// This derived HttpClient is created with a handler to make sure the AWS credentials are used to create the authorization header for calls to the deploy tool server mode.
/// Instances of this class are created with the ServerModeHttpClientFactory factory.
///
public class ServerModeHttpClient : HttpClient
{
internal ServerModeHttpClient(ServerModeHttpClientAuthorizationHandler handler)
: base(handler)
{
}
}
///
/// HttpClient handler that gets the latest credentials from the client sets the authorization header.
///
public class ServerModeHttpClientAuthorizationHandler : HttpClientHandler
{
private readonly Func> _credentialsGenerator;
private readonly Aes? _aes;
private static readonly object AES_LOCK = new object();
internal ServerModeHttpClientAuthorizationHandler(Func> credentialsGenerator, Aes? aes = null)
{
_credentialsGenerator = credentialsGenerator;
_aes = aes;
}
protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var awsCreds = await _credentialsGenerator();
if(awsCreds != null)
{
var immutableCredentials = await awsCreds.GetCredentialsAsync();
AddAuthorizationHeader(request, immutableCredentials, _aes);
}
return await base.SendAsync(request, cancellationToken);
}
public static void AddAuthorizationHeader(HttpRequestMessage request, ImmutableCredentials credentials, Aes? aes = null)
{
var authParameters = new Dictionary
{
{"awsAccessKeyId", credentials.AccessKey },
{"awsSecretKey", credentials.SecretKey },
{"requestId", Guid.NewGuid().ToString() },
{"issueDate", DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss.fffZ", DateTimeFormatInfo.InvariantInfo) }
};
if(!string.IsNullOrEmpty(credentials.Token))
{
authParameters["awsSessionToken"] = credentials.Token;
}
var json = Newtonsoft.Json.JsonConvert.SerializeObject(authParameters);
string base64;
if(aes != null)
{
byte[] iv;
lock (AES_LOCK)
{
aes.GenerateIV();
iv = aes.IV;
}
var encryptor = aes.CreateEncryptor(aes.Key, iv);
using var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(json));
using var outputStream = new MemoryStream();
using (var encryptStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write))
{
inputStream.CopyTo(encryptStream);
}
base64 = $"{Convert.ToBase64String(iv)} {Convert.ToBase64String(outputStream.ToArray())}";
}
else
{
base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(json));
}
request.Headers.Authorization = new AuthenticationHeaderValue("aws-deploy-tool-server-mode", base64);
}
}
///
/// Factory for creating the ServerModeHttpClient.
///
public static class ServerModeHttpClientFactory
{
public static ServerModeHttpClient ConstructHttpClient(Func> credentialsGenerator, Aes? aes = null)
{
return new ServerModeHttpClient(new ServerModeHttpClientAuthorizationHandler(credentialsGenerator, aes));
}
}
}