Utilização de Impersonation para execução de PowerShell via ASP.Net

Dentre os meus últimos desafios surgiu-me a necessidade de executar um comando PowerShell em um servidor via uma página ASP.Net, mas com a premissa de executar este comando dentro de um contexto seguro, isto é baseado em permissões de usuário distintas daquelas que estão rodando o Application Pool do IIS.

Desta forma, de acordo com as credenciais do usuário logado na aplicação, seria permitido ou não a execução dos comandos via PowerShell. Entretanto, como princípio de segurança, a execução deste comando seria feita sobre um contexto diferente do usuário do Application Pool.

Para tanto, me baseei em dois posts: o primeiro de Antoine Habert (MVP de PowerShell) que ensina como executar comandos PowerShell via páginas ASP.Net (http://devinfra-us.blogspot.com/2011/02/using-powershell-20-from-aspnet-part-1.html) e um segundo post do Microsoft Support que demonstra como implementar Impersonation programaticamente em aplicações ASP.Net (http://support.microsoft.com/kb/306158).

Para a execução deste exemplo será preciso fazer download e instalação do Windows PowerShell 2.0 Software Development Kit (http://http://www.microsoft.com/download/en/details.aspx?id=2560).

O download do exemplo na integra pode ser feito por aqui: http://code.msdn.microsoft.com/Utilizao-de-Impersonate-4dde6f1d.

Para execução do Impersonation foi necessário fazer referência aos namespaces System.Runtime.InteropServices e System.Security.Principal, além de utilizar o seguinte bloco de código:

public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;

WindowsImpersonationContext impersonationContext;

[DllImport("advapi32.dll")]
public static extern int LogonUserA(String lpszUserName,
    String lpszDomain,
    String lpszPassword,
    int dwLogonType,
    int dwLogonProvider,
    ref IntPtr phToken);

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken,
    int impersonationLevel,
    ref IntPtr hNewToken);

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);

private bool ImpersonateValidUser(String userName, String domain, String password)
{
    WindowsIdentity tempWindowsIdentity;
    IntPtr token = IntPtr.Zero;
    IntPtr tokenDuplicate = IntPtr.Zero;

    if (RevertToSelf())
    {
        if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
            LOGON32_PROVIDER_DEFAULT, ref token) != 0)
        {
            if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
            {
                tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                impersonationContext = tempWindowsIdentity.Impersonate();
                if (impersonationContext != null)
                {
                    CloseHandle(token);
                    CloseHandle(tokenDuplicate);
                    return true;
                }
            }
        }
    }
    if (token != IntPtr.Zero)
        CloseHandle(token);
    if (tokenDuplicate != IntPtr.Zero)
        CloseHandle(tokenDuplicate);
    return false;
}

private void UndoImpersonation()
{
    impersonationContext.Undo();
}

E para execução do comando via PowerShell foi necessário fazer referência ao namespace System.Management.Automation e o seguinte código (com a inclusão do Impersonation):

bool userNeedsToBeImpersonated = false;
bool impersonateOccurswithSuccess = false;

#region [ Verificar se é preciso fazer impersonate ]

userNeedsToBeImpersonated = Convert.ToBoolean(this.rdbImpersonate.SelectedValue);

#endregion #region [ Executa impersonate se necessário ]

if (userNeedsToBeImpersonated)
{
    try {
        UserImpersonate user = UserImpersonate.LoadCredentials();

        impersonateOccurswithSuccess = ImpersonateValidUser(user.Login, user.Domain, user.Password);
    }
    catch (Exception ex)
    {
        FormatException(ex);
        return;
    }
}

#endregion #region [ Executa powershell ]

try {
    ResultBox.Text = string.Empty;

    var shell = PowerShell.Create();

    shell.Commands.AddScript(PowerShellCodeBox.Text);

    var results = shell.Invoke();

    if (results.Count > 0)
    {
        var builder = new StringBuilder();

        foreach (var psObject in results)
        {
            builder.Append(psObject.BaseObject.ToString() + "\r\n");
        }

        ResultBox.Text = Server.HtmlEncode(builder.ToString());
    }
}
catch (Exception ex)
{
    FormatException(ex);   
}

#endregion #region [ Volta a execução ao seu usuário normal ]

if (userNeedsToBeImpersonated && impersonateOccurswithSuccess)
{
    UndoImpersonation();
}

#endregion 

Achei interessante o contexto da solução, e também por serem necessários dois recursos que são bastante utilizados em aplicações web mais avançadas, por isso fiz este post.

Obrigado e até o próximo!

Por

Fernando Henrique Inocêncio Borba Ferreira

Publicidade

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.