diff --git a/conf/smart-default.xml b/conf/smart-default.xml index 198d01b2fe..be396cb08b 100644 --- a/conf/smart-default.xml +++ b/conf/smart-default.xml @@ -649,6 +649,26 @@ + + smart.rest.server.auth.predefined.password.encoder + noop + + Default encoder for predefined users passwords in case if password doesn't have {%encoderId%} prefix. + Possible values: + noop (for plaintext passwords), bcrypt, ldap, md4, md5, pbkdf2, scrypt, sha-1, sha-256. + + + + + smart.rest.server.auth.ldap.password.encoder + noop + + Default encoder for LDAP users passwords in case if password doesn't have {%encoderId%} prefix. + Possible values: + noop (for plaintext passwords), bcrypt, ldap, md4, md5, pbkdf2, scrypt, sha-1, sha-256. + + + smart.rest.server.auth.failures.logging.enabled true diff --git a/pom.xml b/pom.xml index ba1eea8c2d..235f00c025 100644 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,7 @@ 2.11 2.28.2 1.19.1 - 1.46 + 1.68 3.7.1 2.5.3 2.5.32 @@ -192,6 +192,11 @@ tape 1.2.3 + + org.bouncycastle + bcprov-jdk15on + ${bouncycastle.version} + com.typesafe.akka akka-actor_2.11 diff --git a/smart-integration/src/test/java/org/smartdata/integration/auth/TestWebServerLdapAuth.java b/smart-integration/src/test/java/org/smartdata/integration/auth/TestWebServerLdapAuth.java index d644abb949..022a05dbbc 100644 --- a/smart-integration/src/test/java/org/smartdata/integration/auth/TestWebServerLdapAuth.java +++ b/smart-integration/src/test/java/org/smartdata/integration/auth/TestWebServerLdapAuth.java @@ -22,6 +22,7 @@ import org.junit.BeforeClass; import org.junit.runners.Parameterized.Parameters; import org.smartdata.conf.SmartConf; +import org.smartdata.server.config.PasswordEncoderFactory.EncoderType; import org.smartdata.server.config.ldap.search.LdapSearchScope; import static org.smartdata.integration.auth.TestWebServerAuth.TestParams.ExpectedResult.FAIL; @@ -35,6 +36,7 @@ import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_GROUP_MEMBER_ATTR; import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_GROUP_NAME_ATTR; import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_GROUP_OBJECT_DEFAULT; +import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_PASSWORD_ENCODER; import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_SEARCH_ADDITIONAL_FILTER; import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_SEARCH_BASE; import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_URL; @@ -104,6 +106,12 @@ public static TestParams[] parameters() { searchWithAdditionalSearch(BIND)), new TestParams("ben", "bens_password", searchWithAdditionalSearch(BIND), FAIL), + new TestParams("hashed_bob", "b0bs_p4ssw0rd", + passwordCompareWithPasswordEncoding(EncoderType.NOOP)), + new TestParams("hashed_july", "kitty_cat", + passwordCompareWithPasswordEncoding(EncoderType.NOOP), FAIL), + new TestParams("hashed_july", "kitty_cat", + passwordCompareWithPasswordEncoding(EncoderType.PBKDF2)), new TestParams("july", "kitty_cat", searchByCustomSearchSeveralUsers(BIND), FAIL), new TestParams("july", "kitty_cat", @@ -120,6 +128,16 @@ private static SmartConf searchByName(AuthType authType) { return conf; } + private static SmartConf passwordCompareWithPasswordEncoding(EncoderType defaultEncoder) { + SmartConf conf = baseConf(); + conf.set(SMART_REST_SERVER_LDAP_AUTH_TYPE, PASSWORD_COMPARE.toString()); + conf.setEnum(SMART_REST_SERVER_LDAP_USER_SEARCH_SCOPE, LdapSearchScope.SUBTREE); + conf.set(SMART_REST_SERVER_LDAP_PASSWORD_ENCODER, defaultEncoder.getId()); + + conf.set(TEST_PARAM_NAME_OPTION, "passwordCompareWithPasswordEncoding"); + return conf; + } + private static SmartConf searchByCustomSearch(AuthType authType) { SmartConf conf = baseConf(); conf.set(SMART_REST_SERVER_LDAP_AUTH_TYPE, authType.toString()); diff --git a/smart-integration/src/test/java/org/smartdata/integration/auth/TestWebServerPredefinedUsersAuth.java b/smart-integration/src/test/java/org/smartdata/integration/auth/TestWebServerPredefinedUsersAuth.java new file mode 100644 index 0000000000..03f6d8602f --- /dev/null +++ b/smart-integration/src/test/java/org/smartdata/integration/auth/TestWebServerPredefinedUsersAuth.java @@ -0,0 +1,95 @@ +/** + * 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.smartdata.integration.auth; + +import org.junit.runners.Parameterized.Parameters; +import org.smartdata.conf.SmartConf; + +import static org.smartdata.integration.auth.TestWebServerAuth.TestParams.ExpectedResult.FAIL; +import static org.smartdata.server.config.ConfigKeys.PREDEFINED_BASIC_AUTH_ENABLED; +import static org.smartdata.server.config.ConfigKeys.PREDEFINED_USERS; +import static org.smartdata.server.config.ConfigKeys.PREDEFINED_USERS_PASSWORD_ENCODER; + +public class TestWebServerPredefinedUsersAuth extends TestWebServerAuth { + + @Parameters(name = "{0}") + public static TestParams[] parameters() { + return new TestParams[]{ + new TestParams("user1", "pass1", plainTextCredentials()), + new TestParams("user2", "pass2", plainTextCredentials()), + new TestParams("unknown", "pass", plainTextCredentials(), FAIL), + new TestParams("user1", "pass1", bcryptCredentials()), + new TestParams("unknown2", "unknown", bcryptCredentials(), FAIL), + new TestParams("user2", "pass2", pbkdf2Credentials()), + new TestParams("unknown3", "unknown2", pbkdf2Credentials(), FAIL), + new TestParams("user1", "pass1", hashCredentialsWithDefaultEncoder()), + new TestParams("user2", "pass2", hashCredentialsWithDefaultEncoder(), FAIL) + }; + } + + private static SmartConf plainTextCredentials() { + SmartConf conf = baseConf(); + conf.set(PREDEFINED_USERS, "user1:pass1,user2:pass2"); + + conf.set(TEST_PARAM_NAME_OPTION, "plainTextCredentials"); + return conf; + } + + private static SmartConf bcryptCredentials() { + SmartConf conf = baseConf(); + conf.set(PREDEFINED_USERS, + // user1:pass1 + "user1:{bcrypt}$2a$10$.E/VDhdRt85NyN7/kzrE8uCVu6Ey3tbGR/efyOOIgw.e4BeYcb8.G," + // user2:pass2 + + "user2:{bcrypt}$2a$10$kEZuKAa9sL3C2YlTwSo7meNfvjkZMHGUkf6T/E6.eSO6.OIt.Q9qa"); + + conf.set(TEST_PARAM_NAME_OPTION, "bcryptCredentials"); + return conf; + } + + private static SmartConf pbkdf2Credentials() { + SmartConf conf = baseConf(); + // user1:pass1 + conf.set(PREDEFINED_USERS, "user1:{pbkdf2}" + + "8d831f4f23dc4f0f43bdabd573372c6ea091f8fb55cb7d0242534d35f7fe14bb932da695a20c3c23," + // user2:pass2 + + "user2:{pbkdf2}" + + "b4f2a5e4a827e83fae4346e2ffc806ee61a79ff40aeea3afea7acac2d6651fe17869968c55902618"); + + conf.set(TEST_PARAM_NAME_OPTION, "pbkdf2Credentials"); + return conf; + } + + private static SmartConf hashCredentialsWithDefaultEncoder() { + SmartConf conf = baseConf(); + // user1:pass1 + conf.set(PREDEFINED_USERS, "user1:$e0801$LBsB5RY9rXDw343mFrViv74uFhi6SRqE5R8KirZjIcDd966x" + + "TJbzrDVgtgnFAX8h+zU+6+raIsPnckqF4D7Smg==" + + "$l72uGTcby92qvT6vd+gekAjUL/WJSDvujf///s/l+sQ="); + conf.set(PREDEFINED_USERS_PASSWORD_ENCODER, "scrypt"); + + conf.set(TEST_PARAM_NAME_OPTION, "hashCredentialsWithDefaultEncoder"); + return conf; + } + + private static SmartConf baseConf() { + SmartConf conf = new SmartConf(); + conf.setBoolean(PREDEFINED_BASIC_AUTH_ENABLED, true); + return conf; + } +} diff --git a/smart-integration/src/test/resources/ldap-server.ldif b/smart-integration/src/test/resources/ldap-server.ldif index 4ab4dea463..2f333a06ed 100644 --- a/smart-integration/src/test/resources/ldap-server.ldif +++ b/smart-integration/src/test/resources/ldap-server.ldif @@ -61,3 +61,24 @@ cn: Bob Hamilton sn: Hamilton uid: bob userPassword: b0bs_p4ssw0rd + +dn: uid=hashed_bob,ou=people,dc=ssm,dc=test +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +cn: Bob Hashed +sn: Hashed +uid: hashed_bob +userPassword: {bcrypt}$2a$10$.R5BfExkMoBcgd2TXWgz0efIsL8NvZr3dcFfeuZpTNjvTmUPtQjZ6 + +dn: uid=hashed_july,ou=people,dc=ssm,dc=test +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +cn: July Hashed +sn: Hashed +uid: hashed_july +userPassword: 6adbc4b34934648df697c5e5d6c422e89ca5e67239621d24961a1404c94c716e52a8d0bf63d9c994 +memberOf: cn=developers,ou=groups,dc=ssm,dc=test diff --git a/smart-web-server/pom.xml b/smart-web-server/pom.xml index 3b6e04f60b..fc0b1bef65 100644 --- a/smart-web-server/pom.xml +++ b/smart-web-server/pom.xml @@ -143,6 +143,11 @@ ${lombok-mapstruct-binding.version} + + org.bouncycastle + bcprov-jdk15on + + junit junit diff --git a/smart-web-server/src/main/java/org/smartdata/server/config/ConfigKeys.java b/smart-web-server/src/main/java/org/smartdata/server/config/ConfigKeys.java index 3d673217c4..b9283c726e 100644 --- a/smart-web-server/src/main/java/org/smartdata/server/config/ConfigKeys.java +++ b/smart-web-server/src/main/java/org/smartdata/server/config/ConfigKeys.java @@ -35,6 +35,12 @@ public class ConfigKeys { public static final String PREDEFINED_USERS = "smart.rest.server.auth.predefined.users"; + public static final String PREDEFINED_USERS_PASSWORD_ENCODER = + "smart.rest.server.auth.predefined.password.encoder"; + + public static final String PREDEFINED_USERS_PASSWORD_ENCODER_DEFAULT = + "noop"; + public static final String SMART_REST_SERVER_KEYTAB_FILE_KEY = "smart.rest.server.auth.spnego.keytab"; @@ -143,4 +149,11 @@ public class ConfigKeys { public static final String SMART_REST_SERVER_AUTH_ERRORS_LOGGING_ENABLED = "smart.rest.server.auth.failures.logging.enabled"; + + public static final String SMART_REST_SERVER_LDAP_PASSWORD_ENCODER = + "smart.rest.server.auth.ldap.password.encoder"; + + public static final String SMART_REST_SERVER_LDAP_PASSWORD_ENCODER_DEFAULT = + "noop"; + } diff --git a/smart-web-server/src/main/java/org/smartdata/server/config/LdapAuthSecurityConfiguration.java b/smart-web-server/src/main/java/org/smartdata/server/config/LdapAuthSecurityConfiguration.java index 99f03bec0d..46d6286727 100644 --- a/smart-web-server/src/main/java/org/smartdata/server/config/LdapAuthSecurityConfiguration.java +++ b/smart-web-server/src/main/java/org/smartdata/server/config/LdapAuthSecurityConfiguration.java @@ -18,13 +18,14 @@ package org.smartdata.server.config; import org.smartdata.conf.SmartConf; +import org.smartdata.server.config.PasswordEncoderFactory.EncoderType; import org.smartdata.server.config.ldap.search.LdapUserSearchFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.ldap.core.support.LdapContextSource; import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.authentication.BindAuthenticator; import org.springframework.security.ldap.authentication.LdapAuthenticationProvider; @@ -41,6 +42,8 @@ import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_AUTH_TYPE; import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_BIND_PASSWORD; import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_BIND_USER_DN; +import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_PASSWORD_ENCODER; +import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_PASSWORD_ENCODER_DEFAULT; import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_SEARCH_BASE; import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_SEARCH_BASE_DEFAULT; import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_URL; @@ -113,8 +116,7 @@ public LdapAuthenticator passswordCompareLdapAuthenticator( PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(contextSource); authenticator.setPasswordAttributeName(passwordAttribute); - // todo support password encoding for LDAP and predefined users auth provider - authenticator.setPasswordEncoder(NoOpPasswordEncoder.getInstance()); + authenticator.setPasswordEncoder(ldapPasswordEncoder(conf)); authenticator.setUserSearch(ldapUserSearch); return authenticator; } @@ -123,4 +125,12 @@ public LdapAuthenticator passswordCompareLdapAuthenticator( public AuthenticationProvider ldapAuthenticationProvider(LdapAuthenticator authenticator) { return new LdapAuthenticationProvider(authenticator); } + + private PasswordEncoder ldapPasswordEncoder(SmartConf smartConf) { + String defaultEncoder = smartConf.get( + SMART_REST_SERVER_LDAP_PASSWORD_ENCODER, + SMART_REST_SERVER_LDAP_PASSWORD_ENCODER_DEFAULT); + + return PasswordEncoderFactory.build(EncoderType.fromId(defaultEncoder)); + } } diff --git a/smart-web-server/src/main/java/org/smartdata/server/config/PasswordEncoderFactory.java b/smart-web-server/src/main/java/org/smartdata/server/config/PasswordEncoderFactory.java new file mode 100644 index 0000000000..fcda381024 --- /dev/null +++ b/smart-web-server/src/main/java/org/smartdata/server/config/PasswordEncoderFactory.java @@ -0,0 +1,85 @@ +/** + * 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.smartdata.server.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.DelegatingPasswordEncoder; +import org.springframework.security.crypto.password.LdapShaPasswordEncoder; +import org.springframework.security.crypto.password.Md4PasswordEncoder; +import org.springframework.security.crypto.password.MessageDigestPasswordEncoder; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; +import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enhanced version of Spring's + * {@link PasswordEncoderFactories#createDelegatingPasswordEncoder}. + */ +public interface PasswordEncoderFactory { + + @RequiredArgsConstructor + @Getter + enum EncoderType { + NOOP("noop"), + BCRYPT("bcrypt"), + LDAP("ldap"), + MD4("md4"), + MD5("md5"), + PBKDF2("pbkdf2"), + SCRYPT("scrypt"), + SHA_1("sha-1"), + SHA_256("sha-256"); + + private final String id; + + public static EncoderType fromId(String id) { + for (EncoderType encoderType : EncoderType.values()) { + if (encoderType.id.equals(id)) { + return encoderType; + } + } + throw new IllegalArgumentException("Wrong encoder id: " + id); + } + } + + @SuppressWarnings("deprecation") + static PasswordEncoder build(EncoderType defaultEncoderType) { + Map encoders = new HashMap<>(); + encoders.put(EncoderType.BCRYPT.id, new BCryptPasswordEncoder()); + encoders.put(EncoderType.LDAP.id, new LdapShaPasswordEncoder()); + encoders.put(EncoderType.MD4.id, new Md4PasswordEncoder()); + encoders.put(EncoderType.MD5.id, new MessageDigestPasswordEncoder("MD5")); + encoders.put(EncoderType.NOOP.id, NoOpPasswordEncoder.getInstance()); + encoders.put(EncoderType.PBKDF2.id, new Pbkdf2PasswordEncoder()); + encoders.put(EncoderType.SCRYPT.id, new SCryptPasswordEncoder()); + encoders.put(EncoderType.SHA_1.id, new MessageDigestPasswordEncoder("SHA-1")); + encoders.put(EncoderType.SHA_256.id, new MessageDigestPasswordEncoder("SHA-256")); + + DelegatingPasswordEncoder passwordEncoder = + new DelegatingPasswordEncoder(defaultEncoderType.id, encoders); + passwordEncoder.setDefaultPasswordEncoderForMatches(encoders.get(defaultEncoderType.id)); + return passwordEncoder; + } +} diff --git a/smart-web-server/src/main/java/org/smartdata/server/config/PredefinedUsersSecurityConfiguration.java b/smart-web-server/src/main/java/org/smartdata/server/config/PredefinedUsersSecurityConfiguration.java index d8e8e1fdd3..8f585ae383 100644 --- a/smart-web-server/src/main/java/org/smartdata/server/config/PredefinedUsersSecurityConfiguration.java +++ b/smart-web-server/src/main/java/org/smartdata/server/config/PredefinedUsersSecurityConfiguration.java @@ -18,17 +18,21 @@ package org.smartdata.server.config; import org.smartdata.conf.SmartConf; +import org.smartdata.server.config.PasswordEncoderFactory.EncoderType; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import java.util.List; import static org.smartdata.server.config.ConfigKeys.PREDEFINED_BASIC_AUTH_ENABLED; +import static org.smartdata.server.config.ConfigKeys.PREDEFINED_USERS_PASSWORD_ENCODER; +import static org.smartdata.server.config.ConfigKeys.PREDEFINED_USERS_PASSWORD_ENCODER_DEFAULT; import static org.smartdata.server.config.ConfigKeys.WEB_SECURITY_ENABLED; import static org.smartdata.server.util.ConfigUtils.parsePredefinedUsers; @@ -45,6 +49,8 @@ public AuthenticationProvider predefinedUsersAuthenticationProvider( List predefinedUsers = parsePredefinedUsers(smartConf); predefinedUsersProvider.setUserDetailsService( new InMemoryUserDetailsManager(predefinedUsers)); + predefinedUsersProvider.setPasswordEncoder( + predefinedUsersPasswordEncoder(smartConf)); return predefinedUsersProvider; } @@ -52,4 +58,12 @@ public AuthenticationProvider predefinedUsersAuthenticationProvider( public SsmAuthHttpConfigurer basicAuthHttpConfigurer() { return new SecurityConfiguration.BasicAuthHttpConfigurer(); } + + private PasswordEncoder predefinedUsersPasswordEncoder(SmartConf smartConf) { + String defaultEncoder = smartConf.get( + PREDEFINED_USERS_PASSWORD_ENCODER, + PREDEFINED_USERS_PASSWORD_ENCODER_DEFAULT); + + return PasswordEncoderFactory.build(EncoderType.fromId(defaultEncoder)); + } } diff --git a/smart-web-server/src/main/java/org/smartdata/server/util/ConfigUtils.java b/smart-web-server/src/main/java/org/smartdata/server/util/ConfigUtils.java index f52cb4c83f..2a2f988e62 100644 --- a/smart-web-server/src/main/java/org/smartdata/server/util/ConfigUtils.java +++ b/smart-web-server/src/main/java/org/smartdata/server/util/ConfigUtils.java @@ -28,7 +28,6 @@ public class ConfigUtils { private static final String USERNAME_PASSWORD_DELIMITER = ":"; - private static final String NO_OP_ENCODER_PREFIX = "{noop}"; public static List parsePredefinedUsers(SmartConf smartConf) { return smartConf.getStringCollection(ConfigKeys.PREDEFINED_USERS) @@ -45,7 +44,7 @@ private static UserDetails parsePredefinedUser(String rawUser) { } return User.withUsername(userParts[0]) - .password(NO_OP_ENCODER_PREFIX + userParts[1]) + .password(userParts[1]) .authorities(Collections.emptyList()) .build(); } diff --git a/smart-web-server/src/test/java/org/smartdata/server/util/ConfigUtilsTest.java b/smart-web-server/src/test/java/org/smartdata/server/util/ConfigUtilsTest.java index 38b28269a3..29b0a5e66e 100644 --- a/smart-web-server/src/test/java/org/smartdata/server/util/ConfigUtilsTest.java +++ b/smart-web-server/src/test/java/org/smartdata/server/util/ConfigUtilsTest.java @@ -76,7 +76,7 @@ public void testParseUsers() { private UserDetails buildUser(String user, String password) { return User.withUsername(user) - .password("{noop}" + password) + .password(password) .roles() .build(); }