Implementar um Web Proxy em um servidor Linux / Debian para superar as limitações de “Cross-Domain”

Publicado: 01/06/2014 em Linux, Programação, Serviços IP
Tags:, , ,

1. Por que da necessidade de um proxy?

Todos os navegadores modernos impõem uma restrição de segurança em conexões de rede, que inclui chamadas para XMLHttpRequest. Esta restrição impede que um script (ou aplicativo) de fazer uma conexão com qualquer servidor web que não seja o do próprio domínio de onde fora carregado (Internet Explorer permitirá solicitações entre domínios se a opção tiver sido ativada nas preferências). Se tanto a aplicação web e os dados XML que usa aplicativos vêm diretamente do mesmo domínio, então não haverá qualquer restrição e solicitações de dados podem ser realizadas livremente. Ou seja, quando um código JavaScript está carregando dados HTML, XML ou JSON de um recurso no mesmo domínio, tudo que é necessário é solicitar normalmente (de forma direta) estes dados. Observe a figura a seguir.

Solicitação de dados do mesmo servidor: sem necessidade de proxy.

Se, entretanto, a aplicação web é carregada de um determinado domínio web e esta aplicação faz solicitações de dados de serviços da Web de outro domínio, o navegador do usuário impedirá que a conexão seja aberta. Pode ser uma chatice a princípio, mas isto se justifica por questões de segurança. Observe a figura a seguir.

Solicitação de dados de outro host: necessidade de proxy.

Há uma série de soluções para esta situação, mas o mais comumente usado é a instalação de um proxy no próprio servidor web de onde a aplicação fora carregada. Em vez de fazer chamadas XMLHttpRequest diretamente para o serviço web, se faz as chamadas para o proxy do servidor web do domínio da aplicação. O proxy, em seguida, passa a chamada para o serviço web no host destino e, em contrapartida, passa os dados para o aplicativo cliente quando estes forem respondidos. Como a conexão é feita para o servidor da aplicação, e os dados de volta provêm do servidor da aplicação, o navegador não tem nada a reclamar. Observe a figura abaixo.

Solicitação de dados de outro servidor: proxy funcionando.

Conclusão: é necessário ter um web proxy funcionando para ultrapassar as limitações de segurança do Javascript quando se tem que requisitar dados de um servidor diferente daquele da aplicação.
OBS:
Mais rigorosamente, a origem de uma página é definida por seu protocolo, host e porta. Por exemplo, a origem da página deste post é (‘http’,’concani3.wordpress.com’, 80).

2. Um simples web proxy em Python para uso com a biblioteca OpenLayers
Vamos mostrar neste post a superação da restrição “cross-domain” através de um simples script com função Proxy, escrito em Python. Isto irá fazer com que o código JavaScript da aplicação possa acessar conteúdos de webpages de outros domínios, superando estas limitações de segurança “cross-domain”. É necessário para isto instalar este script no servidor de onde é carregado a aplicação JavaScript.

2.1 Disponibilizar o script que implementa o proxy
No caso da bilbioteca OpenLayers-2.12, esta traz uma sugestão de script em Python para funcionar como um proxy. Esse script é encontrado em OpenLayers-2.12/examples/proxy.cgi. Há a necessidade de alterar apenas uma instrução deste arquivo: incluir o nomes dos domínios destino (ou dos dominios destinos) no array “allowedHosts”. E nada mais. Por exemplo, se houvesse a necessidade de requisitar dados dos dominios http://www.exemplo1.com.br e de http://www.exemplo2.com.br, a configuração ficaria assim:
allowedHosts = ['www.exemplo1.com.br', 'www.exemplo2.com.br']

Vamos disponibilizar este arquivo numa pasta tradicionalmente comum para scripts cgi: em /usr/lib/cgi-bin. Assim, fazer:

# cp pasta_do_OpenLayers/OpenLayers-2.12/examples/proxy.cgi /usr/lib/cgi-bin/.

OBS:
– se a pasta destino cgi-bin não estiver criada, faça sua criação;
– por padrão, o interpretador Python em máquinas Linux já vem instalado. Portanto, nada a se preocupar quanto a este aspecto.

2.2 Código do web proxy
Por conveniência, vamos disponibilizar aqui este código do web proxy fornecido pela biblioteca OpenLayers:


#!/usr/bin/env python

import urllib2
import cgi
import sys, os

# Designed to prevent Open Proxy type stuff.

allowedHosts = ['www.openlayers.org', 'openlayers.org', 
                'labs.metacarta.com', 'world.freemap.in', 
                'prototype.openmnnd.org', 'geo.openplans.org',
                'sigma.openplans.org', 'demo.opengeo.org',
                'www.openstreetmap.org', 'sample.azavea.com',
                'v2.suite.opengeo.org', 'v-swe.uni-muenster.de:8080', 
                'vmap0.tiles.osgeo.org', 'www.openrouteservice.org',
                'maps.wien.gv.at','www.exemplo1.com.br','www.exemplo2.com.br']

method = os.environ["REQUEST_METHOD"]

if method == "POST":
    qs = os.environ["QUERY_STRING"]
    d = cgi.parse_qs(qs)
    if d.has_key("url"):
        url = d["url"][0]
    else:
        url = "http://www.openlayers.org"
else:
    fs = cgi.FieldStorage()
    url = fs.getvalue('url', "http://www.openlayers.org")

try:
    host = url.split("/")[2]
    if allowedHosts and not host in allowedHosts:
        print "Status: 502 Bad Gateway"
        print "Content-Type: text/plain"
        print
        print "This proxy does not allow you to access that location (%s)." % (host,)
        print
        print os.environ
  
    elif url.startswith("http://") or url.startswith("https://"):
    
        if method == "POST":
            length = int(os.environ["CONTENT_LENGTH"])
            headers = {"Content-Type": os.environ["CONTENT_TYPE"]}
            body = sys.stdin.read(length)
            r = urllib2.Request(url, body, headers)
            y = urllib2.urlopen(r)
        else:
            y = urllib2.urlopen(url)
        
        # print content type header
        i = y.info()
        if i.has_key("Content-Type"):
            print "Content-Type: %s" % (i["Content-Type"])
        else:
            print "Content-Type: text/plain"
        print
        
        print y.read()
        
        y.close()
    else:
        print "Content-Type: text/plain"
        print
        print "Illegal request."

except Exception, E:
    print "Status: 500 Unexpected Error"
    print "Content-Type: text/plain"
    print 
    print "Some unexpected error occurred. Error text was:", E

 
2.3 Configurar o Apache
Vamos fazer aqui uma configuração muito simples do Apache, sem lançar mão de criação de “Virtual Hosts”. Editar o host virtual default, criado quando da instalação do Apache, o 000-default:
Editar /etc/apache2/sites-enabled/000-default, alterando algumas de suas linhas:


    # Para poder executar a chamada proxy pela instrução:
    # OpenLayers.ProxyHost = "/cgi-bin/proxy.cgi?url=";
	ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
	<Directory "/usr/lib/cgi-bin">
		AllowOverride None
		Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
		Order allow,deny
		Allow from all
        </Directory>

 
Reiniciar o Apache:
# /etc/init.d/apache2 restart

2.4 Testar o funcionamento do web proxy
Para verificar o funcionamento do proxy, dois testes muito simples podem ser realizados.

a) Primeiro teste
Na barra de endereços do navegador, digitar o endereço do proxy passando como parâmetro um endereço que consta em “allowedHosts”. Por exemplo, o endereço openlayers.org. Fazer assim:
http://nome-servidor-do-proxy/cgi-bin/proxy.cgi?url=http://openlayers.org

Deve ser mostrado a página inicial do OpenLayers, solicitada através do proxy.

b) Segundo teste
Na barra de endereços do navegador, digitar o endereço do proxy passando como parâmetro um endereço que NÃO consta em “allowedHosts”. Por exemplo, o endereço uol.com.br. Fazer assim:
http://nome-servidor-do-proxy/cgi-bin/proxy.cgi?url=http://uol.com.br

Uma mensagem de negação do proxy deve ser mostrada na tela. Algo assim:


This proxy does not allow you to access that location (uol.com.br).

{'HTTP_COOKIE': '__utma=145174525.209261312.1401672948.1401672948.1401672948.1;
 __utmb=145174525.1.10.1401672948; __utmc=145174525; __utmz=145174525.1401672948.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)',
 'SERVER_SOFTWARE': 'Apache', 'SCRIPT_NAME': '/cgi-bin/proxy.cgi', 'SERVER_SIGNATURE': '', 'REQUEST_METHOD': 'GET',
 'SERVER_PROTOCOL': 'HTTP/1.1', 'QUERY_STRING': 'url=http://uol.com.br', 'PATH': '/usr/local/bin:/usr/bin:/bin',
 'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20131030 Firefox/17.0 Iceweasel/17.0.10',
 'HTTP_CONNECTION': 'keep-alive', 'SERVER_NAME': 'nome-servidor-do-proxy', 'REMOTE_ADDR': 'IP_do_navegador_usuario',
 'SERVER_PORT': '80', 'SERVER_ADDR': 'nome-servidor-do-proxy', 'DOCUMENT_ROOT': '/var/www',
 'SCRIPT_FILENAME': '/usr/lib/cgi-bin/proxy.cgi', 'SERVER_ADMIN': 'webmaster@localhost', 'HTTP_HOST': 'nome-servidor-do-proxy',
 'REQUEST_URI': '/cgi-bin/proxy.cgi?url=http://uol.com.br', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
 'GATEWAY_INTERFACE': 'CGI/1.1', 'REMOTE_PORT': '6315', 'HTTP_ACCEPT_LANGUAGE': 'pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3',
 'HTTP_ACCEPT_ENCODING': 'gzip, deflate'
}

 
2.5 Definição do web proxy na aplicação
Estando o web proxy funcionando, na aplicação JavaScript que utiliza a bilbioteca OpenLayers basta inserir a definição do ProxyHost:

OpenLayers.ProxyHost = "/cgi-bin/proxy.cgi/?url=";

Referências:
1- JavaScript: use a Web Proxy for Cross-Domain XMLHttpRequest Calls
2- Same-origin policy
3- Cross-Domain requests in Javascript
4- Same-origin policy
5- In which ways could a javascript making a cross domain HEAD request be a threat?
6- W3C – Cross-Origin Resource Sharing

Deixe um comentário, pois isto é muito motivante para continuarmos este trabalho

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

Logotipo do WordPress.com

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

Imagem do Twitter

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

Foto do Facebook

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

Foto do Google+

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

Conectando a %s