mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUACAMOLE-1629: Merge support for vault-specific configuration at the connection group level.
This commit is contained in:
@@ -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.conf;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.guacamole.form.Form;
|
||||
|
||||
/**
|
||||
* A service that exposes attributes for the admin UI, specific to the vault
|
||||
* implementation. Any vault implementation will need to expose the attributes
|
||||
* necessary for that implementation.
|
||||
*/
|
||||
public interface VaultAttributeService {
|
||||
|
||||
/**
|
||||
* Return all custom connection group attributes to be exposed through the
|
||||
* admin UI for the current vault implementation.
|
||||
*
|
||||
* @return
|
||||
* All custom connection group attributes to be exposed through the
|
||||
* admin UI for the current vault implementation.
|
||||
*/
|
||||
public Collection<Form> getConnectionGroupAttributes();
|
||||
}
|
@@ -22,6 +22,8 @@ package org.apache.guacamole.vault.secret;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.Connectable;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||
import org.apache.guacamole.token.TokenFilter;
|
||||
|
||||
@@ -55,7 +57,9 @@ public interface VaultSecretService {
|
||||
/**
|
||||
* Returns a Future which eventually completes with the value of the secret
|
||||
* having the given name. If no such secret exists, the Future will be
|
||||
* completed with null.
|
||||
* completed with null. The secrets retrieved from this method are independent
|
||||
* of the context of the particular connection being established, or any
|
||||
* associated user context.
|
||||
*
|
||||
* @param name
|
||||
* The name of the secret to retrieve.
|
||||
@@ -72,6 +76,35 @@ public interface VaultSecretService {
|
||||
*/
|
||||
Future<String> getValue(String name) throws GuacamoleException;
|
||||
|
||||
/**
|
||||
* Returns a Future which eventually completes with the value of the secret
|
||||
* having the given name. If no such secret exists, the Future will be
|
||||
* completed with null. The connection or connection group, as well as the
|
||||
* user context associated with the request are provided for additional context.
|
||||
*
|
||||
* @param userContext
|
||||
* The user context associated with the connection or connection group for
|
||||
* which the secret is being retrieved.
|
||||
*
|
||||
* @param connectable
|
||||
* The connection or connection group for which the secret is being retrieved.
|
||||
*
|
||||
* @param name
|
||||
* The name of the secret to retrieve.
|
||||
*
|
||||
* @return
|
||||
* A Future which completes with value of the secret having the given
|
||||
* name. If no such secret exists, the Future will be completed with
|
||||
* null. If an error occurs asynchronously which prevents retrieval of
|
||||
* the secret, that error will be exposed through an ExecutionException
|
||||
* when an attempt is made to retrieve the value from the Future.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the secret cannot be retrieved due to an error.
|
||||
*/
|
||||
Future<String> getValue(UserContext userContext, Connectable connectable,
|
||||
String name) throws GuacamoleException;
|
||||
|
||||
/**
|
||||
* Returns a map of token names to corresponding Futures which eventually
|
||||
* complete with the value of that token, where each token is dynamically
|
||||
@@ -80,6 +113,12 @@ public interface VaultSecretService {
|
||||
* function should be implemented to provide automatic tokens for those
|
||||
* secrets and remove the need for manual mapping via YAML.
|
||||
*
|
||||
* @param userContext
|
||||
* The user context from which the connectable originated.
|
||||
*
|
||||
* @param connectable
|
||||
* The connection or connection group for which the tokens are being replaced.
|
||||
*
|
||||
* @param config
|
||||
* The configuration of the Guacamole connection for which tokens are
|
||||
* being generated. This configuration may be empty or partial,
|
||||
@@ -99,7 +138,7 @@ public interface VaultSecretService {
|
||||
* If an error occurs producing the tokens and values required for the
|
||||
* given configuration.
|
||||
*/
|
||||
Map<String, Future<String>> getTokens(GuacamoleConfiguration config,
|
||||
TokenFilter filter) throws GuacamoleException;
|
||||
Map<String, Future<String>> getTokens(UserContext userContext, Connectable connectable,
|
||||
GuacamoleConfiguration config, TokenFilter filter) throws GuacamoleException;
|
||||
|
||||
}
|
||||
|
@@ -22,12 +22,20 @@ package org.apache.guacamole.vault.user;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import com.google.inject.assistedinject.AssistedInject;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleServerException;
|
||||
import org.apache.guacamole.form.Form;
|
||||
import org.apache.guacamole.net.auth.Connectable;
|
||||
import org.apache.guacamole.net.auth.Connection;
|
||||
import org.apache.guacamole.net.auth.ConnectionGroup;
|
||||
import org.apache.guacamole.net.auth.TokenInjectingUserContext;
|
||||
@@ -35,6 +43,7 @@ import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||
import org.apache.guacamole.token.GuacamoleTokenUndefinedException;
|
||||
import org.apache.guacamole.token.TokenFilter;
|
||||
import org.apache.guacamole.vault.conf.VaultAttributeService;
|
||||
import org.apache.guacamole.vault.conf.VaultConfigurationService;
|
||||
import org.apache.guacamole.vault.secret.VaultSecretService;
|
||||
import org.slf4j.Logger;
|
||||
@@ -121,6 +130,13 @@ public class VaultUserContext extends TokenInjectingUserContext {
|
||||
@Inject
|
||||
private VaultSecretService secretService;
|
||||
|
||||
/**
|
||||
* Service for retrieving any custom attributes defined for the
|
||||
* current vault implementation.
|
||||
*/
|
||||
@Inject
|
||||
private VaultAttributeService attributeService;
|
||||
|
||||
/**
|
||||
* Creates a new VaultUserContext which automatically injects tokens
|
||||
* containing values of secrets retrieved from a vault. The given
|
||||
@@ -182,6 +198,10 @@ public class VaultUserContext extends TokenInjectingUserContext {
|
||||
* corresponding values from the vault, using the given TokenFilter to
|
||||
* filter tokens within the secret names prior to retrieving those secrets.
|
||||
*
|
||||
* @param connectable
|
||||
* The connection or connection group to which the connection is being
|
||||
* established.
|
||||
*
|
||||
* @param tokenMapping
|
||||
* The mapping dictating the name of the secret which maps to each
|
||||
* parameter token, where the key is the name of the parameter token
|
||||
@@ -211,7 +231,8 @@ public class VaultUserContext extends TokenInjectingUserContext {
|
||||
* If the value for any applicable secret cannot be retrieved from the
|
||||
* vault due to an error.
|
||||
*/
|
||||
private Map<String, Future<String>> getTokens(Map<String, String> tokenMapping,
|
||||
private Map<String, Future<String>> getTokens(
|
||||
Connectable connectable, Map<String, String> tokenMapping,
|
||||
TokenFilter secretNameFilter, GuacamoleConfiguration config,
|
||||
TokenFilter configFilter) throws GuacamoleException {
|
||||
|
||||
@@ -236,14 +257,16 @@ public class VaultUserContext extends TokenInjectingUserContext {
|
||||
|
||||
// Initiate asynchronous retrieval of the token value
|
||||
String tokenName = entry.getKey();
|
||||
Future<String> secret = secretService.getValue(secretName);
|
||||
Future<String> secret = secretService.getValue(
|
||||
this, connectable, secretName);
|
||||
pendingTokens.put(tokenName, secret);
|
||||
|
||||
}
|
||||
|
||||
// Additionally include any dynamic, parameter-based tokens
|
||||
pendingTokens.putAll(secretService.getTokens(config, configFilter));
|
||||
|
||||
pendingTokens.putAll(secretService.getTokens(
|
||||
this, connectable, config, configFilter));
|
||||
|
||||
return pendingTokens;
|
||||
|
||||
}
|
||||
@@ -318,7 +341,8 @@ public class VaultUserContext extends TokenInjectingUserContext {
|
||||
|
||||
// Substitute tokens producing secret names, retrieving and storing
|
||||
// those secrets as parameter tokens
|
||||
tokens.putAll(resolve(getTokens(confService.getTokenMapping(), filter,
|
||||
tokens.putAll(resolve(getTokens(
|
||||
connectionGroup, confService.getTokenMapping(), filter,
|
||||
null, new TokenFilter(tokens))));
|
||||
|
||||
}
|
||||
@@ -398,8 +422,19 @@ public class VaultUserContext extends TokenInjectingUserContext {
|
||||
|
||||
// Substitute tokens producing secret names, retrieving and storing
|
||||
// those secrets as parameter tokens
|
||||
tokens.putAll(resolve(getTokens(confService.getTokenMapping(), filter,
|
||||
config, new TokenFilter(tokens))));
|
||||
tokens.putAll(resolve(getTokens(connection, confService.getTokenMapping(),
|
||||
filter, config, new TokenFilter(tokens))));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Form> getConnectionGroupAttributes() {
|
||||
|
||||
// Add any custom attributes to any previously defined attributes
|
||||
return Collections.unmodifiableCollection(Stream.concat(
|
||||
super.getConnectionGroupAttributes().stream(),
|
||||
attributeService.getConnectionGroupAttributes().stream()
|
||||
).collect(Collectors.toList()));
|
||||
|
||||
}
|
||||
|
||||
|
@@ -21,13 +21,18 @@ package org.apache.guacamole.vault.ksm;
|
||||
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.vault.VaultAuthenticationProviderModule;
|
||||
import org.apache.guacamole.vault.ksm.conf.KsmAttributeService;
|
||||
import org.apache.guacamole.vault.ksm.conf.KsmConfigurationService;
|
||||
import org.apache.guacamole.vault.ksm.secret.KsmSecretService;
|
||||
import org.apache.guacamole.vault.conf.VaultAttributeService;
|
||||
import org.apache.guacamole.vault.conf.VaultConfigurationService;
|
||||
import org.apache.guacamole.vault.ksm.secret.KsmClient;
|
||||
import org.apache.guacamole.vault.ksm.secret.KsmClientFactory;
|
||||
import org.apache.guacamole.vault.ksm.secret.KsmRecordService;
|
||||
import org.apache.guacamole.vault.secret.VaultSecretService;
|
||||
|
||||
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||
|
||||
/**
|
||||
* Guice module which configures injections specific to Keeper Secrets
|
||||
* Manager support.
|
||||
@@ -49,10 +54,15 @@ public class KsmAuthenticationProviderModule
|
||||
protected void configureVault() {
|
||||
|
||||
// Bind services specific to Keeper Secrets Manager
|
||||
bind(KsmClient.class);
|
||||
bind(KsmRecordService.class);
|
||||
bind(VaultAttributeService.class).to(KsmAttributeService.class);
|
||||
bind(VaultConfigurationService.class).to(KsmConfigurationService.class);
|
||||
bind(VaultSecretService.class).to(KsmSecretService.class);
|
||||
|
||||
// Bind factory for creating KSM Clients
|
||||
install(new FactoryModuleBuilder()
|
||||
.implement(KsmClient.class, KsmClient.class)
|
||||
.build(KsmClientFactory.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.guacamole.form.Form;
|
||||
import org.apache.guacamole.form.TextField;
|
||||
import org.apache.guacamole.vault.conf.VaultAttributeService;
|
||||
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
/**
|
||||
* A service that exposes KSM-specific attributes, allowing setting KSM
|
||||
* configuration through the admin interface.
|
||||
*/
|
||||
@Singleton
|
||||
public class KsmAttributeService implements VaultAttributeService {
|
||||
|
||||
/**
|
||||
* The name of the attribute which can contain a KSM configuration blob
|
||||
* associated with a connection group.
|
||||
*/
|
||||
public static final String KSM_CONFIGURATION_ATTRIBUTE = "ksm-config";
|
||||
|
||||
/**
|
||||
* All attributes related to configuring the KSM vault on a
|
||||
* per-connection-group basis.
|
||||
*/
|
||||
public static final Form KSM_CONFIGURATION_FORM = new Form("ksm-config",
|
||||
Arrays.asList(new TextField(KSM_CONFIGURATION_ATTRIBUTE)));
|
||||
|
||||
/**
|
||||
* All KSM-specific connection group attributes, organized by form.
|
||||
*/
|
||||
public static final Collection<Form> KSM_CONNECTION_GROUP_ATTRIBUTES =
|
||||
Collections.unmodifiableCollection(Arrays.asList(KSM_CONFIGURATION_FORM));
|
||||
|
||||
@Override
|
||||
public Collection<Form> getConnectionGroupAttributes() {
|
||||
return KSM_CONNECTION_GROUP_ATTRIBUTES;
|
||||
}
|
||||
|
||||
}
|
@@ -23,21 +23,28 @@ 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.
|
||||
* A utility for parsing base64-encoded JSON, as output by the Keeper Commander
|
||||
* CLI tool via the "sm client add" command into a Keeper Secrets Manager
|
||||
* {@link KeyValueStorage} object.
|
||||
*/
|
||||
public abstract class KsmConfigProperty implements GuacamoleProperty<KeyValueStorage> {
|
||||
public class KsmConfig {
|
||||
|
||||
@Override
|
||||
public KeyValueStorage parseValue(String value) throws GuacamoleException {
|
||||
|
||||
// If no property provided, return null.
|
||||
if (value == null)
|
||||
return null;
|
||||
/**
|
||||
* Given a base64-encoded JSON KSM configuration, parse and return a
|
||||
* KeyValueStorage object.
|
||||
*
|
||||
* @param value
|
||||
* The base64-encoded JSON KSM configuration to parse.
|
||||
*
|
||||
* @return
|
||||
* The KeyValueStorage that is a result of the parsing operation
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the provided value is not valid base-64 encoded JSON KSM configuration.
|
||||
*/
|
||||
public static KeyValueStorage parseKsmConfig(String value) throws GuacamoleException {
|
||||
|
||||
// Parse base64 value as KSM config storage
|
||||
try {
|
@@ -21,10 +21,18 @@ package org.apache.guacamole.vault.ksm.conf;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleServerException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.properties.BooleanGuacamoleProperty;
|
||||
import org.apache.guacamole.properties.StringGuacamoleProperty;
|
||||
import org.apache.guacamole.vault.conf.VaultConfigurationService;
|
||||
|
||||
import com.keepersecurity.secretsManager.core.InMemoryStorage;
|
||||
import com.keepersecurity.secretsManager.core.KeyValueStorage;
|
||||
import com.keepersecurity.secretsManager.core.SecretsManagerOptions;
|
||||
|
||||
/**
|
||||
@@ -57,7 +65,7 @@ public class KsmConfigurationService extends VaultConfigurationService {
|
||||
* The base64-encoded configuration information generated by the Keeper
|
||||
* Commander CLI tool.
|
||||
*/
|
||||
private static final KsmConfigProperty KSM_CONFIG = new KsmConfigProperty() {
|
||||
private static final StringGuacamoleProperty KSM_CONFIG = new StringGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@@ -122,22 +130,68 @@ public class KsmConfigurationService extends VaultConfigurationService {
|
||||
return environment.getProperty(STRIP_WINDOWS_DOMAINS, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the globally-defined base-64-encoded JSON KSM configuration blob
|
||||
* as a string.
|
||||
*
|
||||
* @return
|
||||
* The globally-defined base-64-encoded JSON KSM configuration blob
|
||||
* as a string.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the value specified within guacamole.properties cannot be
|
||||
* parsed or does not exist.
|
||||
*/
|
||||
public String getKsmConfig() throws GuacamoleException {
|
||||
return environment.getRequiredProperty(KSM_CONFIG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a base64-encoded JSON KSM configuration, parse and return a
|
||||
* KeyValueStorage object.
|
||||
*
|
||||
* @param value
|
||||
* The base64-encoded JSON KSM configuration to parse.
|
||||
*
|
||||
* @return
|
||||
* The KeyValueStorage that is a result of the parsing operation
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the provided value is not valid base-64 encoded JSON KSM configuration.
|
||||
*/
|
||||
private static KeyValueStorage parseKsmConfig(String value) throws GuacamoleException {
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* CLI tool. This configuration data must be passed directly as an argument.
|
||||
*
|
||||
* @param ksmConfig
|
||||
* The KSM configuration blob to parse.
|
||||
*
|
||||
* @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.
|
||||
* If an invalid ksmConfig parameter is provided.
|
||||
*/
|
||||
public SecretsManagerOptions getSecretsManagerOptions() throws GuacamoleException {
|
||||
return new SecretsManagerOptions(environment.getRequiredProperty(KSM_CONFIG), null,
|
||||
getAllowUnverifiedCertificate());
|
||||
public SecretsManagerOptions getSecretsManagerOptions(@Nonnull String ksmConfig) throws GuacamoleException {
|
||||
|
||||
return new SecretsManagerOptions(
|
||||
parseKsmConfig(ksmConfig), null, getAllowUnverifiedCertificate());
|
||||
}
|
||||
}
|
||||
|
@@ -20,13 +20,16 @@
|
||||
package org.apache.guacamole.vault.ksm.secret;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import com.google.inject.assistedinject.AssistedInject;
|
||||
import com.keepersecurity.secretsManager.core.Hosts;
|
||||
import com.keepersecurity.secretsManager.core.KeeperRecord;
|
||||
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 com.keepersecurity.secretsManager.core.SecretsManagerOptions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -40,8 +43,8 @@ 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;
|
||||
|
||||
@@ -53,7 +56,6 @@ import org.slf4j.LoggerFactory;
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
@@ -61,12 +63,6 @@ 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.
|
||||
*/
|
||||
@@ -94,6 +90,11 @@ public class KsmClient {
|
||||
*/
|
||||
private static final long CACHE_INTERVAL = 5000;
|
||||
|
||||
/**
|
||||
* The KSM configuration associated with this client instance.
|
||||
*/
|
||||
private final SecretsManagerOptions ksmConfig;
|
||||
|
||||
/**
|
||||
* Read/write lock which guards access to all cached data, including the
|
||||
* timestamp recording the last time the cache was refreshed. Readers of
|
||||
@@ -178,6 +179,17 @@ public class KsmClient {
|
||||
*/
|
||||
private final Set<String> cachedAmbiguousUsernames = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Create a new KSM client based around the provided KSM configuration.
|
||||
*
|
||||
* @param ksmConfig
|
||||
* The KSM configuration to use when retrieving properties from KSM.
|
||||
*/
|
||||
@AssistedInject
|
||||
public KsmClient(@Assisted SecretsManagerOptions ksmConfig) {
|
||||
this.ksmConfig = ksmConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that all cached data is current with respect to
|
||||
* {@link #CACHE_INTERVAL}, refreshing data from the server as needed.
|
||||
@@ -210,7 +222,7 @@ public class KsmClient {
|
||||
|
||||
// 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());
|
||||
KeeperSecrets secrets = SecretsManager.getSecrets(ksmConfig);
|
||||
List<KeeperRecord> records = secrets.getRecords();
|
||||
|
||||
// Store all secrets within cache
|
||||
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 javax.annotation.Nonnull;
|
||||
|
||||
import com.keepersecurity.secretsManager.core.SecretsManagerOptions;
|
||||
|
||||
/**
|
||||
* Factory for creating KsmClient instances.
|
||||
*/
|
||||
public interface KsmClientFactory {
|
||||
|
||||
/**
|
||||
* Returns a new instance of a KsmClient instance associated with
|
||||
* the provided KSM configuration options.
|
||||
*
|
||||
* @param ksmConfigOptions
|
||||
* The KSM config options to use when constructing the KsmClient
|
||||
* object.
|
||||
*
|
||||
* @return
|
||||
* A new KsmClient instance associated with the provided KSM config
|
||||
* options.
|
||||
*/
|
||||
KsmClient create(@Nonnull SecretsManagerOptions ksmConfigOptions);
|
||||
|
||||
}
|
@@ -22,31 +22,47 @@ package org.apache.guacamole.vault.ksm.secret;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import com.keepersecurity.secretsManager.core.KeeperRecord;
|
||||
import com.keepersecurity.secretsManager.core.SecretsManagerOptions;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.Connectable;
|
||||
import org.apache.guacamole.net.auth.Connection;
|
||||
import org.apache.guacamole.net.auth.ConnectionGroup;
|
||||
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.conf.KsmAttributeService;
|
||||
import org.apache.guacamole.vault.ksm.conf.KsmConfigurationService;
|
||||
import org.apache.guacamole.vault.secret.VaultSecretService;
|
||||
import org.apache.guacamole.vault.secret.WindowsUsername;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Service which retrieves secrets from Keeper Secrets Manager.
|
||||
* The configuration used to connect to KSM can be set at a global
|
||||
* level using guacamole.properties, or using a connection group
|
||||
* attribute.
|
||||
*/
|
||||
@Singleton
|
||||
public class KsmSecretService implements VaultSecretService {
|
||||
|
||||
/**
|
||||
* Client for retrieving records and secrets from Keeper Secrets Manager.
|
||||
* Logger for this class.
|
||||
*/
|
||||
@Inject
|
||||
private KsmClient ksm;
|
||||
private static final Logger logger = LoggerFactory.getLogger(VaultSecretService.class);
|
||||
|
||||
/**
|
||||
* Service for retrieving data from records.
|
||||
@@ -60,6 +76,51 @@ public class KsmSecretService implements VaultSecretService {
|
||||
@Inject
|
||||
private KsmConfigurationService confService;
|
||||
|
||||
/**
|
||||
* Factory for creating KSM client instances.
|
||||
*/
|
||||
@Inject
|
||||
private KsmClientFactory ksmClientFactory;
|
||||
|
||||
/**
|
||||
* A map of base-64 encoded JSON KSM config blobs to associated KSM client instances.
|
||||
* A distinct KSM client will exist for every KSM config.
|
||||
*/
|
||||
private final ConcurrentMap<String, KsmClient> ksmClientMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Create and return a KSM client for the provided KSM config if not already
|
||||
* present in the client map, otherwise return the existing client entry.
|
||||
*
|
||||
* @param ksmConfig
|
||||
* The base-64 encoded JSON KSM config blob associated with the client entry.
|
||||
* If an associated entry does not already exist, it will be created using
|
||||
* this configuration.
|
||||
*
|
||||
* @return
|
||||
* A KSM client for the provided KSM config if not already present in the
|
||||
* client map, otherwise the existing client entry.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while creating the KSM client.
|
||||
*/
|
||||
private KsmClient getClient(@Nonnull String ksmConfig)
|
||||
throws GuacamoleException {
|
||||
|
||||
// If a client already exists for the provided config, use it
|
||||
KsmClient ksmClient = ksmClientMap.get(ksmConfig);
|
||||
if (ksmClient != null)
|
||||
return ksmClient;
|
||||
|
||||
// Create and store a new KSM client instance for the provided KSM config blob
|
||||
SecretsManagerOptions options = confService.getSecretsManagerOptions(ksmConfig);
|
||||
ksmClient = ksmClientFactory.create(options);
|
||||
KsmClient prevClient = ksmClientMap.putIfAbsent(ksmConfig, ksmClient);
|
||||
|
||||
// If the client was already set before this thread got there, use the existing one
|
||||
return prevClient != null ? prevClient : ksmClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String canonicalize(String nameComponent) {
|
||||
try {
|
||||
@@ -74,9 +135,21 @@ public class KsmSecretService implements VaultSecretService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<String> getValue(UserContext userContext, Connectable connectable,
|
||||
String name) throws GuacamoleException {
|
||||
|
||||
// Attempt to find a KSM config for this connection or group
|
||||
String ksmConfig = getConnectionGroupKsmConfig(userContext, connectable);
|
||||
|
||||
return getClient(ksmConfig).getSecret(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<String> getValue(String name) throws GuacamoleException {
|
||||
return ksm.getSecret(name);
|
||||
|
||||
// Use the default KSM configuration from guacamole.properties
|
||||
return getClient(confService.getKsmConfig()).getSecret(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,13 +226,82 @@ public class KsmSecretService implements VaultSecretService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a KSM configuration attribute, recursing up the connection group tree
|
||||
* until a connection group with the appropriate attribute is found. If the KSM config
|
||||
* is found, it will be returned. If not, the default value from the config file will
|
||||
* be returned.
|
||||
*
|
||||
* @param userContext
|
||||
* The userContext associated with the connection or connection group.
|
||||
*
|
||||
* @param connectable
|
||||
* A connection or connection group for which the tokens are being replaced.
|
||||
*
|
||||
* @return
|
||||
* The value of the KSM configuration attribute if found in the tree, the default
|
||||
* KSM config blob defined in guacamole.properties otherwise.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while attempting to retrieve the KSM config attribute, or if
|
||||
* no KSM config is found in the connection group tree, and the value is also not
|
||||
* defined in the config file.
|
||||
*/
|
||||
private String getConnectionGroupKsmConfig(
|
||||
UserContext userContext, Connectable connectable) throws GuacamoleException {
|
||||
|
||||
// Check to make sure it's a usable type before proceeding
|
||||
if (
|
||||
!(connectable instanceof Connection)
|
||||
&& !(connectable instanceof ConnectionGroup)) {
|
||||
logger.warn(
|
||||
"Unsupported Connectable type: {}; skipping KSM config lookup.",
|
||||
connectable.getClass());
|
||||
|
||||
// Use the default value if searching is impossible
|
||||
return confService.getKsmConfig();
|
||||
}
|
||||
|
||||
// For connections, start searching the parent group for the KSM config
|
||||
// For connection groups, start searching the group directly
|
||||
String parentIdentifier = (connectable instanceof Connection)
|
||||
? ((Connection) connectable).getParentIdentifier()
|
||||
: ((ConnectionGroup) connectable).getIdentifier();
|
||||
|
||||
Directory<ConnectionGroup> connectionGroupDirectory = userContext.getConnectionGroupDirectory();
|
||||
while (true) {
|
||||
|
||||
// Fetch the parent group, if one exists
|
||||
ConnectionGroup group = connectionGroupDirectory.get(parentIdentifier);
|
||||
if (group == null)
|
||||
break;
|
||||
|
||||
// If the current connection group has the KSM configuration attribute, return immediately
|
||||
String ksmConfig = group.getAttributes().get(KsmAttributeService.KSM_CONFIGURATION_ATTRIBUTE);
|
||||
if (ksmConfig != null)
|
||||
return ksmConfig;
|
||||
|
||||
// Otherwise, keep searching up the tree until an appropriate configuration is found
|
||||
parentIdentifier = group.getParentIdentifier();
|
||||
}
|
||||
|
||||
// If no KSM configuration was ever found, use the default value
|
||||
return confService.getKsmConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Future<String>> getTokens(GuacamoleConfiguration config,
|
||||
TokenFilter filter) throws GuacamoleException {
|
||||
public Map<String, Future<String>> getTokens(UserContext userContext, Connectable connectable,
|
||||
GuacamoleConfiguration config, TokenFilter filter) throws GuacamoleException {
|
||||
|
||||
Map<String, Future<String>> tokens = new HashMap<>();
|
||||
Map<String, String> parameters = config.getParameters();
|
||||
|
||||
// Attempt to find a KSM config for this connection or group
|
||||
String ksmConfig = getConnectionGroupKsmConfig(userContext, connectable);
|
||||
|
||||
// Get a client instance for this KSM config
|
||||
KsmClient ksm = getClient(ksmConfig);
|
||||
|
||||
// Retrieve and define server-specific tokens, if any
|
||||
String hostname = parameters.get("hostname");
|
||||
if (hostname != null && !hostname.isEmpty())
|
||||
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_KEEPER_SECRETS_MANAGER" : {
|
||||
"NAME" : "Keeper Secrets Manager"
|
||||
},
|
||||
|
||||
"CONNECTION_GROUP_ATTRIBUTES" : {
|
||||
"SECTION_HEADER_KSM_CONFIG" : "Keeper Secrets Manager",
|
||||
"FIELD_HEADER_KSM_CONFIG" : "KSM Service Configuration "
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user