GUACAMOLE-641: Automatically pull Guacamole properties from vault.

This commit is contained in:
Michael Jumper
2022-01-21 15:23:41 -08:00
parent 46501f4b63
commit 86d1de5f2c
4 changed files with 118 additions and 11 deletions

View File

@@ -47,6 +47,13 @@ public class AzureKeyVaultConfigurationService extends VaultConfigurationService
*/
private static final String TOKEN_MAPPING_FILENAME = "azure-keyvault-token-mapping.yml";
/**
* The name of the properties file containing Guacamole configuration
* properties whose values are the names of corresponding secrets within
* Azure Key Vault.
*/
private static final String PROPERTIES_FILENAME = "guacamole.properties.azure";
/**
* The number of milliseconds that each retrieved secret should be cached
* for.
@@ -101,12 +108,15 @@ public class AzureKeyVaultConfigurationService extends VaultConfigurationService
/**
* Creates a new AzureKeyVaultConfigurationService which reads the token
* mapping from "azure-keyvault-token-mapping.yml". The token mapping is a
* YAML file which lists each connection parameter token and the name of
* the secret from which the value for that token should be read.
* mapping from "azure-keyvault-token-mapping.yml" and properties from
* "guacamole.properties.azure". The token mapping is a YAML file which
* lists each connection parameter token and the name of the secret from
* which the value for that token should be read, while the properties
* file is an alternative to guacamole.properties where each property
* value is the name of a secret containing the actual value.
*/
public AzureKeyVaultConfigurationService() {
super(TOKEN_MAPPING_FILENAME);
super(TOKEN_MAPPING_FILENAME, PROPERTIES_FILENAME);
}
/**

View File

@@ -22,10 +22,12 @@ package org.apache.guacamole.vault;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
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.conf.VaultConfigurationService;
import org.apache.guacamole.vault.user.VaultUserContextFactory;
/**
@@ -47,10 +49,22 @@ public abstract class VaultAuthenticationProvider
*
* @param module
* The module to use to configure dependency injection.
*
* @throws GuacamoleException
* If the properties file containing vault-mapped Guacamole
* configuration properties exists but cannot be read.
*/
protected VaultAuthenticationProvider(VaultAuthenticationProviderModule module) {
protected VaultAuthenticationProvider(VaultAuthenticationProviderModule module)
throws GuacamoleException {
Injector injector = Guice.createInjector(module);
this.userContextFactory = injector.getInstance(VaultUserContextFactory.class);
// Automatically pull properties from vault
Environment environment = injector.getInstance(Environment.class);
VaultConfigurationService confService = injector.getInstance(VaultConfigurationService.class);
environment.addGuacamoleProperties(confService.getProperties());
}
@Override

View File

@@ -27,10 +27,16 @@ import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.properties.FileGuacamoleProperties;
import org.apache.guacamole.properties.GuacamoleProperties;
import org.apache.guacamole.properties.PropertiesGuacamoleProperties;
import org.apache.guacamole.vault.VaultAuthenticationProviderModule;
import org.apache.guacamole.vault.secret.VaultSecretService;
/**
* Base class for services which retrieve key vault configuration information.
@@ -47,6 +53,9 @@ public abstract class VaultConfigurationService {
@Inject
private Environment environment;
@Inject
private VaultSecretService secretService;
/**
* ObjectMapper for deserializing YAML.
*/
@@ -58,15 +67,30 @@ public abstract class VaultConfigurationService {
*/
private final String tokenMappingFilename;
/**
* The name of the properties file containing Guacamole configuration
* properties. Unlike guacamole.properties, the values of these properties
* are read from the vault. Each property is expected to contain a secret
* name instead of a property value.
*/
private final String propertiesFilename;
/**
* Creates a new VaultConfigurationService which retrieves the token/secret
* mapping from a YAML file having the given name.
* mappings and Guacamole configuration properties from the files with the
* given names.
*
* @param tokenMappingFilename
* The name of the YAML file containing the token/secret mapping.
*
* @param propertiesFilename
* The name of the properties file containing Guacamole configuration
* properties whose values are the names of corresponding secrets.
*/
protected VaultConfigurationService(String tokenMappingFilename) {
protected VaultConfigurationService(String tokenMappingFilename,
String propertiesFilename) {
this.tokenMappingFilename = tokenMappingFilename;
this.propertiesFilename = propertiesFilename;
}
/**
@@ -114,4 +138,53 @@ public abstract class VaultConfigurationService {
}
/**
* Returns a GuacamoleProperties instance which automatically reads the
* values of requested properties from the vault. The name of the secret
* corresponding to a property stored in the vault is defined via the
* properties filename supplied at construction time.
*
* @return
* A GuacamoleProperties instance which automatically reads property
* values from the vault.
*
* @throws GuacamoleException
* If the properties file containing the property/secret mappings
* exists but cannot be read.
*/
public GuacamoleProperties getProperties() throws GuacamoleException {
// Use empty properties if file cannot be found
File propFile = new File(environment.getGuacamoleHome(), propertiesFilename);
if (!propFile.exists())
return new PropertiesGuacamoleProperties(new Properties());
// Automatically pull properties from vault
return new FileGuacamoleProperties(propFile) {
@Override
public String getProperty(String name) throws GuacamoleException {
try {
String secretName = super.getProperty(name);
if (secretName == null)
return null;
return secretService.getValue(secretName).get();
}
catch (InterruptedException | ExecutionException e) {
if (e.getCause() instanceof GuacamoleException)
throw (GuacamoleException) e;
throw new GuacamoleServerException(String.format("Property "
+ "\"%s\" could not be retrieved from the vault.", name), e);
}
}
};
}
}

View File

@@ -46,6 +46,13 @@ public class KsmConfigurationService extends VaultConfigurationService {
*/
private static final String TOKEN_MAPPING_FILENAME = "ksm-token-mapping.yml";
/**
* The name of the properties file containing Guacamole configuration
* properties whose values are the names of corresponding secrets within
* Keeper Secrets Manager.
*/
private static final String PROPERTIES_FILENAME = "guacamole.properties.ksm";
/**
* The base64-encoded configuration information generated by the Keeper
* Commander CLI tool.
@@ -71,12 +78,15 @@ public class KsmConfigurationService extends VaultConfigurationService {
/**
* 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.
* from "ksm-token-mapping.yml" and properties from
* "guacamole.properties.ksm". The token mapping is a YAML file which lists
* each connection parameter token and the name of the secret from which
* the value for that token should be read, while the properties file is an
* alternative to guacamole.properties where each property value is the
* name of a secret containing the actual value.
*/
public KsmConfigurationService() {
super(TOKEN_MAPPING_FILENAME);
super(TOKEN_MAPPING_FILENAME, PROPERTIES_FILENAME);
}
/**