From 49a4a6c7a0c505e82f947b29e2f28a9556b99a68 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 20 Oct 2021 23:35:13 -0700 Subject: [PATCH] GUACAMOLE-957: Support reading multiple LDAP server configurations from "ldap-servers.yml". --- .../jackson-2.12.2/dep-coordinates.txt | 1 + doc/licenses/snakeyaml-1.27/README | 8 + .../snakeyaml-1.27/dep-coordinates.txt | 1 + extensions/guacamole-auth-ldap/pom.xml | 10 + .../ldap/AuthenticationProviderService.java | 2 +- .../auth/ldap/conf/ConfigurationService.java | 43 ++- .../ldap/conf/JacksonLDAPConfiguration.java | 315 ++++++++++++++++++ pom.xml | 5 + 8 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 doc/licenses/snakeyaml-1.27/README create mode 100644 doc/licenses/snakeyaml-1.27/dep-coordinates.txt create mode 100644 extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java diff --git a/doc/licenses/jackson-2.12.2/dep-coordinates.txt b/doc/licenses/jackson-2.12.2/dep-coordinates.txt index 6dcc7917e..3f73c2e2c 100644 --- a/doc/licenses/jackson-2.12.2/dep-coordinates.txt +++ b/doc/licenses/jackson-2.12.2/dep-coordinates.txt @@ -1,4 +1,5 @@ com.fasterxml.jackson.core:jackson-databind:jar:2.12.2 com.fasterxml.jackson.core:jackson-core:jar:2.12.2 com.fasterxml.jackson.core:jackson-annotations:jar:2.12.2 +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.12.2 com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.12.2 diff --git a/doc/licenses/snakeyaml-1.27/README b/doc/licenses/snakeyaml-1.27/README new file mode 100644 index 000000000..3fcd837d6 --- /dev/null +++ b/doc/licenses/snakeyaml-1.27/README @@ -0,0 +1,8 @@ +SnakeYAML (https://bitbucket.org/asomov/snakeyaml/) +--------------------------------------------------- + + Version: 1.27 + From: 'Andrey Somov' (https://bitbucket.org/asomov/) + License(s): + Apache v2.0 + diff --git a/doc/licenses/snakeyaml-1.27/dep-coordinates.txt b/doc/licenses/snakeyaml-1.27/dep-coordinates.txt new file mode 100644 index 000000000..d7cbad91a --- /dev/null +++ b/doc/licenses/snakeyaml-1.27/dep-coordinates.txt @@ -0,0 +1 @@ +org.yaml:snakeyaml:jar:1.27 diff --git a/extensions/guacamole-auth-ldap/pom.xml b/extensions/guacamole-auth-ldap/pom.xml index fa9d8a215..5aa8f967d 100644 --- a/extensions/guacamole-auth-ldap/pom.xml +++ b/extensions/guacamole-auth-ldap/pom.xml @@ -60,6 +60,16 @@ guice + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java index 42afdf510..f56f9e592 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java @@ -196,7 +196,7 @@ public class AuthenticationProviderService { String password) throws GuacamoleException { // Get relevant LDAP configurations for user - Collection configs = confService.getLDAPConfigurations(username); + Collection configs = confService.getLDAPConfigurations(username); if (configs.isEmpty()) { logger.info("User \"{}\" does not map to any defined LDAP configurations.", username); return null; diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java index d84ac47ea..e8bafcaa6 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java @@ -19,17 +19,41 @@ package org.apache.guacamole.auth.ldap.conf; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.inject.Inject; +import java.io.File; +import java.io.IOException; import java.util.Collection; import java.util.Collections; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.environment.Environment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Service for retrieving configuration information regarding LDAP servers. */ public class ConfigurationService { + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(ConfigurationService.class); + + /** + * ObjectMapper for deserializing YAML. + */ + private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + /** + * The name of the file within GUACAMOLE_HOME that defines each available + * LDAP server (if not using guacamole.properties). + */ + private static final String LDAP_SERVERS_YML = "ldap-servers.yml"; + /** * The Guacamole server environment. */ @@ -54,8 +78,25 @@ public class ConfigurationService { * If the configuration information of the LDAP servers related to the * user having the given username cannot be retrieved due to an error. */ - public Collection getLDAPConfigurations(String username) throws GuacamoleException { + public Collection getLDAPConfigurations(String username) throws GuacamoleException { + + // Read configuration from YAML, if available + File ldapServers = new File(environment.getGuacamoleHome(), LDAP_SERVERS_YML); + if (ldapServers.exists()) { + try { + logger.debug("Reading LDAP configuration from \"{}\"...", ldapServers); + return mapper.readValue(ldapServers, new TypeReference>() {}); + } + catch (IOException e) { + logger.error("\"{}\" could not be read/parsed: {}", ldapServers, e.getMessage()); + throw new GuacamoleServerException("Cannot read LDAP configuration from \"" + ldapServers + "\"", e); + } + } + + // Use guacamole.properties if not using YAML + logger.debug("Reading LDAP configuration from guacamole.properties..."); return Collections.singletonList(new EnvironmentLDAPConfiguration(environment)); + } } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java new file mode 100644 index 000000000..1676d8ac3 --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java @@ -0,0 +1,315 @@ +/* + * 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.ldap.conf; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; +import java.util.List; +import org.apache.directory.api.ldap.model.filter.ExprNode; +import org.apache.directory.api.ldap.model.filter.PresenceNode; +import org.apache.directory.api.ldap.model.message.AliasDerefMode; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; + +/** + * LDAPConfiguration implementation that is annotated for deserialization by + * Jackson. + */ +public class JacksonLDAPConfiguration implements LDAPConfiguration { + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_HOSTNAME}. If + * not set within the YAML, this will be null. + */ + @JsonProperty("hostname") + private String hostname; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_PORT}. If not + * set within the YAML, this will be null. + */ + @JsonProperty("port") + private Integer port; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USERNAME_ATTRIBUTES}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("username-attribute") + private List usernameAttributes; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_BASE_DN}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("user-base-dn") + private String userBaseDn; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_CONFIG_BASE_DN}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("config-base-dn") + private String configBaseDn; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_BASE_DN}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("group-base-dn") + private String groupBaseDn; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_NAME_ATTRIBUTES}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("group-name-attribute") + private List groupNameAttributes; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_SEARCH_BIND_DN}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("search-bind-dn") + private String searchBindDn; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_SEARCH_BIND_PASSWORD}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("search-bind-password") + private String searchBindPassword; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_ENCRYPTION_METHOD}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("encryption-method") + private String encryptionMethod; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MAX_SEARCH_RESULTS}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("max-search-results") + private Integer maxSearchResults; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_DEREFERENCE_ALIASES}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("dereference-aliases") + private String dereferenceAliases; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_FOLLOW_REFERRALS}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("follow-referrals") + private Boolean followReferrals; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MAX_REFERRAL_HOPS}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("max-referral-hops") + private Integer maxReferralHops; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_SEARCH_FILTER}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("user-search-filter") + private String userSearchFilter; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_SEARCH_FILTER}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("group-search-filter") + private String groupSearchFilter; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_OPERATION_TIMEOUT}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("operation-timeout") + private Integer operationTimeout; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_ATTRIBUTES}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("user-attributes") + private List userAttributes; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MEMBER_ATTRIBUTE}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("member-attribute") + private String memberAttribute; + + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MEMBER_ATTRIBUTE_TYPE}. + * If not set within the YAML, this will be null. + */ + @JsonProperty("member-attribute-type") + private String memberAttributeType; + + @Override + public String getServerHostname() { + return hostname != null ? hostname : "localhost"; + } + + @Override + public int getServerPort() throws GuacamoleException { + return port != null ? port : getEncryptionMethod().DEFAULT_PORT; + } + + @Override + public List getUsernameAttributes() { + return usernameAttributes != null ? usernameAttributes : Collections.singletonList("uid"); + } + + @Override + public Dn getUserBaseDN() throws GuacamoleException { + + Dn parsedDn = LDAPGuacamoleProperties.LDAP_USER_BASE_DN.parseValue(userBaseDn); + if (parsedDn == null) + throw new GuacamoleServerException("The \"user-base-dn\" property is required for all LDAP servers."); + + return parsedDn; + + } + + @Override + public Dn getConfigurationBaseDN() throws GuacamoleException { + return LDAPGuacamoleProperties.LDAP_CONFIG_BASE_DN.parseValue(configBaseDn); + } + + @Override + public List getGroupNameAttributes() throws GuacamoleException { + return groupNameAttributes != null ? groupNameAttributes : Collections.singletonList("cn"); + } + + @Override + public Dn getGroupBaseDN() throws GuacamoleException { + return LDAPGuacamoleProperties.LDAP_GROUP_BASE_DN.parseValue(groupBaseDn); + } + + @Override + public String getSearchBindDN() throws GuacamoleException { + return searchBindDn; + } + + @Override + public String getSearchBindPassword() throws GuacamoleException { + return searchBindPassword; + } + + @Override + public EncryptionMethod getEncryptionMethod() throws GuacamoleException { + + EncryptionMethod parsedMethod = LDAPGuacamoleProperties.LDAP_ENCRYPTION_METHOD.parseValue(encryptionMethod); + if (parsedMethod == null) + return EncryptionMethod.NONE; + + return parsedMethod; + + } + + @Override + public int getMaxResults() throws GuacamoleException { + return maxSearchResults != null ? maxSearchResults : 1000; + } + + @Override + public AliasDerefMode getDereferenceAliases() throws GuacamoleException { + + AliasDerefMode parsedMode = LDAPGuacamoleProperties.LDAP_DEREFERENCE_ALIASES.parseValue(dereferenceAliases); + if (parsedMode == null) + return AliasDerefMode.NEVER_DEREF_ALIASES; + + return parsedMode; + + } + + @Override + public boolean getFollowReferrals() throws GuacamoleException { + return followReferrals != null ? followReferrals : false; + } + + @Override + public int getMaxReferralHops() throws GuacamoleException { + return maxReferralHops != null ? maxReferralHops : 5; + } + + @Override + public ExprNode getUserSearchFilter() throws GuacamoleException { + + ExprNode parsedFilter = LDAPGuacamoleProperties.LDAP_USER_SEARCH_FILTER.parseValue(userSearchFilter); + if (parsedFilter == null) + return new PresenceNode("objectClass"); + + return parsedFilter; + + } + + @Override + public ExprNode getGroupSearchFilter() throws GuacamoleException { + + ExprNode parsedFilter = LDAPGuacamoleProperties.LDAP_GROUP_SEARCH_FILTER.parseValue(groupSearchFilter); + if (parsedFilter == null) + return new PresenceNode("objectClass"); + + return parsedFilter; + + } + + @Override + public int getOperationTimeout() throws GuacamoleException { + return operationTimeout != null ? operationTimeout : 30; + } + + @Override + public List getAttributes() throws GuacamoleException { + return userAttributes != null ? userAttributes : Collections.emptyList(); + } + + @Override + public String getMemberAttribute() throws GuacamoleException { + return memberAttribute != null ? memberAttribute : "member"; + } + + @Override + public MemberAttributeType getMemberAttributeType() + throws GuacamoleException { + + MemberAttributeType parsedType = LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE_TYPE.parseValue(memberAttributeType); + if (parsedType == null) + return MemberAttributeType.DN; + + return parsedType; + + } + +} diff --git a/pom.xml b/pom.xml index 0480d5bf1..f133e2c18 100644 --- a/pom.xml +++ b/pom.xml @@ -377,6 +377,11 @@ jackson-databind ${jackson.version} + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson.version} + com.fasterxml.jackson.module jackson-module-jaxb-annotations