Skip to content

GUACAMOLE-2137 : OpenBao/Hashicorp Vault extension #1216

Open
adb014 wants to merge 22 commits into
apache:mainfrom
adb014:hashicorp
Open

GUACAMOLE-2137 : OpenBao/Hashicorp Vault extension #1216
adb014 wants to merge 22 commits into
apache:mainfrom
adb014:hashicorp

Conversation

@adb014

@adb014 adb014 commented May 28, 2026

Copy link
Copy Markdown

This PR was first based on #1143, but has now combined with #1116 as well, so I stole the Jira ticket of #1116. The main changes are

  • It supports both OpenBao and Hashicorp Vault, so this PR also address both GUACAMOLE-2137: Introduce HashiCorp Vault token handler (based on the KSM module) #1116 and GUACAMOLE-2196: OpenBao Vault Integration Extension #1143
  • It uses the same idea as GUACAMOLE-2137: Introduce HashiCorp Vault token handler (based on the KSM module) #1116 to have the tokens represented by their paths in the Vault, but uses a URL like token vault://
  • Uses the vault path-help function on the path /sys/internal/ui/mounts/ to detect the secret engine type of a path. Access to /sys/mounts is not needed (it presents a security risk)
  • It uses spring-vault to talk to the vault
  • It adds support for both KV_1 and KV_2 Key/Values secret engines
  • It adds support of the LDAP secret engine for static, dynamic and service accounts
  • It adds support of the SSH secret engine including both SSH one-time passwords and signed User certificates
  • It adds supports for the database secret engine, allowing Guacamole itself to obtain its username, password and if the database is configured with additional static values, the URI of the database server and the database itself. Note with OpenBao only the username and password is configurable in this manner, but with Hashicorp version 1.10 or later and static fields in the database engine, all of the database arguments can be configured
  • Adds the possiblity of including sub-tokens with the Vault tokens (ex: vault://kv1/users{GUAC_USERNAME}/password)
  • Gets the connectionGroup and User fallback functions in GUACAMOLE-2137: Introduce HashiCorp Vault token handler (based on the KSM module) #1116 to actually work
  • Doesn't use a Base64 configuration string, but real Guacamole configuration options
  • Doesn't use a sanitize function on TextFields of the Form, but rather PasswordField types
  • Finally kept the user vaults as the way its implemented the risk is minimal by default and the adminsitrator really has to mess up to create a risk
  • Replace the hand-rolled cache of GUACAMOLE-2137: Introduce HashiCorp Vault token handler (based on the KSM module) #1116 with Caffeine and use for very short term (10 seconds by default cache)

As this PR is fully documented in the markdown/jinja I won't do in to details of the working of this extension, but rather discuss the problems I encountered

Simplified token names:

The KSM extension uses token names like KEEPER_SERVER_USERNAME as only a single Keeper secret record with relatively well controls key values is used. This extension allows basically any mount path of the secret engine and path to the secret record within the Vault and so multiple secret records. Its therefore difficult to see how the KSM token naming method could be projected on to a Vault.

As in #1116 I chose to expose the Vault paths directly in the tokens, allowing the complete freedom. This means that the file
vault-token-mapping.yml is probably not needed,

Token paths:

In most cases the token name to use will be the path to the Vault secret record post-pending with the secret value to extract. This has several issues

  1. The Vault itself doesn't create the SSH certificates so the secret names "unsigned", "public" and "private" are imposed by Guacamole
  2. The LDAP service accounts are checked out via and endpoint "/check-out". I chose to drop the "/check-out" in the token path in the extension
  3. Certain secret keys are added for consistency with other endpoints. For example the password for SSH OTP is returned in the value "key". This value is kept but copied to "password". LDAP service account usernames and returned in "service_account_name" which is copied to "username"

Secret caching:
When obtaining a secret record from the Vault, the secrets in the record are associated between themselves. For example a dynamic LDAP account has a single use username and password. The structure of the base vault extension assumes a function getValue that returns a single secret value. We therefore need to cache the values for a single record to keep the association between common values.

In the getTokens method the VaultSecretService, this is not a problem as all tokens for a single connection are resolved at once and we can set a UUID as a key in the cache to ensure common values are kept together. It also prevents session stealing by timing issues in concurrent connections. For the secrets looked up for guacamole.propertes.vlt that basically only concern secrets without any user context this is no problem. The issue is with tokens looked up in vault-token-mapping.yml. These secrets are looked up individually, but they have a user context. To avoid secret stealing and try to keep common secrets together I used a cache key combining GUAC_USERNAME, USERNAME and the path to the Vault secret record.

By the nature of Vault tokens where the password rotation at each usage, this cache should only be very short term, in the order of a few seconds. I used caffiene for the cache

Spring-Vault :
Due to the Tomcat 9 dependency of Guacamole I was forced to use a version of spring-vault compatible with Java-11, so I'm left using version 2.3.4 of spring-vault, when spring-vault is currently working on version 4.x. This came with many issues, the main one being that the LifecycleAwareSessionManager class of spring-vault in version 2.3.4 is pretty much broken, so I had to reimplement my own SessionManager. The use of spring-vault in this pull-request is therefore more of a promise to be able to simplify the extension when Guacamole migrates to a newer Tomcat

SSH ed25519:
The SSH certificate signing reqires Guacamole to be able to generate temporary SSH certificates. I used apache SSHD for this. The Java 11 versions of apache SSHD don't include ed25519 directly and I had to include i2p as a crypto provider for ed25519. I removed the BouncyCastle Fips crypto provider from #1116, as it wasn't initially used and it prevented the use of ed25519 as FIPS-140 is a relatively old standard that doesn't include or allow elliptique curve crypto

LDAP Service accounts:
The LDAP service accounts in the Vault must be checked out and checked back in again after use. This presented two problems for this extension.

  1. Within the extension the GuacamoleTunnel is not yet opened, and so I have no unique connection ID for the extension. I addressed this by assuming that the TunnelConnectEvent will be very rapidly after the call to getTokens in the extension, and setting the connection UUID in the TunnelConnectEvent.
  2. The TunnelCloseEvent is a bit random when and if it is called. I wrote the code to check-in the service account if a TunnelCloseEvent is recieved, but as a back-up I fixed the TTL of the checked out accounts to 2 hours to ensure the Vault automatically checks in these accounts. In Guacamole the service accounts check in information is just dropped after 2 hours. If we can get more reliable TunnelCloseEvents on the termination of the sessions, the code as is should just work

Non renewable or expired token
Even in recent spring-vault implementations of LifecycleAwareSessionManager, an expired or non renewable token will cause the SessionManager to just stop in expiry. I'd like to use VaultAgent for complex authentification methods via a token sink file. This essentially means that a reauthentication via a call to the function ClientAuthentication.login() of spring-vault should be used if the token is expired or non-renewable. I implemented this in my SessionManager

@adb014

adb014 commented Jun 1, 2026

Copy link
Copy Markdown
Author

Rebased on staging/1.6.1 and the PR updated

TdlQ and others added 11 commits June 5, 2026 17:59
…apache#1214

- It supports both OpenBao and Hashicorp Vault
- Uses url like tokens of the form "vault://<mount>/<path>/<secret>"
- Uses the path-help function of the vault to determine the vault type
- Uses spring-vault to communicate with the vault
- Adds support for both KV_1 and KV_2 Key-Value secret engines
- Adds support for LDAP secret engine and static, dynamic and service accounts
- Adds support of the SSH secret engine including both SSH one-time passwords and signed user certificates
- Adds supports for the database secret engine, allowing Guacamole itself to obtain its username, password and if the database is configured with additional static values, the URI of the database server and the database itself
- Adds the possiblity of including sub-tokens with the Vault tokens (ex: vault://kv1/users{GUAC_USERNAME}/password)
- Gets the connectionGroup and User fallback functions in apache#1116 to actually work
- Doesn't use a Base64 configuration string, but real Guacamole configuration options
- Doesn't use a sanitize function on TextFields of the Form, but rather PasswordField types
…e statements. Remove unused GuacamoleExceptionSupplier
… that the Future isn't invalidated in the cache before its returned
  - Use final on all local and method arguments where possible
  - Comment all public methods and constructor
  - Catch only specific exceptions
  - Remove unused imports
  - Remove code that can no longer be used
  - No nested if block more than 2 levels deep
  - Prefer getter functions to direct access to class variables
  - Don't throw and catch exception in the same function, the the catch blocks
    closer to where the exceptions are thrown
  - Split HvClientProvider out from HvSecretService to reduce complexity and
    increase readibility of both classes
  - Create helper function for HvSecretService.prepareToken to regroup similar
    logic and reduce CognitiveComplexity of prepareToken
  - Split HvClient fallback logic into a seperate function, use an ordered List
    of HvClients in this function to simplify the logic
@adb014 adb014 changed the base branch from staging/1.6.1 to main June 5, 2026 16:22
@adb014

adb014 commented Jun 5, 2026

Copy link
Copy Markdown
Author

I converted this PR to a Draft as after having run the code through a SAST/linter and taking a deep breath after the
conclusions, there are some serious refactoring to do to make this code ready... I've already made most of these changes my SAST (using PMD) suggested.

Didn't last long in Draft form, 'cause I committed my changes a few hours after following testing

@adb014 adb014 marked this pull request as ready for review June 5, 2026 16:27
@adb014

adb014 commented Jun 10, 2026

Copy link
Copy Markdown
Author

For discussion of my proposed TokenFilter change, please see regex101.com for an example of how the regular expression I propose functions

David Bateman added 2 commits June 12, 2026 10:12
  - Add ECDSA ec256 to Guacamole signed certifcates
  - Add ability to get the Vault to issue the certificate
  - Add URL query parameters to modify certificate type on a token
    by token basis.
David Bateman added 4 commits June 15, 2026 16:09
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