mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-839: Implement base support for SSO using SSL/TLS authentication (certificates / smart cards).
This commit is contained in:
@@ -12,6 +12,10 @@
|
|||||||
"NAME" : "SAML SSO Backend"
|
"NAME" : "SAML SSO Backend"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"DATA_SOURCE_SSL" : {
|
||||||
|
"NAME" : "SSL/TLS SSO Backend"
|
||||||
|
},
|
||||||
|
|
||||||
"LOGIN" : {
|
"LOGIN" : {
|
||||||
"FIELD_HEADER_ID_TOKEN" : "",
|
"FIELD_HEADER_ID_TOKEN" : "",
|
||||||
"FIELD_HEADER_STATE" : "",
|
"FIELD_HEADER_STATE" : "",
|
||||||
@@ -20,6 +24,7 @@
|
|||||||
"NAME_IDP_CAS" : "CAS",
|
"NAME_IDP_CAS" : "CAS",
|
||||||
"NAME_IDP_OPENID" : "OpenID",
|
"NAME_IDP_OPENID" : "OpenID",
|
||||||
"NAME_IDP_SAML" : "SAML",
|
"NAME_IDP_SAML" : "SAML",
|
||||||
|
"NAME_IDP_SSL" : "Certificate / Smart Card",
|
||||||
"SECTION_HEADER_SSO_OPTIONS" : "Sign in with:"
|
"SECTION_HEADER_SSO_OPTIONS" : "Sign in with:"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/.gitignore
vendored
Normal file
3
extensions/guacamole-auth-sso/modules/guacamole-auth-sso-ssl/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*~
|
||||||
|
target/
|
||||||
|
src/main/resources/generated/
|
@@ -0,0 +1 @@
|
|||||||
|
src/main/resources/html/*.html
|
@@ -0,0 +1,74 @@
|
|||||||
|
<?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.5.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.5.0</version>
|
||||||
|
<relativePath>../../</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@@ -0,0 +1,413 @@
|
|||||||
|
/*
|
||||||
|
* 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.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.naming.InvalidNameException;
|
||||||
|
import javax.naming.ldap.LdapName;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
import org.apache.guacamole.GuacamoleClientException;
|
||||||
|
import org.apache.guacamole.auth.ssl.conf.ConfigurationService;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.GuacamoleServerException;
|
||||||
|
import org.apache.guacamole.auth.sso.NonceService;
|
||||||
|
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.GuacamoleInsufficientCredentialsException;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for validating and generating unique nonce values. Here, these
|
||||||
|
* nonces are used specifically for generating unique domains.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private NonceService subdomainNonceService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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 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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes the provided URL-encoded string as UTF-8, returning the result.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The URL-encoded string to decode.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The decoded string.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the provided value is not a value URL-encoded string.
|
||||||
|
*/
|
||||||
|
private byte[] decode(String value) throws GuacamoleException {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 credentials
|
||||||
|
* The credentials received by Guacamole in the authentication request.
|
||||||
|
*
|
||||||
|
* @param certificate
|
||||||
|
* The raw bytes of the X.509 certificate retrieved from the request.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A new SSOAuthenticatedUser representing the identity of the user
|
||||||
|
* asserted by the SSL termination service via that user's X.509
|
||||||
|
* certificate.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the provided X.509 certificate is not valid or cannot be parsed.
|
||||||
|
* It is expected that the SSL termination service will already have
|
||||||
|
* validated the certificate; this function validates only the
|
||||||
|
* certificate timestamps.
|
||||||
|
*/
|
||||||
|
private SSOAuthenticatedUser authenticateUser(Credentials credentials,
|
||||||
|
byte[] certificate) throws GuacamoleException {
|
||||||
|
|
||||||
|
// Parse and re-verify certificate is valid with respect to timestamps
|
||||||
|
X509Certificate cert;
|
||||||
|
try (InputStream input = new ByteArrayInputStream(certificate)) {
|
||||||
|
|
||||||
|
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||||
|
cert = (X509Certificate) certFactory.generateCertificate(input);
|
||||||
|
|
||||||
|
// Verify certificate is valid (it should be given pre-validation
|
||||||
|
// from SSL termination, but it's worth rechecking for sanity)
|
||||||
|
cert.checkValidity();
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (CertificateException e) {
|
||||||
|
throw new GuacamoleClientException("The X.509 certificate "
|
||||||
|
+ "presented is not valid.", e);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new GuacamoleServerException("Provided X.509 certificate "
|
||||||
|
+ "could not be read.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract user's DN from their X.509 certificate
|
||||||
|
LdapName dn;
|
||||||
|
try {
|
||||||
|
Principal principal = cert.getSubjectX500Principal();
|
||||||
|
dn = new LdapName(principal.getName());
|
||||||
|
}
|
||||||
|
catch (InvalidNameException e) {
|
||||||
|
throw new GuacamoleClientException("The X.509 certificate "
|
||||||
|
+ "presented does not contain a valid subject DN.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify DN actually contains components
|
||||||
|
int numComponents = dn.size();
|
||||||
|
if (numComponents < 1)
|
||||||
|
throw new GuacamoleClientException("The X.509 certificate "
|
||||||
|
+ "presented contains an empty subject DN.");
|
||||||
|
|
||||||
|
// Simply use first component of DN as username (TODO: Enforce
|
||||||
|
// requirements on the attribute providing the username and the base DN,
|
||||||
|
// and consider using components following the username to determine
|
||||||
|
// group memberships)
|
||||||
|
String username = dn.getRdn(numComponents - 1).getValue().toString();
|
||||||
|
|
||||||
|
SSOAuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
|
||||||
|
authenticatedUser.init(username, credentials,
|
||||||
|
Collections.emptySet(), Collections.emptyMap());
|
||||||
|
return authenticatedUser;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the given HTTP request, returning the identity represented by
|
||||||
|
* the auth session token present in that request. If no such token is
|
||||||
|
* present, or the token does not represent a valid identity, null is
|
||||||
|
* returned.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* The HTTP request to process.
|
||||||
|
*
|
||||||
|
* @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(HttpServletRequest request) {
|
||||||
|
String state = request.getParameter(AUTH_SESSION_PARAMETER_NAME);
|
||||||
|
return sessionManager.getIdentity(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the X.509 certificate in the headers of the given HTTP
|
||||||
|
* request, returning an authentication session token representing the
|
||||||
|
* identity in that certificate. If the certificate is invalid or not
|
||||||
|
* present, null is returned.
|
||||||
|
*
|
||||||
|
* @param credentials
|
||||||
|
* The credentials submitted in the HTTP request being processed.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* The HTTP request to process.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* An authentication session token representing the identity in the
|
||||||
|
* certificate in the given HTTP request, or null if the request does
|
||||||
|
* not contain a valid certificate.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If any configuration parameters related to retrieving certificates
|
||||||
|
* from HTTP request cannot be parsed.
|
||||||
|
*/
|
||||||
|
private String processCertificate(Credentials credentials,
|
||||||
|
HttpServletRequest request) throws GuacamoleException {
|
||||||
|
|
||||||
|
// Verify that SSL termination has already verified the certificate
|
||||||
|
String verified = request.getHeader(confService.getClientVerifiedHeader());
|
||||||
|
if (!CLIENT_VERIFIED_HEADER_SUCCESS_VALUE.equals(verified))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String certificate = request.getHeader(confService.getClientCertificateHeader());
|
||||||
|
if (certificate == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
SSOAuthenticatedUser authenticatedUser = authenticateUser(credentials, decode(certificate));
|
||||||
|
long validityDuration = TimeUnit.MINUTES.toMillis(confService.getMaxTokenValidity());
|
||||||
|
return sessionManager.defer(new SSLAuthenticationSession(authenticatedUser, validityDuration));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirects the current user back to the main URL of the Guacamole
|
||||||
|
* instance to continue the authentication process after having identified
|
||||||
|
* themselves using SSL/TLS client authentication.
|
||||||
|
*
|
||||||
|
* @param token
|
||||||
|
* The authentication session token generated for the current user's
|
||||||
|
* identity.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* To redirect the user to the main URL of the Guacamole instance.
|
||||||
|
*/
|
||||||
|
private void resumeAuthenticationAtRedirectURI(String token)
|
||||||
|
throws GuacamoleException {
|
||||||
|
|
||||||
|
URI redirectURI = UriBuilder.fromUri(confService.getRedirectURI())
|
||||||
|
.queryParam(AUTH_SESSION_PARAMETER_NAME, token)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Request that the provided credentials, now tokenized, be
|
||||||
|
// resubmitted in that tokenized form to the original host for
|
||||||
|
// authentication
|
||||||
|
throw new GuacamoleInsufficientCredentialsException("Please "
|
||||||
|
+ "resubmit your tokenized credentials using the "
|
||||||
|
+ "following URI.",
|
||||||
|
new CredentialsInfo(Arrays.asList(new Field[] {
|
||||||
|
new RedirectField(AUTH_SESSION_PARAMETER_NAME, redirectURI,
|
||||||
|
new TranslatableMessage("LOGIN.INFO_IDP_REDIRECT_PENDING"))
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSOAuthenticatedUser authenticateUser(Credentials credentials)
|
||||||
|
throws GuacamoleException {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Overall flow:
|
||||||
|
//
|
||||||
|
// 1) Unauthenticated user is given a temporary auth session token
|
||||||
|
// and redirected to the SSL termination instance that provides
|
||||||
|
// SSL client auth. This redirect uses a unique and temporary
|
||||||
|
// subdomain to ensure each SSL client auth attempt is fresh and
|
||||||
|
// does not use cached auth details.
|
||||||
|
//
|
||||||
|
// 2) Unauthenticated user with a temporary auth session token
|
||||||
|
// is validated by SSL termination, with that SSL termination
|
||||||
|
// adding HTTP headers containing the validated certificate to the
|
||||||
|
// user's HTTP request.
|
||||||
|
//
|
||||||
|
// 3) If valid, the user is assigned a temporary token and redirected
|
||||||
|
// back to the original URL. That temporary token is accepted by
|
||||||
|
// this extension at the original URL as proof of the user's
|
||||||
|
// identity.
|
||||||
|
//
|
||||||
|
// 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 can't authenticate using SSL/TLS client auth unless there's an
|
||||||
|
// associated HTTP request
|
||||||
|
HttpServletRequest request = credentials.getRequest();
|
||||||
|
if (request == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// We MUST have the domain associated with the request to ensure we
|
||||||
|
// always get fresh SSL sessions when validating client certificates
|
||||||
|
String host = request.getHeader("Host");
|
||||||
|
if (host == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Handle only auth session tokens at the main redirect URI, using the
|
||||||
|
// pre-verified information from those tokens to determine user
|
||||||
|
// identity.
|
||||||
|
//
|
||||||
|
|
||||||
|
String redirectHost = confService.getRedirectURI().getHost();
|
||||||
|
if (host.equals(redirectHost)) {
|
||||||
|
|
||||||
|
SSOAuthenticatedUser user = processIdentity(request);
|
||||||
|
if (user != null)
|
||||||
|
return user;
|
||||||
|
|
||||||
|
// Redirect unauthenticated requests to the endpoint requiring
|
||||||
|
// SSL client auth to request identity verification
|
||||||
|
throw new GuacamoleInvalidCredentialsException("Invalid login.",
|
||||||
|
new CredentialsInfo(Arrays.asList(new Field[] {
|
||||||
|
new RedirectField(AUTH_SESSION_PARAMETER_NAME, getLoginURI(), // <-- Each call to getLoginURI() produces a unique subdomain that is valid only for ONE use (see below)
|
||||||
|
new TranslatableMessage("LOGIN.INFO_IDP_REDIRECT_PENDING"))
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
else if (subdomainNonceService.isValid(confService.getClientAuthenticationSubdomain(host))) {
|
||||||
|
String token = processCertificate(credentials, request);
|
||||||
|
if (token != null)
|
||||||
|
resumeAuthenticationAtRedirectURI(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All other requests are not allowed - refuse to authenticate
|
||||||
|
throw new GuacamoleClientException("Direct authentication against "
|
||||||
|
+ "this endpoint is not valid without first requesting to "
|
||||||
|
+ "authenticate at the primary URL of this Guacamole "
|
||||||
|
+ "instance.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getLoginURI() throws GuacamoleException {
|
||||||
|
long validityDuration = TimeUnit.MINUTES.toMillis(confService.getMaxDomainValidity());
|
||||||
|
String uniqueSubdomain = subdomainNonceService.generate(validityDuration);
|
||||||
|
return confService.getClientAuthenticationURI(uniqueSubdomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
sessionManager.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
import org.apache.guacamole.auth.sso.SSOResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, SSOResource.class,
|
||||||
|
new SSLAuthenticationProviderModule());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIdentifier() {
|
||||||
|
return "ssl";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.apache.guacamole.auth.ssl.conf.ConfigurationService;
|
||||||
|
import org.apache.guacamole.auth.sso.NonceService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guice module which configures injections specific to SSO using SSL/TLS
|
||||||
|
* client authentication.
|
||||||
|
*/
|
||||||
|
public class SSLAuthenticationProviderModule extends AbstractModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(ConfigurationService.class);
|
||||||
|
bind(NonceService.class);
|
||||||
|
bind(SSLAuthenticationSessionManager.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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;
|
||||||
|
|
||||||
|
import org.apache.guacamole.auth.sso.AuthenticationSession;
|
||||||
|
import org.apache.guacamole.auth.sso.user.SSOAuthenticatedUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 SSOAuthenticatedUser 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(SSOAuthenticatedUser 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 SSOAuthenticatedUser getIdentity() {
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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.auth.sso.AuthenticationSessionManager;
|
||||||
|
import org.apache.guacamole.auth.sso.user.SSOAuthenticatedUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 SSOAuthenticatedUser getIdentity(String identifier) {
|
||||||
|
|
||||||
|
SSLAuthenticationSession session = resume(identifier);
|
||||||
|
if (session != null)
|
||||||
|
return session.getIdentity();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,325 @@
|
|||||||
|
/*
|
||||||
|
* 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 javax.ws.rs.core.UriBuilder;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
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_CLIENT_AUTH_URI =
|
||||||
|
new WildcardURIGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "ssl-client-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_REDIRECT_URI =
|
||||||
|
new URIGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "ssl-redirect-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_CLIENT_CERTIFICATE_HEADER =
|
||||||
|
new StringGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "ssl-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_CLIENT_VERIFIED_HEADER =
|
||||||
|
new StringGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "ssl-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 redirecting the user
|
||||||
|
* back to the main Guacamole URL, 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_MAX_TOKEN_VALIDITY =
|
||||||
|
new IntegerGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "ssl-max-token-validity"; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 redirecting
|
||||||
|
* the user to the SSL termination service enforcing SSL/TLS
|
||||||
|
* 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_MAX_DOMAIN_VALIDITY =
|
||||||
|
new IntegerGuacamoleProperty() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "ssl-max-domain-validity"; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Guacamole server environment.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private Environment environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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_CLIENT_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 {
|
||||||
|
|
||||||
|
URI authURI = environment.getRequiredProperty(SSL_CLIENT_AUTH_URI);
|
||||||
|
String baseHostname = authURI.getHost();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
if (!hostname.regionMatches(true, firstPeriod + 1, baseHostname, 0, baseHostname.length()))
|
||||||
|
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 redirect URI is missing
|
||||||
|
* or cannot be parsed.
|
||||||
|
*/
|
||||||
|
public URI getRedirectURI() throws GuacamoleException {
|
||||||
|
return environment.getRequiredProperty(SSL_REDIRECT_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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_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_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_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_MAX_DOMAIN_VALIDITY, DEFAULT_MAX_DOMAIN_VALIDITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
// 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.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
"guacamoleVersion" : "1.5.0",
|
||||||
|
|
||||||
|
"name" : "SSL Authentication Extension",
|
||||||
|
"namespace" : "ssl",
|
||||||
|
|
||||||
|
"authProviders" : [
|
||||||
|
"org.apache.guacamole.auth.ssl.SSLAuthenticationProvider"
|
||||||
|
],
|
||||||
|
|
||||||
|
"css" : [
|
||||||
|
"styles/sso-providers.css"
|
||||||
|
],
|
||||||
|
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,4 @@
|
|||||||
|
<meta name="after-children" content=".login-ui .sso-provider-list:last-child">
|
||||||
|
<li class="sso-provider sso-provider-ssl"><a href="api/ext/ssl/login">{{
|
||||||
|
'LOGIN.NAME_IDP_SSL' | translate
|
||||||
|
}}</a></li>
|
@@ -49,6 +49,7 @@
|
|||||||
<module>modules/guacamole-auth-sso-cas</module>
|
<module>modules/guacamole-auth-sso-cas</module>
|
||||||
<module>modules/guacamole-auth-sso-openid</module>
|
<module>modules/guacamole-auth-sso-openid</module>
|
||||||
<module>modules/guacamole-auth-sso-saml</module>
|
<module>modules/guacamole-auth-sso-saml</module>
|
||||||
|
<module>modules/guacamole-auth-sso-ssl</module>
|
||||||
|
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user