dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.43k stars 10.01k forks source link

.IsInRole() exception - Active Directory user principal role checking broken during trusted domain interruptions #49039

Open afun-entp opened 1 year ago

afun-entp commented 1 year ago

Is there an existing issue for this?

Describe the bug

Manual use of .IsInRole() or invocation of a user principal role check through the use of Blazor's AuthorizeView component, will by default transverse all windows domain trusts when in an Active Directory context.

This creates a weak point in deployed applications within multi-domain environments, since interruption of contact with any trusted domain will cause exception status for all running instances within the primary domain.

This impact is not limited by:

Expected Behavior

I expected to find either:

Steps To Reproduce

True reproduction requires the configuration of an Active Directory domain trust, and then severing the access to the trusted domain. (For anyone seeking to truly reproduce, the simplest method might be to intentionally create a DNS failure between 2 testing domains.)

Here is example code of .IsInRole() being called manually, illustrating fairly common use:

    using (PrincipalContext pc = new(ContextType.Domain, ActiveDirectoryDomain))
    {
        using (UserPrincipal up = UserPrincipal.FindByIdentity(pc, user.Identity.Name))
        {
            appState.SetCurrentUser(user.Identity.Name, up.DisplayName);

            foreach (var role in ADGroupRole)
            {
                if (userGroups.Any(x => x.Name.ToLower() == role.Key.ToLower()) == true && user.IsInRole(role.Value) == false)
                {
                    var userClaims = new ClaimsIdentity(new List<Claim>()
                        {
                            new Claim(ClaimTypes.Role, role.Value)
                        });
                    user.AddIdentity(userClaims);
                }
            }

...

Here is example code of Role checking being invoked through Blazor's AuthorizeView component:

<AuthorizeView Roles="ProdSupervisor, Management, Admin, Support" >
...
</AuthorizeView>

Both result in identical exception.

Exceptions (if any)

Win32Exception: The trust relationship between the primary domain and the trusted domain failed

( Exception details captured from Blazor application )

System.ComponentModel.Win32Exception (1788): The trust relationship between the primary domain and the trusted domain failed. at System.Security.Principal.NTAccount.TranslateToSids(IdentityReferenceCollection sourceAccounts, Boolean& someFailed) at System.Security.Principal.NTAccount.Translate(IdentityReferenceCollection sourceAccounts, Type targetType, Boolean& someFailed) at System.Security.Principal.NTAccount.Translate(IdentityReferenceCollection sourceAccounts, Type targetType, Boolean forceSuccess) at System.Security.Principal.WindowsPrincipal.IsInRole(String role) at Microsoft.AspNetCore.Authorization.Infrastructure.RolesAuthorizationRequirement.<>cDisplayClass4_0.b0(String r) at System.Linq.Enumerable.Any[TSource](IEnumerable1 source, Func2 predicate) at Microsoft.AspNetCore.Authorization.Infrastructure.RolesAuthorizationRequirement.HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement) at Microsoft.AspNetCore.Authorization.AuthorizationHandler1.HandleAsync(AuthorizationHandlerContext context) at Microsoft.AspNetCore.Authorization.Infrastructure.PassThroughAuthorizationHandler.HandleAsync(AuthorizationHandlerContext context) at Microsoft.AspNetCore.Authorization.DefaultAuthorizationService.AuthorizeAsync(ClaimsPrincipal user, Object resource, IEnumerable1 requirements) at Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.IsAuthorizedAsync(ClaimsPrincipal user) at Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.OnParametersSetAsync() at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync() at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception) at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task, ComponentState owningComponentState) at Microsoft.AspNetCore.Components.Rendering.ComponentState.SupplyCombinedParameters(ParameterView directAndCascadingParameters) at Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters) at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(DiffContext& diffContext, Int32 frameIndex) at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(DiffContext& diffContext, Int32 frameIndex) at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(DiffContext& diffContext, Int32 newFrameIndex) at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl) at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, Int32 componentId, ArrayRange1 oldTree, ArrayRange1 newTree) at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, Exception& renderFragmentException) at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception) at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue() at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender() at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment) at Microsoft.AspNetCore.Components.RenderHandle.Render(RenderFragment renderFragment) at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged() at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync() at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception) at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task, ComponentState owningComponentState) at Microsoft.AspNetCore.Components.Rendering.ComponentState.SupplyCombinedParameters(ParameterView directAndCascadingParameters) at Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters) at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(Int32 componentId, ParameterView initialParameters) at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.CreateInitialRenderAsync(Type componentType, ParameterView initialParameters) at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.RenderComponentAsync(Type componentType, ParameterView initialParameters) at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c11`1.<b__11_0>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.ViewFeatures.StaticComponentRenderer.PrerenderComponentAsync(ParameterView parameters, HttpContext httpContext, Type componentType) at Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) at Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.RenderComponentAsync(ViewContext viewContext, Type componentType, RenderMode renderMode, Object parameters) at Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output) at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.gAwaited|0_0(Task task, TagHelperExecutionContext executionContext, Int32 i, Int32 count) at MesIntegrationPortal.Pages.PagesHost.b24_1() in C:\Users---\Pages_Host.cshtml:line 25 at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync() at MesIntegrationPortal.Pages.PagesHost.ExecuteAsync() in C:\Users----\Pages_Host.cshtml:line 5 at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context) at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts) at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context) at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable1 statusCode) at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable1 statusCode) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

.NET Version

7.0.302

Anything else?

It is possible to find multiple occurrences of this issue being discussed on sites like StackExchange, but most discussions are obscured by the fact that it is an environment specific risk (and frankly many developers don't have the Domain administration background to fully understand the context).

JeremyLikness commented 1 year ago

Hi @afun-entp,

I'm looking into this issue and am curious what form of exception handling you are looking for. Would you want an ignore flag that basically traps the exception and ignores that instance (i.e. IsInRole won't be satisfied by the failing request, but could get populated as it cycles through others), some sort of retry policy, a combination of both or something else? Can you clarify what a successful resolution of this issue would look like for you?

afun-entp commented 1 year ago

Thank you for your response!

I don't have an understanding of the technical complexity behind the scenes, so this may be a very naive perspective... but here goes:

Most effective - prevention: A flag that allows or prevents resolution and traversal of Trusted Domains when calling .IsInRole()

Since many applications are only going to be targeted to memberships within a single domain context, traversing external trusted domains adds wait time overhead as well as opening the opportunity for cross domain issues. {Interruptions of service in this case. But, I'm also curious if domain trusts might open up "membership confusion" when both domains have identically named groups. }

Metaphorically, IsInRole automatically checking Trusted Domains is like a postal worker trying to enter your neighbor's house before they decide whether or not to deliver your mail. You and your neighbor may have high degrees of trust with each other, and you may have shared keys with each other for just in case... but ideally you both want tight control over whether or not an activity piggybacks on that.

So to me; my ideal magic-wand solution would be an option on the .NET level that allows or prevents Trusted Domain resolution, and then implementing that into Blazor's component as a parameter.

Next most effective - trapping: Since Blazor's AuthorizeView component calls IsInRole() the end developer does not have the option of implementing a Try/Catch at that level.

So a flag for trapping would indeed be a way of allowing the process to cycle forward to see if a non-domain role has been assigned by the application.

ajallath commented 9 months ago

Hello Guys,

I have the same issue; it occurs when my Blazor application starts and does the domain validation. I’m getting the following error:

Win32Exception: The trust relationship between the primary domain and the trusted domain failed.

It works if I run the app on the same domain as the IIS server, but if I run from any other domain, I get the error. It is weird because I have access to the domain and shared resources. It seems to be an Active Directory problem, but I would like to know if I can skip that validation. I’ve done some research on the Internet but no solution until now, any help will be appreciated.

ghost commented 9 months ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.