GUACAMOLE-641: Use record service to resolve hostname/username of records for later lookup.

This commit is contained in:
Michael Jumper
2022-01-21 15:23:41 -08:00
parent 55b7e6f867
commit 87b26fe2c8
2 changed files with 91 additions and 60 deletions

View File

@@ -71,6 +71,12 @@ public class KsmClient {
@Inject @Inject
private KsmConfigurationService confService; private KsmConfigurationService confService;
/**
* Service for retrieving data from records.
*/
@Inject
private KsmRecordService recordService;
/** /**
* The publicly-accessible URL for Keeper's documentation covering Keeper * The publicly-accessible URL for Keeper's documentation covering Keeper
* notation. * notation.
@@ -226,28 +232,17 @@ public class KsmClient {
cachedRecordsByUsername.clear(); cachedRecordsByUsername.clear();
// Store all records, sorting each into host-based and login-based // Store all records, sorting each into host-based and login-based
// buckets (note that a single record may be associated with // buckets
// multiple hosts and logins)
records.forEach(record -> { records.forEach(record -> {
// Store based on UID ... // Store based on UID ...
cachedRecordsByUid.put(record.getRecordUid(), record); cachedRecordsByUid.put(record.getRecordUid(), record);
// ... and standard fields ... // ... and hostname/address ...
KeeperRecordData data = record.getData(); addRecordForHost(record, recordService.getHostname(record));
addRecordForHosts(record, (Hosts) data.getField(Hosts.class));
addRecordForLogin(record, (Login) data.getField(Login.class));
// ... and custom fields // ... and username
List<KeeperRecordField> custom = data.getCustom(); addRecordForLogin(record, recordService.getUsername(record));
if (custom != null) {
custom.forEach(field -> {
if (field instanceof Hosts)
addRecordForHosts(record, (Hosts) field);
else if (field instanceof Login)
addRecordForLogin(record, (Login) field);
});
}
}); });
@@ -262,62 +257,51 @@ public class KsmClient {
} }
/** /**
* Associates the given record with each of the hosts in the given Hosts * Associates the given record with the given hostname. The hostname may be
* field. The given Hosts field may be null. Both {@link #cachedRecordsByHost} * null. Both {@link #cachedRecordsByHost} and {@link #cachedAmbiguousHosts}
* and {@link #cachedAmbiguousHosts} 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 hosts in the given field.
*
* @param hosts
* The Hosts field containing the hosts that the given record should be
* associated with. This may be null.
*/
private void addRecordForHosts(KeeperRecord record, Hosts hosts) {
if (hosts == null)
return;
hosts.getValue().stream().map(host -> host.getHostName())
.forEachOrdered(hostname -> {
KeeperRecord existing = cachedRecordsByHost.putIfAbsent(hostname, record);
if (existing != null && record != existing)
cachedAmbiguousHosts.add(hostname);
});
}
/**
* Associates the given record with each of the usernames in the given
* Login field. The given Hosts field may be null. Both
* {@link #cachedRecordsByUsername} and {@link #cachedAmbiguousUsernames}
* are updated appropriately. The write lock of {@link #cacheLock} must * are updated appropriately. The write lock of {@link #cacheLock} must
* already be acquired before invoking this function. * already be acquired before invoking this function.
* *
* @param record * @param record
* The record to associate with the hosts in the given field. * The record to associate with the hosts in the given field.
* *
* @param login * @param hostname
* The Login field containing the usernames that the given record * The hostname/address that the given record should be associated
* should be associated with. This may be null. * with. This may be null.
*/ */
private void addRecordForLogin(KeeperRecord record, Login login) { private void addRecordForHost(KeeperRecord record, String hostname) {
if (login == null) if (hostname == null)
return; return;
login.getValue().stream() KeeperRecord existing = cachedRecordsByHost.putIfAbsent(hostname, record);
.forEachOrdered(username -> { if (existing != null && record != existing)
cachedAmbiguousHosts.add(hostname);
KeeperRecord existing = cachedRecordsByUsername.putIfAbsent(username, record); }
if (existing != null && record != existing)
cachedAmbiguousUsernames.add(username);
}); /**
* Associates the given record with the given username. The given username
* may be null. Both {@link #cachedRecordsByUsername} and
* {@link #cachedAmbiguousUsernames} 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 given username.
*
* @param username
* The username that the given record should be associated with. This
* may be null.
*/
private void addRecordForLogin(KeeperRecord record, String username) {
if (username == null)
return;
KeeperRecord existing = cachedRecordsByUsername.putIfAbsent(username, record);
if (existing != null && record != existing)
cachedAmbiguousUsernames.add(username);
} }

View File

@@ -21,6 +21,8 @@ package org.apache.guacamole.vault.ksm.secret;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.keepersecurity.secretsManager.core.HiddenField; import com.keepersecurity.secretsManager.core.HiddenField;
import com.keepersecurity.secretsManager.core.Host;
import com.keepersecurity.secretsManager.core.Hosts;
import com.keepersecurity.secretsManager.core.KeeperRecord; import com.keepersecurity.secretsManager.core.KeeperRecord;
import com.keepersecurity.secretsManager.core.KeeperRecordData; import com.keepersecurity.secretsManager.core.KeeperRecordData;
import com.keepersecurity.secretsManager.core.KeeperRecordField; import com.keepersecurity.secretsManager.core.KeeperRecordField;
@@ -40,6 +42,13 @@ import java.util.regex.Pattern;
@Singleton @Singleton
public class KsmRecordService { public class KsmRecordService {
/**
* Regular expression which matches the labels of custom fields containing
* hostnames/addresses.
*/
private static final Pattern HOSTNAME_LABEL_PATTERN =
Pattern.compile("hostname|(ip\\s*)?address", Pattern.CASE_INSENSITIVE);
/** /**
* Regular expression which matches the labels of custom fields containing * Regular expression which matches the labels of custom fields containing
* usernames. * usernames.
@@ -225,6 +234,44 @@ public class KsmRecordService {
} }
/**
* Returns the single hostname (or address) associated with the given
* record. If the record has no associated hostname, or multiple hostnames,
* null is returned. Hostnames are retrieved from "Hosts" fields, as well
* as "Text" and "Hidden" fields that have the label "hostname", "address",
* or "ip address" (case-insensitive, space optional).
*
* @param record
* The record to retrieve the hostname from.
*
* @return
* The hostname associated with the given record, or null if the record
* has no associated hostname or multiple hostnames.
*/
public String getHostname(KeeperRecord record) {
// Prefer standard login field
Hosts hostsField = getField(record, Hosts.class, null);
if (hostsField != null)
return getSingleValue(hostsField.getValue(), Host::getHostName);
KeeperRecordData data = record.getData();
List<KeeperRecordField> custom = data.getCustom();
// Use text "hostname" custom field as fallback ...
Text textField = getField(custom, Text.class, HOSTNAME_LABEL_PATTERN);
if (textField != null)
return getSingleValue(textField.getValue());
// ... or hidden "hostname" custom field
HiddenField hiddenField = getField(custom, HiddenField.class, HOSTNAME_LABEL_PATTERN);
if (hiddenField != null)
return getSingleValue(hiddenField.getValue());
return null;
}
/** /**
* Returns the single username associated with the given record. If the * Returns the single username associated with the given record. If the
* record has no associated username, or multiple usernames, null is * record has no associated username, or multiple usernames, null is