Skip to content

Conversation

tkuhlengel
Copy link

Summary

In my company's business use case for Guacamole, for security and auditing purposes, we need to be able to ensure that any idle user is disconnected and logged off within a set period of time of idleness. 

In an ideal version of such a system, we would do the following. 

  1. Check if the user has interacted with the connection in the last X minutes, or if an active SFTP transfer is happening on the connection.
  2. If not, terminate the user connection after X minutes of inactivity. 
  3. If they remain idle, the Guacamole idle timer will log them out after the pre-configured login idle timeout.

This requires a lot of conditionals and would be more difficult to implement and maintain in an ongoing project like Guacamole. 

A more practical, yet sufficient, version is:

  1. The Administrator sets a maximum duration for any connection, specified in minutes. 
  2. Any connection that exceeds that duration, regardless of activity, is terminated while the user remains logged in.  They are free to reconnect if the user is still active. 
  3. The login idle timeout starts when the connection ends. 

This second option meets our business needs, and we would like to share it with others. 

Features

  • Add parameter connection-timeout in guacamole.properties, disconnecting users after connection-timeout minutes. Defaults to 0, disabling the feature.
  • Implement the connection timeout using a Map that stores the creation time of the connection.
  • Integrate the connection timeout check into the existing idle timeout function.

* Add defaults for parameter connection-timeout in guacamole.properties to disable feature.
* Implement the connection timeout using a Map based on creation time
* Integrate the connection timeout check into the existing idle timeout function.
@Copilot Copilot AI review requested due to automatic review settings September 16, 2025 19:14
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a connection timeout feature to automatically terminate connections after a specified duration, regardless of activity level. This addresses security and auditing requirements for ensuring idle users are disconnected within set time limits.

  • Adds a new connection-timeout parameter in guacamole.properties (defaults to 0/disabled)
  • Implements connection timeout tracking using a Map to store tunnel creation times
  • Integrates connection timeout checks into the existing session eviction mechanism

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
HashTokenSessionMap.java Adds connection-timeout property configuration and integrates timeout checking into session eviction task
GuacamoleSession.java Implements tunnel creation time tracking and expiration logic for individual connections

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

catch (GuacamoleException e) {
logger.error("Unable to read guacamole.properties: {}", e.getMessage());
logger.debug("Error while reading connection timeout value.", e);
connectionTimeoutValue = 0;
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra whitespace at the end of the line should be removed.

Suggested change
connectionTimeoutValue = 0;
connectionTimeoutValue = 0;

Copilot uses AI. Check for mistakes.

Comment on lines 365 to 379
UserTunnel tunnel = tunnels.get(tunnelId);
if (tunnel != null) {
try {
tunnel.close();
logger.debug("Closed tunnel \"{}\" due to connection timeout.", tunnelId);
}
catch (GuacamoleException e) {
logger.debug("Unable to close expired tunnel \"" + tunnelId + "\".", e);
}
// Remove from both maps regardless of whether close succeeded
tunnels.remove(tunnelId);
tunnelCreationTimes.remove(tunnelId);
closedCount++;
}
}
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tunnel removal logic is duplicated here and in the removeTunnel method. Consider extracting this into a private helper method to avoid code duplication.

Suggested change
UserTunnel tunnel = tunnels.get(tunnelId);
if (tunnel != null) {
try {
tunnel.close();
logger.debug("Closed tunnel \"{}\" due to connection timeout.", tunnelId);
}
catch (GuacamoleException e) {
logger.debug("Unable to close expired tunnel \"" + tunnelId + "\".", e);
}
// Remove from both maps regardless of whether close succeeded
tunnels.remove(tunnelId);
tunnelCreationTimes.remove(tunnelId);
closedCount++;
}
}
if (removeTunnelById(tunnelId, "Closed tunnel \"{}\" due to connection timeout.", "Unable to close expired tunnel \"{}\".")) {
closedCount++;
}
}
/**
* Removes the tunnel with the given ID from the session, closing it and removing
* its creation time. Returns true if a tunnel was removed, false otherwise.
*
* @param tunnelId
* The ID of the tunnel to remove.
* @param successMsg
* The message to log on successful close.
* @param errorMsg
* The message to log on error.
* @return
* true if a tunnel was removed, false otherwise.
*/
private boolean removeTunnelById(String tunnelId, String successMsg, String errorMsg) {
UserTunnel tunnel = tunnels.get(tunnelId);
if (tunnel != null) {
try {
tunnel.close();
logger.debug(successMsg, tunnelId);
}
catch (GuacamoleException e) {
logger.debug(errorMsg, tunnelId, e);
}
tunnels.remove(tunnelId);
tunnelCreationTimes.remove(tunnelId);
return true;
}
return false;
}

Copilot uses AI. Check for mistakes.

Comment on lines 65 to 69
/**
* Creation times for all tunnels, indexed by tunnel UUID.
*/
private final Map<String, Long> tunnelCreationTimes = new ConcurrentHashMap<>();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on whether it makes sense to do it this way, or to just add a way within the various Tunnel interfaces/classes, to track tunnel creation time? The GuacamoleTunnel interface could get a new method getCreationTime() (or whatever makes sense), and then one of the implementing classes could get either a Long or perhaps one of the Java Time/DateTime fields to track this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with changing it to use the GuacamoleTunnel as backing for the creation time. I just need a hint on how to iterate through the currently open connections/Tunnels

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the tunnels Map, directly above this, has all currently active tunnels indexed by UUID. So, you should just be able to iterate through tunnels and, for each tunnel, grab the creation time and close it if it has expired. I think the only real substantive change over what you're already doing is that, instead of tracking tunnel creation time in a separate Map you're just using the already-implemented map and iterating through that?

- Introduce creationTime field in AbstractGuacamoleTunnel
- Implement getCreationTime method in GuacamoleTunnel interface
- Update DelegatingGuacamoleTunnel to return tunnel creation time
- Remove tunnel creation time tracking map from GuacamoleSession
- Adjust methods in GuacamoleSession to use GuacamoleTunnel.getCreationTime() and reuse the existing Map of active tunnels.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants