docker.net-corerestsharpntlm-authentication

Unable to query NTLM-protected APIs from within a Docker Container


We are building a Worker service that we want to host in a Docker container. This container interacts with the REST APIs for our on-premise SharePoint server. Authentication in SharePoint REST APIs uses Windows authentication.

When I run the code below from Visual Studio Code (using dotnet run on my Windows development machine), the code works without the exception thrown.

var opts = new RestClientOptions("https://sharepoint.domain.local");
opts.Credentials = new NetworkCredential("username", "password", "domain.local");

var spclient = new RestClient(opts);

var req = new RestRequest("_api/lists/getbytitle('MY_LIST')/items", Method.Get);

var resp = spclient.Execute(req);
_logger.LogDebug("Status code {x} returned from API call {y}", resp.StatusCode, resp.ResponseUri);
_logger.LogTrace("Raw response from {url}. Content: {raw}", resp.ResponseUri, resp.Content);

if(!resp.IsSuccessful)
{
    throw new Exception();
}

Now, when I run the same code in the Docker container, the API call is failing and I am getting an Unauthorized status code from the SharePoint REST API, with the following JSON response:

{
  "odata.error": {
    "code": "-2147024891, System.UnauthorizedAccessException",
    "message": {
      "lang": "en-US",
      "value": "Access denied. You do not have permission to perform this action or access this resource."
    }
  }
}

For testing purposes, I am using the same username/password as I use on my DEV machine. I am certain the username and password is provided and not empty.

Update

I've also tried specifying NTLM using the following code. No change in behaviour:

CredentialCache cc = new CredentialCache();
cc.Add("https://sharepoint.domain.local", "NTLM", "username", "password", "domain.local");


var opts = new RestClientOptions("https://sharepoint.domain.local");
opts.Credentials = cc;

var spclient = new RestClient(opts);

var req = new RestRequest("_api/lists/getbytitle('MY_LIST')/items", Method.Get);

var resp = spclient.Execute(req);
_logger.LogDebug("Status code {x} returned from API call {y}", resp.StatusCode, resp.ResponseUri);
_logger.LogTrace("Raw response from {url}. Content: {raw}", resp.ResponseUri, resp.Content);

if(!resp.IsSuccessful)
{
    throw new Exception();
}

Any ideas on how to get this working, please?


Solution

  • If anyone else runs into this problem, there are some packages you need to install into your container image for this to work.

    Let's say you have this code (note: domain is optional. Username username@domain.local is acceptable):

    var opts = new RestClientOptions("https://sharepoint.domain.local");
    opts.Credentials = new NetworkCredential("username", "password", "domain.local");
    
    var spclient = new RestClient(opts);
    
    var req = new RestRequest("_api/lists/getbytitle('MY_LIST')/items", Method.Get);
    
    var resp = spclient.Execute(req);
    _logger.LogDebug("Status code {x} returned from API call {y}", resp.StatusCode, resp.ResponseUri);
    _logger.LogTrace("Raw response from {url}. Content: {raw}", resp.ResponseUri, resp.Content);
    
    if(!resp.IsSuccessful)
    {
        throw new Exception();
    }
    

    Next, build a Dockerfile with similar contents:

    FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
    WORKDIR /source
    
    # copy csproj and restore as distinct layers
    COPY ./src .
    RUN dotnet restore
    
    # publish app and libraries
    RUN dotnet publish -c release -o /app
    
    # final stage
    FROM mcr.microsoft.com/dotnet/aspnet:8.0
    WORKDIR /app
    
    # Install necessary packages for NTLM/Kerberos
    RUN apt-get update && apt-get install -y libkrb5-3 libgssapi-krb5-2 gss-ntlmssp && rm -rf /var/lib/apt/lists/*
    
    # Create the ./logs directory
    RUN mkdir -p ./logs
    
    COPY --from=build /app .
    ENTRYPOINT [ "dotnet", "my.application.dll" ]