Add .gitignore and .ratignore files for various directories
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
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,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>
|
@@ -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();
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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";
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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.");
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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());
|
||||
}
|
||||
|
||||
}
|
@@ -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.");
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
||||
}]);
|
@@ -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"
|
||||
]
|
||||
|
||||
}
|
@@ -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>
|
@@ -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.
|
||||
*/
|
@@ -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;
|
||||
|
||||
}]);
|
@@ -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');
|
Reference in New Issue
Block a user