[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