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 finalUSER rootRUN whoamiRUN apt update && apt dist-upgrade -yADD ca/ca.crt /usr/local/share/ca-certificates/ca.crtRUN chmod 644 /usr/local/share/ca-certificates/*RUN update-ca-certificatesRUN DEBIAN_FRONTEND=noninteractive apt install -y krb5-config krb5-userCOPY krb5.conf /etc/krb5.confRUN mkdir /appRUN echo BQIAAA..== | base64 -d > /app/is.k01.HTTP.keytabWORKDIR /app#RUN docker versionRUN groupadd --gid 1000 app && useradd --uid 1000 --gid app --shell /bin/bash -d /app appRUN 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.shENV KRB5_KTNAME=/app/is.k01.HTTP.keytabENV KRB5_TRACE=/dev/stdoutENV ASPNETCORE_URLS=http://*:80;https://+:443RUN chown app:app /app -RUSER app
the app is being run by run.sh
service syslog-ng startkinit HTTP/is.k01.mydomain.com@MYDOMAIN.COM -k -t /app/is.k01.HTTP.keytabklistdotnet dev-certs httpsdotnet /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("\n", 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("\n", 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\n"); 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)
Authenticate .NET Core Client of SQL Server with Integrated Security from Linux Docker Container