Uma necessidade bastante comum para desenvolvedores de soluções de e-commerce é utilizar múltiplos certificados SSL para o mesmo Web Site do IIS.
Isso acontece principalmente quando a solução de e-commerce é multi-inquilino, ou seja, uma única aplicação/website no IIS contém diferentes lojas virtuais de diferentes clientes/empresas.
Então, vamos pensar o seguinte cenário:
Somos uma empresa de plataforma de e-commerce com 4 clientes, com seus planos específicos e respectivas urls.
- Cliente 1 (Premium): www.loja1.com
- Cliente 2 (Silver): www.loja2.com
- Cliente 3 (Silver): www.loja3.com
- Cliente 4 (Free): www.loja4.com
O plano Premium oferece um ambiente dedicado para o cliente, com direito a domínio próprio para HTTP e HTTPS.
O plano Silver oferece um ambiente compartilhado para o cliente, com direito a domínio próprio para HTTP e HTTPS.
O plano Free oferece um ambiente compartilhado para o cliente, com direito a domínio próprio apenas para HTTP, o HTTPS é um domínio nosso (ex: https://loja4.ecommerce.com).
A nossa aplicação já está preparada para carregar a loja correta de acordo com o domínio da requisição. Ou seja, se vier uma requisição com o domínio www.loja1.com vamos carregar o banco de dados da loja 1 e mostrar a loja 1.
Como montar isso no Windows Azure?
Uma forma é montar um ambiente assim como montaríamos dentro de casa, criando máquinas virtuais, cada uma com seu IP e DNS, utilizando CNAMEs e certificados SSL wildcard e etc.
Mas aí estaríamos desperdiçando um dos maiores poderes do Windows Azure, a plataforma como serviço dos Cloud Services, ou Serviços de Nuvem.
Utilizando Cloud Services com Multiplos certificados SSL
Para montar o nosso cenário utilizando Cloud Services e obter todos os benefícios de automatização e escalabilidade, vamos fazer o seguinte:
Para os clientes Premium, vamos tratar cada loja de cliente como se fosse uma aplicação isolada, cada uma tendo o seu Cloud Service separado. Assim conseguimos garantir o “ambiente dedicado” que oferecemos no plano. O processo para utilizar SSL neles então é bastante simples, basta criar um endpoint HTTPS com o certificado SSL correspondente. Para usar o domínio próprio, basta configurar um CNAME no servidor de DNS.
Para os clientes Free, o processo é mais simples. Teremos apenas uma aplicação/Cloud Service que atenderá todas as lojas no plano Free. Basta criar um endpoint HTTPS com um certificado SSL wildcard de subdomínio (no nosso caso *.ecommerce.com). Para cada loja, como prometemos domínio próprio para HTTP, para cada loja será necessário configurar um CNAME no servidor de DNS. A nossa aplicação terá que ter a inteligência de redirecionar o usuário para o subdomínio seguro corresponte sempre que precisar usar HTTPS (por exemplo nas páginas de pagamento).
Então cada loja teria duas Urls diferentes, por exemplo: http://www.loja4.com para HTTP e https://loja4.ecommerce.com para HTTPS.
E finalmente, para os clientes Silver, temos uma situação especial. Precisamos deixar todas as lojas na mesma aplicação/CloudService e garantir que cada uma tenha seu domínio próprio tanto para HTTP quanto para HTTPS. Para isso acontecer, para cada loja será necessário configurar um CNAME no servidor de DNS da mesma maneira que fizemos antes.
Por exemplo:
www.loja2.com CNAME ecommerce.cloudapp.net
www.loja3.com CNAME ecommerce.cloudapp.net
E agora, para cada loja ter seu próprio domínio seguro, cada uma precisa ter seu certificado SSL correspondente.
A dificuldade aparece quando precisamos criar os endpoints HTTPS, pois não é possível adicionar mais de um certificado para o mesmo endpoint.
Uma solução seria utilizar certificados agregados, onde um único arquivo de certificado contém dezenas de certificados para domínios diferentes, mas essa solução pode ser mais cara e ainda você terá que fazer um deploy novo sempre que precisar adicionar um domínio a mais no certificado.
Uma outra solução seria criar endpoints HTTPS em portas diferentes, por exemplo, 443, 444, 445, mas isso pode ficar bem estranho, e em alguns casos não funcionar por bloqueios de proxy e firewalls, além de existir um limite de 25 endpoints externos.
E finalmente, existe a possibilidade de criar apenas um endpoint HTTPS na porta 443, mas usar uma nova feature do IIS 8.0 que permite criar vários bindings HTTPS, na mesma porta para domínios diferentes. Essa feature é baseada em uma extensão do SSL chamado Server Name Indication (SNI), cuja grande maioria dos browsers suporta (na prática, apenas browsers que rodam no Windows XP não suportam essa feature).
Para isso funcionar, o que precisamos fazer é instalar os certificados desejados e criar os bindings no site do IIS, marcando a opção SNI. Veja aqui uma explicação da feature do IIS e um passo-a-passo de como fazer isso manualmente.
Mas como fazer isso num Cloud Service que é stateless e que a cada deploy ou quando necessário o Windows Azure “zera” as configurações do IIS?
Solução
Precisamos automatizar a instalação dos certificados e criação dos bindings do IIS no processo de inicialização da Web Role no Cloud Service.
Instalando o certificado
Conseguimos instalar o certificado com 5 linhas de código C#
var newCert = new X509Certificate2(“certificado.pfx”, “senhaDoCertificado”, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
var readWriteMyStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
readWriteMyStore.Open(OpenFlags.ReadWrite);
readWriteMyStore.Add(newCert);
readWriteMyStore.Close();
Criando o binding HTTPS com SNI ligado
E com mais 5 linhas conseguimos criar o binding (adicione o pacote Microsoft.Web.Administration via Nuget)
var serverManager = new ServerManager();
var site = serverManager.Sites[0];
var binding = site.Bindings.Add(“:443:www.loja2.com”, newCert.GetHash(), “My”);
binding.SetAttributeValue(“sslFlags”, 1); //ativa o SNI
serverManager.CommitChanges();
Não esquece que para esse código rodar, a role tem que rodar em modo elevado:
Coloque a tag abaixo no arquivo ServiceDefinition.csdef dentro da <WebRole>
<Runtime executionContext=”elevated” />
Solução completa
Isso já seria o suficiente para você adicionar um código no Role.OnStart e fazer o setup dos seus domínios seguros sempre que a role iniciar.
No entanto, para garantir que a role não precisa ser reiniciada sempre que entrar um certificado novo, criei um job que fica de tempos em tempos consultando se é necessário instalar certificados e fazer bindings.
A solução está flexível para que você guarde seus certificados num blob container ou num banco de dados por exemplo, basta você criar seu “provider” que implemente a interface ICertificateBindingStorage.
No exemplo, eu tenho um container chamado “certificates” que possui os certificados armazenados como blobs e nos metadados deles as informações para instalar e configurar o binding.
Exemplo de um certificado no blob storage:
http://vitorciaramella.blob.core.windows.net/certificates/loja2.pfx
Metadados:
PortNumber: 443
HostHeader: www.loja2.com
CertificateSubject: CN=www.loja1.com
CertificatePassword: Pa$$w0rd
Dessa forma, sempre que eu precisar de uma nova loja, basta eu criar o CNAME dela e subir o certificado dela no blob container com os respectivos metadados. Um minuto depois ela estará pronta para ser utilizada.
Baixe aqui o código-fonte completo da solução: http://sdrv.ms/Zasjbp
E se você quiser saber mais sobre esse assunto de certificados e SNI, veja esse post: http://blogs.msdn.com/b/kaushal/archive/2012/09/04/server-name-indication-sni-in-iis-8-windows-server-2012.aspx
Dúvidas? sugestões? -> Deixe seu comentário Image may be NSFW.
Clik here to view.
Vitor Ciaramella