atechdad make it so

The Poor Man’s Guide to Hosted Virtual Desktop Infrastructure (DaaS-VDI)

What follows is an account of my attempts to install a desktop environment in the cloud. I’ve done this successfully using a vps from digitalocean (referral link ;)). A second part of this will be demonstrating how to pull this into a docker container.

When you complete this tutorial you will have a hosted virtual desktop behind a html5 frontend.

First, we’ll update and upgrade, install the desktop environment, guacamole, a virtual framebuffer, the VNC server, and the md5 hash generator.

sudo apt-get update && apt-get upgrade -y
sudo apt-get install -y --no-install-recommends guacamole-tomcat gnome-core xfce4 xvfb vnc4server isomd5sum

You can select no when prompted to restart the tomcat server. We’ll do this later.

We’re going to generate a md5 hash based off of a password. It’s a little more secure than plaintext… Use your own password please. This one’s mine.

echo -n PASSWORD | md5sum

Remeber your password. Save off the hash itself, but leave out spaces and the dash (-).

Now let’s clear our command history.

history -c

Next, we’ll edit the guacamole user mapping XML file which defines our user and vnc client configuration.

sudo nano /etc/guacamole/user-mapping.xml

Replace all of the content with the following. In nano you can hold ctrl-k to delete lines. Again, don’t steal my super secure credentials. Replace the values in bold with your own.

<user-mapping>
     <authorize
          username="admin"
          password="319f4d26e3c536b5dd871bb2c52e3178"
          encoding="md5">
          <protocol>vnc</protocol>
          <param name="hostname">localhost</param>
          <param name="port">5901</param>
          <param name="password">VNCPASS</param>
     </authorize>
</user-mapping>

Next, we’ll start the VNC server to generate the default config files, then kill it. Make sure you start this as the user you’ll be wanting to run the VNC server.

vncserver

Enter the VNC password when prompted. This should match the password you used to replaced VNCPASS in the prior step.
Now we’ll kill VNC.

vncserver -kill :1

Next, we’ll edit the VNC startup to start our GUI on boot.

nano .vnc/xstartup

Replace all content with :

#!/bin/sh

[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources

startxfce4 &
xsetroot -solid grey

Now we’ll restart tomcat.

sudo /etc/init.d/tomcat6 restart

And finally we’ll start the server.

vncserver -localhost

You should now be able to reach your new desktop from : http://YOURserverIP:8080/guacamole

Ouya PS4 Remote Play

The OUYA was a dream that was unrealized by some. While many have gotten immense pleasure out of the android based console, many have ended up either selling their OUYA or not using it at all.

The kids got a PS4 for Christmas. It hadn’t been even hooked up yet when I started thinking of a Playstation TV and other remote play goodness. Some quick research led me to the official Sony remote play android app– and subsequently to the ‘hacked’ version.

I installed it on my phone first– and it worked out of the box. I then went around and began testing it on a several other android-enabled devices throughout the house. The OUYA did not work out of the box. The more I thought about it, the more I wanted it. I knew it must be possible.

The following is an account of my attempts to use my OUYA for Remote Play with the PS4.

My first attempt was obvious. I tried to install the apk onto the Ouya directly. While the app didn’t crash, it also didn’t work. Those who have tried have been greeted by this lovely screen:

ouyaFail

Meh.

Second attempt:

What follows is what I did. I had to replace the stock firmware with Cyanogenmod.  I cannot guarantee this will work for you. Paraphrased from the Cyanogenmod site:

I am not responsible for bricked devices, dead SD cards, or any other issues. Please do some research if you have any concerns about these instructions. YOU are choosing to make these modifications. Your warranty will be void if you tamper with any part of your device / software.

Avoiding tl;dr, here is the summary:

Observations:

  • Wireless network play is laggy/choppy. Use a hardwired connection on both devices if possible.
  • The PS4 Remote is the only remote I was able to get working. Others may be possible. There are rumors the OUYA remote will work, but I haven’t got this going yet.

Proof:

ouya1ouya2

Merry Christmas from 1993!

Sorry for the delay. Don’t say I never got you anything.

12 Games in 12 Months : Game 1, Week 1

Hey again all,
As promised in my previous post, here is a my progress report on Game 1:

The game is being built using Libgdx. It’s kind of like Pacman. I haven’t completed much to date except for the base engine. I don’t have any menus, or UI to speak of. There is no scorekeeping or goal completion yet. I hope to stitch this together to meet my deadline. I am targeting release for android and web environments.

Here’s the obligatory screenshot :

mower

I didn’t create the artwork for the guy. While he probably won’t make it into the final cut, I would still like to give credit if I could find where I got him from. If you know, let me know and I’ll update this :)

 

 

Anemone : Anonymity as a Service

I’ve been reading about Docker and it’s started me thinking about the possible services that could be hosted. One instance I thought of was hosting the Tor Browser (I’ve since seen people do this as well). Inspired by Slumlord Hosting with Docker which was inspired by Memcached as a Service, I came up with my own recipe.

The Concept

This is the concept, watered down:

aaasconcept

You can skip to the bottom for a demonstration video if you’d prefer.

What We’ll Need:

  • Ubuntu 14.04.1 LAMP Server (I got mine from DigitalOcean : Referral Link)
  • Ubuntu 14.04.1 Docker Server

I built these with two network interfaces. One on a private network and one on the public network. It could be argued that the Docker server doesn’t need a public interface.

Log into your Web/LAMP server.

Install LAMP: (Highlights from here):

sudo apt-get update
sudo apt-get install apache2 -y
sudo apt-get install mysql-server libapache2-mod-auth-mysql php5-mysql -y
sudo apt-get install php5 libapache2-mod-php5 php5-mcrypt php5-curl -y
sudo apt-get install makepasswd
sudo mysql_install_db
sudo /usr/bin/mysql_secure_installation
rm /var/www/html/index.html

Install Guacamole and the SQL Authentication Plugin:

sudo apt-get install guacamole-tomcat -y
sudo service tomcat6 stop
sudo service guacd stop
cd ~
wget http://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.23.tar.gz
wget http://downloads.sourceforge.net/project/guacamole/current/extensions/guacamole-auth-mysql-0.8.4.tar.gz

tar xzvf guacamole-auth-mysql-0.8.4.tar.gz
tar xzvf mysql-connector-java-5.1.23.tar.gz

sudo mkdir /var/lib/guacamole/classpath

sudo cp guacamole-auth-mysql-0.8.4/lib/* /var/lib/guacamole/classpath/
sudo cp mysql-connector-java-5.1.23/mysql-connector-java-5.1.23-bin.jar /var/lib/guacamole/classpath/
mysql -u root -p
CREATE DATABASE guacamole;
CREATE USER 'guacamole'@'localhost' IDENTIFIED BY 'YOURPWCHANGEME!';
GRANT SELECT,INSERT,UPDATE,DELETE ON guacamole.* TO 'guacamole'@'localhost';
FLUSH PRIVILEGES;
exit
cat guacamole-auth-mysql-0.8.4/schema/*.sql | mysql -u root -p guacamole
sudo nano /etc/guacamole/guacamole.properties

replace the contents with this:

guacd-hostname: localhost
guacd-port: 4822

lib-directory: /var/lib/guacamole/classpath

# Auth provider class
auth-provider: net.sourceforge.guacamole.net.auth.mysql.MySQLAuthenticationProvider

# MySQL properties
mysql-hostname: localhost
mysql-port: 3306
mysql-database: guacamole
mysql-username: guacamole
mysql-password: YOURPWCHANGEME!
sudo service tomcat6 start
sudo service guacd start

You should be able to hit http://yourip:8080/guacamole now. The default username:pass is guacadmin:guacadmin. I suggest you change that now…

guacamole

Log into your Docker server.

Install Docker (Highlights from here):

sudo apt-get update
sudo apt-get install linux-image-extra-`uname -r` -y
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
sudo apt-get update
sudo apt-get install lxc-docker -y

Create your Dockerfile

nano Dockerfile
from ubuntu:trusty
run apt-get update
run apt-get install -y wget x11vnc xvfb xz-utils firefox
run useradd anon --create-home -s /bin/sh
run wget https://dist.torproject.org/torbrowser/4.0.2/tor-browser-linux64-4.0.2_en-US.tar.xz
run tar -C /home/anon -xvJf tor-browser-linux64-4.0.2_en-US.tar.xz
run rm /*.xz

add start.sh /home/anon/start.sh

run chmod +x /home/anon/start.sh

cmd ["/home/anon/start.sh"]

EXPOSE 5900

USER anon

Create the start.sh

nano start.sh

#!/bin/bash
x11vnc -once -create -geometry 1280x1024 \
-env X11VNC_CREATE_GEOM=${1:-1280x1024x8} \
-env FD_PROG="/home/anon/tor-browser_en-US/start-tor-browser \
-height 1024 -width 1280" &

sleep 10m

Build the docker image:

sudo docker build -t anemone_img .

Enable the docker remote API **(from here): **

**There is no authentication for the Docker the API out of the box. You should implement your own. I am restricting the API to the private IP address. In a real world deployment you should implement as many security best practices as you feasibly can. This can include changing the default port, listenting only on the private interface, implementing authentication, SSL, etc.**

sudo nano /etc/init/docker.conf

Find the line that starts with DOCKER_OPTS, and change it to the below

DOCKER_OPTS='-H tcp://YOURDOCKER'sPRIVATEIP:4243 -H unix:///var/run/docker.sock'

Save, Exit. Then run:

sudo service docker stop
sudo service docker start

Log back into your webserver.

Please be understanding. I hack things together. I never said I do it well– or prettily. I know I should be using stored procedures. I know I am not using best practices. I know my PHP sucks. Instead of tearing me up for writing bad code, create your own and share? :)

index.php

<?php

session_start();
$duration = (10 * 60);
$guacServer = "YOUR WEB SERVER PUBLIC IP";

if(isset($_SESSION['started']))
{
$time = ($duration - (time() - $_SESSION['started']));
if($time <= 0)
{
unset($_SESSION['UN']);
unset($_SESSION['PW']);
unset($_SESSION['started']);
}
}

if(!isset($_SESSION['started']))
{
$_SESSION['started'] = time();
$ch = curl_init('http://127.0.0.1/createInstance.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
$obj = json_decode($result);
$_SESSION['UN'] = sanitize_text_field($obj->name);
$_SESSION['PW'] = sanitize_text_field($obj->pw);
}
print ("User: ".$_SESSION['UN']."<br />");
print ("Password: ".$_SESSION['PW']."<br />");
print ('<iframe src="http://'.$guacServer.':8080/guacamole" width="800" height="600" frameBorder="0"></iframe>');
?>

createUser.php

<?php
if ( isset( $_GET['user'] ) && !empty( $_GET['user'] ) )
{

$user = strtolower($_GET['user']);
$pw = $_GET['pw'];
$cn = strtolower($_GET['cn']);
$ip = strtolower($_GET['ip']);
$port = strtolower($_GET['port']);

$mysqli = new mysqli("localhost", "guacamole", "YOURPWCHANGEME!", "guacamole");
if ($mysqli->connect_errno) {
echo "Failed to connect to MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}

$sql = "SET @salt = UNHEX(SHA2(UUID(), 256)); ";
$sql.= "INSERT INTO guacamole_user (username, password_salt, password_hash) VALUES ('".$user."', @salt, UNHEX(SHA2(CONCAT('".$pw."', HEX(@salt)), 256))); ";
$sql.= "Set @userid = LAST_INSERT_ID(); ";
$sql.= "INSERT INTO guacamole_connection (connection_name, protocol) VALUES ('".$cn."', 'vnc'); ";
$sql.= "SET @id = LAST_INSERT_ID(); ";
$sql.= "INSERT INTO guacamole_connection_parameter VALUES (@id, 'hostname', '".$ip."'); ";
$sql.= "INSERT INTO guacamole_connection_parameter VALUES (@id, 'port', '".$port."'); ";
$sql.= "INSERT INTO guacamole_connection_parameter VALUES (@id, 'password', '".$pw."'); ";
$sql.= "INSERT INTO guacamole_connection_permission VALUES (@userid, @id, 'READ'); ";

print ($sql);

if (!$mysqli->multi_query($sql)) {
echo "Multi query failed: (" . $mysqli->errno . ") " . $mysqli->error;
}
}

?>

createInstance.php

<?php
$dockerhost = 'YOUR DOCKER HOST'S PRIVATE IP';
$data_string = '{"Image":"anemone_img"}';

$ch = curl_init('http://'.$dockerhost.':4243/containers/create');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json',
    'Content-Length: ' . strlen($data_string))
);

$result = curl_exec($ch);

if( !($result = curl_exec($ch)) ) {
    curl_close($ch);
    exit;
}
curl_close($ch);
$obj = json_decode($result);

$contId = $obj->{'Id'};

$rndPort = mt_rand(11000, 11900);
$data_string = '{"PortBindings":{ "5900/tcp": [{ "HostIp" : "'.$dockerhost.'", "HostPort": "'.$rndPort.'" }] }}';

$ch = curl_init('http://'.$dockerhost.':4243/containers/' . $contId . '/start');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json',
    'Content-Length: ' . strlen($data_string))
);

$result = curl_exec($ch);

curl_close($ch);

$ch = curl_init('http://'.$dockerhost.':4243/containers/' . $contId . '/json');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
if( !($result = curl_exec($ch)) ) {
    curl_close($ch);
    exit;
}
curl_close($ch);

$obj = json_decode($result);

$hostPort =  $obj->HostConfig->{'PortBindings'}->{'5900/tcp'}[0]->{'HostPort'};
$name = $obj->Name;
$name = str_replace("/", "", $name);

$pass = trim(shell_exec('makepasswd --minchars 15 --maxchars 25'));

$ch = curl_init('http://127.0.0.1/createUser.php?user='.$name.'&pw='.$pass.'&cn='.$name.'&ip='.$dockerhost.'&port='.$hostPort);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
$arr = array ('name'=>$name,'pw'=>$pass);
echo json_encode($arr);

?>

Screenshots:

guac2

guac3
Video:

You get the point. You’ll need to implement some kind of flood control, user cleanup, etc. There are so many things that would need to be cleaned up to make this a viable product. Again, this is just a proof of concept. I am not going to make this example usable, but I made some screenshots and a video to demonstrate.