Ayende @ Rahien

Hi!
My name is Oren Eini
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:

ayende@ayende.com

+972 52-548-6969

, @ Q c

Posts: 10 | Comments: 37

filter by tags archive

Are you an administrator?

time to read 4 min | 778 words

In RavenDB vNext, we tightened the security story a bit. Some operations that used to be possible for standard users are now administrator operations. For example, creating a new database require you to be admin.

Figuring out whatever you are admin is a bit tough, though. In particular, we use the following logic to determine that:

  • If you logged in using OAuth, the credentials will tell us whatever you are admin or not.
  • If you are logged in using Windows Auth, we make the following assumption:
    • If you are a Windows Admin, you are an administrator (ouch!).
    • If you are running on the same user as the one RavenDB is using, you are an administrator (debug / dev scenarios).
  • If you are running embedded, you are admin.

You might have noticed that there is an “ouch” on the Windows Admin line. The reason for that is that it is actually quite hard to figure that one out. RavenDB is running as a web server, and when we use Windows Auth, we get a WindowsIdentity that we can use. The problem is with UAC. When that is turned on, what we get is the non elevated user. But that user is not an Admin in the Windows sense of the word. We don’t actually care about that (it isn’t like we need to impersonate the user), we just use that as a “yes/no” for certain ops.

This is documented here: https://connect.microsoft.com/VisualStudio/feedback/details/679546/problem-with-windowsprincipal-isinrole-when-uac-is-enabled

The resolution is by design.

So… we need another way to check for this. Luckily, since we don’t need impersonation, we can just check Active Directory for that. Here is how we do so:

private static bool IsAdministratorNoCache(string username)
{
    PrincipalContext ctx;
    try
    {
        Domain.GetComputerDomain();
        try
        {
            ctx = new PrincipalContext(ContextType.Domain);
        }
        catch (PrincipalServerDownException)
        {
            // can't access domain, check local machine instead 
            ctx = new PrincipalContext(ContextType.Machine);
        }
    }
    catch (ActiveDirectoryObjectNotFoundException)
    {
        // not in a domain
        ctx = new PrincipalContext(ContextType.Machine);
    }
    var up = UserPrincipal.FindByIdentity(ctx, username);
    if (up != null)
    {
        PrincipalSearchResult<Principal> authGroups = up.GetAuthorizationGroups();
        return authGroups.Any(principal =>
                              principal.Sid.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid) ||
                              principal.Sid.IsWellKnown(WellKnownSidType.AccountDomainAdminsSid) ||
                              principal.Sid.IsWellKnown(WellKnownSidType.AccountAdministratorSid) ||
                              principal.Sid.IsWellKnown(WellKnownSidType.AccountEnterpriseAdminsSid));
    }
    return false;
}

Here we check whatever the user is directly or indirectly and admin. Note that we have to take care of cases in which we are running inside & outside a domain, as well as cases where the domain controller is down.

This works, but there is just one problem with that, it is sloooow. As in, multiple seconds slow. Even on the local machine without any domain involved.

I’ll discuss how we solved that on my next post.


Comments

Phil Bolduc

You could help the API by telling it you are providing a SamAccountName.

var up = UserPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, username);

Not really sure the difference between GetAuthorizationGroups and GetGroups, but, you could just call

using (PrincipalSearchResult groups = up.GetGroups()) { return groups.Any(principal => principal.Sid.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid) || principal.Sid.IsWellKnown(WellKnownSidType.AccountDomainAdminsSid) || principal.Sid.IsWellKnown(WellKnownSidType.AccountAdministratorSid) || principal.Sid.IsWellKnown(WellKnownSidType.AccountEnterpriseAdminsSid)); }

Look forward to seeing your analysis of what took all the time,

njy
njy

i suppose your solution will have something to do with the current name of the method, "IsAdministratorNoCache" and in particular that "NO CACHE" part ;)

gunteman

whatever whether

Just sayin' ...

Ayende Rahien

Phil, The SamAccountName isn't really helping perf in any material way. And the GetAuthorizationGroups() is used to get all groups in the hierarchy.

Comment preview

Comments have been closed on this topic.

FUTURE POSTS

  1. Production postmortem: The case of the memory eater and high load - about one day from now
  2. Production postmortem: The case of the lying configuration file - 2 days from now
  3. Production postmortem: The industry at large - 3 days from now
  4. The insidious cost of allocations - 4 days from now
  5. Find the bug: The concurrent memory buster - 5 days from now

And 4 more posts are pending...

There are posts all the way to Sep 10, 2015

RECENT SERIES

  1. Find the bug (5):
    20 Apr 2011 - Why do I get a Null Reference Exception?
  2. Production postmortem (10):
    14 Aug 2015 - The case of the man in the middle
  3. What is new in RavenDB 3.5 (7):
    12 Aug 2015 - Monitoring support
  4. Career planning (6):
    24 Jul 2015 - The immortal choices aren't
View all series

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats