Miguel Isidoro

Blog sobre .NET, Sharepoint e tecnologia em geral.

My Links

Archives

Post Categories

Login

Blog Stats

Blogs Portugueses

Blogs Sharepoint

Aprovisionamento de sites no IIS6 usando uma aplicação ASP.NET

O exemplo apresentado de seguida permite efectuar a criação de um site a partir de uma template de site. Esta solução torna-se particularmente útil para cenários em que seja necessário efectuar o aprovisionamento de vários sites com a mesma estrutura, composta tipicamente por um conjunto de directorias virtuais. Para efectuar a criação de uma template de site é apenas necessário, na consola de administração do IIS, exportar a configuração de um site com a estrutura pretendida para ficheiro (para mais detalhes sobre este processo, cliquem aqui). A template obtida é um ficheiro xml, designado por ficheiro de metabase do site, contendo toda a configuração de um site. Para mais detalhes sobre a metabase do IIS6, cliquem aqui.

A solução

A solução apresentada de seguida pode ser decomposta nos seguintes passos:
  • Criação do novo site
  • Geração da configuração do novo site com base na configuração do site base
  • Importação da configuração do novo site
As operações realizadas durante o processo de criação de um site fazem uso de dois scripts de base do IIS, localizados na directoria C:\Windows\System32:
  • Iisweb.vbs: permite efectuar todas as operações de gestão de um site (criação, remoção, etc). Para mais detalhes, cliquem aqui.
  • iiscnfg.vbs: permite efectuar as operações de gestão da configuração de um site (exportação e importação de configuração, etc). Para mais detalhes, cliquem aqui.

Criação do novo site

Neste passo, é efectuada a criação do site no IIS. De referir que no processo de criação um site no IIS6, podem ser utilizados três modos de acesso:
  • Endereço IP – permite a definição de um endereço IP como modo de acesso do site. Deste modo, o porto 80 pode ser usado por mais do que um site desde que sejam usados endereços IP diferentes.
  • Porto – permite a definição de um porto como modo de acesso do site. Deste modo, tem que ser usado um porto diferente para cada site mas permite o uso do mesmo endereço IP.
  • Host Header – um host header, tal como o nome indica, é um cabeçalho definido ao nível do site e permite que diversos sites utilizem o mesmo porto para o mesmo endereço IP. Exemplos de host headers são “www.create.pt” ou “blogit.create.pt” (o URL que se pretende para o acesso externo ao site sem “http//”). Um pedido HTTP chega ao servidor web e o utilizador é redireccionado para o site correcto com base no URL. Como nota final, de referir que usando host headers, é necessário criar uma entrada no DNS por cada site criado a apontar para o endereço IP externo do servidor web.
Para mais detalhes sobre o processo de criação de um site no IIS6, cliquem aqui.

 

O seguinte método é responsável pela criação do site. O site, nesta fase, não é iniciado, apenas o sendo no final.

 

//constantes usadas ao longo dos exemplos

private const string IIS_WEB_COMMAND = @"C:\WINDOWS\system32\iisWeb.vbs";

private const string IIS_CONFIG_COMMAND = @"C:\WINDOWS\system32\iisCnfg.vbs";

private const string WINDOWS_COMMAND = "cmd";

private const string SITEPATH_REGULAR_EXPRESSION = @"W3SVC/\d*";

 

private int CreateSite(string webSiteHomeDirectory, string siteName, string ip, string port, string hostHeader)

{

    Process process = new Process();

 

    process.EnableRaisingEvents = false;

 

    //criar site mas não arrancá-lo

    //iisweb /create /donstart

    StringBuilder commandArguments = new StringBuilder(String.Format(@" /create {0} {1} /dontstart", webSiteHomeDirectory, siteName));

 

    //modo de acesso por endereço IP

    if (ip != null && ip.Trim().Length > 0)

        commandArguments.Append(String.Format(" /i {0}", ip));

 

    //modo de acesso por porto

    if (port != null && port.Trim().Length > 0)

        commandArguments.Append(String.Format(" /b {0}", port));

 

    //modo de acesso por host header

    if (hostHeader != null && hostHeader.Trim().Length > 0)

        commandArguments.Append(String.Format(" /d {0}", hostHeader));

 

    //assignar o script a correr

    process.StartInfo.FileName = IIS_WEB_COMMAND;

    process.StartInfo.Arguments = commandArguments.ToString();

    /iniciar o processo

    process.Start();

    process.WaitForExit();

 

    //retornar código de execução do processo. Retorna 0 se tudo correr bem.

    return process.ExitCode;

}

 

Ao longo dos exemplos, é utilizada a classe System.Diagnostics.Process para efectuar a execução dos scripts do IIS. Para mais detalhes sobre esta classe, cliquem aqui.

Geração da configuração do novo site com base na configuração do site base

Neste passo, é lida o ficheiro de metabase do site base e gerado a partir deste, o ficheiro de configuração do novo site. As alterações a efectuar à configuração base são compostos tipicamente por:

  • Alteração do ID do site para o ID do novo site
  • Alteração da directoria do site (e das directorias virtuais se existirem) para as do novo site
  • Alterar o nome do site para o do novo site
  • Alterar o host header para o do novo site
O seguinte método é responsável pela geração do ficheiro de metabase do novo site. Este ficheiro será posteriormente utilizado para importar a configuração do novo site. De referir que tratando-se o ficheiro de metabase um ficheiro xml, foi utilizada a classe XmlDocument para efectuar a manipulação do mesmo.

 

private string CreateSiteTemplateXml(string xmlConfigPath, string masterWebID, string createdWebID, string siteName, string hostHeader)

{

    XmlDocument xmlDocument = new XmlDocument();

    //ler ficheiro de metabase do site base

    xmlDocument.Load(xmlConfigPath);

 

    //seleccionar elemento xml IISWebServer da metabase

    XmlNodeList xmlNodeList = xmlDocument.DocumentElement.SelectNodes(“./MBProperty/IIsWebServer”);

 

    //substituir host header

    foreach(XmlNode xmlNode in xmlNodeList)

    {

        xmlAttribute = XmlHelper.GetAttribute(xmlNode.Attributes, “ServerBindings”);

        string serverBinding = xmlAttribute.Value;

        string [] serverBindings = serverBinding.Split(':');

        string oldHostHeader = serverBindings[serverBindings.Length - 1];

        xmlAttribute.Value = xmlAttribute.Value.Replace(oldHostHeader, hostHeader);

    }

 

    //substituir nome site

    foreach(XmlNode xmlNode in xmlNodeList)

    {

        xmlAttribute = XmlHelper.GetAttribute(xmlNode.Attributes, “ServerComment”);

        xmlAttribute.Value = xmlAttribute.Value.Replace(xmlAttribute.Value, siteName);

    }

 

    //seleccionar elemento xml IIsWebVirtualDir da metabase

    xmlNodeList = xmlDocument.DocumentElement.SelectNodes(“./MBProperty/IIsWebVirtualDir”);

 

    foreach(XmlNode xmlNode in xmlNodeList)

    {

        //substituir atributo AppRoot com o id do novo site

        xmlAttribute = XmlHelper.GetAttribute(xmlNode.Attributes, “AppRoot”);

        xmlAttribute.Value = xmlAttribute.Value.Replace(masterWebID, createdWebID);

    }

 

    //substituir directorias do site (existe um elemento IIsWebVirtualDir para a root e para cada directoria virtual). Este exemplo assume que os nomes das directorias do site irão conter o id do site no IIS, sendo substituido o id do site base pelo id do novo site. Ex: C:\inetpub\wwwroot\site -> C:\inetpub\wwwroot\site

    foreach(XmlNode xmlNode in xmlNodeList)

    {

        xmlAttribute = XmlHelper.GetAttribute(xmlNode.Attributes, “Path”);

        xmlAttribute.Value = xmlAttribute.Value.Replace(masterWebID, createdWebID);

    }

 

    //gerar ficheiro metabase novo site

    string newPath = String.Format("{0}{1}.xml", xmlConfigPath.Replace(".xml", String.Empty), createdWebID.Replace(Constants.IISWebSiteIdentifierPrefix, String.Empty));

 

    xmlDocument.Save(newPath);

 

    //retornar caminho para ficheiro metabase do novo site

    return newPath;

}

Importação da configuração do novo site

Neste passo, é efectuada a importação da configuração criada no passo anterior. O seguinte método e responsável por esta operação.

 

private int ImportSiteConfiguration(string xmlConfigPath, string masterWebID, string createdWebID)

{

     Process process = new Process();

 

     process.EnableRaisingEvents=false;

 

     string commandArguments = String.Format(@" /import /f ""{0}"" /sp /LM/{1}/root /dp /LM/{2}/root /children", xmlConfigPath, masterWebID, createdWebID);

 

     process.StartInfo.FileName = IIS_CONFIG_COMMAND;

     process.StartInfo.Arguments = commandArguments;

     process.Start();

     process.WaitForExit();

 

     return process.ExitCode;

}

O processo de criação do site

O método seguinte efectua a chamada a cada uma dos métodos anteriores e alguns métodos auxiliares para efectuar o processo de criação do site.

 

public void CreateNewWebSite(string webSiteHomeDirectory, string webSiteBackOfficeFolder, string siteName,

            string ip, string port, string hostHeader)

{

    //criar site

    int createSiteResult = CreateSite(webSiteHomeDirectory, siteName, null, null, hostHeader);

    if (createSiteResult == 0) //criação site ok

    {

        //obter id do novo site

        string createdSiteID = GetSiteID(siteName);

        //gerar ficheiro configuração novo site

        string xmlConfigPath = CreateSiteTemplateXml(webSiteHomeDirectory, webSiteBackOfficeFolder, Settings.MasterWebSiteXmlConfigurationPath,

        Settings.MasterWebSiteID, createdSiteID, siteName, hostHeader);

        //importar configuraçao novo site

        int importSiteResult = ImportSiteConfiguration(xmlConfigPath, Settings.MasterWebSiteID, createdSiteID);

        if (importSiteResult == 0) //importação ok

            StartSite(siteName);

    }

}

Outros Métodos Utilizados

Foram ainda utilizados os seguintes métodos:

 

GetSiteID: método que obtém o id do site a partir do nome.

 

private string GetSiteID(string siteName)

{

    string siteID = null;

    string commandToExecute = null;

 

    commandToExecute = String.Format(@"{0} /query {1}", IIS_WEB_COMMAND, siteName);

 

    string commandResult = RunShellCommand(commandToExecute);

    siteID = GetRegularExpressionResult(SITEPATH_REGULAR_EXPRESSION, commandResult);

 

    return siteID;

}

 

RunShellCommand: método que executa um comando externo e obtém o resultado da sua execução.

 

private string RunShellCommand(string shellCommandToExecute)

{

    Process process = new Process();

 

    process.EnableRaisingEvents=false;

 

    //obter nome ficheiro temporário               

    string tempFileName = String.Format(@"{0}\{1}.txt", Settings.WebSiteCreationTempFolder, DateTime.Now.ToString(FILE_DATETIME_FORMAT));

 

    //executar comando e escrever resultado para ficheiro

    string commandArguments = String.Format(@" /c {0} > {1}", shellCommandToExecute, tempFileName);

 

    process.StartInfo.FileName = WINDOWS_COMMAND;

    process.StartInfo.Arguments = commandArguments;

    process.Start();

    process.WaitForExit();

 

    //ler resultados de ficheiro temporário

    StreamReader streamReader = new StreamReader(tempFileName);

    string commandResults = streamReader.ReadToEnd();

 

    streamReader.Close();

 

    //apagar ficheiro temporário

    File.Delete(tempFileName);

 

    //devolver resultado

    return commandResults;

}

 

GetRegularExpressionResult: método que aplica uma expressão regular a uma string.

 

private string GetRegularExpressionResult(string pattern, string inputString)

{

    Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);

    MatchCollection matches = regex.Matches(inputString);

 

    return matches.Count == 1 ? matches[0].Value : String.Empty;

}

 

StartSite: método que inicia o site.

 

public void StartSite(string siteName)

{

    Process new Process();

 

    process.EnableRaisingEvents=false;

 

    string commandArguments = String.Format(@" /start {0} ", siteName);

 

    process.StartInfo.FileName = IIS_WEB_COMMAND;

    process.StartInfo.Arguments = commandArguments;

    process.Start();

    process.WaitForExit();

}

Segurança

Existem questões de segurança associadas à criação de sites no IIS6 a partir de uma aplicação ASP.NET. Por omissão, uma aplicação ASP.NET a correr sobre IIS6, é executada usando uma conta de serviço com permissões muito restritas (conta “Network Service”). Para se criar um site no IIS, é necessário ter permissões de administração. Qualquer solução para resolver este problema implica riscos em termos de segurança, sendo o objectivo encontrar uma solução que mitigue estes riscos. Uma solução possível é a criação de um web service que fica responsável pela criação de sites. Este web service seria colocado no IIS numa segunda Web Application a correr numa Application Pool executada com uma conta de serviço com permissões de administração. Desta forma, a aplicação ASP.NET continuaria a correr com a conta de serviço de permissões restritas, sendo a criação de sites a única operação a correr com permissões de administração. Outras medidas de segurança podem ser implementadas, encontrando-se entre elas efectuar a restrição do acesso ao web service, de forma a que possa ser chamado apenas a partir da máquina onde se encontra a aplicação ASP.NET.

Resumo

A solução aqui apresentada permite efectuar a criação de sites no IIS6 a partir de uma template de site usando uma aplicação ASP.NET. É uma solução bastante flexível, na medida em que pode ser usada para efectuar o aprovisionamento de sites a partir de qualquer template. Para tal, basta criar um ficheiro xml de metabase de um site, resultado da exportação da configuração de um site para ficheiro.

posted on Monday, November 06, 2006 5:33 PM

Feedback

No comments posted yet
Title  
Name  
Url
Box Code
Protected by FormShield
Comments