-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
WindowsIdentity impersonation does not achieve delegation #17828
Comments
@Tratcher we can also see the same issue when using the EF Core framework. (Its not passing the credentials to SQL Server). Browser -> Web App -> SQL Server (this always shows using SQL Profiler as the dotnet identity process). |
@jruckert are you impersonating the user before calling SQL? Asp.net core does not do this impersonation for you. |
Example of how we are attempting to use the impersonation var callerIdentity = localSecurityService.CurrentUser() as WindowsIdentity;
using (callerIdentity.Impersonate())
{
return ((DbContext)Current).SaveChanges();
} |
cc: @bartonjs |
@jruckert that doesn't make sense, the WindowsIdentity.Impersonate() API isn't even in .NET Core, it's only in the full framework. You need to call RunImpersonated instead. |
I agree its a bit weird, here is our framework definition in the project.json file. Note: our current definition of WindowsIdentity does not have RunImpersonated, only Impersonate. I'm going to look into this now.
|
Maybe related to #16842 Open System.Security.Principal.WindowsIdentity |
@Tratcher can you point me to the repo code? |
Is there any update on this issue? |
[edited as it's the source of many mistakes] The original comment was describing a bug, not the right pattern. |
So, this tells me that the problem is not the HTTP stack (HttpClient using WinHTTP). Rather, it is a process / delegation problem in .NET Core. |
cc: @karelz |
I'm confident that this is working now (we are using WindowsIdentity.RunImpersonated) with Kerberos Delegation. |
Here is the middleware we created: https://github.com/novaworksau/impersonate |
Thanks @jruckert for letting us know! That saves us a lot of time 👍
@karelz no, my changes were only triage. Closing now since an external customer reports the scenario works fine. This is in-line with @davidsh's hypothesis of this being a process/delegation issue in .NET Core, which makes sense since the same WinHTTP code worked fine in Desktop. @Tratcher, can you please give this a try on 2.0 and reopen if you can still repro? |
We are struggling with this also, I'm trying to understand if this was verified in .net CORE 1.1? Seems link @jruckert jruckert has it working, we have implemented the middleware but we are still not there yet. Have others had success implementing the middleware? |
@jruckert That impersonation middleware doesn't actually work, please take it down. Actually most of the samples posted so far have made the same mistake. You're using the overload To really make this work the CLR needs to add a separate RunImpersonatedAsync method that does not revoke the impersonation until the async Task is completed. Even then, making sure the identity flows properly across threads will be problematic. |
@CIPop, @Tratcher @jruckert @karelz Can you please reopen this issue. Do you know if there is a planned solution for this? Doesn't seem like @Tratcher was comfortable with how to solve the problem but maybe its on someone else's radar? Then I would ask if it is possibly set for a 2.0 release? I know that's a lot to ask but unfortunately moved forward with a solution believing this is possible and changing the approach at this point in time would be rather expensive. Thanks for you time, Thank you, |
I would suggest to create a new bug. It is confusing for me to understand what is the ask here. |
We had a discussion with @Tratcher and @CIPop. @Tratcher is trying to resurrect his setup from last year, let's see how it goes. Realistically, I don't think it will meet the 2.0 bar. However, we can start the discussion about servicing patch, once we know where the problem is and once we understand how much is the scenario important to how many customers. |
Good news, I've gotten this to work now for ASP.NET Core 1.1 and 2.0 preview1 (and ASP.NET 4.5 as a baseline). Setting up Kerberos for IIS was the hard part. Here are the highlights:
This setup can be used to test both ASP.NET 4.5 and ASP.NET Core apps. Here is the default.aspx file I placed in my back-end website for testing all scenarios: <%@ Page Language="C#" %>
<script runat=server>
protected System.Security.Principal.WindowsIdentity GetUser()
{
return (System.Security.Principal.WindowsIdentity)Context.User.Identity;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:LoginView ID="LoginView1" runat="server">
<LoggedInTemplate>
Hi <%= Context.User.Identity.Name %> <br>
State: <%= GetUser().ImpersonationLevel %> <br>
</LoggedInTemplate>
<AnonymousTemplate>
Hi Guest
</AnonymousTemplate>
</asp:LoginView>
</div>
</form>
</body>
</html> Here's the default.aspx file I placed on my middle tier server for testing ASP.NET 4.5 scenarios: <%@ Page Language="C#" %>
<script runat=server>
protected System.Security.Principal.WindowsIdentity GetUser()
{
return (System.Security.Principal.WindowsIdentity)Context.User.Identity;
}
protected string GetUserState()
{
using (GetUser().Impersonate())
{
return System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel.ToString();
}
}
protected string GetSubSection()
{
using (GetUser().Impersonate())
{
return new System.Net.WebClient() { UseDefaultCredentials = true }.DownloadString("http://Win2012r2-DC.testing.net/");
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:LoginView ID="LoginView1" runat="server">
<LoggedInTemplate>
Hi <%= Context.User.Identity.Name %> <br>
State: <%= GetUser().ImpersonationLevel %> <br>
Impersonated State: <%= GetUserState() %> <br>
Next Hop: <%= GetSubSection() %> <br>
</LoggedInTemplate>
<AnonymousTemplate>
Hi Guest
</AnonymousTemplate>
</asp:LoginView>
</div>
</form>
</body>
</html> Results:
And here's an ASP.NET Core repro app you can build and publish to the middle tier server. I tested this with netcoreapp1.0, netcoreapp2.0, net461, AspNetCore 1.0, 1.1, 2.0-preview1 and 2.0-dev packages. AuthApp.csproj <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.0.0" />
</ItemGroup>
</Project> program.cs using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace AuthApp
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
new WebHostBuilder()
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
}
} startup.cs using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Linq;
using System.Threading.Tasks;
using System.Security.Principal;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace AuthApp
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
}
// 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();
}
app.Run(async (context) =>
{
try
{
await context.Response.WriteAsync("Hello World!<br>");
var user = (WindowsIdentity)context.User.Identity;
await context.Response.WriteAsync($"User: {user.Name}<br>");
await context.Response.WriteAsync($"State: {user.ImpersonationLevel}<br>");
/*
await context.Response.WriteAsync($"Downstream:<br>"
+ "WebClient: <br>" + new WebClient() { UseDefaultCredentials = true }
.DownloadString("http://win2012r2-dc.testing.net"));
*/
await context.Response.WriteAsync($"Downstream:<br>"
+ "HttpClient: <br>" + await new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true })
.GetStringAsync("http://win2012r2-dc.testing.net"));
await context.Response.WriteAsync($"Impersonating:<br>");
#if NET461
using (user.Impersonate())
#else
WindowsIdentity.RunImpersonated(user.AccessToken, () =>
#endif
{
var useri = WindowsIdentity.GetCurrent();
var text = $"User: {useri.Name}<br>State: {useri.ImpersonationLevel}<br>";
/*
text += "WebClient: <br>" + new WebClient() { UseDefaultCredentials = true }
.DownloadString("http://win2012r2-dc.testing.net");
*/
text += "HttpClient: <br>" + new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true })
.GetStringAsync("http://win2012r2-dc.testing.net").Result;
var bytes = System.Text.Encoding.UTF8.GetBytes(text);
context.Response.Body.Write(bytes, 0 , bytes.Length);
}
#if !NET461
);
#endif
}
catch (Exception e)
{
await context.Response.WriteAsync(e.ToString());
}
});
}
}
} Results:
@brentschmaltz The only remaining mystery is why the ImpersonationLevel is reported as Impersonation on the middle tier rather than Delegation. Delegation is working, we see the identity passed to back-end site. Note: The FREB logs do show the requests as TokenImpersonationLevel ImpersonationDelegate. |
@Tratcher Thanks for all of your work so far! One question on your set up. Is this step really required? "back-end web site with the same authentication configuration." I hadn't seen that as a required step when looking at how to set up impersonation. That was always done on the Middle Tier Web Server but I hadn't seen that as a requirements on the Site/App that hosts the Web Service that gets called where you want the impersonation to work. In my case I'm attempting to impersonate a call to TFS's rest API as the end user, so are you saying we may need to update the TFS Services' web site security settings? In general I have seen enabling Kerberos on the service accounts but I hadn't seen changing the Service itself. I've also had seen that if you don't set up a SPN you can't enable Kerberos on the service accounts, seems like you were able to do that though? Thanks, |
@gperrego I didn't spend a lot of time tinkering with the auth settings on the back-end site, I mirrored them for simplicity. It's likely they are not as strict as those on the middle tier, but you still need Windows auth enabled and Anonymous disabled. I didn't have to set any special SPNs because I was always using the machine FQDN, which is registered as an SPN by default. You need additional SPNs if your machine name doesn't match your site name, like when you scale across multiple machines. |
Filed https://github.com/dotnet/corefx/issues/24977 for async impersonation. |
@Tratcher Can this issue be closed now? There doesn't seem to be any more actionable work on this issue. |
There's one minor issue left to investigate:
|
@Tratcher, is this still an issue? @brentschmaltz, this is assigned to you. Are you working on this? |
Hard to say, it takes a long time to set up a repro environment (private domain controller, two web servers, and client). |
I think this is a problem in HttpClient. Looking at related issues and the code, it is not passing the right flags for delegation requests to the Negotiate SSPI. See: https://github.com/dotnet/corefx/issues/34697#issuecomment-475453845 |
@davidsh delegation was working, last I checked. Only something was wrong with WindowsIdentity reporting the current impersonation level. |
Chris,
As for our issue at my client we found that we think it had something to do with cross domain authentication issues. Let me see if I can dig up the old emails and find it.
Thanks,
Greg
…________________________________
From: VaniKulkarni <[email protected]>
Sent: Monday, March 25, 2019 4:02 AM
To: dotnet/corefx
Cc: gperrego; Mention
Subject: Re: [dotnet/corefx] WindowsIdentity impersonation does not achieve delegation (#9996)
test
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub<https://github.com/dotnet/corefx/issues/9996#issuecomment-476108196>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AV-QZ9jma54QwuRpZxXHfehxcUgQpKZCks5vaJCVgaJpZM4JJ5lq>.
|
I've solved this mystery. "Delegation" is only reported as the impersonation level for unconstrained delegation. The incoming ticket to the application must have This is the case straight from the win32 APIs, regardless of .NET.
vs.
|
Does this mean that everything is working as expected and we can close this issue now? |
Setup:
Client: IE or Chrome
Server: Asp.Net Core 1.0 via IIS or WebListener, with Windows Auth enabled.
Scenario: A client logs into a web app using windows credentials. The web app in turn impersonates that user to make outgoing HttpClient requests also using windows credentials.
Expected: The outgoing HttpClient request should made using the impersonated user's credentials.
Actual: The outgoing HttpClient request is made using the web apps default credentials.
The scenario works fine when running Asp.Net Core 1.0 on .NET 4.6, it only fails on .NET Core.
In both the IIS and WebListener scenarios the WindowsIdentity is constructed from an existing handle from a native API: https://github.com/aspnet/IISIntegration/blob/ed85f504d8da633202b3fec5fdf11e8d6153d447/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs#L112. Since this works with .NET 4.6 apps we assume the original handle is valid and something is wrong inside WindowsIdentity or HttpClient.
HttpClient does work with impersonated WindowsIdentities created locally (http://stackoverflow.com/questions/7710538/impersonate-with-username-and-password). Only the delegation scenario appears to be broken.
@brentschmaltz @CIPop
Ping me directly for repro code, there are a lot of different parts.
The text was updated successfully, but these errors were encountered: