GUACAMOLE-641: Rename guacamole-auth-vault to guacamole-vault (does not provide auth).

This commit is contained in:
Michael Jumper
2022-01-21 15:23:41 -08:00
parent 2df24bf911
commit f99b3a3213
25 changed files with 53 additions and 52 deletions

View File

@@ -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;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.vault.user.VaultUserContextFactory;
/**
* AuthenticationProvider implementation which automatically injects tokens
* containing the values of secrets retrieved from a vault.
*/
public abstract class VaultAuthenticationProvider
extends AbstractAuthenticationProvider {
/**
* Factory for creating instances of the relevant vault-specific
* UserContext implementation.
*/
private final VaultUserContextFactory userContextFactory;
/**
* Creates a new VaultAuthenticationProvider which uses the given module to
* configure dependency injection.
*
* @param module
* The module to use to configure dependency injection.
*/
protected VaultAuthenticationProvider(VaultAuthenticationProviderModule module) {
Injector injector = Guice.createInjector(module);
this.userContextFactory = injector.getInstance(VaultUserContextFactory.class);
}
@Override
public UserContext decorate(UserContext context,
AuthenticatedUser authenticatedUser, Credentials credentials)
throws GuacamoleException {
return userContextFactory.create(context);
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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;
import com.google.inject.AbstractModule;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.vault.user.VaultUserContext;
import org.apache.guacamole.vault.user.VaultUserContextFactory;
/**
* Guice module which configures injections specific to the base support for
* key vaults. When adding support for a key vault provider, a subclass
* specific to that vault implementation will need to be created.
*
* @see AzureKeyVaultAuthenticationProviderModule
*/
public abstract class VaultAuthenticationProviderModule extends AbstractModule {
/**
* Guacamole server environment.
*/
private final Environment environment;
/**
* Creates a new VaultAuthenticationProviderModule which configures
* dependency injection for the Azure Key Vault authentication provider.
*
* @throws GuacamoleException
* If an error occurs while retrieving the Guacamole server
* environment.
*/
public VaultAuthenticationProviderModule() throws GuacamoleException {
this.environment = LocalEnvironment.getInstance();
}
/**
* Configures injections for interfaces which are implementation-specific
* to the vault service in use. Subclasses MUST provide a version of this
* function which binds concrete implementations to the following
* interfaces:
*
* - VaultConfigurationService
* - VaultSecretService
*
* @see AzureKeyVaultAuthenticationProviderModule
*/
protected abstract void configureVault();
/**
* Returns the instance of the Guacamole server environment which will be
* exposed to other classes via dependency injection.
*
* @return
* The instance of the Guacamole server environment which will be
* exposed via dependency injection.
*/
protected Environment getEnvironment() {
return environment;
}
@Override
protected void configure() {
// Bind Guacamole server environment
bind(Environment.class).toInstance(environment);
// Bind factory for creating UserContexts
install(new FactoryModuleBuilder()
.implement(UserContext.class, VaultUserContext.class)
.build(VaultUserContextFactory.class));
// Bind all other implementation-specific interfaces
configureVault();
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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 com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.vault.VaultAuthenticationProviderModule;
/**
* Base class for services which retrieve key vault configuration information.
* A concrete implementation of this class must be defined and bound for key
* vault support to work.
*
* @see VaultAuthenticationProviderModule
*/
public abstract class VaultConfigurationService {
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* ObjectMapper for deserializing JSON.
*/
private static final ObjectMapper mapper = new ObjectMapper();
/**
* The name of the file containing a JSON mapping of Guacamole parameter
* token to vault secret name.
*/
private final String tokenMappingFilename;
/**
* Creates a new VaultConfigurationService which retrieves the token/secret
* mapping from a JSON file having the given name.
*
* @param tokenMappingFilename
* The name of the JSON file containing the token/secret mapping.
*/
protected VaultConfigurationService(String tokenMappingFilename) {
this.tokenMappingFilename = tokenMappingFilename;
}
/**
* Returns a mapping dictating the name of the secret which maps to each
* parameter token. In the returned mapping, the value of each entry is the
* name of the secret to use to populate the value of the parameter token,
* and the key of each entry is the name of the parameter token which
* should receive the value of the secret.
*
* The name of the secret may contain its own tokens, which will be
* substituted using values from the given filter. See the definition of
* VaultUserContext for the names of these tokens and the contexts in which
* they can be applied to secret names.
*
* @return
* A mapping dictating the name of the secret which maps to each
* parameter token.
*
* @throws GuacamoleException
* If the JSON file defining the token/secret mapping cannot be read.
*/
public Map<String, String> getTokenMapping() throws GuacamoleException {
// Get configuration file from GUACAMOLE_HOME
File confFile = new File(environment.getGuacamoleHome(), tokenMappingFilename);
// Deserialize token mapping from JSON
try {
return mapper.readValue(confFile, new TypeReference<Map<String, String>>() {});
}
// Fail if JSON is invalid/unreadable
catch (IOException e) {
throw new GuacamoleServerException("Unable to read token mapping "
+ "configuration file \"" + tokenMappingFilename + "\".", e);
}
}
}

View File

@@ -0,0 +1,200 @@
/*
* 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.secret;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Caching implementation of VaultSecretService. Requests for the values of
* secrets will automatically be cached for a duration determined by the
* implementation. Subclasses must implement refreshCachedSecret() to provide
* a mechanism for CachedVaultSecretService to explicitly retrieve a value
* which is missing from the cache or has expired.
*/
public abstract class CachedVaultSecretService implements VaultSecretService {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(CachedVaultSecretService.class);
/**
* The cached value of a secret.
*/
protected class CachedSecret {
/**
* A Future which contains or will contain the value of the secret at
* the time it was last retrieved.
*/
private final Future<String> value;
/**
* The time the value should be considered out-of-date, in milliseconds
* since midnight of January 1, 1970 UTC.
*/
private final long expires;
/**
* Creates a new CachedSecret which represents a cached snapshot of the
* value of a secret. Each CachedSecret has a limited lifespan after
* which it should be considered out-of-date.
*
* @param value
* A Future which contains or will contain the current value of the
* secret. If no such secret exists, the given Future should
* complete with null.
*
* @param ttl
* The maximum number of milliseconds that this value should be
* cached.
*/
public CachedSecret(Future<String> value, int ttl) {
this.value = value;
this.expires = System.currentTimeMillis() + ttl;
}
/**
* Returns the value of the secret at the time it was last retrieved.
* The actual value of the secret may have changed.
*
* @return
* A Future which will eventually complete with the value of the
* secret at the time it was last retrieved. If no such secret
* exists, the Future will be completed with null. If an error
* occurs 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.
*/
public Future<String> getValue() {
return value;
}
/**
* Returns whether this specific cached value has expired. Expired
* values will be automatically refreshed by CachedVaultSecretService.
*
* @return
* true if this cached value has expired, false otherwise.
*/
public boolean isExpired() {
return System.currentTimeMillis() >= expires;
}
}
/**
* Cache of past requests to retrieve secrets. Expired secrets are lazily
* removed.
*/
private final ConcurrentHashMap<String, Future<CachedSecret>> cache = new ConcurrentHashMap<>();
/**
* Explicitly retrieves the value of the secret having the given name,
* returning a result that can be cached. The length of time that this
* specific value will be cached is determined by the TTL value provided to
* the returned CachedSecret. This function will be automatically invoked
* in response to calls to getValue() when the requested secret is either
* not cached or has expired. Expired secrets are not removed from the
* cache until another request is made for that secret.
*
* @param name
* The name of the secret to retrieve.
*
* @return
* A CachedSecret which defines the current value of the secret and the
* point in time that value should be considered potentially
* out-of-date.
*
* @throws GuacamoleException
* If an error occurs while retrieving the secret from the vault.
*/
protected abstract CachedSecret refreshCachedSecret(String name)
throws GuacamoleException;
@Override
public Future<String> getValue(String name) throws GuacamoleException {
CompletableFuture<CachedSecret> refreshEntry;
try {
// Attempt to use cached result of previous call
Future<CachedSecret> cachedEntry = cache.get(name);
if (cachedEntry != null) {
// Use cached result if not yet expired
CachedSecret secret = cachedEntry.get();
if (!secret.isExpired()) {
logger.debug("Using cached secret for \"{}\".", name);
return secret.getValue();
}
// Evict if expired
else {
logger.debug("Cached secret for \"{}\" is expired.", name);
cache.remove(name, cachedEntry);
}
}
// If no cached result, or result is too old, race with other
// threads to be the thread which refreshes the entry
refreshEntry = new CompletableFuture<>();
cachedEntry = cache.putIfAbsent(name, refreshEntry);
// If a refresh operation is already in progress, wait for that
// operation to complete and use its value
if (cachedEntry != null)
return cachedEntry.get().getValue();
}
catch (InterruptedException | ExecutionException e) {
throw new GuacamoleServerException("Attempt to retrieve secret "
+ "failed.", e);
}
// If we reach this far, the cache entry is stale or missing, and it's
// this thread's responsibility to refresh the entry
try {
CachedSecret secret = refreshCachedSecret(name);
refreshEntry.complete(secret);
logger.debug("Cached secret for \"{}\" will be refreshed.", name);
return secret.getValue();
}
// Abort the refresh operation if an error occurs
catch (Error | RuntimeException | GuacamoleException e) {
refreshEntry.completeExceptionally(e);
cache.remove(name, refreshEntry);
logger.debug("Cached secret for \"{}\" could not be refreshed.", name);
throw e;
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.secret;
import java.util.concurrent.Future;
import org.apache.guacamole.GuacamoleException;
/**
* Generic service for retrieving the value of a secret stored in a vault.
*/
public interface VaultSecretService {
/**
* Translates an arbitrary string, which may contain characters not allowed
* by the vault implementation, into a string which is a valid secret name.
* The type of transformation performed on the string, if any, will depend
* on the specific requirements of the vault provider.
*
* NOTE: It is critical that this transformation is deterministic and
* reasonably predictable for users. If an implementation must apply a
* transformation to secret names, that transformation needs to be
* documented.
*
* @param name
* An arbitrary string intended for use as a secret name, but which may
* contain characters not allowed by the vault implementation.
*
* @return
* A name containing essentially the same content as the provided
* string, but transformed deterministically such that it is acceptable
* as a secret name by the vault provider.
*/
String canonicalize(String name);
/**
* 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.
*
* @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(String name) throws GuacamoleException;
}

View File

@@ -0,0 +1,329 @@
/*
* 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.user;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.ConnectionGroup;
import org.apache.guacamole.net.auth.TokenInjectingUserContext;
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.VaultConfigurationService;
import org.apache.guacamole.vault.secret.VaultSecretService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* UserContext implementation which automatically injects tokens containing the
* values of secrets retrieved from a vault.
*/
public class VaultUserContext extends TokenInjectingUserContext {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(VaultUserContext.class);
/**
* The name of the token which will be replaced with the username of the
* current user if specified within the name of a secret. This token
* applies to both connections and connection groups.
*/
private static final String USERNAME_TOKEN = "GUAC_USERNAME";
/**
* The name of the token which will be replaced with the name of the
* current connection group if specified within the name of a secret. This
* token only applies only to connection groups.
*/
private static final String CONNECTION_GROUP_NAME_TOKEN = "CONNECTION_GROUP_NAME";
/**
* The name of the token which will be replaced with the identifier of the
* current connection group if specified within the name of a secret. This
* token only applies only to connection groups.
*/
private static final String CONNECTION_GROUP_IDENTIFIER_TOKEN = "CONNECTION_GROUP_ID";
/**
* The name of the token which will be replaced with the \"hostname\"
* connection parameter of the current connection if specified within the
* name of a secret. This token only applies only to connections.
*/
private static final String CONNECTION_HOSTNAME_TOKEN = "CONNECTION_HOSTNAME";
/**
* The name of the token which will be replaced with the \"username\"
* connection parameter of the current connection if specified within the
* name of a secret. This token only applies only to connections.
*/
private static final String CONNECTION_USERNAME_TOKEN = "CONNECTION_USERNAME";
/**
* The name of the token which will be replaced with the name of the
* current connection if specified within the name of a secret. This token
* only applies only to connections.
*/
private static final String CONNECTION_NAME_TOKEN = "CONNECTION_NAME";
/**
* The name of the token which will be replaced with the identifier of the
* current connection if specified within the name of a secret. This token
* only applies only to connections.
*/
private static final String CONNECTION_IDENTIFIER_TOKEN = "CONNECTION_ID";
/**
* Service for retrieving configuration information.
*/
@Inject
private VaultConfigurationService confService;
/**
* Service for retrieving the values of secrets stored in a vault.
*/
@Inject
private VaultSecretService secretService;
/**
* Creates a new VaultUserContext which automatically injects tokens
* containing values of secrets retrieved from a vault. The given
* UserContext is decorated such that connections and connection groups
* will receive additional tokens during the connection process.
*
* Note that this class depends on concrete implementations of the
* following classes to be provided via dependency injection:
*
* - VaultConfigurationService
* - VaultSecretService
*
* Bindings providing these concrete implementations will need to be
* provided by subclasses of VaultAuthenticationProviderModule for each
* supported vault.
*
* @param userContext
* The UserContext instance to decorate.
*/
@AssistedInject
public VaultUserContext(@Assisted UserContext userContext) {
super(userContext);
}
/**
* Creates a new TokenFilter instance with token values set for all tokens
* which are not specific to connections or connection groups. Currently,
* this is only the username token ("GUAC_USERNAME").
*
* @return
* A new TokenFilter instance with token values set for all tokens
* which are not specific to connections or connection groups.
*/
private TokenFilter createFilter() {
TokenFilter filter = new TokenFilter();
filter.setToken(USERNAME_TOKEN, self().getIdentifier());
return filter;
}
/**
* Initiates asynchronous retrieval of all applicable tokens and
* corresponding values from the vault, using the given TokenFilter to
* filter tokens within the secret names prior to retrieving those secrets.
*
* @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
* and the value is the name of the secret. The name of the secret
* may contain its own tokens, which will be substituted using values
* from the given filter.
*
* @param filter
* The filter to use to substitute values for tokens in the names of
* secrets to be retrieved from the vault.
*
* @return
* A Map of token name to Future, where each Future represents the
* pending retrieval operation which will ultimately be completed with
* the value of all secrets mapped to that token.
*
* @throws GuacamoleException
* 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,
TokenFilter filter) throws GuacamoleException {
// Populate map with pending secret retrieval operations corresponding
// to each mapped token
Map<String, Future<String>> pendingTokens = new HashMap<>(tokenMapping.size());
for (Map.Entry<String, String> entry : tokenMapping.entrySet()) {
// Translate secret pattern into secret name, ignoring any
// secrets which cannot be translated
String secretName;
try {
secretName = secretService.canonicalize(filter.filterStrict(entry.getValue()));
}
catch (GuacamoleTokenUndefinedException e) {
logger.debug("Secret for token \"{}\" will not be retrieved. "
+ "Token \"{}\" within mapped secret name has no "
+ "defined value in the current context.",
entry.getKey(), e.getTokenName());
continue;
}
// Initiate asynchronous retrieval of the token value
String tokenName = entry.getKey();
Future<String> secret = secretService.getValue(secretName);
pendingTokens.put(tokenName, secret);
}
return pendingTokens;
}
/**
* Waits for all pending secret retrieval operations to complete,
* transforming each Future within the given Map into its contained String
* value.
*
* @param pendingTokens
* A Map of token name to Future, where each Future represents the
* pending retrieval operation which will ultimately be completed with
* the value of all secrets mapped to that token.
*
* @return
* A Map of token name to the corresponding String value retrieved for
* that token from the vault.
*
* @throws GuacamoleException
* If the value for any applicable secret cannot be retrieved from the
* vault due to an error.
*/
private Map<String, String> resolve(Map<String,
Future<String>> pendingTokens) throws GuacamoleException {
// Populate map with tokens containing the values of their
// corresponding secrets
Map<String, String> tokens = new HashMap<>(pendingTokens.size());
for (Map.Entry<String, Future<String>> entry : pendingTokens.entrySet()) {
// Complete secret retrieval operation, blocking if necessary
String secretValue;
try {
secretValue = entry.getValue().get();
}
catch (InterruptedException | ExecutionException e) {
throw new GuacamoleServerException("Retrieval of secret value "
+ "failed.", e);
}
// If a value is defined for the secret in question, store that
// value under the mapped token
String tokenName = entry.getKey();
if (secretValue != null) {
tokens.put(tokenName, secretValue);
logger.debug("Token \"{}\" populated with value from "
+ "secret.", tokenName);
}
else
logger.debug("Token \"{}\" not populated. Mapped "
+ "secret has no value.", tokenName);
}
return tokens;
}
@Override
protected Map<String, String> getTokens(ConnectionGroup connectionGroup)
throws GuacamoleException {
String name = connectionGroup.getName();
String identifier = connectionGroup.getIdentifier();
logger.debug("Injecting tokens from vault for connection group "
+ "\"{}\" (\"{}\").", identifier, name);
// Add general and connection-group-specific tokens
TokenFilter filter = createFilter();
filter.setToken(CONNECTION_GROUP_NAME_TOKEN, name);
filter.setToken(CONNECTION_GROUP_IDENTIFIER_TOKEN, identifier);
// Substitute tokens producing secret names, retrieving and storing
// those secrets as parameter tokens
return resolve(getTokens(confService.getTokenMapping(), filter));
}
@Override
protected Map<String, String> getTokens(Connection connection)
throws GuacamoleException {
String name = connection.getName();
String identifier = connection.getIdentifier();
logger.debug("Injecting tokens from vault for connection \"{}\" "
+ "(\"{}\").", identifier, name);
// Add general and connection-specific tokens
TokenFilter filter = createFilter();
filter.setToken(CONNECTION_NAME_TOKEN, connection.getName());
filter.setToken(CONNECTION_IDENTIFIER_TOKEN, identifier);
// Add hostname and username tokens if available (implementations are
// not required to expose connection configuration details)
GuacamoleConfiguration config = connection.getConfiguration();
String hostname = config.getParameter("hostname");
if (hostname != null)
filter.setToken(CONNECTION_HOSTNAME_TOKEN, hostname);
else
logger.debug("Hostname for connection \"{}\" (\"{}\") not "
+ "available. \"{}\" token will not be populated in "
+ "secret names.", identifier, name,
CONNECTION_HOSTNAME_TOKEN);
String username = config.getParameter("username");
if (username != null)
filter.setToken(CONNECTION_USERNAME_TOKEN, username);
else
logger.debug("Username for connection \"{}\" (\"{}\") not "
+ "available. \"{}\" token will not be populated in "
+ "secret names.", identifier, name,
CONNECTION_USERNAME_TOKEN);
// Substitute tokens producing secret names, retrieving and storing
// those secrets as parameter tokens
return resolve(getTokens(confService.getTokenMapping(), filter));
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.user;
import org.apache.guacamole.net.auth.UserContext;
/**
* Factory for creating UserContext instances which automatically inject tokens
* containing the values of secrets retrieved from a vault.
*/
public interface VaultUserContextFactory {
/**
* Returns a new instance of a UserContext implementation which
* automatically injects tokens containing values of secrets retrieved from
* a vault. The given UserContext is decorated such that connections and
* connection groups will receive additional tokens during the connection
* process.
*
* @param userContext
* The UserContext instance to decorate.
*
* @return
* A new UserContext instance which automatically injects tokens
* containing values of secrets retrieved from a vault.
*/
UserContext create(UserContext userContext);
}

View File

@@ -0,0 +1,7 @@
{
"DATA_SOURCE_AZURE_KEYVAULT" : {
"NAME" : "Azure Key Vault"
}
}