在Linux Docker容器中的Windows身份验证

27
我正在尝试在Kubernetes下的Linux Docker容器中使用Windows身份验证。我遵循了这些设置:https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.1&tabs=visual-studio#kestrel。应用程序是基于.NET Core 3,使用Microsoft.AspNetCore.Authentication.Negotiate NuGet,在kestrel上运行。我已经添加了


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

以及

app.UseAuthentication();

并设置我的开发基础镜像为

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

TFS中的构建管道创建基于上述内容的应用程序Docker镜像,并添加以下环境变量,还将构建内容复制到/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

该应用程序由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列出分配SPN给机器的主体

在IE和Firefox中,我已将network.negotiate-auth.trusted-uris添加到我的应用程序中

然而,我得到了登录对话框,但无法成功登录

所以问题是:

如何使用Microsoft.AspNetCore.Authentication.Negotiate包启用调试日志?

我的假设是,这个包没有正确地与Kerberos通信,可能缺少某个包,没有运行或其他原因。

还要注意,容器和.NET应用程序已成功连接到域,因为我使用集成安全性连接到数据库,它可以正常工作。

**** 编辑 > 答案第一部分

要启用日志记录,应该在kestrel中启用日志记录:

在appsettings.json中:

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

在 program.cs 文件中:
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
    logging.AddFilter("Microsoft", LogLevel.Debug);
    logging.AddFilter("System", LogLevel.Debug);
    logging.ClearProviders();
    logging.AddConsole();
})
.ConfigureWebHostDefaults(webBuilder =>
{

在 Startup.cs 中,可以跟踪协商事件:
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;
            }
        };
    }
);

**** 编辑

与此同时,根据我的目标,在 .net core docker web 应用程序中允许 Windows 认证,我正在查看 .net core、corefx 的源代码,并将 auth 代码截断为以下示例控制台应用程序:

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);
}

我发现这个库与System.Net.NTAuthentication通信,后者又与System.Net.Security.NegotiateStreamPal通信,最终与unix版本的Interop.NetSecurityNative.InitSecContext交互。

这应该会在操作系统中触发GSSAPI。

在dotnet runtime git中,他们告诉我们需要gss-ntlmssp才能使其正常工作,即使在aspnet core文档中没有提到。

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

尽管如此,我已经编译了gss-ntlmssp并发现没有这个库会抛出错误“请求的机制不受支持。”,而使用我的库会抛出“未提供凭据,或凭据不可用或无法访问。”的错误,但从未访问任何gss_*方法。

我通过向文件添加日志条目来测试gss方法的使用情况,但从未出现过..例如:

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);
}

因此,.net 调用 gssapi,而 gssapi 并未调用机制。

我已在 CentOS7 虚拟机、Ubuntu Windows 子系统和 Debian Docker 镜像(自定义的 mcr.microsoft.com/dotnet/core/sdk:3.1-buster)中观察到同样的行为。

现在的问题是,我该如何调试 gssapi?

我假设当前的 gssapi 是由这个库管理的:

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]
 0x0000000000000001 (NEEDED)             Shared library: [libk5crypto.so.3]
 0x0000000000000001 (NEEDED)             Shared library: [libcom_err.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libkrb5support.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libdl.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libkeyutils.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libresolv.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000e (SONAME)             Library soname: [libgssapi_krb5.so.2]
 0x000000000000000c (INIT)               0xb1d8
 0x000000000000000d (FINI)               0x3ebcc
 0x0000000000000019 (INIT_ARRAY)         0x24a120
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x24a128
 0x000000000000001c (FINI_ARRAYSZ)       16 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x1f0
 0x0000000000000005 (STRTAB)             0x3048
 0x0000000000000006 (SYMTAB)             0x720
 0x000000000000000a (STRSZ)              9167 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000003 (PLTGOT)             0x24b000
 0x0000000000000002 (PLTRELSZ)           8088 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x9240
 0x0000000000000007 (RELA)               0x58b0
 0x0000000000000008 (RELASZ)             14736 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffc (VERDEF)             0x5788
 0x000000006ffffffd (VERDEFNUM)          3
 0x000000006ffffffe (VERNEED)            0x57e0
 0x000000006fffffff (VERNEEDNUM)         4
 0x000000006ffffff0 (VERSYM)             0x5418
 0x000000006ffffff9 (RELACOUNT)          504
 0x0000000000000000 (NULL)               0x0

到目前为止,我已经从MIT源代码编译了新的最新GSSAPI,并发现它抛出了错误"An unsupported mechanism was requested.",因为GSSAPI需要GSS解释器,而不提供。在CentOS 7中,我遇到了另一个问题,即openssl库使用不兼容的共享kerberos库,因此yum停止工作。

***编辑

我发现gss-ntlmssp有标志GSS_C_MA_NOT_DFLT_MECH,因此它会失败并显示消息"No credentials were supplied, or the credentials were unavailable or inaccessible."。解决方法是构建自定义的gss-ntlmssp,去掉这个属性,因为我想将其用作默认身份验证机制。

我的样本控制台应用程序现在可以检查凭据,我将尝试将其工作放入docker容器中。

***编辑

我成功地在Kubernetes中运行了我的ConsoleApp:

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

RUN mkdir /app

RUN apt install -y mc sudo syslog-ng python3-software-properties software-properties-common packagekit git gssproxy vim
RUN apt install -y autoconf automake libxslt-dev doxygen findutils libgettextpo-dev libtool m4 make libunistring-dev libssl-dev zlib1g-dev gettext xsltproc libxml2-utils libxml2-dev xml-core docbook-xml docbook-xsl bison libkrb5-dev
RUN systemctl enable syslog-ng
RUN mkdir /src 
RUN cd /src && wget https://web.mit.edu/kerberos/dist/krb5/1.18/krb5-1.18.tar.gz
RUN cd /src && tar -xf krb5-1.18.tar.gz
RUN cd /src/krb5-1.18/src && ./configure && make && make install

RUN cd /src && git clone https://github.com/scholtz/gss-ntlmssp.git 
RUN cd /src/gss-ntlmssp/ && autoreconf -f -i && ./configure && make && make install
RUN cp /src/gss-ntlmssp/examples/mech.ntlmssp.conf /etc/gss/mech.d/mech.ntlmssp.conf

COPY testgss /testgss
RUN cd /testgss && dotnet ConsoleApp3.dll

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

RUN echo BQIA..AAAB | base64 -d > /app/user.keytab
RUN echo BQIA..oQ== | base64 -d > /etc/krb5.keytab
RUN echo BQIA..oQ== | base64 -d > /app/is.k01.HTTP.keytab
RUN echo BQIA..AAA= | base64 -d > /app/is.k01.kerb.keytab

COPY krb5.conf /etc/krb5.conf
COPY krb5.conf /usr/local/etc/krb5.conf
RUN ln -s /etc/gss /usr/local/etc/gss

RUN cd /app
WORKDIR /app

在此输入图片描述

然而,我现在遇到了这个错误:

System.Exception: An authentication exception occured (0xD0000/0x4E540016).
 ---> Interop+NetSecurityNative+GssApiException: GSSAPI operation failed with error - Unspecified GSS failure.  Minor code may provide more information (Feature not available).
   at System.Net.Security.NegotiateStreamPal.GssAcceptSecurityContext(SafeGssContextHandle& context, Byte[] buffer, Byte[]& outputBuffer, UInt32& outFlags)
   at System.Net.Security.NegotiateStreamPal.AcceptSecurityContext(SafeFreeCredentials credentialsHandle, SafeDeleteContext& securityContext, ContextFlagsPal requestedContextFlags, Byte[] incomingBlob, ChannelBinding channelBinding, Byte[]& resultBlob, ContextFlagsPal& contextFlags)

***编辑 现在它在这里失败: gssntlm_init_sec_context.. gssntlm_acquire_cred.. gssntlm_acquire_cred_from..

现在它在这里失败:gssntlm_init_sec_context、gssntlm_acquire_cred和gssntlm_acquire_cred_from。
    if (cred_store != GSS_C_NO_CRED_STORE) {
        retmin = get_creds_from_store(name, cred, cred_store);
    } else {
        retmin = get_user_file_creds(name, cred);
        if (retmin) {
            retmin = external_get_creds(name, cred);
        }
    }

由于我想要从AD中验证用户,而没有设置特定的文件,因此get_user_file_creds()返回错误。

external_get_creds()在这里失败:

    wbc_status = wbcCredentialCache(&params, &result, NULL);
    if(!WBC_ERROR_IS_OK(wbc_status)) goto done;

external_get_creds尝试使用winbind库进行身份验证,显然在凭据缓存中没有用户。

我成功地使用了Samba提供的winbind库进行编译。

现在的问题是:如何设置winbind库与AD通信?

*** 编辑

我尝试使用.net 5,因为在GitHub上有人告诉我NTLM可以在.net 5中工作。但是我得到了与.net 3.1相同的结果。

我尝试过的Docker镜像:

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

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

RUN mkdir /app

RUN apt install -y mc sudo syslog-ng python3-software-properties software-properties-common packagekit git gssproxy vim apt-utils
RUN apt install -y autoconf automake libxslt-dev doxygen findutils libgettextpo-dev libtool m4 make libunistring-dev libssl-dev zlib1g-dev gettext xsltproc libxml2-utils libxml2-dev xml-core docbook-xml docbook-xsl bison libkrb5-dev
RUN systemctl enable syslog-ng
RUN mkdir /src 


#RUN cd /src && git clone https://github.com/scholtz/gss-ntlmssp.git 
RUN DEBIAN_FRONTEND=noninteractive apt install -y libwbclient-dev samba samba-dev
#RUN cat /usr/include/samba-4.0/wbclient.h

COPY gss-ntlmssp /usr/local/src/gss-ntlmssp
RUN cd /usr/local/src/gss-ntlmssp/ && autoreconf -f -i && ./configure && make && make install
RUN cp /usr/local/src/gss-ntlmssp/examples/mech.ntlmssp.conf /etc/gss/mech.d/mech.ntlmssp.conf
RUN groupadd --gid 1000 app && useradd --uid 1000 --gid app --shell /bin/bash -d /app app

RUN echo BQIAAABMA..ArHdoQ== | base64 -d > /etc/krb5.keytab

COPY krb5.conf /etc/krb5.conf
COPY smb.conf /etc/samba/smb.conf
COPY krb5.conf /usr/local/etc/krb5.conf

RUN DEBIAN_FRONTEND=noninteractive apt install -y winbind

ENV KRB5_TRACE=/dev/stdout

RUN mkdir /src2
WORKDIR /src2
RUN dotnet --list-runtimes
RUN dotnet new webapi --auth Windows 
RUN dotnet add package Microsoft.AspNetCore.Authentication.Negotiate


RUN sed -i '/services.AddControllers/i services.AddAuthentication(Microsoft.AspNetCore.Authentication.Negotiate.NegotiateDefaults.AuthenticationScheme).AddNegotiate();' Startup.cs 

RUN sed -i  '/app.UseAuthorization/i app.UseAuthentication();' Startup.cs
run echo a
RUN cat Startup.cs

RUN dotnet restore
RUN dotnet build


ENV ASPNETCORE_URLS="http://*:5002;https://*:5003"
EXPOSE 5002
EXPOSE 5003

RUN cd /app
WORKDIR /app

docker run -it -p 5003:5003 -it registry.k01.mydomain.com/k01-devbase:latest

在 Docker 容器中:
kinit HTTP/myuser@MYDOMAIN.COM -k -t /etc/krb5.keytab
klist

enter image description here

dotnet run src2.dll

我已经在gssntlmssp库中放置了自己的调试信息,并将其写入文件。

在此输入图片描述

cat /tmp/gss-debug.log

在这里输入图片描述

这正是我使用 .net core 3.1 结束的地方。

wbcCredentialCache(samba lib)失败的原因是找不到缓存的凭据。

这是我的 krb5.conf 文件:

[appdefaults]
    default_lifetime      = 25hrs
    krb4_convert          = false
    krb4_convert_524      = false

    ksu = {
        forwardable       = false
    }

    pam = {
        minimum_uid       = 100
        forwardable       = true
    }

    pam-afs-session = {
        minimum_uid       = 100
    }

[libdefaults]
    default_realm         = MYDOMAIN.COM

[realms]
     MYDOMAIN.COM = {
        kdc            = DC01.MYDOMAIN.COM
        default_domain = MYDOMAIN.COM
    }

[domain_realm]
    mydomain.com.    = MYDOMAIN.COM
    .mydomain.com.    = MYDOMAIN.COM

[logging]
default      = CONSOLE
default      = SYSLOG:INFO
default = FILE:/var/log/krb5-default.log
kdc = CONSOLE
kdc = SYSLOG:INFO:DAEMON
kdc = FILE:/var/log/krb5-kdc.log
admin_server = SYSLOG:INFO
admin_server = DEVICE=/dev/tty04
admin_server = FILE:/var/log/krb5-kadmin.log

并且是Samba文件的一部分:

[global]
  security = domain
  workgroup = mydomain.com
  password server = *
  idmap config * : range = 16777216-33554431
  template shell = /bin/bash
  winbind use default domain = yes
  winbind offline logon = false
  wins server = 10.0.0.2

在我看来,我更希望使用NTLM而不是Negotiate,因为据我所知,Negotiate在浏览器中并不受支持。例如,在Firefox中,用户必须设置关于:config以进行Negotiate服务器的配置。通配符不受支持,...

尽管如此,似乎我将无法使用NTLM运行.NET Core 5 Web应用程序,因此我现在将尝试使用一些默认的Kerberos机制来设置它,而不使用gssntlmssp库。 我的krb5.conf设置有什么问题吗?

****编辑 因此,我现在正在尝试两种不同的方法:

  1. NTLM-在我看来,这是首选的方式,因为我已经看到了例如在iis express中,ntlm验证用户时没有对话框,并且不需要在Firefox或通过组策略进行任何特殊配置(如果我错了,请纠正我)
  2. Negotiate

关于Negotiate,我已经取得了一些进展...

通过这个Docker容器,我能够绕过不支持的机制:

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

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

RUN mkdir /app

RUN apt install -y mc sudo syslog-ng python3-software-properties software-properties-common packagekit git gssproxy vim apt-utils
RUN apt install -y autoconf automake libxslt-dev doxygen findutils libgettextpo-dev libtool m4 make libunistring-dev libssl-dev zlib1g-dev gettext xsltproc libxml2-utils libxml2-dev xml-core docbook-xml docbook-xsl bison libkrb5-dev
RUN systemctl enable syslog-ng
RUN mkdir /src 

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

RUN echo BQIAAAA8..vI | base64 -d > /etc/krb5.keytab


COPY krb5.conf /etc/krb5.conf
COPY krb5.conf /usr/local/etc/krb5.conf
ADD ca/is.k01.mydomain.com.p12 /etc/ssl/certs/is.k01.mydomain.com.pfx

RUN cd /app
WORKDIR /app

现在我有另一个问题:

在keytab中找到HTTP/is.k01.mydomain.com@MYDOMAIN.com kvno 3的请求票据服务器,但未使用rc4-hmac加密类型

这对我来说似乎意味着keytab没有使用rc4-hmac加密类型,这是正确的,因为keytab是使用生成的

ktpass -princ HTTP/is.k01.mydomain.com@MYDOMAIN.COM -pass *****  -mapuser MYDOMAIN\is.k01.kerb -pType KRB5_NT_PRINCIPAL -out c:\temp\is.k01.HTTP.keytab -crypto AES256-SHA1

根据 .net 文档所述。

我无法禁止使用 rc4-hmac 并仅允许使用更新的编码方式,因此我请求我的基础架构部门生成带有旧的 rc4-hmac 编码的新密钥表。

这一步使我进一步了解到以下错误:在密钥表中找不到 HTTP/is.k01.mydomain.com@MYDOMAIN.COM kvno 4 的请求票证服务器;密钥表可能已过期*

这非常奇怪,因为密钥表不可能过期,密码在一小时前生成密钥表时是100%有效的且未更改,并且网上没有任何关于“在密钥表中找不到 kvno 4”的信息 - 在 Google 搜索只返回 4 条结果。

**** 编辑

最终,我成功地解决了问题 :)

“在密钥表中找不到 kvno 4”的问题在于 krb5.conf 文件,我为了强制使用 aes 加密而添加了以下行

#   default_tkt_enctypes  = aes256-cts-hmac-sha1-96 aes256-cts-hmac-sha1-9
#   default_tgs_enctypes  = aes256-cts-hmac-sha1-96 aes256-cts-hmac-sha1-9
#   permitted_enctypes    = aes256-cts-hmac-sha1-96 aes256-cts-hmac-sha1-9

我将它们注释掉后,使用Negotiate进行身份验证开始工作了。我已经测试了使用 .net 5 的 NTLM,但仍无法正常工作。

在上述构建的Docker容器中,Negotiate使用的krb5.conf文件如下:

[appdefaults]
    default_lifetime      = 25hrs
    krb4_convert          = false
    krb4_convert_524      = false

    ksu = {
        forwardable       = false
    }

    pam = {
        minimum_uid       = 100
        forwardable       = true
    }

    pam-afs-session = {
        minimum_uid       = 100
    }

[libdefaults]
    default_realm         = MYDOMAIN.COM

[realms]
     MYDOMAIN.COM = {
        kdc            = DC02.MYDOMAIN.COM
        default_domain = MYDOMAIN.COM
    }

[domain_realm]
    mydomain.com.    = MYDOMAIN.COM
    .mydomain.com.    = MYDOMAIN.COM

[logging]
default      = CONSOLE
default      = SYSLOG:INFO
default = FILE:/var/log/krb5-default.log
kdc = CONSOLE
kdc = SYSLOG:INFO:DAEMON
kdc = FILE:/var/log/krb5-kdc.log
admin_server = SYSLOG:INFO
admin_server = DEVICE=/dev/tty04
admin_server = FILE:/var/log/krb5-kadmin.log

现在的问题是:有没有办法允许多个服务运行协商协议,而不需要逐个添加到spn中,并手动设置浏览器?

目前每个新的Web服务都必须具备以下要求:

setspn -S HTTP/mywebservice.mydomain.com mymachine
setspn -S HTTP/mywebservice@MYDOMAIN.COM mymachine

必须在Internet Explorer的设置中允许以下内容:Security > Web > Details > 在那里列出域名。

在Firefox中,需在about:config > network.negotiate-auth.trusted-uris中进行设置。

据我所知,Chrome采用Internet Explorer的设置。

我认为可以通过域组策略以某种方式更新Internet Explorer的设置。有人有任何想法吗?

****编辑 我已经在浏览器中测试了使用通配符的域名协商设置,以下是结果:

  • Chrome:支持*.k01.mydomain.com
  • IE:支持*.k01.mydomain.com
  • Firefox(73.0.1(64位)):不支持*.k01.mydomain.com——只支持完整域名,例如is.k01.mydomain.com
  • Edge 44.18362.449.0 - 不知道为什么,没有一个IE设置被传播...不适用于*.k01.mydomain.com或is.k01.mydomain.com

****编辑 我已开始使用Negotiate的Win身份验证,但现在在.NET Core中遇到了一些问题。

在IIS Express下运行的此代码以MYDOMAIN\myuser的形式显示用户:

var userId = string.Join(',', User?.Identities?.Select(c => c.Name)) ?? "?";

在Linux中,它显示为myuser@mydomain.com。

在IIS Express下,User.Indentities.First()是WindowsIdentity,我可以列出用户的所有组。

在Linux下,User.Indentities.First()是ClaimsIdentity,没有组信息。

当我尝试在IIS Express中通过组来限制它时,我得到以下结果:

//Access granted
[Authorize(Roles = "MYDOMAIN\\GROUP1")]

//403
[Authorize(Roles = "MYDOMAIN\\GROUP_NOT_EXISTS")]

使用Negotiate的Linux kestrel:

//403
[Authorize(Roles = "MYDOMAIN\\GROUP1")]

看起来,kestrel中的negotiate无法正确列出组。现在我将调查如何在kestrel中获取WindowsIdentity


3
嗨,Scholtz,你是否已经彻底解决了这个问题?如果是,并且你还记得解决方案,那么如果你能在你的问题中添加一个答案,那就太好了。 - Kevin
嗨Scholtz,你能解决这个问题了吗? - Thili
1
嗨,不是的...我创建了一个服务,从LDAP获取数据,并使用JWT令牌提供用户及其组信息。 - Scholtz
2个回答

16

这篇文章是对事情运作方式的误解的一个很好的例子。我不建议完全按照作者描述的方式来操作。

相反,我推荐学习Kerberos认证,了解它是如何工作的,需要哪些设置。这篇文章阐述得很清晰易懂。

首先, 如果你使用浏览器(例如Fiddler)对HTTP流量进行分析,你可以在第二个请求中找到一个TGS令牌。

  • 如果它以Negotiate TlR开头,则使用的验证方式是NTLM。
  • 如果它以Negotiate YII开头,则使用的验证方式是Kerberos。

其次, 就像David之前说的那样,ASP.NET Core 3.1在Linux上完全不支持NTLM。因此,如果你有TlR令牌和ntlm-gssapi机制,你将会收到"未提供凭据,或者凭据不可用或无法访问"的错误信息。 如果你有TlR令牌并使用默认的Kerberos机制,你将会收到"请求了一个不支持的机制"的错误信息。

接下来, 让你的应用程序正常工作的唯一方法是正确地创建SPN并为Kerberos认证生成keytab。不幸的是,这个过程没有很好的文档说明。因此,我在这里提供一个例子来使事情更加清晰明了。

假设你有:

  • AD域为MYDOMAIN.COM
  • webapp.webservicedomain.com是主机名的Web应用程序。它可以以mydomain.com结尾,但在我的情况下不是这样。
  • 已加入AD的Windows计算机名称为mymachine
  • 计算机帐户为MYDOMAIN\mymachine

根据此处所述的说明,您需要执行以下操作:

  1. 向机器帐户添加新的Web服务SPN:
  • setspn -S HTTP/webapp.webservicedomain.com mymachine
  • setspn -S HTTP/webapp@MYDOMAIN.COM mymachine
  1. 使用ktpass生成keytab文件
  • ktpass -princ HTTP/webapp.webservicedomain.com@MYDOMAIN.COM -pass myKeyTabFilePassword -mapuser MYDOMAIN\mymachine$ -pType KRB5_NT_PRINCIPAL -out c:\temp\mymachine.HTTP.keytab -crypto AES256-SHA1*。

*确保MYDOMAIN\mymachine在AD中允许AES256-SHA1

最后,

完成以上所有工作并将应用程序部署到带有 keytab 的 Linux 容器后,集成的 Windows 身份验证应该可以正常工作。我的实验表明,您可以在任何地方使用 keytab,而不仅仅是在名为“mymachine”的主机上。


1
如果它以 Negotiate TlR 开头,那么你正在使用 NTLM 进行身份验证。好的,我怎样才能让它不这样做?为什么它要使用 NTLM 而不是 Kerberos?我不想让它使用 NTLM。 - Arrow_Raider
@Arrow_Raider 你好,我之前也遇到了同样的问题,直到成功生成SPNs和keytab文件。请尝试按照上面的建议操作。同时,请确保你的应用程序主机名在浏览器的受信任网站内部网区域中。 - ZEvS
2
我刪除了 SPN,然後再試一次,然後它們就可以運作了。因此對於其他人來說:我非常確定 SPN 是使其使用 krb 而不是 ntlm 的原因,您必須使用 setspn 命令而不是 adsiedit。 - Arrow_Raider
3
SPN确实是Kerberos的关键。但需要注意的是,客户端(浏览器)决定SPN是什么,服务提供者服务器和域必须尊重这一点。在我的情况下,由于我在开发过程中使用了非标准端口运行服务,因此我尝试将SPN注册为HTTP / [hostname]:6000。浏览器忽略了端口部分,并要求域为SPN“HTTP / [hostname_without_port]”颁发票据。当域找不到它时,会静默回退到NTLM(在.NET中不受支持)。 - huancz
@ZEvS 你知道在dotnet 5中是否有任何更改吗?我遇到了相同的错误。当我尝试在docker中本地运行我的服务时,你能告诉我如何进行setspn吗?我的问题 - Mozartos
您可以通过生成keytab文件来使用“-crypto ALL”,以免限制于特定的加密算法。但我不知道是否存在安全问题。 - Reza Bayat

3
在 dotnet runtime git 中,他们告诉我们需要 gss-ntlmssp 才能使其工作,即使在 aspnet core 文档中没有任何提及。'gss-ntlmssp' 包是支持 GSS-API 的 NTLM 协议的插件。它支持原始的 NTLM 协议以及在“Negotiate”(SPNEGO 协议)使用时从 Kerberos 到 NTLM 的降级。参考:https://learn.microsoft.com/en-us/openspecs/windows_protocols/MS-SPNG/f377a379-c24f-4a0f-a3eb-0d835389e28a
从上面的讨论和您发布的图片中可以看出,该应用程序正在尝试使用 NTLM 而不是 Kerberos。您可以通过编码为 base64 的令牌以“T”开头而不是以“Y”开头来确定。ASP.NET Core 服务器(Kestrel)根本不支持在 Linux 上服务器端使用 NTLM。它只提供了 'Www-Authenticate: Negotiate' 发送回客户端。通常这意味着将使用 Kerberos。Negotiate 可以降级到使用 NTLM。但是,在 ASP.NET Core 中除了 .NET 5 之外都不适用。这个问题正在这里讨论:https://github.com/dotnet/aspnetcore/issues/19397
如果您的应用程序不希望降级到 NTLM,那么可能 Kerberos 环境没有完全设置好。这可能由多种问题引起,包括 SPN 和 Linux keytab 文件不正确。它也可能是由客户端尝试使用不属于 Kerberos 领域的用户名/密码引起的。我建议继续在 aspnet core 仓库问题讨论中进行讨论。

1
嗨,感谢您的回复。我已经尝试了.NET 5,但是结果与.NET Core 3.1相同。我将在Git上继续关注此对话线程。 - Scholtz
你好,如果可以的话,能否提供一个如何使用Kerberos的链接?虽然CodeProject上有一份文档,但并没有太大帮助。谢谢。 - Hameed Syed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接