O exemplo apresentado de seguida permite efectuar a criação de um site no IIS6 a partir de uma template. 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 <directoria> <nome> /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<idsitebase> -> C:\inetpub\wwwroot\site<idsitenovo>

    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 commandToExecute = String.Format(@"{0} /query {1}", IIS_WEB_COMMAND, siteName);

 

    string commandResult = RunShellCommand(commandToExecute);

    string 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.

LEAVE A REPLY

Please enter your comment!
Please enter your name here