Skip to content

GUACAMOLE-2219: Fix vault token resolution for BALANCING groups#1163

Open
subbareddyalamur wants to merge 1 commit intoapache:mainfrom
subbareddyalamur:fix/vault-balancing-group-token-resolution
Open

GUACAMOLE-2219: Fix vault token resolution for BALANCING groups#1163
subbareddyalamur wants to merge 1 commit intoapache:mainfrom
subbareddyalamur:fix/vault-balancing-group-token-resolution

Conversation

@subbareddyalamur
Copy link

Summary

When connecting through a BALANCING connection group, the JDBC layer internally selects and connects a child connection, bypassing the vault extension's TokenInjectingConnection wrapper. This means addTokens(Connection) is never called for the selected child, and vault-managed tokens (e.g. KEEPER_USER_PASSWORD) are not resolved — causing authentication failures on the child connection.

This PR fixes two related issues:

  • VaultUserContext.addTokens(ConnectionGroup) now detects BALANCING groups and pre-resolves vault tokens for all child connections, ensuring tokens are available when the JDBC layer applies them to the selected child's configuration
  • KsmSecretService.getTokens() now guards against null GuacamoleConfiguration, which is always null for connection groups (they have no protocol configuration). Previously this caused a NullPointerException

Root Cause

TokenInjectingConnectionGroup.connect() calls addTokens(ConnectionGroup) and then delegates to the underlying JDBC connection group. For BALANCING groups, AbstractGuacamoleTunnelService acquires the child connection internally and connects it directly — the child is a raw ModeledConnection, NOT wrapped by TokenInjectingConnection. Therefore addTokens(Connection) is never invoked for the child, and vault tokens that depend on connection parameters (hostname, username) are never resolved.

Changes

File Change
VaultUserContext.java Iterate child connections of BALANCING groups in addTokens(ConnectionGroup) and resolve vault tokens for each child using privileged access to connection configuration
KsmSecretService.java Return early from getTokens() when config is null (connection groups have no GuacamoleConfiguration)

Test Plan

  • Connect to a direct connection with KSM vault tokens configured — tokens should resolve as before (no regression)
  • Connect through a BALANCING connection group containing connections with KSM vault tokens — tokens should now resolve correctly for the selected child connection
  • Connect through an ORGANIZATIONAL connection group — should behave as before (no BALANCING logic triggered)
  • Verify no NullPointerException in logs when connecting through any connection group type

@subbareddyalamur subbareddyalamur changed the title GUACAMOLE-XXXX: Fix vault token resolution for BALANCING groups GUACAMOLE-2219: Fix vault token resolution for BALANCING groups Feb 11, 2026
@subbareddyalamur subbareddyalamur force-pushed the fix/vault-balancing-group-token-resolution branch from 98e3ee3 to 8998fca Compare February 11, 2026 15:45
Comment on lines +378 to +418
for (String childId : childIds) {
try {

Connection child = getPrivileged()
.getConnectionDirectory().get(childId);
if (child == null)
continue;

GuacamoleConfiguration childConfig =
child.getConfiguration();
if (childConfig == null)
continue;

logger.debug("Resolving vault tokens for BALANCING "
+ "child connection \"{}\" (\"{}\").",
child.getIdentifier(), child.getName());

TokenFilter childFilter = createFilter();
childFilter.setToken(CONNECTION_NAME_TOKEN,
child.getName());
childFilter.setToken(CONNECTION_IDENTIFIER_TOKEN,
child.getIdentifier());

Map<String, String> parameters =
childConfig.getParameters();

String hostname = parameters.get("hostname");
if (hostname != null && !hostname.isEmpty())
childFilter.setToken(CONNECTION_HOSTNAME_TOKEN,
hostname);

String username = parameters.get("username");
if (username != null && !username.isEmpty())
childFilter.setToken(CONNECTION_USERNAME_TOKEN,
username);

tokens.putAll(resolve(getTokens(child,
confService.getTokenMapping(), childFilter,
childConfig, new TokenFilter(tokens))));

}
Copy link
Contributor

Choose a reason for hiding this comment

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

I see what you're trying to do, but I'm not sure this is the best way to go about it. It looks to me like the code, here, basically duplicates code elsewhere, processing a handful of standard tokens (connection name/identifier, username, etc.). This creates two problems:

  • What if other extensions provide other tokens? For example, the LDAP extension provides the ability to use LDAP attributes as tokens in the connection data, as do the various SSO providers. I think the implementation, here, would not capture those correctly?
  • What happens if other tokens are added in the future? Whether processing these standard ones or all of the ones from all of the extensions, creating a location, here, where we have to maintain all of the tokens we want available and process them seems cumbersome and destined to get out-of-sync with the rest of the code.

There has to be a better way...

@subbareddyalamur
Copy link
Author

subbareddyalamur commented Feb 12, 2026

@necouchman Thank you for the review. I've refactored the approach based on your feedback.

Instead of manually building tokens for each BALANCING child (which duplicated the logic in addTokens(Connection)), the code now simply delegates to the existing addTokens(child, tokens) for each child connection:

for (String childId : childIds) {
    Connection child = getPrivileged()
            .getConnectionDirectory().get(childId);
    if (child == null) continue;
    addTokens(child, tokens);
}

This eliminates the code duplication entirely — the same addTokens(Connection, Map) method used for direct connections is now reused for BALANCING children. Any future changes to connection token resolution (new token types, etc.) will automatically apply to both paths.

Regarding other extensions' tokens (LDAP attributes, SSO tokens): this is a deeper issue in the JDBC BALANCING pipeline itself — AbstractGuacamoleTunnelService.getGuacamoleTunnel(ConnectionGroup) selects and connects a ModeledConnection directly without running it through the extension decoration chain, so no extension's addTokens(Connection) gets called for the selected child. The proper fix for that would be at the JDBC layer. This PR ensures the vault extension's own token resolution works correctly for BALANCING children without duplicating any code.

@subbareddyalamur subbareddyalamur force-pushed the fix/vault-balancing-group-token-resolution branch from 8998fca to d12222a Compare February 12, 2026 03:24
@necouchman
Copy link
Contributor

@subbareddyalamur I certainly like the new approach better - I think calling the existing functions is preferable to rewriting all of the logic within that block. That said, I'm still a little unsure as to whether or not we want to preload the data for all of the connections? It seems like there ought to be a way to have the resolution of these tokens done after the connection is selected?

@subbareddyalamur
Copy link
Author

@subbareddyalamur I certainly like the new approach better - I think calling the existing functions is preferable to rewriting all of the logic within that block. That said, I'm still a little unsure as to whether or not we want to preload the data for all of the connections? It seems like there ought to be a way to have the resolution of these tokens done after the connection is selected?

@necouchman Not feasible without changes to the JDBC tunnel layer. The resolution would require introducing a new hook in AbstractGuacamoleTunnelService (JDBC layer), which is out of scope for a vault-only fix. The pre-resolution approach is a tradeoff — slightly more work upfront, but it stays within the vault extension's boundaries without touching the core tunnel service

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.

3 participants