8

Good afternoon,

I am currently facing an issue where I have a list of server names, and I need to verify that a windows authenticated user has permissions to the specified server names without attempting to establish a connection to the specified server, before I present the list of servers to them. So for example:

Servers: A, B, C, D

Joe has permissions to A and D but not B and C so Joe should only see A and D in his server list.

How should I be tackling this issue? Should I be pulling from Active Directory? I know how to pull my user's identity, but where to pull the server information and find out if a user has permissions is a completely different story. Any documentation, code samples, articles, etc. are helpful.

Notes About The Work Environment

  1. All of the servers in the list are database servers running SQL Server 2008 R2 and higher.
  2. This is a government environment where there are heavy restrictions.
  3. I am unable to modify Active Directory in any way.
  4. I can query Active Directory and SQL Server all day, provided the user has permissions.
  5. Using third party libraries and tools are not authorized.

Side Note About The Server List

This list is stored in a database, however I don't think this really helps with the permissions side. I've already solved the issue of checking permissions against the individual databases with the following SQL query:

SELECT name
FROM sys.databases
WHERE HAS_DBACCESS(name) = 1
ORDER BY name

Thank you for your help!

-Jamie

Conclusion

Essentially, I have found no possible way to query the AD groups on the remote server without attempting to establish a connection, thus the execution of all samples found will flag the users account with IA. For others with a similar issue that don't have to worry about IA dropping a hammer on users, I have included a few solutions below you can try to meet your needs (all of the following will work when implemented correctly).

Solutions

  1. Query the server in active directory and retrieve the groups from the server, if this fails 9 times out of ten the exception message will be 'Access is denied.'. If it succeeds, proceed to pull the current user's groups and compare to the groups pulled from the server. The selected answer to this post combined with this post and/or this post will get you where you need to be with this solution.
  2. If you have access to the server already through impersonation or other means (SQL Server Auth) then you can use the following to see if the member has any server roles assigned:

    SELECT IS_SRVROLEMEMBER('public')

  3. You could also use the Login class available in the Microsoft.SqlServer.Smo assembly using the Microsoft.SqlServer.Management.Smo namespace. However, you may or may not have issues moving the Microsoft.SqlServer.SqlClrProvider namespace from the GAC to the BIN. More information about this can be found at this StackOverflow post, and at this Microsoft Connect thread which states the following:

Client applications should not be using the assemblies from the Program Files folders unless they are from the specific SDK folders (such as "C:\Program Files (x86)\Microsoft SQL Server\130\SDK")

  1. You could even do a basic connection test wrapped in a try catch to see if the connection will work.

    using (SqlConnection conn = new SqlConnection(connString)) { try { conn.Open(); conn.Close(); } catch (Exception e) { Console.Write($"Connection test failed: {e.Message}"); } }

There are a small variety of ways to achieve the overall goal, it just depends on your particular situation and how you want to approach it. For me, none of the solutions will work since in each scenario the test will attempt a connection to the server in question which will flag the user due to lack of permissions.

  • Do you mean you want to check against syslogins of the individual servers? – SchmitzIT Jul 13 '17 at 20:00
  • I need to verify a user has access to a database server. I would assume there are no SQL queries involved, but it would be nice if there were. I am assuming this is most likely an Active Directory query. –  Jul 13 '17 at 20:02
  • Well, sys.server_principals will contain information on which users have been created on the server. You could always poll your SQL Servers whenever a user signs in, and only show those where they have been added as a user with access. Not sure if there is a better AD way, but you might have to combine the two, as SQL can also accept members of groups – SchmitzIT Jul 13 '17 at 20:08
  • Get the allowed logins using sys.server_principals (e.g. Windows Group, SQL login). Match the Windows Group to the user memberof from AD. – William Han Jul 13 '17 at 20:11
  • I will run a few tests and let you know how this goes. All further answers are still welcome as every bit of knowledge shared helps. I too think I may have to combine if this is the case, but if AD can meet the requirement without SQL then even better. –  Jul 13 '17 at 20:12
  • You could execute xp_logininfo against each server, passing the user name (e.g. `EXECUTE xp_logininfo 'YOURDOMAIN\UserName'`). No rows will be returned if the user doesn't have access either directly or via role membership. Easy to do in Powershell. – Dan Guzman Jul 13 '17 at 20:29
  • @DanGuzman Is the execute command in your comment Powershell or SQL? I have no experience with Powershell. I don't think running a Powershell script inside of my code would be a good idea. –  Jul 13 '17 at 20:53
  • @lxxtacoxxl, sorry, I meant C# code to execute the proc. – Dan Guzman Jul 13 '17 at 21:29
  • In C#, try connecting and trap the error – Nick.Mc Jul 13 '17 at 23:08
  • @Nick.McDermaid, I am unable to attempt the connection as this will flag the user with IA if the user doesn't have permissions. And given a list of over 50 servers, if a user doesn't have access to more than three the user's account would be locked, hence the big issue. –  Jul 15 '17 at 01:42
  • Connection failure via AD does not lock the account. Because the account isn't there. – Nick.Mc Jul 15 '17 at 02:01
  • IA locks the account, not AD. –  Jul 15 '17 at 02:54
  • You'll have to excuse my ignorance - whats IA? – Nick.Mc Jul 15 '17 at 03:37
  • ... it seems that understanding this bogeyman 'IA' is pretty critical to coming up with a solution – Nick.Mc Jul 16 '17 at 01:53
  • IA is information assurance. –  Aug 02 '17 at 16:10

2 Answers2

2

If you have Active Directory implemented then you should be giving users access rights to things like servers via AD groups anyways or else that creates a management nightmare. Imagine if John Smith joins your company as a sys admin, are you going to go to every server and explicitly assign him rights? Much easier to just create a server admin AD group then assign it to the server (or dictate what AD groups exists on servers and permission levels by group policy.

Why this also helps you is that when you develop applications, you can use the built in AD role provider to serve up things like this. Here is a simple example of grabbing a users groups by AD user Name

    using System.DirectoryServices.AccountManagement;

    public List<string> GetGroupNames(string userName)
    {
        List<string> result = new List<string>();
        using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAINHERE"))
        {
            using (PrincipalSearchResult<Principal> src = UserPrincipal.FindByIdentity(pc, userName).GetGroups(pc))
            {
                src.ToList().ForEach(sr => result.Add(sr.SamAccountName));
            }
        }
        return result;
    }

EDIT: So if you absolutely refuse to use active directory groups to manage permissions on servers and buying a tool is out of the question, here is a class that will iterate through all of your local machine groups and give you a list of users within those groups. You could do something like have it run as a scheduled task on the server (or win service) and save it's results back to a DB so you can query or build a UI to pull and monitor this info at any time. This doesn't reach out and grab sql server permissions as you said you already have that.

public class MachinePermissions
{
    string machineName { get; set; }
    public List<LocalGroup> localGroups { get; set; }

    public List<string> GetGroupMembers(string sGroupName)
    {
        List<String> myItems = new List<String>();
        GroupPrincipal oGroupPrincipal = GetGroup(sGroupName);
        PrincipalSearchResult<Principal> oPrincipalSearchResult = oGroupPrincipal.GetMembers();
        foreach (Principal oResult in oPrincipalSearchResult)
        {
            myItems.Add(oResult.Name);
        }
        return myItems;
    }

    private GroupPrincipal GetGroup(string sGroupName)
    {
        PrincipalContext oPrincipalContext = GetPrincipalContext();
        GroupPrincipal oGroupPrincipal = GroupPrincipal.FindByIdentity(oPrincipalContext, sGroupName);
        return oGroupPrincipal;
    }

    private PrincipalContext GetPrincipalContext()
    {
        PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Machine);
        return oPrincipalContext;
    }

    public MachinePermissions()
    {
        machineName = Environment.MachineName;
        PrincipalContext ctx = new PrincipalContext(ContextType.Machine, Environment.MachineName);
        GroupPrincipal gp = new GroupPrincipal(ctx);
        gp.Name = "*";
        PrincipalSearcher ps = new PrincipalSearcher();
        ps.QueryFilter = gp;
        PrincipalSearchResult<Principal> result = ps.FindAll();
        if(result.Count() > 0)
        {
            localGroups = new List<LocalGroup>();
            foreach (Principal p in result)
            {
                LocalGroup g = new LocalGroup();
                g.groupName = p.Name;
                g.users = GetGroupMembers(g.groupName);
                localGroups.Add(g);
            }
        }
    }
}

public class LocalGroup
{
    public string groupName { get; set; }
    public List<String> users { get; set; }
}
Travis Acton
  • 4,292
  • 2
  • 18
  • 30
  • Active Directory is a very powerful tool and makes life in a large environment extremely simple for administration. My knowledge of how it works is good enough, but how to pull the information I need in my C# code is not. –  Jul 13 '17 at 20:55
  • What kind of c# project are you creating? – Travis Acton Jul 13 '17 at 20:56
  • The most descriptive I can get is that it pulls server names from a known list of servers, then pulls all databases that are not system databases from those servers for display to the user. Allowing the full list of servers to be chose from would be a potential nightmare for the users since IA would lock their accounts if they try to connect to servers they don't have access to. Beyond that, the descriptions I can give are very, very, **very** limited. –  Jul 13 '17 at 20:59
  • I just added a very small example of how to query AD groups since you seem very limited on the details you are giving me. – Travis Acton Jul 13 '17 at 21:06
  • Though this is helpful, it still doesn't explain how to specify a server to validate against, nor a method to perform the validation. As the question itself states, I need to verify the user has permissions to a specified server. I still greatly appreciate you answer though since it alleviates the members of groups issue when groups are assigned to the server instead of users. –  Jul 13 '17 at 21:09
  • 1
    I will run some tests today and let you know how this goes. Also, please note that I do not refuse to use AD groups, they are already in place and I do not have the authorization to modify AD in any way. –  Jul 14 '17 at 14:02
1

You can create AD group for accessing each database, then add users to them. In your app you can add list of groups and check if user Is member of them.

It's common practice and allow to create secure scenarios for different access right for different users. You only set permissions for group once and all members can benefit from access rights.

  • Unable to modify AD in any way. Very restricted environment. Thank you for the idea though! Maybe this can help someone in a less restricted environment than my own. –  Jul 13 '17 at 20:56