[pve-devel] idea for implementation of a spice http connect proxy, with pve authentification
Alexandre DERUMIER
aderumier at odiso.com
Sun Jun 16 13:35:58 CEST 2013
After doing some tests, I think it should be possible to implement this in full perl - anyevents.
Client make 1 http CONNECT query for each spice channel, so it's should work.
maybe http:proxy is buggy ?
http proxy look this this:, return code 200 + stream the datas from the spice socket.
# INTERNAL method
# FIXME no man-in-the-middle for now
sub _handle_CONNECT {
my ($self, $served) = @_;
my $last = 0;
my $conn = $self->client_socket;
my $req = $self->request;
my $upstream;
# connect upstream
if ( my $up = $self->agent->proxy('http') ) {
# clean up authentication info from proxy URL
$up =~ s{^http://[^/\@]*\@}{http://};
# forward to upstream proxy
$self->log( PROXY, "PROXY",
"Forwarding CONNECT request to next proxy: $up" );
my $response = $self->agent->simple_request($req);
# check the upstream proxy's response
my $code = $response->code;
if ( $code == 407 ) { # don't forward Proxy Authentication requests
my $response_407 = $response->as_string;
$response_407 =~ s/^Client-.*$//mg;
$response = HTTP::Response->new(502);
$response->content_type("text/plain");
$response->content( "Upstream proxy ($up) "
. "requested authentication:\n\n"
. $response_407 );
$self->response($response);
return $last;
}
elsif ( $code != 200 ) { # forward every other failure
$self->response($response);
return $last;
}
$upstream = $response->{client_socket};
}
else { # direct connection
$upstream = IO::Socket::INET->new( PeerAddr => $req->uri->host_port );
}
# no upstream socket obtained
if( !$upstream ) {
my $response = HTTP::Response->new( 500 );
$response->content_type( "text/plain" );
$response->content( "CONNECT failed: $@");
$self->response($response);
return $last;
}
# send the response headers (FIXME more headers required?)
my $response = HTTP::Response->new(200);
$self->response($response);
$self->{$_}{response}->select_filters( $response ) for qw( headers body );
$self->_send_response_headers( $served );
# we now have a TCP connection
$last = 1;
my $select = IO::Select->new;
for ( $conn, $upstream ) {
$_->autoflush(1);
$_->blocking(0);
$select->add($_);
}
# loop while there is data
while ( my @ready = $select->can_read ) {
for (@ready) {
my $data = "";
my ($sock, $peer, $from ) = $conn eq $_
? ( $conn, $upstream, "client" )
: ( $upstream, $conn, "server" );
# read the data
my $read = $sock->sysread( $data, 4096 );
# check for errors
if(not defined $read ) {
$self->log( ERROR, "CONNECT", "Read undef from $from ($!)" );
next;
}
# end of connection
if ( $read == 0 ) {
$_->close for ( $sock, $peer );
$select->remove( $sock, $peer );
$self->log( SOCKET, "CONNECT", "Connection closed by the $from" );
$self->log( PROCESS, "PROCESS", "Served $served requests" );
next;
}
# proxy the data
$self->log( CONNECT, "CONNECT", "$read bytes received from $from" );
$peer->syswrite($data, length $data);
}
}
$self->log( CONNECT, "CONNECT", "End of CONNECT proxyfication");
return $last;
}
This small nodejs implementation works fine
var httpProxy = require('http-proxy'),
url = require('url'),
net = require('net'),
http = require('http');
process.on('uncaughtException', logError);
function truncate(str) {
var maxLength = 64;
return (str.length >= maxLength ? str.substring(0,maxLength) + '...' : str);
}
function logRequest(req) {
console.log(req.method + ' ' + truncate(req.url));
for (var i in req.headers)
console.log(' * ' + i + ': ' + truncate(req.headers[i]));
}
function logError(e) {
console.warn('*** ' + e);
}
// this proxy will handle regular HTTP requests
var regularProxy = new httpProxy.RoutingProxy();
// standard HTTP server that will pass requests
// to the proxy
var server = http.createServer(function (req, res) {
logRequest(req);
uri = url.parse(req.url);
regularProxy.proxyRequest(req, res, {
host: uri.hostname,
port: uri.port || 80
});
});
// when a CONNECT request comes in, the 'connect'
// event is emitted
server.on('connect', function(req, socket, head) {
logRequest(req);
// URL is in the form 'hostname:port'
var parts = req.url.split(':', 2);
// open a TCP connection to the remote host
var conn = net.connect(parts[1], parts[0], function() {
// respond to the client that the connection was made
socket.write("HTTP/1.1 200 OK\r\n\r\n");
// create a tunnel between the two hosts
socket.pipe(conn);
conn.pipe(socket);
});
});
server.listen(3333);
----- Mail original -----
De: "Alexandre DERUMIER" <aderumier at odiso.com>
À: pve-devel at pve.proxmox.com
Envoyé: Dimanche 16 Juin 2013 09:53:26
Objet: Re: [pve-devel] idea for implementation of a spice http connect proxy, with pve authentification
Also, here a working implementation in nodejs
https://github.com/nodejitsu/node-http-proxy
support http connect proxy + websockify (spice-html5)
also it's seem to be possible to use perl inside nodejs :)
https://npmjs.org/package/perl
----- Mail original -----
De: "Alexandre DERUMIER" <aderumier at odiso.com>
À: pve-devel at pve.proxmox.com
Envoyé: Dimanche 16 Juin 2013 09:13:15
Objet: [pve-devel] idea for implementation of a spice http connect proxy, with pve authentification
Hi,
I'm working again on spice, I have an idea to implement authentification.
a spice client config file is like that:
[virt-viewer]
type=spice
proxy=kvmtest1.odiso.net:3128
host=localhost
tls-port=60000
password=tempticketpassword
password field is limited to 60 characters (client side), so it's too short to crypt with rsa username,password,etc...
what we can do
[virt-viewer]
type=spice
proxy=kvmtest1.odiso.net:3128
host=rsa(base64(localhost:$plain:$username:$path))
tls-port=60000
password=tempticketpassword
So, the proxy can decode the host field, to verify authentification of the user, like for vnc ticket.
Now, I have tried with cpan HTTP::Proxy, which implemented the HTTP CONNECT method.
The problem is that it don't work with spice, because spice is doing 4 connections (after the http connect).
Spice use a different connection for main,display,inputs,mouse,....
And HTTP::Proxy use fork, from cpan doc:
"An important thing to note is that the proxy is (except when running the NoFork engine) a forking proxy: it doesn't support passing information between child processes, and you can count on reliable information passing only during a single HTTP connection (request + response)."
So only the first connection to main spice channel is made, and after that the client hang.
I don't known if it's possible to resolve that in HTTP::Proxy ?
I have find a working small http connect proxy written in python here:
https://gist.github.com/fmoo/2068759
So I don't known if we can use this ? (with authentification verification through pve webservices)
Other thing, about guests spice listen on unix domain socket.
Currently I get it work with a small qemu patch + using socat to forward to tcp. (nc don't work because of multiple spice connections).
This works without tls, but for tls, it'll require a small patch on libspice server side. (I'll try to look at this this week)
(Note that tls works fine on tcp + proxmox certificates).
So I don't known if we want something complex with guest listen on domain sockets like
client---->proxy---->tcp:localhost:socat--->ssh---> unix:target node
or
client----->proxy--------->tcp:target node
(with iptables to block guests spice ports from outside world)
_______________________________________________
pve-devel mailing list
pve-devel at pve.proxmox.com
http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
_______________________________________________
pve-devel mailing list
pve-devel at pve.proxmox.com
http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
More information about the pve-devel
mailing list