diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java index 5572ef71d..c854fbd3b 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java @@ -34,6 +34,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -45,6 +46,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.vault.ksm.conf.KsmConfigurationService; +import org.apache.guacamole.vault.secret.WindowsUsername; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,6 +66,12 @@ public class KsmClient { */ private static final Logger logger = LoggerFactory.getLogger(KsmClient.class); + /** + * Service for retrieving configuration information. + */ + @Inject + private KsmConfigurationService confService; + /** * Service for retrieving data from records. */ @@ -179,6 +188,30 @@ public class KsmClient { */ private final Set cachedAmbiguousUsernames = new HashSet<>(); + /** + * All records retrieved from Keeper Secrets Manager, where each key is the + * domain of the corresponding record. The domain of a record is + * determined by {@link Login} fields, thus a record may be associated with + * multiple domains. If a record is associated with multiple domains, there + * will be multiple references to that record within this Map. The contents + * of this Map are automatically updated if {@link #validateCache()} + * refreshes the cache. This Map must not be accessed without + * {@link #cacheLock} acquired appropriately. Before using a value from + * this Map, {@link #cachedAmbiguousDomains} must first be checked to + * verify that there is indeed only one record associated with that domain. + */ + private final Map cachedRecordsByDomain = new HashMap<>(); + + /** + * The set of all domains that are associated with multiple records, and + * thus cannot uniquely identify a record. The contents of this Set are + * automatically updated if {@link #validateCache()} refreshes the cache. + * This Set must not be accessed without {@link #cacheLock} acquired + * appropriately. This Set must be checked before using a value retrieved + * from {@link #cachedRecordsByDomain}. + */ + private final Set cachedAmbiguousDomains = new HashSet<>(); + /** * Create a new KSM client based around the provided KSM configuration. * @@ -239,9 +272,17 @@ public class KsmClient { cachedAmbiguousUsernames.clear(); cachedRecordsByUsername.clear(); - // Store all records, sorting each into host-based and login-based - // buckets - records.forEach(record -> { + // Clear cache of domain-based records + cachedAmbiguousDomains.clear(); + cachedRecordsByDomain.clear(); + + // Store all records, sorting each into host-based, login-based, + // and domain-based buckets + Iterator recordIterator = records.iterator(); + while(recordIterator.hasNext()) { + + // Go through records one at a time + KeeperRecord record = recordIterator.next(); // Store based on UID ... cachedRecordsByUid.put(record.getRecordUid(), record); @@ -250,13 +291,38 @@ public class KsmClient { String hostname = recordService.getHostname(record); addRecordForHost(record, hostname); + // ... and domain + String domain = recordService.getDomain(record); + addRecordForDomain(record, domain); + + // Fetch the username + String username = recordService.getUsername(record); + + // If domains should be split out from usernames + if (username != null && confService.getSplitWindowsUsernames()) { + + // Attempt to split the domain of the username + WindowsUsername usernameAndDomain = ( + WindowsUsername.splitWindowsUsernameFromDomain(username)); + + if (usernameAndDomain.hasDomain()) { + + // Update the username if a domain has been stripped off + username = usernameAndDomain.getUsername(); + + // Use the username-split domain if not already set explicitly + if (domain == null) + addRecordForDomain(record, usernameAndDomain.getDomain()); + } + } + // Store based on username ONLY if no hostname (will otherwise // result in ambiguous entries for servers tied to identical // accounts) if (hostname == null) - addRecordForLogin(record, recordService.getUsername(record)); + addRecordForLogin(record, username); - }); + } // Cache has been refreshed this.cacheTimestamp = System.currentTimeMillis(); @@ -268,6 +334,30 @@ public class KsmClient { } + /** + * Associates the given record with the given domain. The domain may be + * null. Both {@link #cachedRecordsByDomain} and {@link #cachedAmbiguousDomains} + * are updated appropriately. The write lock of {@link #cacheLock} must + * already be acquired before invoking this function. + * + * @param record + * The record to associate with the domains in the given field. + * + * @param domain + * The domain that the given record should be associated with. + * This may be null. + */ + private void addRecordForDomain(KeeperRecord record, String domain) { + + if (domain == null) + return; + + KeeperRecord existing = cachedRecordsByDomain.putIfAbsent(domain, record); + if (existing != null && record != existing) + cachedAmbiguousDomains.add(domain); + + } + /** * Associates the given record with the given hostname. The hostname may be * null. Both {@link #cachedRecordsByHost} and {@link #cachedAmbiguousHosts} @@ -432,6 +522,40 @@ public class KsmClient { } } + /** + * Returns the record associated with the given domain. If no such record + * exists, or there are multiple such records, null is returned. + * + * @param domain + * The domain of the record to return. + * + * @return + * The record associated with the given domain, or null if there is + * no such record or multiple such records. + * + * @throws GuacamoleException + * If an error occurs that prevents the record from being retrieved. + */ + public KeeperRecord getRecordByDomain(String domain) throws GuacamoleException { + validateCache(); + cacheLock.readLock().lock(); + try { + + if (cachedAmbiguousDomains.contains(domain)) { + logger.debug("The domain \"{}\" is referenced by multiple " + + "Keeper records and cannot be used to locate " + + "individual secrets.", domain); + return null; + } + + return cachedRecordsByDomain.get(domain); + + } + finally { + cacheLock.readLock().unlock(); + } + } + /** * Returns the value of the secret stored within Keeper Secrets Manager and * represented by the given Keeper notation. Keeper notation locates the diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java index d8168dc8f..604ae2865 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmSecretService.java @@ -344,6 +344,18 @@ public class KsmSecretService implements VaultSecretService { addRecordTokens(tokens, "KEEPER_GATEWAY_USER_", ksm.getRecordByLogin(filter.filter(gatewayUsername))); + // Retrieve and define domain tokens, if any + String domain = parameters.get("domain"); + if (domain != null && !domain.isEmpty()) + addRecordTokens(tokens, "KEEPER_DOMAIN_", + ksm.getRecordByDomain(filter.filter(domain))); + + // Retrieve and define gateway domain tokens, if any + String gatewayDomain = parameters.get("gateway-domain"); + if (gatewayDomain != null && !gatewayDomain.isEmpty()) + addRecordTokens(tokens, "KEEPER_GATEWAY_DOMAIN_", + ksm.getRecordByDomain(filter.filter(gatewayDomain))); + } return tokens;