Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WinAuth] Clarify app configuration and deployment #12144

Closed
bdlane opened this issue Apr 24, 2019 — with docs.microsoft.com · 35 comments · Fixed by #12554
Closed

[WinAuth] Clarify app configuration and deployment #12144

bdlane opened this issue Apr 24, 2019 — with docs.microsoft.com · 35 comments · Fixed by #12554
Assignees
Labels
Pri2 Source - Docs.ms Docs Customer feedback via GitHub Issue

Comments

Copy link

bdlane commented Apr 24, 2019

Adding the web.config with the authentication elements causes a configuration error at runtime.

Starting from the 'Web Application' template in the new project dialog in Visual Studio 2019, with Windows Authentication enabled, I can run and debug the project fine.

Then, following the steps outlined in the 'Development side configuration with a local web.confg' section (i.e. adding a web.config file with the <system.webServer><security><authentication> tags), running the project returns a HTTP 500.19, with the following config error:

This configuration section cannot be used at this path. This happens when the section is locked at a parent level. Locking is either by default (overrideModeDefault="Deny"), or set explicitly by a location tag with overrideMode="Deny" or the legacy allowOverride="false".

You can fix the problem by modifying the applicationHost.config file in the solution's .vs directory to unlock the relevant sections. However,

  1. this step isn't described in the docs, and
  2. this can't be right, can it? The applicationHost.config file is auto-generated (by VS?), so might get overwritten, and will need to be done on every developer's machine.

Is this the expected behaviour?


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

@dotnet-bot dotnet-bot added the Source - Docs.ms Docs Customer feedback via GitHub Issue label Apr 24, 2019
@guardrex
Copy link
Collaborator

guardrex commented Apr 25, 2019

Thanks for the report @bdlane ... I think we need to clarify something in the topic for sure (at least for the time being): "development-side configuration" doesn't address running the project on the development machine with IIS Express. It only addresses setting up the project for WinAuth while it's on the development machine so that when it's deployed to an IIS production environment it has the web.config with the correct settings for WinAuth. The next section, Publish and deploy your project to the IIS site folder probably adds to the confusion because although it speaks to deployment (to an IIS site folder) it immediately says to "launch the app." It could be clarified a bit further: It really means deploy the app to the IIS production environment ... then run it.

Anyway ... Yes! ... you probably do need to modify applicationHost.config on the local machine for it to work with IIS Express. Those sections are locked by default. But again, the topic isn't trying to explain how to run the app locally that way ... perhaps it should ... but it doesn't today.

@Tratcher What direction would you like to take:

  • Should I modify this to clarify that it applies to IIS/IIS local only?
  • IIRC, IIS local install has those sections unlocked by default ... is that correct?
  • Shall we broaden the coverage to include IIS Express? If so, it will require unlocking the sections: What's the best approach to do that?

@guardrex guardrex self-assigned this Apr 25, 2019
@guardrex guardrex added the Pri2 label Apr 25, 2019
@guardrex guardrex added this to the 2019 Q2 ends June 30 milestone Apr 25, 2019
@guardrex guardrex changed the title HTTP error 500.19 when using a web.config Clarify WinAuth app configuration and deployment Apr 25, 2019
@Tratcher
Copy link
Member

@shirhatti @jkotalik

@Nitro66215
Copy link

If I may add a couple points. Setting up in the web.config file also does not make the current session user ie: WindowsIdentity.GetCurrent().Name equal the Context.User.Identity.Name. The WindowsIdentity.GetCurrent() is equal to the computer / account used to run the website in IIS. The forced way for my SQL queries to run under the Context.User is to do a WindowsIdentity.RunImpersonated on the User accesstoken. I would prefer to avoid that and just have the imperonated setting set to true in the web.config

@guardrex
Copy link
Collaborator

guardrex commented May 6, 2019

@Nitro66215 Engineering approved what's in the topic. They'll respond here when they see your post. Right now, this is the approved guidance for that ... https://docs.microsoft.com/aspnet/core/security/authentication/windowsauth#impersonation ... i.e., so far, they're saying don't do it app-wide ...

... wrapping entire requests or middleware chains isn't supported or recommended.

@Tratcher
Copy link
Member

Tratcher commented May 6, 2019

Running impersonated as the user has many side effects and should be avoided where not absolutely necessary for outbound IO. It's also not practical until .NET Core supports proper async impersonation. https://github.com/dotnet/corefx/issues/24977

Copy link

Understand. This is not application wide. Only on the calls to SQL. However, this is also in a separate class that the calls to SQL are being done. HttpContext is passed in. However, I'm still running into the User.Identity being looked at as the host computer and not the windows user on the published application. Running locally under debug it operates as it should.

@Tratcher
Copy link
Member

Tratcher commented May 6, 2019

HttpContext.User.Identity is the host computer account? That's not something ASP.NET would do. User.Identity is either anonymous or the client identity. Is this a second tier request being made from another server endpoint? In that case it was the machine account that made the request.

Debugging locally IIS Express runs as the current user.

Copy link

I misspoke. That was something else I must have done before.

Currently the WindowsIdentity.GetCurrent().Name is coming up as NT AUTHORITY\NETWORK SERVICE. When it hits the class module with the referenced httpContext, the RunImpersonated section gets NT AUTHORITY\ANONYMOUS LOGON from httpContext.User.Identity. On the page itself, Context.User.Identity.Name is correctly showing the currently logged in windows user.

@Tratcher
Copy link
Member

Tratcher commented May 6, 2019

Can you show how you did the impersonation?

It's also possible you don't have permissions to impersonate (though I'd expect that to cause an error). What does ImpersonationLevel for the HttpContext identity say?
https://docs.microsoft.com/en-us/dotnet/api/system.security.principal.windowsidentity.impersonationlevel?view=netframework-4.8

Copy link

Without giving too much code... the basics...

class module is a static reference in the HomeController.
HttpContext for the Index page in the HomeController is passed to the class module.
Every following pages constructor passes the HttpContext of that page to the HomeControllers instance of the class module.

when the call is made to hit SQL with the required params...

var user = (WindowsIdentity)_classModule._httpContext.User.Identity;
WindowsIdentity.RunImpersonated(user.AccessToken, () =>
{
try
{

The impersonationlevel shows "None"

@Tratcher
Copy link
Member

Tratcher commented May 6, 2019

impersonationlevel was checked on user before RunImpersonated?
Is it showing the expected user.Name before RunImpersonated?

None doesn't make sense, you should always get at least Identification for an authenticated user.
https://docs.microsoft.com/en-us/dotnet/api/system.security.principal.tokenimpersonationlevel?view=netcore-3.0

@Nitro66215
Copy link

There has to be something misconfigured somewhere.

Running the website from the computer that hosts the IIS website, the SQL Run impersonated seams to act correctly.
I put on the _Layout page WindowsIdentity.GetCurrent().Name.ToString() - @WindowsIdentity.GetCurrent().ImpersonationLevel.ToString() as well as Context.User.Identity.Name.ToString(). It shows IIS APPPOOL\Website - None and Domain\User (me).

When I run the website from a different computer connecting to it over the network, It has IIS APPPOOL\Website- None and Domain\User (me). However, the run impersonate code fails with Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON'. So it hits SQL with the wrong login.

@Tratcher
Copy link
Member

Tratcher commented May 6, 2019

What is Context.User.Identity's ImpersonationLevel?

@Nitro66215
Copy link

it shows "None" as well on the _Layout.... @(((WindowsIdentity)Context.User.Identity).ImpersonationLevel.ToString())

@Nitro66215
Copy link

Nitro66215 commented May 6, 2019

I tried that with "identity impersonate="true"" in web.config as well as it removed from web.config (ASP.NET Imperonation = Disabled in IIS)

@Tratcher
Copy link
Member

Tratcher commented May 6, 2019

Ok, something is very wrong with this setup if ImpersonationLevel consistently reports None for Context.User.Identity.

At this point I can only suggest creating a minimal application with nothing but default "Empty" template content and trying to get that working in your server.

@Nitro66215
Copy link

Nitro66215 commented May 7, 2019

[EDIT by @guardrex to format code blocks]

OK. Basic WebApplication1 with WindowsAuthentication. Added to _Layout...

<a class="label label-info">@WindowsIdentity.GetCurrent().Name.ToString() - @WindowsIdentity.GetCurrent().ImpersonationLevel.ToString()</a>

and

<a class="label label-info">@Context.User.Identity.Name.ToString() -  @(((WindowsIdentity)Context.User.Identity).ImpersonationLevel.ToString())</a>

Both show None for Impersonation Level.

Created web.config in the application...

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <security>
        <authentication>
          <windowsAuthentication enabled="true" authPersistNonNTLM="true"></windowsAuthentication>
        </authentication>
      </security>
      <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" hostingModel="InProcess">
        <environmentVariables>
          <environmentVariable name="ASPNETCORE_HTTPS_PORT" value="44316" />
          <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
        </environmentVariables>
      </aspNetCore>
    </system.webServer>
  </location>
  <system.web>
    <authorization>
      <deny users="?" />
    </authorization>
    <identity impersonate="true" />
  </system.web>
</configuration>

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Server.IISIntegration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace WebApplication1
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddAuthentication(IISDefaults.AuthenticationScheme);

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseAuthentication();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

@Nitro66215
Copy link

Nitro66215 commented May 7, 2019

Outside of "RunImpersonated"... the Impersonation Level is always None.

Within RunImpersonated - The ImpersonationLevel is showing Impersonated.

var identity = WindowsIdentity.GetCurrent();

            HttpContext.Session.SetString("CurUser", identity.Name);
            HttpContext.Session.SetString("CurImpLevel", identity.ImpersonationLevel.ToString());

            var user = (WindowsIdentity)HttpContext.User.Identity;
            WindowsIdentity.RunImpersonated(user.AccessToken, () =>
            {
                HttpContext.Session.SetString("ImpUser", WindowsIdentity.GetCurrent().Name);
                HttpContext.Session.SetString("ImpLevel", WindowsIdentity.GetCurrent().ImpersonationLevel.ToString());               
            });

_Layout change....

<a class="label label-info">@Context.Session.GetString("CurUser").ToString() -@Context.Session.GetString("CurImpLevel").ToString()</a>

<a class="label label-info">@Context.Session.GetString("ImpUser").ToString() -@Context.Session.GetString("ImpLevel").ToString()</a>

@guardrex
Copy link
Collaborator

guardrex commented May 7, 2019

@Nitro66215 ... Edit the post that I updated to see how to format your code blocks.

@Nitro66215
Copy link

@Nitro66215 ... Edit the post that I updated to see how to format your code blocks.

Better?

@Nitro66215
Copy link

Now if this isn't messed up.....

I added a helper class to get HttpContext.Current.

namespace System.Web
{

    public static class HttpContext
    {
        private static Microsoft.AspNetCore.Http.IHttpContextAccessor m_httpContextAccessor;


        public static void Configure(Microsoft.AspNetCore.Http.IHttpContextAccessor httpContextAccessor)
        {
            m_httpContextAccessor = httpContextAccessor;
        }


        public static Microsoft.AspNetCore.Http.HttpContext Current
        {
            get
            {
                return m_httpContextAccessor.HttpContext;
            }
        }


    }


}

added to Startup.cs

System.Web.HttpContext.Configure(app.ApplicationServices.
                GetRequiredService<IHttpContextAccessor>()
            );

within my SQL class module.. wrapped around the call to SQL as well as the error trapping....

var user = (WindowsIdentity)System.Web.HttpContext.Current.User.Identity;
                WindowsIdentity.RunImpersonated(user.AccessToken, () =>
               {
                   try
                   {

                       System.Web.HttpContext.Current.Session.SetString("ImpUser", WindowsIdentity.GetCurrent().Name);
                       System.Web.HttpContext.Current.Session.SetString("ImpLevel", WindowsIdentity.GetCurrent().ImpersonationLevel.ToString());
......

in _Layout ...

<a class="label label-info">
            @{ if (@System.Web.HttpContext.Current.Session.GetString("ImpUser") != null)
                {@System.Web.HttpContext.Current.Session.GetString("ImpUser").ToString()} } -
            @{ if (@System.Web.HttpContext.Current.Session.GetString("ImpLevel") != null)
                {@System.Web.HttpContext.Current.Session.GetString("ImpLevel").ToString()} }
        </a>

The ImpUser is the Domain\User that is logged in. And ImpLevel is showing Impersonation.
However, SQL is returning Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON'. I've verified that the SQL call is within the RunImpersonated code. Even the return Exception is within that code.

@guardrex
Copy link
Collaborator

@Tratcher I'm up to this one now.

I'm planning to clarify that the topic is only seeking to get the app configured for WinAuth on the server ... not run it under IIS Express (e.g., unlock config via applicationHost.config for example).

Also, I wasn't planning on making significant changes to how impersonation is described today.

Is that what we're going with (at least for now)? Alternatively, do you want to expand into IIS Express coverage? ... what about updates for impersonation? If you wish more work on either (or both) of those, I'm going to schedule for June. I'm 🏃😅 at the moment. Minutes for this today is OK ... hours ... not OK. 😄

@guardrex guardrex changed the title Clarify WinAuth app configuration and deployment [WinAuth] Clarify app configuration and deployment May 24, 2019
@Tratcher
Copy link
Member

Tratcher commented May 24, 2019

Yeah, don't ever touch VS's applicationHost.config, use the VS UI to enable windows auth.

The impersonation doc seems OK for now. The remainder seems to be a SQL issue. You could ask someone from EF about that.

@guardrex
Copy link
Collaborator

guardrex commented May 27, 2019

This is not application wide. Only on the calls to SQL. However, this is also in a separate class that the calls to SQL are being done.

@Nitro66215 Since that's (for now anyway) an issue outside of our doc set, I'll refer you to the EF Core GH repo issues to open an issue there. If you (and/or they) find some Core-to-EF interaction that we need to cover here, please do let us know about it.

For now on this issue, I'm going to work on our development-side language (VS UI for WinAuth and what the topic says about development-side configuration with web.config). I'll also fix a content layout problem described on #12102.

The EF Core repo issues for the SQL/EF issue that you described 👉 https://github.com/aspnet/EntityFrameworkCore/issues

[EDIT] Indeed ... I think I see where things are a bit dicey. The key content is here, but it's a bit misleading: The VS property page (or a new app from the template) only affects the launch settings. The dev must configure IIS (on the server) or supply the web.config for published output with the proper settings. The topic has all of this content, but it looks like some minor clarifications are in order.

[EDIT 2] I'm going to assume that VSC behaves the same way as VS when it uses the launchSettings.json configuration for debugger runs.

@Nitro66215
Copy link

@guardrex Thanks. I did add the Auth startup.cs code. What I've learned is that it seams to be a "double" (for lack of brainpower at the moment) authentication issue. IIS is seeing the user as the impersonated user when "RunImpersonated" is used. However, SQL within that RunImpersonated does not see them. Another forum / website mentioned using Negotiate:Kerberos. When setting that as the Top Level provider in IIS, IE and Edge work when SQL is called within the RunImpersonated code. However, Chrome does not like it.

@Nitro66215
Copy link

There was also an article about having AD permissions set for the Computer Account. The Computer Account must have Delegation allowed.

@guardrex
Copy link
Collaborator

@Tratcher This is the first feedback that I recall seeing in this vein. I defer to you on when/how/what to cover, if anything. I'll definitely keep an 👂 out for more feedback that matches @Nitro66215's experience and scenario.

@Tratcher
Copy link
Member

Ah, yes, Kerberos Delegation is a thing, but it's well outside the scope of these docs. That needs to be configured on your domain controller and isn't the sort of thing the average web dev has access to.

This comment has been minimized.

@Nitro66215
Copy link

Is there not something in the RunImpersonate code that needs to be done in order to allow that second "hop" to SQL? Or is that an inherent IIS issue?

@Tratcher
Copy link
Member

Without delegation permissions granted on the domain controller, there's nothing your app can do (See ImpersonationLevel). This is a Windows thing, not an IIS thing.

(Note for future reference: we're adding Linux Kerberos support in 3.0 but delegation and impersonation are Windows specific concepts.)

Aside from that, we need to confirm with the EF folks if any extra steps are required. @divega

@Nitro66215
Copy link

Got it. And Chrome not working? Is that just something that is disabled, or windows specific?

@divega
Copy link
Contributor

divega commented May 29, 2019

@Tratcher can you please point me to specific comments I can read to get some context on what the question is about? Thanks!

@Tratcher
Copy link
Member

@divega sorry, nevermind. I re-read #12144 (comment) and they got it working without any EF changes.

@divega
Copy link
Contributor

divega commented May 29, 2019

No worries @Tratcher!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Pri2 Source - Docs.ms Docs Customer feedback via GitHub Issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants