1

In my project, some EC2 instances will be shut down. These instances will only be connected when the user needs to work. Users will access the instances using a clientless remote desktop gateway called Apache Guacamole.

If the instance is stopped, how start an EC2 instance through Apache Guacamole?

Home Screen enter image description here

Edvaldo Silva
  • 904
  • 2
  • 16
  • 29

3 Answers3

2

Guacamole is, essentially, an RDP/VNC/SSH client and I don't think you can get the instances to startup by themselves since there is no possibility for a wake-on-LAN feature or something like it out-of-the-box.

I used to have a similar issue and we always had one instance up and running and used it to run the AWS CLI to startup the instances we wanted.

Alternatively you could modify the calls from Guacamole to invoke a Lambda function to check if the instance you wish to connect to is running and start it up if not; but then you'd have to deal with the timeout for starting a session from Guacamole (not sure if this is a configurable value from the web admin console, or files), or set up another way of getting feedback for when your instance becomes available.

Oscar De León
  • 300
  • 1
  • 2
  • 7
0

There was a discussion in the Guacamole mailing list regarding Wake-on-LAN feature and one approach was proposed. It is based on the script that monitors connection attempts and launches instances when needed.

Although it is more a workaround, maybe it will be helpful for you. For the proper solution, it is possible to develop an extension.

You may find the discussion and a link to the script here:

http://apache-guacamole-general-user-mailing-list.2363388.n4.nabble.com/guacamole-and-wake-on-LAN-td7526.html

http://apache-guacamole-general-user-mailing-list.2363388.n4.nabble.com/Wake-on-lan-function-working-td2832.html

mnikolic
  • 572
  • 1
  • 5
  • 9
0

There is unfortunately not a very simple solution. The Lambda approach is the way we solved it.

Guacamole has a feature that logs accesses to Cloudwatch Logs. So next we need the the information of the connection_id and the username/id as a tag on the instance. We are automatically assigning theses tags with our back-end tool when starting the instances. Now when a user connects to a machine, a log is written to Cloudwatch Logs. A filter is applied to only get login attempts and trigger Lambda.

The triggered Lambda script checks if there is an instance with such tags corresponding to the current connection attempt and if the instance is stopped, plus other constraints, like if an instance is expired for example. If yes, then the instance gets started, and in roughly 40 seconds the user is able to connect.

The lambda scripts looks like this:

#receive information from cloudwatch event, parse it call function to start instances

import re
import boto3
import datetime

from conn_inc import *
from start_instance import *

def lambda_handler(event, context):

    # Variables
    region = "eu-central-1"
    cw_region = "eu-central-1"
    
    # Clients
    ec2Client = boto3.client('ec2')
   
    # Session
    session = boto3.Session(region_name=region)
    
    # Resource
    ec2 = session.resource('ec2', region)
    
    print(event)
    #print ("awsdata: ", event['awslogs']['data'])
    userdata ={}
    userdata = get_userdata(event['awslogs']['data'])
    
    print ("logDataUserName: ", userdata["logDataUserName"], "connection_ids: ", userdata["startConnectionId"])

    start_instance(ec2,ec2Client, userdata["logDataUserName"],userdata["startConnectionId"])  
import boto3
import datetime
from datetime import date
import gzip
import json
import base64
from start_related_instances import *

def start_instance(ec2,ec2Client,logDataUserName,startConnectionId):
    
    # Boto 3
    # Use the filter() method of the instances collection to retrieve
    # all stopped EC2 instances which have the tag connection_ids.

    instances = ec2.instances.filter(
        Filters=[
          {
            'Name': 'instance-state-name',
            'Values': ['stopped'],
          },
          {
            'Name': 'tag:connection_ids',
            'Values': [f"*{startConnectionId}*"],
          }
        ]
    )
    # print ("instances: ", list(instances))
    #check if instances are found
    if len(list(instances)) == 0:
        print("No instances with connectionId ", startConnectionId, " found that is stopped.")
    else:
        for instance in instances:
            print(instance.id, instance.instance_type)
             
            expire = ""
            connectionName = ""
            
            for tag in instance.tags:
                if tag["Key"] == 'expire':    #get expiration date
                      expire = tag["Value"]   
            
            if (expire == ""):
                print ("Start instance: ", instance.id, ", no expire found")
                ec2Client.start_instances(
                    InstanceIds=[instance.id]
                    )
            else:
                
                print("Check if instance already expired.")
                
                splitDate = expire.split(".")
                expire = datetime.datetime(int(splitDate[2]) , int(splitDate[1]) , int(splitDate[0]) ) 
                args = date.today().timetuple()[:6]
                today = datetime.datetime(*args)
        
                if (expire >= today):
                    print("Instance is not yet expired.")
                    print ("Start instance: ", instance.id, "expire: ", expire, ", today: ", today)
                    ec2Client.start_instances(
                         InstanceIds=[instance.id]
                         )
                        
                else:
                    print ("Instance not started, because it already expired: ", instance.id,"expiration: ", f"{expire}", "today:", f"{today}")          


def get_userdata(cw_data):
    compressed_payload = base64.b64decode(cw_data)
    uncompressed_payload = gzip.decompress(compressed_payload)
    payload = json.loads(uncompressed_payload)
    message = ""
    
    log_events = payload['logEvents']
    for log_event in log_events:
        message = log_event['message']
        # print(f'LogEvent: {log_event}')
    
    #regex = r"\'.*?\'"

    #m = re.search(str(regex), str(message), re.DOTALL)
    
    logDataUserName = message.split('"')[1]   #get the username from the user logged into guacamole "Adm_EKoester_1134faD"
    startConnectionId = message.split('"')[3]   #get the connection Id of the connection which should be started

    
    # create dict
    dict={}
    dict["connected"] = False
    dict["disconnected"] = False
    dict["error"] = True
    dict["guacamole"] = payload["logStream"]
    dict["logDataUserName"] = logDataUserName
    dict["startConnectionId"] = startConnectionId
    
    # check for connected or disconnected
    ind_connected = message.find("connected to connection")
    ind_disconnected = message.find("disconnected from connection")
    # print ("ind_connected: ", ind_connected)
    # print ("ind_disconnected: ", ind_disconnected)
    
    if ind_connected > 0 and not ind_disconnected > 0:
        dict["connected"] = True
        dict["error"] = False
    elif ind_disconnected > 0 and not ind_connected > 0:
        dict["disconnected"] = True
        dict["error"] = False
    
    return dict

The cloudwatch logs trigger for lambda like that: enter image description here

tickietackie
  • 327
  • 4
  • 12