diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 52e30fc48cf..3396478f9c5 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -68,7 +68,7 @@ limitations under the License. | Tag sync | `tagsync/` | Atlas/Kafka; writes to Admin | **In** | | Audit framework | `agents-audit/`, `audit-server/` | Solr/OpenSearch/Kafka/HDFS/DB audit sinks | **In** | | Ranger KMS | `kms/`, `plugin-kms/` | Key store (DB/HSM), HDFS NameNode/DataNode | **In** (model at its own trust level — handles key material) | -| Authentication | `ranger-authn/`, `unixauthservice/`, `agents-cred/`, `credentialbuilder/` | Kerberos/SPNEGO/LDAP; credential stores | **In** | +| Authentication | `ranger-authn/`, `security-admin/` (PAM/JAAS login), `agents-cred/`, `credentialbuilder/` | Kerberos/SPNEGO/LDAP/PAM; credential stores | **In** | | Examples / sample app + tools | `ranger-examples/`, `ranger-tools/` *(documented — repo layout)* | Demo / utility | **In** *(maintainer, 2026-06-12 — §14 Q4)* | | Build / install / migration / ugsync tooling | `distro/`, `agents-installer/`, `migration-util/`, ugsync `filesourceusersynctool`/`ldapconfigchecktool`, `dev-support/` | Build/deploy host | **Out** *(maintainer, 2026-06-12 — §14 Q4)* | diff --git a/dev-support/ranger-docker/scripts/usersync/ranger-usersync.sh b/dev-support/ranger-docker/scripts/usersync/ranger-usersync.sh index 18346c294dd..457b3518d5b 100755 --- a/dev-support/ranger-docker/scripts/usersync/ranger-usersync.sh +++ b/dev-support/ranger-docker/scripts/usersync/ranger-usersync.sh @@ -47,7 +47,7 @@ fi cd ${RANGER_HOME}/usersync && ./start.sh -RANGER_USERSYNC_PID=`ps -ef | grep -v grep | grep -i "org.apache.ranger.authentication.UnixAuthenticationService" | awk '{print $2}'` +RANGER_USERSYNC_PID=`ps -ef | grep -v grep | grep -i "proc_rangerusersync" | awk '{print $2}'` # prevent the container from exiting if [ -z "$RANGER_USERSYNC_PID" ] diff --git a/distro/pom.xml b/distro/pom.xml index fa97c3fbd12..5b41c9003d7 100644 --- a/distro/pom.xml +++ b/distro/pom.xml @@ -464,18 +464,6 @@ ${project.version} provided - - org.apache.ranger - unixauthclient - ${project.version} - provided - - - org.apache.ranger - unixauthservice - ${project.version} - provided - org.apache.ranger unixusersync @@ -589,6 +577,35 @@ + + ranger-usersync + + + + maven-assembly-plugin + ${assembly.plugin.version} + + ranger-${project.version} + ../target + + + + + single + + package + + false + + src/main/assembly/usersync.xml + + + + + + + + ranger-hdfs-plugin diff --git a/distro/src/main/assembly/admin-web.xml b/distro/src/main/assembly/admin-web.xml index b28ef08121c..46483f66d83 100644 --- a/distro/src/main/assembly/admin-web.xml +++ b/distro/src/main/assembly/admin-web.xml @@ -38,7 +38,6 @@ true - org.apache.ranger:unixauthclient ews/ranger_jaas diff --git a/distro/src/main/assembly/usersync.xml b/distro/src/main/assembly/usersync.xml index 0b3ed0868f4..6949a4114f7 100644 --- a/distro/src/main/assembly/usersync.xml +++ b/distro/src/main/assembly/usersync.xml @@ -26,7 +26,6 @@ true - org.apache.ranger:unixauthservice org.apache.ranger:unixusersync @@ -126,20 +125,30 @@ + + ../ugsync/target + dist + + unixusersync-${project.version}.jar + + 644 + + + ../ugsync/target/lib + lib + 644 + 755 644 conf.dist - ../unixauthservice/conf.dist - - jaas.conf - + ../ugsync/conf.dist 755 544 - ../unixauthservice/scripts + ../ugsync/scripts *.properties initd @@ -149,7 +158,7 @@ 755 700 - ../unixauthservice/scripts + ../ugsync/scripts *.properties @@ -158,34 +167,16 @@ 755 644 conf.dist/cert - ../unixauthservice/cert - - - 755 - 750 - native - ../unixauthnative/target - - credValidator.* - - - - 755 - 750 - native - ../unixauthpam/target - - pamCredValidator.* - + ../ugsync/cert 755 + 444 ${project.build.directory} version - 444 @@ -243,7 +234,7 @@ - ${project.parent.basedir}/unixauthservice/scripts/initd + ${project.parent.basedir}/ugsync/scripts/initd ranger-usersync 755 diff --git a/migration-util/ambari2.0-hdp2.2-ranger0.40/bin/import_ranger_to_ambari.py b/migration-util/ambari2.0-hdp2.2-ranger0.40/bin/import_ranger_to_ambari.py index 615eb358d8c..36129907452 100755 --- a/migration-util/ambari2.0-hdp2.2-ranger0.40/bin/import_ranger_to_ambari.py +++ b/migration-util/ambari2.0-hdp2.2-ranger0.40/bin/import_ranger_to_ambari.py @@ -176,9 +176,6 @@ def add_advanced_ranger_configurations(add_admin_or_usersync, ranger_service_pro advanced_admin_properties['policymgr_external_url'] = ranger_service_properties_from_file['xa.webapp.url.root'] advanced_admin_properties['policymgr_http_enabled'] = ranger_service_properties_from_file['http.enabled'] advanced_admin_properties['authentication_method'] = get_authentication_method() - advanced_admin_properties['remoteLoginEnabled'] = ranger_service_properties_from_file.get('remoteLoginEnabled','false') - advanced_admin_properties['authServiceHostName'] = ranger_service_properties_from_file.get('authServiceHostName','localhost') - advanced_admin_properties['authServicePort'] = ranger_service_properties_from_file.get('authServicePort','5151') advanced_admin_properties['xa_ldap_url'] = ranger_service_properties_from_file['xa_ldap_url'] advanced_admin_properties['xa_ldap_userDNpattern'] = ranger_service_properties_from_file['xa_ldap_userDNpattern'] advanced_admin_properties['xa_ldap_groupSearchBase'] = ranger_service_properties_from_file['xa_ldap_groupSearchBase'] @@ -426,12 +423,10 @@ def get_additional_properties_for_admin(ranger_admin_properties_from_file): ranger_webserver_properties_path = os.path.join(ranger_conf_path, 'ranger_webserver.properties') ranger_ldap_properties_path = os.path.join(ranger_conf_path, 'xa_ldap.properties') ranger_system_properties_path = os.path.join(ranger_conf_path, 'xa_system.properties') - ranger_unixauth_properties_path = os.path.join(ranger_conf_path, 'ranger_jaas', 'unixauth.properties') try: ranger_admin_properties_from_file = import_properties_from_file(ranger_webserver_properties_path, ranger_admin_properties_from_file) ranger_admin_properties_from_file = import_properties_from_file(ranger_ldap_properties_path, ranger_admin_properties_from_file) ranger_admin_properties_from_file = import_properties_from_file(ranger_system_properties_path, ranger_admin_properties_from_file) - ranger_admin_properties_from_file = import_properties_from_file(ranger_unixauth_properties_path, ranger_admin_properties_from_file) except Exception, e: print "Error loading property files: ", str(e) @@ -500,12 +495,14 @@ def get_additional_properties_for_admin(ranger_admin_properties_from_file): def get_additional_properties_for_usersync(ranger_usersync_properties_from_file): ranger_conf_path = '/etc/ranger/usersync/conf' - unix_auth_properties_path = os.path.join(ranger_conf_path, 'unixauthservice.properties') - ranger_usersync_properties_from_file = import_properties_from_file(unix_auth_properties_path, ranger_usersync_properties_from_file) - if (('unix'.lower()) in str(ranger_usersync_properties_from_file['usergroupSync.source.impl.class']).lower()): + ranger_ugsync_default_site_xml_properties = os.path.join(ranger_conf_path, 'ranger-ugsync-default.xml') + ranger_ugsync_site_xml_properties = os.path.join(ranger_conf_path, 'ranger-ugsync-site.xml') + ranger_usersync_properties_from_file = import_properties_from_xml(ranger_ugsync_default_site_xml_properties, ranger_usersync_properties_from_file) + ranger_usersync_properties_from_file = import_properties_from_xml(ranger_ugsync_site_xml_properties, ranger_usersync_properties_from_file) + if (('unix'.lower()) in str(ranger_usersync_properties_from_file.get('ranger.usersync.source.impl.class', '')).lower()): print('sync_source is unix') ranger_usersync_properties_from_file['SYNC_SOURCE'] = 'unix' - if (('ldap'.lower()) in str(ranger_usersync_properties_from_file['usergroupSync.source.impl.class']).lower()): + if (('ldap'.lower()) in str(ranger_usersync_properties_from_file.get('ranger.usersync.source.impl.class', '')).lower()): print('sync source is ldap') ranger_usersync_properties_from_file['SYNC_SOURCE'] = 'ldap' return ranger_usersync_properties_from_file diff --git a/migration-util/ambari2.1-hdp2.3-ranger0.50/bin/import_ranger_to_ambari.py b/migration-util/ambari2.1-hdp2.3-ranger0.50/bin/import_ranger_to_ambari.py index 195a116ddda..4603064b759 100755 --- a/migration-util/ambari2.1-hdp2.3-ranger0.50/bin/import_ranger_to_ambari.py +++ b/migration-util/ambari2.1-hdp2.3-ranger0.50/bin/import_ranger_to_ambari.py @@ -303,9 +303,6 @@ def add_advanced_ranger_configurations(add_admin_or_usersync, ranger_service_pro advanced_ranger_admin_site_properties['ranger.jpa.audit.jdbc.user'] = ranger_service_properties_from_file.get('ranger.jpa.audit.jdbc.user','{{ranger_audit_db_user}}') advanced_ranger_admin_site_properties['ranger.jpa.audit.jdbc.password'] = ranger_service_properties_from_file.get('ranger.jpa.audit.jdbc.password','_') advanced_ranger_admin_site_properties['ranger.jpa.audit.jdbc.credential.alias'] = ranger_service_properties_from_file.get('ranger.jpa.audit.jdbc.credential.alias','rangeraudit') - advanced_ranger_admin_site_properties['ranger.unixauth.remote.login.enabled'] = ranger_service_properties_from_file.get('ranger.unixauth.remote.login.enabled','true') - advanced_ranger_admin_site_properties['ranger.unixauth.service.hostname'] = ranger_service_properties_from_file.get('ranger.unixauth.service.hostname','localhost') - advanced_ranger_admin_site_properties['ranger.unixauth.service.port'] = ranger_service_properties_from_file.get('ranger.unixauth.service.port','5151') advanced_ranger_admin_site_properties['ranger.jpa.jdbc.dialect'] = ranger_service_properties_from_file.get('ranger.jpa.jdbc.dialect','{{jdbc_dialect}}') advanced_ranger_admin_site_properties['ranger.jpa.audit.jdbc.dialect'] = ranger_service_properties_from_file.get('ranger.jpa.audit.jdbc.dialect','{{jdbc_dialect}') advanced_ranger_admin_site_properties['ranger.audit.solr.zookeepers'] = ranger_service_properties_from_file.get('ranger.audit.solr.zookeepers','NONE') @@ -344,13 +341,10 @@ def add_advanced_ranger_configurations(add_admin_or_usersync, ranger_service_pro sys.exit(1) elif (add_admin_or_usersync == 2): - advanced_user_sync_properties['ranger.usersync.port'] = ranger_service_properties_from_file['ranger.usersync.port'] - advanced_user_sync_properties['ranger.usersync.ssl'] = ranger_service_properties_from_file['ranger.usersync.ssl'] advanced_user_sync_properties['ranger.usersync.keystore.file'] = ranger_service_properties_from_file['ranger.usersync.keystore.file'] advanced_user_sync_properties['ranger.usersync.keystore.password'] = ranger_service_properties_from_file.get('ranger.usersync.keystore.password','UnIx529p') advanced_user_sync_properties['ranger.usersync.truststore.file'] = ranger_service_properties_from_file.get('ranger.usersync.truststore.file','/usr/hdp/current/ranger-usersync/conf/mytruststore.jks') advanced_user_sync_properties['ranger.usersync.truststore.password'] = ranger_service_properties_from_file.get('ranger.usersync.truststore.password','changeit') - advanced_user_sync_properties['ranger.usersync.passwordvalidator.path'] = ranger_service_properties_from_file['ranger.usersync.passwordvalidator.path'] advanced_user_sync_properties['ranger.usersync.sink.impl.class'] = ranger_service_properties_from_file['ranger.usersync.sink.impl.class'] advanced_user_sync_properties['ranger.usersync.policymanager.baseURL'] = ranger_service_properties_from_file['ranger.usersync.policymanager.baseURL'] advanced_user_sync_properties['ranger.usersync.policymanager.maxrecordsperapicall'] = ranger_service_properties_from_file['ranger.usersync.policymanager.maxrecordsperapicall'] diff --git a/mkdocs/docs/project/release-process.md b/mkdocs/docs/project/release-process.md index a539c0a6ad4..d025f1460d4 100644 --- a/mkdocs/docs/project/release-process.md +++ b/mkdocs/docs/project/release-process.md @@ -101,7 +101,6 @@ mvn versions:set -DnewVersion=${RANGER_VERSION} -DgenerateBackupPoms=false # Also, manually update versions in: # - dev-support/ranger-docker/.env # - docs/pom.xml -# - unixauthnative/pom.xml # - ranger-trino-plugin-shim/pom.xml ``` diff --git a/pom.xml b/pom.xml index b27251823f6..cb47d869a54 100755 --- a/pom.xml +++ b/pom.xml @@ -976,8 +976,6 @@ ugsync ugsync-util ugsync/ldapconfigchecktool/ldapconfigcheck - unixauthclient - unixauthservice @@ -1327,22 +1325,6 @@ ugsync ugsync-util ugsync/ldapconfigchecktool/ldapconfigcheck - unixauthclient - unixauthservice - - - - linux-pam - - - linux - - - /usr/include/security/pam_appl.h - - - - unixauthpam @@ -1415,8 +1397,6 @@ ugsync ugsync-util ugsync/ldapconfigchecktool/ldapconfigcheck - unixauthclient - unixauthservice diff --git a/security-admin/pom.xml b/security-admin/pom.xml index 4df6c6a8228..e1d09cab93c 100644 --- a/security-admin/pom.xml +++ b/security-admin/pom.xml @@ -545,11 +545,6 @@ - - org.apache.ranger - unixauthclient - ${project.version} - org.apache.solr solr-solrj @@ -710,6 +705,11 @@ org.glassfish.jersey.media jersey-media-multipart + + org.kohsuke + libpam4j + ${libpam4j.version} + org.slf4j log4j-over-slf4j diff --git a/security-admin/scripts/install.properties b/security-admin/scripts/install.properties index 6a6ababc0fb..52b0701bd6c 100644 --- a/security-admin/scripts/install.properties +++ b/security-admin/scripts/install.properties @@ -173,23 +173,11 @@ unix_group=ranger # # -# UNIX authentication service for Policy Manager -# -# PolicyManager can authenticate using UNIX username/password -# The UNIX server specified here as authServiceHostName needs to be installed with ranger-unix-ugsync package. -# Once the service is installed on authServiceHostName, the UNIX username/password from the host can be used to login into policy manager -# -# ** The installation of ranger-unix-ugsync package can be installed after the policymanager installation is finished. +# Authentication method for Policy Manager UI login. +# UNIX uses PAM on the Ranger Admin host (local OS accounts). # #LDAP|ACTIVE_DIRECTORY|UNIX|NONE authentication_method=NONE -remoteLoginEnabled=true -authServiceHostName=localhost -authServicePort=5151 -ranger_unixauth_keystore=keystore.jks -ranger_unixauth_keystore_password=password -ranger_unixauth_truststore=cacerts -ranger_unixauth_truststore_password=changeit ####LDAP settings - Required only if have selected LDAP authentication #### # diff --git a/security-admin/scripts/setup.sh b/security-admin/scripts/setup.sh index b0bc262148a..35504cdd00d 100755 --- a/security-admin/scripts/setup.sh +++ b/security-admin/scripts/setup.sh @@ -129,13 +129,6 @@ unix_user=$(get_prop 'unix_user' $PROPFILE) unix_user_pwd=$(get_prop 'unix_user_pwd' $PROPFILE) unix_group=$(get_prop 'unix_group' $PROPFILE) authentication_method=$(get_prop 'authentication_method' $PROPFILE) -remoteLoginEnabled=$(get_prop_or_default 'remoteLoginEnabled' $PROPFILE 'false') -authServiceHostName=$(get_prop_or_default 'authServiceHostName' $PROPFILE '') -authServicePort=$(get_prop_or_default 'authServicePort' $PROPFILE '') -ranger_unixauth_keystore=$(get_prop_or_default 'ranger_unixauth_keystore' $PROPFILE '') -ranger_unixauth_keystore_password=$(get_prop_or_default 'ranger_unixauth_keystore_password' $PROPFILE '') -ranger_unixauth_truststore=$(get_prop_or_default 'ranger_unixauth_truststore' $PROPFILE '') -ranger_unixauth_truststore_password=$(get_prop_or_default 'ranger_unixauth_truststore_password' $PROPFILE '') xa_ldap_url=$(get_prop 'xa_ldap_url' $PROPFILE) xa_ldap_userDNpattern=$(get_prop 'xa_ldap_userDNpattern' $PROPFILE) xa_ldap_groupSearchBase=$(get_prop 'xa_ldap_groupSearchBase' $PROPFILE) @@ -1219,72 +1212,9 @@ update_properties() { fi fi - if [ "${ranger_unixauth_keystore}" != "" ] && [ "${ranger_unixauth_keystore_password}" != "" ] - then - propertyName=ranger.unixauth.keystore - newPropertyValue="${ranger_unixauth_keystore}" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $to_file_default - - ranger_unixauth_keystore_alias=unixAuthKeyStoreAlias - propertyName=ranger.unixauth.keystore.credential.alias - newPropertyValue="${ranger_unixauth_keystore_alias}" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $to_file_default - if [ "${keystore}" != "" ] - then - propertyName=ranger.unixauth.keystore.password - newPropertyValue="_" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $to_file_default - $PYTHON_COMMAND_INVOKER ranger_credential_helper.py -l "cred/lib/*" -f "$keystore" -k "$ranger_unixauth_keystore_alias" -v "$ranger_unixauth_keystore_password" -c 1 - - if test -f "${keystore}"; then - chown -R ${unix_user}:${unix_group} ${keystore} - else - propertyName=ranger.unixauth.keystore.password - newPropertyValue="${ranger_unixauth_keystore_password}" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $to_file_default - fi - else - propertyName=ranger.unixauth.keystore.password - newPropertyValue="${ranger_unixauth_keystore_password}" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $to_file_default - fi - fi - - if [ "${ranger_unixauth_truststore}" != "" ] && [ "${ranger_unixauth_truststore_password}" != "" ] - then - propertyName=ranger.unixauth.truststore - newPropertyValue="${ranger_unixauth_truststore}" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $to_file_default - - ranger_unixauth_truststore_alias=unixAuthTrustStoreAlias - propertyName=ranger.unixauth.truststore.credential.alias - newPropertyValue="${ranger_unixauth_truststore_alias}" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $to_file_default - - if [ "${keystore}" != "" ] - then - propertyName=ranger.unixauth.truststore.password - newPropertyValue="_" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $to_file_default - $PYTHON_COMMAND_INVOKER ranger_credential_helper.py -l "cred/lib/*" -f "$keystore" -k "$ranger_unixauth_truststore_alias" -v "$ranger_unixauth_truststore_password" -c 1 - - if test -f $keystore; then - chown -R ${unix_user}:${unix_group} ${keystore} - else - propertyName=ranger.unixauth.truststore.password - newPropertyValue="${ranger_unixauth_truststore_password}" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $to_file_default - fi - else - propertyName=ranger.unixauth.truststore.password - newPropertyValue="${ranger_unixauth_truststore_password}" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $to_file_default - fi - fi - } -do_unixauth_setup() { +do_unix_authentication_method_setup() { ldap_file=$app_home/WEB-INF/classes/conf/ranger-admin-site.xml if test -f $ldap_file; then @@ -1293,18 +1223,6 @@ do_unixauth_setup() { propertyName=ranger.authentication.method newPropertyValue="${authentication_method}" updatePropertyToFilePy $propertyName "${newPropertyValue}" $ldap_file - - propertyName=ranger.unixauth.remote.login.enabled - newPropertyValue="${remoteLoginEnabled}" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $ldap_file - - propertyName=ranger.unixauth.service.hostname - newPropertyValue="${authServiceHostName}" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $ldap_file - - propertyName=ranger.unixauth.service.port - newPropertyValue="${authServicePort}" - updatePropertyToFilePy $propertyName "${newPropertyValue}" $ldap_file else log "[E] $ldap_file does not exists" ; exit 1; fi @@ -1501,8 +1419,8 @@ do_authentication_setup(){ log "[E] $ldap_file does not exists" ; exit 1; fi fi - if [ $authentication_method = "UNIX" ] ; then - do_unixauth_setup + if [ $authentication_method = "UNIX" ] || [ $authentication_method = "PAM" ] ; then + do_unix_authentication_method_setup fi if [ $authentication_method = "NONE" ] ; then diff --git a/security-admin/scripts/setup_authentication.sh b/security-admin/scripts/setup_authentication.sh index 9fe892f60c5..744a39da463 100755 --- a/security-admin/scripts/setup_authentication.sh +++ b/security-admin/scripts/setup_authentication.sh @@ -16,7 +16,7 @@ # limitations under the License. -USAGE="Usage: setup_authentication.sh [UNIX|LDAP|AD|NONE] " +USAGE="Usage: setup_authentication.sh [UNIX|PAM|LDAP|AD|NONE] " if [ $# -ne 2 ] then @@ -28,7 +28,7 @@ authentication_method=$1 path=$2 CONFIG_FILE=$path/WEB-INF/classes/conf/security-applicationContext.xml -if [ $authentication_method = "UNIX" ] ; then +if [ $authentication_method = "UNIX" ] || [ $authentication_method = "PAM" ] ; then echo $path; awk 'FNR==NR{ _[++d]=$0;next} /UNIX_BEAN_SETTINGS_START/{ diff --git a/security-admin/scripts/upgrade_admin.py b/security-admin/scripts/upgrade_admin.py index 1509a7f06db..08132c3d205 100755 --- a/security-admin/scripts/upgrade_admin.py +++ b/security-admin/scripts/upgrade_admin.py @@ -50,11 +50,9 @@ def showUsage(): # configDirectory: where OLD (champlain) configuration exists and NEW (dal) configuration is written to # configDirectory = '/etc/ranger/admin/conf' -rangerJAASDirectoryName = join(configDirectory,'ranger_jaas') xaSystemPropFile = 'xa_system.properties' ldapPropFile = 'xa_ldap.properties' -rangerJAASPropFile = 'unixauth.properties' securityContextFile = 'security-applicationContext.xml' webserverConfigFile = 'ranger_webserver.properties' @@ -193,9 +191,6 @@ def main(): ldapPropFileName = join(configDirectory, ldapPropFile) xaLdapProps = getPropertiesConfigMap (ldapPropFileName) - - jaasPropFileName = join(rangerJAASDirectoryName, rangerJAASPropFile) - unixauthProps = getPropertiesConfigMap (jaasPropFileName) webserverConfigFileName = join(configDirectory, webserverConfigFile) webconfig = getPropertiesConfigMap(webserverConfigFileName) @@ -206,8 +201,6 @@ def main(): xmlVal = xaSysProps[k] elif (k in list(xaLdapProps)): xmlVal = xaLdapProps[k] - elif (k in list(unixauthProps)): - xmlVal = unixauthProps[k] elif (k in list(webconfig)): xmlVal = webconfig[k] else: @@ -290,9 +283,6 @@ def main(): installProps['xa_ldap_groupRoleAttribute'] = xaLdapProps['xa_ldap_groupRoleAttribute'] elif ( len(root.findall(".//*[@id='jaasAuthProvider']",ns)) > 0 ): installProps['authentication_method'] = 'UNIX' - installProps['remoteLoginEnabled'] = unixauthProps['remoteLoginEnabled'] - installProps['authServiceHostName'] = unixauthProps['authServiceHostName'] - installProps['authServicePort'] = unixauthProps['authServicePort'] else: installProps['authentication_method'] = 'NONE' diff --git a/security-admin/src/bin/install.properties b/security-admin/src/bin/install.properties index 6b4854b7496..0dbee410833 100644 --- a/security-admin/src/bin/install.properties +++ b/security-admin/src/bin/install.properties @@ -83,19 +83,11 @@ unix_group=xasecure # # -# UNIX authentication service for Policy Manager -# -# PolicyManager can authenticate using UNIX username/password -# The UNIX server specified here as authServiceHostName needs to be installed with xasecure-unix-ugsync package. -# Once the service is installed on authServiceHostName, the UNIX username/password from the host can be used to login into policy manager -# -# ** The installation of xasecure-unix-ugsync package can be installed after the policymanager installation is finished. +# Authentication method for Policy Manager UI login. +# UNIX uses PAM on the Ranger Admin host (local OS accounts). # #LDAP|ACTIVE_DIRECTORY|UNIX|NONE authentication_method=NONE -remoteLoginEnabled=true -authServiceHostName=localhost -authServicePort=5151 ####LDAP settings - Required only if have selected LDAP authentication #### # diff --git a/security-admin/src/bin/ranger_usersync.py b/security-admin/src/bin/ranger_usersync.py index 4374896c768..d764f5b773b 100644 --- a/security-admin/src/bin/ranger_usersync.py +++ b/security-admin/src/bin/ranger_usersync.py @@ -99,7 +99,7 @@ def get_java_env(): init_variables() jdk_options = get_jdk_options() class_path = get_ranger_classpath() - java_class = 'org.apache.ranger.authentication.UnixAuthenticationService' + java_class = 'org.apache.ranger.usergroupsync.UserSyncService' class_arguments = '' dom = getDOMImplementation() diff --git a/unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/ConsolePromptCallbackHandler.java b/security-admin/src/main/java/org/apache/ranger/authentication/unix/jaas/ConsolePromptCallbackHandler.java similarity index 100% rename from unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/ConsolePromptCallbackHandler.java rename to security-admin/src/main/java/org/apache/ranger/authentication/unix/jaas/ConsolePromptCallbackHandler.java diff --git a/unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/PamLoginModule.java b/security-admin/src/main/java/org/apache/ranger/authentication/unix/jaas/PamLoginModule.java similarity index 100% rename from unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/PamLoginModule.java rename to security-admin/src/main/java/org/apache/ranger/authentication/unix/jaas/PamLoginModule.java diff --git a/unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/PamPrincipal.java b/security-admin/src/main/java/org/apache/ranger/authentication/unix/jaas/PamPrincipal.java similarity index 100% rename from unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/PamPrincipal.java rename to security-admin/src/main/java/org/apache/ranger/authentication/unix/jaas/PamPrincipal.java diff --git a/unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/UnixGroupPrincipal.java b/security-admin/src/main/java/org/apache/ranger/authentication/unix/jaas/UnixGroupPrincipal.java similarity index 100% rename from unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/UnixGroupPrincipal.java rename to security-admin/src/main/java/org/apache/ranger/authentication/unix/jaas/UnixGroupPrincipal.java diff --git a/unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/UsernamePasswordCallbackHandler.java b/security-admin/src/main/java/org/apache/ranger/authentication/unix/jaas/UsernamePasswordCallbackHandler.java similarity index 100% rename from unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/UsernamePasswordCallbackHandler.java rename to security-admin/src/main/java/org/apache/ranger/authentication/unix/jaas/UsernamePasswordCallbackHandler.java diff --git a/security-admin/src/main/java/org/apache/ranger/common/PropertiesUtil.java b/security-admin/src/main/java/org/apache/ranger/common/PropertiesUtil.java index e640048cdea..bce4b997c2a 100644 --- a/security-admin/src/main/java/org/apache/ranger/common/PropertiesUtil.java +++ b/security-admin/src/main/java/org/apache/ranger/common/PropertiesUtil.java @@ -266,40 +266,6 @@ protected void processProperties(ConfigurableListableBeanFactory beanFactory, Pr System.setProperty("javax.net.ssl.keyStorePassword", propertiesMap.get("ranger.keystore.password")); } - //update unixauth keystore and truststore credentials - if (propertiesMap.containsKey("ranger.credential.provider.path")) { - String path = propertiesMap.get("ranger.credential.provider.path"); - - if (path != null) { - String unixAuthKeyStoreAlias = getProperty("ranger.unixauth.keystore.alias", "unixAuthKeyStoreAlias"); - - if (unixAuthKeyStoreAlias != null) { - String unixAuthKeyStorePass = CredentialReader.getDecryptedString(path.trim(), unixAuthKeyStoreAlias.trim(), storeType); - - if (unixAuthKeyStorePass != null && !unixAuthKeyStorePass.trim().isEmpty() && !unixAuthKeyStorePass.trim().equalsIgnoreCase("none")) { - propertiesMap.put("ranger.unixauth.keystore.password", unixAuthKeyStorePass); - props.put("ranger.unixauth.keystore.password", unixAuthKeyStorePass); - } else { - LOG.info("unixauth keystore password not applied; clear text password shall be applicable"); - } - } - - // - String unixAuthTrustStoreAlias = getProperty("ranger.unixauth.truststore.alias", "unixAuthTrustStoreAlias"); - - if (unixAuthTrustStoreAlias != null) { - String unixAuthTrustStorePass = CredentialReader.getDecryptedString(path.trim(), unixAuthTrustStoreAlias.trim(), storeType); - - if (unixAuthTrustStorePass != null && !unixAuthTrustStorePass.trim().isEmpty() && !unixAuthTrustStorePass.trim().equalsIgnoreCase("none")) { - propertiesMap.put("ranger.unixauth.truststore.password", unixAuthTrustStorePass); - props.put("ranger.unixauth.truststore.password", unixAuthTrustStorePass); - } else { - LOG.info("unixauth truststore password not applied; clear text password shall be applicable"); - } - } - } - } - //update credential from keystore if (propertiesMap.containsKey("ranger.credential.provider.path") && propertiesMap.containsKey("ranger.jpa.jdbc.credential.alias")) { String path = propertiesMap.get("ranger.credential.provider.path"); diff --git a/security-admin/src/main/java/org/apache/ranger/security/handler/RangerAuthenticationProvider.java b/security-admin/src/main/java/org/apache/ranger/security/handler/RangerAuthenticationProvider.java index a21d08ea0cd..837e8b8bcae 100644 --- a/security-admin/src/main/java/org/apache/ranger/security/handler/RangerAuthenticationProvider.java +++ b/security-admin/src/main/java/org/apache/ranger/security/handler/RangerAuthenticationProvider.java @@ -134,9 +134,7 @@ public Authentication authenticate(Authentication authentication) throws Authent } } } else if ("UNIX".equalsIgnoreCase(rangerAuthenticationMethod)) { - boolean isPAMAuthEnabled = PropertiesUtil.getBooleanProperty("ranger.pam.authentication.enabled", false); - - authentication = (isPAMAuthEnabled ? getPamAuthentication(authentication) : getUnixAuthentication(authentication)); + authentication = getPamAuthentication(authentication); if (authentication != null && authentication.isAuthenticated()) { return authentication; @@ -315,57 +313,6 @@ public Authentication getPamAuthentication(Authentication authentication) { return authentication; } - public Authentication getUnixAuthentication(Authentication authentication) { - try { - String rangerLdapDefaultRole = PropertiesUtil.getProperty("ranger.ldap.default.role", "ROLE_USER"); - DefaultJaasAuthenticationProvider jaasAuthenticationProvider = new DefaultJaasAuthenticationProvider(); - String loginModuleName = "org.apache.ranger.authentication.unix.jaas.RemoteUnixLoginModule"; - LoginModuleControlFlag controlFlag = LoginModuleControlFlag.REQUIRED; - Map options = PropertiesUtil.getPropertiesMap(); - AppConfigurationEntry appConfigurationEntry = new AppConfigurationEntry(loginModuleName, controlFlag, options); - AppConfigurationEntry[] appConfigurationEntries = new AppConfigurationEntry[] {appConfigurationEntry}; - Map appConfigurationEntriesOptions = new HashMap<>(); - - appConfigurationEntriesOptions.put("SPRINGSECURITY", appConfigurationEntries); - - Configuration configuration = new InMemoryConfiguration(appConfigurationEntriesOptions); - - jaasAuthenticationProvider.setConfiguration(configuration); - - RoleUserAuthorityGranter authorityGranter = new RoleUserAuthorityGranter(); - RoleUserAuthorityGranter[] authorityGranters = new RoleUserAuthorityGranter[] {authorityGranter}; - - jaasAuthenticationProvider.setAuthorityGranters(authorityGranters); - jaasAuthenticationProvider.afterPropertiesSet(); - - String userName = authentication.getName(); - String userPassword = ""; - - if (authentication.getCredentials() != null) { - userPassword = authentication.getCredentials().toString(); - } - - // getting user authenticated - if (userName != null && userPassword != null && !userName.trim().isEmpty() && !userPassword.trim().isEmpty()) { - final List grantedAuths = new ArrayList<>(); - - grantedAuths.add(new SimpleGrantedAuthority(rangerLdapDefaultRole)); - - final UserDetails principal = new User(userName, userPassword, grantedAuths); - final Authentication finalAuthentication = new UsernamePasswordAuthenticationToken(principal, userPassword, grantedAuths); - - authentication = jaasAuthenticationProvider.authenticate(finalAuthentication); - authentication = getAuthenticationWithGrantedAuthority(authentication); - } - - return authentication; - } catch (Exception e) { - logger.debug("Unix Authentication Failed:", e); - } - - return authentication; - } - public String getRangerAuthenticationMethod() { return rangerAuthenticationMethod; } diff --git a/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml b/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml index 164b63d027a..b8e3ca6243c 100644 --- a/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml +++ b/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml @@ -257,58 +257,7 @@ # --> - - - - ranger.unixauth.remote.login.enabled - true - - - ranger.unixauth.service.hostname - localhost - - - ranger.unixauth.service.port - 5151 - - - ranger.unixauth.ssl.enabled - true - - - ranger.unixauth.debug - false - - - ranger.unixauth.server.cert.validation - false - - - - ranger.unixauth.keystore - keystore.jks - - - ranger.unixauth.keystore.credential.alias - unixAuthKeyStoreAlias - - - ranger.unixauth.keystore.password - password - - - ranger.unixauth.truststore - cacerts - - - ranger.unixauth.truststore.credential.alias - unixAuthTrustStoreAlias - - - ranger.unixauth.truststore.password - changeit - - + diff --git a/security-admin/src/main/webapp/META-INF/contextXML/unix_bean_settings.xml b/security-admin/src/main/webapp/META-INF/contextXML/unix_bean_settings.xml index 1aab7ba5301..4fee750cbbd 100644 --- a/security-admin/src/main/webapp/META-INF/contextXML/unix_bean_settings.xml +++ b/security-admin/src/main/webapp/META-INF/contextXML/unix_bean_settings.xml @@ -28,7 +28,7 @@ + value="org.apache.ranger.authentication.unix.jaas.PamLoginModule" /> diff --git a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestConsolePromptCallbackHandler.java b/security-admin/src/test/java/org/apache/ranger/authentication/unix/jaas/TestConsolePromptCallbackHandler.java similarity index 100% rename from unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestConsolePromptCallbackHandler.java rename to security-admin/src/test/java/org/apache/ranger/authentication/unix/jaas/TestConsolePromptCallbackHandler.java diff --git a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamLoginModule.java b/security-admin/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamLoginModule.java similarity index 100% rename from unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamLoginModule.java rename to security-admin/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamLoginModule.java diff --git a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamPrincipal.java b/security-admin/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamPrincipal.java similarity index 100% rename from unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamPrincipal.java rename to security-admin/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamPrincipal.java diff --git a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUsernamePasswordCallbackHandler.java b/security-admin/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUsernamePasswordCallbackHandler.java similarity index 100% rename from unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUsernamePasswordCallbackHandler.java rename to security-admin/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUsernamePasswordCallbackHandler.java diff --git a/security-admin/src/test/java/org/apache/ranger/security/handler/TestRangerAuthenticationProvider.java b/security-admin/src/test/java/org/apache/ranger/security/handler/TestRangerAuthenticationProvider.java index 33145329f9f..a6f48500da4 100644 --- a/security-admin/src/test/java/org/apache/ranger/security/handler/TestRangerAuthenticationProvider.java +++ b/security-admin/src/test/java/org/apache/ranger/security/handler/TestRangerAuthenticationProvider.java @@ -181,13 +181,6 @@ public void getPamAuthentication_skipsWhenNoPassword() { assertSame(input, result); } - @Test - public void getUnixAuthentication_skipsWhenNoPassword() { - UsernamePasswordAuthenticationToken input = new UsernamePasswordAuthenticationToken("u", ""); - Authentication result = provider.getUnixAuthentication(input); - assertSame(input, result); - } - @Test public void getLdapAuthentication_skipsWhenNoPassword() throws Exception { UsernamePasswordAuthenticationToken input = new UsernamePasswordAuthenticationToken("u", ""); diff --git a/ugsync/cert/.gitkeep b/ugsync/cert/.gitkeep new file mode 100644 index 00000000000..ae1e83eeb3d --- /dev/null +++ b/ugsync/cert/.gitkeep @@ -0,0 +1,14 @@ +# 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. diff --git a/unixauthservice/conf.dist/logback.xml b/ugsync/conf.dist/logback.xml similarity index 100% rename from unixauthservice/conf.dist/logback.xml rename to ugsync/conf.dist/logback.xml diff --git a/unixauthservice/conf.dist/ranger-ugsync-default.xml b/ugsync/conf.dist/ranger-ugsync-default.xml similarity index 65% rename from unixauthservice/conf.dist/ranger-ugsync-default.xml rename to ugsync/conf.dist/ranger-ugsync-default.xml index 2e48da7e64c..879a0eaebe4 100644 --- a/unixauthservice/conf.dist/ranger-ugsync-default.xml +++ b/ugsync/conf.dist/ranger-ugsync-default.xml @@ -17,42 +17,6 @@ - - ranger.usersync.port - 5151 - - - ranger.usersync.ssl - true - - - ranger.usersync.https.ssl.enabled.protocols - TLSv1.2 - - - ranger.usersync.unixauth.max.failed.attempts - 5 - - - ranger.usersync.unixauth.attempt.window.ms - 60000 - - - ranger.usersync.unixauth.lockout.duration.ms - 30000 - - - ranger.usersync.unixauth.require.client.auth - false - - - ranger.usersync.unixauth.socket.timeout.ms - 10000 - - - ranger.usersync.passwordvalidator.path - ./native/credValidator.uexe - ranger.usersync.enabled true diff --git a/ugsync/pom.xml b/ugsync/pom.xml index 1743187546b..e8561a49b53 100644 --- a/ugsync/pom.xml +++ b/ugsync/pom.xml @@ -326,6 +326,26 @@ + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-usersync-runtime-libs + + copy-dependencies + + package + + ${project.build.directory}/lib + runtime + true + true + false + + + + org.apache.maven.plugins maven-surefire-plugin diff --git a/unixauthservice/scripts/initd b/ugsync/scripts/initd similarity index 100% rename from unixauthservice/scripts/initd rename to ugsync/scripts/initd diff --git a/unixauthservice/scripts/install.properties b/ugsync/scripts/install.properties similarity index 97% rename from unixauthservice/scripts/install.properties rename to ugsync/scripts/install.properties index 7d7ed5554bd..0d9c2b59861 100644 --- a/unixauthservice/scripts/install.properties +++ b/ugsync/scripts/install.properties @@ -61,12 +61,11 @@ hadoop_conf=/etc/hadoop/conf # CRED_KEYSTORE_FILENAME=/etc/ranger/usersync/conf/rangerusersync.jceks -# SSL Authentication -AUTH_SSL_ENABLED=false -AUTH_SSL_KEYSTORE_FILE=/etc/ranger/usersync/conf/cert/unixauthservice.jks -AUTH_SSL_KEYSTORE_PASSWORD=UnIx529p -AUTH_SSL_TRUSTSTORE_FILE= -AUTH_SSL_TRUSTSTORE_PASSWORD= +# Client TLS keystore for usersync-to-policymanager and LDAP connections +USERSYNC_SSL_KEYSTORE_FILE=/etc/ranger/usersync/conf/cert/usersync.jks +USERSYNC_SSL_KEYSTORE_PASSWORD=UnIx529p +USERSYNC_SSL_TRUSTSTORE_FILE= +USERSYNC_SSL_TRUSTSTORE_PASSWORD= # --------------------------------------------------------------- # The following properties are relevant only if SYNC_SOURCE = ldap diff --git a/unixauthservice/scripts/ranger-usersync-services.sh b/ugsync/scripts/ranger-usersync-services.sh similarity index 97% rename from unixauthservice/scripts/ranger-usersync-services.sh rename to ugsync/scripts/ranger-usersync-services.sh index 5b1c2e4d423..c7badd3af85 100644 --- a/unixauthservice/scripts/ranger-usersync-services.sh +++ b/ugsync/scripts/ranger-usersync-services.sh @@ -99,6 +99,7 @@ if [ "${action}" == "START" ]; then if [ -z "${logdir}" ]; then logdir=${cdir}/logs fi + mkdir -p "${logdir}" if [ -z "${USERSYNC_CONF_DIR}" ]; then USERSYNC_CONF_DIR=${cdir}/conf fi @@ -113,7 +114,7 @@ if [ "${action}" == "START" ]; then fi fi SLEEP_TIME_AFTER_START=5 - nohup java -Dproc_rangerusersync -Djdk.tls.ephemeralDHKeySize=2048 -Dlogback.configurationFile=file:${USERSYNC_CONF_DIR}/logback.xml ${JAVA_OPTS} -Duser=${USER} -Dhostname=${HOSTNAME} -Dlogdir="${logdir}" -cp "${cp}" org.apache.ranger.authentication.UnixAuthenticationService -enableUnixAuth > ${logdir}/auth.log 2>&1 & + nohup java -Dproc_rangerusersync -Djdk.tls.ephemeralDHKeySize=2048 -Dlogback.configurationFile=file:${USERSYNC_CONF_DIR}/logback.xml ${JAVA_OPTS} -Duser=${USER} -Dhostname=${HOSTNAME} -Dlogdir="${logdir}" -cp "${cp}" org.apache.ranger.usergroupsync.UserSyncService > ${logdir}/usersync.log 2>&1 & VALUE_OF_PID=$! echo "Starting Apache Ranger Usersync Service" sleep $SLEEP_TIME_AFTER_START diff --git a/unixauthservice/scripts/set_globals.sh b/ugsync/scripts/set_globals.sh similarity index 100% rename from unixauthservice/scripts/set_globals.sh rename to ugsync/scripts/set_globals.sh diff --git a/unixauthservice/scripts/setup.py b/ugsync/scripts/setup.py similarity index 94% rename from unixauthservice/scripts/setup.py rename to ugsync/scripts/setup.py index e219b5e21c3..af0728e3250 100755 --- a/unixauthservice/scripts/setup.py +++ b/ugsync/scripts/setup.py @@ -43,7 +43,7 @@ confBaseDirName = 'conf' confDistBaseDirName = 'conf.dist' certBaseDirName = 'cert' -defaultCertFileName = 'unixauthservice.jks' +defaultCertFileName = 'usersync.jks' outputFileName = 'ranger-ugsync-site.xml' installPropFileName = 'install.properties' @@ -63,14 +63,10 @@ installTemplateDirName = join(RANGER_USERSYNC_HOME, 'templates') confDistDirName = join(RANGER_USERSYNC_HOME, confDistBaseDirName) # ugsyncLogFolderName = join(logFolderName, 'usersync') -nativeAuthFolderName = join(RANGER_USERSYNC_HOME, 'native') localConfFolderName = join(RANGER_USERSYNC_HOME, confBaseDirName) -nativeAuthProgramName = join(nativeAuthFolderName, 'credValidator.uexe') -pamAuthProgramName = join(nativeAuthFolderName, 'pamCredValidator.uexe') - defaultKSPassword = 'UnIx529p' -defaultDNAME = 'cn=unixauthservice,ou=authenticator,o=mycompany,c=US' +defaultDNAME = 'cn=rangerusersync,ou=usersync,o=mycompany,c=US' unixUserProp = 'unix_user' unixGroupProp = 'unix_group' @@ -567,26 +563,6 @@ def _safe_chown_chmod(path, uid, gid, mode): for obj in files: _safe_chown_chmod(join(root, obj), ownerId, groupId, 0o750) - if isfile(nativeAuthProgramName): - try: - os.chown(nativeAuthProgramName, rootOwnerId, groupId) - os.chmod(nativeAuthProgramName, 0o750) - except PermissionError: - print("WARNING: chmod(4550), chown(%s:%s) failed for Unix Authentication Program (%s) " % ("root", groupName, nativeAuthProgramName)) - else: - print("WARNING: Unix Authentication Program (%s) is not available for setting chmod(4550), chown(%s:%s) " % ( - nativeAuthProgramName, "root", groupName)) - - if isfile(pamAuthProgramName): - try: - os.chown(pamAuthProgramName, rootOwnerId, groupId) - os.chmod(pamAuthProgramName, 0o750) - except PermissionError: - print("WARNING: chmod(0o750), chown(%s:%s) failed for Unix Authentication Program (%s) " % ("root", groupName, pamAuthProgramName)) - else: - print("WARNING: Unix Authentication Program (%s) is not available for setting chmod(4550), chown(%s:%s) " % ( - pamAuthProgramName, "root", groupName)) - write_env_files("logdir", logFolderName, ENV_LOGDIR_FILE); write_env_files("RANGER_USERSYNC_HADOOP_CONF_DIR", hadoop_conf, ENV_HADOOP_CONF_FILE); write_env_files("USERSYNC_PID_DIR_PATH", pid_dir_path, ENV_PID_FILE); diff --git a/unixauthservice/scripts/setup.sh b/ugsync/scripts/setup.sh similarity index 100% rename from unixauthservice/scripts/setup.sh rename to ugsync/scripts/setup.sh diff --git a/unixauthservice/scripts/start.sh b/ugsync/scripts/start.sh similarity index 100% rename from unixauthservice/scripts/start.sh rename to ugsync/scripts/start.sh diff --git a/unixauthservice/scripts/stop.sh b/ugsync/scripts/stop.sh similarity index 100% rename from unixauthservice/scripts/stop.sh rename to ugsync/scripts/stop.sh diff --git a/unixauthservice/scripts/templates/installprop2xml.properties b/ugsync/scripts/templates/installprop2xml.properties similarity index 92% rename from unixauthservice/scripts/templates/installprop2xml.properties rename to ugsync/scripts/templates/installprop2xml.properties index 8b06e762c3f..5964a28268f 100644 --- a/unixauthservice/scripts/templates/installprop2xml.properties +++ b/ugsync/scripts/templates/installprop2xml.properties @@ -48,11 +48,10 @@ SYNC_PAGED_RESULTS_SIZE=ranger.usersync.pagedresultssize SYNC_SOURCE = SYNC_SOURCE unix_user = unix_user unix_group = unix_group -AUTH_SSL_KEYSTORE_FILE = ranger.usersync.keystore.file -AUTH_SSL_KEYSTORE_PASSWORD = ranger.usersync.keystore.password -AUTH_SSL_TRUSTSTORE_FILE = ranger.usersync.truststore.file -AUTH_SSL_TRUSTSTORE_PASSWORD = ranger.usersync.truststore.password -AUTH_SSL_ENABLED = ranger.usersync.ssl +USERSYNC_SSL_KEYSTORE_FILE = ranger.usersync.keystore.file +USERSYNC_SSL_KEYSTORE_PASSWORD = ranger.usersync.keystore.password +USERSYNC_SSL_TRUSTSTORE_FILE = ranger.usersync.truststore.file +USERSYNC_SSL_TRUSTSTORE_PASSWORD = ranger.usersync.truststore.password SYNC_LDAP_REFERRAL = ranger.usersync.ldap.referral usersync_principal= ranger.usersync.kerberos.principal usersync_keytab= ranger.usersync.kerberos.keytab diff --git a/unixauthservice/scripts/templates/ranger-ugsync-template.xml b/ugsync/scripts/templates/ranger-ugsync-template.xml similarity index 96% rename from unixauthservice/scripts/templates/ranger-ugsync-template.xml rename to ugsync/scripts/templates/ranger-ugsync-template.xml index 52f027a5c6b..de405920136 100644 --- a/unixauthservice/scripts/templates/ranger-ugsync-template.xml +++ b/ugsync/scripts/templates/ranger-ugsync-template.xml @@ -117,10 +117,6 @@ ranger.usersync.pagedresultssize - - ranger.usersync.passwordvalidator.path - - ranger.usersync.policymanager.baseURL @@ -133,10 +129,6 @@ ranger.usersync.policymanager.mockrun - - ranger.usersync.port - - ranger.usersync.sink.impl.class @@ -149,10 +141,6 @@ ranger.usersync.source.impl.class - - ranger.usersync.ssl - - ranger.usersync.unix.minUserId diff --git a/unixauthservice/scripts/update_property.py b/ugsync/scripts/update_property.py similarity index 100% rename from unixauthservice/scripts/update_property.py rename to ugsync/scripts/update_property.py diff --git a/unixauthservice/scripts/updatepolicymgrpassword.py b/ugsync/scripts/updatepolicymgrpassword.py similarity index 100% rename from unixauthservice/scripts/updatepolicymgrpassword.py rename to ugsync/scripts/updatepolicymgrpassword.py diff --git a/unixauthservice/scripts/updatepolicymgrpassword.sh b/ugsync/scripts/updatepolicymgrpassword.sh similarity index 80% rename from unixauthservice/scripts/updatepolicymgrpassword.sh rename to ugsync/scripts/updatepolicymgrpassword.sh index 82be91320aa..d4d6c70fe06 100644 --- a/unixauthservice/scripts/updatepolicymgrpassword.sh +++ b/ugsync/scripts/updatepolicymgrpassword.sh @@ -80,7 +80,7 @@ SYNC_LDAP_BIND_KEYSTOREPATH=`grep '^[ \t]*CRED_KEYSTORE_FILENAME[ \t]*=' ${cdir} # END Grep configuration properties from install.properties # Store POLICY_MGR user password in credential store -SYNC_POLICY_MGR_ALIAS="policymgr.user.password" +SYNC_POLICY_MGR_ALIAS="ranger.usersync.policymgr.password" SYNC_POLICY_MGR_PASSWORD="rangerusersync" SYNC_POLICY_MGR_USERNAME="rangerusersync" count=0 @@ -122,26 +122,15 @@ then $JAVA_HOME/bin/java -cp "lib/*" org.apache.ranger.credentialapi.buildks create "$SYNC_POLICY_MGR_ALIAS" -value "$SYNC_POLICY_MGR_PASSWORD" -provider jceks://file$SYNC_LDAP_BIND_KEYSTOREPATH fi -# Create $INSTALL_DIR/conf/unixauthservice.properties +# Update ranger-ugsync-site.xml policymgr settings -CFG_FILE="${cdir}/conf/unixauthservice.properties" -NEW_CFG_FILE=${cdir}/conf/unixauthservice.properties.tmp +CFG_FILE="${cdir}/conf/ranger-ugsync-site.xml" -if [ -f ${CFG_FILE} ] -then - sed \ - -e "s|^\( *userSync.policyMgrUserName *=\).*|\1 ${SYNC_POLICY_MGR_USERNAME}|" \ - -e "s|^\( *userSync.policyMgrKeystore *=\).*|\1 ${SYNC_LDAP_BIND_KEYSTOREPATH}|" \ - -e "s|^\( *userSync.policyMgrAlias *=\).*|\1 ${SYNC_POLICY_MGR_ALIAS}|" \ - ${CFG_FILE} > ${NEW_CFG_FILE} - - echo "<${logdir}> ${CFG_FILE} > ${NEW_CFG_FILE}" -else - echo "ERROR: Required file, not found: ${CFG_FILE}, Aborting installation" +if [ ! -f "${CFG_FILE}" ]; then + log "[E] Required file not found: ${CFG_FILE}, aborting" exit 8 fi -mv ${cdir}/conf/unixauthservice.properties ${cdir}/conf/unixauthservice.properties.${curDt} -mv ${cdir}/conf/unixauthservice.properties.tmp ${cdir}/conf/unixauthservice.properties - -#END Create $INSTALL_DIR/conf/unixauthservice.properties \ No newline at end of file +python "${cdir}/update_property.py" "ranger.usersync.policymgr.username" "${SYNC_POLICY_MGR_USERNAME}" "${CFG_FILE}" +python "${cdir}/update_property.py" "ranger.usersync.policymgr.keystore" "${SYNC_LDAP_BIND_KEYSTOREPATH}" "${CFG_FILE}" +python "${cdir}/update_property.py" "ranger.usersync.policymgr.alias" "${SYNC_POLICY_MGR_ALIAS}" "${CFG_FILE}" \ No newline at end of file diff --git a/ugsync/src/main/java/org/apache/ranger/unixusersync/process/FileSourceUserGroupBuilder.java b/ugsync/src/main/java/org/apache/ranger/unixusersync/process/FileSourceUserGroupBuilder.java index 381af1212e4..ed37268222e 100644 --- a/ugsync/src/main/java/org/apache/ranger/unixusersync/process/FileSourceUserGroupBuilder.java +++ b/ugsync/src/main/java/org/apache/ranger/unixusersync/process/FileSourceUserGroupBuilder.java @@ -231,7 +231,7 @@ public void buildUserGroupInfo() throws Throwable { public void buildUserGroupList() throws Throwable { if (userGroupFilename == null) { - throw new Exception("User Group Source File is not Configured. Please maintain in unixauthservice.properties or pass it as command line argument for org.apache.ranger.unixusersync.process.FileSourceUserGroupBuilder"); + throw new Exception("User Group Source File is not Configured. Please maintain in ranger-ugsync-site.xml or pass it as command line argument for org.apache.ranger.unixusersync.process.FileSourceUserGroupBuilder"); } File f = new File(userGroupFilename); diff --git a/ugsync/src/main/java/org/apache/ranger/usergroupsync/UserSyncService.java b/ugsync/src/main/java/org/apache/ranger/usergroupsync/UserSyncService.java new file mode 100644 index 00000000000..381e9325543 --- /dev/null +++ b/ugsync/src/main/java/org/apache/ranger/usergroupsync/UserSyncService.java @@ -0,0 +1,83 @@ +/* + * 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.ranger.usergroupsync; + +import org.apache.ranger.unixusersync.config.UserGroupSyncConfig; +import org.apache.ranger.unixusersync.ha.UserSyncHAInitializerImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * JVM entry point for Ranger UserSync (user/group sync, HA, metrics). + */ +public class UserSyncService { + private static final Logger LOG = LoggerFactory.getLogger(UserSyncService.class); + + private UserSyncHAInitializerImpl userSyncHAInitializerImpl; + private Thread userGroupSyncThread; + + public static void main(String[] args) { + UserSyncService service = new UserSyncService(); + service.userSyncHAInitializerImpl = UserSyncHAInitializerImpl.getInstance(UserGroupSyncConfig.getInstance().getUserGroupConfig()); + service.run(); + } + + public void run() { + try { + LOG.info("Starting User Sync Service!"); + startUserGroupSyncProcess(); + if (userGroupSyncThread != null) { + userGroupSyncThread.join(); + } + } catch (Throwable t) { + LOG.error("ERROR: User Sync Service failed", t); + } finally { + LOG.info("User Sync Service - STOPPED"); + if (userSyncHAInitializerImpl != null) { + LOG.info("Stopping curator leader latch service as main thread is closing"); + userSyncHAInitializerImpl.stop(); + } + } + } + + private void startUserGroupSyncProcess() { + LOG.info("Start : startUserGroupSyncProcess"); + UserGroupSync syncProc = new UserGroupSync(); + Thread newSyncProcThread = new Thread(syncProc); + newSyncProcThread.setName("UnixUserSyncThread"); + newSyncProcThread.setDaemon(false); + newSyncProcThread.start(); + userGroupSyncThread = newSyncProcThread; + LOG.info("UnixUserSyncThread started"); + LOG.info("creating UserSyncMetricsProducer thread with default metrics location : {}", System.getProperty("logdir")); + + boolean isUserSyncMetricsEnabled = UserGroupSyncConfig.getInstance().isUserSyncMetricsEnabled(); + if (isUserSyncMetricsEnabled) { + UserSyncMetricsProducer userSyncMetricsProducer = new UserSyncMetricsProducer(); + Thread userSyncMetricsProducerThread = new Thread(userSyncMetricsProducer); + userSyncMetricsProducerThread.setName("UserSyncMetricsProducerThread"); + userSyncMetricsProducerThread.setDaemon(false); + userSyncMetricsProducerThread.start(); + LOG.info("UserSyncMetricsProducer started"); + } else { + LOG.info(" Ranger userSync metrics is not enabled"); + } + } +} diff --git a/ugsync/src/test/java/org/apache/ranger/usergroupsync/TestUserSyncService.java b/ugsync/src/test/java/org/apache/ranger/usergroupsync/TestUserSyncService.java new file mode 100644 index 00000000000..718c01b9d01 --- /dev/null +++ b/ugsync/src/test/java/org/apache/ranger/usergroupsync/TestUserSyncService.java @@ -0,0 +1,108 @@ +/* + * 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.ranger.usergroupsync; + +import org.apache.ranger.unixusersync.config.UserGroupSyncConfig; +import org.apache.ranger.unixusersync.ha.UserSyncHAInitializerImpl; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class TestUserSyncService { + @Test + public void userSyncServiceClassIsLoadable() { + assertNotNull(UserSyncService.class); + } + + @Test + public void run_stopsHaWhenSyncThreadEnds() throws Exception { + UserSyncService svc = new UserSyncService(); + UserSyncHAInitializerImpl ha = mock(UserSyncHAInitializerImpl.class); + + setField(svc, "userSyncHAInitializerImpl", ha); + + try (MockedStatic ugscStatic = mockStatic(UserGroupSyncConfig.class)) { + UserGroupSyncConfig ugsc = mock(UserGroupSyncConfig.class); + ugscStatic.when(UserGroupSyncConfig::getInstance).thenReturn(ugsc); + when(ugsc.isUserSyncMetricsEnabled()).thenReturn(false); + + try (MockedConstruction syncConstruction = mockConstruction(UserGroupSync.class)) { + svc.run(); + + verify(ha).stop(); + assertEquals(1, syncConstruction.constructed().size()); + } + } + } + + @Test + public void startUserGroupSyncProcess_metricsEnabledAndDisabled() throws Exception { + UserSyncService svc = new UserSyncService(); + Method method = UserSyncService.class.getDeclaredMethod("startUserGroupSyncProcess"); + method.setAccessible(true); + + try (MockedStatic ugscStatic = mockStatic(UserGroupSyncConfig.class)) { + UserGroupSyncConfig ugsc = mock(UserGroupSyncConfig.class); + ugscStatic.when(UserGroupSyncConfig::getInstance).thenReturn(ugsc); + + when(ugsc.isUserSyncMetricsEnabled()).thenReturn(true); + try (MockedConstruction threadConstruction = mockConstruction(Thread.class, (mockThread, ctx) -> { + doNothing().when(mockThread).setName(any()); + doNothing().when(mockThread).setDaemon(false); + doNothing().when(mockThread).start(); + })) { + method.invoke(svc); + assertEquals(2, threadConstruction.constructed().size()); + } + + when(ugsc.isUserSyncMetricsEnabled()).thenReturn(false); + try (MockedConstruction threadConstruction = mockConstruction(Thread.class, (mockThread, ctx) -> { + doNothing().when(mockThread).setName(any()); + doNothing().when(mockThread).setDaemon(false); + doNothing().when(mockThread).start(); + })) { + method.invoke(svc); + assertEquals(1, threadConstruction.constructed().size()); + } + } + } + + private static void setField(Object target, String fieldName, Object value) throws Exception { + Field field = UserSyncService.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } +} diff --git a/ugsync/src/test/resources/ranger-ugsync-site.xml b/ugsync/src/test/resources/ranger-ugsync-site.xml index 5ed504b8694..52b80c78799 100644 --- a/ugsync/src/test/resources/ranger-ugsync-site.xml +++ b/ugsync/src/test/resources/ranger-ugsync-site.xml @@ -70,7 +70,7 @@ ranger.usersync.keystore.file - /usr/hdp/current/ranger-usersync/conf/unixauthservice.jks + /usr/hdp/current/ranger-usersync/conf/usersync.jks diff --git a/unixauthclient/.gitignore b/unixauthclient/.gitignore deleted file mode 100644 index 8604ba3c425..00000000000 --- a/unixauthclient/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target/ -/bin/ -/target -.settings/ diff --git a/unixauthclient/pom.xml b/unixauthclient/pom.xml deleted file mode 100644 index d6e11536645..00000000000 --- a/unixauthclient/pom.xml +++ /dev/null @@ -1,107 +0,0 @@ - - - - 4.0.0 - - org.apache.ranger - ranger - 3.0.0-SNAPSHOT - .. - - unixauthclient - jar - Unix Authentication Client - Unix authentication client - - - com.google.code.gson - gson - - - commons-cli - commons-cli - ${commons.cli.version} - - - commons-collections - commons-collections - ${commons.collections.version} - - - org.apache.commons - commons-configuration2 - ${commons.configuration.version} - - - org.apache.commons - commons-text - - - - - org.apache.commons - commons-lang3 - ${commons.lang3.version} - - - org.apache.commons - commons-text - ${commons.text.version} - - - org.apache.hadoop - hadoop-auth - ${hadoop.version} - - - log4j - * - - - net.minidev - json-smart - - - org.apache.zookeeper - zookeeper - - - org.slf4j - * - - - - - org.kohsuke - libpam4j - ${libpam4j.version} - - - org.bouncycastle - bcpkix-jdk18on - ${org.bouncycastle.bcpkix-jdk18on} - test - - - org.mockito - mockito-junit-jupiter - ${mockito.version} - test - - - diff --git a/unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/RemoteUnixLoginModule.java b/unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/RemoteUnixLoginModule.java deleted file mode 100644 index 794c5644bc9..00000000000 --- a/unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/RemoteUnixLoginModule.java +++ /dev/null @@ -1,400 +0,0 @@ -/* - * 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.ranger.authentication.unix.jaas; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.LoginException; -import javax.security.auth.spi.LoginModule; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.net.Socket; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.Map; -import java.util.Properties; - -public class RemoteUnixLoginModule implements LoginModule { - private static final String DEBUG_PARAM = "ranger.unixauth.debug"; - private static final String REMOTE_LOGIN_HOST_PARAM = "ranger.unixauth.service.hostname"; - private static final String REMOTE_LOGIN_AUTH_SERVICE_PORT_PARAM = "ranger.unixauth.service.port"; - private static final String SSL_KEYSTORE_PATH_PARAM = "ranger.unixauth.keystore"; - private static final String SSL_KEYSTORE_PATH_PASSWORD_PARAM = "ranger.unixauth.keystore.password"; - private static final String SSL_TRUSTSTORE_PATH_PARAM = "ranger.unixauth.truststore"; - private static final String SSL_TRUSTSTORE_PATH_PASSWORD_PARAM = "ranger.unixauth.truststore.password"; - private static final String SSL_ENABLED_PARAM = "ranger.unixauth.ssl.enabled"; - private static final String SERVER_CERT_VALIDATION_PARAM = "ranger.unixauth.server.cert.validation"; - private static final String JAAS_ENABLED_PARAM = "ranger.unixauth.remote.login.enabled"; - private static final String SSL_ALGORITHM = "TLS"; - - private String userName; - private String remoteHostName; - private String loginGroups; - private String keyStorePath; - private String keyStorePathPassword; - private String trustStorePath; - private String trustStorePathPassword; - private char[] password; - private int remoteHostAuthServicePort; - private Subject subject; - private CallbackHandler callbackHandler; - private boolean ssEnabled; - private boolean loginSuccessful; - private boolean debug = true; - private boolean serverCertValidation = true; - private boolean remoteLoginEnabled = true; - - public RemoteUnixLoginModule() { - log("Created RemoteUnixLoginModule"); - } - - @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - this.subject = subject; - this.callbackHandler = callbackHandler; - - log("RemoteUnixLoginModule::initialize() has been called with callbackhandler: " + this.callbackHandler); - - if (this.callbackHandler == null) { - this.callbackHandler = new ConsolePromptCallbackHandler(); - } - - Properties config = new Properties(); - config.putAll(options); - initParams(config); - } - - @Override - public boolean login() throws LoginException { - if (remoteLoginEnabled && this.callbackHandler != null) { - NameCallback nameCallback = new NameCallback("UserName:"); - PasswordCallback passwordCallback = new PasswordCallback("Password:", false); - try { - callbackHandler.handle(new Callback[] {nameCallback, passwordCallback}); - } catch (IOException | UnsupportedCallbackException e) { - throw new LoginException("Unable to get username/password due to exception: " + e); - } - - userName = nameCallback.getName(); - String modifiedUserName = userName; - - if (userName != null) { - int atStartsAt = userName.indexOf("@"); - if (atStartsAt > -1) { - modifiedUserName = userName.substring(0, atStartsAt); - } - } - - password = passwordCallback.getPassword(); - - log("userName:" + userName); - log("modified UserName:" + modifiedUserName); - - char[] modifiedPasschar; - if (password != null) { - modifiedPasschar = Arrays.copyOf(password, password.length); - } else { - modifiedPasschar = new char[0]; - } - - doLogin(modifiedUserName, modifiedPasschar); - - Arrays.fill(password, ' '); - Arrays.fill(modifiedPasschar, ' '); - loginSuccessful = true; - } - - return loginSuccessful; - } - - @Override - public boolean commit() throws LoginException { - log("RemoteUnixLoginModule::commit() has been called. -> isLoginSuccess [" + loginSuccessful + "]"); - if (loginSuccessful) { - if (subject != null) { - subject.getPrincipals().add(new UnixUserPrincipal(userName.trim())); - if (loginGroups != null) { - loginGroups = loginGroups.trim(); - for (String group : loginGroups.split(",")) { - subject.getPrincipals().add(new UnixGroupPrincipal(group.trim())); - } - } - } - } else { - if (subject != null) { - subject.getPrincipals().clear(); - } - } - return loginSuccessful; - } - - @Override - public boolean abort() throws LoginException { - log("RemoteUnixLoginModule::abort() has been called."); - loginSuccessful = false; - return true; - } - - @Override - public boolean logout() throws LoginException { - if (subject != null) { - subject.getPrincipals().clear(); - } - return true; - } - - public void initParams(Properties options) { - String val = (String) options.get(JAAS_ENABLED_PARAM); - - if (val != null) { - remoteLoginEnabled = val.trim().equalsIgnoreCase("true"); - if (!remoteLoginEnabled) { - log("Skipping RemoteLogin - [" + JAAS_ENABLED_PARAM + "] => [" + val + "]"); - return; - } - } else { - remoteLoginEnabled = true; - } - - val = (String) options.get(DEBUG_PARAM); - debug = val != null && (!val.equalsIgnoreCase("false")); - - remoteHostName = (String) options.get(REMOTE_LOGIN_HOST_PARAM); - log("RemoteHostName:" + remoteHostName); - - val = (String) options.get(REMOTE_LOGIN_AUTH_SERVICE_PORT_PARAM); - if (val != null) { - remoteHostAuthServicePort = Integer.parseInt(val.trim()); - } - log("remoteHostAuthServicePort:" + remoteHostAuthServicePort); - - val = (String) options.get(SSL_ENABLED_PARAM); - ssEnabled = (val != null) && val.trim().equalsIgnoreCase("true"); - log("ssEnabled: " + ssEnabled); - - if (ssEnabled) { - trustStorePath = (String) options.get(SSL_TRUSTSTORE_PATH_PARAM); - log("trustStorePath: " + trustStorePath); - - if (trustStorePath != null) { - trustStorePathPassword = (String) options.get(SSL_TRUSTSTORE_PATH_PASSWORD_PARAM); - if (trustStorePathPassword == null) { - trustStorePathPassword = ""; - } - log("trustStorePathPassword:*****"); - } - - keyStorePath = (String) options.get(SSL_KEYSTORE_PATH_PARAM); - log("keyStorePath:" + keyStorePath); - if (keyStorePath != null) { - keyStorePathPassword = (String) options.get(SSL_KEYSTORE_PATH_PASSWORD_PARAM); - if (keyStorePathPassword == null) { - keyStorePathPassword = ""; - } - log("keyStorePathPassword:*****"); - } - - String certValidationFlag = (String) options.get(SERVER_CERT_VALIDATION_PARAM); - serverCertValidation = (!(certValidationFlag != null && ("false".equalsIgnoreCase(certValidationFlag.trim())))); - log("Server Cert Validation : " + serverCertValidation); - } - } - - public void doLogin(String aUserName, char[] modifiedPasschar) throws LoginException { - /* Possible values - * null - * OK: group1, group2, group3 - * FAILED: Invalid Password - */ - String ret = getLoginReplyFromAuthService(aUserName, modifiedPasschar); - - if (ret == null) { - throw new LoginException("FAILED: unable to authenticate to AuthenticationService: " + remoteHostName + ":" + remoteHostAuthServicePort); - } else if (ret.startsWith("OK:")) { - loginSuccessful = true; - if (ret.length() > 3) { - this.loginGroups = ret.substring(3); - } - } else if (ret.startsWith("FAILED:")) { - loginSuccessful = false; - throw new LoginException("FAILED: unable to authenticate to AuthenticationService: " + remoteHostName + ":" + remoteHostAuthServicePort); - } else { - throw new LoginException("FAILED: unable to authenticate to AuthenticationService: " + remoteHostName + ":" + remoteHostAuthServicePort + ", msg:" + ret); - } - } - - private String getLoginReplyFromAuthService(String aUserName, char[] modifiedPasschar) throws LoginException { - String ret = null; - Socket sslsocket = null; - - char[] prefix = ("LOGIN:" + aUserName + " ").toCharArray(); - char[] tail = "\n".toCharArray(); - char[] loginData = new char[prefix.length + modifiedPasschar.length + tail.length]; - System.arraycopy(prefix, 0, loginData, 0, prefix.length); - System.arraycopy(modifiedPasschar, 0, loginData, prefix.length, modifiedPasschar.length); - System.arraycopy(tail, 0, loginData, prefix.length + modifiedPasschar.length, tail.length); - try { - try { - if (ssEnabled) { - SSLContext context = SSLContext.getInstance(SSL_ALGORITHM); - - KeyManager[] km = null; - - if (keyStorePath != null) { - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - - try (InputStream in = getFileInputStream(keyStorePath)) { - ks.load(in, keyStorePathPassword.toCharArray()); - } - - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, keyStorePathPassword.toCharArray()); - km = kmf.getKeyManagers(); - } - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - - TrustManager[] tm = null; - - if (serverCertValidation) { - KeyStore trustStoreKeyStore; - - if (trustStorePath != null) { - trustStoreKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - - try (InputStream in = getFileInputStream(trustStorePath)) { - trustStoreKeyStore.load(in, trustStorePathPassword.toCharArray()); - - trustManagerFactory.init(trustStoreKeyStore); - - tm = trustManagerFactory.getTrustManagers(); - } - } - } else { - TrustManager ignoreValidationTM = new X509TrustManager() { - public void checkClientTrusted(X509Certificate[] chain, String authType) { - // Ignore Server Certificate Validation - } - - public void checkServerTrusted(X509Certificate[] chain, String authType) { - // Ignore Server Certificate Validation - } - - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - }; - - tm = new TrustManager[] {ignoreValidationTM}; - } - - SecureRandom random = new SecureRandom(); - - context.init(km, tm, random); - - SSLSocketFactory sf = context.getSocketFactory(); - - sslsocket = sf.createSocket(remoteHostName, remoteHostAuthServicePort); - } else { - sslsocket = new Socket(remoteHostName, remoteHostAuthServicePort); - } - - OutputStreamWriter writer = new OutputStreamWriter(sslsocket.getOutputStream()); - - writer.write(loginData); - writer.flush(); - - BufferedReader reader = new BufferedReader(new InputStreamReader(sslsocket.getInputStream())); - - ret = reader.readLine(); - - reader.close(); - writer.close(); - } finally { - if (sslsocket != null) { - sslsocket.close(); - } - } - } catch (Throwable t) { - throw new LoginException("FAILED: unable to authenticate to AuthenticationService: " + remoteHostName + ":" + remoteHostAuthServicePort + ", Exception: [" + t + "]"); - } finally { - log("Login of user String: {" + aUserName + "}, return from AuthServer: {" + ret + "}"); - Arrays.fill(loginData, ' '); - Arrays.fill(modifiedPasschar, ' '); - } - - return ret; - } - - private InputStream getFileInputStream(String path) throws FileNotFoundException { - InputStream ret; - - File f = new File(path); - - if (f.exists()) { - ret = new FileInputStream(f); - } else { - ret = getClass().getResourceAsStream(path); - - if (ret == null) { - if (!path.startsWith("/")) { - ret = getClass().getResourceAsStream("/" + path); - } - } - - if (ret == null) { - ret = ClassLoader.getSystemClassLoader().getResourceAsStream(path); - if (ret == null) { - if (!path.startsWith("/")) { - ret = ClassLoader.getSystemResourceAsStream("/" + path); - } - } - } - } - - return ret; - } - - private void log(String msg) { - if (debug) { - System.err.println("RemoteUnixLoginModule: " + msg); - } - } -} diff --git a/unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/UnixUserPrincipal.java b/unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/UnixUserPrincipal.java deleted file mode 100644 index d280e689b6e..00000000000 --- a/unixauthclient/src/main/java/org/apache/ranger/authentication/unix/jaas/UnixUserPrincipal.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.ranger.authentication.unix.jaas; - -import java.io.Serializable; -import java.security.Principal; - -public class UnixUserPrincipal implements Principal, Serializable { - private static final long serialVersionUID = -3568658536591178268L; - - private final String userName; - - public UnixUserPrincipal(String userName) { - this.userName = userName; - } - - @Override - public String getName() { - return userName; - } -} diff --git a/unixauthclient/src/test/com/xasecure/test/authentication/UnixAuthenticationTester.java b/unixauthclient/src/test/com/xasecure/test/authentication/UnixAuthenticationTester.java deleted file mode 100644 index d80dbc6ab90..00000000000 --- a/unixauthclient/src/test/com/xasecure/test/authentication/UnixAuthenticationTester.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 com.xasecure.test.authentication; - -import javax.security.auth.login.LoginContext; - -public class UnixAuthenticationTester { - - public static void main(String[] args) throws Throwable { - new UnixAuthenticationTester().run(); - } - - public void run() throws Throwable { - LoginContext loginContext = new LoginContext("PolicyManager"); - System.err.println("After login ..."); - loginContext.login(); - System.err.println("Subject:" + loginContext.getSubject()); - loginContext.logout(); - } -} diff --git a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestRemoteUnixLoginModule.java b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestRemoteUnixLoginModule.java deleted file mode 100644 index 9f97f057301..00000000000 --- a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestRemoteUnixLoginModule.java +++ /dev/null @@ -1,408 +0,0 @@ -/* - * 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.ranger.authentication.unix.jaas; - -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLServerSocketFactory; -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.LoginException; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.math.BigInteger; -import java.net.ServerSocket; -import java.net.Socket; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStore; -import java.security.Principal; -import java.security.SecureRandom; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @generated by Cursor - * @description : Unit Test cases for RemoteUnixLoginModule - */ - -@ExtendWith(MockitoExtension.class) -@TestMethodOrder(MethodOrderer.MethodName.class) -public class TestRemoteUnixLoginModule { - @Test - public void test01_login_success_commit_addsPrincipals_and_logout_clears() throws Exception { - try (OneShotAuthServer server = new OneShotAuthServer("OK: g1, g2")) { - RemoteUnixLoginModule m = new RemoteUnixLoginModule(); - Subject subject = new Subject(); - Map opts = baseOptions("127.0.0.1", server.getPort()); - m.initialize(subject, fixedCredentials("alice", "secret"), new HashMap<>(), opts); - - assertTrue(m.login()); - assertTrue(m.commit()); - - Set principals = subject.getPrincipals(); - assertTrue(principals.stream().anyMatch(p -> p instanceof UnixUserPrincipal && p.getName().equals("alice"))); - assertTrue(principals.stream().anyMatch(p -> p instanceof UnixGroupPrincipal && p.getName().equals("g1"))); - assertTrue(principals.stream().anyMatch(p -> p instanceof UnixGroupPrincipal && p.getName().equals("g2"))); - - assertTrue(m.logout()); - assertTrue(subject.getPrincipals().isEmpty()); - } - } - - @Test - public void test02_login_failed_throws_LoginException() throws Exception { - try (OneShotAuthServer server = new OneShotAuthServer("FAILED: Invalid Password")) { - RemoteUnixLoginModule m = new RemoteUnixLoginModule(); - Subject subject = new Subject(); - Map opts = baseOptions("127.0.0.1", server.getPort()); - m.initialize(subject, fixedCredentials("alice", "secret"), new HashMap<>(), opts); - assertThrows(LoginException.class, m::login); - assertFalse(m.commit()); - } - } - - @Test - public void test03_login_nullReply_throws_LoginException() throws Exception { - try (OneShotAuthServer server = new OneShotAuthServer(null)) { - RemoteUnixLoginModule m = new RemoteUnixLoginModule(); - Subject subject = new Subject(); - Map opts = baseOptions("127.0.0.1", server.getPort()); - m.initialize(subject, fixedCredentials("bob", "x"), new HashMap<>(), opts); - assertThrows(LoginException.class, m::login); - } - } - - @Test - public void test04_login_disabled_returnsFalse_and_commitClearsPrincipals() throws Exception { - RemoteUnixLoginModule m = new RemoteUnixLoginModule(); - Subject subject = new Subject(); - Map opts = baseOptions("localhost", 65535); - opts.put("ranger.unixauth.remote.login.enabled", "false"); - m.initialize(subject, fixedCredentials("tom", "y"), new HashMap<>(), opts); - - assertFalse(m.login()); - assertFalse(m.commit()); - assertTrue(subject.getPrincipals().isEmpty()); - } - - @Test - public void test05_ssl_disabledValidation_connectFailure_throwsLoginException() { - RemoteUnixLoginModule m = new RemoteUnixLoginModule(); - Subject subject = new Subject(); - Map opts = baseOptions("127.0.0.1", 65000); - opts.put("ranger.unixauth.ssl.enabled", "true"); - opts.put("ranger.unixauth.server.cert.validation", "false"); - m.initialize(subject, fixedCredentials("alice", "secret"), new HashMap<>(), opts); - assertThrows(LoginException.class, m::login); - } - - @Test - public void test06_ssl_withTruststore_loadFails_throwsLoginException() { - RemoteUnixLoginModule m = new RemoteUnixLoginModule(); - Subject subject = new Subject(); - Map opts = baseOptions("127.0.0.1", 65001); - opts.put("ranger.unixauth.ssl.enabled", "true"); - opts.put("ranger.unixauth.server.cert.validation", "true"); - opts.put("ranger.unixauth.truststore", "nonexistent.jks"); - opts.put("ranger.unixauth.truststore.password", ""); - m.initialize(subject, fixedCredentials("bob", "pw"), new HashMap<>(), opts); - assertThrows(LoginException.class, m::login); - } - - @Test - public void test07_ssl_withKeystore_loadFails_throwsLoginException() { - RemoteUnixLoginModule m = new RemoteUnixLoginModule(); - Subject subject = new Subject(); - Map opts = baseOptions("127.0.0.1", 65002); - opts.put("ranger.unixauth.ssl.enabled", "true"); - opts.put("ranger.unixauth.keystore", "nonexistent-keystore.jks"); - opts.put("ranger.unixauth.keystore.password", ""); - // disable server cert validation to avoid needing truststore - opts.put("ranger.unixauth.server.cert.validation", "false"); - m.initialize(subject, fixedCredentials("carol", "pw"), new HashMap<>(), opts); - assertThrows(LoginException.class, m::login); - } - - @Test - public void test08_abort_resetsLoginState_and_commitReturnsFalse() throws Exception { - try (OneShotAuthServer server = new OneShotAuthServer("OK:")) { - RemoteUnixLoginModule m = new RemoteUnixLoginModule(); - Subject subject = new Subject(); - Map opts = baseOptions("127.0.0.1", server.getPort()); - m.initialize(subject, fixedCredentials("alice", "secret"), new HashMap<>(), opts); - - assertTrue(m.login()); - assertTrue(m.abort()); - - // After abort(), loginSuccessful should be false; commit should return false and clear principals - subject.getPrincipals().add(new UnixUserPrincipal("temp")); - assertFalse(m.commit()); - assertTrue(subject.getPrincipals().isEmpty()); - } - } - - @Test - public void test09_ssl_ignoreValidation_successfulHandshake_executesTrustManager_andLogin() throws Exception { - try (SslOneShotAuthServer server = new SslOneShotAuthServer("OK: gA, gB")) { - RemoteUnixLoginModule m = new RemoteUnixLoginModule(); - Subject subject = new Subject(); - Map opts = baseOptions("127.0.0.1", server.getPort()); - opts.put("ranger.unixauth.ssl.enabled", "true"); - opts.put("ranger.unixauth.server.cert.validation", "false"); - m.initialize(subject, fixedCredentials("eve", "pw"), new HashMap<>(), opts); - - assertTrue(m.login()); - assertTrue(m.commit()); - assertTrue(subject.getPrincipals().stream().anyMatch(p -> p instanceof UnixUserPrincipal && p.getName().equals("eve"))); - assertTrue(subject.getPrincipals().stream().anyMatch(p -> p instanceof UnixGroupPrincipal && p.getName().equals("gA"))); - assertTrue(m.logout()); - } - } - - @Test - public void test10_login_callbackHandlerThrowsIOException_isWrappedAsLoginException() { - RemoteUnixLoginModule m = new RemoteUnixLoginModule(); - Subject subject = new Subject(); - Map opts = baseOptions("127.0.0.1", 65010); - CallbackHandler cb = new CallbackHandler() { - @Override - public void handle(Callback[] callbacks) throws IOException { - throw new IOException("boom"); - } - }; - m.initialize(subject, cb, new HashMap<>(), opts); - assertThrows(LoginException.class, m::login); - } - - @Test - public void test11_login_callbackHandlerThrowsUnsupported_isWrappedAsLoginException() { - RemoteUnixLoginModule m = new RemoteUnixLoginModule(); - Subject subject = new Subject(); - Map opts = baseOptions("127.0.0.1", 65011); - CallbackHandler cb = new CallbackHandler() { - @Override - public void handle(Callback[] callbacks) throws UnsupportedCallbackException { - throw new UnsupportedCallbackException(new NameCallback("user")); - } - }; - m.initialize(subject, cb, new HashMap<>(), opts); - assertThrows(LoginException.class, m::login); - } - - private static Map baseOptions(String host, int port) { - Map opts = new HashMap<>(); - opts.put("ranger.unixauth.remote.login.enabled", "true"); - opts.put("ranger.unixauth.service.hostname", host); - opts.put("ranger.unixauth.service.port", String.valueOf(port)); - opts.put("ranger.unixauth.ssl.enabled", "false"); - opts.put("ranger.unixauth.debug", "false"); - return opts; - } - - private static CallbackHandler fixedCredentials(String user, String pass) { - return new CallbackHandler() { - @Override - public void handle(Callback[] callbacks) throws IOException { - for (Callback cb : callbacks) { - if (cb instanceof NameCallback) { - ((NameCallback) cb).setName(user); - } else if (cb instanceof PasswordCallback) { - ((PasswordCallback) cb).setPassword(pass.toCharArray()); - } - } - } - }; - } - - private static class OneShotAuthServer implements AutoCloseable { - private final ServerSocket server; - private final Thread thread; - private final CountDownLatch ready = new CountDownLatch(1); - private final String reply; - - OneShotAuthServer(String reply) throws IOException { - this.server = new ServerSocket(0); - this.reply = reply; - this.thread = new Thread(() -> { - ready.countDown(); - try (Socket s = server.accept()) { - BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); - in.readLine(); - try (OutputStreamWriter out = new OutputStreamWriter(s.getOutputStream())) { - if (reply != null) { - out.write(reply + "\n"); - out.flush(); - } else { - // close without sending - } - } - } catch (IOException ignored) { - } finally { - try { - server.close(); - } catch (IOException ignored) { - } - } - }); - this.thread.setDaemon(true); - this.thread.start(); - try { - ready.await(5, TimeUnit.SECONDS); - } catch (InterruptedException ignored) { - } - } - - @Override - public void close() { - try { - server.close(); - } catch (IOException ignored) { - } - } - - int getPort() { - return server.getLocalPort(); - } - } - - private static class SslOneShotAuthServer implements AutoCloseable { - private final SSLServerSocket server; - private final Thread thread; - private final CountDownLatch ready = new CountDownLatch(1); - private final String reply; - - SslOneShotAuthServer(String reply) throws Exception { - this.reply = reply; - - KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); - kpg.initialize(2048); - KeyPair kp = kpg.generateKeyPair(); - - X509Certificate cert = generateSelfSigned("CN=localhost", kp); - - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - ks.load(null, null); - char[] pass = "changeit".toCharArray(); - ks.setKeyEntry("server", kp.getPrivate(), pass, new Certificate[] {cert}); - - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, pass); - KeyManager[] kms = kmf.getKeyManagers(); - - SSLContext ctx = SSLContext.getInstance("TLSv1.2"); - ctx.init(kms, null, new SecureRandom()); - SSLServerSocketFactory ssf = ctx.getServerSocketFactory(); - this.server = (SSLServerSocket) ssf.createServerSocket(0); - - this.thread = new Thread(() -> { - ready.countDown(); - try (Socket s = server.accept()) { - BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); - in.readLine(); - try (OutputStreamWriter out = new OutputStreamWriter(s.getOutputStream())) { - out.write(reply + "\n"); - out.flush(); - } - } catch (IOException ignored) { - } finally { - try { - server.close(); - } catch (IOException ignored) { - } - } - }); - this.thread.setDaemon(true); - this.thread.start(); - try { - ready.await(5, TimeUnit.SECONDS); - } catch (InterruptedException ignored) { - } - } - - @Override - public void close() { - try { - server.close(); - } catch (IOException ignored) { - } - } - - int getPort() { - return server.getLocalPort(); - } - - private static X509Certificate generateSelfSigned(String dn, KeyPair keyPair) throws Exception { - // Generate a self-signed certificate at runtime using Bouncy Castle - // This avoids hardcoded certificates and uses only standard APIs - - long now = System.currentTimeMillis(); - Date notBefore = new Date(now - 60000); // 1 minute ago - Date notAfter = new Date(now + 86400000L); // 1 day from now - - BigInteger serial = new BigInteger(64, new SecureRandom()); - X500Name subject = new X500Name(dn); - - JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder( - subject, // issuer = subject (self-signed) - serial, - notBefore, - notAfter, - subject, - keyPair.getPublic()); - - ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate()); - - X509CertificateHolder holder = certBuilder.build(signer); - - return new JcaX509CertificateConverter().getCertificate(holder); - } - } -} diff --git a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixGroupPrincipal.java b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixGroupPrincipal.java deleted file mode 100644 index 407c43895cd..00000000000 --- a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixGroupPrincipal.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.ranger.authentication.unix.jaas; - -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * @generated by Cursor - * @description : Unit Test cases for UnixGroupPrincipal - */ - -@ExtendWith(MockitoExtension.class) -@TestMethodOrder(MethodOrderer.MethodName.class) -public class TestUnixGroupPrincipal { - @Test - public void test01_getName_returnsGroupName() { - UnixGroupPrincipal principal = new UnixGroupPrincipal("dev"); - assertEquals("dev", principal.getName()); - } -} diff --git a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixUserPrincipal.java b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixUserPrincipal.java deleted file mode 100644 index e53d0b678f6..00000000000 --- a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixUserPrincipal.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.ranger.authentication.unix.jaas; - -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * @generated by Cursor - * @description : Unit Test cases for UnixUserPrincipal - */ - -@ExtendWith(MockitoExtension.class) -@TestMethodOrder(MethodOrderer.MethodName.class) -public class TestUnixUserPrincipal { - @Test - public void test01_getName_returnsUserName() { - UnixUserPrincipal principal = new UnixUserPrincipal("alice"); - assertEquals("alice", principal.getName()); - } -} diff --git a/unixauthnative/.gitignore b/unixauthnative/.gitignore deleted file mode 100644 index 02f68bb35da..00000000000 --- a/unixauthnative/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target/ -/bin/ -.settings/ diff --git a/unixauthnative/pom.xml b/unixauthnative/pom.xml deleted file mode 100644 index f24b14e3f76..00000000000 --- a/unixauthnative/pom.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - 4.0.0 - - org.apache.ranger - ranger - 3.0.0-SNAPSHOT - .. - - credValidator - uexe - Unix Native Authenticator - Unix authentication service - - - - org.codehaus.mojo - native-maven-plugin - ${native-maven-plugin.version} - true - - - ${commonCompilerOptions} - - - - src/main/c - - **/*.c - - - - - -lcrypt - - - - - - diff --git a/unixauthnative/src/main/c/credValidator.c b/unixauthnative/src/main/c/credValidator.c deleted file mode 100644 index 891b0b9e059..00000000000 --- a/unixauthnative/src/main/c/credValidator.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define STRLEN 64 - -int main(int ac, char **av, char **ev) -{ - char username[STRLEN] ; - char password[STRLEN] ; - char line[512] ; - char format[20]; - struct passwd *pwp; - struct spwd *spwd ; - - fgets(line,512,stdin) ; - sprintf(format, "LOGIN:%%%ds %%%ds", STRLEN-1, STRLEN-1); - sscanf(line, format, username,password) ; - - pwp = getpwnam(username) ; - - if (pwp == (struct passwd *)NULL) { - fprintf(stdout, "FAILED: [%s] does not exists.\n", username) ; - exit(1) ; - } - - spwd = getspnam(pwp->pw_name) ; - - if (spwd == (struct spwd *)NULL) { - fprintf(stdout, "FAILED: unable to get (shadow) password for '%s', because '%s'\n", username, strerror(errno)); - exit(1) ; - } - else { - char *gen = crypt(password,spwd->sp_pwdp) ; - if (gen == (char *)NULL) { - fprintf(stdout, "FAILED: crypt failed with: '%s'\n", strerror(errno)); - exit(1); - } - if (strcmp(spwd->sp_pwdp,gen) == 0) { - fprintf(stdout, "OK:\n") ; - exit(0); - } - else { - fprintf(stdout, "FAILED: Password did not match.\n") ; - exit(1) ; - } - } - exit(0) ; -} diff --git a/unixauthpam/.gitignore b/unixauthpam/.gitignore deleted file mode 100644 index 02f68bb35da..00000000000 --- a/unixauthpam/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target/ -/bin/ -.settings/ diff --git a/unixauthpam/pom.xml b/unixauthpam/pom.xml deleted file mode 100644 index f8bf697f59e..00000000000 --- a/unixauthpam/pom.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - 4.0.0 - - org.apache.ranger - ranger - 3.0.0-SNAPSHOT - .. - - pamCredValidator - uexe - PAM Authenticator - PAM authentication service - - - - org.codehaus.mojo - native-maven-plugin - 1.0-alpha-11 - true - - - ${commonCompilerOptions} - - - - src/main/c - - **/*.c - - - - - -lpam - - - - - - diff --git a/unixauthpam/src/main/c/pamCredValidator.c b/unixauthpam/src/main/c/pamCredValidator.c deleted file mode 100644 index 8ec9f0e3e21..00000000000 --- a/unixauthpam/src/main/c/pamCredValidator.c +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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. - */ - - /* - You need to add the following (or equivalent) to the - /etc/pam.d/ranger-remote file: - # check authorization - auth required pam_unix.so - account required pam_unix.so - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define STRLEN 64 - -int pamconv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { - if (num_msg != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF) { - fprintf(stderr, "ERROR: Unexpected PAM conversation '%d/%s'\n", msg[0]->msg_style, msg[0]->msg); - return PAM_CONV_ERR; - } - if (!appdata_ptr) { - fprintf(stderr, "ERROR: No password available to conversation!\n"); - return PAM_CONV_ERR; - } - *resp = calloc(num_msg, sizeof(struct pam_response)); - if (!*resp) { - fprintf(stderr, "ERROR: Out of memory!\n"); - return PAM_CONV_ERR; - } - (*resp)[0].resp = strdup((char *) appdata_ptr); - (*resp)[0].resp_retcode = 0; - - return ((*resp)[0].resp ? PAM_SUCCESS : PAM_CONV_ERR); -} - -struct pam_conv conv = { pamconv, NULL }; - -int main(int ac, char **av, char **ev) -{ - char username[STRLEN] ; - char password[STRLEN] ; - char line[512] ; - char format[20]; - - int retval; - pam_handle_t *pamh = NULL; - - sprintf(format, "LOGIN:%%%ds %%%ds", STRLEN-1, STRLEN-1); - fgets(line,512,stdin) ; - sscanf(line, format, username,password) ; - conv.appdata_ptr = (char *) password; - - retval = pam_start("ranger-remote", username, &conv, &pamh); - if (retval != PAM_SUCCESS) { - /* why expose this? */ - fprintf(stdout, "FAILED: [%s] does not exists.\n", username) ; - if (pamh) { - pam_end(pamh, retval); - } - exit(1); - } - - retval = pam_authenticate(pamh, 0); - if (retval != PAM_SUCCESS) { - fprintf(stdout, "FAILED: Password did not match(%s).\n", pam_strerror(pamh, retval)) ; - if (pamh) { - pam_end(pamh, retval); - } - exit(1); - } - - /* authorize */ - retval = pam_acct_mgmt(pamh, 0); - if (retval != PAM_SUCCESS) { - fprintf(stdout, "FAILED: [%s] is not authorized.\n", username) ; - if (pamh) { - pam_end(pamh, retval); - } - exit(1); - } - - /* establish the requested credentials */ - if ((retval = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { - fprintf(stdout, "FAILED: Error setting credentials for [%s].\n", username) ; - if (pamh) { - pam_end(pamh, retval); - } - exit(1); - } - - /* not opening a session, as logout has not been implemented as a remote service */ - fprintf(stdout, "OK:\n") ; - - if (pamh) { - pam_end(pamh, retval); - } - - exit(0) ; -} diff --git a/unixauthservice/.gitignore b/unixauthservice/.gitignore deleted file mode 100644 index 02f68bb35da..00000000000 --- a/unixauthservice/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target/ -/bin/ -.settings/ diff --git a/unixauthservice/conf.dist/jaas.conf b/unixauthservice/conf.dist/jaas.conf deleted file mode 100644 index f6b30faf698..00000000000 --- a/unixauthservice/conf.dist/jaas.conf +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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. - */ -PolicyManager { - org.apache.ranger.authentication.unix.jaas.RemoteUnixLoginModule REQUIRED - authServiceHostName="192.168.22.88" authServicePort="5151" debug="true" - keyStore="keystore.jks" keyStorePassword="password" - trustStore="unixauthclient.jks" trustStorePassword="mEmePaS3w9rD" ; -} ; diff --git a/unixauthservice/pom.xml b/unixauthservice/pom.xml deleted file mode 100644 index 117f8bb0de6..00000000000 --- a/unixauthservice/pom.xml +++ /dev/null @@ -1,134 +0,0 @@ - - - - 4.0.0 - - org.apache.ranger - ranger - 3.0.0-SNAPSHOT - .. - - unixauthservice - jar - Unix Authentication Service - Unix authentication service - - - ch.qos.logback - logback-classic - ${logback.version} - - - com.google.code.gson - gson - - - commons-cli - commons-cli - ${commons.cli.version} - - - commons-collections - commons-collections - ${commons.collections.version} - - - io.netty - netty-handler - ${netty-all.version} - - - io.netty - netty-transport-native-epoll - ${netty-all.version} - - - jakarta.activation - jakarta.activation-api - ${jakarta.activation-api.version} - - - jakarta.ws.rs - jakarta.ws.rs-api - ${jakarta.ws.rs-api.version} - - - jakarta.xml.bind - jakarta.xml.bind-api - ${jakarta.xml.bind-api.version} - - - javax.inject - javax.inject - ${javax.inject.version} - - - org.apache.commons - commons-configuration2 - ${commons.configuration.version} - - - org.apache.commons - commons-lang3 - ${commons.lang3.version} - - - org.apache.hadoop - hadoop-auth - ${hadoop.version} - - - com.sun.jersey - * - - - log4j - * - - - net.minidev - json-smart - - - org.apache.zookeeper - zookeeper - - - org.slf4j - * - - - - - org.apache.ranger - unixusersync - ${project.version} - - - org.slf4j - slf4j-api - ${slf4j.version} - - - org.mockito - mockito-junit-jupiter - ${mockito.version} - test - - - diff --git a/unixauthservice/src/main/java/org/apache/ranger/authentication/LoginAttemptTracker.java b/unixauthservice/src/main/java/org/apache/ranger/authentication/LoginAttemptTracker.java deleted file mode 100644 index 073bcb137c5..00000000000 --- a/unixauthservice/src/main/java/org/apache/ranger/authentication/LoginAttemptTracker.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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.ranger.authentication; - -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -public class LoginAttemptTracker { - private static final long CLEANUP_INTERVAL_MS = TimeUnit.MINUTES.toMillis(5); - - private final ConcurrentHashMap attemptsByIp = new ConcurrentHashMap<>(); - private final ConcurrentHashMap attemptsByAccount = new ConcurrentHashMap<>(); - - private final int maxFailures; - private final long windowMs; - private final long lockoutMs; - - private final boolean accountFanoutEnabled; - private final int accountDistinctIpThreshold; - private final long accountWindowMs; - private final long accountBaseDelayMs; - private final long accountMaxDelayMs; - - private volatile long lastCleanupTime; - - public LoginAttemptTracker(int maxFailures, long windowMs, long lockoutMs) { - this(maxFailures, windowMs, lockoutMs, false, 0, 0, 0, 0); - } - - public LoginAttemptTracker(int maxFailures, long windowMs, long lockoutMs, - boolean accountFanoutEnabled, int accountDistinctIpThreshold, - long accountWindowMs, long accountBaseDelayMs, long accountMaxDelayMs) { - this.maxFailures = maxFailures; - this.windowMs = windowMs; - this.lockoutMs = lockoutMs; - this.lastCleanupTime = System.currentTimeMillis(); - - this.accountFanoutEnabled = accountFanoutEnabled; - this.accountDistinctIpThreshold = accountDistinctIpThreshold; - this.accountWindowMs = accountWindowMs; - this.accountBaseDelayMs = accountBaseDelayMs; - this.accountMaxDelayMs = accountMaxDelayMs; - } - - public boolean isBlocked(String sourceIp) { - IpRecord record = attemptsByIp.get(sourceIp); - - if (record == null) { - return false; - } - - return record.lockedUntil > System.currentTimeMillis(); - } - - public long getAccountDelayMs(String account) { - if (!accountFanoutEnabled || account == null || account.isEmpty()) { - return 0; - } - - AccountRecord record = attemptsByAccount.get(account); - - if (record == null) { - return 0; - } - - int distinctIpCount; - - synchronized (record) { - if (System.currentTimeMillis() - record.windowStart > accountWindowMs) { - return 0; - } - - distinctIpCount = record.failingIps.size(); - } - - if (distinctIpCount <= accountDistinctIpThreshold) { - return 0; - } - - long delay = accountBaseDelayMs * (distinctIpCount - accountDistinctIpThreshold); - - return Math.min(delay, accountMaxDelayMs); - } - - public void recordFailure(String sourceIp) { - recordFailure(sourceIp, null); - } - - public void recordFailure(String sourceIp, String account) { - maybeCleanup(); - recordIpFailure(sourceIp); - - if (accountFanoutEnabled && account != null && !account.isEmpty()) { - recordAccountFailure(account, sourceIp); - } - } - - private void recordIpFailure(String sourceIp) { - long now = System.currentTimeMillis(); - IpRecord record = attemptsByIp.computeIfAbsent(sourceIp, k -> new IpRecord(now)); - - synchronized (record) { - if (now - record.windowStart > windowMs) { - record.windowStart = now; - record.failCount.set(0); - } - - record.lastActivity = now; - - int count = record.failCount.incrementAndGet(); - - if (count >= maxFailures) { - record.lockedUntil = now + lockoutMs; - } - } - } - - private void recordAccountFailure(String account, String sourceIp) { - long now = System.currentTimeMillis(); - AccountRecord record = attemptsByAccount.computeIfAbsent(account, k -> new AccountRecord(now)); - - synchronized (record) { - if (now - record.windowStart > accountWindowMs) { - record.windowStart = now; - record.failingIps.clear(); - } - - record.lastActivity = now; - record.failingIps.add(sourceIp); - } - } - - public void recordSuccess(String sourceIp) { - recordSuccess(sourceIp, null); - } - - public void recordSuccess(String sourceIp, String account) { - attemptsByIp.remove(sourceIp); - - if (accountFanoutEnabled && account != null && !account.isEmpty()) { - attemptsByAccount.remove(account); - } - } - - private void maybeCleanup() { - long now = System.currentTimeMillis(); - - if (now - lastCleanupTime < CLEANUP_INTERVAL_MS) { - return; - } - - lastCleanupTime = now; - long ipCutoff = now - windowMs; - Iterator> ipIt = attemptsByIp.entrySet().iterator(); - while (ipIt.hasNext()) { - IpRecord record = ipIt.next().getValue(); - - if (record.lastActivity < ipCutoff && record.lockedUntil <= now) { - ipIt.remove(); - } - } - - if (accountFanoutEnabled) { - long accountCutoff = now - accountWindowMs; - Iterator> acctIt = attemptsByAccount.entrySet().iterator(); - while (acctIt.hasNext()) { - AccountRecord record = acctIt.next().getValue(); - if (record.lastActivity < accountCutoff) { - acctIt.remove(); - } - } - } - } - - private static final class IpRecord { - final AtomicInteger failCount = new AtomicInteger(0); - volatile long windowStart; - volatile long lastActivity; - volatile long lockedUntil; - - IpRecord(long now) { - this.windowStart = now; - this.lastActivity = now; - } - } - - private static final class AccountRecord { - final Set failingIps = ConcurrentHashMap.newKeySet(); - volatile long windowStart; - volatile long lastActivity; - - AccountRecord(long now) { - this.windowStart = now; - this.lastActivity = now; - } - } -} diff --git a/unixauthservice/src/main/java/org/apache/ranger/authentication/PasswordValidator.java b/unixauthservice/src/main/java/org/apache/ranger/authentication/PasswordValidator.java deleted file mode 100644 index c3925dc728f..00000000000 --- a/unixauthservice/src/main/java/org/apache/ranger/authentication/PasswordValidator.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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.ranger.authentication; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.net.Socket; -import java.util.List; - -public class PasswordValidator implements Runnable { - private static final Logger LOG = LoggerFactory.getLogger(PasswordValidator.class); - private static final String GENERIC_FAILURE_RESPONSE = "FAILED: Authentication failed."; - private static final String LOCKED_OUT_RESPONSE = "FAILED: Too many attempts. Try again later."; - - private static List adminUserList; - private static String adminRoleNames; - private static String validatorProgram; - - private final LoginAttemptTracker loginAttemptTracker; - private Socket client; - - public PasswordValidator(Socket client, LoginAttemptTracker loginAttemptTracker) { - this.client = client; - this.loginAttemptTracker = loginAttemptTracker; - } - - public static String getValidatorProgram() { - return validatorProgram; - } - - public static void setValidatorProgram(String validatorProgram) { - PasswordValidator.validatorProgram = validatorProgram; - } - - public static List getAdminUserList() { - return adminUserList; - } - - public static void setAdminUserList(List adminUserList) { - PasswordValidator.adminUserList = adminUserList; - } - - public static String getAdminRoleNames() { - return adminRoleNames; - } - - public static void setAdminRoleNames(String adminRoleNames) { - PasswordValidator.adminRoleNames = adminRoleNames; - } - - @Override - public void run() { - BufferedReader reader; - PrintWriter writer = null; - - String userName = null; - - String remoteIp = client.getInetAddress() != null ? client.getInetAddress().getHostAddress() : "unknown"; - try { - reader = new BufferedReader(new InputStreamReader(client.getInputStream())); - writer = new PrintWriter(new OutputStreamWriter(client.getOutputStream())); - String request = reader.readLine(); - userName = parseUserName(request); - if (loginAttemptTracker.isBlocked(remoteIp)) { - LOG.warn("Rejected UnixAuth attempt from [{}] - source IP is temporarily locked out due to repeated failures.", remoteIp); - writer.println(LOCKED_OUT_RESPONSE); - writer.flush(); - return; - } - if (request == null) { - loginAttemptTracker.recordFailure(remoteIp, userName); - LOG.warn("Rejected UnixAuth attempt from [{}] - connection closed before sending any data.", remoteIp); - writer.println(GENERIC_FAILURE_RESPONSE); - writer.flush(); - return; - } - long accountDelayMs = loginAttemptTracker.getAccountDelayMs(userName); - - if (accountDelayMs > 0) { - LOG.warn("Applying {}ms delay for user [{}] from [{}] - failures against this account recently seen from multiple source IPs.", accountDelayMs, userName, remoteIp); - - try { - Thread.sleep(accountDelayMs); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - } - } - - if (validatorProgram == null) { - loginAttemptTracker.recordFailure(remoteIp, userName); - LOG.error("Response [{}] for user: {} from [{}] as ValidatorProgram is not defined in configuration.", GENERIC_FAILURE_RESPONSE, userName, remoteIp); - writer.println(GENERIC_FAILURE_RESPONSE); - writer.flush(); - } else { - BufferedReader pReader; - PrintWriter pWriter; - Process p = null; - - try { - p = Runtime.getRuntime().exec(validatorProgram); - - pReader = new BufferedReader(new InputStreamReader(p.getInputStream())); - pWriter = new PrintWriter(new OutputStreamWriter(p.getOutputStream())); - - pWriter.println(request); - pWriter.flush(); - - String res = pReader.readLine(); - - if (res != null && res.startsWith("OK")) { - loginAttemptTracker.recordSuccess(remoteIp, userName); - if (adminRoleNames != null && adminUserList != null) { - if (adminUserList.contains(userName)) { - res = res + " " + adminRoleNames; - } - } - } else { - loginAttemptTracker.recordFailure(remoteIp, userName); - res = GENERIC_FAILURE_RESPONSE; - } - - LOG.info("Response [{}] for user: {} from [{}]", res, userName, remoteIp); - - writer.println(res); - writer.flush(); - } finally { - if (p != null) { - p.destroy(); - } - } - } - } catch (Throwable t) { - loginAttemptTracker.recordFailure(remoteIp, userName); - if (userName != null && writer != null) { - LOG.error("Response [{}] for user: {} from [{}], {}", GENERIC_FAILURE_RESPONSE, userName, remoteIp, t.getMessage()); - writer.println(GENERIC_FAILURE_RESPONSE); - writer.flush(); - } - } finally { - try { - if (client != null) { - client.close(); - } - } catch (IOException ioe) { - LOG.debug("Close socket failure. Detail: ", ioe); - } finally { - client = null; - } - } - } - - private static String parseUserName(String request) { - if (request == null || !request.startsWith("LOGIN:")) { - return null; - } - String line = request.substring(6).trim(); - int passwordAt = line.indexOf(' '); - if (passwordAt == -1) { - return null; - } - return line.substring(0, passwordAt).trim(); - } -} diff --git a/unixauthservice/src/main/java/org/apache/ranger/authentication/UnixAuthenticationService.java b/unixauthservice/src/main/java/org/apache/ranger/authentication/UnixAuthenticationService.java deleted file mode 100644 index 8f5a461107b..00000000000 --- a/unixauthservice/src/main/java/org/apache/ranger/authentication/UnixAuthenticationService.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * 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.ranger.authentication; - -import org.apache.ranger.credentialapi.CredentialReader; -import org.apache.ranger.plugin.util.XMLUtils; -import org.apache.ranger.unixusersync.config.UserGroupSyncConfig; -import org.apache.ranger.unixusersync.ha.UserSyncHAInitializerImpl; -import org.apache.ranger.usergroupsync.UserGroupSync; -import org.apache.ranger.usergroupsync.UserSyncMetricsProducer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLServerSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.ServerSocket; -import java.net.Socket; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Properties; -import java.util.Set; - -public class UnixAuthenticationService { - private static final Logger LOG = LoggerFactory.getLogger(UnixAuthenticationService.class); - - private static final String serviceName = "UnixAuthenticationService"; - private static final String SSL_ALGORITHM = "TLS"; - private static final String REMOTE_LOGIN_AUTH_SERVICE_PORT_PARAM = "ranger.usersync.port"; - private static final String SSL_KEYSTORE_PATH_PARAM = "ranger.usersync.keystore.file"; - private static final String SSL_TRUSTSTORE_PATH_PARAM = "ranger.usersync.truststore.file"; - private static final String SSL_KEYSTORE_FILE_TYPE_PARAM = "ranger.keystore.file.type"; - private static final String SSL_TRUSTSTORE_FILE_TYPE_PARAM = "ranger.truststore.file.type"; - private static final String SSL_KEYSTORE_PATH_PASSWORD_ALIAS = "usersync.ssl.key.password"; - private static final String SSL_TRUSTSTORE_PATH_PASSWORD_ALIAS = "usersync.ssl.truststore.password"; - private static final String CRED_VALIDATOR_PROG = "ranger.usersync.passwordvalidator.path"; - private static final String ADMIN_USER_LIST_PARAM = "admin.users"; - private static final String ADMIN_ROLE_LIST_PARAM = "admin.roleNames"; - private static final String SSL_ENABLED_PARAM = "ranger.usersync.ssl"; - private static final String CREDSTORE_FILENAME_PARAM = "ranger.usersync.credstore.filename"; - private static final String[] UGSYNC_CONFIG_XML_FILES = {"ranger-ugsync-default.xml", "ranger-ugsync-site.xml"}; - private static final String UNIXAUTH_MAX_FAILED_ATTEMPTS_PARAM = "ranger.usersync.unixauth.max.failed.attempts"; - private static final String UNIXAUTH_ATTEMPT_WINDOW_MS_PARAM = "ranger.usersync.unixauth.attempt.window.ms"; - private static final String UNIXAUTH_LOCKOUT_DURATION_MS_PARAM = "ranger.usersync.unixauth.lockout.duration.ms"; - private static final String UNIXAUTH_REQUIRE_CLIENT_AUTH_PARAM = "ranger.usersync.unixauth.require.client.auth"; - private static final String UNIXAUTH_SOCKET_TIMEOUT_MS_PARAM = "ranger.usersync.unixauth.socket.timeout.ms"; - private static final String UNIXAUTH_ACCOUNT_FANOUT_ENABLED_PARAM = "ranger.usersync.unixauth.account.fanout.enabled"; - private static final String UNIXAUTH_ACCOUNT_DISTINCT_IP_THRESHOLD_PARAM = "ranger.usersync.unixauth.account.distinct.ip.threshold"; - private static final String UNIXAUTH_ACCOUNT_WINDOW_MS_PARAM = "ranger.usersync.unixauth.account.window.ms"; - private static final String UNIXAUTH_ACCOUNT_BASE_DELAY_MS_PARAM = "ranger.usersync.unixauth.account.base.delay.ms"; - private static final String UNIXAUTH_ACCOUNT_MAX_DELAY_MS_PARAM = "ranger.usersync.unixauth.account.max.delay.ms"; - - private static boolean enableUnixAuth; - - private final List adminUserList = new ArrayList<>(); - - private String adminRoleNames; - private String keyStorePath; - private String keyStorePathPassword; - private String keyStoreType; - private String trustStorePath; - private String trustStorePathPassword; - private String trustStoreType; - private List enabledProtocolsList; - private List enabledCipherSuiteList; - private UserSyncHAInitializerImpl userSyncHAInitializerImpl; - private int portNum; - private boolean sslEnabled; - private boolean requireClientAuth; - private LoginAttemptTracker loginAttemptTracker; - private int socketTimeoutMs; - - public UnixAuthenticationService() { - } - - public static void main(String[] args) { - enableUnixAuth = Arrays.stream(args).anyMatch("-enableUnixAuth"::equalsIgnoreCase); - UnixAuthenticationService service = new UnixAuthenticationService(); - service.userSyncHAInitializerImpl = UserSyncHAInitializerImpl.getInstance(UserGroupSyncConfig.getInstance().getUserGroupConfig()); - service.run(); - } - - public void run() { - try { - LOG.info("Starting User Sync Service!"); - startUnixUserGroupSyncProcess(); - Thread.sleep(5000); - if (enableUnixAuth) { - LOG.info("Enabling Unix Auth Service!"); - init(); - startService(); - } else { - LOG.info("Unix Auth Service Disabled!"); - } - } catch (Throwable t) { - LOG.error("ERROR: Service: {}", serviceName, t); - } finally { - LOG.info("Service: {} - STOPPED", serviceName); - if (this.userSyncHAInitializerImpl != null) { - LOG.info("Stopping curator leader latch service as main thread is closing"); - this.userSyncHAInitializerImpl.stop(); - } - } - } - - public void startService() throws Throwable { - SSLContext context = SSLContext.getInstance(SSL_ALGORITHM); - - KeyManager[] km = null; - - if (keyStorePath != null && !keyStorePath.isEmpty()) { - KeyStore ks = KeyStore.getInstance(keyStoreType); - - try (InputStream in = getFileInputStream(keyStorePath)) { - if (keyStorePathPassword == null) { - keyStorePathPassword = ""; - } - ks.load(in, keyStorePathPassword.toCharArray()); - } - - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, keyStorePathPassword.toCharArray()); - km = kmf.getKeyManagers(); - } - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - - KeyStore trustStoreKeyStore = null; - - if (trustStorePath != null && !trustStorePath.isEmpty()) { - trustStoreKeyStore = KeyStore.getInstance(trustStoreType); - - try (InputStream in = getFileInputStream(trustStorePath)) { - if (trustStorePathPassword == null) { - trustStorePathPassword = ""; - } - trustStoreKeyStore.load(in, trustStorePathPassword.toCharArray()); - } - } - - trustManagerFactory.init(trustStoreKeyStore); - - TrustManager[] tm = trustManagerFactory.getTrustManagers(); - - SecureRandom random = new SecureRandom(); - - context.init(km, tm, random); - - SSLServerSocketFactory sf = context.getServerSocketFactory(); - - try (ServerSocket socket = (sslEnabled ? sf.createServerSocket(portNum) : new ServerSocket(portNum))) { - if (sslEnabled) { - SSLServerSocket secureSocket = (SSLServerSocket) socket; - String[] protocols = secureSocket.getEnabledProtocols(); - Set allowedProtocols = new HashSet<>(); - for (String ep : protocols) { - if (enabledProtocolsList.contains(ep.toUpperCase())) { - LOG.info("Enabling Protocol: [{}]", ep); - allowedProtocols.add(ep); - } else { - LOG.info("Disabling Protocol: [{}]", ep); - } - } - - if (!allowedProtocols.isEmpty()) { - secureSocket.setEnabledProtocols(allowedProtocols.toArray(new String[0])); - } - String[] enabledCipherSuites = secureSocket.getEnabledCipherSuites(); - Set allowedCipherSuites = new HashSet<>(); - for (String enabledCipherSuite : enabledCipherSuites) { - if (enabledCipherSuiteList.contains(enabledCipherSuite)) { - LOG.debug("Enabling CipherSuite : [{}]", enabledCipherSuite); - allowedCipherSuites.add(enabledCipherSuite); - } else { - LOG.debug("Disabling CipherSuite : [{}]", enabledCipherSuite); - } - } - if (requireClientAuth) { - if (trustStoreKeyStore == null) { - throw new RuntimeException("[" + UNIXAUTH_REQUIRE_CLIENT_AUTH_PARAM + "] is enabled but [" + SSL_TRUSTSTORE_PATH_PARAM + "] is not configured - a truststore is required to validate client certificates."); - } - LOG.info("Enabling TLS client certificate authentication for UnixAuth listener."); - secureSocket.setNeedClientAuth(true); - } - if (!allowedCipherSuites.isEmpty()) { - secureSocket.setEnabledCipherSuites(allowedCipherSuites.toArray(new String[0])); - } - } - - Socket client; - - try { - while ((client = socket.accept()) != null) { - try { - client.setSoTimeout(socketTimeoutMs); - } catch (java.net.SocketException se) { - LOG.warn("Failed to set socket timeout for connection from [{}]: {}", client.getInetAddress(), se.getMessage()); - } - Thread clientValidatorThread = new Thread(new PasswordValidator(client, loginAttemptTracker)); - clientValidatorThread.start(); - } - } catch (IOException e) { - socket.close(); - throw (e); - } - } - } - - private void startUnixUserGroupSyncProcess() { - // Start user group synchronization service - LOG.info("Start : startUnixUserGroupSyncProcess "); - UserGroupSync syncProc = new UserGroupSync(); - Thread newSyncProcThread = new Thread(syncProc); - newSyncProcThread.setName("UnixUserSyncThread"); - - /* If this thread is set as daemon, then the entire process will terminate if enableUnixAuth is false - Therefore this is marked as non-daemon thread. Don't change the following line - */ - newSyncProcThread.setDaemon(false); - newSyncProcThread.start(); - LOG.info("UnixUserSyncThread started"); - LOG.info("creating UserSyncMetricsProducer thread with default metrics location : {}", System.getProperty("logdir")); - - //Start the user sync metrics - boolean isUserSyncMetricsEnabled = UserGroupSyncConfig.getInstance().isUserSyncMetricsEnabled(); - if (isUserSyncMetricsEnabled) { - UserSyncMetricsProducer userSyncMetricsProducer = new UserSyncMetricsProducer(); - Thread userSyncMetricsProducerThread = new Thread(userSyncMetricsProducer); - userSyncMetricsProducerThread.setName("UserSyncMetricsProducerThread"); - userSyncMetricsProducerThread.setDaemon(false); - userSyncMetricsProducerThread.start(); - LOG.info("UserSyncMetricsProducer started"); - } else { - LOG.info(" Ranger userSync metrics is not enabled"); - } - } - - //TODO: add more validation code - private void init() { - Properties prop = new Properties(); - - for (String fn : UGSYNC_CONFIG_XML_FILES) { - XMLUtils.loadConfig(fn, prop); - } - - String credStoreFileName = prop.getProperty(CREDSTORE_FILENAME_PARAM); - keyStorePath = prop.getProperty(SSL_KEYSTORE_PATH_PARAM); - trustStorePath = prop.getProperty(SSL_TRUSTSTORE_PATH_PARAM); - keyStoreType = prop.getProperty(SSL_KEYSTORE_FILE_TYPE_PARAM, KeyStore.getDefaultType()); - trustStoreType = prop.getProperty(SSL_TRUSTSTORE_FILE_TYPE_PARAM, KeyStore.getDefaultType()); - - if (credStoreFileName == null) { - throw new RuntimeException("Credential file is not defined. param = [" + CREDSTORE_FILENAME_PARAM + "]"); - } - - File credFile = new File(credStoreFileName); - - if (!credFile.exists()) { - throw new RuntimeException("Credential file [" + credStoreFileName + "]: does not exists."); - } - - if (!credFile.canRead()) { - throw new RuntimeException("Credential file [" + credStoreFileName + "]: can not be read."); - } - - if ("bcfks".equalsIgnoreCase(keyStoreType)) { - String crendentialProviderPrefixBcfks = "bcfks://file"; - credStoreFileName = crendentialProviderPrefixBcfks + credStoreFileName; - } - - keyStorePathPassword = CredentialReader.getDecryptedString(credStoreFileName, SSL_KEYSTORE_PATH_PASSWORD_ALIAS, keyStoreType); - trustStorePathPassword = CredentialReader.getDecryptedString(credStoreFileName, SSL_TRUSTSTORE_PATH_PASSWORD_ALIAS, trustStoreType); - portNum = Integer.parseInt(prop.getProperty(REMOTE_LOGIN_AUTH_SERVICE_PORT_PARAM)); - String validatorProg = prop.getProperty(CRED_VALIDATOR_PROG); - - if (validatorProg != null) { - PasswordValidator.setValidatorProgram(validatorProg); - } - - String adminUsers = prop.getProperty(ADMIN_USER_LIST_PARAM); - - if (adminUsers != null && !adminUsers.trim().isEmpty()) { - for (String u : adminUsers.split(",")) { - LOG.info("Adding Admin User: {}", u.trim()); - adminUserList.add(u.trim()); - } - PasswordValidator.setAdminUserList(adminUserList); - } - - adminRoleNames = prop.getProperty(ADMIN_ROLE_LIST_PARAM); - - if (adminRoleNames != null) { - LOG.info("Adding Admin Group: {}", adminRoleNames); - PasswordValidator.setAdminRoleNames(adminRoleNames); - } - - String sslEnabledProp = prop.getProperty(SSL_ENABLED_PARAM); - - sslEnabled = (sslEnabledProp != null && (sslEnabledProp.equalsIgnoreCase("true"))); - String defaultEnabledProtocols = "TLSv1.2"; - String enabledProtocols = prop.getProperty("ranger.usersync.https.ssl.enabled.protocols", defaultEnabledProtocols); - String enabledCipherSuites = prop.getProperty("ranger.usersync.https.ssl.enabled.cipher.suites", ""); - enabledProtocolsList = new ArrayList<>(Arrays.asList(enabledProtocols.toUpperCase().trim().split("\\s*,\\s*"))); - enabledCipherSuiteList = new ArrayList<>(Arrays.asList(enabledCipherSuites.toUpperCase().trim().split("\\s*,\\s*"))); - int maxFailedAttempts = Integer.parseInt(prop.getProperty(UNIXAUTH_MAX_FAILED_ATTEMPTS_PARAM, "5")); - long attemptWindowMs = Long.parseLong(prop.getProperty(UNIXAUTH_ATTEMPT_WINDOW_MS_PARAM, "60000")); - long lockoutDurationMs = Long.parseLong(prop.getProperty(UNIXAUTH_LOCKOUT_DURATION_MS_PARAM, "30000")); - socketTimeoutMs = Integer.parseInt(prop.getProperty(UNIXAUTH_SOCKET_TIMEOUT_MS_PARAM, "10000")); - - boolean accountFanoutEnabled = Boolean.parseBoolean(prop.getProperty(UNIXAUTH_ACCOUNT_FANOUT_ENABLED_PARAM, "true")); - int accountDistinctIpThreshold = Integer.parseInt(prop.getProperty(UNIXAUTH_ACCOUNT_DISTINCT_IP_THRESHOLD_PARAM, "3")); - long accountWindowMs = Long.parseLong(prop.getProperty(UNIXAUTH_ACCOUNT_WINDOW_MS_PARAM, "60000")); - long accountBaseDelayMs = Long.parseLong(prop.getProperty(UNIXAUTH_ACCOUNT_BASE_DELAY_MS_PARAM, "200")); - long accountMaxDelayMs = Long.parseLong(prop.getProperty(UNIXAUTH_ACCOUNT_MAX_DELAY_MS_PARAM, "2000")); - - loginAttemptTracker = new LoginAttemptTracker(maxFailedAttempts, attemptWindowMs, lockoutDurationMs, accountFanoutEnabled, - accountDistinctIpThreshold, accountWindowMs, accountBaseDelayMs, accountMaxDelayMs); - - String requireClientAuthProp = prop.getProperty(UNIXAUTH_REQUIRE_CLIENT_AUTH_PARAM, "false"); - requireClientAuth = requireClientAuthProp.trim().equalsIgnoreCase("true"); - } - - private InputStream getFileInputStream(String path) throws FileNotFoundException { - InputStream ret; - - File f = new File(path); - - if (f.exists()) { - ret = new FileInputStream(f); - } else { - ret = getClass().getResourceAsStream(path); - if (ret == null) { - ret = getClass().getResourceAsStream(File.separator + path); - } - } - - return ret; - } -} diff --git a/unixauthservice/src/main/resources/logback.xml b/unixauthservice/src/main/resources/logback.xml deleted file mode 100644 index e96421b9007..00000000000 --- a/unixauthservice/src/main/resources/logback.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - ${logdir}/usersync-${hostname}-${user}.log - - %d{dd MMM yyyy HH:mm:ss} %5p %c{1} [%t] - %m%n - - - ${logdir}/usersync-${hostname}-${user}.log.%d{'.'yyyy-MM-dd} - 15 - true - - - - - - diff --git a/unixauthservice/src/test/java/org/apache/ranger/authentication/LoginAttemptTrackerTest.java b/unixauthservice/src/test/java/org/apache/ranger/authentication/LoginAttemptTrackerTest.java deleted file mode 100644 index 7671d0168a4..00000000000 --- a/unixauthservice/src/test/java/org/apache/ranger/authentication/LoginAttemptTrackerTest.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * 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.ranger.authentication; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class LoginAttemptTrackerTest { - @Test - public void allowsAttemptsBelowThreshold() { - LoginAttemptTracker tracker = new LoginAttemptTracker(5, 60_000, 30_000); - String ip = "10.0.0.1"; - for (int i = 0; i < 4; i++) { - assertFalse(tracker.isBlocked(ip), "should not be blocked before threshold"); - tracker.recordFailure(ip); - } - assertFalse(tracker.isBlocked(ip), "still not blocked at 4 failures with threshold 5"); - } - - @Test - public void locksOutAfterThresholdReached() { - LoginAttemptTracker tracker = new LoginAttemptTracker(5, 60_000, 30_000); - String ip = "10.0.0.2"; - for (int i = 0; i < 5; i++) { - tracker.recordFailure(ip); - } - assertTrue(tracker.isBlocked(ip), "should be locked out at threshold"); - } - - @Test - public void successClearsFailureHistory() { - LoginAttemptTracker tracker = new LoginAttemptTracker(5, 60_000, 30_000); - String ip = "10.0.0.3"; - for (int i = 0; i < 4; i++) { - tracker.recordFailure(ip); - } - tracker.recordSuccess(ip); - assertFalse(tracker.isBlocked(ip)); - // failure count should have reset - one more failure should not lock out - tracker.recordFailure(ip); - assertFalse(tracker.isBlocked(ip)); - } - - @Test - public void windowExpiryResetsFailureCount() throws InterruptedException { - // very short window so the test runs fast - LoginAttemptTracker tracker = new LoginAttemptTracker(3, 50, 30_000); - String ip = "10.0.0.4"; - tracker.recordFailure(ip); - tracker.recordFailure(ip); - assertFalse(tracker.isBlocked(ip)); - Thread.sleep(100); // let the window expire - tracker.recordFailure(ip); // should start a fresh window, count = 1 - assertFalse(tracker.isBlocked(ip), "window should have reset the failure count"); - } - - @Test - public void reLocksImmediatelyIfRetriedRightAfterLockoutExpires() throws InterruptedException { - // short lockout, long window - so lockout expires but window doesn't - LoginAttemptTracker tracker = new LoginAttemptTracker(3, 60_000, 50); - String ip = "10.0.0.5"; - tracker.recordFailure(ip); - tracker.recordFailure(ip); - tracker.recordFailure(ip); - assertTrue(tracker.isBlocked(ip)); - Thread.sleep(100); // let the lockout expire, window is still active - assertFalse(tracker.isBlocked(ip), "lockout should have expired"); - tracker.recordFailure(ip); // one more failure within the same window - assertTrue(tracker.isBlocked(ip), "should re-lock immediately since window's failure count carried over"); - } - - @Test - public void independentIpsTrackedSeparately() { - LoginAttemptTracker tracker = new LoginAttemptTracker(3, 60_000, 30_000); - for (int i = 0; i < 3; i++) { - tracker.recordFailure("10.0.0.6"); - } - assertTrue(tracker.isBlocked("10.0.0.6")); - assertFalse(tracker.isBlocked("10.0.0.7"), "a different source IP must not be affected"); - } - - // threshold=3, base delay=200ms, max delay=2000ms, account window=60s - private static LoginAttemptTracker newFanoutTracker() { - return new LoginAttemptTracker(100, 60_000, 30_000, true, 3, 60_000, 200, 2000); - } - - @Test - public void fanout_legitimateSingleIpNeverDelayed() { - LoginAttemptTracker tracker = newFanoutTracker(); - // simulate a real admin mistyping their password 5 times from ONE ip - for (int i = 0; i < 5; i++) { - tracker.recordFailure("10.0.0.1", "alice"); - } - assertEquals(0, tracker.getAccountDelayMs("alice"), "single-IP retries must never trigger an account delay"); - } - - @Test - public void fanout_distinctIpsAcrossThresholdTriggerDelay() { - LoginAttemptTracker tracker = newFanoutTracker(); - tracker.recordFailure("10.0.0.1", "bob"); - tracker.recordFailure("10.0.0.2", "bob"); - assertEquals(0, tracker.getAccountDelayMs("bob"), "2 distinct IPs, threshold 3 - no delay yet"); - tracker.recordFailure("10.0.0.3", "bob"); - assertEquals(0, tracker.getAccountDelayMs("bob"), "3 distinct IPs, at threshold - still no delay"); - tracker.recordFailure("10.0.0.4", "bob"); - assertTrue(tracker.getAccountDelayMs("bob") > 0, "4th distinct IP crosses the threshold - delay should apply"); - } - - @Test - public void fanout_delayScalesLinearlyBeforeCap() { - LoginAttemptTracker tracker = newFanoutTracker(); - String account = "carol"; - for (int i = 0; i < 4; i++) { - tracker.recordFailure("10.0.1." + i, account); // 4 distinct -> 1 over threshold - } - assertEquals(200, tracker.getAccountDelayMs(account), "4 distinct IPs -> exactly 1x base delay"); - tracker.recordFailure("10.0.1.4", account); // 5 distinct -> 2 over threshold - assertEquals(400, tracker.getAccountDelayMs(account), "5 distinct IPs -> exactly 2x base delay"); - tracker.recordFailure("10.0.1.5", account); // 6 distinct -> 3 over threshold - assertEquals(600, tracker.getAccountDelayMs(account), "6 distinct IPs -> exactly 3x base delay"); - tracker.recordFailure("10.0.1.6", account); // 7 distinct -> 4 over threshold - assertEquals(800, tracker.getAccountDelayMs(account), "7 distinct IPs -> exactly 4x base delay"); - } - - @Test - public void fanout_delayCapsInsteadOfGrowingUnbounded() { - LoginAttemptTracker tracker = newFanoutTracker(); - String account = "carol_capped"; - for (int i = 0; i < 30; i++) { - tracker.recordFailure("10.0.1." + i, account); // well past the threshold - } - assertEquals(2000, tracker.getAccountDelayMs(account), "delay must cap at accountMaxDelayMs regardless of distinct-IP count"); - } - - @Test - public void fanout_successClearsAccountStateImmediately() { - LoginAttemptTracker tracker = newFanoutTracker(); - String account = "dave"; - for (int i = 0; i < 5; i++) { - tracker.recordFailure("10.0.2." + i, account); - } - assertTrue(tracker.getAccountDelayMs(account) > 0, "delay should be present before success"); - tracker.recordSuccess("10.0.2.99", account); - assertEquals(0, tracker.getAccountDelayMs(account), "delay must clear immediately after a successful login"); - } - - @Test - public void fanout_windowExpiryResetsAccountState() throws InterruptedException { - // short account window so the test runs fast - LoginAttemptTracker tracker = new LoginAttemptTracker(100, 60_000, 30_000, true, 2, 80, 200, 2000); - String account = "erin"; - tracker.recordFailure("10.0.3.1", account); - tracker.recordFailure("10.0.3.2", account); - tracker.recordFailure("10.0.3.3", account); // 3 distinct, over threshold of 2 -> delay - assertTrue(tracker.getAccountDelayMs(account) > 0, "delay should be present within the window"); - Thread.sleep(150); // let the account window expire - assertEquals(0, tracker.getAccountDelayMs(account), "delay must clear once the window has expired, even without a success"); - } - - @Test - public void fanout_disabledMeansAlwaysZero() { - // fan-out disabled entirely - LoginAttemptTracker tracker = new LoginAttemptTracker(100, 60_000, 30_000, false, 1, 60_000, 200, 2000); - String account = "frank"; - for (int i = 0; i < 10; i++) { - tracker.recordFailure("10.0.4." + i, account); - } - assertEquals(0, tracker.getAccountDelayMs(account), "fan-out disabled must mean the delay is always 0, regardless of distinct IPs"); - } - - @Test - public void fanout_ipLockoutStillIndependentAndUnaffected() { - LoginAttemptTracker tracker = new LoginAttemptTracker(3, 60_000, 30_000, true, 2, 60_000, 200, 2000); - for (int i = 0; i < 3; i++) { - tracker.recordFailure("10.0.5.1", "grace"); // same IP, 3 failures -> IP lockout still fires - } - assertTrue(tracker.isBlocked("10.0.5.1"), "existing per-IP hard lockout must still work unchanged alongside account fan-out tracking"); - } - - @Test - public void fanout_sameIpRetriedManyTimesCountsOnce() { - LoginAttemptTracker tracker = newFanoutTracker(); - String account = "henry"; - for (int i = 0; i < 20; i++) { - tracker.recordFailure("10.0.6.1", account); // SAME ip, 20 times - } - assertEquals(0, tracker.getAccountDelayMs(account), "20 retries from one IP must never cross the distinct-IP threshold"); - } - - @Test - public void fanout_independentAccountsDoNotCrossContaminate() { - LoginAttemptTracker tracker = newFanoutTracker(); - for (int i = 0; i < 10; i++) { - tracker.recordFailure("10.0.8." + i, "jack"); // fan-out attack on "jack" - } - assertTrue(tracker.getAccountDelayMs("jack") > 0, "the attacked account should be delayed"); - assertEquals(0, tracker.getAccountDelayMs("kate"), "an unrelated account must be completely unaffected"); - } - - @Test - public void fanout_nullOrEmptyAccountIsSafeNoOp() { - LoginAttemptTracker tracker = newFanoutTracker(); - // simulates PasswordValidator's request==null / unparsable-request path - for (int i = 0; i < 10; i++) { - tracker.recordFailure("10.0.9." + i); // 1-arg overload, no account at all - } - assertEquals(0, tracker.getAccountDelayMs(null), "no-account failures must never populate any account delay"); - assertEquals(0, tracker.getAccountDelayMs(""), "empty-string account must also always be a no-op"); - tracker.recordFailure("10.0.9.1", ""); // explicit empty string account - tracker.recordFailure("10.0.9.1", null); // explicit null account - assertEquals(0, tracker.getAccountDelayMs(""), "explicit empty/null account args must never throw and never trigger a delay"); - } - - @Test - public void fanout_concurrentFailuresFromManyIpsAreThreadSafe() throws InterruptedException { - // Mirrors PasswordValidator's real threading model: one thread per - // connection, all potentially hitting the same account concurrently. - LoginAttemptTracker tracker = newFanoutTracker(); - String account = "concurrent_victim"; - int threadCount = 50; - Thread[] threads = new Thread[threadCount]; - for (int i = 0; i < threadCount; i++) { - final String ip = "10.1.0." + i; // 50 distinct IPs - threads[i] = new Thread(() -> tracker.recordFailure(ip, account)); - } - for (Thread t : threads) { - t.start(); - } - for (Thread t : threads) { - t.join(5000); - } - // 50 distinct IPs, threshold 3 -> 47 over threshold -> would be 47*200=9400ms - // uncapped, so this must land exactly at the configured cap. - assertEquals(2000, tracker.getAccountDelayMs(account), "50 concurrent distinct-IP failures must land exactly at the cap, with no race corruption"); - // an unrelated account, hit concurrently at the same time, must still - // read back as unaffected - proves no cross-account leakage under - // concurrent access either. - assertEquals(0, tracker.getAccountDelayMs("bystander"), "an untouched account must remain unaffected during concurrent activity"); - } -} diff --git a/unixauthservice/src/test/java/org/apache/ranger/authentication/TestPasswordValidator.java b/unixauthservice/src/test/java/org/apache/ranger/authentication/TestPasswordValidator.java deleted file mode 100644 index 2e48dd958e4..00000000000 --- a/unixauthservice/src/test/java/org/apache/ranger/authentication/TestPasswordValidator.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * 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.ranger.authentication; - -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.MockedStatic; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @generated by Cursor - * @description : Unit Test cases for PasswordValidator - */ - -@ExtendWith(MockitoExtension.class) -@TestMethodOrder(MethodOrderer.MethodName.class) -public class TestPasswordValidator { - private static LoginAttemptTracker permissiveTracker() { - return new LoginAttemptTracker(1000, 60_000, 30_000); - } - - @Test - public void test00_staticGettersAndSetters() { - PasswordValidator.setValidatorProgram("/tmp/prog"); - PasswordValidator.setAdminUserList(Collections.singletonList("root")); - PasswordValidator.setAdminRoleNames("ROLE_SUPER"); - - assertEquals("/tmp/prog", PasswordValidator.getValidatorProgram()); - assertEquals(Collections.singletonList("root"), PasswordValidator.getAdminUserList()); - assertEquals("ROLE_SUPER", PasswordValidator.getAdminRoleNames()); - } - - @Test - public void test01_nullProgramWritesFailedAndClosesSocket() throws Exception { - PasswordValidator.setValidatorProgram(null); - PasswordValidator.setAdminUserList(null); - PasswordValidator.setAdminRoleNames(null); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Socket socket = buildSocket("LOGIN: alice secret", out); - - new PasswordValidator(socket, permissiveTracker()).run(); - - String response = out.toString(StandardCharsets.UTF_8.name()).trim(); - assertEquals("FAILED: Authentication failed.", response); - verify(socket, times(1)).close(); - } - - @Test - public void test02_okResponseAppendsAdminRoleAndDestroysProcess() throws Exception { - PasswordValidator.setValidatorProgram("/bin/validator"); - PasswordValidator.setAdminUserList(Arrays.asList("alice", "bob")); - PasswordValidator.setAdminRoleNames("ROLE_ADMIN"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Socket socket = buildSocket("LOGIN: alice secret", out); - - Process process = mock(Process.class); - ByteArrayInputStream processStdout = new ByteArrayInputStream("OK".getBytes(StandardCharsets.UTF_8)); - when(process.getInputStream()).thenReturn(processStdout); - when(process.getOutputStream()).thenReturn(new ByteArrayOutputStream()); - - try (MockedStatic runtimeStatic = mockStatic(Runtime.class)) { - Runtime rt = mock(Runtime.class); - runtimeStatic.when(Runtime::getRuntime).thenReturn(rt); - when(rt.exec("/bin/validator")).thenReturn(process); - - new PasswordValidator(socket, permissiveTracker()).run(); - } - - String response = out.toString(StandardCharsets.UTF_8).trim(); - assertEquals("OK ROLE_ADMIN", response); - verify(process, times(1)).destroy(); - verify(socket, times(1)).close(); - } - - @Test - public void test03_okResponseWithoutAdminDoesNotAppendRole() throws Exception { - PasswordValidator.setValidatorProgram("/bin/validator"); - PasswordValidator.setAdminUserList(Collections.singletonList("bob")); - PasswordValidator.setAdminRoleNames("ROLE_ADMIN"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Socket socket = buildSocket("LOGIN: alice secret", out); - - Process process = mock(Process.class); - ByteArrayInputStream processStdout = new ByteArrayInputStream("OK".getBytes(StandardCharsets.UTF_8)); - when(process.getInputStream()).thenReturn(processStdout); - when(process.getOutputStream()).thenReturn(new ByteArrayOutputStream()); - - try (MockedStatic runtimeStatic = mockStatic(Runtime.class)) { - Runtime rt = mock(Runtime.class); - runtimeStatic.when(Runtime::getRuntime).thenReturn(rt); - when(rt.exec("/bin/validator")).thenReturn(process); - - new PasswordValidator(socket, permissiveTracker()).run(); - } - - String response = out.toString(StandardCharsets.UTF_8.name()).trim(); - assertEquals("OK", response); - verify(process, times(1)).destroy(); - verify(socket, times(1)).close(); - } - - @Test - public void test04_execThrowsWritesFailed() throws Exception { - PasswordValidator.setValidatorProgram("/bin/validator"); - PasswordValidator.setAdminUserList(Collections.singletonList("alice")); - PasswordValidator.setAdminRoleNames("ROLE_ADMIN"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Socket socket = buildSocket("LOGIN: alice secret", out); - - try (MockedStatic runtimeStatic = mockStatic(Runtime.class)) { - Runtime rt = mock(Runtime.class); - runtimeStatic.when(Runtime::getRuntime).thenReturn(rt); - when(rt.exec("/bin/validator")).thenThrow(new IOException("boom")); - - new PasswordValidator(socket, permissiveTracker()).run(); - } - - String response = out.toString(StandardCharsets.UTF_8).trim(); - assertEquals("FAILED: Authentication failed.", response); - verify(socket, times(1)).close(); - } - - @Test - public void test05_closeSocketIOExceptionHandled() throws Exception { - PasswordValidator.setValidatorProgram(null); - PasswordValidator.setAdminUserList(null); - PasswordValidator.setAdminRoleNames(null); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Socket socket = buildSocket("LOGIN: alice secret", out); - doThrow(new IOException("close failure")).when(socket).close(); - - new PasswordValidator(socket, permissiveTracker()).run(); - - // No exception should be thrown even if close() fails; nothing to assert besides method completed - verify(socket, times(1)).close(); - } - - @Test - public void test06_blockedIpSkipsValidatorAndReturnsLockedOutResponse() throws Exception { - PasswordValidator.setValidatorProgram("/bin/validator"); - PasswordValidator.setAdminUserList(null); - PasswordValidator.setAdminRoleNames(null); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Socket socket = buildSocket("LOGIN: alice secret", out); - LoginAttemptTracker tracker = mock(LoginAttemptTracker.class); - when(tracker.isBlocked(anyString())).thenReturn(true); - new PasswordValidator(socket, tracker).run(); - String response = out.toString(StandardCharsets.UTF_8).trim(); - assertEquals("FAILED: Too many attempts. Try again later.", response); - verify(socket, times(1)).close(); - } - - @Test - public void test07_failedValidatorResponseRecordsFailureOnTracker() throws Exception { - PasswordValidator.setValidatorProgram("/bin/validator"); - PasswordValidator.setAdminUserList(null); - PasswordValidator.setAdminRoleNames(null); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Socket socket = buildSocket("LOGIN: alice secret", out); - - Process process = mock(Process.class); - ByteArrayInputStream processStdout = new ByteArrayInputStream("FAILED: Password did not match.".getBytes(StandardCharsets.UTF_8)); - when(process.getInputStream()).thenReturn(processStdout); - when(process.getOutputStream()).thenReturn(new ByteArrayOutputStream()); - - LoginAttemptTracker tracker = mock(LoginAttemptTracker.class); - when(tracker.isBlocked(anyString())).thenReturn(false); - - try (MockedStatic runtimeStatic = mockStatic(Runtime.class)) { - Runtime rt = mock(Runtime.class); - runtimeStatic.when(Runtime::getRuntime).thenReturn(rt); - when(rt.exec("/bin/validator")).thenReturn(process); - - new PasswordValidator(socket, tracker).run(); - } - - String response = out.toString(StandardCharsets.UTF_8).trim(); - assertEquals("FAILED: Authentication failed.", response); - verify(tracker, times(1)).recordFailure(anyString(), anyString()); - verify(tracker, never()).recordSuccess(anyString(), anyString()); - } - - @Test - public void test08_okValidatorResponseRecordsSuccessOnTracker() throws Exception { - PasswordValidator.setValidatorProgram("/bin/validator"); - PasswordValidator.setAdminUserList(null); - PasswordValidator.setAdminRoleNames(null); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Socket socket = buildSocket("LOGIN: alice secret", out); - - Process process = mock(Process.class); - ByteArrayInputStream processStdout = new ByteArrayInputStream("OK".getBytes(StandardCharsets.UTF_8)); - when(process.getInputStream()).thenReturn(processStdout); - when(process.getOutputStream()).thenReturn(new ByteArrayOutputStream()); - - LoginAttemptTracker tracker = mock(LoginAttemptTracker.class); - when(tracker.isBlocked(anyString())).thenReturn(false); - - try (MockedStatic runtimeStatic = mockStatic(Runtime.class)) { - Runtime rt = mock(Runtime.class); - runtimeStatic.when(Runtime::getRuntime).thenReturn(rt); - when(rt.exec("/bin/validator")).thenReturn(process); - - new PasswordValidator(socket, tracker).run(); - } - - String response = out.toString(StandardCharsets.UTF_8).trim(); - assertEquals("OK", response); - verify(tracker, times(1)).recordSuccess(anyString(), anyString()); - verify(tracker, never()).recordFailure(anyString(), anyString()); - } - - private Socket buildSocket(String requestLine, ByteArrayOutputStream responseOut) throws IOException { - Socket socket = mock(Socket.class); - InputStream in = new ByteArrayInputStream((requestLine + "\n").getBytes(StandardCharsets.UTF_8)); - OutputStream out = responseOut; - when(socket.getInputStream()).thenReturn(in); - when(socket.getOutputStream()).thenReturn(out); - return socket; - } -} diff --git a/unixauthservice/src/test/java/org/apache/ranger/authentication/TestUnixAuthenticationService.java b/unixauthservice/src/test/java/org/apache/ranger/authentication/TestUnixAuthenticationService.java deleted file mode 100644 index f582e698e83..00000000000 --- a/unixauthservice/src/test/java/org/apache/ranger/authentication/TestUnixAuthenticationService.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * 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.ranger.authentication; - -import org.apache.hadoop.conf.Configuration; -import org.apache.ranger.credentialapi.CredentialReader; -import org.apache.ranger.plugin.util.XMLUtils; -import org.apache.ranger.unixusersync.config.UserGroupSyncConfig; -import org.apache.ranger.unixusersync.ha.UserSyncHAInitializerImpl; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.MockedConstruction; -import org.mockito.MockedStatic; -import org.mockito.junit.jupiter.MockitoExtension; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLServerSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.Socket; -import java.security.KeyStore; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockConstruction; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @generated by Cursor - * @description : Unit Test cases for UnixAuthenticationService - */ - -@ExtendWith(MockitoExtension.class) -@TestMethodOrder(MethodOrderer.MethodName.class) -public class TestUnixAuthenticationService { - @Test - public void test01_initThrowsWhenCredFileMissing() throws Exception { - UnixAuthenticationService svc = new UnixAuthenticationService(); - - try (MockedStatic xmlMock = mockStatic(XMLUtils.class)) { - xmlMock.when(() -> XMLUtils.loadConfig(any(String.class), any(Properties.class))).thenAnswer(inv -> null); - - try (MockedStatic credMock = mockStatic(CredentialReader.class)) { - // Do not need to reach CredentialReader because missing file check will throw first - Properties props = new Properties(); - props.setProperty("ranger.usersync.credstore.filename", "/no/such/file.jceks"); - props.setProperty("ranger.usersync.port", "6168"); - - // Inject properties via XMLUtils sequence - xmlMock.when(() -> XMLUtils.loadConfig(eq("ranger-ugsync-default.xml"), any(Properties.class))) - .thenAnswer(inv -> { - ((Properties) inv.getArgument(1)).putAll(props); - return null; - }); - xmlMock.when(() -> XMLUtils.loadConfig(eq("ranger-ugsync-site.xml"), any(Properties.class))) - .thenAnswer(inv -> null); - - InvocationTargetException ex = assertThrows(InvocationTargetException.class, () -> invokeInit(svc)); - assertTrue(ex.getCause() instanceof RuntimeException); - } - } - } - - @Test - public void test02_getFileInputStreamFallsBackToResourcePath() throws Exception { - UnixAuthenticationService svc = new UnixAuthenticationService(); - Method m = UnixAuthenticationService.class.getDeclaredMethod("getFileInputStream", String.class); - m.setAccessible(true); - - // Point to a file guaranteed to not exist; should fallback to resource stream, which will be null - InputStream is = (InputStream) m.invoke(svc, "/this/does/not/exist.txt"); - // Since no resource with that name, it can still be null; verify method returns (null allowed) - // Next, try with a real file via temp file which should be returned - File tmp = File.createTempFile("ranger-test", ".txt"); - try { - InputStream fis = (InputStream) m.invoke(svc, tmp.getAbsolutePath()); - assertNotNull(fis); - fis.close(); - } finally { - //noinspection ResultOfMethodCallIgnored - tmp.delete(); - } - } - - @Test - public void test03_runInterruptedStopsHA() throws Exception { - UnixAuthenticationService svc = new UnixAuthenticationService(); - UserSyncHAInitializerImpl ha = mock(UserSyncHAInitializerImpl.class); - setField(svc, "userSyncHAInitializerImpl", ha); - - try (MockedConstruction threadConstruction = mockConstruction(Thread.class, (mock, ctx) -> { - doNothing().when(mock).setName(any()); - doNothing().when(mock).setDaemon(false); - doNothing().when(mock).start(); - })) { - // Interrupt current thread so Thread.sleep throws immediately - Thread.currentThread().interrupt(); - // Call run; should catch InterruptedException and then stop HA in finally - svc.run(); - // Clear interrupted status for subsequent tests - Thread.interrupted(); - } - } - - @Test - public void test04_startServiceSslAndAcceptIOExceptionPath() throws Throwable { - UnixAuthenticationService svc = new UnixAuthenticationService(); - - // Reflectively set fields needed by startService - setField(svc, "sslEnabled", true); - setField(svc, "portNum", 0); - setField(svc, "enabledProtocolsList", Collections.singletonList("TLSV1.2")); - setField(svc, "enabledCipherSuiteList", new ArrayList<>()); - setField(svc, "keyStorePath", ""); - setField(svc, "trustStorePath", ""); - setField(svc, "keyStoreType", KeyStore.getDefaultType()); - setField(svc, "trustStoreType", KeyStore.getDefaultType()); - - try (MockedStatic sslStatic = mockStatic(SSLContext.class)) { - SSLContext sslContext = mock(SSLContext.class); - SSLServerSocketFactory sf = mock(SSLServerSocketFactory.class); - SSLServerSocket secureSocket = mock(SSLServerSocket.class); - - when(SSLContext.getInstance("TLS")).thenReturn(sslContext); - when(sslContext.getServerSocketFactory()).thenReturn(sf); - when(sf.createServerSocket(anyInt())).thenReturn(secureSocket); - when(secureSocket.getEnabledProtocols()).thenReturn(new String[] {"TLSv1.2"}); - when(secureSocket.getEnabledCipherSuites()).thenReturn(new String[] {}); - when(secureSocket.accept()).thenThrow(new IOException("accept fail")); - - assertThrows(IOException.class, svc::startService); - } - } - - @Test - public void test05_initSuccessPathWithBcfksAndAdminLists() throws Exception { - UnixAuthenticationService svc = new UnixAuthenticationService(); - - Properties props = new Properties(); - File credFile = File.createTempFile("ranger-cred", ".bcfks"); - try { - props.setProperty("ranger.usersync.credstore.filename", credFile.getAbsolutePath()); - props.setProperty("ranger.usersync.keystore.file", ""); - props.setProperty("ranger.usersync.truststore.file", ""); - props.setProperty("ranger.keystore.file.type", "bcfks"); - props.setProperty("ranger.truststore.file.type", "bcfks"); - props.setProperty("ranger.usersync.port", "6168"); - props.setProperty("ranger.usersync.passwordvalidator.path", "/bin/validator"); - props.setProperty("admin.users", "alice,bob"); - props.setProperty("admin.roleNames", "ROLE_ADMIN,ROLE_AUDITOR"); - props.setProperty("ranger.usersync.ssl", "true"); - props.setProperty("ranger.usersync.https.ssl.enabled.protocols", "TLSv1.2"); - props.setProperty("ranger.usersync.https.ssl.enabled.cipher.suites", ""); - - try (MockedStatic xmlMock = mockStatic(XMLUtils.class); - MockedStatic credMock = mockStatic(CredentialReader.class)) { - xmlMock.when(() -> XMLUtils.loadConfig(any(String.class), any(Properties.class))).thenAnswer(inv -> { - ((Properties) inv.getArgument(1)).putAll(props); - return null; - }); - credMock.when(() -> CredentialReader.getDecryptedString(any(), any(), any())).thenReturn("pass"); - - // invoke private init - invokeInit(svc); - } - } finally { - //noinspection ResultOfMethodCallIgnored - credFile.delete(); - } - } - - @Test - public void test06_startServiceWithKeyAndTrustStores() throws Throwable { - UnixAuthenticationService svc = new UnixAuthenticationService(); - - setField(svc, "sslEnabled", true); - setField(svc, "portNum", 0); - setField(svc, "enabledProtocolsList", Collections.singletonList("TLSV1.2")); - setField(svc, "enabledCipherSuiteList", new ArrayList<>()); - setField(svc, "keyStorePath", "/tmp/keystore.jks"); - setField(svc, "trustStorePath", "/tmp/truststore.jks"); - setField(svc, "keyStoreType", KeyStore.getDefaultType()); - setField(svc, "trustStoreType", KeyStore.getDefaultType()); - // leave passwords as null to exercise empty defaulting branch - - try (MockedStatic kmfStatic = mockStatic(KeyManagerFactory.class); - MockedStatic tmfStatic = mockStatic(TrustManagerFactory.class); - MockedStatic sslStatic = mockStatic(SSLContext.class)) { - KeyManagerFactory kmf = mock(KeyManagerFactory.class); - kmfStatic.when(() -> KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())).thenReturn(kmf); - when(kmf.getKeyManagers()).thenReturn(new KeyManager[0]); - - TrustManagerFactory tmf = mock(TrustManagerFactory.class); - tmfStatic.when(() -> TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())).thenReturn(tmf); - when(tmf.getTrustManagers()).thenReturn(new TrustManager[0]); - - SSLContext sslContext = mock(SSLContext.class); - SSLServerSocketFactory sf = mock(SSLServerSocketFactory.class); - SSLServerSocket secureSocket = mock(SSLServerSocket.class); - - when(SSLContext.getInstance("TLS")).thenReturn(sslContext); - when(sslContext.getServerSocketFactory()).thenReturn(sf); - when(sf.createServerSocket(anyInt())).thenReturn(secureSocket); - when(secureSocket.getEnabledProtocols()).thenReturn(new String[] {"TLSv1.2"}); - when(secureSocket.getEnabledCipherSuites()).thenReturn(new String[] {}); - when(secureSocket.accept()).thenThrow(new java.io.IOException("accept fail")); - - assertThrows(java.io.IOException.class, svc::startService); - } - } - - @Test - public void test07_mainEnableUnixAuthInvokesRun() { - try (MockedStatic ugscStatic = mockStatic(UserGroupSyncConfig.class); - MockedStatic haStatic = mockStatic(UserSyncHAInitializerImpl.class); - MockedConstruction serviceConstruction = mockConstruction(UnixAuthenticationService.class, (mock, ctx) -> { - // service.run() will be called by main; make it a no-op - })) { - UserGroupSyncConfig ugsc = mock(UserGroupSyncConfig.class); - ugscStatic.when(UserGroupSyncConfig::getInstance).thenReturn(ugsc); - when(ugsc.getUserGroupConfig()).thenReturn(new Configuration()); - - UserSyncHAInitializerImpl ha = mock(UserSyncHAInitializerImpl.class); - haStatic.when(() -> UserSyncHAInitializerImpl.getInstance(any())).thenReturn(ha); - - UnixAuthenticationService.main(new String[] {"-enableUnixAuth"}); - } - } - - @Test - public void test08_startServiceEnablesCipherSuites() throws Throwable { - UnixAuthenticationService svc = new UnixAuthenticationService(); - setField(svc, "sslEnabled", true); - setField(svc, "portNum", 0); - setField(svc, "enabledProtocolsList", Collections.singletonList("TLSV1.2")); - ArrayList enabledCiphers = new ArrayList<>(); - enabledCiphers.add("CIPHER_A"); - setField(svc, "enabledCipherSuiteList", enabledCiphers); - setField(svc, "keyStorePath", ""); - setField(svc, "trustStorePath", ""); - setField(svc, "keyStoreType", KeyStore.getDefaultType()); - setField(svc, "trustStoreType", KeyStore.getDefaultType()); - - try (MockedStatic sslStatic = mockStatic(SSLContext.class)) { - SSLContext sslContext = mock(SSLContext.class); - SSLServerSocketFactory sf = mock(SSLServerSocketFactory.class); - SSLServerSocket secureSocket = mock(SSLServerSocket.class); - - when(SSLContext.getInstance("TLS")).thenReturn(sslContext); - when(sslContext.getServerSocketFactory()).thenReturn(sf); - when(sf.createServerSocket(anyInt())).thenReturn(secureSocket); - when(secureSocket.getEnabledProtocols()).thenReturn(new String[] {"TLSv1.2"}); - when(secureSocket.getEnabledCipherSuites()).thenReturn(new String[] {"CIPHER_A", "CIPHER_B"}); - when(secureSocket.accept()).thenThrow(new IOException("accept fail")); - - assertThrows(IOException.class, svc::startService); - verify(secureSocket).setEnabledCipherSuites(new String[] {"CIPHER_A"}); - } - } - - @Test - public void test09_startServiceSpawnsValidatorThread() throws Throwable { - UnixAuthenticationService svc = new UnixAuthenticationService(); - setField(svc, "sslEnabled", true); - setField(svc, "portNum", 0); - setField(svc, "enabledProtocolsList", Collections.singletonList("TLSV1.2")); - setField(svc, "enabledCipherSuiteList", new ArrayList<>()); - setField(svc, "keyStorePath", ""); - setField(svc, "trustStorePath", ""); - setField(svc, "keyStoreType", KeyStore.getDefaultType()); - setField(svc, "trustStoreType", KeyStore.getDefaultType()); - - final boolean[] runnableIsPasswordValidator = new boolean[] {false}; - - try (MockedStatic sslStatic = mockStatic(SSLContext.class); - MockedConstruction threadConstruction = mockConstruction(Thread.class, (mockThread, ctx) -> { - Object arg0 = ctx.arguments().get(0); - runnableIsPasswordValidator[0] = (arg0 instanceof Runnable) && arg0.getClass().getName().equals(PasswordValidator.class.getName()); - doNothing().when(mockThread).start(); - })) { - SSLContext sslContext = mock(SSLContext.class); - SSLServerSocketFactory sf = mock(SSLServerSocketFactory.class); - SSLServerSocket secureSocket = mock(SSLServerSocket.class); - Socket client = mock(Socket.class); - - when(SSLContext.getInstance("TLS")).thenReturn(sslContext); - when(sslContext.getServerSocketFactory()).thenReturn(sf); - when(sf.createServerSocket(anyInt())).thenReturn(secureSocket); - when(secureSocket.getEnabledProtocols()).thenReturn(new String[] {"TLSV1.2"}); - when(secureSocket.getEnabledCipherSuites()).thenReturn(new String[] {}); - when(secureSocket.accept()).thenReturn(client).thenThrow(new IOException("done")); - - assertThrows(IOException.class, svc::startService); - assertTrue(runnableIsPasswordValidator[0]); - } - } - - @Test - public void test10_startUnixUserGroupSyncProcess_MetricsEnabledAndDisabled() throws Exception { - UnixAuthenticationService svc = new UnixAuthenticationService(); - Method m = UnixAuthenticationService.class.getDeclaredMethod("startUnixUserGroupSyncProcess"); - m.setAccessible(true); - - try (MockedStatic ugscStatic = mockStatic(UserGroupSyncConfig.class)) { - UserGroupSyncConfig ugsc = mock(UserGroupSyncConfig.class); - ugscStatic.when(UserGroupSyncConfig::getInstance).thenReturn(ugsc); - - // Metrics enabled -> two threads created - when(ugsc.isUserSyncMetricsEnabled()).thenReturn(true); - try (MockedConstruction threadConstruction = mockConstruction(Thread.class, (mockThread, ctx) -> { - doNothing().when(mockThread).setName(any()); - doNothing().when(mockThread).setDaemon(false); - doNothing().when(mockThread).start(); - })) { - m.invoke(svc); - assertEquals(2, threadConstruction.constructed().size()); - } - - // Metrics disabled -> one thread created - when(ugsc.isUserSyncMetricsEnabled()).thenReturn(false); - try (MockedConstruction threadConstruction = mockConstruction(Thread.class, (mockThread, ctx) -> { - doNothing().when(mockThread).setName(any()); - doNothing().when(mockThread).setDaemon(false); - doNothing().when(mockThread).start(); - })) { - m.invoke(svc); - assertEquals(1, threadConstruction.constructed().size()); - } - } - } - - private void invokeInit(UnixAuthenticationService svc) throws Exception { - Method m = UnixAuthenticationService.class.getDeclaredMethod("init"); - m.setAccessible(true); - m.invoke(svc); - } - - private void setField(Object target, String fieldName, Object value) throws Exception { - Field f = UnixAuthenticationService.class.getDeclaredField(fieldName); - f.setAccessible(true); - f.set(target, value); - } -}