GUACAMOLE-641: Add initial draft implementation of KSM vault support for Guacamole.

This commit is contained in:
Michael Jumper
2022-01-21 15:23:41 -08:00
parent 786430612e
commit 8bedbe746c
24 changed files with 1037 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.vault.ksm;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.vault.VaultAuthenticationProvider;
/**
* VaultAuthenticationProvider implementation which reads secrets from Keeper
* Secrets Manager
*/
public class KsmAuthenticationProvider extends VaultAuthenticationProvider {
/**
* Creates a new KsmKeyVaultAuthenticationProvider which reads secrets
* from a configured Keeper Secrets Manager.
*
* @throws GuacamoleException
* If configuration details cannot be read from guacamole.properties.
*/
public KsmAuthenticationProvider() throws GuacamoleException {
super(new KsmAuthenticationProviderModule());
}
@Override
public String getIdentifier() {
return "keeper-secrets-manager";
}
}

View File

@@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.vault.ksm;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.vault.VaultAuthenticationProviderModule;
import org.apache.guacamole.vault.ksm.conf.KsmConfigurationService;
import org.apache.guacamole.vault.ksm.secret.KsmSecretService;
import org.apache.guacamole.vault.conf.VaultConfigurationService;
import org.apache.guacamole.vault.ksm.secret.KsmClient;
import org.apache.guacamole.vault.secret.VaultSecretService;
/**
* Guice module which configures injections specific to Keeper Secrets
* Manager support.
*/
public class KsmAuthenticationProviderModule
extends VaultAuthenticationProviderModule {
/**
* Creates a new KsmAuthenticationProviderModule which
* configures dependency injection for the Keeper Secrets Manager
* authentication provider and related services.
*
* @throws GuacamoleException
* If configuration details in guacamole.properties cannot be parsed.
*/
public KsmAuthenticationProviderModule() throws GuacamoleException {}
@Override
protected void configureVault() {
// Bind services specific to Keeper Secrets Manager
bind(KsmClient.class);
bind(VaultConfigurationService.class).to(KsmConfigurationService.class);
bind(VaultSecretService.class).to(KsmSecretService.class);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.vault.ksm.conf;
import com.keepersecurity.secretsManager.core.InMemoryStorage;
import com.keepersecurity.secretsManager.core.KeyValueStorage;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.properties.GuacamoleProperty;
/**
* A GuacamoleProperty whose value is Keeper Secrets Manager {@link KeyValueStorage}
* object. The value of this property must be base64-encoded JSON, as output by
* the Keeper Commander CLI tool via the "sm client add" command.
*/
public abstract class KsmConfigProperty implements GuacamoleProperty<KeyValueStorage> {
@Override
public KeyValueStorage parseValue(String value) throws GuacamoleException {
// If no property provided, return null.
if (value == null)
return null;
// Parse base64 value as KSM config storage
try {
return new InMemoryStorage(value);
}
catch (IllegalArgumentException e) {
throw new GuacamoleServerException("Invalid base64 configuration "
+ "for Keeper Secrets Manager.", e);
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.vault.ksm.conf;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.properties.BooleanGuacamoleProperty;
import org.apache.guacamole.vault.conf.VaultConfigurationService;
import com.keepersecurity.secretsManager.core.SecretsManagerOptions;
/**
* Service for retrieving configuration information regarding the Keeper
* Secrets Manager authentication extension.
*/
@Singleton
public class KsmConfigurationService extends VaultConfigurationService {
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* The name of the file which contains the YAML mapping of connection
* parameter token to secrets within Keeper Secrets Manager.
*/
private static final String TOKEN_MAPPING_FILENAME = "ksm-token-mapping.yml";
/**
* The base64-encoded configuration information generated by the Keeper
* Commander CLI tool.
*/
private static final KsmConfigProperty KSM_CONFIG = new KsmConfigProperty() {
@Override
public String getName() {
return "ksm-config";
}
};
/**
* Whether unverified server certificates should be accepted.
*/
private static final BooleanGuacamoleProperty ALLOW_UNVERIFIED_CERT = new BooleanGuacamoleProperty() {
@Override
public String getName() {
return "ksm-allow-unverified-cert";
}
};
/**
* Creates a new KsmConfigurationService which reads the configuration
* from "ksm-token-mapping.yml". The token mapping is a YAML file which
* lists each connection parameter token and the title of the secret from
* which the value for that token should be read.
*/
public KsmConfigurationService() {
super(TOKEN_MAPPING_FILENAME);
}
/**
* Return whether unverified server certificates should be accepted when
* communicating with Keeper Secrets Manager.
*
* @return
* true if unverified server certificates should be accepted, false
* otherwise.
*
* @throws GuacamoleException
* If the value specified within guacamole.properties cannot be
* parsed.
*/
public boolean getAllowUnverifiedCertificate() throws GuacamoleException {
return environment.getProperty(ALLOW_UNVERIFIED_CERT, false);
}
/**
* Returns the options required to authenticate with Keeper Secrets Manager
* when retrieving secrets. These options are read from the contents of
* base64-encoded JSON configuration data generated by the Keeper Commander
* CLI tool.
*
* @return
* The options that should be used when connecting to Keeper Secrets
* Manager when retrieving secrets.
*
* @throws GuacamoleException
* If required properties are not specified within
* guacamole.properties or cannot be parsed.
*/
public SecretsManagerOptions getSecretsManagerOptions() throws GuacamoleException {
return new SecretsManagerOptions(environment.getRequiredProperty(KSM_CONFIG), null,
getAllowUnverifiedCertificate());
}
}

View File

@@ -0,0 +1,491 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.vault.ksm.secret;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.keepersecurity.secretsManager.core.Hosts;
import com.keepersecurity.secretsManager.core.KeeperFile;
import com.keepersecurity.secretsManager.core.KeeperRecord;
import com.keepersecurity.secretsManager.core.KeeperRecordData;
import com.keepersecurity.secretsManager.core.KeeperRecordField;
import com.keepersecurity.secretsManager.core.KeeperSecrets;
import com.keepersecurity.secretsManager.core.Login;
import com.keepersecurity.secretsManager.core.Notation;
import com.keepersecurity.secretsManager.core.SecretsManager;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Client which retrieves records from Keeper Secrets Manager, allowing
* content-based record retrieval. Note that because KSM is zero-knowledge,
* searching or indexing based on content can only be accomplished by
* retrieving and indexing everything. Except for record UIDs (which contain no
* information), it's not possible for the server to perform a search of
* content on the client's behalf. The client has to perform its own search.
*/
@Singleton
public class KsmClient {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(KsmClient.class);
/**
* Service for retrieving configuration information.
*/
@Inject
private KsmConfigurationService confService;
/**
* The publicly-accessible URL for Keeper's documentation covering Keeper
* notation.
*/
private static final String KEEPER_NOTATION_DOC_URL =
"https://docs.keeper.io/secrets-manager/secrets-manager/about/keeper-notation";
/**
* The regular expression that Keeper notation must match to be related to
* file retrieval. As the Keeper SDK provides mutually-exclusive for
* retrieving secret values and files via notation, the notation must first
* be tested to determine whether it refers to a file.
*/
private static final Pattern KEEPER_FILE_NOTATION = Pattern.compile("^(keeper://)?[^/]*/file/.+");
/**
* The maximum amount of time that an entry will be stored in the cache
* before being refreshed, in milliseconds.
*/
private static final long CACHE_INTERVAL = 5000;
/**
* Read/write lock which guards access to all cached data, including the
* timestamp recording the last time the cache was refreshed. Readers of
* the cache must first acquire (and eventually release) the read lock, and
* writers of the cache must first acquire (and eventually release) the
* write lock.
*/
private final ReadWriteLock cacheLock = new ReentrantReadWriteLock();
/**
* The timestamp that the cache was last refreshed, in milliseconds, as
* returned by System.currentTimeMillis(). This value is automatically
* updated if {@link #validateCache()} refreshes the cache. This value must
* not be accessed without {@link #cacheLock} acquired appropriately.
*/
private volatile long cacheTimestamp = 0;
/**
* The full cached set of secrets last retrieved from Keeper Secrets
* Manager. This value is automatically updated if {@link #validateCache()}
* refreshes the cache. This value must not be accessed without
* {@link #cacheLock} acquired appropriately.
*/
private KeeperSecrets cachedSecrets = null;
/**
* All records retrieved from Keeper Secrets Manager, where each key is the
* UID of the corresponding record. 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.
*/
private final Map<String, KeeperRecord> cachedRecordsByUid = new HashMap<>();
/**
* All records retrieved from Keeper Secrets Manager, where each key is the
* hostname or IP address of the corresponding record. The hostname or IP
* address of a record is determined by {@link Hosts} fields, thus a record
* may be associated with multiple hosts. If a record is associated with
* multiple hosts, 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 #cachedAmbiguousHosts} must first be
* checked to verify that there is indeed only one record associated with
* that host.
*/
private final Map<String, KeeperRecord> cachedRecordsByHost = new HashMap<>();
/**
* The set of all hostnames or IP addresses 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 #cachedRecordsByHost}.
*/
private final Set<String> cachedAmbiguousHosts = new HashSet<>();
/**
* All records retrieved from Keeper Secrets Manager, where each key is the
* username of the corresponding record. The username of a record is
* determined by {@link Login} fields, thus a record may be associated with
* multiple users. If a record is associated with multiple users, 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 #cachedAmbiguousUsernames} must first be checked to
* verify that there is indeed only one record associated with that user.
*/
private final Map<String, KeeperRecord> cachedRecordsByUsername = new HashMap<>();
/**
* The set of all usernames 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 #cachedRecordsByUsername}.
*/
private final Set<String> cachedAmbiguousUsernames = new HashSet<>();
/**
* Validates that all cached data is current with respect to
* {@link #CACHE_INTERVAL}, refreshing data from the server as needed.
*
* @throws GuacamoleException
* If an error occurs preventing the cached data from being refreshed.
*/
private void validateCache() throws GuacamoleException {
long currentTime = System.currentTimeMillis();
// Perform a read-only check that the cache has actually expired before
// continuing
cacheLock.readLock().lock();
try {
if (currentTime - cacheTimestamp < CACHE_INTERVAL)
return;
}
finally {
cacheLock.readLock().unlock();
}
cacheLock.writeLock().lock();
try {
// Cache may have been updated since the read-only check. Re-verify
// that the cache has expired before continuing with a full refresh
if (currentTime - cacheTimestamp < CACHE_INTERVAL)
return;
// Attempt to pull all records first, allowing that operation to
// succeed/fail BEFORE we clear out the last cached success
KeeperSecrets secrets = SecretsManager.getSecrets(confService.getSecretsManagerOptions());
List<KeeperRecord> records = secrets.getRecords();
// Store all secrets within cache
cachedSecrets = secrets;
// Clear unambiguous cache of all records by UID
cachedRecordsByUid.clear();
// Clear cache of host-based records
cachedAmbiguousHosts.clear();
cachedRecordsByHost.clear();
// Clear cache of login-based records
cachedAmbiguousUsernames.clear();
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)
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 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);
});
}
});
// Cache has been refreshed
this.cacheTimestamp = System.currentTimeMillis();
}
finally {
cacheLock.writeLock().unlock();
}
}
/**
* 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}
* 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.
*/
private void addRecordForLogin(KeeperRecord record, Login login) {
if (login == null)
return;
login.getValue().stream()
.forEachOrdered(username -> {
KeeperRecord existing = cachedRecordsByUsername.putIfAbsent(username, record);
if (existing != null && record != existing)
cachedAmbiguousUsernames.add(username);
});
}
/**
* Returns all records accessible via Keeper Secrets Manager. The records
* returned are arbitrarily ordered.
*
* @return
* An unmodifiable Collection of all records accessible via Keeper
* Secrets Manager, in no particular order.
*
* @throws GuacamoleException
* If an error occurs that prevents records from being retrieved.
*/
public Collection<KeeperRecord> getRecords() throws GuacamoleException {
validateCache();
cacheLock.readLock().lock();
try {
return Collections.unmodifiableCollection(cachedRecordsByUid.values());
}
finally {
cacheLock.readLock().unlock();
}
}
/**
* Returns the record having the given UID. If no such record exists, null
* is returned.
*
* @param uid
* The UID of the record to return.
*
* @return
* The record having the given UID, or null if there is no such record.
*
* @throws GuacamoleException
* If an error occurs that prevents the record from being retrieved.
*/
public KeeperRecord getRecord(String uid) throws GuacamoleException {
validateCache();
cacheLock.readLock().lock();
try {
return cachedRecordsByUid.get(uid);
}
finally {
cacheLock.readLock().unlock();
}
}
/**
* Returns the record associated with the given hostname or IP address. If
* no such record exists, or there are multiple such records, null is
* returned.
*
* @param hostname
* The hostname of the record to return.
*
* @return
* The record associated with the given hostname, 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 getRecordByHost(String hostname) throws GuacamoleException {
validateCache();
cacheLock.readLock().lock();
try {
if (cachedAmbiguousHosts.contains(hostname))
return null;
return cachedRecordsByHost.get(hostname);
}
finally {
cacheLock.readLock().unlock();
}
}
/**
* Returns the record associated with the given username. If no such record
* exists, or there are multiple such records, null is returned.
*
* @param username
* The username of the record to return.
*
* @return
* The record associated with the given username, 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 getRecordByLogin(String username) throws GuacamoleException {
validateCache();
cacheLock.readLock().lock();
try {
if (cachedAmbiguousUsernames.contains(username))
return null;
return cachedRecordsByUsername.get(username);
}
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
* value of a specific field, custom field, or file associated with a
* specific record. See: https://docs.keeper.io/secrets-manager/secrets-manager/about/keeper-notation
*
* @param notation
* The Keeper notation of the secret to retrieve.
*
* @return
* A Future which completes with the value of the secret represented by
* the given Keeper notation, or null if there is no such secret.
*
* @throws GuacamoleException
* If the requested secret cannot be retrieved or the Keeper notation
* is invalid.
*/
public Future<String> getSecret(String notation) throws GuacamoleException {
validateCache();
cacheLock.readLock().lock();
try {
Matcher fileNotationMatcher = KEEPER_FILE_NOTATION.matcher(notation);
if (fileNotationMatcher.matches()) {
// Retrieve any relevant file asynchronously
KeeperFile file = Notation.getFile(cachedSecrets, notation);
return CompletableFuture.supplyAsync(() -> {
return new String(SecretsManager.downloadFile(file), StandardCharsets.UTF_8);
});
}
// Retrieve string values synchronously
return CompletableFuture.completedFuture(Notation.getValue(cachedSecrets, notation));
}
// Unfortunately, the notation parser within the Keeper SDK throws
// plain Errors for retrieval failures ...
catch (Error e) {
logger.warn("Record \"{}\" does not exist.", notation);
logger.debug("Retrieval of record by Keeper notation failed.", e);
return CompletableFuture.completedFuture(null);
}
// ... and plain Exceptions for parse failures (no subclasses)
catch (Exception e) {
logger.warn("\"{}\" is not valid Keeper notation. Please check "
+ "the documentation at {} for valid formatting.",
notation, KEEPER_NOTATION_DOC_URL);
logger.debug("Provided Keeper notation could not be parsed.", e);
return CompletableFuture.completedFuture(null);
}
finally {
cacheLock.readLock().unlock();
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.vault.ksm.secret;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.concurrent.Future;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.vault.secret.VaultSecretService;
/**
* Service which retrieves secrets from Keeper Secrets Manager.
*/
@Singleton
public class KsmSecretService implements VaultSecretService {
/**
* Client for retrieving records and secrets from Keeper Secrets Manager.
*/
@Inject
private KsmClient ksm;
@Override
public String canonicalize(String nameComponent) {
try {
// As Keeper notation is essentially a URL, encode all components
// using standard URL escaping
return URLEncoder.encode(nameComponent, "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new UnsupportedOperationException("Unexpected lack of UTF-8 support.", e);
}
}
@Override
public Future<String> getValue(String name) throws GuacamoleException {
return ksm.getSecret(name);
}
}

View File

@@ -0,0 +1,16 @@
{
"guacamoleVersion" : "1.4.0",
"name" : "Keeper Secrets Manager",
"namespace" : "keeper-secrets-manager",
"authProviders" : [
"org.apache.guacamole.vault.ksm.KsmAuthenticationProvider"
],
"translations" : [
"translations/en.json"
]
}