Amazon Elastic Container Registry Service authentication seems different from Docker token spec

159 Views Asked by At

I am trying to get image manifest information from various container image registries. In order to authenticate, I have implemented the token specification (https://docs.docker.com/registry/spec/auth/token/) by Docker and it seems to be working with Docker Hub (public and private repositories), Azure Container Registry Service, and Google Container Registry Service. It appears that AWS Elastic Container Registry Service requires an extra call to GetAuthorizationToken API which returns the username and password to be used in Basic Http Authentication. Is my understanding correct that (accroding to Docker token spec) there is no way I could use a fixed username and password pair and then using WWW-Authenticate challenge header I could obtain a bearer token to obtain the access token?

The challenge I am facing now is, I need to special case my code to authenticate based on if it is AWS ECR vs. other registry services.

My C# code so far:

class Program
{
    internal sealed class BearerToken
    {
        public string token;
        public string access_token;
        public string scope;
        public string expires_in;
        public string issued_at;
        public string refresh_token;
    }

    private static HttpClient httpClient = new HttpClient()
    {
        Timeout = TimeSpan.FromSeconds(100)
    };

    static void Main(string[] args)
    {
        string imagename = "hello-world:latest";
        Uri manifestUri = new Uri("https://619296171463.dkr.ecr.us-east-2.amazonaws.com/v2/hello-world/manifests/latest");

        // aws access key id
        //string username = "AWSACCESSKEYID";

        // aws secret access key
        //string password = "AWSSECRETACCESSKEY";

        // aws ecr get-login output
        string username = "AWS";
        string password = "24HRSLONGPASSWORDSTRING";

        HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, manifestUri);
        requestMessage.Headers.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json");

        HttpResponseMessage responseMessage = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None).GetAwaiter().GetResult();

        if (responseMessage.StatusCode == HttpStatusCode.Unauthorized)
        {
            /*string authorizationHeaderChallenge = responseMessage.Headers.WwwAuthenticate.ToString();
            string[] challengeItems = authorizationHeaderChallenge.Split(new char[] { ' ' }, 2);
            string authScheme = challengeItems[0];
            string authParamFormat = @"(?<key>\w+)=([""'])(?<value>.*)([""'])";
            Regex authParamRegEx = new Regex(authParamFormat);
            var authParamsItems = challengeItems[1].Split(',');
            Dictionary<string, string> authParams = new Dictionary<string, string>();
            foreach (var authParam in authParamsItems)
            {
                var match = authParamRegEx.Match(authParam);
                authParams[match.Groups["key"].Value] = match.Groups["value"].Value;
            }

            UriBuilder uriBuilder = new UriBuilder($"{authParams["realm"]}");
            //string query = $"{"scope"}={"repository:containerimageinfoproject/demo-image:pull"}&{ "service"}={authParams["service"]}";
            string query = $"{"service"}={authParams["service"]}";
            uriBuilder.Query = query;

            Uri authorizationEndPoint = uriBuilder.Uri;*/

            string authenticationTokenValue = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}"));

            requestMessage = new HttpRequestMessage(HttpMethod.Get, manifestUri);
            requestMessage.Headers.Authorization =
                new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authenticationTokenValue);

            responseMessage = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None).GetAwaiter().GetResult();

            string response = responseMessage.Content.ReadAsStringAsync().GetAwaiter().GetResult();

            /*BearerToken bearerToken = JsonConvert.DeserializeObject<BearerToken>(response);

            requestMessage = new HttpRequestMessage(HttpMethod.Get, manifestUri);
            requestMessage.Headers.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json");
            requestMessage.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", bearerToken.token);

            responseMessage = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None).GetAwaiter().GetResult();*/
        }
    }
}

Here if I use (username, password) = (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) then I keep getting:

{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers: { Docker-Distribution-Api-Version: registry/2.0 Connection: keep-alive Date: Wed, 01 Aug 2018 21:40:37 GMT WWW-Authenticate: Basic realm="https://619296171463.dkr.ecr.us-east-2.amazonaws.com/",service="ecr.amazonaws.com" Content-Length: 15 Content-Type: text/plain; charset=utf-8 }}

Whereas if I use (username, password) = ("AWS", "LONGPASSWORD_GENERATED_USING_AWS_ECR_GETLOGIN"), I can get the image manifest.

0

There are 0 best solutions below