This page explains how to use a Guacamole server with VCL. It does not cover the details of getting a Guacamole server set up.
Configure Guacamole for database support using MariaDB as well as the same authentication backend that you use with your VCL site so that you don't need to manage accounts separately.
Create a user account on the VM running Guacamole that VCL can use to ssh into the VM for running commands (I'll use vclguac). The account will need sudo access to run two scripts that will be created for managing iptables. The account will also need an account within MariaDB for executing SQL statements against the guacamole database (I'll use vclguacsql). The MariaDB account will need SELECT, INSERT, and DELETE for the guacamole database.
Create a directory named logs in vclguac's home directory:
mkdir /home/vclguac/logs
Create 3 scripts and a logs directory in vclguac's home directory. Make sure you make the .sh scripts executable after creating them using "chmod +x <filename>":
#!/bin/bash pass=replace_with_your_pass logfile=/home/vclguac/logs/addremoveconnections.log if [ "$#" -lt 3 ]; then echo "Usage:" echo "" echo "$0 <username> <IP> \"<image name>\"" exit 1 fi username=$1 IP=$2 image=$3 remoteIP=$4 conname="$image - $IP" now=`date` echo "================================================================================" >> $logfile echo "$now - adding connection entry" >> $logfile echo "user: $username" >> $logfile echo "IP: $IP" >> $logfile echo "userIP: $remoteIP" >> $logfile echo "image: $image" >> $logfile echo "name: $conname" >> $logfile # check that user has account cnt=$(mysql -s -u vclguacsql -p$pass guacamole_db -e "SELECT COUNT(user_id) FROM guacamole_user WHERE full_name = '$username';" | tail -n 1) if [[ "$cnt" -ne 1 ]]; then cat /home/vclguac/createusertemplate.sql | sed "s/THENEWUSER/$username/g" | mysql -u vclguacsql -p$pass guacamole_db | tee -a $logfile fi mysql -u vclguacsql -p$pass guacamole_db -e "INSERT INTO guacamole_connection (connection_name, protocol) VALUES ('$conname', 'rdp');" | tee -a $logfile mysql -u vclguacsql -p$pass guacamole_db -e "INSERT INTO guacamole_connection_permission (entity_id, connection_id, permission) VALUES ((SELECT entity_id FROM guacamole_entity WHERE name = '$usern ame' AND \`type\` = 'USER'), (SELECT connection_id FROM guacamole_connection WHERE connection_name = '$conname' AND protocol = 'rdp'), 'READ');" | tee -a $logfile mysql -u vclguacsql -p$pass guacamole_db -e "INSERT INTO guacamole_connection_parameter (connection_id, parameter_name, parameter_value) VALUES ((SELECT connection_id FROM guacamole_connection WHE RE connection_name = '$conname' AND protocol = 'rdp'), 'disable-auth', 'true'), ((SELECT connection_id FROM guacamole_connection WHERE connection_name = '$conname' AND protocol = 'rdp'), 'hostname ', '$IP'), ((SELECT connection_id FROM guacamole_connection WHERE connection_name = '$conname' AND protocol = 'rdp'), 'ignore-cert', 'true'), ((SELECT connection_id FROM guacamole_connection WHERE connection_name = '$conname' AND protocol = 'rdp'), 'port', '3389'), ((SELECT connection_id FROM guacamole_connection WHERE connection_name = '$conname' AND protocol = 'rdp'), 'resize-method', 'd isplay-update'), ((SELECT connection_id FROM guacamole_connection WHERE connection_name = '$conname' AND protocol = 'rdp'), 'security', 'any');" | tee -a $logfile if [[ $remoteIP != "" ]]; then sudo /usr/local/bin/add_iptables_client.sh $remoteIP $username $IP | tee -a $logfile fi
-- Generate salt SET @salt = UNHEX(SHA2(UUID(), 256)); -- Create base entity entry for user INSERT INTO guacamole_entity (name, type) VALUES ('THENEWUSER', 'USER'); -- Create user and hash password with salt INSERT INTO guacamole_user ( entity_id, password_salt, password_hash, password_date, full_name ) SELECT entity_id, @salt, UNHEX(SHA2(CONCAT(LEFT(MD5(RAND()), 20), HEX(@salt)), 256)), CURRENT_TIMESTAMP, 'THENEWUSER' FROM guacamole_entity WHERE name = 'THENEWUSER' AND type = 'USER'; INSERT INTO guacamole_user_permission (entity_id, affected_user_id, permission) VALUES ((SELECT entity_id FROM guacamole_entity WHERE name = 'THENEWUSER' AND `type` = 'USER'), (SELECT user_id FROM guacamole_user WHERE full_name = 'THENEWUSER'), 'READ'), (1, (SELECT user_id FROM guacamole_user WHERE full_name = 'THENEWUSER'), 'READ'), (1, (SELECT user_id FROM guacamole_user WHERE full_name = 'THENEWUSER'), 'UPDATE'), (1, (SELECT user_id FROM guacamole_user WHERE full_name = 'THENEWUSER'), 'ADMINISTER'), (1, (SELECT user_id FROM guacamole_user WHERE full_name = 'THENEWUSER'), 'DELETE')
#!/bin/bash pass=replace_with_your_pass logfile=/home/vclguac/logs/addremoveconnections.log if [ "$#" -lt 2 ]; then echo "Usage:" echo "" echo "$0 <IP> \"<image name>\"" exit 1 fi IP=$1 image=$2 username=$3 remoteIP=$4 conname="$image - $IP" now=`date` echo "================================================================================" >> $logfile echo "$now - deleting connection entry" >> $logfile echo "user: $username" >> $logfile echo "IP: $IP" >> $logfile echo "userIP: $remoteIP" >> $logfile echo "image: $image" >> $logfile echo "name: $conname" >> $logfile mysql -u vclguacsql -p$pass guacamole_db -e "DELETE FROM guacamole_connection WHERE connection_name = '$conname' AND protocol = 'rdp';" | tee -a $logfile if [[ $remoteIP != "" ]]; then sudo /usr/local/bin/del_iptables_client.sh $remoteIP $username $IP fi
Create 2 scripts at /usr/local/bin. Again, make sure you make the script executable after creating them using "chmod +x <filename>":
#!/bin/bash if [ "$#" -ne 3 ]; then echo "Usage:" echo "" echo "$0 <IP address> <username> <VCL node IP>" exit 1 fi userIP=$1 username=$2 vclIP=$3 /sbin/iptables -A clients -s $userIP -p tcp -m comment --comment "VCL: $username $vclIP" -m tcp --dport 443 -j ACCEPT /sbin/iptables-save > /etc/sysconfig/iptables
#!/bin/bash if [ "$#" -ne 3 ]; then echo "Usage:" echo "" echo "$0 <IP address> <username> <VCL node IP>" exit 1 fi userIP=$1 username=$2 vclIP=$3 /sbin/iptables -D clients -s $userIP -p tcp -m comment --comment "VCL: $username $vclIP" -m tcp --dport 443 -j ACCEPT /sbin/iptables-save > /etc/sysconfig/iptables
Grant vclguac sudo access to run the two scripts for managing IP tables by adding the following line to /etc/sudoers:
vclsystem ALL= NOPASSWD: /usr/local/bin/add_iptables_client.sh,/usr/local/bin/del_iptables_client.sh
The DataStructure.pm file needs to be patched to add OS information to it. Create a patch file named guacamole_DataStructure.patch with the following content in it:
diff --git managementnode/lib/VCL/DataStructure.pm managementnode/lib/VCL/DataStructure.pm index 8ffe9a2..238e164 100644 --- managementnode/lib/VCL/DataStructure.pm +++ managementnode/lib/VCL/DataStructure.pm @@ -2342,6 +2342,7 @@ sub get_reservation_info_json_string { $json_data->{reservation} = prune_hash_child_references($request_data_clone->{reservation}{$reservation_id}); $json_data->{imagerevision} = prune_hash_child_references($request_data_clone->{reservation}{$reservation_id}{imagerevision}); $json_data->{image} = prune_hash_child_references($request_data_clone->{reservation}{$reservation_id}{image}); + $json_data->{OS} = prune_hash_child_references($request_data_clone->{reservation}{$reservation_id}{image}{OS}); $json_data->{computer} = prune_hash_child_references($request_data_clone->{reservation}{$reservation_id}{computer}); if (defined($request_data_clone->{reservation}{$reservation_id}{computer}{vmhost})) { @@ -2349,10 +2350,15 @@ sub get_reservation_info_json_string { $json_data->{vmhost_computer} = prune_hash_child_references($request_data_clone->{reservation}{$reservation_id}{computer}{vmhost}{computer}); } - # TODO: figure out how to handle user info, what structure, etc #$json_data->{users} = $request_data_clone->{reservation}{$reservation_id}{users}; - #$json_data->{user} = $request_data_clone->{user}; - #$json_data->{user}{username} = $json_data->{user}{unityid}; + $json_data->{user} = prune_hash_child_references($request_data_clone->{user}); + $json_data->{user}{username} = $json_data->{user}{unityid}; + $json_data->{user}{affiliation} = $request_data_clone->{user}{affiliation}{name}; + + my @delete_keys = ('IMtypeid', 'sshpublickeys', 'showallgroups', 'validated', 'usepublickeys', 'RETRIEVAL_TIME', 'lastupdated', 'FEDERATED_LINUX_AUTHENTICATION', 'IMid', 'ROOTACCESS'); + foreach my $key (@delete_keys) { + delete $json_data->{user}{$key}; + } # IMPORTANT: delete vmprofile data and anything else that may contain passwords #delete $json_data->{computer}{vmhost}{vmprofile};
After creating the patch, cd to /usr/local/vcl and apply the patch with the following command (assuming the patch is in /tmp):
patch -p1 < /tmp/guacamole_DataStructure.patch
You should see output similar to:
patching file lib/VCL/DataStructure.pm
Hunk #1 succeeded at 2303 (offset -39 lines).
Hunk #2 succeeded at 2311 (offset -39 lines).
Restart vcld after applying the patch.
There are three hook scripts that need to be added to the management node so that connections are added/removed on the Guacamole server as reservations are created and deleted. All of the scripts are located under /usr/local/vcl/tools/ManagementNode/Scripts. Ensure to set the IP address for your Guacamole server in place of 10.10.10.10 for $guacServerIP.
#!/usr/bin/perl use strict; use warnings FATAL => 'all'; use JSON; my $guacServerIP=10.10.10.10; my $datafile = $ARGV[0]; if(! (open(DATA, '<', "$datafile"))) { exit 1; } my $jsondata = do { local $/; <DATA> }; close(DATA); my $data = decode_json($jsondata); my $image = $data->{image}{prettyname}; $image =~ s/[\[\]\(\)\!\@\#\$\%\^\&\*\+\=\<\>]//g; if($data->{OS}{type} eq 'windows') { `ssh -q $data->{computer}{privateIPaddress} c:/windows/sysnative/netsh.exe advfirewall firewall add rule name=\\\\\\"VCL: allow TCP/3389 from Guacamole server\\\\\\" dir=in action=allow protocol=tcp localport=3389 remoteip=$guacServerIP`; `echo "\$(date) added firewall rule on $data->{computer}{IPaddress} for Guacamole server" >> /var/log/guacamole.log`; }
#!/usr/bin/perl use strict; use warnings FATAL => 'all'; use JSON; my $guacServerIP=10.10.10.10; my $datafile = $ARGV[0]; if(! (open(DATA, '<', "$datafile"))) { exit 1; } my $jsondata = do { local $/; <DATA> }; close(DATA); my $data = decode_json($jsondata); my $image = $data->{image}{prettyname}; $image =~ s/[\[\]\(\)\!\@\#\$\%\^\&\*\+\=\<\>]//g; if($data->{OS}{type} eq 'windows') { `ssh -q -i /etc/vcl/vcl.key vclguac\@ $guacServerIP ./delconnection.sh $data->{computer}{IPaddress} \\'$image\\' $data->{user}{unityid} $data->{reservation}{remoteIP}`; `ssh -q $data->{computer}{privateIPaddress} c:/windows/sysnative/netsh.exe advfirewall firewall delete rule name=\\\\\\"VCL: allow TCP/3389 from Guacamole server\\\\\\"`; `echo "\$(date) deleted firewall rule on $data->{computer}{IPaddress} for Guacamole server" >> /var/log/guacamole.log`; }
#!/usr/bin/perl use strict; use warnings FATAL => 'all'; use JSON; my $guacServerIP=10.10.10.10; my $datafile = $ARGV[0]; if(! (open(DATA, '<', "$datafile"))) { exit 1; } my $jsondata = do { local $/; <DATA> }; close(DATA); my $data = decode_json($jsondata); my $image = $data->{image}{prettyname}; $image =~ s/[\[\]\(\)\!\@\#\$\%\^\&\*\+\=\<\>]//g; if($data->{OS}{type} eq 'windows') { `ssh -q -i /etc/vcl/vcl.key vclguac\@$guacServerIP ./addconnection.sh $data->{user}{unityid} $data->{computer}{IPaddress} \\'$image\\' $data->{reservation}{remoteIP}`; `ssh -q $data->{computer}{privateIPaddress} c:/windows/sysnative/netsh.exe advfirewall firewall add rule name=\\\\\\"VCL: allow TCP/3389 from Guacamole server\\\\\\" dir=in action=allow protocol=tcp localport=3389 remoteip= $guacServerIP`; `echo "\$(date) added firewall rule on $data->{computer}{IPaddress} for Guacamole server" >> /var/log/guacamole.log`; `ssh -q $data->{computer}{privateIPaddress} \"echo vcld_tainted=user may have logged in >> currentimage.txt\"` }
For users to be able to see information about connecting to their reservation through Guacamole, a connection method needs to be added to the VCL database that can then be assigned to images. Use the following SQL statements with your VCL database to do so. Make sure to adjust the URL in the first query and change 10.10.10.10 to match your setup. It would be a good idea to get a valid SSL certificate (vs a self signed one) and use the hostname of your Guacamole server in the URL before giving your users access to connect using Guacamole.
INSERT INTO connectmethod (name, description, connecttext, servicename, startupscript) VALUES ('guacamole', 'Guacamole Web Connection', 'Use your web browser to connect to the following URL. If you only have one reservation, you will be connected to it after logging in to Guacamole. If you have multiple reservations, select the desired reservation after logging in to the Guacamole server. Use the following username and password when logging in to your reservation after logging in to the Guacamole server. <br>\r\n<ul>\r\n<li><b>URL</b>: <a href=\"https://10.10.10.10/\">https://10.10.10.10/</a></li>\r\n<li><b>User ID</b>: #userid#</li>\r\n<li><b>Password</b>: #password#</li>\r\n</ul>\r\n', '', NULL); INSERT INTO connectmethodmap (connectmethodid, OStypeid, OSid, imagerevisionid, disabled, autoprovisioned) VALUES ((SELECT id FROM connectmethod WHERE name = 'guacamole'), (SELECT id FROM OStype WHERE name = 'windows'), NULL, NULL, 0, 1);
After adding these entries to your database, you will be able to assign Guacamole Web Connection to your Windows images under the Advanced Options in Edit Image Profiles.