Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

kerberos - Windows authentication in linux docker container

i am trying to use windows authentication in linux docker container under kubernetes.

I am following this settings: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.1&tabs=visual-studio#kestrel

App is in .net core3, with nuget Microsoft.AspNetCore.Authentication.Negotiate and running in kestrel

I have added the

services.AddAuthentication(Microsoft.AspNetCore.Authentication.Negotiate.NegotiateDefaults.AuthenticationScheme).AddNegotiate();

as well as

app.UseAuthentication();

and setup my devbase image as

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster as final
USER root
RUN whoami
RUN apt update && apt dist-upgrade -y

ADD ca/ca.crt /usr/local/share/ca-certificates/ca.crt
RUN chmod 644 /usr/local/share/ca-certificates/*
RUN update-ca-certificates


RUN DEBIAN_FRONTEND=noninteractive apt install -y krb5-config krb5-user

COPY krb5.conf /etc/krb5.conf
RUN mkdir /app

RUN echo BQIAAA..== | base64 -d > /app/is.k01.HTTP.keytab
WORKDIR /app

#RUN docker version

RUN groupadd --gid 1000 app && useradd --uid 1000 --gid app --shell /bin/bash -d /app app

RUN apt install -y mc sudo syslog-ng realmd gss-ntlmssp

the build in tfs pipeline creates app docker image derived from above and adds following env variables, also copies build to /app

RUN chmod 0700 run.sh
ENV KRB5_KTNAME=/app/is.k01.HTTP.keytab
ENV KRB5_TRACE=/dev/stdout
ENV ASPNETCORE_URLS=http://*:80;https://+:443
RUN chown app:app /app -R
USER app

the app is being run by run.sh

service syslog-ng start
kinit HTTP/is.k01.mydomain.com@MYDOMAIN.COM -k -t /app/is.k01.HTTP.keytab
klist
dotnet dev-certs https
dotnet /app/SampleApi.dll

klist lists the principal which has assigned the SPN to the machine

in ie and firefox i have added the network.negotiate-auth.trusted-uris to my app

however i am getting the login dialog with no success to log in

so the question is:

How can I enable debug log with Microsoft.AspNetCore.Authentication.Negotiate package?

My assumption is that this package does not communicate with kerberos properly, perhaps some package is missing, not running or something.

Also note that the container and .net app is connected successfully to the domain because I use integrated security for connection to the database which works.

**** Edit > Answer to first part

To enable logs, one should enable logs in kestrel: in appsettings.json:

  "Logging": {
    "LogLevel": {
      "Default": "Debug",
    }
  },

In program.cs:

Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
    logging.AddFilter("Microsoft", LogLevel.Debug);
    logging.AddFilter("System", LogLevel.Debug);
    logging.ClearProviders();
    logging.AddConsole();
})
.ConfigureWebHostDefaults(webBuilder =>
{

In Startup.cs one can track the negotiate events:

services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate(

    options =>
    {
        options.PersistKerberosCredentials = true;
        options.Events = new NegotiateEvents()
        {
            OnAuthenticated = challange =>
            {
                ..
            },
            OnChallenge = challange =>
            {
                ..
            },
            OnAuthenticationFailed = context =>
            {
                // context.SkipHandler();
                Console.WriteLine($"{DateTimeOffset.Now.ToString(czechCulture)} OnAuthenticationFailed/Scheme: {context.Scheme.Str()}, Request: {context.Request.Str()}");
                Console.WriteLine("context?.HttpContext?.Features?.Select(f=>f.Key.Name.ToString())");
                var items = context?.HttpContext?.Features?.Select(f => "- " + f.Key?.Name?.ToString());
                if (items != null)
                {
                    Console.WriteLine(string.Join("
", items));
                }
                Console.WriteLine("context.HttpContext.Features.Get<IConnectionItemsFeature>()?.Items " + context.HttpContext.Features.Get<IConnectionItemsFeature>()?.Items?.Count);
                var items2 = context.HttpContext?.Features.Get<IConnectionItemsFeature>()?.Items?.Select(f => "- " + f.Key?.ToString() + "=" + f.Value?.ToString());
                if (items2 != null) {
                    Console.WriteLine(string.Join("
", items2));
                }
                return Task.CompletedTask;
            }
        };
    }
);

**** Edit

Meanwhile according my goal to allow windows authentication in .net core docker web app i was going through the source code of .net core, and corefx and trucated the auth code to this sample console app:

try
{
    var token = "MyToken==";
    var secAssembly = typeof(AuthenticationException).Assembly;
    Console.WriteLine("var ntAuthType = secAssembly.GetType(System.Net.NTAuthentication, throwOnError: true);");
    var ntAuthType = secAssembly.GetType("System.Net.NTAuthentication", throwOnError: true);
    Console.WriteLine("var _constructor = ntAuthType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).First();");
    var _constructor = ntAuthType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).First();
    Console.WriteLine("var credential = CredentialCache.DefaultCredentials;");
    var credential = CredentialCache.DefaultCredentials;
    Console.WriteLine("var _instance = _constructor.Invoke(new object[] { true, Negotiate, credential, null, 0, null });");
    var _instance = _constructor.Invoke(new object[] { true, "Negotiate", credential, null, 0, null });

    var negoStreamPalType = secAssembly.GetType("System.Net.Security.NegotiateStreamPal", throwOnError: true);
    var _getException = negoStreamPalType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Where(info => info.Name.Equals("CreateExceptionFromError")).Single();


    Console.WriteLine("var _getOutgoingBlob = ntAuthType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => info.Name.Equals(GetOutgoingBlob) && info.GetParameters().Count() == 3).Single();");
    var _getOutgoingBlob = ntAuthType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => info.Name.Equals("GetOutgoingBlob") && info.GetParameters().Count() == 3).Single();
    Console.WriteLine("var decodedIncomingBlob = Convert.FromBase64String(token);;");
    var decodedIncomingBlob = Convert.FromBase64String(token);
    Console.WriteLine("var parameters = new object[] { decodedIncomingBlob, false, null };");
    var parameters = new object[] { decodedIncomingBlob, false, null };
    Console.WriteLine("var blob = (byte[])_getOutgoingBlob.Invoke(_instance, parameters);");
    var blob = (byte[])_getOutgoingBlob.Invoke(_instance, parameters);
    if (blob != null)
    {
        Console.WriteLine("var out1 = Convert.ToBase64String(blob);");
        var out1 = Convert.ToBase64String(blob);
        Console.WriteLine(out1);
    }
    else
    {
        Console.WriteLine("null blob value returned");


        var securityStatusType = secAssembly.GetType("System.Net.SecurityStatusPal", throwOnError: true);
        var _statusException = securityStatusType.GetField("Exception");
        var securityStatus = parameters[2];
        var error = (Exception)(_statusException.GetValue(securityStatus) ?? _getException.Invoke(null, new[] { securityStatus }));
        Console.WriteLine("Error:");
        Console.WriteLine(error);
        Console.WriteLine("securityStatus:");
        Console.WriteLine(securityStatus.ToString());
    }
}
catch(Exception exc)
{
    Console.WriteLine(exc.Message);
}

So i found out that the library communicates with System.Net.NTAuthentication which communicates with System.Net.Security.NegotiateStreamPal which communicates with unix version of Interop.NetSecurityNative.InitSecContext

which should somehow trigger the GSSAPI in os

In dotnet runtime git they tell us that gss-ntlmssp is required for this to work even that it is not mentioned anyhow in the aspnet core documentation.

https://github.com/dotnet/runtime/issues?utf8=%E2%9C%93&q=gss-ntlmssp

Nevertheless I have compiled the gss-ntlmssp and found out that without this library it throws error "An unsupported mechanism was requested.". With my library it throws error "No credentials were supplied, or the credentials were unavailable or inaccessible.", but never access to any gss_* methods.

I have tested usage of gss methods by adding the log entry to file which never occured.. fe:

OM_uint32 gss_init_sec_context(OM_uint32 *minor_status,
                               gss_cred_id_t claimant_cred_handle,
                               gss_ctx_id_t *context_handle,
                               gss_name_t target_name,
                               gss_OID mech_type,
                               OM_uint32 req_flags,
                               OM_uint32 time_req,
                               gss_channel_bindings_t input_chan_bindings,
                               gss_buffer_t input_token,
                               gss_OID *actual_mech_type,
                               gss_buffer_t output_token,
                               OM_uint32 *ret_flags,
                               OM_uint32 *time_rec)
{
   FILE *fp;
   fp = fopen("/tmp/gss-debug.log", "w+");
   fprintf(fp, "gss_init_sec_context
");
   fclose(fp);
    return gssntlm_init_sec_context(minor_status,
                                    claimant_cred_handle,
                                    context_handle,
                                    target_name,
                                    mech_type,
                                    req_flags,
                                    time_req,
                                    input_chan_bindings,
                                    input_token,
                                    actual_mech_type,
                                    output_token,
                                    ret_flags,
                                    time_rec);
}

So .net calls gssapi, and gssapi does not call mechanism.

I have observed the same behavior in centos7 vm, ubuntu windows subsystem, and debian docker image (customized mcr.microsoft.com/dotnet/core/sdk:3.1-buster)

So the question now is, how can I debug gssapi ?

I assume my current gssapi is managed by this library:

readelf -d /usr/lib64/libgssapi_krb5.so
Dynamic section at offset 0x4aa48 contains 34 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libkrb5.so.3]
 0x00000000000

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This article is a good example of misunderstanding how things work. I don't recommend to follow the way(like I did) author described here at all .

Instead, I would recommend learning about Kerberos authentication, how it works, what settings it requires. This article visualizes it good.

First, If you profile http traffic coming from browser(user Fiddler, for example) you can find a TGS token in the second request.

  • If it starts with Negotiate TlR then you're doing auth over NTLM.
  • If it starts with Negotiate YII then you're doing auth over Kerberos.

Second, Like David said before ASP.NET Core 3.1 doesn't support NTLM on Linux at all. So if you have TlR token and ntlm-gssapi mechanism you will get "No credentials were supplied, or the credentials were unavailable or inaccessible." error. If you have TlR token and use default Kerberos mechanism you will get "An unsupported mechanism was requested."

Next, The only way to get your app works well is to create SPNs and generate keytab correctly for Kerberos authentication. Unfortunately, this is not documented well. So, I gonna give an example here to make things more clear.

Let's say you have:

  • AD domain MYDOMAIN.COM
  • The web application with host webapp.webservicedomain.com. This can ends with mydomain.com, but not in my case.
  • Windows machine joined to AD with name mymachine.
  • Machine account MYDOMAINmymachine

Regarding the instructions described here you need to do:

  1. Add new web service SPNs to the machine account:
  • setspn -S HTTP/webapp.webservicedomain.com mymachine
  • setspn -S HTTP/webapp@MYDOMAIN.COM mymachine
  1. Use ktpass to generate a keytab file
  • ktpass -princ HTTP/webapp.webservicedomain.com@MYDOMAIN.COM -pass myKeyTabFilePassword -mapuser MYDOMAINmymachine$ -pType KRB5_NT_PRINCIPAL -out c:empmymachine.HTTP.keytab -crypto AES256-SHA1*.

*Make sure MYDOMAINmymachine has AES256-SHA1 allowed in AD.

Finally, After making all above things done and deploying the app into Linux container with keytab the Integrated Windows Authentication is supposed to worked well. My experiment showed you can use keytab wherever you want not only on the host with name "mymachine".


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...