diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/GuacamoleExceptionSupplier.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/GuacamoleExceptionSupplier.java new file mode 100644 index 000000000..d966a7320 --- /dev/null +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/GuacamoleExceptionSupplier.java @@ -0,0 +1,42 @@ +/* + * 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; + +/** + * A class that is basically equivalent to the standard Supplier class in + * Java, except that the get() function can throw GuacamoleException, which + * is impossible with any of the standard Java lambda type classes, since + * none of them can handle checked exceptions + */ +public abstract class GuacamoleExceptionSupplier { + + /** + * Returns a value of the declared type. + * + * @return + * A value of the declared type. + * + * @throws GuacamoleException + * If an error occurs while attemping to calculate the return value. + */ + public abstract T get() throws GuacamoleException; +} \ No newline at end of file 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 6e0bdf177..74a27e472 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,7 +34,6 @@ 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,10 +44,12 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.Nullable; + import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.net.auth.User; import org.apache.guacamole.vault.ksm.conf.KsmConfigurationService; import org.apache.guacamole.vault.secret.WindowsUsername; +import org.apache.guacamole.vault.ksm.GuacamoleExceptionSupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -595,6 +596,38 @@ public class KsmClient { * is invalid. */ public Future getSecret(String notation) throws GuacamoleException { + return getSecret(notation, null); + } + + /** + * 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 + * If a fallbackFunction is provided, it will be invoked to generate + * a return value in the case where no secrest is found with the given + * keeper notation. + * + * @param notation + * The Keeper notation of the secret to retrieve. + * + * @param fallbackFunction + * A function to invoke in order to produce a Future for return, + * if the requested secret is not found. If the provided Function + * is null, it will not be run. + * + * @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 getSecret( + String notation, + @Nullable GuacamoleExceptionSupplier> fallbackFunction) + throws GuacamoleException { validateCache(); cacheLock.readLock().lock(); try { @@ -614,6 +647,11 @@ public class KsmClient { catch (Error e) { logger.warn("Record \"{}\" does not exist.", notation); logger.debug("Retrieval of record by Keeper notation failed.", e); + + // If the secret is not found, invoke the fallback function + if (fallbackFunction != null) + return fallbackFunction.get(); + return CompletableFuture.completedFuture(null); } 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 a3b772256..6b8ba92a2 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 @@ -48,6 +48,7 @@ import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.UserContext; import org.apache.guacamole.protocol.GuacamoleConfiguration; import org.apache.guacamole.token.TokenFilter; +import org.apache.guacamole.vault.ksm.GuacamoleExceptionSupplier; import org.apache.guacamole.vault.ksm.conf.KsmAttributeService; import org.apache.guacamole.vault.ksm.conf.KsmConfigurationService; import org.apache.guacamole.vault.secret.VaultSecretService; @@ -147,7 +148,24 @@ public class KsmSecretService implements VaultSecretService { // Attempt to find a KSM config for this connection or group String ksmConfig = getConnectionGroupKsmConfig(userContext, connectable); - return getClient(ksmConfig).getSecret(name); + return getClient(ksmConfig).getSecret(name, new GuacamoleExceptionSupplier>() { + + @Override + public Future get() throws GuacamoleException { + + // Get the user-supplied KSM config, if allowed by config and + // set by the user + String userKsmConfig = getUserKSMConfig(userContext, connectable); + + // If the user config happens to be the same as admin-defined one, + // don't bother trying again + if (userKsmConfig != ksmConfig) + return getClient(userKsmConfig).getSecret(name); + + return CompletableFuture.completedFuture(null); + } + + }); } @Override @@ -335,6 +353,44 @@ public class KsmSecretService implements VaultSecretService { } + /** + * Return the KSM config blob for the current user IFF user KSM configs + * are enabled globally, and are enabled for the given connectable. If no + * KSM config exists for the given user or KSM configs are not enabled, + * null will be returned. + * + * @param userContext + * The user context from which the current user should be fetched. + * + * @param connectable + * The connectable to which the connection is being established. This + * is the conneciton which will be checked to see if user KSM configs + * are enabled. + * + * @return + * The base64 encoded KSM config blob for the current user if one + * exists, and if user KSM configs are enabled globally and for the + * provided connectable. + * + * @throws GuacamoleException + * If an error occurs while attempting to fetch the KSM config. + */ + private String getUserKSMConfig( + UserContext userContext, Connectable connectable) throws GuacamoleException { + + // Check if user KSM configs are enabled globally, and for the given connectable + if (confService.getAllowUserConfig() && isKsmUserConfigEnabled(connectable)) + + // Return the user-specific KSM config, if one exists + return userContext.self().getAttributes().get( + KsmAttributeService.KSM_CONFIGURATION_ATTRIBUTE); + + + // If user-specific KSM config is disabled globally or for the given + // connectable, return null to indicate that no user config exists + return null; + } + @Override public Map> getTokens(UserContext userContext, Connectable connectable, GuacamoleConfiguration config, TokenFilter filter) throws GuacamoleException { @@ -351,16 +407,9 @@ public class KsmSecretService implements VaultSecretService { // Only use the user-specific KSM config if explicitly enabled in the global // configuration, AND for the specific connectable being connected to - if (confService.getAllowUserConfig() && isKsmUserConfigEnabled(connectable)) { - - // Find a user-specific KSM config, if one exists - String userKsmConfig = userContext.self().getAttributes().get( - KsmAttributeService.KSM_CONFIGURATION_ATTRIBUTE); - - // If a user-specific config exsts, process it first - if (userKsmConfig != null && !userKsmConfig.trim().isEmpty()) - ksmClients.add(0, getClient(userKsmConfig)); - } + String userKsmConfig = getUserKSMConfig(userContext, connectable); + if (userKsmConfig != null && !userKsmConfig.trim().isEmpty()) + ksmClients.add(0, getClient(userKsmConfig)); // Iterate through the KSM clients, processing using the user-specific // config first (if it exists), to ensure that any admin-defined values @@ -431,6 +480,7 @@ public class KsmSecretService implements VaultSecretService { addRecordTokens(tokens, "KEEPER_USER_", ksm.getRecordByLogin(filter.filter(username), null)); } + } return tokens;