Pergunta

The Problem: When new IIS Application Pools are created and set to use the Application Pool Identity for permissions, I am unsure how to add those identities to User Groups such as Administrator or Performance Counter Users.

The Background: I'm currently writing a C#.NET library which uses Microsoft.Web.Administration in order to do the following:

  • Detect if IIS 7.x is installed, and if so, what components.
  • Install or upgrade IIS 7.x to a provided list of required components.
  • Create/manage one or more web sites through IIS.
  • Automatically create/manage one application pool per web site

The context is that this library is to be used by executable installers to provide automated deployment of a web server and web sites/services on Windows Server OSes as part of a larger software deployment. So far, all of the above has been implemented, tested, and is (mostly) functional except for the automation of some permissions that need to be performed on Application Pool / Website creation.

In my method for installing a new website, I create a new Application Pool and force it to use the Application Pool Identity:

static public void InstallSite(string name, string path, int port)
{
    Site site;
    var appPoolName = ApplicationPoolBaseName + name;

    using (var iisManager = new ServerManager())
    {
        // Set up a custom application pool for any site we run.
        if (!iisManager.ApplicationPools.Any(pool => pool.Name.Equals(appPoolName)))
        {
            iisManager.ApplicationPools.Add(appPoolName);
            iisManager.ApplicationPools[appPoolName].ManagedRuntimeVersion = "v4.0";
        }
        iisManager.CommitChanges();
    }

    // ... other code here ('site' gets initialized) ...

    using (var iisManager = new ServerManager())
    {
        // Set anonymous auth appropriately
        var config = iisManager.GetWebConfiguration(site.Name);
        var auth = config.GetSection("system.web/authentication");
        auth.SetMetadata("mode", "Windows");
        var authSection = config.GetSection("system.webServer/security/authentication/anonymousAuthentication");
        authSection.SetAttributeValue("enabled", true);
        authSection.SetAttributeValue("userName", string.Empty); // Forces the use of the Pool's Identity.
        authSection = config.GetSection("system.webServer/security/authentication/basicAuthentication");
        authSection.SetAttributeValue("enabled", false);
        authSection = config.GetSection("system.webServer/security/authentication/digestAuthentication");
        authSection.SetAttributeValue("enabled", false);
        authSection = config.GetSection("system.webServer/security/authentication/windowsAuthentication");
        authSection.SetAttributeValue("enabled", false);

        iisManager.CommitChanges();
    }

    // ... other code here ...
}

As I understand it, this would be the best security practice, and I would then add permissions to specific web sites for anything more than minimal system access. Part of this process would be to add these Application Pool identities to User Groups, such as Administrator or Performance Monitor Users. This is where complications arise.

Now, as documented elsewhere, each Application Pool Identity exists in the format of IIS AppPool\\<pool_name> but this faux-user is not listed through the normal GUI user management controls, and does not seem to be accessible through libraries such as System.DirectoryServices.AccountManagement when following this example on SO. Also, other questions about the Application Pool Identity seem to relate to referencing it from within a child website, not from within an installation context.

So, does anyone know what the proper methods are for

  • a) Referencing and accessing Application Pool Identities programmatically.
  • b) Giving Application Pool Identities permissions by adding them User Groups.
Foi útil?

Solução 2

A solution presented itself sooner than I expected, though it's not the one I preferred. For anyone interested, there are a couple of additional options on this pinvoke page. The managed solution did not work for me, but the sample using DllImport worked. I ended up adjusting the sample to handle arbitrary groups based on mapping an enum to SID strings, and including another DllImport for:

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool ConvertStringSidToSid(
    string StringSid,
    out IntPtr ptrSid);

The modified (working) function looks something like this:

static public bool AddUserToGroup(string user, UserGroup group)
{
    var name = new StringBuilder(512);
    var nameSize = (uint)name.Capacity;
    var refDomainName = new StringBuilder(512);
    var refDomainNameSize = (uint)refDomainName.Capacity;
    var sid = new IntPtr();
    switch (group)
    {
        case UserGroup.PerformanceMonitorUsers:
            ConvertStringSidToSid("S-1-5-32-558", out sid);
            break;
        case UserGroup.Administrators:
            ConvertStringSidToSid("S-1-5-32-544", out sid);
            break;
        // Add additional Group/cases here.
    }

    // Find the user and populate our local variables.
    SID_NAME_USE sidType;
    if (!LookupAccountSid(null, sid, name, ref nameSize,
        refDomainName, ref refDomainNameSize, out sidType))
        return false;

    LOCALGROUP_MEMBERS_INFO_3 info;
    info.Domain = user;

    // Add the user to the group.
    var val = NetLocalGroupAddMembers(null, name.ToString(), 3, ref info, 1);

    // If the user is in the group, success!
    return val.Equals(SUCCESS) || val.Equals(ERROR_MEMBER_IN_ALIAS);
}

Hopefully this will be of interest to someone else, and I would still like to know if anyone comes across a working, fully managed solution.

Outras dicas

Thanks for your well-written question. It is exactly the problem that I was trying to solve last night and it gave me enough to go on that I was able finally cobble together an answer that uses only managed code. There were three steps that I found to getting the framework to find and work with the virtual user:

  • using new System.Security.Principal.NTAccount(@"IIS APPPOOL\<appPoolName>") to get a handle on the account.
  • using .Translate(typeof (System.Security.Principal.SecurityIdentifier)) to convert it to a SID
  • understanding that Principal.FindByIdentity() treats that SID like it is a group, rather than a user

A final working program (Windows Server 2012 for my test) is as follows:

using System;
using System.DirectoryServices.AccountManagement;

namespace WebAdminTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var user = new System.Security.Principal.NTAccount(@"IIS APPPOOL\10e6c294-9836-44a9-af54-207385846ebf");
            var sid = user.Translate(typeof (System.Security.Principal.SecurityIdentifier));

            var ctx = new PrincipalContext(ContextType.Machine);

            // This is weird - the user SID resolves to a group prinicpal, but it works that way.
            var appPoolIdentityGroupPrincipal = GroupPrincipal.FindByIdentity(ctx, IdentityType.Sid, sid.Value);

            Console.WriteLine(appPoolIdentityGroupPrincipal.Name);
            Console.WriteLine(appPoolIdentityGroupPrincipal.DisplayName);

            GroupPrincipal targetGroupPrincipal = GroupPrincipal.FindByIdentity(ctx, "Performance Monitor Users");

            // Making appPoolIdentity "group" a member of the "Performance Monitor Users Group"
            targetGroupPrincipal.Members.Add(appPoolIdentityGroupPrincipal);
            targetGroupPrincipal.Save();

            Console.WriteLine("DONE!");
            Console.ReadKey();
        }
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top