Add .gitignore and .ratignore files for various directories
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
gyurix
2025-04-29 21:43:12 +02:00
parent 983ecbfc53
commit be9f66dee9
2167 changed files with 254128 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
*~
target/
src/main/resources/generated/

View File

@@ -0,0 +1 @@
src/main/resources/html/*.html

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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-sso-ssl</artifactId>
<packaging>jar</packaging>
<version>1.6.0</version>
<name>guacamole-auth-sso-ssl</name>
<url>http://guacamole.apache.org/</url>
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-sso</artifactId>
<version>1.6.0</version>
<relativePath>../../</relativePath>
</parent>
<build>
<plugins>
<!-- JS/CSS Minification Plugin -->
<plugin>
<groupId>com.github.buckelieg</groupId>
<artifactId>minify-maven-plugin</artifactId>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<charset>UTF-8</charset>
<webappSourceDir>${basedir}/src/main/resources</webappSourceDir>
<webappTargetDir>${project.build.directory}/classes</webappTargetDir>
<jsSourceDir>/</jsSourceDir>
<jsTargetDir>/</jsTargetDir>
<jsFinalFile>ssl.js</jsFinalFile>
<jsSourceFiles>
<jsSourceFile>license.txt</jsSourceFile>
</jsSourceFiles>
<jsSourceIncludes>
<jsSourceInclude>**/*.js</jsSourceInclude>
</jsSourceIncludes>
<!-- Do not minify and include tests -->
<jsSourceExcludes>
<jsSourceExclude>**/*.test.js</jsSourceExclude>
</jsSourceExcludes>
<jsEngine>CLOSURE</jsEngine>
<!-- Disable warnings for JSDoc annotations -->
<closureWarningLevels>
<misplacedTypeAnnotation>OFF</misplacedTypeAnnotation>
<nonStandardJsDocs>OFF</nonStandardJsDocs>
</closureWarningLevels>
</configuration>
<goals>
<goal>minify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Guacamole Extension API -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-ext</artifactId>
</dependency>
<!-- Core SSO support -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-sso-base</artifactId>
</dependency>
<!-- Guice -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<!-- Java servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
<!-- JAX-RS Annotations -->
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</dependency>
<!-- Use FIPS variant of Bouncy Castle crypto library -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-fips</artifactId>
<version>2.1.9</version>
<exclusions>
<!--
Force usage of known version of bc-fips, rather than a
future unknown version (bcpkix-fips references bc-fips using
a version range, resulting in newer versions getting pulled
in automatically, breaking the automated license check).
-->
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bc-fips</artifactId>
</exclusion>
<!--
Force usage of known version of bcutil-fips, rather than a
future unknown version (bcpkix-fips references bctuil-fips
using a version range, resulting in newer versions getting
pulled in automatically, breaking the automated license
check).
-->
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-fips</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Force usage of known version of bc-fips (see bcpkix-fips above) -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bc-fips</artifactId>
<version>2.1.0</version>
</dependency>
<!-- Force usage of known version of bcutil-fips (see bcpkix-fips above) -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-fips</artifactId>
<version>2.1.4</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,191 @@
/*
* 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.auth.ssl;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import org.apache.guacamole.auth.ssl.conf.ConfigurationService;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.auth.sso.SSOAuthenticationProviderService;
import org.apache.guacamole.auth.sso.user.SSOAuthenticatedUser;
import org.apache.guacamole.form.Field;
import org.apache.guacamole.form.RedirectField;
import org.apache.guacamole.language.TranslatableMessage;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
/**
* Service that authenticates Guacamole users using SSL/TLS authentication
* provided by an external SSL termination service.
*/
@Singleton
public class AuthenticationProviderService implements SSOAuthenticationProviderService {
/**
* Service for retrieving configuration information.
*/
@Inject
private ConfigurationService confService;
/**
* Session manager for generating and maintaining unique tokens to
* represent the authentication flow of a user who has only partially
* authenticated. Here, these tokens represent a user that has been
* validated by SSL termination and allow the Guacamole instance that
* doesn't require SSL/TLS authentication to retrieve the user's identity
* and complete the authentication process.
*/
@Inject
private SSLAuthenticationSessionManager sessionManager;
/**
* Provider for AuthenticatedUser objects.
*/
@Inject
private Provider<SSOAuthenticatedUser> authenticatedUserProvider;
/**
* The name of the query parameter containing the temporary session token
* representing the current state of an in-progress authentication attempt.
*/
private static final String AUTH_SESSION_PARAMETER_NAME = "state";
/**
* Return the value of the session identifier associated with the given
* credentials, or null if no session identifier is found in the credentials.
*
* @param credentials
* The credentials from which to extract the session identifier.
*
* @return
* The session identifier associated with the given credentials, or
* null if no identifier is found.
*/
public static String getSessionIdentifier(Credentials credentials) {
// Return the session identifier from the request params, if set, or
// null otherwise
return credentials != null ? credentials.getParameter(AUTH_SESSION_PARAMETER_NAME) : null;
}
/**
* Processes the given credentials, returning the identity represented by
* the auth session token present in that request associated with the
* credentials. If no such token is present, or the token does not represent
* a valid identity, null is returned.
*
* @param credentials
* The credentials to extract the auth session token from.
*
* @return
* The identity represented by the auth session token in the request,
* or null if there is no such token or the token does not represent a
* valid identity.
*/
private SSOAuthenticatedUser processIdentity(Credentials credentials) {
String state = getSessionIdentifier(credentials);
String username = sessionManager.getIdentity(state);
if (username == null)
return null;
SSOAuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
authenticatedUser.init(username, credentials,
Collections.emptySet(), Collections.emptyMap());
return authenticatedUser;
}
@Override
public SSOAuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
//
// Overall flow:
//
// 1) An unauthenticated user makes a GET request to
// ".../api/ext/ssl/identity". After a series of redirects
// intended to prevent that identity from being inadvertently
// cached and inherited by future authentication attempts on the
// same client machine, an external SSL termination service requests
// and validates the user's certificate, those details are passed
// back to Guacamole via HTTP headers, and Guacamole produces a JSON
// response containing an opaque state value.
//
// 2) The user (still unauthenticated) resubmits the opaque state
// value from the received JSON as the "state" parameter of a
// standard Guacamole authentication request (".../api/tokens").
//
// 3) If the certificate received was valid, the user is authenticated
// according to the identity asserted by that certificate. If not,
// authentication is refused.
//
// NOTE: All SSL termination endpoints in front of Guacamole MUST
// be configured to drop these headers from any inbound requests
// or users may be able to assert arbitrary identities, since this
// extension does not validate anything but the certificate timestamps.
// It relies purely on SSL termination to validate that the certificate
// was signed by the expected CA.
//
// We MUST have the domain associated with the request to ensure we
// always get fresh SSL sessions when validating client certificates
String host = credentials.getHeader("Host");
if (host == null)
return null;
//
// Handle only auth session tokens at the primary URI, using the
// pre-verified information from those tokens to determine user
// identity.
//
if (confService.isPrimaryHostname(host))
return processIdentity(credentials);
// All other requests are not allowed - redirect to proper hostname
throw new GuacamoleInvalidCredentialsException("Authentication is "
+ "only allowed against the primary URL of this Guacamole "
+ "instance.",
new CredentialsInfo(Arrays.asList(new Field[] {
new RedirectField("primaryURI", confService.getPrimaryURI(),
new TranslatableMessage("LOGIN.INFO_REDIRECT_PENDING"))
}))
);
}
@Override
public URI getLoginURI() throws GuacamoleException {
throw new GuacamoleResourceNotFoundException("No such resource.");
}
@Override
public void shutdown() {
sessionManager.shutdown();
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.auth.ssl;
/**
* REST API response that reports the result of attempting to authenticate the
* user using SSL/TLS client authentication. The information within this
* result is intentionally opaque and must be resubmitted in a separate
* authentication request for authentication to finally succeed or fail.
*/
public class OpaqueAuthenticationResult {
/**
* An arbitrary value representing the result of authenticating the
* current user.
*/
private final String state;
/**
* Creates a new OpaqueAuthenticationResult containing the given opaque
* state value. Successful authentication results must be indistinguishable
* from unsuccessful results with respect to this value. Only using this
* value within ANOTHER authentication attempt can determine whether
* authentication is successful.
*
* @param state
* An arbitrary value representing the result of authenticating the
* current user.
*/
public OpaqueAuthenticationResult(String state) {
this.state = state;
}
/**
* Returns an arbitrary value representing the result of authenticating the
* current user. This value may be resubmitted as the "state" parameter of
* an authentication request beneath the primary URI of the web application
* to finalize the authentication procedure and determine whether the
* operation has succeeded or failed.
*
* @return
* An arbitrary value representing the result of authenticating the
* current user.
*/
public String getState() {
return state;
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.auth.ssl;
import com.google.inject.Inject;
import org.apache.guacamole.auth.sso.SSOAuthenticationEventListener;
import org.apache.guacamole.net.auth.Credentials;
/**
* A Listener that will reactivate or invalidate SSL auth sessions depending on
* overall auth success or failure.
*/
public class SSLAuthenticationEventListener extends SSOAuthenticationEventListener {
/**
* Session manager for generating and maintaining unique tokens to
* represent the authentication flow of a user who has only partially
* authenticated.
*
* Requires static injection due to the fact that the webapp just calls the
* constructor directly when creating new Listeners. The instances will not
* be constructed by guice.
*/
@Inject
protected static SSLAuthenticationSessionManager sessionManager;
@Override
protected String getSessionIdentifier(Credentials credentials) {
return AuthenticationProviderService.getSessionIdentifier(credentials);
}
@Override
protected void reactivateSession(String sessionIdentifier) {
sessionManager.reactivateSession(sessionIdentifier);
}
@Override
protected void invalidateSession(String sessionIdentifier) {
sessionManager.invalidateSession(sessionIdentifier);
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.auth.ssl;
import org.apache.guacamole.auth.sso.SSOAuthenticationProvider;
/**
* Guacamole authentication backend which authenticates users using SSL/TLS
* client authentication provided by some external SSL termination system. This
* SSL termination system must be configured to provide access to this same
* instance of Guacamole and must have both a wildcard certificate and wildcard
* DNS. No storage for connections is provided - only authentication. Storage
* must be provided by some other extension.
*/
public class SSLAuthenticationProvider extends SSOAuthenticationProvider {
/**
* Creates a new SSLAuthenticationProvider that authenticates users against
* an external SSL termination system using SSL/TLS client authentication.
*/
public SSLAuthenticationProvider() {
super(AuthenticationProviderService.class, SSLClientAuthenticationResource.class,
new SSLAuthenticationProviderModule());
}
@Override
public String getIdentifier() {
return "ssl";
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.auth.ssl;
import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
import org.apache.guacamole.auth.ssl.conf.ConfigurationService;
import org.apache.guacamole.auth.ssl.conf.SSLEnvironment;
import org.apache.guacamole.auth.sso.NonceService;
import org.apache.guacamole.environment.Environment;
/**
* Guice module which configures injections specific to SSO using SSL/TLS
* client authentication.
*/
public class SSLAuthenticationProviderModule extends AbstractModule {
/**
* The configuration environment of this server and extension.
*/
private final Environment environment = new SSLEnvironment();
@Override
protected void configure() {
bind(ConfigurationService.class);
bind(NonceService.class).in(Scopes.SINGLETON);
bind(SSLAuthenticationSessionManager.class);
bind(Environment.class).toInstance(environment);
requestStaticInjection(SSLAuthenticationEventListener.class);
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.auth.ssl;
import org.apache.guacamole.net.auth.AuthenticationSession;
/**
* Representation of an in-progress SSL/TLS authentication attempt.
*/
public class SSLAuthenticationSession extends AuthenticationSession {
/**
* The identity asserted by the external SSL termination service.
*/
private final String identity;
/**
* Creates a new AuthenticationSession representing an in-progress SSL/TLS
* authentication attempt.
*
* @param identity
* The identity asserted by the external SSL termination service. This
* MAY NOT be null.
*
* @param expires
* The number of milliseconds that may elapse before this session must
* be considered invalid.
*/
public SSLAuthenticationSession(String identity, long expires) {
super(expires);
this.identity = identity;
}
/**
* Returns the identity asserted by the external SSL termination service.
* As authentication will have completed with respect to the SSL
* termination service by the time this session is created, this will
* always be non-null.
*
* @return
* The identity asserted by the external SSL termination service.
*/
public String getIdentity() {
return identity;
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.auth.ssl;
import com.google.inject.Singleton;
import org.apache.guacamole.net.auth.AuthenticationSessionManager;
/**
* Manager service that temporarily stores SSL/TLS authentication attempts
* while the authentication flow is underway.
*/
@Singleton
public class SSLAuthenticationSessionManager
extends AuthenticationSessionManager<SSLAuthenticationSession> {
/**
* Returns the identity asserted by the external SSL termination service at
* the end of the authentication process represented by the authentication
* session with the given identifier. If there is no such authentication
* session, or no valid identity has been asserted for that session, null
* is returned.
*
* @param identifier
* The unique string returned by the call to defer(). For convenience,
* this value may safely be null.
*
* @return
* The identity asserted by the external SSL termination service at the
* end of the authentication process represented by the authentication
* session with the given identifier, or null if there is no such
* identity.
*/
public String getIdentity(String identifier) {
SSLAuthenticationSession session = resume(identifier);
if (session != null)
return session.getIdentity();
return null;
}
}

View File

@@ -0,0 +1,439 @@
/*
* 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.auth.ssl;
import com.google.inject.Inject;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.ws.rs.GET;
import javax.ws.rs.core.Response;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.auth.ssl.conf.ConfigurationService;
import org.apache.guacamole.auth.sso.NonceService;
import org.apache.guacamole.auth.sso.SSOResource;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.RFC4519Style;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.openssl.PEMParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* REST API resource that allows the user to retrieve an opaque state value
* representing their identity as determined by SSL/TLS client authentication.
* The opaque value may represent a valid identity or an authentication
* failure, and must be resubmitted within a normal Guacamole authentication
* request to finalize the authentication process.
*/
public class SSLClientAuthenticationResource extends SSOResource {
/**
* The string value that the SSL termination service uses for its client
* verification header to represent that the client certificate has been
* verified.
*/
private static final String CLIENT_VERIFIED_HEADER_SUCCESS_VALUE = "SUCCESS";
/**
* The string value that the SSL termination service uses for its client
* verification header to represent that the client certificate is absent.
*/
private static final String CLIENT_VERIFIED_HEADER_NONE_VALUE = "NONE";
/**
* The string prefix that the SSL termination service uses for its client
* verification header to represent that the client certificate has failed
* validation. The error message describing the nature of the failure is
* provided by the SSL termination service after this prefix.
*/
private static final String CLIENT_VERIFIED_HEADER_FAILED_PREFIX = "FAILED:";
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(SSLClientAuthenticationResource.class);
/**
* Service for retrieving configuration information.
*/
@Inject
private ConfigurationService confService;
/**
* Session manager for generating and maintaining unique tokens to
* represent the authentication flow of a user who has only partially
* authenticated. Here, these tokens represent a user that has been
* validated by SSL termination and allow the Guacamole instance that
* doesn't require SSL/TLS authentication to retrieve the user's identity
* and complete the authentication process.
*/
@Inject
private SSLAuthenticationSessionManager sessionManager;
/**
* Service for validating and generating unique nonce values. Here, these
* nonces are used specifically for generating unique domains.
*/
@Inject
private NonceService subdomainNonceService;
/**
* Retrieves a single value from the HTTP header having the given name. If
* there are multiple HTTP headers present with this name, the first
* matching header in the request is used. If there are no such headers in
* the request, null is returned.
*
* @param headers
* The HTTP headers present in the request.
*
* @param name
* The name of the header to retrieve.
*
* @return
* The first value of the HTTP header having the given name, or null if
* there is no such header.
*/
private String getHeader(HttpHeaders headers, String name) {
List<String> values = headers.getRequestHeader(name);
if (values == null || values.isEmpty())
return null;
return values.get(0);
}
/**
* Decodes the provided URL-encoded string as UTF-8, returning the result.
* <p>
* NOTE: The escape() function of the Apache HTTPD server is known to not
* encode plus signs, which can appear in the base64-encoded certificates
* typically received here. To avoid mangling such certificates, this
* function specifically avoids decoding plus signs as spaces (as would
* otherwise happen if URLDecoder is used directly).
*
* @param value
* The URL-encoded string to decode.
*
* @return
* The decoded string.
*
* @throws GuacamoleException
* If the provided value is not a valid URL-encoded string.
*/
private byte[] decode(String value) throws GuacamoleException {
// Ensure all plus signs are decoded literally rather than as spaces
// (the Apache HTTPD implementation of URL escaping that applies to
// request headers does not encode plus signs, whereas the Nginx
// implementation does)
value = value.replace("+", "%2B");
try {
return URLDecoder.decode(value, StandardCharsets.UTF_8.name())
.getBytes(StandardCharsets.UTF_8);
}
catch (IllegalArgumentException e) {
throw new GuacamoleClientException("Invalid URL-encoded value.", e);
}
catch (UnsupportedEncodingException e) {
// This should never happen, as UTF-8 is a standard charset that
// the JVM is required to support
throw new UnsupportedOperationException("Unexpected lack of UTF-8 support.", e);
}
}
/**
* Extracts a user's username from the X.509 subject name, which should be
* in LDAP DN format. If specific username attributes are configured, only
* those username attributes are used to determine the name. If a specific
* base DN is configured, only subject names that are formatted as LDAP DNs
* within that base DN will be accepted.
*
* @param name
* The subject name to extract the username from.
*
* @return
* The username of the user represented by the given subject name.
*
* @throws GuacamoleException
* If any configuration parameters related to retrieving certificates
* from HTTP request cannot be parsed, or if the provided subject name
* cannot be parsed or is not acceptable (wrong base DN or wrong
* username attribute).
*/
public String getUsername(String name) throws GuacamoleException {
// Extract user's DN from their X.509 certificate
LdapName dn;
try {
dn = new LdapName(name);
}
catch (InvalidNameException e) {
throw new GuacamoleClientException("Subject \"" + name + "\" is "
+ "not a valid DN: " + e.getMessage(), e);
}
// Verify DN actually contains components
int numComponents = dn.size();
if (numComponents < 1)
throw new GuacamoleClientException("Subject DN is empty.");
// Verify DN is within configured base DN (if any)
LdapName baseDN = confService.getSubjectBaseDN();
if (baseDN != null && !(numComponents > baseDN.size() && dn.startsWith(baseDN)))
throw new GuacamoleClientException("Subject DN \"" + dn + "\" is "
+ "not within the configured base DN.");
// Retrieve the least significant attribute from the parsed DN - this
// will be the username
Rdn nameRdn = dn.getRdn(numComponents - 1);
// Verify that the username is specified with one of the allowed
// attributes
Collection<String> usernameAttributes = confService.getSubjectUsernameAttributes();
if (usernameAttributes != null && !usernameAttributes.stream().anyMatch(nameRdn.getType()::equalsIgnoreCase))
throw new GuacamoleClientException("Subject DN \"" + dn + "\" "
+ "does not contain an acceptable username attribute.");
// The DN is valid - extract the username from the least significant
// component
String username = nameRdn.getValue().toString();
logger.debug("Username \"{}\" extracted from subject DN \"{}\".", username, dn);
return username;
}
/**
* Authenticates a user using HTTP headers containing that user's verified
* X.509 certificate. It is assumed that this certificate is being passed
* to Guacamole from an SSL termination service that has already verified
* that this certificate is valid and authorized for access to that
* Guacamole instance.
*
* @param certificate
* The raw bytes of the X.509 certificate retrieved from the request.
*
* @return
* The username of the user asserted by the SSL termination service via
* that user's X.509 certificate.
*
* @throws GuacamoleException
* If any configuration parameters related to retrieving certificates
* from HTTP request cannot be parsed, or if the certificate is not
* valid/present.
*/
public String getUsername(byte[] certificate) throws GuacamoleException {
// Parse and re-verify certificate is valid with respect to timestamps
X509CertificateHolder cert;
try (Reader reader = new StringReader(new String(certificate, StandardCharsets.UTF_8))) {
PEMParser parser = new PEMParser(reader);
Object object = parser.readObject();
// Verify received data is indeed an X.509 certificate
if (object == null || !(object instanceof X509CertificateHolder))
throw new GuacamoleClientException("Certificate did not "
+ "contain an X.509 certificate.");
// Verify sanity of received certificate (there should be only
// one object here)
if (parser.readObject() != null)
throw new GuacamoleClientException("Certificate contains "
+ "more than a single X.509 certificate.");
cert = (X509CertificateHolder) object;
// Verify certificate is valid (it should be given pre-validation
// from SSL termination, but it's worth rechecking for sanity)
if (!cert.isValidOn(new Date()))
throw new GuacamoleClientException("Certificate has expired.");
}
catch (IOException e) {
throw new GuacamoleServerException("Certificate could not be read: " + e.getMessage(), e);
}
// Extract user's DN from their X.509 certificate in LDAP (RFC 4919) format
X500Name subject = X500Name.getInstance(RFC4519Style.INSTANCE, cert.getSubject());
return getUsername(subject.toString());
}
/**
* Processes the X.509 certificate in the given set of HTTP request
* headers, returning an authentication session token representing the
* identity in that certificate. If the certificate is invalid or not
* present, an invalid session token is returned.
*
* @param headers
* The headers of the HTTP request to process.
*
* @return
* An authentication session token representing the identity in the
* certificate in the given HTTP request, or an invalid session token
* if no valid identity was asserted.
*/
private String processCertificate(HttpHeaders headers) {
//
// NOTE: A result with an associated state is ALWAYS returned by
// processCertificate(), even if the request does not actually contain
// a valid certificate. This is by design and ensures that the nature
// of a certificate (valid vs. invalid) cannot be determined except
// via Guacamole's authentication endpoint, thus allowing auth failure
// hooks to consider attempts to use invalid certificates as auth
// failures.
//
try {
// Verify that SSL termination has already verified the certificate
String verified = getHeader(headers, confService.getClientVerifiedHeader());
if (verified != null && verified.startsWith(CLIENT_VERIFIED_HEADER_FAILED_PREFIX)) {
String message = verified.substring(CLIENT_VERIFIED_HEADER_FAILED_PREFIX.length());
throw new GuacamoleClientException("Client certificate did "
+ "not pass validation. SSL termination reports the "
+ "following failure: \"" + message + "\"");
}
else if (CLIENT_VERIFIED_HEADER_NONE_VALUE.equals(verified)) {
throw new GuacamoleClientException("No client certificate was presented.");
}
else if (!CLIENT_VERIFIED_HEADER_SUCCESS_VALUE.equals(verified)) {
throw new GuacamoleClientException("Client certificate did not pass validation.");
}
String certificate = getHeader(headers, confService.getClientCertificateHeader());
if (certificate == null)
throw new GuacamoleClientException("Client certificate missing from request.");
String username = getUsername(decode(certificate));
long validityDuration = TimeUnit.MINUTES.toMillis(confService.getMaxTokenValidity());
return sessionManager.defer(new SSLAuthenticationSession(username, validityDuration));
}
catch (GuacamoleClientException e) {
logger.warn("SSL/TLS client authentication attempt rejected: {}", e.getMessage());
logger.debug("SSL/TLS client authentication failed.", e);
}
catch (GuacamoleException e) {
logger.error("SSL/TLS client authentication attempt could not be processed: {}", e.getMessage());
logger.debug("SSL/TLS client authentication failed.", e);
}
catch (RuntimeException | Error e) {
logger.error("SSL/TLS client authentication attempt failed internally: {}", e.getMessage());
logger.debug("Internal failure processing SSL/TLS client authentication attempt.", e);
}
return sessionManager.generateInvalid();
}
/**
* Attempts to authenticate the current user using SSL/TLS client
* authentication, returning an opaque value that represents their
* authenticated status. If necessary, the user is first redirected to a
* unique endpoint that supports SSL/TLS client authentication.
*
* @param headers
* All HTTP headers submitted in the user's authentication request.
*
* @param host
* The hostname that the user specified in their HTTP request.
*
* @return
* A Response containing an opaque value representing the user's
* authenticated status, or a Response redirecting the user to a
* unique endpoint that can provide this.
*
* @throws GuacamoleException
* If any required configuration information is missing or cannot be
* parsed, or if the request was not received at a valid subdomain.
*/
@GET
@Path("identity")
public Response authenticateClient(@Context HttpHeaders headers,
@HeaderParam("Host") String host) throws GuacamoleException {
// Redirect any requests to the domain that does NOT require SSL/TLS
// client authentication to the same endpoint at a domain that does
// require SSL/TLS authentication
String subdomain = confService.getClientAuthenticationSubdomain(host);
if (subdomain == null) {
long validityDuration = TimeUnit.MINUTES.toMillis(confService.getMaxDomainValidity());
String uniqueSubdomain = subdomainNonceService.generate(validityDuration);
URI clientAuthURI = UriBuilder.fromUri(confService.getClientAuthenticationURI(uniqueSubdomain))
.path("api/ext/ssl/identity")
.build();
return Response.seeOther(clientAuthURI).build();
}
//
// Process certificates only at valid single-use subdomains dedicated
// to client authentication, redirecting back to the main redirect URI
// for final authentication if that processing is successful.
//
// NOTE: This is CRITICAL. If unique subdomains are not generated and
// tied to strictly one authentication attempt, then those subdomains
// could be reused by a user on a shared machine to assume the cached
// credentials of another user that used that machine earlier. The
// browser and/or OS may cache the certificate so that it can be reused
// for future SSL sessions to that same domain. Here, we ensure each
// generated domain is unique and only valid for certificate processing
// ONCE. The domain may still be valid with DNS, but will no longer be
// usable for certificate authentication.
//
if (subdomainNonceService.isValid(subdomain))
return Response.ok(new OpaqueAuthenticationResult(processCertificate(headers)))
.header("Access-Control-Allow-Origin", confService.getPrimaryOrigin().toString())
.type(MediaType.APPLICATION_JSON)
.build();
throw new GuacamoleResourceNotFoundException("No such resource.");
}
}

View File

@@ -0,0 +1,481 @@
/*
* 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.auth.ssl.conf;
import com.google.inject.Inject;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import javax.naming.ldap.LdapName;
import javax.ws.rs.core.UriBuilder;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
import org.apache.guacamole.properties.StringGuacamoleProperty;
import org.apache.guacamole.properties.URIGuacamoleProperty;
/**
* Service for retrieving configuration information regarding SSO using SSL/TLS
* authentication.
*/
public class ConfigurationService {
/**
* The default name of the header to use to retrieve the URL-encoded client
* certificate from an HTTP request received from an SSL termination
* service providing SSL/TLS client authentication.
*/
private static String DEFAULT_CLIENT_CERTIFICATE_HEADER = "X-Client-Certificate";
/**
* The default name of the header to use to retrieve the verification
* status of the certificate an HTTP request received from an SSL
* termination service providing SSL/TLS client authentication.
*/
private static String DEFAULT_CLIENT_VERIFIED_HEADER = "X-Client-Verified";
/**
* The default amount of time that a temporary authentication token for
* SSL/TLS authentication may remain valid, in minutes.
*/
private static int DEFAULT_MAX_TOKEN_VALIDITY = 5;
/**
* The default amount of time that the temporary, unique subdomain
* generated for SSL/TLS authentication may remain valid, in minutes.
*/
private static int DEFAULT_MAX_DOMAIN_VALIDITY = 5;
/**
* The property representing the URI that should be used to authenticate
* users with SSL/TLS client authentication. This must be a URI that points
* to THIS instance of Guacamole, but behind SSL termination that requires
* SSL/TLS client authentication.
*/
private static final WildcardURIGuacamoleProperty SSL_AUTH_URI =
new WildcardURIGuacamoleProperty() {
@Override
public String getName() { return "ssl-auth-uri"; }
};
/**
* The property representing the URI of this instance without SSL/TLS
* client authentication required. This must be a URI that points
* to THIS instance of Guacamole, but behind SSL termination that DOES NOT
* require or request SSL/TLS client authentication.
*/
private static final URIGuacamoleProperty SSL_AUTH_PRIMARY_URI =
new URIGuacamoleProperty() {
@Override
public String getName() { return "ssl-auth-primary-uri"; }
};
/**
* The property representing the name of the header to use to retrieve the
* URL-encoded client certificate from an HTTP request received from an
* SSL termination service providing SSL/TLS client authentication.
*/
private static final StringGuacamoleProperty SSL_AUTH_CLIENT_CERTIFICATE_HEADER =
new StringGuacamoleProperty() {
@Override
public String getName() { return "ssl-auth-client-certificate-header"; }
};
/**
* The property representing the name of the header to use to retrieve the
* verification status of the certificate an HTTP request received from an
* SSL termination service providing SSL/TLS client authentication. This
* value of this header must be "SUCCESS" (all uppercase) if the
* certificate was successfully verified.
*/
private static final StringGuacamoleProperty SSL_AUTH_CLIENT_VERIFIED_HEADER =
new StringGuacamoleProperty() {
@Override
public String getName() { return "ssl-auth-client-verified-header"; }
};
/**
* The property representing the amount of time that a temporary
* authentication token for SSL/TLS authentication may remain valid, in
* minutes. This token is used to represent the user's asserted identity
* after it has been verified by the SSL termination service. This interval
* must be long enough to allow for network delays in receiving the token,
* but short enough that unused tokens do not consume unnecessary server
* resources and cannot potentially be guessed while the token is still
* valid. These tokens are 256-bit secure random values.
*/
private static final IntegerGuacamoleProperty SSL_AUTH_MAX_TOKEN_VALIDITY =
new IntegerGuacamoleProperty() {
@Override
public String getName() { return "ssl-auth-max-token-validity"; }
};
/**
* The property defining the LDAP attribute or attributes that may be used
* to represent a username within the subject DN of a user's X.509
* certificate. If the least-significant attribute of the subject DN is not
* one of these attributes, the certificate will be rejected. By default,
* any attribute is accepted.
*/
private static final StringGuacamoleProperty SSL_AUTH_SUBJECT_USERNAME_ATTRIBUTE =
new StringGuacamoleProperty () {
@Override
public String getName() { return "ssl-auth-subject-username-attribute"; }
};
/**
* The property defining the base DN containing all valid subject DNs. If
* specified, only certificates asserting subject DNs beneath this base DN
* will be accepted. By default, all DNs are accepted.
*/
private static final LdapNameGuacamoleProperty SSL_AUTH_SUBJECT_BASE_DN =
new LdapNameGuacamoleProperty () {
@Override
public String getName() { return "ssl-auth-subject-base-dn"; }
};
/**
* The property representing the amount of time that the temporary, unique
* subdomain generated for SSL/TLS authentication may remain valid, in
* minutes. This subdomain is used to ensure each SSL/TLS authentication
* attempt is fresh and does not potentially reuse a previous
* authentication attempt that was cached by the browser or OS. This
* interval must be long enough to allow for network delays in
* authenticating the user with the SSL termination service that enforces
* SSL/TLS client authentication, but short enough that an unused domain
* does not consume unnecessary server resources and cannot potentially be
* guessed while that subdomain is still valid. These subdomains are
* 128-bit secure random values.
*/
private static final IntegerGuacamoleProperty SSL_AUTH_MAX_DOMAIN_VALIDITY =
new IntegerGuacamoleProperty() {
@Override
public String getName() { return "ssl-auth-max-domain-validity"; }
};
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* Returns whether the given hostname matches the hostname of the given
* URI. The provided hostname may be the value of an HTTP "Host" header,
* and may include a port number. If a port number is included in the
* hostname, it is ignored.
*
* @param hostname
* The hostname to check, which may alternatively be the value of an
* HTTP "Host" header, with or without port number. The port number is
* not considered when determining whether this hostname matches the
* hostname of the provided URI.
*
* @param offset
* The character offset within the provided hostname where checking
* should start. Any characters before this offset are ignored. This
* offset does not affect where checking starts within the hostname of
* the provided URI.
*
* @param uri
* The URI to check the given hostname against.
*
* @return
* true if the provided hostname from the given offset onward is
* identical to the hostname of the given URI, false otherwise.
*/
private boolean hostnameMatches(String hostname, int offset, URI uri) {
// Locate end of actual hostname portion of "Host" header
int endOfHostname = hostname.indexOf(':');
if (endOfHostname == -1)
endOfHostname = hostname.length();
// Before checking substring equivalence, we need to verify that the
// length actually matches what we expect (we'd otherwise consider the
// host to match if it starts with the expected hostname, ignoring any
// remaining characters)
String expectedHostname = uri.getHost();
if (expectedHostname.length() != endOfHostname - offset)
return false;
return hostname.regionMatches(true, offset, expectedHostname, 0, expectedHostname.length());
}
/**
* Returns a URI that should be used to authenticate users with SSL/TLS
* client authentication. The returned URI will consist of the configured
* client authentication URI with the wildcard portion ("*.") replaced with
* the given subdomain.
*
* @param subdomain
* The subdomain that should replace the wildcard portion of the
* configured client authentication URI.
*
* @return
* A URI that should be used to authenticate users with SSL/TLS
* client authentication.
*
* @throws GuacamoleException
* If the required property for configuring the client authentication
* URI is missing or cannot be parsed.
*/
public URI getClientAuthenticationURI(String subdomain) throws GuacamoleException {
URI authURI = environment.getRequiredProperty(SSL_AUTH_URI);
String baseHostname = authURI.getHost();
// Add provided subdomain to auth URI
return UriBuilder.fromUri(authURI)
.host(subdomain + "." + baseHostname)
.build();
}
/**
* Given a hostname that was used by a user for SSL/TLS client
* authentication, returns the subdomain at the beginning of that hostname.
* If the hostname does not match the pattern of hosts represented by the
* configured client authentication URI, null is returned.
*
* @param hostname
* The hostname to extract the subdomain from.
*
* @return
* The subdomain at the beginning of the provided hostname, if that
* hostname matches the pattern of hosts represented by the
* configured client authentication URI, or null otherwise.
*
* @throws GuacamoleException
* If the required property for configuring the client authentication
* URI is missing or cannot be parsed.
*/
public String getClientAuthenticationSubdomain(String hostname) throws GuacamoleException {
// Any hostname that matches the explicitly-specific primary URI is not
// a client auth subdomain
if (isPrimaryHostname(hostname))
return null;
// Verify the first domain component is at least one character in
// length
int firstPeriod = hostname.indexOf('.');
if (firstPeriod <= 0)
return null;
// Verify domain matches the configured auth URI except for the leading
// subdomain
URI authURI = environment.getRequiredProperty(SSL_AUTH_URI);
if (!hostnameMatches(hostname, firstPeriod + 1, authURI))
return null;
// Extract subdomain
return hostname.substring(0, firstPeriod);
}
/**
* Returns the URI of this instance without SSL/TLS client authentication
* required.
*
* @return
* The URI of this instance without SSL/TLS client authentication
* required.
*
* @throws GuacamoleException
* If the required property for configuring the primary URI is missing
* or cannot be parsed.
*/
public URI getPrimaryURI() throws GuacamoleException {
return environment.getRequiredProperty(SSL_AUTH_PRIMARY_URI);
}
/**
* Returns the HTTP request origin for requests originating from this
* instance via the primary URI (as returned by {@link #getPrimaryURI()}.
* This value is essentially the same as the primary URI but with only the
* scheme, host, and port present.
*
* @return
* The HTTP request origin for requests originating from this instance
* via the primary URI.
*
* @throws GuacamoleException
* If the required property for configuring the primary URI is missing
* or cannot be parsed.
*/
public URI getPrimaryOrigin() throws GuacamoleException {
URI primaryURI = getPrimaryURI();
try {
return new URI(primaryURI.getScheme(), null, primaryURI.getHost(), primaryURI.getPort(), null, null, null);
}
catch (URISyntaxException e) {
throw new GuacamoleServerException("Request origin could not be "
+ "derived from the configured primary URI.", e);
}
}
/**
* Returns whether the given hostname is the same as the hostname in the
* primary URI (as returned by {@link #getPrimaryURI()}. Hostnames are
* case-insensitive.
*
* @param hostname
* The hostname to test.
*
* @return
* true if the hostname is the same as the hostname in the primary URI,
* false otherwise.
*
* @throws GuacamoleException
* If the required property for configuring the primary URI is missing
* or cannot be parsed.
*/
public boolean isPrimaryHostname(String hostname) throws GuacamoleException {
URI primaryURI = getPrimaryURI();
return hostnameMatches(hostname, 0, primaryURI);
}
/**
* Returns the name of the header to use to retrieve the URL-encoded client
* certificate from an HTTP request received from an SSL termination
* service providing SSL/TLS client authentication.
*
* @return
* The name of the header to use to retrieve the URL-encoded client
* certificate from an HTTP request received from an SSL termination
* service providing SSL/TLS client authentication.
*
* @throws GuacamoleException
* If the property for configuring the client certificate header cannot
* be parsed.
*/
public String getClientCertificateHeader() throws GuacamoleException {
return environment.getProperty(SSL_AUTH_CLIENT_CERTIFICATE_HEADER, DEFAULT_CLIENT_CERTIFICATE_HEADER);
}
/**
* Returns the name of the header to use to retrieve the verification
* status of the certificate an HTTP request received from an SSL
* termination service providing SSL/TLS client authentication.
*
* @return
* The name of the header to use to retrieve the verification
* status of the certificate an HTTP request received from an SSL
* termination service providing SSL/TLS client authentication.
*
* @throws GuacamoleException
* If the property for configuring the client verification header
* cannot be parsed.
*/
public String getClientVerifiedHeader() throws GuacamoleException {
return environment.getProperty(SSL_AUTH_CLIENT_VERIFIED_HEADER, DEFAULT_CLIENT_VERIFIED_HEADER);
}
/**
* Returns the maximum amount of time that the token generated by the
* Guacamole server representing current SSL authentication state should
* remain valid, in minutes. This imposes an upper limit on the amount of
* time any particular authentication request can result in successful
* authentication within Guacamole when SSL/TLS client authentication is
* configured. By default, this will be 5.
*
* @return
* The maximum amount of time that an SSL authentication token
* generated by the Guacamole server should remain valid, in minutes.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public int getMaxTokenValidity() throws GuacamoleException {
return environment.getProperty(SSL_AUTH_MAX_TOKEN_VALIDITY, DEFAULT_MAX_TOKEN_VALIDITY);
}
/**
* Returns the maximum amount of time that a unique client authentication
* subdomain generated by the Guacamole server should remain valid, in
* minutes. This imposes an upper limit on the amount of time any
* particular authentication request can result in successful
* authentication within Guacamole when SSL/TLS client authentication is
* configured. By default, this will be 5.
*
* @return
* The maximum amount of time that a unique client authentication
* subdomain generated by the Guacamole server should remain valid, in
* minutes.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public int getMaxDomainValidity() throws GuacamoleException {
return environment.getProperty(SSL_AUTH_MAX_DOMAIN_VALIDITY, DEFAULT_MAX_DOMAIN_VALIDITY);
}
/**
* Returns the base DN that contains all valid subject DNs. If there is no
* such base DN (and all subject DNs are valid), null is returned.
*
* @return
* The base DN that contains all valid subject DNs, or null if all
* subject DNs are valid.
*
* @throws GuacamoleException
* If the configured base DN cannot be read or is not a valid LDAP DN.
*/
public LdapName getSubjectBaseDN() throws GuacamoleException {
return environment.getProperty(SSL_AUTH_SUBJECT_BASE_DN);
}
/**
* Returns a list of all attributes that may be used to represent a user's
* username within their subject DN. If all attributes may be accepted,
* null is returned.
*
* @return
* A list of all attributes that may be used to represent a user's
* username within their subject DN, or null if any attribute may be
* used.
*
* @throws GuacamoleException
* If the configured set of username attributes cannot be read.
*/
public Collection<String> getSubjectUsernameAttributes() throws GuacamoleException {
return environment.getPropertyCollection(SSL_AUTH_SUBJECT_USERNAME_ATTRIBUTE);
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.auth.ssl.conf;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.properties.GuacamoleProperty;
/**
* A GuacamoleProperty whose value is an LDAP name, such as a distinguished
* name.
*/
public abstract class LdapNameGuacamoleProperty implements GuacamoleProperty<LdapName> {
@Override
public LdapName parseValue(String value) throws GuacamoleException {
if (value == null)
return null;
try {
return new LdapName(value);
}
catch (InvalidNameException e) {
throw new GuacamoleServerException("Value \"" + value
+ "\" is not a valid LDAP name.", e);
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.auth.ssl.conf;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.DelegatingEnvironment;
import org.apache.guacamole.environment.LocalEnvironment;
/**
* An environment for retrieving SSL-related properties from the Guacamole
* configuration.
*/
public class SSLEnvironment extends DelegatingEnvironment {
/**
* Create a new instance of the configuration environment for the
* SSL SSO module, pulling the default instance of the LocalEnvironment.
*/
public SSLEnvironment() {
super(LocalEnvironment.getInstance());
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.auth.ssl.conf;
import java.net.URI;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.properties.URIGuacamoleProperty;
/**
* A GuacamoleProperty whose value is a wildcard URI. The behavior of this
* property is identical to URIGuacamoleProperty except that it verifies a
* wildcard hostname prefix ("*.") is present and strips that prefix from the
* parsed URI.
*/
public abstract class WildcardURIGuacamoleProperty extends URIGuacamoleProperty {
/**
* Regular expression that broadly matches URIs that contain wildcards in
* their hostname. This regular expression is NOT strict and will match
* invalid URIs. It is only strict enough to recognize a wildcard hostname
* prefix.
*/
private static final Pattern WILDCARD_URI_PATTERN = Pattern.compile("([^:]+://(?:[^@]+@)?)\\*\\.(.*)");
@Override
public URI parseValue(String value) throws GuacamoleException {
if (value == null)
return null;
// Verify wildcard prefix is present
Matcher matcher = WILDCARD_URI_PATTERN.matcher(value);
if (matcher.matches()) {
// Strip wildcard prefix from URI and verify a valid hostname is
// still present
URI uri = super.parseValue(matcher.group(1) + matcher.group(2));
if (uri.getHost() != null)
return uri;
}
// All other values are not valid wildcard URIs
throw new GuacamoleServerException("Value \"" + value
+ "\" is not a valid wildcard URI.");
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.
*/
/**
* A directive which automatically attempts to log the current user in using
* SSL/TLS client authentication when the associated element is clicked.
*/
angular.module('element').directive('guacSslAuth', ['$injector', function guacSslAuth($injector) {
// Required services
var clientAuthService = $injector.get('clientAuthService');
var directive = {
restrict: 'A'
};
directive.link = function linkGuacSslAuth($scope, $element) {
/**
* The element which will register the click.
*
* @type Element
*/
const element = $element[0];
// Attempt SSL/TLS client authentication upon click
element.addEventListener('click', function elementClicked() {
clientAuthService.authenticate();
});
};
return directive;
}]);

View File

@@ -0,0 +1,39 @@
{
"guacamoleVersion" : "1.6.0",
"name" : "SSL Authentication Extension",
"namespace" : "ssl",
"authProviders" : [
"org.apache.guacamole.auth.ssl.SSLAuthenticationProvider"
],
"listeners" : [
"org.apache.guacamole.auth.ssl.SSLAuthenticationEventListener"
],
"css" : [
"styles/sso-providers.css"
],
"js" : [ "ssl.min.js" ],
"html" : [
"html/sso-providers.html",
"html/sso-provider-ssl.html"
],
"translations" : [
"translations/ca.json",
"translations/de.json",
"translations/en.json",
"translations/fr.json",
"translations/ja.json",
"translations/ko.json",
"translations/pt.json",
"translations/ru.json",
"translations/zh.json"
]
}

View File

@@ -0,0 +1,4 @@
<meta name="after-children" content=".login-ui .sso-provider-list">
<li class="sso-provider sso-provider-ssl"><a guac-ssl-auth href="">{{
'LOGIN.NAME_IDP_SSL' | translate
}}</a></li>

View File

@@ -0,0 +1,18 @@
/*
* 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.
*/

View File

@@ -0,0 +1,58 @@
/*
* 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.
*/
/**
* Service for authenticating a user using SSL/TLS client authentication.
*/
angular.module('guacSsoSsl').factory('clientAuthService', ['$injector',
function clientAuthServiceProvider($injector) {
// Required services
var requestService = $injector.get('requestService');
var authenticationService = $injector.get('authenticationService');
var service = {};
/**
* Attempt to authenticate using a unique token obtained through SSL/TLS
* client authentication.
*/
service.authenticate = function authenticate() {
// Transform SSL/TLS identity into an opaque "state" value and
// attempt authentication using that value
authenticationService.authenticate(
requestService({
method: 'GET',
headers : {
'Cache-Control' : undefined, // Avoid sending headers that would result in a pre-flight OPTIONS request for CORS
'Pragma' : undefined
},
url: 'api/ext/ssl/identity'
})
.then(function identityRetrieved(data) {
return { 'state' : data.state || '' };
})
)['catch'](requestService.IGNORE);
};
return service;
}]);

View File

@@ -0,0 +1,29 @@
/*
* 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.
*/
/**
* The module for code implementing SSO using SSL/TLS client authentication.
*/
angular.module('guacSsoSsl', [
'auth',
'rest'
]);
// Ensure the guacSsoSsl module is loaded along with the rest of the app
angular.module('index').requires.push('guacSsoSsl');