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
private KsmConfigurationService confService;
/**
* Service for retrieving data from records.
*/
@Inject
private KsmRecordService recordService;
/**
* The publicly-accessible URL for Keeper's documentation covering Keeper
* notation.
@@ -226,28 +232,17 @@ public class KsmClient {
cachedRecordsByUsername.clear();
// Store all records, sorting each into host-based and login-based
// buckets (note that a single record may be associated with
// multiple hosts and logins)
// buckets
records.forEach(record -> {
// Store based on UID ...
cachedRecordsByUid.put(record.getRecordUid(), record);
// ... and standard fields ...
KeeperRecordData data = record.getData();
addRecordForHosts(record, (Hosts) data.getField(Hosts.class));
addRecordForLogin(record, (Login) data.getField(Login.class));
// ... and hostname/address ...
addRecordForHost(record, recordService.getHostname(record));
// ... and custom fields
List<KeeperRecordField> custom = data.getCustom();
if (custom != null) {
custom.forEach(field -> {
if (field instanceof Hosts)
addRecordForHosts(record, (Hosts) field);
else if (field instanceof Login)
addRecordForLogin(record, (Login) field);
});
}
// ... and username
addRecordForLogin(record, recordService.getUsername(record));
});
@@ -262,62 +257,51 @@ public class KsmClient {
}
/**
* Associates the given record with each of the hosts in the given Hosts
* field. The given Hosts field may be null. Both {@link #cachedRecordsByHost}
* 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}
* Associates the given record with the given hostname. The hostname may be
* null. Both {@link #cachedRecordsByHost} 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 login
* The Login field containing the usernames that the given record
* should be associated with. This may be null.
* @param hostname
* The hostname/address that the given record should be associated
* 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;
login.getValue().stream()
.forEachOrdered(username -> {
KeeperRecord existing = cachedRecordsByHost.putIfAbsent(hostname, record);
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.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.KeeperRecordData;
import com.keepersecurity.secretsManager.core.KeeperRecordField;
@@ -40,6 +42,13 @@ import java.util.regex.Pattern;
@Singleton
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
* 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
* record has no associated username, or multiple usernames, null is