| <?xml version="1.0" encoding="UTF-8"?> |
| |
| <chapter xml:id="custom-auth" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en" |
| xmlns:xi="http://www.w3.org/2001/XInclude"> |
| <title>Custom authentication</title> |
| <indexterm xmlns:xl="http://www.w3.org/1999/xlink"> |
| <primary>authentication</primary> |
| <secondary>custom</secondary> |
| </indexterm> |
| <para>Guacamole's authentication layer is designed to be extendable such that users can |
| integrate Guacamole into existing authentication systems without having to resort to writing |
| their own web application around the Guacamole API.</para> |
| <para>The web application comes with a default authentication mechanism which uses an XML file |
| to associate users with connections. Extensions for Guacamole that provide LDAP-based |
| authentication or database-based authentication have also been developed.</para> |
| <para>To demonstrate the principles involved, we will implement a very simple authentication |
| extension which associates a single user/password pair with a single connection, with all |
| this information saved in properties inside the <filename>guacamole.properties</filename> |
| file.</para> |
| <para>In general, all other authentication extensions for Guacamole will use the principles |
| demonstrated here. This tutorial demonstrates the simplest way to create an authentication |
| extension for Guacamole - an authentication extension that does not support management of |
| users and connections via the web interface.</para> |
| <section xml:id="custom-auth-model"> |
| <title>Guacamole's authentication model</title> |
| <para>When you view any page in Guacamole, whether that be the login screen or the client |
| interface, the page makes an authentication attempt with the web application, sending |
| all available credentials. After entering your username and password, the exact same |
| process occurs, except the web application receives the username and password as |
| well.</para> |
| <para>The web application handles this authentication attempt by collecting all credentials |
| available and passing them to designated classes called "authentication providers". |
| Given the set of credentials, authentication providers return a context object that |
| provides restricted access to other users and connections, if any.</para> |
| </section> |
| <section xml:id="custom-auth-skeleton"> |
| <title>A Guacamole extension skeleton</title> |
| <para>For simplicity's sake, and because this is how things are done upstream in the |
| Guacamole project, we will use Maven to build our extension.</para> |
| <para>The bare minimum required for a Guacamole authentication extension is a |
| <filename>pom.xml</filename> file listing guacamole-ext as a dependency, a single |
| .java file implementing our stub of an authentication provider, and a |
| <filename>guac-manifest.json</filename> file describing the extension and pointing |
| to our authentication provider class.</para> |
| <para>In our stub, we won't actually do any authentication yet; we'll just universally |
| reject all authentication attempts by returning <varname>null</varname> for any |
| credentials given. You can verify that this is what happens by checking the server |
| logs.</para> |
| <example> |
| <title>Barebones <filename>pom.xml</filename> required for a simple authentication |
| extension.</title> |
| <programlisting><project xmlns="http://maven.apache.org/POM/4.0.0" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 |
| http://maven.apache.org/maven-v4_0_0.xsd"> |
| |
| <modelVersion>4.0.0</modelVersion> |
| <groupId>org.apache.guacamole</groupId> |
| <artifactId>guacamole-auth-tutorial</artifactId> |
| <packaging>jar</packaging> |
| <version>1.1.0</version> |
| <name>guacamole-auth-tutorial</name> |
| |
| <properties> |
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| </properties> |
| |
| <build> |
| <plugins> |
| |
| <!-- Written for 1.6 --> |
| <plugin> |
| <groupId>org.apache.maven.plugins</groupId> |
| <artifactId>maven-compiler-plugin</artifactId> |
| <version>3.3</version> |
| <configuration> |
| <source>1.6</source> |
| <target>1.6</target> |
| </configuration> |
| </plugin> |
| |
| </plugins> |
| </build> |
| |
| <dependencies> |
| |
| <!-- Guacamole Extension API --> |
| <dependency> |
| <groupId>org.apache.guacamole</groupId> |
| <artifactId>guacamole-ext</artifactId> |
| <version>1.1.0</version> |
| <scope>provided</scope> |
| </dependency> |
| |
| </dependencies> |
| |
| </project></programlisting> |
| </example> |
| <para>We won't need to update this <filename>pom.xml</filename> throughout the rest of the |
| tutorial. Even after adding new files, Maven will just find them and compile as |
| necessary.</para> |
| <para>Naturally, we need the actual authentication extension skeleton code. While you can |
| put this in whatever file and package you want, for the sake of this tutorial, we will |
| assume you are using |
| <classname>org.apache.guacamole.auth.TutorialAuthenticationProvider</classname>.</para> |
| <example> |
| <title>A skeleton <classname>TutorialAuthenticationProvider</classname></title> |
| <programlisting>package org.apache.guacamole.auth; |
| |
| import java.util.Map; |
| import org.apache.guacamole.GuacamoleException; |
| import org.apache.guacamole.net.auth.simple.SimpleAuthenticationProvider; |
| import org.apache.guacamole.net.auth.Credentials; |
| import org.apache.guacamole.protocol.GuacamoleConfiguration; |
| |
| /** |
| * Authentication provider implementation intended to demonstrate basic use |
| * of Guacamole's extension API. The credentials and connection information for |
| * a single user are stored directly in guacamole.properties. |
| */ |
| public class TutorialAuthenticationProvider extends SimpleAuthenticationProvider { |
| |
| @Override |
| public String getIdentifier() { |
| return "tutorial"; |
| } |
| |
| @Override |
| public Map<String, GuacamoleConfiguration> |
| getAuthorizedConfigurations(Credentials credentials) |
| throws GuacamoleException { |
| |
| // Do nothing ... yet |
| return null; |
| |
| } |
| |
| }</programlisting> |
| </example> |
| <para>To conform with Maven, this skeleton file must be placed within |
| <filename>src/main/java/org/apache/guacamole/auth</filename> as |
| <filename>TutorialAuthenticationProvider.java</filename>.</para> |
| <para>Notice how simple the authentication provider is. The |
| <classname>SimpleAuthenticationProvider</classname> base class simplifies the |
| <classname>AuthenticationProvider</classname> interface, requiring nothing more than |
| a unique identifier (we will use "tutorial") and a single |
| <methodname>getAuthorizedConfigurations()</methodname> implementation, which must |
| return a <classname>Map</classname> of <classname>GuacamoleConfiguration</classname> |
| each associated with some arbitrary unique ID. This unique ID will be presented to the |
| user in the connection list after they log in.</para> |
| <para>For now, <methodname>getAuthorizedConfigurations()</methodname> will just return |
| <varname>null</varname>. This will cause Guacamole to report an invalid login for |
| every attempt. Note that there is a difference in semantics between returning an empty |
| map and returning <varname>null</varname>, as the former indicates the credentials are |
| authorized but simply have no associated configurations, while the latter indicates the |
| credentials are not authorized at all.</para> |
| <para>The only remaining piece for the overall skeleton to be complete is a |
| <filename>guac-manifest.json</filename> file. <emphasis>This file is absolutely |
| required for all Guacamole extensions.</emphasis> The |
| <filename>guac-manifest.json</filename> format is described in more detail in <xref |
| xmlns:xlink="http://www.w3.org/1999/xlink" linkend="guacamole-ext"/>. It provides |
| for quite a few properties, but for our authentication extension we are mainly |
| interested in the Guacamole version sanity check (to make sure an extension built for |
| the API of Guacamole version X is not accidentally used against version Y) and telling |
| Guacamole where to find our authentication provider class.</para> |
| <para>The Guacamole extension format requires that <filename>guac-manifest.json</filename> |
| be placed in the root directory of the extension <filename>.jar</filename> file. To |
| accomplish this with Maven, we place it within the |
| <filename>src/main/resources</filename> directory. Maven will automatically pick it |
| up during the build and include it within the <filename>.jar</filename>.</para> |
| <example> |
| <title>The required <filename>guac-manifest.json</filename></title> |
| <programlisting>{ |
| |
| "guacamoleVersion" : "1.1.0", |
| |
| "name" : "Tutorial Authentication Extension", |
| "namespace" : "guac-auth-tutorial", |
| |
| "authProviders" : [ |
| "org.apache.guacamole.auth.TutorialAuthenticationProvider" |
| ] |
| |
| }</programlisting> |
| </example> |
| </section> |
| <section xml:id="custom-auth-building"> |
| <title>Building the extension</title> |
| <para>Once all three of the above files are in place, the extension will build, and can even |
| be installed within Guacamole (see <xref xmlns:xlink="http://www.w3.org/1999/xlink" |
| linkend="custom-auth-installing"/> at the end of this chapter), even though it is |
| just a skeleton at this point. It won't do anything yet other than reject all |
| authentication attempts, but it's good to at least try building the extension to make |
| sure nothing is missing and that all steps have been followed correctly so far:</para> |
| <informalexample> |
| <screen><prompt>$</prompt> mvn package |
| <computeroutput>[INFO] Scanning for projects... |
| [INFO] ------------------------------------------------------------------------ |
| [INFO] Building guacamole-auth-tutorial 1.1.0 |
| [INFO] ------------------------------------------------------------------------ |
| ... |
| [INFO] ------------------------------------------------------------------------ |
| [INFO] BUILD SUCCESS |
| [INFO] ------------------------------------------------------------------------ |
| [INFO] Total time: 2.345 s |
| [INFO] Finished at: 2015-12-16T13:39:00-08:00 |
| [INFO] Final Memory: 14M/138M |
| [INFO] ------------------------------------------------------------------------</computeroutput> |
| <prompt>$</prompt></screen> |
| </informalexample> |
| <para>Assuming you see the "<computeroutput>BUILD SUCCESS</computeroutput>" message when you |
| build the extension, there will be a new file, |
| <filename>target/guacamole-auth-tutorial-1.1.0.jar</filename>, which can be |
| installed within Guacamole and tested. If you changed the name or version of the project |
| in the <filename>pom.xml</filename> file, the name of this new <filename>.jar</filename> |
| file will be different, but it can still be found within |
| <filename>target/</filename>.</para> |
| </section> |
| <section xml:id="custom-auth-config"> |
| <title>Configuration and authentication</title> |
| <para>Once we receive credentials, we need to validate those credentials against the |
| associated properties in <filename>guacamole.properties</filename> (our source of |
| authentication information for the sake of this tutorial).</para> |
| <para>We will define four properties:<variablelist> |
| <varlistentry> |
| <term><property>tutorial-user</property></term> |
| <listitem> |
| <para>The name of the only user we accept.</para> |
| </listitem> |
| </varlistentry> |
| <varlistentry> |
| <term><property>tutorial-password</property></term> |
| <listitem> |
| <para>The password we require for the user specified to be |
| authenticated.</para> |
| </listitem> |
| </varlistentry> |
| <varlistentry> |
| <term><property>tutorial-protocol</property></term> |
| <listitem> |
| <para>The protocol of the configuration this user is authorized to use, |
| which will be sent to guacd when the user logs in and selects their |
| connection.</para> |
| </listitem> |
| </varlistentry> |
| <varlistentry> |
| <term><property>tutorial-parameters</property></term> |
| <listitem> |
| <para>A comma-delimited list of |
| <code><replaceable>name</replaceable>=<replaceable>value</replaceable></code> |
| pairs. For the sake of simplicity, we'll assume there will never be any |
| commas in the values.</para> |
| </listitem> |
| </varlistentry> |
| </variablelist></para> |
| <para>If the username and password match what is stored in the file, we read the |
| configuration information, store it in a <classname>GuacamoleConfiguration</classname>, |
| and return the configuration within a set, telling Guacamole that this user is |
| authorized but only to access the configurations returned.</para> |
| <para>Upstream, we always place the properties of authentication providers in their own |
| class, and so we will also do that here in this tutorial, as it keeps things |
| organized.</para> |
| <example> |
| <title><filename>TutorialProperties.java</filename>, a class containing property |
| definitions</title> |
| <programlisting>package org.apache.guacamole.auth; |
| |
| import org.apache.guacamole.properties.StringGuacamoleProperty; |
| |
| /** |
| * Utility class containing all properties used by the custom authentication |
| * tutorial. The properties defined here must be specified within |
| * guacamole.properties to configure the tutorial authentication provider. |
| */ |
| public class TutorialGuacamoleProperties { |
| |
| /** |
| * This class should not be instantiated. |
| */ |
| private TutorialGuacamoleProperties() {} |
| |
| /** |
| * The only user to allow. |
| */ |
| public static final StringGuacamoleProperty TUTORIAL_USER = |
| new StringGuacamoleProperty() { |
| |
| @Override |
| public String getName() { return "tutorial-user"; } |
| |
| }; |
| |
| /** |
| * The password required for the specified user. |
| */ |
| public static final StringGuacamoleProperty TUTORIAL_PASSWORD = |
| new StringGuacamoleProperty() { |
| |
| @Override |
| public String getName() { return "tutorial-password"; } |
| |
| }; |
| |
| |
| /** |
| * The protocol to use when connecting. |
| */ |
| public static final StringGuacamoleProperty TUTORIAL_PROTOCOL = |
| new StringGuacamoleProperty() { |
| |
| @Override |
| public String getName() { return "tutorial-protocol"; } |
| |
| }; |
| |
| |
| /** |
| * All parameters associated with the connection, as a comma-delimited |
| * list of name="value" |
| */ |
| public static final StringGuacamoleProperty TUTORIAL_PARAMETERS = |
| new StringGuacamoleProperty() { |
| |
| @Override |
| public String getName() { return "tutorial-parameters"; } |
| |
| }; |
| |
| }</programlisting> |
| </example> |
| <para>Normally, we would define a new type of <classname>GuacamoleProperty</classname> to |
| handle the parsing of the parameters required by <varname>TUTORIAL_PARAMETERS</varname>, |
| but for the sake of simplicity, parsing of this parameter will be embedded in the |
| authentication function later.</para> |
| <para>You will need to modify your existing <filename>guacamole.properties</filename> file, |
| adding each of the above properties to describe one of your available |
| connections.</para> |
| <example> |
| <title>Properties describing a user and connection, as required by this tutorial</title> |
| <programlisting># Username and password |
| tutorial-user: <replaceable>tutorial</replaceable> |
| tutorial-password: <replaceable>password</replaceable> |
| |
| # Connection information |
| tutorial-protocol: <replaceable>vnc</replaceable> |
| tutorial-parameters: <replaceable>hostname=localhost, port=5900</replaceable></programlisting> |
| </example> |
| <para>Once these properties and their accessor class are in place, it's simple enough to |
| read the properties within <methodname>getAuthorizedConfigurations()</methodname> and |
| authenticate the user based on their username and password.</para> |
| <example> |
| <title>Checking the credentials against the properties</title> |
| <programlisting>@Override |
| public Map<String, GuacamoleConfiguration> |
| getAuthorizedConfigurations(Credentials credentials) |
| throws GuacamoleException { |
| |
| // Get the Guacamole server environment |
| Environment environment = new LocalEnvironment(); |
| |
| // Get username from guacamole.properties |
| String username = environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_USER |
| ); |
| |
| // If wrong username, fail |
| if (!username.equals(credentials.getUsername())) |
| return null; |
| |
| // Get password from guacamole.properties |
| String password = environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_PASSWORD |
| ); |
| |
| // If wrong password, fail |
| if (!password.equals(credentials.getPassword())) |
| return null; |
| |
| // Successful login. Return configurations (STUB) |
| return new HashMap<String, GuacamoleConfiguration>(); |
| |
| }</programlisting> |
| </example> |
| <para>As is, the authentication provider will work in its current state in that the correct |
| username and password will authenticate the user, while an incorrect username or |
| password will not, but we still aren't returning an actual map of configurations. We |
| need to construct the configuration based on the properties in the |
| <filename>guacamole.properties</filename> file after the user has been |
| authenticated, and return that configuration to the web application.</para> |
| </section> |
| <section xml:id="custom-auth-more-config"> |
| <title>Parsing the configuration</title> |
| <para>The only remaining task before we have a fully-functioning authentication provider is |
| to actually parse the configuration from the <filename>guacamole.properties</filename> |
| file.</para> |
| <example> |
| <title>Parsing and returning a <classname>GuacamoleConfiguration</classname></title> |
| <programlisting>@Override |
| public Map<String, GuacamoleConfiguration> |
| getAuthorizedConfigurations(Credentials credentials) |
| throws GuacamoleException { |
| |
| // Get the Guacamole server environment |
| Environment environment = new LocalEnvironment(); |
| |
| // Get username from guacamole.properties |
| String username = environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_USER |
| ); |
| |
| // If wrong username, fail |
| if (!username.equals(credentials.getUsername())) |
| return null; |
| |
| // Get password from guacamole.properties |
| String password = environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_PASSWORD |
| ); |
| |
| // If wrong password, fail |
| if (!password.equals(credentials.getPassword())) |
| return null; |
| |
| // Successful login. Return configurations. |
| Map<String, GuacamoleConfiguration> configs = |
| new HashMap<String, GuacamoleConfiguration>(); |
| |
| // Create new configuration |
| GuacamoleConfiguration config = new GuacamoleConfiguration(); |
| |
| // Set protocol specified in properties |
| config.setProtocol(environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_PROTOCOL |
| )); |
| |
| // Set all parameters, splitting at commas |
| for (String parameterValue : environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_PARAMETERS |
| ).split(",\\s*")) { |
| |
| // Find the equals sign |
| int equals = parameterValue.indexOf('='); |
| if (equals == -1) |
| throw new GuacamoleServerException("Required equals sign missing"); |
| |
| // Get name and value from parameter string |
| String name = parameterValue.substring(0, equals); |
| String value = parameterValue.substring(equals+1); |
| |
| // Set parameter as specified |
| config.setParameter(name, value); |
| |
| } |
| |
| configs.put("Tutorial Connection", config); |
| return configs; |
| |
| }</programlisting> |
| </example> |
| <para>The extension is now complete and can be built as described earlier in <xref |
| xmlns:xlink="http://www.w3.org/1999/xlink" linkend="custom-auth-building"/>.</para> |
| </section> |
| <section xml:id="custom-auth-installing"> |
| <title>Installing the extension</title> |
| <para>Guacamole extensions are self-contained <filename>.jar</filename> files which are |
| installed by being placed within <filename>GUACAMOLE_HOME/extensions</filename>, and |
| this extension is no different. As described in <xref |
| xmlns:xlink="http://www.w3.org/1999/xlink" linkend="configuring-guacamole"/>, |
| <varname>GUACAMOLE_HOME</varname> is a placeholder used to refer to the directory |
| that Guacamole uses to locate its configuration files and extensions. Typically, this |
| will be the <filename>.guacamole</filename> directory within the home directory of the |
| user running Tomcat.</para> |
| <para>To install your extension, ensure that the required properties have been added to your |
| <filename>guacamole.properties</filename>, copy the |
| <filename>target/guacamole-auth-tutorial-1.1.0.jar</filename> file into |
| <filename>GUACAMOLE_HOME/extensions</filename> and restart Tomcat. Guacamole will |
| automatically load your extension, logging an informative message that it has done |
| so:</para> |
| <informalexample> |
| <screen>Extension "Tutorial Authentication Extension" loaded.</screen> |
| </informalexample> |
| </section> |
| </chapter> |