Skip to content

razsm-git/resetme

Repository files navigation

Readme

Resetme - это web сервис, который позволяет пользователям самостоятельно "сбрасывать" пароли в AD, даже когда срок действия пароля уже истёк или пользователь вообще забыл свой пароль.

Сервис требует ввода "капчи", для защиты от ботов, а также проводит верификацию и подтверждение личности пользователя с посмощью отправки смс сообщений на мобильный телефон пользователя, который указан в атрибуте mobile пользователя, в AD. Рекомендуется использовать сервис resetme только в локальной сети из соображений безопасности.

Important

Для работы resetme, Вы должны создать доменную учётную запись с правами на сброс паролей пользователей в AD. Не рекомендуется добавлять учётную запись в группу администраторов домена. Вместо этого можно делигировать права на сброс паролей для пользователей в определенном OU

Подготовительные работы

Делегирование прав для учетной записи в Microsoft Windows:

Внесите изменения в скрипт delegate_permission.ps1. Переменная $ou содержит OU, в котором находятся пользователи домена, которым сервис resetme будет сбрасывать пароли. В переменной $group укажите имя группы в домене, для которой нужно делегировать права на "сброс" паролей. Запустите скрипт delegate_permission.ps1 (из под учетной записи с правами администратора домена). Добавьте пользователя(например, service_resetme) в группу, которую указали в переменной $group. В дальнейшем, пользователя из предыдушего шага, например, service_resetme, нужно будет указать при добавлении нового домена через web интерфейс администратора.

Установка

В качестве окружения lxc контейнер с образом Debian 11 bullseye (5.15.83-1)/Debian 12 bookworm (6.8.4-3). В других ОС и средах виртуализации/контейнеризации не тестировался.

База данных postgresql. Кэш пользовательских сессий redis.

Django установлен с помощью pip, т.к. в репозитории debian версия сильно старше.

Скрипт автоматической установки

wget https://github.com/razsm-git/resetme/raw/main/install.sh; /bin/bash ./install.sh

В процессе инсталяции будет предложено ввести ответы на вопросы.

После установки перейдите к шагу Перед началом работы. Также полезными могут быть разделы Описание конфигурационных файлов и Сообщения об ошибках/коды возврата

Установка вручную

Клонируем репозиторий и устанавливаем зависимости:

cd /tmp
git clone https://github.com/razsm-git/resetme.git
cd /resetme
xargs apt -y install < requirements_apt
pip install -r requirements_pip --break-system-packages

Будьте внимательны, без опции --break-system-packages установка не будет успешна. Нам рекомендуют создать виртуальную среду Python, но поскольку мы уже в виртуальной среде на уровне ОС, игнорируем это требование.

Создаем проект django:

mkdir -p /resetmer/webapp
cd $_
django-admin startproject webapp .
python3 manage.py startapp resetme

Скопируйте секретный ключ сервера django (переменная SECRET_KEY) из файла settings.py и перенесите данные из репозитория в директорию с проектом: cp -r /tmp/resetme/* /resetme/

Создайте пользователя для gunicorn и назначте необходимые права

 useradd -M -r -U -s /usr/sbin/nologin gunicorn
 chown -R www-data:gunicorn /var/log/gunicorn
 chmod -R g+w /var/log/gunicorn

Samples

В папке samples "лежат" образцы файлов конфигураций для таких программ как nginx, gunicorn(systemd units)

Скопируйте их в соответствующие папки:

 cd /resetme
 cp samples/gunicorn.* /etc/systemd/system/
 cp samples/nginx_resetme /etc/nginx/sites-available/nginx_resetme
 cp samples/redis.conf /etc/redis/redis.conf
 cp samples/vars_sample.py vars.py
 cp samples/secret_sample.py secret.py
 ln -s /etc/nginx/sites-available/nginx_resetme /etc/nginx/sites-enabled/nginx_resetme

Добавьте данные из файла nginx.conf в секцию http основного конфигурационного файла nginx В файле /etc/nginx/sites-available/nginx_resetme измените url сайта в двух местах: директива server_name, а также в условии if ($host !~ ^(example.ru|www.example.ru)$ ). Укажите адрес локальной подсети: директива location /admin - allow (для доступа к /admin)

Создаем БД, пользователя и назначаем права доступа:

su - postgres
psql 
    CREATE USER $db_user WITH PASSWORD $($echo_path "'$db_pass'");
    ALTER ROLE $db_user SET client_encoding TO 'utf8';
    ALTER ROLE $db_user SET default_transaction_isolation TO 'read committed';
    ALTER ROLE $db_user SET timezone TO 'UTC';
    CREATE DATABASE $db_name OWNER $db_user;
    GRANT ALL PRIVILEGES ON DATABASE $db_name TO $db_user;
    \connect $db_name
    CREATE TABLE public.$new_table (
        id int8 GENERATED BY DEFAULT AS IDENTITY NOT NULL,
        domain_name varchar(50) NOT NULL,
        ad_server varchar(50) NOT NULL,
        base_dn varchar(500) NOT NULL,
        retrieve_attributes varchar(500) NOT NULL,
        search_filter varchar(500) NOT NULL,
        admin_username varchar(50) NOT NULL,
        admin_password varchar(50) NOT NULL,
        "enable" bool NOT NULL,
        CONSTRAINT $($echo_path $new_table)_pkey PRIMARY KEY (id)
    );
    GRANT ALL ON TABLE public.$new_table TO $db_user;
\q

,где $db_name - имя базы данных, $db_user - пользователь базы данных, $db_pass - пароль для пользователя базы данных, $new_table - имя таблицы (по умолчанию необходимо указать resetme_domain)

Добавляем в автозапуск и запускаем службы:

systemctl daemon-reload && systemctl enable --now gunicorn.service gunicorn.socket nginx redis-server

Внесите необходимые данные в файлы secret.py и vars.py. Перейдите к шагу Перед началом работы. Также полезными могут быть разделы Описание конфигурационных файлов и Сообщения об ошибках/коды возврата

Описание конфигурационных файлов

В проекте есть файлы vars.py и secret.py, которые хранят в себе переменные и логины/пароли.

secret.py

В репозитории есть secret_sample.py, который необходимо перенести на уровень выше и переименовать при установке вручную. В случае установки с помощью скрипта данные будут заполнены автоматически.

  • secret_key_django - ключ django сервера, который перенесён из settings.py (скопировать из файла settings.py перед импортом проекта resetme)
  • resetme_db_host = 'ip адрес хоста БД, например, localhost'
  • resetme_db_port = 'если порт стандартный, можно оставить пустым'
  • resetme_db = 'имя БД'
  • resetme_db_user = 'имя пользователя БД'
  • resetme_db_pass = 'пароль пользователя БД'

Данные для подключения к сервису отправки смс сообщений. На данный момент resetme рассчитан на работу только с провайдером smsc.ru средствами API через HTTPS GET запросы

  • sms_login = 'логин'
  • sms_password = 'пароль'

vars.py

В репозитории есть vars_sample.py, который необходимо перенести на уровень выше и переименовать при установке вручную. В случае установки с помощью скрипта данные будут заполнены автоматически.

Переменная site_url содержит полный url, по которому осуществляется переход на сайт. Она используется в коде, чтобы запретить прямой переход на страницы верификации, смены пароля и т.д. Также измените ALLOWED_HOSTS в webapp/webapp/settings.py

Переменная company_name - имя Вашей организации. Оно будет расположено в нижнем левом углу каждой страницы сайта.

В переменных background_color_* указаны цвета фона в шестнадцатеричном коде цветов для каждой страницы сервиса. Обратите внимание, # в начале и ; обязательны. Например,

background_color_index = '#1d1f10;'
background_color_domain_choice = '#005773;'
background_color_verify_phone = '#4d2637;'
background_color_change_password = '#006c73;'
background_color_success = '#00735b;'

Переменная title_name содержит название вкладки в браузере.

Переменная dc_time_sync хранит значение в минутах. Это время, которое необходимо Вашим контролерам домена, чтобы завершить синхронизацию. Также это время будет показано пользователю на финишной странице сайта (чтобы оповестить, через какой максимальный промежуток времени пройдет успешная синхронизация со всеми контроллерами доменов)

login_validator и sms_code_validator - это регулярные выражения для проверки введенных в соответствующие формы данных.

Чтобы указать кол-во неудачных попыток ввода имени пользователя/"капчи", после которых сессия пользователя будет "сброшена" средствами django, измените значения этой переменной. По умолчанию count_of_fails_form_threshold = 3

Чтобы указать кол-во неудачных попыток ввода смс кода, после которых сессия пользователя будет "сброшена" средствами django, измените значения этой переменной. По умолчанию count_of_fails_code_threshold = 3

В нижеуказанных переменных значения для "хэширования" и "соли" введённых паролей пользователей. Это сделано для хранения и отслеживания "истории паролей". Если Вы не знаете, что менять, оставьте значения по умолчанию.

urandom_bytes = 16
algoritm = 'sha256'
coding = 'utf-8'
iter = 100000
dklen = 128

Caution

При изменении значений переменных для "хэширования" и "соли" на уже "рабочей" БД, необходимо вручную очистить таблицу "resetme_user" базы данных от всех значений, т.к. сервис не сможет проверить хэш.

Следующая переменная, это словарь, в котором указаны критерии сложности пароля. Они проверяются функцией re.findall. Их количество жёстко задано, поэтому нельзя увеличивать/уменьшать количество ключей в словаре.

  • len: пароль должен быть не короче указанной длины
  • upper: пароль должен включать заглавные буквы
  • lower: пароль должен включать строчные буквы
  • number: пароль должен включать wbahs
  • history: указываем, какое количество записей будет хранится в БД, для обеспечения работы "истории паролей"

Обратите внимание, что чем больше значение этой переменной, тем дольше будет проверять функция. Это скажется на времени ожидания web страницы пользователем

  • change_per_day: разрешенное количество изменений пароля для одного пользователя в сутки

conditions = {'len': 8, 'upper': '[A-Z]', 'lower': '[a-z]', 'number': '[0-9]', 'history': 10, 'change_per_day': 1}

Сервис Resetme использует redis, для хранения некоторых временных данных. Ниже приведены основные настройки.

  • redis_host = 'localhost'
  • redis_port = 6379
  • db = 0
  • redis_ttl = это таймаут нахождения пользователя на одной странице. После истечения указанного значения, сессия пользователя будет "сброшена" средствами django и пользователю придётся начать всю процедуру с первой страницы. По умолчанию 600 секунд.
  • redis_ttl_sms_code = это время, в течении которого валиден смс код, который был отправлен пользователю. По умолчанию 120 секунд

P.S. Данные о сессиях пользователей хранятся в кэше redis, поэтому нет необходимости настраивать удаление этих данных из БД. Если меняете параметры в vars.py необходим перезапуск web сервера systemctl restart gunicorn

Сообщения об ошибках/коды возврата:

Для пользователя:

Сообщение:

Замечена подозрительная активность с участием вашего аккаунта. обратитесь в отдел ИТ для изменения пароля

Значение:

Пользователь пытается сбросить пароль более, чем значение ключа change_per_dayв словаре conditions, в файле vars.py, в сутки.

Сообщение:

Введенные пароли не совпадают!

Значение:

Пользователь ввёл разные пароли в поле пароль и подтверждение пароля

Сообщение:

Пароль уже использовался Вами ранее. Введите другой пароль

Значение:

Пользователь пытается установить пароль, который ранее был "сброшен" через сервис resetme и хранится в истории паролей (значение переменной conditions['history'])

Сообщение:

Упс..Что-то пошло не так...Сообщите об этом в отдел ИТ

Значение:

Вероятно, произошла ошибка при попытке подключения к каталогу LDAP или при попытке сбросить пароль в LDAP

Сообщение:

При отправке кода произошла ошибка. Попробуйте ещё раз

Значение:

Вероятно, произошла ошибка при попытке подключения к провайдеру отпрвки смс сообщений (функция send_code_by_sms вернула код ответа != 200)

Сообщение:

Такого пользователя не существует. Обратитесь в отдел ИТ

Значение:

Пользователя не существует в LDAP или его учетная запись отключена или не заполнены поля атрибутов, по которым производиться поиск (search filter, который был указан при создании записи домена через административную панель django)

Сообщение:

Для того, чтобы верифицировать Вас, нехватает данных или они не верны. Обратитесь в отдел ИТ

Значение:

Проверьте значение атрибутов mobile, givenName и distinguishedName.

Внутренние коды возврата или сообщения об ошибках:

Возврат из функции check_user():

  • 0 - OK
  • 1 - пользователь не существует в каталоге LDAP или отключен
  • 2 - некоторые поля атрибутов пользователя в каталоге LDAP не заполенны или заполнены не корректно

Возврат из функции send_code_by_sms():

  • 200 - OK
  • any - сообщение не отправлено/не доставлено

Возврат из функции hash_unsalt():

  • {'err_code': 0} - OK
  • {'err_code': 1, 'err_msg': "Пароль уже использовался Вами ранее. Введите другой пароль"} - Пользователь пытается установить пароль, который ранее был "сброшен" через сервис resetme и хранится в истории паролей (значение переменной conditions['history'])

Подкючение к каталогу ldaps с самоподписным сертификатом:

  • сконвертировать сертификат в формат .crt Если сертификат был экспортирован из среды MS CA в формате .cer, то команда следующая: openssl x509 -inform der -in /путь до вашего сертификата -out new.crt

  • скопировать сертификат CA в формате .crt в директорию cp new.crt /usr/local/share/ca-certificates/new.crt

  • update-ca-certificates

  • в файле конфигурации /etc/ldap/ldap.conf (если он отсутствует, то: установить пакет libldap-common или создать файл вручную)

    • TLS_REQCERT demand
    • TLS_CACERT /etc/ssl/certs/ca-certificates.crt

Для проверки работы ldaps:

openssl s_client -connect dc1.test.local:636 , где dc1.test.local - fqdn вашего сервера с каталогом LDAP

ldapsearch -H ldaps://dc1.test.local -D 'CN=user,OU=test,OU=test2,DC=test,DC=local' -w password -b 'OU=unit,OU=unnit2,DC=test,DC=local' -d 5 sAMAccountName, где значение ключа -D это расположение пользователя, под которым подключаетесь к LDAPs, -w пароль этого пользователя, -b подразделение, в котором ищете, -d уровень debug, sAMAccountName - атрибут, который ищете

Соединение должно пройти без ошибок, связанных с TLS. Вы должны увидеть данные, которые запросили в каталоге LDAP

Статические файлы

При необходимости изменить статические файлы(css, js, img ect), разместите их по пути webapp/resetme/static, в соответсвующих папках, после чего необходимо выполнить команды:

python3 manage.py collectstatic
systemctl restart nginx

Перед началом работы

  1. Разместите ssl сертификаты в соответствующей директории (путь был указан при установке через скрипт)
  2. Измените значения переменных в файле vars.py, учитывая ваши индивидуальные требования (справочная информация)
  • Выберите цвет фона(измените значение переменных). Остальную стилизацию, если она необходима, можно делать средствами CSS, в файле main.css (рядом лежат файлы bootstrap)
  1. Загрузите собственные файлы логотипов в формате svg в папку webapp/resetme/static/images

    • favicon.svg - миниатюра иконки на вкладке браузера (не более чем width="120" height="120")
    • logo1.svg - логотип компании в верхнем левом углу (не более чем width="200px" height="200px")
    • logo2.svg - логотип компании в нижнем левом углу (не более чем width="200px" height="200px")
    • выполнить команды:
    python3 /resetme/webapp/manage.py collectstatic
    systemctl restart nginx gunicorn
    
  2. Зайдите по url адресу вашего сайта resetme в панель администратора (например, https://resetme.example.ru/admin). В разделе Resetme - Domains нажмите Add, чтобы добавить данные доменов. Их кол-во ничем не ограничено.

    • Domain name: имя домена. Отображается в панели администартора, а также видно пользователям в выпадающем списке, при выборе домена
    • Ad server: FQDN вашего контролера домена, с указанием протокола и порта, например, ldaps://dc.domain.test:636
    • Base dn: OU, в котором расположены пользователи, которым будет доступна функция сброса пароля, через данный сервис, например, OU=Users,DC=domain,DC=test
    • Retrieve attributes: список с атрибутами мобильного телефона и имени пользователя в AD, например, mobile,givenName
    • Search filter: LDAP фильтр, для поиска пользователей в каталоге. По умолчанию ищем только активных пользователей, у которых указан e-mail и мобильный телефон, согласно регулярному выражению. С помощью фильтра "!(memberOf:1.2.840.113556.1.4.1941:=" указываем dn группы Администраторов домена, чтобы невозможно было сбросить им пароль через сервис. Если данный функционал Вам не нужен, то удалите этот фильтр. Например, (&(sAMAccountName={})(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(mail=*@example.ru)(mobile=+7*)(!(memberOf:1.2.840.113556.1.4.1941:=CN=Domain Admins,CN=Users,DC=example,DC=ru)))
    • Admin username: логин учётной записи с правами на "сброс" паролей пользователей
    • Admin_password: пароль учётной записи с правами на "сброс" паролей пользователей
    • Enable: активировать или деактивировать запись для домена

После добавления или удаления(отключения) доменов, не забудьте перезапустить web сервис systemctl restart gunicorn

Disclaimer

Автор не несет ответственности за:

  • неправомерное использование данного материала
  • порчу оборудования или программного обеспечения
  • незаконное изменение, добавление или удаление информации
  • за любой ущерб, включая прямой, косвенный, случайный, специальный или другой, возникший в результате использования или невозможности использования программного обеспечения

Автор также не гарантирует, что программное обеспечение будет работать без сбоев, ошибок или других проблем Вы пользуетесь этой инструкцией и ПО на свой страх и риск!