Self-Service-Password 部署
Self-Service-Password (自助密码)部署
目录
介绍
LDAP 工具箱自助密码是面向最终用户的 Web 应用程序。 它允许他们在丢失密码时更改或重置密码。
它适用于任何 LDAP 目录,包括 Active Directory。
特征
l 标准密码更改
l 通过问题重置、通过邮件发送的令牌、通过短信发送的令牌
l 本地密码策略
l LDAP 高级用法:密码修改扩展作、密码策略控制
l SSH 密钥更改
l Active Directory 和 Samba 模式
l Prehook/Posthook:可以在更改密码之前和之后启动脚本
l 邮件通知
l REST API
安装
先决条件:
l Apache 或其他 Web 服务器
l PHP (>=7.4)
l php-curl (haveibeenpwned api)
l php 过滤器
l PHP-GD (验证码)
l php-ldap 格式
l php-mbstring (重置邮件)
l php-openssl (令牌加密,可能是内置的)
l Smarty (3 或 4)
CentOS / RedHat下安装
1. 配置 yum 存储库:
vi /etc/yum.repos.d/ltb-project.repo
[ltb-project-noarch]
name=LTB project packages (noarch)
baseurl=https://ltb-project.org/rpm/$releasever/noarch
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-LTB-project
2. 更新
yum update
3. 导入存储库密钥
l 对于 EL7/EL8:
pm --import https://ltb-project.org/documentation/_static/RPM-GPG-KEY-LTB-project
l 对于 EL9:
rpm --import https://ltb-project.org/documentation/_static/RPM-GPG-KEY-LTB-PROJECT-SECURITY
4. 安装
yum install self-service-password
服务器配置
Web服务配置
在apache环境下服务器会生成默认配置文件在
/etc/httpd/conf.d/self-service-password.conf
修改配置文件servername为对对应FQDN名即可
<VirtualHost *:80>
ServerName sd.x.h
DocumentRoot /usr/share/service-desk/htdocs
DirectoryIndex index.php
<Directory /usr/share/service-desk/htdocs>
AllowOverride None
<IfVersion >= 2.3>
Require all granted
</IfVersion>
<IfVersion < 2.3>
Order Deny,Allow
Allow from all
</IfVersion>
</Directory>
LogLevel warn
ErrorLog /var/log/httpd/sd_error.log
CustomLog /var/log/httpd/sd_access.log combined
</VirtualHost>
openladp组件配置
导出活动目录证书
1. 打开证书管理器
l 按下 Win + R 输入 certlm.msc,回车(以管理员身份运行)。
l 或者通过 MMC控制台:
n 打开运行(Win + R),输入 mmc。
n 点击 文件 > 添加/删除管理单元,选择 证书 > 计算机账户 > 本地计算机
2. 找到域控制器证书
l 导航到 证书 > 个人 > 证书。
l 在右侧列表中找到域控制器的证书(通常用途为“客户端身份验证”或“服务器身份验证”)
3. 导出证书
右键点击证书,选择 所有任务 > 导出。
选择导出选项:仅导出公钥(CER格式):选择“不导出私钥”,导出为 .cer 文件。导出文件格式为Base64编码
4. 完成导出
选择保存路径和文件名,完成导出。
下载企业内部CA证书
1. 访问企业CA服务器的Web页面(通常为 http://CA服务器地址/certsrv)。
2. 下载根证书和中间证书(格式一般为 .crt 或 .cer)。选择Base64编码
配置 ldap.conf
1. 将上面下载的两个证书复制到/etc/openldap/certs目录
修改ldap.conf文件
#
# LDAP Defaults
#
# See ldap.conf(5) for details
# This file should be world readable but not world writable.
#BASE dc=example,dc=com
#URI ldap://ldap.example.com ldap://ldap-master.example.com:666
#SIZELIMIT 12
#TIMELIMIT 15
#DEREF never
# When no CA certificates are specified the Shared System Certificates
# are in use. In order to have these available along with the ones specified
# by TLS_CACERTDIR one has to include them explicitly:
#TLS_CACERT /etc/pki/tls/cert.pem
# System-wide Crypto Policies provide up to date cipher suite which should
# be used unless one needs a finer grinded selection of ciphers. Hence, the
# PROFILE=SYSTEM value represents the default behavior which is in place
# when no explicit setting is used. (see openssl-ciphers(1) for more info)
#TLS_CIPHER_SUITE PROFILE=SYSTEM
# Turning this off breaks GSSAPI used with krb5 when rdns = false
SASL_NOCANON on
TLS_CACERT /etc/openldap/certs/dcca.cer
TLS_CACERTDIR /etc/openldap/certs
TLS_REQCERT allow
活动目录模式配置
1. 必须在 LDAP 连接上使用 SSL,因为 AD 拒绝更改 password 的 clear 连接
2. 启用活动目录模式
$ad_mode = true;
3. 调整搜索过滤器
$ldap_filter = "(&(objectClass=user)(sAMAccountName={login})(!(userAccountControl:1.2.840.113556.1.4.803:=2)))";
配置文件示例
<?php
#==============================================================================
# LTB Self Service Password
#
# Copyright (C) 2024 Clement OUDOT
# Copyright (C) 2024 LTB-project.org
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# GPL License: http://www.gnu.org/licenses/gpl.txt
#
#==============================================================================
#==============================================================================
# All the default values are kept here, you should not modify it but use
# config.inc.local.php file instead to override the settings from here.
#==============================================================================
#==============================================================================
# Configuration
#==============================================================================
# Debug mode
# true: log and display any errors or warnings (use this in configuration/testing)
# false: log only errors and do not display them (use this in production)
$debug = true;
# LDAP
$ldap_url = "ldaps://dc.x.h:636";
$ldap_starttls = false;
$ldap_binddn = "cn=sspadmin,ou=XAdmin,dc=x,dc=h";
$ldap_bindpw = 'password';
// for GSSAPI authentication, comment out ldap_bind* and uncomment ldap_krb5ccname lines
//$ldap_krb5ccname = "/path/to/krb5cc";
$ldap_base = "dc=x,dc=h";
$ldap_login_attribute = "sAMAccountName";
#$ldap_login_attribute = "cn";
$ldap_fullname_attribute = "cn";
#$ldap_filter = "(&(objectClass=person)($ldap_login_attribute={login}))";
$ldap_filter = "(&(objectClass=user)(sAMAccountName={login})(!(userAccountControl:1.2.840.113556.1.4.803:=2)))";
$ldap_scope = "sub"; # possible values: sub, one, base
$ldap_use_exop_passwd = false;
$ldap_use_ppolicy_control = false;
$ldap_network_timeout = 10;
$ldap_page_size = 0;
# Active Directory mode
# true: use unicodePwd as password field
# false: LDAPv3 standard behavior
$ad_mode = true;
$ad_options=[];
# Force account unlock when password is changed
$ad_options['force_unlock'] = true;
# Force user change password at next login
$ad_options['force_pwd_change'] = false;
# Allow user with expired password to change password
$ad_options['change_expired_password'] = true;
# Samba mode
# true: update sambaNTpassword and sambaPwdLastSet attributes too
# false: just update the password
$samba_mode = false;
$samba_options=[];
# Set password min/max age in Samba attributes
#$samba_options['min_age'] = 5;
#$samba_options['max_age'] = 45;
#$samba_options['expire_days'] = 90;
# Shadow options - require shadowAccount objectClass
$shadow_options=[];
# Update shadowLastChange
$shadow_options['update_shadowLastChange'] = false;
$shadow_options['update_shadowExpire'] = false;
# Default to -1, never expire
$shadow_options['shadow_expire_days'] = -1;
# Hash mechanism for password:
# SSHA, SSHA256, SSHA384, SSHA512
# SHA, SHA256, SHA384, SHA512
# SMD5
# MD5
# CRYPT
# ARGON2
# clear (the default)
# auto (will check the hash of current password)
# This option is not used with ad_mode = true
$hash = "clear";
$hash_options=[];
# Prefix to use for salt with CRYPT
$hash_options['crypt_salt_prefix'] = "$6$";
$hash_options['crypt_salt_length'] = "6";
# USE rate-limiting by IP and/or by user
$use_ratelimit = false;
# dir for json db's (system default tmpdir)
#$ratelimit_dbdir = '/tmp';
# block attempts for same login ?
$max_attempts_per_user = 2;
# block attempts for same IP ?
$max_attempts_per_ip = 2;
# how many time to refuse subsequent requests ?
$max_attempts_block_seconds = "60";
# Header to use for client IP (HTTP_X_FORWARDED_FOR ?)
$client_ip_header = 'REMOTE_ADDR';
# JSON file to filter by IP
#$ratelimit_filter_by_ip_jsonfile = "/usr/share/self-service-password/conf/rrl_filter_by_ip.json";
# Local password policy
# This is applied before directory password policy
# Minimal length
$pwd_min_length = 8;
# Maximal length
$pwd_max_length = 20;
# Minimal lower characters
$pwd_min_lower = 0;
# Minimal upper characters
$pwd_min_upper = 0;
# Minimal digit characters
$pwd_min_digit = 0;
# Minimal special characters
$pwd_min_special = 0;
# Definition of special characters
$pwd_special_chars = "^a-zA-Z0-9";
# Forbidden characters
#$pwd_forbidden_chars = "@%";
# Don't reuse the same password as currently
$pwd_no_reuse = true;
# Check that password is different than login
$pwd_diff_login = true;
# Check new passwords differs from old one - minimum characters count
$pwd_diff_last_min_chars = 0;
# Forbidden words which must not appear in the password
$pwd_forbidden_words = array();
# Forbidden ldap fields
# Respective values of the user's entry must not appear in the password
# example: $pwd_forbidden_ldap_fields = array('cn', 'givenName', 'sn', 'mail');
$pwd_forbidden_ldap_fields = array();
# Complexity: number of different class of character required
$pwd_complexity = 0;
# use pwnedpasswords api v2 to securely check if the password has been on a leak
$use_pwnedpasswords = false;
# show password entropy bar (require php zxcvbn module)
$pwd_display_entropy = false;
# enforce password entropy check
$pwd_check_entropy = false;
# minimum entropy level required (when $pwd_check_entropy enabled)
$pwd_min_entropy = 3;
# Show policy constraints message:
# always
# never
# onerror
$pwd_show_policy = "above";
# Position of password policy constraints message:
# above - the form
# below - the form
$pwd_show_policy_pos = "above";
# disallow use of the only special character as defined in `$pwd_special_chars` at the beginning and end
$pwd_no_special_at_ends = false;
# Who changes the password?
# Also applicable for question/answer save
# user: the user itself
# manager: the above binddn
$who_change_password = "user";
# Show extended error message returned by LDAP directory when password is refused
$show_extended_error = false;
## Standard change
# Use standard change form?
$use_change = true;
## SSH Key Change
# Allow changing of sshPublicKey?
$change_sshkey = false;
# What attribute should be changed by the changesshkey action?
$change_sshkey_attribute = "sshPublicKey";
# What objectClass is required for that attribute?
$change_sshkey_objectClass = "ldapPublicKey";
# Ensure the SSH Key submitted uses a type we trust
$ssh_valid_key_types = array('ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519');
# Who changes the sshPublicKey attribute?
# Also applicable for question/answer save
# user: the user itself
# manager: the above binddn
$who_change_sshkey = "user";
# Notify users anytime their sshPublicKey is changed
## Requires mail configuration below
$notify_on_sshkey_change = false;
## Questions/answers
# Use questions/answers?
$use_questions = true;
# Allow to register more than one answer?
$multiple_answers = false;
# Store many answers in a single string attribute
# (only used if $multiple_answers = true)
$multiple_answers_one_str = false;
# Answer attribute should be hidden to users!
$answer_objectClass = "extensibleObject";
$answer_attribute = "info";
# Crypt answers inside the directory
$crypt_answers = true;
# Extra questions (built-in questions are in lang/$lang.inc.php)
# Should the built-in questions be included?
$questions_use_default = true;
#$messages['questions']['ice'] = "What is your favorite ice cream flavor?";
# How many questions must be answered.
# If = 1: legacy behavior
# If > 1:
# this many questions will be included in the page forms
# this many questions must be set at a time
# user must answer this many correctly to reset a password
# $multiple_answers must be true
# at least this many possible questions must be available (there are only 2 questions built-in)
$questions_count = 1;
# Should the user be able to select registered question(s) by entering only the login?
$question_populate_enable = false;
## Token
# Use tokens?
# true (default)
# false
$use_tokens = true;
# Crypt tokens?
# true (default)
# false
$crypt_tokens = true;
# Token lifetime in seconds
$token_lifetime = "3600";
## File cache parameters
# cache type: File or Redis
$cache_type = "File";
# cache namespace: cache entries are grouped in this directory
$cache_namespace = "sspCache";
# cache directory: cache entries would be created in this extra
# directory inside namespace
$cache_directory = null;
# default lifetime for all cached entry
# not really usefull for now as each cache entry has a defined expiration
# (see cache_token_expiration and cache_form_expiration)
$cache_default_lifetime = 0;
## Redis cache parameters
# cache type: File or Redis
#$cache_type = "Redis";
# Data Source Name (DSN) for accessing to Redis server
# See https://symfony.com/doc/current/components/cache/adapters/redis_adapter.html
#$cache_redis_url = "redis:user:password@?host[redis1:6379]&timeout=5&dbindex=0";
# cache namespace: cache entries are prefixed by this namespace
#$cache_namespace = "sspCache";
# default lifetime for all cached entries
# not really usefull for now as each cache entry has a defined expiration
# (see cache_token_expiration and cache_form_expiration)
#$cache_default_lifetime = 0;
## General cache parameters
# $cache_token_expiration: integer, duration in seconds of cached objects
# each time a token is involved
# (for example when sending a token by sms or by mail)
# it is recommended to set a value >= $token_lifetime
$cache_token_expiration = 3600;
# $cache_form_expiration: integer, duration in seconds of cached objects
# at some steps when a user has to validate a form
# (for example when validating the email address before we send the mail)
# it is recommended to set a value high enough for a user to fill a form
$cache_form_expiration = 120;
# Reset URL (mandatory)
$reset_url = "http://ssp.x.h/";
# If inside a virtual host
#$reset_url = ($_SERVER['HTTPS'] ? "https" : "http") . "://" . $_SERVER['SERVER_NAME'] . $_SERVER['SCRIPT_NAME'];
# If behind a reverse proxy with a virtual host
#$reset_url = $_SERVER['HTTP_X_FORWARDED_PROTO'] . "://" . $_SERVER['HTTP_X_FORWARDED_HOST'] . $_SERVER['SCRIPT_NAME'];
# LDAP mail attribute
$mail_attributes = array( "mail", "gosaMailAlternateAddress", "proxyAddresses" );
#$mail_attributes = "mail";
# Get mail address directly from LDAP (only first mail entry)
# and hide mail input field
# default = false
$mail_address_use_ldap = true;
# Who the email should come from
$mail_from = "sspadmin@xhome.local";
$mail_from_name = "Self Service Password";
$mail_signature = "SSP";
# Notify users anytime their password is changed
$notify_on_change = true;
# PHPMailer configuration (see https://github.com/PHPMailer/PHPMailer)
$mail_sendmailpath = '/usr/sbin/sendmail';
$mail_protocol = 'smtp';
$mail_smtp_debug = 2;
$mail_debug_format = 'error_log';
$mail_smtp_host = 'Exch2019.x.h';
$mail_smtp_auth = false;
$mail_smtp_user = 'sspadmin@x.h';
$mail_smtp_pass = 'Password';
$mail_smtp_port = 10025;
$mail_smtp_timeout = 30;
$mail_smtp_keepalive = false;
$mail_smtp_secure = 'false';
$mail_smtp_autotls = false;
$mail_smtp_options = array('tls' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
));
$mail_contenttype = 'text/plain';
$mail_wordwrap = 0;
$mail_charset = 'utf-8';
$mail_priority = 3;
## SMS
# Use sms
$use_sms = false;
# Get telephone number directly from LDAP (only first number entry)
# and hide telephone number input field
# default = false
$sms_use_ldap = false;
# SMS method (mail, api)
$sms_method = "mail";
# path to SMS library to use
# currently, 3 libraries are bundled:
# - lib/smsapi-signal-cli.inc.php
# - lib/smsapi-twilio.inc.php
# - lib/smsovh/smsapi-ovh.inc.php
# The last one needs php-ovh-sms dependency, do `composer update` in lib/smsovh
# you can also write your own library
$sms_api_lib = "";
# GSM number attribute
$sms_attributes = array( "mobile", "pager", "ipPhone", "homephone" );
# Partially hide number
$sms_partially_hide_number = true;
# Send SMS mail to address. {sms_attribute} will be replaced by real sms number
$smsmailto = "{sms_attribute}@service.provider.com";
# Subject when sending email to SMTP to SMS provider
$smsmail_subject = "Provider code";
# Message
$sms_message = "{smsresetmessage} {smstoken}";
# Remove non digit characters from GSM number
$sms_sanitize_number = false;
# Truncate GSM number
$sms_truncate_number = false;
$sms_truncate_number_length = 10;
# SMS token length
$sms_token_length = 6;
# Max attempts allowed for SMS token
$sms_max_attempts_token = 3;
# Encryption, decryption keyphrase, required if $use_tokens = true and $crypt_tokens = true, or $use_sms, or $crypt_answer
# Please change it to anything long, random and complicated, you do not have to remember it
# Changing it will also invalidate all previous tokens and SMS codes
$keyphrase = "xhwAdmin135";
# Use attributes update page
$use_attributes = false;
#$attribute_mail = "mail";
#$attribute_phone = "mobile";
#$who_change_attributes = "manager";
# Display help messages
$show_help = true;
# Default language
$lang = "en";
# List of authorized languages. If empty, all language are allowed.
# If not empty and the user's browser language setting is not in that list, language from $lang will be used.
$allowed_lang = array();
# Display menu on top
$show_menu = true;
# Logo
$logo = "images/ltb-logo.png";
# Background image
$background_image = "images/unsplash-space.jpeg";
# Path is relative to htdocs/html and the custom CSS file should be created in css/ directory. For example: "css/sample.css"
$custom_css = "";
$display_footer = true;
# Where to log password resets - Make sure apache has write permission
# By default, they are logged in Apache log
#$reset_request_log = "/var/log/self-service-password";
# Invalid characters in login
# Set at least "*()&|" to prevent LDAP injection
# If empty, only alphanumeric characters are accepted
$login_forbidden_chars = "*()&|";
## Captcha
$use_captcha = false;
$captcha_class = "InternalCaptcha";
#$captcha_class = "FriendlyCaptcha";
#$friendlycaptcha_apiurl = "https://api.friendlycaptcha.com/api/v1/siteverify";
#$friendlycaptcha_sitekey = "secret";
#$friendlycaptcha_secret = "secret";
#$captcha_class = "ReCaptcha";
#$recaptcha_url = "https://www.google.com/recaptcha/api/siteverify";
#$recaptcha_sitekey = "sitekey";
#$recaptcha_secretkey = "secretkey";
#$recaptcha_minscore = 0.5;
## Default action
# change
# sendtoken
# sendsms
# changecustompwdfield
$default_action = "change";
## default page of custom password field to display
#$default_custompwdindex = 0;
## Rest API
$use_restapi = false;
## Extra messages
# They can also be defined in lang/ files
#$messages['passwordchangedextramessage'] = NULL;
#$messages['changehelpextramessage'] = NULL;
# Audit
#$audit_log_file = "/var/log/self-service-password/audit.log";
## Pre Hook
# Launch a prehook script before changing password.
# Script should return with 0, to allow password change.
# Any other exit code would abort password modification
#$prehook = "/usr/share/self-service-password/prehook.sh";
# Display prehook error
#$display_prehook_error = true;
# Encode passwords sent to prehook script as base64. This will prevent alteration of the passwords if set to true.
# To read the actual password in the prehook script, use a base64_decode function/tool
#$prehook_password_encodebase64 = false;
# Ignore prehook error. This will allow to change password even if prehook script fails.
#$ignore_prehook_error = true;
## Post Hook
# Launch a posthook script after successful password change
#$posthook = "/usr/share/self-service-password/posthook.sh";
# Display posthook error
#$display_posthook_error = true;
# Encode passwords sent to posthook script as base64. This will prevent alteration of the passwords if set to true.
# To read the actual password in the posthook script, use a base64_decode function/tool
#$posthook_password_encodebase64 = false;
# Force setlocale if your default PHP configuration is not correct
#setlocale(LC_CTYPE, "en_US.UTF-8");
# Hide some messages to not disclose sensitive information
$obscure_usernotfound_sendtoken = true;
$obscure_notfound_sendsms = true;
# HTTP Header name that may hold a login to preset in forms
#$header_name_preset_login="Auth-User";
# The name of an HTTP Header that may hold a reference to an extra config file to include.
#$header_name_extra_config="SSP-Extra-Config";
# Cache directory
$smarty_compile_dir = "/var/cache/self-service-password/templates_c";
$smarty_cache_dir = "/var/cache/self-service-password/cache";
# Smarty debug mode - will popup debug information on web interface
# and add many smarty debug messages in error logs
$smarty_debug = false;
## Custom Password Fields
# Change Custom Password Fields
$change_custompwdfield = array();
# Allow to override current settings with local configuration
if (file_exists (__DIR__ . '/config.inc.local.php')) {
require_once __DIR__ . '/config.inc.local.php';
}
# Smarty
if (!defined("SMARTY")) {
define("SMARTY", "/usr/share/php/Smarty/Smarty.class.php");
}
# Set preset login from HTTP header $header_name_preset_login
$presetLogin = "";
if (isset($header_name_preset_login)) {
$presetLoginKey = "HTTP_".strtoupper(str_replace('-','_',$header_name_preset_login));
if (array_key_exists($presetLoginKey, $_SERVER)) {
$presetLogin = preg_replace("/[^a-zA-Z0-9-_@\.]+/", "", filter_var($_SERVER[$presetLoginKey], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH));
}
}
# Allow to override current settings with an extra configuration file, whose reference is passed in HTTP_HEADER $header_name_extra_config
if (isset($header_name_extra_config)) {
$extraConfigKey = "HTTP_".strtoupper(str_replace('-','_',$header_name_extra_config));
if (array_key_exists($extraConfigKey, $_SERVER)) {
$extraConfig = preg_replace("/[^a-zA-Z0-9-_]+/", "", filter_var($_SERVER[$extraConfigKey], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH));
if (strlen($extraConfig) > 0 && file_exists (__DIR__ . "/config.inc.".$extraConfig.".php")) {
require_once __DIR__ . "/config.inc.".$extraConfig.".php";
}
}
}