diff --git a/src/include/openbao-optional.properties.in b/src/include/openbao-optional.properties.in new file mode 100644 index 00000000..04032072 --- /dev/null +++ b/src/include/openbao-optional.properties.in @@ -0,0 +1,46 @@ +# A username of an account in the Vault for use with Guacamole +vault-username: guacamole + +# A password for the account in the Vault used by Guacamole +vault-password: my-secret-password + +# The lifetime of the data in the Guacamole cache in milliseconds. +# This is used to avoid multiple concurrent requests for the same +# Vault record with different secret values. After this time the +# secret values are cleared from Guacamole +vault-cache-lifetime: 5000 + +# The maximum time that a request to the vault server can take in +# milliseconds. After this time a null value is returned for the +# secret requested +vault-request-timeout: 5000 + +# The maximum time allowed for a connection to the vault server in +# milliseconds. After this time a null value is returned for the +# secret requested +vault-connection-timeout: 5000 + +# The duration validity of the certificates generated for signed +# certification SSH connections. After this time the signed certificate +# can not be reused for a connection. +# +# It should be noted that in the case of drift of the clock between the +# ssh clients and the Vault server in seconds, a certificate might be +# invalidated incorrectly. This value must be sufficiently large to +# account for clock drift. +vault-ssh-connection-timeout: 1800 + +# The renewal delay for expiring Vault tokens in milliseconds. Tokens +# will be renewed prior to expiration by this delay +vault-token-renewal-delay: 10000 + +# The type of the SSH certificates generated by Guacamole for signed +# SSH certificate access. Valid types are `ed25519` and `rsa`. Only +# ed25519 and 4096-bit RSA certificates are supported. +# +# Please note that if your server is configured for strict FIPS-140 +# compliance, then ed25519 certificates will not be available, and +# Guacamole will fallback to using 4096-bit RSA certificates +# automatically +vault-ssh-type: ed25519 + diff --git a/src/include/openbao.properties.in b/src/include/openbao.properties.in new file mode 100644 index 00000000..7b795bd5 --- /dev/null +++ b/src/include/openbao.properties.in @@ -0,0 +1,6 @@ +# The URI of the OpenBao or Hashicorp Vault server to use. +vault-uri: http://localhost:8200 + +# The authentication token to use to access the vault +vault-token: s.IPzVb3b5dThhjIrBX245szisjTQcylwSjMEeyJqcidaH8Hf + diff --git a/src/index.md b/src/index.md index 2adfe741..1d9b59c6 100644 --- a/src/index.md +++ b/src/index.md @@ -50,7 +50,7 @@ troubleshooting Active Directory / LDAP mfa Single sign-on -vault +Vault token injection external-auth RADIUS Ad-hoc connections diff --git a/src/vault.md.j2 b/src/keeper.md.j2 similarity index 97% rename from src/vault.md.j2 rename to src/keeper.md.j2 index 20c91c9d..591bbf43 100644 --- a/src/vault.md.j2 +++ b/src/keeper.md.j2 @@ -1,21 +1,20 @@ {# vim: set filetype=markdown.jinja : #} {%- import 'include/ext-macros.md.j2' as ext with context -%} -Retrieving secrets from a vault -=============================== +Retrieving secrets from a Keeper Secret Manager +=============================================== Guacamole supports reading secrets such as connection-specific passwords from a +[Keeper Secret Manager (KSM)](https://www.keepersecurity.com/secrets-manager.html) key vault, automatically injecting those secrets into connection configurations -using [parameter tokens](parameter-tokens) or Guacamole configuration -properties via an additional, vault-specific configuration file analogous to -`guacamole.properties`. This support is intended with multiple vault providers -in mind and currently supports [Keeper Secrets Manager (KSM)](https://www.keepersecurity.com/secrets-manager.html). - +using [parameter tokens](parameter-tokens) or Guacamole configuration properties +via an additional, vault-specific configuration file analogous to +`guacamole.properties`. ```{include} include/warn-config-changes.md ``` -(vault-downloading)= +(keeper-downloading)= Installing/Enabling the vault extension --------------------------------------- @@ -83,11 +82,11 @@ question should be able to access. This token can be used to generate a base64-encoded configuration blob as described in the following step, or it can be used directly to set a KSM - config for a user or connection, as described in [the following section](guac-vault-config). + config for a user or connection, as described in [the following section](guac-keeper-config). 5. Copy the provided one-time token using [the KSM CLI tool](https://docs.keeper.io/secrets-manager/secrets-manager/secrets-manager-command-line-interface/init-command) to obtain the base64-encoded configuration that must be provided to - Guacamole with [the `ksm-config` property](guac-vault-config). **This token + Guacamole with [the `ksm-config` property](guac-keeper-config). **This token can only be used once, but the base64 configuration can be used indefinitely unless manually revoked within KSM:** @@ -105,7 +104,7 @@ question should be able to access. $ ``` -(guac-vault-config)= +(guac-keeper-config)= Required configuration ---------------------- @@ -178,7 +177,7 @@ The following additional, optional {{ ext.properties() }} may be set as desired to tailor the behavior of the KSM support: {% endcall %} -(completing-vault-install)= +(completing-keeper-install)= Completing installation ----------------------- diff --git a/src/openbao.md.j2 b/src/openbao.md.j2 new file mode 100644 index 00000000..d70dd6f2 --- /dev/null +++ b/src/openbao.md.j2 @@ -0,0 +1,1479 @@ +{# vim: set filetype=markdown.jinja : #} +{%- import 'include/ext-macros.md.j2' as ext with context -%} + +Retrieving Secrets from a Vault +=============================== + +Guacamole supports retrieving secrets—such as connection-specific passwords, +private keys, certificates, and credentials—from a **Vault**. These secrets are +injected automatically into connection configurations using **parameter tokens** +or into Guacamole configuration properties via an additional Vault-specific +configuration file analogous to `guacamole.properties`. + +This extension supports both **[OpenBao](https://openbao.org)** and +**[HashiCorp Vault](https://www.hashicorp.com/products/vault)**. Throughout this +document, the generic term **Vault** is used to refer to either implementation +unless a distinction is required. + +```{include} include/warn-config-changes.md +``` + +(vault-downloading)= +Installing/Enabling the Vault Extension +--------------------------------------- + +{{ ext.install('openbao', 'guacamole-vault', 'openbao/guacamole-vault-openbao') }} + +(adding-guac-to-vlt)= + +## Adding Guacamole to a Vault + +This section provides concrete examples for setting up the supported secret +engines in Vault for use with Guacamole. To allow an application like Guacamole +to access secrets stored in a Vault, the following steps are required: + +1. **Enable the required secret engines** (e.g., KV, SSH, LDAP, Database). +2. **Create appropriate mount paths** for each secret engine. +3. **Define access policies** to restrict Guacamole’s permissions. +4. **Configure authentication credentials** for Guacamole to authenticate with Vault. + +:::{note} +This document **does not** cover general Vault administration. For details on +creating and managing mount paths, authentication backends, and policies, refer +to the official documentation: + +- [OpenBao Documentation](https://openbao.org/docs) +- [HashiCorp Vault Documentation](https://developer.hashicorp.com/vault/docs) +::: + +All configuration examples below use **curl** exclusively to remain agnostic to +the specific Vault implementation (OpenBao or HashiCorp). + +Unless otherwise stated, the supplied commands should work for both OpenBao and +Hashicorp Vault. + +### Vault Connection Information + +Guacamole requires the following to connect to a Vault server: + +- The **network address** of the Vault server (e.g., `https://vault.example.com:8200`). +- **Valid authentication credentials** (e.g., token, username/password, or file-based). + +Vault exposes a standard **HTTP REST API**, and a typical Vault URI looks like: + +``` +https://vault.example.com:8200 +``` + +### Setting Up a Guacamole Access Policy + +Guacamole requires the following permissions in Vault: + +- **Read access** to the secrets it consumes. +- **Limited write access** for certain secret engines (e.g., SSH certificate signing). + +For **security and auditability**, it is **strongly recommended** to create a +**dedicated Vault policy** for Guacamole. Below is an example policy that grants +Guacamole access to supported secret engines, restricted to `guacamole` roles +in each engine. + +#### Example Policy (`guacamole.hcl`) + + +```bash +# Read from key-value secrets dedicated to Guacamole +path "kv/guacamole/*" { + capabilities = ["read"] +} + +# Allow SSH certificate signing for Guacamole +path "ssh/sign/guacamole_cert" { + capabilities = ["update"] +} + +# Allow SSH one-time password generation +path "ssh/creds/guacamole_otp" { + capabilities = ["update"] +} + +# Read/Update for LDAP service credentials +path "ldap/library/guacamole/*" { + capabilities = ["read", "update"] +} + +# Read static/dynamic LDAP credentials +path "ldap/*/guacamole/*" { + capabilities = ["read"] +} + +# Read database credentials +path "db/guacamole/*" { + capabilities = ["read"] +} +``` + +:::{note} +The first matching rule in the policy is used. It is therefore important to +place the specific rules in the policy before the general more lax rules. The +LDAP rules above are an example of this. +::: + +#### Uploading the Policy to Vault + +To upload the policy to Vault, use the following command: + +```bash +export VAULT_TOKEN="your_admin_token" +curl -sS \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --request PUT \ + --data @guacamole.hcl \ + http://127.0.0.1:8200/v1/sys/policies/acl/guacamole +``` + +:::{note} +- The variable `VAULT_TOKEN`, here and below, contains a valid Vault token having **administrative access**. +- Replace `127.0.0.1:8200` with the address of your Vault server. +- The mount paths in the policy (e.g., `kv/guacamole/*`) are **examples** and should be adapted to match your actual paths. +::: + +## Configuring Secret Engines for Guacamole + +Guacamole supports the following Vault secret engines: + +- **[Key-Value (KV)](https://openbao.org/docs/secrets/kv/kv-v2)**: For static secrets like passwords, certificates, and unmanaged account credentials. +- **[SSH](https://openbao.org/docs/secrets/ssh)**: For signed SSH certificates or one-time passwords (OTP). +- **[LDAP](https://openbao.org/docs/secrets/ldap)**: For dynamic or static LDAP credentials. +- **[Database](https://openbao.org/docs/secrets/databases)**: For dynamic database credentials. + +Below are detailed instructions for configuring each engine. + + +### Creating a Key-Value Store for Guacamole + +The **Key-Value (KV) secret engine** is the simplest way to store and retrieve +secrets in Vault. It can store arbitrary key-value pairs, such as usernames, +passwords, or certificates. The KV engine comes in two versions: + +- **KV v1**: Stores only the latest values. +- **KV v2**: Stores metadata (e.g., version history, last modified time) alongside the values. + +:::{warning} +The KV secret engine **does not support secret rotation**. If you use it to store passwords, +you are **entirely responsible** for adhering to corporate password policies (e.g., manual rotation). +::: + +#### Enabling the KV v1 Secret Engine + +To enable the **KV v1** secret engine on a mount path named `kv1`, use the +following command: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"type": "kv", "options": {"version": "1"}}' \ + http://127.0.0.1:8200/v1/sys/mounts/kv1 +``` + +#### Enabling the KV v2 Secret Engine + +To enable the **KV v2** secret engine on a mount path named `kv2`, use the +following command: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"type": "kv", "options": {"version": "2"}}' \ + http://127.0.0.1:8200/v1/sys/mounts/kv2 +``` + +#### Adding a Secret to KV v1 + +To add a secret (e.g., username and password for a connection named `batman`) +to the **KV v1** engine: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"username": "bruce", "password": "wayne"}' \ + http://127.0.0.1:8200/v1/kv1/guacamole/batman +``` + +Here: + +- The **path** of the secret is `guacamole/batman`. +- The stored secrets are `username` and `password`. You can use any keys you like. + +#### Adding a Secret to KV v2 + +To add the same secret to the **KV v2** engine, use the following command +(note the `/data/` prefix): + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"data": {"username": "bruce", "password": "wayne"}}' \ + http://127.0.0.1:8200/v1/kv2/data/guacamole/batman +``` + +:::{note} +Although the KV v2 requires the `/data/` prefix in API paths (e.g. +`kv2/data/guacamole/batman`). Guacamole handles this internally, so you **do +not** need to include `/data/` in your token paths. +::: + +:::{tip} +Use **complex paths** (e.g., `guacamole/production/batman`) to organize secrets +by environment, team, or application. +::: + +### Creating an SSH Secret Engine for Guacamole + +The **SSH secret engine** allows Vault to generate: + +1. **Signed SSH certificates** (recommended). +2. **One-time passwords (OTP)**. + +Guacamole supports both methods, but **signed certificates** are the more modern and secure approach. + +#### Enabling the SSH Secret Engine + +To enable the SSH secret engine on a mount path named `ssh`: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"type": "ssh"}' \ + http://127.0.0.1:8200/v1/sys/mounts/ssh +``` + +#### Generating a CA for Certificate Signing + +For **signed SSH certificates**, Vault needs a **Certificate Authority (CA)**. +You can either: + +- Upload an existing CA certificate and private key. +- Let Vault generate a new CA. + +To generate a new CA in Vault: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"generate_signing_key": true}' \ + http://127.0.0.1:8200/v1/ssh/config/ca +``` + +The response will include a `public_key`. **Save this key** to +`/etc/ssh/trusted_user_ca.pem` on all target machines where Guacamole will +connect via SSH. + +#### Configuring SSH to Trust the CA + +1. On each target machine, add the following line to `/etc/ssh/sshd_config`: +``` + TrustedUserCAKeys /etc/ssh/trusted_user_ca.pem +``` +2. Reload the SSH daemon to apply the changes: +```bash + pkill -SIGHUP /usr/sbin/sshd +``` + +:::{note} +This sends a `SIGHUP` signal to the SSH daemon, forcing it to reload its +configuration **without interrupting existing connections**. +::: + +#### Creating a Signing Role for Guacamole + +Guacamole requires the `permit-pty` option for SSH sessions. To create a +signing role named `guacamole_cert`: + +```bash +curl -sS \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{ + "algorithm_signer": "rsa-sha2-256", + "allow_user_certificates": true, + "allowed_users": "*", + "key_type": "ca", + "max_ttl": "30m", + "allowed_extensions": "permit-pty" + }' \ + http://127.0.0.1:8200/v1/ssh/roles/guacamole_cert +``` + +:::{note} +- `max_ttl`: Limits the lifetime of signed certificates to **30 minutes**. Adjust this value based on: + - Network latency between Guacamole and the target machines. + - Clock drift between the Vault server and SSH clients. +- `permit-pty`: Required for Guacamole to allocate a pseudo-terminal (PTY) for SSH sessions. +::: + +#### Creating an OTP Role for Guacamole + +For **one-time passwords (OTP)**, create a role named `guacamole_otp` for a +default user (e.g., `testuser`): + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"key_type": "otp", "default_user": "testuser"}' \ + http://127.0.0.1:8200/v1/ssh/roles/guacamole_otp +``` + +:::{warning} +- Each OTP role is **tied to a single username**. If you need OTPs for multiple + users, create **separate roles** for each user. +- Vault’s OTP engine **does not support** specifying the client IP address. + Since Guacamole’s SSH connections originate from the `guacd` daemon (and + Guacamole’s Vault extension cannot access the IP of `guacd`), you must + disable IP restrictions for the OTP role, by not specifying a `cidr_list`. +::: + +To allow the `guacamole_otp` role to be used **without IP restrictions**, run: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"roles": "guacamole"}' \ + http://127.0.0.1:8200/v1/ssh/config/zeroaddress +``` + +#### Configuring Target Machines for OTP + +To use **OTP authentication**, the target machines must be configured to +validate OTPs via **PAM (Pluggable Authentication Modules)**. + +1. Modify `/etc/pam.d/sshd` to include the following line at the **top** of the file: +``` + auth sufficient pam_exec.so expose_authtok /usr/local/bin/verify_otp.sh +``` +:::{note} +This line tells PAM to use the `verify_otp.sh` script to validate OTPs. The +`expose_authtok` option passes the OTP to the script. This is only an example +and should be adapted to your specific PAM installation. +::: + +2. **Create the OTP Verification Script** (`/usr/local/bin/verify_otp.sh`): +```bash + #!/bin/sh + set -u + IFS= read -r OTP + USERNAME="${PAM_USER}" + [ -z "${OTP}" ] && exit 1 + [ -z "${USERNAME}" ] && exit 2 + RESPONSE=$(curl -s \ + --fail \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"otp\": \"${OTP}\"}" \ + http://127.0.0.1:8200/v1/ssh/verify) + [ "$?" -eq 0 ] || exit 3 + [ "$(echo $RESPONSE | jq -r .data.username)" = "$USERNAME" ] || exit 4 +``` + +3. **Make the Script Executable**: +```bash + chmod 755 /usr/local/bin/verify_otp.sh +``` + +:::{warning} +Using a **shell script for authentication is not a best practice** for +production environments. Instead, use the **[Hashicorp Vault SSH Helper](https://github.com/hashicorp/vault-ssh-helper)**, +which is designed for this purpose and integrates directly with OpenSSH. +::: + +### Creating an LDAP Secret Engine for Guacamole + +The **LDAP secret engine** allows Vault to manage credentials for LDAP +directories, such as **OpenLDAP** or **Microsoft Active Directory**. +Guacamole can use these credentials for connections that authenticate against +LDAP. + +:::{note} +Vault **rotates its own LDAP password** automatically. For this reason, it is +**highly recommended** to use a **dedicated LDAP account** for Vault (e.g. +`uid=vault,ou=users,dc=example,dc=com`). +::: + +#### Prerequisites for LDAP + +1. **Generate a Password Hash**: +For modern LDAP servers (e.g., OpenLDAP with Argon2 support), generate a password hash like this: + +```bash +slappasswd -o module-load=argon2.so -h '{ARGON2}' -s your_secret_password_here +``` + +which returns a password hash like + +``` +{ARGON2}$argon2id$v=19$m=7168,t=5,p=1$4JbzrJjMiH8x6Qfe5WsNtA$n5tzU8dGW2lFE+KDJRCLImKK+feE+mZaBmDP7nE9fyI +``` + +Replace `your_secret_password_here` with the actual password for the Vault LDAP account. + +2. **Create a Dedicated LDAP Account for Vault (file: adduser.ldif)**: + - This account should have permissions to: + - **Read** user attributes (for static roles). + - **Modify passwords** (for dynamic roles or static roles with rotation). + - Example LDIF for creating a Vault LDAP account (using the above Argon2 password hash): +```ldif +dn: uid=vault,ou=users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +uid: vault +cn: Vault User +sn: Vault +displayName: Vault +description: Account used by Vault Server +userPassword: {ARGON2}$argon2id$v=19$m=7168,t=5,p=1$4JbzrJjMiH8x6Qfe5WsNtA$n5tzU8dGW2lFE+KDJRCLImKK+feE+mZaBmDP7nE9fyI +``` + +3. **Add the LDAP Account to the Directory**: +```bash +ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f adduser.ldif +``` +4. **Grant Vault Permissions to Modify Passwords**: +Modify the LDAP ACLs to allow the Vault account to read and modify the +`userPassword` attribute. Example LDIF for OpenLDAP: + +```ldif +dn: olcDatabase={1}mdb,cn=config +changetype: modify +add: olcAccess +olcAccess: {0}to attrs=userPassword + by dn.exact="uid=vault,ou=users,dc=example,dc=com" write + by self write + by anonymous auth + by dn.base="cn=admin,dc=example,dc=com" manage + by * none +``` + +:::{warning} +Extreme care must be taken when modifying the permissions to access the LDAP. +The risk of removing all administrative access for the LDAP server exists. +Please respect any existing olcAccess attributes and double check any changes +you make. +::: + +```bash +ldapmodify x -D "cn=admin,cn=config" -W -f userpassword.ldif +``` +:::{warning} +The above rule grants the Vault server **write access to all passwords** in +the `ou=users,dc=example,dc=com` subtree. **This is not secure for production!** +**Best Practice**: Isolate Vault-managed accounts in a **dedicated DN** +(e.g., `ou=vault,dc=example,dc=com`) and restrict write permissions to this DN +only. Example: + +```ldif +dn: olcDatabase={2}mdb,cn=config +changetype: modify +add: olcAccess +olcAccess: {0}to dn.subtree="ou=vault,dc=example,dc=com" attrs=userPassword + by dn.exact="uid=vault,ou=users,dc=example,dc=com" manage + by anonymous auth + by dn.base="cn=admin,dc=example,dc=com" manage + by * none +``` + +This rule **must appear before** any generic `userPassword` rules to take effect. +::: + +5. **For Dynamic LDAP Roles**: +If you plan to use **dynamic LDAP roles**, Vault needs permissions to **create +and delete accounts** in the LDAP directory. Example LDIF: + +```ldif +dn: olcDatabase={1}mdb,cn=config +changetype: modify +add: olcAccess +olcAccess: {3}to dn.subtree="dc=example,dc=com" + by dn.exact="uid=vault,ou=users,dc=example,dc=com" write +``` + +#### Configuring the LDAP Secret Engine in Vault + +1. **Create a Password Policy for LDAP**: +Vault can generate random passwords for LDAP accounts based on a **password +policy**. Example policy (`ldap-policy.hcl`): + + +```bash +length = 20 + +rule "charset" { + charset = "abcdefghijklmnopqrstuvwxyz" + min_chars = 1 +} + +rule "charset" { + charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + min_chars = 1 +} + +rule "charset" { + charset = "0123456789" + min_chars = 1 +} + +rule "charset" { + charset = "!@#%^&*" + min_chars = 1 +} +``` + +2. **Upload the policy to Vault:** + +```bash +curl -s --header "X-Vault-Token: $VAULT_TOKEN" \ + --request POST --data-urlencode "policy@ldap-policy.hcl" \ + http://127.0.0.1:8200/v1/sys/policies/password/ldap-policy +``` + +3. **Enable the LDAP secrets engine**: + +To enable the database secret engine on a mount path named `db`: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"type": "ldap"}' \ + http://127.0.0.1:8200/v1/sys/mounts/ldap +``` + +4. **Configure the LDAP Connection in Vault**: + +```bash + curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request PUT \ + --data '{ + "schema": "openldap", + "url": "ldaps://ldap.example.com", + "binddn": "uid=vault,ou=users,dc=example,dc=com", + "bindpass": "your_secret_password_here", + "userdn": "ou=users,dc=example,dc=com", + "userattr": "uid", + "password_policy": "ldap-policy", + "insecure_tls": false + }' \ + http://127.0.0.1:8200/v1/ldap/config +``` +:::{warning} +The `insecure_tls: false` option is **not secure** for production. Instead: +- Ensure your LDAP server uses **TLS with a valid certificate**. +- Specify the CA certificate using the `tls_ca_cert` or `tls_ca_cert_file` options. +::: + +5. **Test the LDAP Connection**: +Before proceeding, verify that Vault can communicate with the LDAP server by +forcing a password rotation: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --request POST \ + http://127.0.0.1:8200/v1/ldap/rotate-root +``` + +If the command succeeds, Vault has successfully rotated its LDAP password and +can communicate with the LDAP server. + +#### Creating a Static LDAP Role for Guacamole + +Static roles use **existing LDAP accounts** and rotate their passwords +automatically. The syntax is different here depending on whether you are +using OpenBao or Hashicorp Vault. + +**For HashiCorp Vault**: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{ + "role_name": "guacamole", + "username": "guacamole", + "dn": "cn=guacamole,ou=users,dc=example,dc=com", + "rotation_period": "1h" + }' \ + http://127.0.0.1:8200/v1/ldap/static-role +``` + +**For OpenBao**: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{ + "username": "guacamole", + "dn": "cn=guacamole,ou=users,dc=example,dc=com", + "rotation_period": "1h" + }' \ + http://127.0.0.1:8200/v1/ldap/static-role/guacamole +``` + +:::{note} +- Guacamole **does not** manage LDAP lease revocation. Rotated credentials + remain valid until manually revoked or rotated again by Vault. +- The `rotation_period` specifies how often Vault rotates the password for + the LDAP account (e.g., every 1 hour). +::: + +#### Creating a Dynamic LDAP Role for Guacamole + +To enable **dynamic LDAP account creation** in Vault for Guacamole, you need +to configure Vault to **automatically create, delete, and roll back LDAP +accounts** on demand. This is useful for scenarios where you want Vault to +**temporarily provision LDAP accounts** for Guacamole connections and +**automatically clean them up** after use. + +Dynamic roles require **three LDIF files** to define how Vault interacts with +your LDAP directory: + +- **creation.ldif**: Defines how new accounts are created. +- **deletion.ldif**: Defines how accounts are deleted. +- **rollback.ldif**: Defines how to revert changes if account creation fails (often identical to `deletion.ldif`). + +:::{note} +The exact content of these files **depends on your LDAP schema and directory +structure**. The examples below are for **Unix-like accounts** (e.g. +`inetOrgPerson`, `posixAccount`) and will need to be adapted to match your +environment. +::: + +1. **Create the `creation.ldif` File** + +The `creation.ldif` file defines the **attributes of new LDAP accounts** +created by Vault. Below is an example for creating **Unix user accounts** in an +OpenLDAP directory: + +```ldif +{% raw %} +dn: uid={{.Username}},ou=users,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: {{.Username}} +cn: {{.Username}} +sn: {{.Username}} +gidNumber: 50 +homeDirectory: /home/{{.Username}} +loginShell: /bin/sh +userPassword: {{.Password}} +{% endraw %} +``` + +:::{note} +- `{% raw %}{{.Username}}{% endraw %}` and `{% raw %}{{.Password}}{% endraw %}` are **placeholders** that Vault + replaces with dynamically generated values. +- The `uidNumber` attribute is **intentionally omitted** in this example. + Vault **does not** automatically increment `uidNumber` values. If you need unique + `uidNumber` values, consider: + - Using the `autonumber` overlay in OpenLDAP to auto-assign `uidNumber`. + - Pre-defining a `uidNumber` value knowing all dynamic accounts will reuse it. +::: + +:::{warning} +- The `userPassword` field is provided in **plaintext** in this LDIF file. To + ensure the password is **hashed before storage**, configure your LDAP server with: + +```ldif +olcPPolicyHashCleartext: TRUE +``` + + This ensures the LDAP server **hashes the password** before storing it in the + directory. +::: + +2. **Create the `deletion.ldif` and `rollback.ldif` Files** + +The `deletion.ldif` and `rollback.ldif` files define how Vault **removes +accounts** from the LDAP directory. In most cases, these files are +**identical**, as rolling back a failed creation typically involves deleting +the account. + +Example `deletion.ldif` (and `rollback.ldif`): + +```ldif +{% raw %} +dn: uid={{.Username}},ou=users,dc=example,dc=com +changetype: delete +{% endraw %} +``` + +:::{note} +- The `changetype: delete` directive tells LDAP to **remove the entire + entry** matching the `dn`. +- The `{% raw %}{{.Username}}{% endraw %}` placeholder is replaced with the username of the + account to delete. +::: + +3. **Configure LDAP Permissions for Dynamic Roles** + +For Vault to **create and delete accounts**, it needs **sufficient +permissions** in your LDAP directory. Specifically: + +- **Write access** to the **DN subtree** where accounts will be created (e.g. + `ou=users,dc=example,dc=com`). +- **Write access** for the `userPassword` attribute + +To grant Vault **full write access** to a subtree (e.g., `ou=dynamic,dc=example,dc=com`), use the following LDIF: + +```ldif +dn: olcDatabase={1}mdb,cn=config +changetype: modify +add: olcAccess +olcAccess: {2}to dn.subtree="ou=dynamic,dc=example,dc=com" + by dn.exact="uid=vault,ou=users,dc=example,dc=com" write + by * none +``` + +:::{warning} +Granting **write access** to a broad subtree (e.g., `ou=users,dc=example,dc=com`) +can be a **security risk**. **Best practice** is to: + +- **Isolate dynamic accounts** in a dedicated subtree (e.g., `ou=dynamic,dc=example,dc=com`). +- **Restrict Vault’s permissions** to only this subtree. +::: + +4. **Create the Dynamic Role in Vault** + +Once the LDIF files are ready, create a **dynamic role** in Vault using the +command below. This role defines how Vault generates and manages dynamic +LDAP accounts for Guacamole. + +```bash +{% raw %} +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{ + "creation_ldif": "dn: uid={{.Username}},ou=users,dc=example,dc=com\nobjectClass: inetOrgPerson\nobjectClass: posixAccount\nobjectClass: shadowAccount\nuid: {{.Username}}\ncn: {{.Username}}\nsn: {{.Username}}\ngidNumber: 50\nhomeDirectory: /home/{{.Username}}\nloginShell: /bin/sh\nuserPassword: {{.Password}}", + "deletion_ldif": "dn: uid={{.Username}},ou=users,dc=example,dc=com\nchangetype: delete", + "rollback_ldif": "dn: uid={{.Username}},ou=users,dc=example,dc=com\nchangetype: delete", + "username_template": "v_{{.RoleName}}_{{random 6}}", + "dn": "uid=vault,ou=users,dc=example,dc=com", + "default_ttl": "1h", + "max_ttl": "24h" + }' \ + http://127.0.0.1:8200/v1/ldap/roles/guacamole +{% endraw %} +``` + +5. **Test the Dynamic Role** + +After creating the role, test it by **requesting a dynamic LDAP credential** +from Vault: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + http://127.0.0.1:8200/v1/ldap/creds/guacamole +``` + +The response will include: + +- A **dynamically generated username** (e.g., `v_guacamole_abc123`). +- A **password** for the account. +- The **TTL** (time-to-live) of the credential. + +:::{note} +- The account will be **automatically deleted** from LDAP after the `default_ttl` + (1 hour in the example). +- You can **manually revoke** the credential by deleting its lease in Vault. +::: + +#### Creating a LDAP Service Accounts for Guacamole + +Before continuing you should confirm that the Vault LDAP configuration +includes valid values for `userdn` and `userattr`, with a command like: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + http://127.0.0.1:8200/v1/ldap/config | \ + jq '.data.userdn, .data.userattr' +``` +This should produce something like + +``` +"ou=users,dc=example,dc=com" +"uid" +``` +This means that user records in your LDAP will be of the form +`uid=testuser,ou=users,dc=example,dc=com`. If this is not the case +the LDAP secret engine should be reconfigured before continuing + +Given the returned value of `userattr`, Vault will search with the account +`uid` values. To create a LDAP service account in the Vault we can then +do something like + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{ + "ttl": "2h", + "max_ttl": "24h", + "disable_checkin_enforcement": "true", + "service_account_names": "user1,user2" + }' \ + http://127.0.0.1:8200/v1/ldap/library/guacamole +``` + +where `user1` and `user2` and valid existing user accounts managed by the +Vault. + +:::{note} +- Vault will change the password of these accounts on check-out and so these + account should be dedicated to Vault. +- Guacamole requests a TTL of 2 hours. If the role is configured with a shorter + TTL, the account will be assumed to be checked in after this shorter delay. +::: + +:::{warning} +At the moment Guacamole does not guarantee that the account will be checked in +when the connection is terminated, though it does attempt to do so. The pool of +service accounts available should take this into account and assume the check-in +will happen at the end of the 2 hours TTL. +::: + +### Creating a Database Secret Engine for Guacamole + +The **Database secret engine** allows Vault to generate **dynamic database +credentials** for Guacamole. This is useful for connections that require +database access (e.g., PostgreSQL, MySQL). + +#### Prerequisites for Database Engine + +1. **Create a Dedicated Database Role for Vault**: + - This role should have permissions to: + - **Create new roles** (for dynamic credentials). + - **Modify passwords** (for rotation). + - **Revoke roles** (to remove dynamic credentials) + - **Grant permission on tables** (to dynamic credentials to alter the tables) + +This poses a problem, of ownership on the tables in the database. Using +Postgresql you could use + +```sql +CREATE ROLE vault_admin WITH LOGIN PASSWORD 'secure_password_here' CREATEROLE NOINHERIT; +GRANT CREATE ON DATABASE guacamole_db TO vault_admin; +GRANT USAGE ON SCHEMA public TO vault_admin; +``` + +if and only if vault_admin is owner of the tables in the database. Otherwise the +role needs to be a `superuser` and created like + +```sql +CREATE ROLE vault_admin WITH LOGIN PASSWORD 'secure_password_here' SUPERUSER NOINHERIT; +``` + +2. **Apply the SQL Commands**: + Save the above SQL to a file (e.g., `create_vault_role.sql`) and run it: + +```bash +cat create_vault_role.sql | psql --dbname "guacamole_db" +``` + +#### Enabling the Database Secret Engine + +To enable the database secret engine on a mount path named `db`: + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"type": "database"}' \ + http://127.0.0.1:8200/v1/sys/mounts/db +``` + +#### Creating a Dynamic Database Role for Guacamole + +To create a role that generates **dynamic credentials** for Guacamole: + +```bash +{% raw %} +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{ + "db_name": "guacamole_db", + "default_ttl": "1h", + "max_ttl": "24h", + "creation_statements": [ + "CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '\''{{password}}'\'' VALID UNTIL '\''{{expiration}}'\'';", + "GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";", + "GRANT SELECT,USAGE ON ALL SEQUENCES IN SCHEMA public TO \"{{name}}\";", + "GRANT ALL ON SCHEMA public TO \"{{name}}\";" + ], + "renew_statements": [ + "ALTER ROLE \"{{name}}\" WITH PASSWORD '\''{{password}}'\'' VALID UNTIL '\''{{expiration}}'\'';" + ], + "revocation_statements": [ + "REASSIGN OWNED BY \"{{name}}\" TO CURRENT_USER;", + "DROP OWNED BY \"{{name}}\";", + "DROP ROLE IF EXISTS \"{{name}}\";" + ] + }' \ + http://127.0.0.1:8200/v1/db/roles/guacamole +{% endraw %} +``` + +:::{note} +- `default_ttl`: Default time-to-live for generated credentials (e.g., 1 hour). +- `max_ttl`: Maximum time-to-live for generated credentials (e.g., 24 hours). +- `create_statements`: SQL commands to create a new database role with the + generated credentials. +- `rotation_statements`: SQL commands to rotate the password for an existing + role. +- `{% raw %}{{name}}{% endraw %}` and `{% raw %}{{password}}{% endraw %}`: + Placeholders for the dynamically generated username and password. +::: + +#### Creating a Static Database Role for Guacamole + +If you prefer to use a **static username** (e.g., `guacamole_user`) instead of +dynamic usernames, modify the role as follows: + +```bash +{% raw %} +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{ + "db_name": "guacamole_db", + "default_ttl": "1h", + "max_ttl": "24h", + "creation_statements": [ + "CREATE ROLE \"guacamole_user\" WITH LOGIN PASSWORD '\''{{password}}'\'' VALID UNTIL '\''{{expiration}}'\'';", + "GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA public TO \"guacamole_user\";", + "GRANT SELECT,USAGE ON ALL SEQUENCES IN SCHEMA public TO \"guacamole_user\";", + "GRANT ALL ON SCHEMA public TO \"guacamole_user\";" + ], + "renew_statements": [ + "ALTER ROLE \"guacamole_user\" WITH PASSWORD '\''{{password}}'\'';" + ], + "static_username": true + }' \ + http://127.0.0.1:8200/v1/db/roles/guacamole +{% endraw %} +``` + +#### Configuring the Database Connection + +Configure Vault to connect to your database (e.g., PostgreSQL): + +```bash +{% raw %} +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{ + "plugin_name": "postgresql-database-plugin", + "connection_url": "postgresql://{{username}}:{{password}}@127.0.0.1:5432/guacamole_db", + "allowed_roles": "guacamole", + "username": "vault_admin", + "password": "secure_password_here", + "password_authentication": "scram-sha-256" + }' \ + http://127.0.0.1:8200/v1/db/config/guacamole_db +{% endraw %} +``` + +:::{note} +- `connection_url`: The URL template for connecting to the database. + `{% raw %}{{username}}{% endraw %}` and `{% raw %}{{password}}{% endraw %}` + are placeholders that Vault replaces with dynamic credentials. +- `allowed_roles`: Comma-separated list of roles that can use this connection. +- `username` and `password`: Credentials for the Vault database account (e.g. + `vault_admin`). +::: + +## Authenticating Guacamole to Vault + +Guacamole supports the following authentication methods to Vault: + +| Method | Description | +| --------------------- | ------------------------------------------------------------------------------------------ | +| **Token String** | Static or renewable token provided directly in Guacamole’s configuration. | +| **Token Sink File** | Vault Agent writes a token to a file; Guacamole reads it. Supports complex roles. | +| **Username/Password** | Uses Vault’s `userpass` auth method. Tokens are short-lived and auto-renewed by Guacamole. | + +### Authentication Using a Token String + +Guacamole can authenticate to Vault using a **static token**. This token must: + +- Use a **role with the `guacamole` policy** (as defined earlier). +- **Not expire** (set `ttl: 0`), or Guacamole may lose access after network issues or reboots. + +If expiring tokens are used, Guacamole will renew the token on expiration. +However, in case of network problems this renewal might not succeed in time. +Also, there is a hard limit on the maximum TTL of renewable tokens, that is by +default configured as 32 days in the Vault. After this period the token will fail +to be renewed + +#### Creating a Non-Expiring Token for Guacamole + +```bash +curl -sS \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --request POST \ + --data '{ + "policies": ["guacamole"], + "ttl": "0", + "renewable": false, + "display_name": "service-guacamole", + "no_parent": true + }' \ + http://127.0.0.1:8200/v1/auth/token/create +``` + +:::{note} +- The `ttl: 0` ensures the token **never expires**. +- The `no_parent: true` option prevents the token from being revoked if its + parent token is revoked. +- The token value is returned in the `.auth.client_token` field of the + response. Use this value in Guacamole’s configuration (e.g., `vault-token` + in `guacamole.properties`). +::: + +### Authentication Using a Token Sink File + +Guacamole **does not** implement complex authentication methods like +**AppRole** directly. However, you can use a **[Vault Agent](https://openbao.org/docs/agent-and-proxy/agent)** +to manage authentication (e.g., AppRole) and write a **standard Vault token** +to a **sink file**. Guacamole can then read this file at startup or when the +token is about to expire. + +#### How It Works + +1. The `vault_token` property of **Guacamole** is configured with a location of a file. +2. **Vault Agent** authenticates to Vault (e.g., using AppRole) and writes a + token to this file (e.g., `/etc/guacamole/vault-token`). +3. **Guacamole** reads this file at startup and whenever the token is about to expire. +4. **Vault Agent** automatically renews the token as needed. + +:::{note} +The configuration of Vault Agent is **beyond the scope of this document**. Refer to: + +- [OpenBao Vault Agent Documentation](https://openbao.org/docs/agent-and-proxy/agent) +- [HashiCorp Vault Agent Documentation](https://developer.hashicorp.com/vault/docs/agent-and-proxy/agent) +::: + +This is the best solution for production, but it requires Vault Agent. + +### Authentication Using Username and Password + +If you prefer to authenticate Guacamole to Vault using a **username and +password**, you must first enable the `userpass` authentication method in +Vault. + +#### Enabling the `userpass` Auth Method + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"type": "userpass"}' \ + http://127.0.0.1:8200/v1/sys/auth/userpass +``` + +#### Creating a Guacamole User in Vault + +```bash +curl -s \ + --header "X-Vault-Token: $VAULT_TOKEN" \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{ + "password": "strong-password-here", + "policies": ["guacamole"], + "token_ttl": "10m", + "token_max_ttl": "60m" + }' \ + http://127.0.0.1:8200/v1/auth/userpass/users/guacamole +``` + +:::{note} +- The `token_ttl` (10 minutes) is the default lifetime of tokens generated + for this user. +- Guacamole will **automatically renew** the token up to the `token_max_ttl` + (60 minutes). After this, Guacamole will reauthenticate and generate a new + token. +- Vault **cannot** rotate the password for `userpass` users. If you change the + password in Vault, you must also update it in Guacamole’s configuration. +::: + +Vault **cannot** rotate `userpass` passwords, as the user must know the value. +Therefore, the user is responsible for the rotation of this password. + +(guac-vault-config)= + +Required configuration +---------------------- + +{% call ext.config('openbao', required=True) %} + +Strictly speaking, Guacamole requires only `vault-uri`, the address of the Vault +server. The property `vault-token` can be replaced by `vault-username` and +`vault-password`. +{% endcall %} + + +### Additional Configuration Options + +{% call ext.config('openbao-optional') %} +The following additional, optional {{ ext.properties() }} may be set as desired +to tailor the behavior of the Vault support: +{% endcall %} + + +(completing-vault-install)= + +Completing installation +----------------------- + +```{include} include/ext-completing.md +``` + +Retrieving connection secrets from a vault +------------------------------------------ + +Secrets for connection parameters provided by **[parameter tokens](parameter-tokens)** +by Vault all take the form {samp}`$\{vault://\{MOUNT_PATH\}/\{PATH\}/\{SECRET\}\}`, where + +- `{MOUNT_PATH}`: The mount path of the Vault secret engine used (e.g., `kv2`, `ssh`, `ldap`). +- `{PATH}`: The path to the secret record. +- `{SECRET}`: The key within the secret record (e.g., `username`, `password`). + +Guacamole generally uses the values for `{MOUNT_PATH}` and `{PATH}` as used by the +Vault itself. For example if the OpenBao command used to recover a secret record is + +```bash +bao read kv2/guacamole/myaccount +``` + +the **parameter token** in Guacamole will start with `vault://kv2/guacamole/myaccount`. +The exception to this rule is for LDAP service accounts, where OpenBao would check +out the account with the command + +```bash +bao write -f ldap/library/guacamole/myteam/check-out +``` + +the corresponding Guacamole token will start with `vault://ldap/library/guacamole/myteam`. + +The secret record recovered from the Vault is JSON with both data +and metadata. Guacamole requires that `{SECRET}` corresponds to one of the +parameters in a data block. For example the command + +```bash +curl --header "X-Vault-Token: $VAULT_TOKEN" http://127.0.0.1:8200/v1/kv2/secrets/my-secret | jq +``` +will return the JSON + +```json +{ + "data": { + "data": { + "foo": "bar" + }, + "metadata": { + "created_time": "2018-03-22T02:24:06.945319214Z", + "custom_metadata": { + "owner": "jdoe", + "mission_critical": "false" + }, + "deletion_time": "", + "destroyed": false, + "version": 2 + } + } +} +``` + +and the only possible value of `{SECRET}` for this secret record is `foo`. +So for this case the only possible **parameter token** is +{samp}`$\{vault://kv2/secrets/my-secret/foo\}`. + +### Mount Path Detection + +Guacamole **automatically detects** the mount path and type of secret engine +used from the token the token itself, using the path-help functionality of the +Vault. + +Guacamole make no attempt to do the detction itself but relies and the values +that Vault itself returns. + +### Secret Engines and Paths Supported + +The following Vault secret engines are supported by Guacamole: + +#### LDAP Secret Engine + +The Vault LDAP secret engine supports both **open-source LDAP servers** (e.g., OpenLDAP) and **Microsoft Active Directory**. There are three types of LDAP accounts in Vault: + +1. **Static roles**: Use a **fixed username** (e.g., `guacamole`). Vault rotates the password automatically. +2. **Dynamic roles**: Vault **creates and deletes** an account for each request. +3. **Service roles**: Vault manages a **pool of accounts** that can be reused. + +Guacamole simplifies the token format for LDAP paths as follows: + +``` +${VAULT:{MOUNT_PATH}/{static-cred|creds|library}/{ROLE}/{username|password}} +``` + +An example of a static credential for Vault are of the form + +``` +${vault://{MOUNT_PATH}/static-cred/{ROLE}/{username/password}} +``` + +For dynamic credentials Vault use the form + +``` +${vault://{MOUNT_PATH}/creds/{ROLE}/{username/password}} +``` + +For LDAP service accounts, Guacamole simplifies the tokens for the LDAP paths +in the following manner + +``` +${vault://{MOUNT_PATH}/library/{ROLE}/{username/password}} +``` + +An example of a valid token might then be `${vault://ldap/creds/guacamole/username}`, +where the value of `ROLE` here is `guacamole`. + +Please note that Guacamole does not touch the lease times associated with these +accounts in the vault. + +:::{warning} +Guacamole attempts to check-in the service accounts when the tunnel is closed. +However, a maximum lease time of the service accounts of 2 hours is imposed by +Guacamole and after this time the Vault will automatically assume the account +is again available. +::: + +#### SSH Secret Engine + +The Vault SSH secret engine supports: + +1. **One-time passwords (OTP)**. +2. **Signed certificates**. + +Guacamole supports both methods, but **signed certificates are recommended** +for security and ease of use. + +##### Tokens for One-Time Passwords (OTP) + +Tokens for OTP take the form: + +``` +${vault://{MOUNT_PATH}/creds/{ROLE}/{username|password}} +``` + +Example: + +``` +${vault://ssh/otp/myaccount/username} +``` + +Here, `{ROLE}` is `myaccount`. + +:::{note} +OTP authentication requires **client-side configuration** (e.g., PAM and the +`verify_otp.sh` script, as described earlier). +::: + +##### Tokens for Signed Certificates + +Tokens for signed certificates take the form: + +``` +${vault://{MOUNT_PATH}/sign/{ROLE}/{public/private}} +``` + +Example: + +``` +${vault://ssh/sign/guacamole_cert/public} +``` + +This token returns a **signed SSH public certificate** that Guacamole can +use for authentication. + +:::{note} +The SSH secret engine **does not support** generating certificates directly. +Instead, it **signs** certificates using a CA stored in Vault. The creation +of the temporary certificates is performed by Guacamole itself. + +The username of the connection is a necessary field for the generated +certificate. It can be an explicit value or another token, that will be +resolved before inclusion in the ssh certificate. +::: + +#### Database Secret Engine + +The Vault database secret engine generates **dynamic database credentials** for +Guacamole. Tokens for database credentials take the form: + +``` +${vault://{MOUNT_PATH}/creds/{ROLE}/{username|password}} +``` + +Example: + +``` +${vault://db/creds/guacamole/username} +``` + +Here, `{ROLE}` is the name of the database role (e.g., `guacamole`). So that +Guacamole can use these values, they must be placed in the +`guacamole.properties.vlt` file. + +:::{note} +- The credentials are **rotated automatically** by Vault based on the + `default_ttl` and `max_ttl` settings of the role. +::: + +#### Key-Value (KV) Secret Engine + +The KV secret engine stores **static secrets** (e.g., passwords, certificates). +Tokens for KV secrets take the form: + +``` +${vault://{MOUNT_PATH}/{PATH}/{SECRET}} +``` + +Example: + +``` +${vault://kv2/guacamole/batman/password} +``` + +Here: + +- `{MOUNT_PATH}` is the mount path of the KV engine (e.g., `kv2`). +- `{PATH}` is the path to the secret record (e.g., `guacamole/batman`). +- `{SECRET}` is the key within the secret record (e.g., `password`). + +:::{note} +For **KV v2**, Guacamole automatically handles the `/data/` prefix in the API +path. You **do not** need to include `/data/` in your token paths. +::: + +### Sub-token replacement + +In addition, the following sub-tokens can be used with the **parameter token** +to modify its value when it is referenced. These sub-tokens are of the form +`{SUB_TOKEN}` within the **parameter token** itself. If `{SUB_TOKEN}` is valid +literal with the **parameter token**, you can use `$${SUB_TOKEN}` to ensure it +is interpreted as `{SUB_TOKEN}`. The allowed sub-token values are: + +`{GUAC_USERNAME}` +: The value of the token `GUAC_USERNAME`, the current logged in user, is + substituted into the vault token before it is looked up. + +`{USERNAME}` +: The username field of the connection is substituted into the vault token + before it is looked up. The username of the connection must not be empty + or itself include a token. + +`{HOSTNAME}` +: The hostname field of the connection is substituted into the vault token + before it is looked up. The hostname of the connection must not be empty + or itself include a token. + +`{GATEWAY}` +: Identical to `HOSTNAME`, except that the value of the `gateway-hostname` + parameter is used. This is only applicable to RDP connections. The + `gateway-hostname` of the connection must not be empty or itself include a + token. + +`{GATEWAY_USER}` +: Identical to `USERNAME`, except that the value of the `gateway-username` + parameter is used. This is only applicable to RDP connections. The + `gateway-username` of the connection must not be empty or itself include a + token. + +For example {samp}`$\{vault://ldap/org/example/\{SERVER\}/\{USERNAME\}/password` is +a valid **paramater token**. + +### Manual Mapping of Secrets + +For cases where **automatic injection** is not suitable (e.g., load-balancing +connection groups), you can **manually map** secrets to connections using a +`vault-token-mapping.yml` file. + +#### Example `vault-token-mapping.yml` + +```yaml +WINDOWS_ADMIN_PASSWORD: vault://ldap/static/org/host/account/password +LINUX_SERVER_KEY: vault://kv/org/host/server_key.pem +``` + +(guacamole-properties-vlt)= + +Retrieving configuration properties from a vault +------------------------------------------------ + +Secrets for Guacamole configuration properties are provided through [an +additional file within `GUACAMOLE_HOME` called `guacamole.properties.vlt`](guacamole-properties-vlt). +This file is _identical_ to `guacamole.properties` except that the values of +properties are references to **parameter tokens** in the format as above. + +Secrets can be used for any Guacamole configuration property that isn't +required to configure the Vault support. + +For example, the following `guacamole.properties.vlt` defines both the +`postgresql-username` and `postgresql-password` properties using values from a +role in the Vault database secret engine. + +``` +postgresql-username: vault://db/guacamole_db/username +postgresql-password: vault://db/guacamole_db/password +``` + +The secret engine in this case is mounted on the path `db/` and this secret +engine defines a role `guacamole`. + diff --git a/src/vault.md b/src/vault.md new file mode 100644 index 00000000..4757f4b5 --- /dev/null +++ b/src/vault.md @@ -0,0 +1,35 @@ +Retrieving secrets from a vault +=============================== + + +:::{toctree} +:hidden: + +Keeper Secret Manager +OpenBao or Hashicorp Vault +::: + +Guacamole supports reading secrets such as connection-specific passwords from a +key vault, automatically injecting those secrets into connection configurations +using [parameter tokens](parameter-tokens) or Guacamole configuration +properties via an additional, vault-specific configuration file analogous to +`guacamole.properties`. Guacamole supports the follow Vault providers: + +[Keeper Secret Manager](keeper) +: [Keeper Secrets Manager (KSM)](https://www.keepersecurity.com/secrets-manager.html) + is a cloud-based secrets management solution that securely stores, rotates, + and injects credentials (API keys, passwords, certificates) into applications + and CI/CD pipelines with strong encryption and zero-knowledge architecture. + +[Hashicorp Vault](openbao) +: [HashiCorp Vault](https://www.hashicorp.com/products/vault) is a widely used + secrets management platform that provides secure storage, dynamic secrets + generation, encryption as a service, and fine-grained access control for + protecting sensitive data in modern infrastructure. + +[OpenBao](openbao) +: [OpenBao](https://openbao.org) is an open-source, community-driven fork of + HashiCorp Vault designed to provide similar secrets management capabilities + (secure storage, dynamic secrets, encryption) under a fully open governance + and licensing model. It is supported by the same extension as Hashicorp + Vault.