Proxy, Man-In-The-
Middle e testes
Stanislaw Pusep YAPC::Br 2015
Contexto
• Migração de LWP 5.x para LWP 6.x em todo o
sistema
• A partir do LWP 6, vários módulos não mais fazem
parte da distribuição libwww-perl!
• LWP::Protocol::https
• LWP::Protocol::connect
Contexto
• Firewall com whitelist
• Conexões externas somente através do proxy
Squid
• SSL/TLS, pois os requests contém dados pessoais
Desafio
• Message Service faz request HTTP
• request handler acessa os dados
• LWP cria request HTTP(S) para servidor externdo
• Proxy Squid
• API externa
Desafio
• Código legado
• Sem teste para a integração com o serviço externo
• Importante demais para testar em produção
• Sistema distribuído, mas impossível de fazer
deploy gradual
Solução?
• $ua->proxy('http', 'http://proxy:3128');
• GET http://api-remota.com/v1/… HTTP/1.1
• $ua->proxy('https', 'connect://proxy:3128');
• CONNECT api-remota.com:443 HTTP/1.1
Solução?
• SOCKS?
• SOCKS4/SOCKS4a/SOCKS5/SOCKS5h
• LWP::Protocol::socks
Solução
• Melhorar os testes de integração com o proxy!
• Proxy = Servidor + Cliente
• Instalar proxy customizado em DEV?
(ngx_http_proxy_module)
• HTTP::Daemon + HTTP::Proxy
• Plack::Middleware::.*Connect.*
Solução!
Implementação
• Primeiro, implementamos um servidor HTTP
minimalista
• AnyEvent::Socket::tcp_server()
• Responde igualmente tanto “GET / HTTP/1.1”
quanto “GET http://blabla.com/ HTTP/1.1”
• Traduz CONNECT para GET
Implementação
our %pool;
my $srv = tcp_server(
'127.0.0.1' => 0,
sub {
my ($fh, $host, $port) = @_;
# código do servidor
...;
}
);
AE->cv->wait;
Implementação
my $h = AnyEvent::Handle->new(
fh => $fh,
on_eof => &cleanup,
on_error => &cleanup,
timeout => 10,
);
$pool{fileno($fh)} = $h;
Implementação
$h->push_read(regex => qr{(015?012){2}}, sub {
my ($h, $data) = @_;
my ($req, $hdr) = split m{015?012}x, $data, 2;
$req =~ s/s+$//sx;
if ($hdr =~ m{bContent-length:s*(d+)b}is) {
$h->push_read(chunk => int($1), sub {
my ($h, $data) = @_;
reply($h, $req, $hdr, $data);
});
} else {
reply($h, $req, $hdr);
}
});
Implementação
sub cleanup {
my ($h, $fatal, $msg) = @_;
my $id = fileno($h->{fh});
delete $pool{$id} if defined $id;
eval {
no warnings;
shutdown $h->{fh}, 2;
};
$h->destroy;
}
sub reply {
my ($h, $req, $hdr, $content) = @_;
...;
$h->push_write(...);
cleanup($h);
}
Implementação
sub reply {
my ($h, $req, $hdr, $content) = @_;
if ($req =~ m{^CONNECTs+([w.-]+):(d+)s+(HTTP/1.[01])$}ix) {
$h->push_read(tls_autostart => ‘accept’);
}
...;
$h->push_write(...);
cleanup($h);
}
Implementação
• Produto final: Test::HTTP::AnyEvent::Server
• my $server = Test::HTTP::AnyEvent::Server->new;
• Pré-configura @ENV{qw(no_proxy http_proxy https_proxy
ftp_proxy all_proxy)}
• GET /echo/head
• GET /echo/body
• GET /repeat/5/PADDING
• GET /delay/5
Usando…
my $server = Test::HTTP::AnyEvent::Server->new(
    custom_handler => sub {
        my ($response) = @_;
        if ($response->request->uri eq '/hello') {
            $response->content('world');
            return 1;
        }
        return 0; # 404 Not Found
    },
);
my $cv = AE::cv;
$cv->begin;
http_request
    GET     => qq(http://ohhai/hello),
    proxy   => [ $server->address => $server->port ],
sub {
like($_[0], qr{bHosts*:s*ohhaib}isx, q(fake host));
$cv->end;
};
$cv->wait;
Obrigado!
• stas@sysd.org
• https://metacpan.org/pod/Test::HTTP::AnyEvent::Server
• https://gist.github.com/creaktive/781246
We are hiring!
workingatbooking.com

Proxy, Man-In-The-Middle e testes

  • 1.
    Proxy, Man-In-The- Middle etestes Stanislaw Pusep YAPC::Br 2015
  • 2.
    Contexto • Migração deLWP 5.x para LWP 6.x em todo o sistema • A partir do LWP 6, vários módulos não mais fazem parte da distribuição libwww-perl! • LWP::Protocol::https • LWP::Protocol::connect
  • 3.
    Contexto • Firewall comwhitelist • Conexões externas somente através do proxy Squid • SSL/TLS, pois os requests contém dados pessoais
  • 4.
    Desafio • Message Servicefaz request HTTP • request handler acessa os dados • LWP cria request HTTP(S) para servidor externdo • Proxy Squid • API externa
  • 5.
    Desafio • Código legado •Sem teste para a integração com o serviço externo • Importante demais para testar em produção • Sistema distribuído, mas impossível de fazer deploy gradual
  • 6.
    Solução? • $ua->proxy('http', 'http://proxy:3128'); •GET http://api-remota.com/v1/… HTTP/1.1 • $ua->proxy('https', 'connect://proxy:3128'); • CONNECT api-remota.com:443 HTTP/1.1
  • 8.
  • 9.
    Solução • Melhorar ostestes de integração com o proxy! • Proxy = Servidor + Cliente • Instalar proxy customizado em DEV? (ngx_http_proxy_module) • HTTP::Daemon + HTTP::Proxy • Plack::Middleware::.*Connect.*
  • 10.
  • 11.
    Implementação • Primeiro, implementamosum servidor HTTP minimalista • AnyEvent::Socket::tcp_server() • Responde igualmente tanto “GET / HTTP/1.1” quanto “GET http://blabla.com/ HTTP/1.1” • Traduz CONNECT para GET
  • 12.
    Implementação our %pool; my $srv= tcp_server( '127.0.0.1' => 0, sub { my ($fh, $host, $port) = @_; # código do servidor ...; } ); AE->cv->wait;
  • 13.
    Implementação my $h =AnyEvent::Handle->new( fh => $fh, on_eof => &cleanup, on_error => &cleanup, timeout => 10, ); $pool{fileno($fh)} = $h;
  • 14.
    Implementação $h->push_read(regex => qr{(015?012){2}},sub { my ($h, $data) = @_; my ($req, $hdr) = split m{015?012}x, $data, 2; $req =~ s/s+$//sx; if ($hdr =~ m{bContent-length:s*(d+)b}is) { $h->push_read(chunk => int($1), sub { my ($h, $data) = @_; reply($h, $req, $hdr, $data); }); } else { reply($h, $req, $hdr); } });
  • 15.
    Implementação sub cleanup { my($h, $fatal, $msg) = @_; my $id = fileno($h->{fh}); delete $pool{$id} if defined $id; eval { no warnings; shutdown $h->{fh}, 2; }; $h->destroy; } sub reply { my ($h, $req, $hdr, $content) = @_; ...; $h->push_write(...); cleanup($h); }
  • 16.
    Implementação sub reply { my($h, $req, $hdr, $content) = @_; if ($req =~ m{^CONNECTs+([w.-]+):(d+)s+(HTTP/1.[01])$}ix) { $h->push_read(tls_autostart => ‘accept’); } ...; $h->push_write(...); cleanup($h); }
  • 17.
    Implementação • Produto final:Test::HTTP::AnyEvent::Server • my $server = Test::HTTP::AnyEvent::Server->new; • Pré-configura @ENV{qw(no_proxy http_proxy https_proxy ftp_proxy all_proxy)} • GET /echo/head • GET /echo/body • GET /repeat/5/PADDING • GET /delay/5
  • 18.
    Usando… my $server =Test::HTTP::AnyEvent::Server->new(     custom_handler => sub {         my ($response) = @_;         if ($response->request->uri eq '/hello') {             $response->content('world');             return 1;         }         return 0; # 404 Not Found     }, ); my $cv = AE::cv; $cv->begin; http_request     GET     => qq(http://ohhai/hello),     proxy   => [ $server->address => $server->port ], sub { like($_[0], qr{bHosts*:s*ohhaib}isx, q(fake host)); $cv->end; }; $cv->wait;
  • 19.
  • 20.