Русский
Русский
English
Статистика
Реклама

Веб-сервер

Apache amp Nginx. Связаны одной цепью

14.07.2020 16:07:05 | Автор: admin
Как реализована связка Apache & Nginx в Timeweb

Для многих компаний Nginx + Apache + PHP очень типовая и распространенная связка, и Timeweb здесь не стал исключением. Однако разобраться, как именно она реализована, может быть любопытно и полезно.

image

Использование такой связки, конечно, продиктовано потребностями наших клиентов. И Nginx и Apache играют особую роль, каждый решает определенную задачу.

Основные настройки Apache выполняются в конфигурационных файлах самого Apache, а настройки для клиентских сайтов происходят через файл .htaccess. .htaccess конфигурационный файл, в котором клиент может самостоятельно настроить правила и поведение веб-сервера. Такая настройка будет относиться конкретно к его сайту. Например, благодаря функционалу Apache пользователи могут менять режим работы в рамках одной версии PHP с mod_php на mod_cgi; можно настраивать редиректы, оптимизацию для SEO, удобный URL, некоторые лимиты для PHP.

Nginx используется как прокси-сервер для перенаправления трафика на Apache и как веб-сервер для отдачи статического контента. Также для Nginx мы разработали модули безопасности, которые позволяют защитить данные наших пользователей, например, разделить права доступа.

Представим, что какой-то пользователь заходит на сайт нашего клиента. Сначала пользователь попадает на Nginx, который отдает статический контент. Это происходит мгновенно. Затем, когда дело доходит до загрузки PHP, Nginx перенаправляет запрос на Apache. И Apache совместно с PHP уже генерирует динамический контент.

Особенности связки Apache & Nginx в Timeweb


На нашем виртуальном хостинге реализованы 2 основные схемы работы Apache & Nginx: Shared и Dedicated.

Схема Shared
Эта схема используется для большинства пользователей. Ее отличает простота и ресурсоемкость: Shared-схема использует меньше ресурсов, поэтому и ее тариф дешевле. Согласно данной схеме на сервере запущен один Nginx, который позволяет обслуживать все пользовательские запросы, и несколько экземпляров Apache.

Схема Shared совершенствовалась долгое время: постепенно мы исправляли недочеты. Удобно, что ее можно сделать без необходимости дорабатывать исходный код.


схема Shared

Схема Dedicated
Dedicated требует больше ресурсов, поэтому ее тариф дороже для клиентов. В Dedicated-схеме для каждого клиента поднимается свой, отдельный Apache. Ресурсы здесь резервируются под клиента, они выделяются эксклюзивно. Как это работает: на сервере есть несколько версий PHP. Мы поддерживаем версии 5.3, 5.4, 5.6, 7.1, 7.2, 7.3, 7.4. Так, для каждой версии PHP запускается свой Apache.


схема Dedicated

Safe zone. Настройка зон в Nginx


Ранее для Nginx мы использовали много зон разделяемой памяти (zone) один блок server на один домен. Такая настройка требует большого количества ресурсов, так как для каждого сайта создается отдельная зона. Однако в настройках Nginx большинство сайтов однотипные, поэтому их удается поместить в одну зону благодаря использованию директив map в модуле ngx_http_map_module, которые позволяют задать соответствия. Например, у нас есть шаблон зоны, в которую мы должны поставлять переменные: путь к сайту, версию PHP, пользователя. Таким образом, ускорилось перечитывание конфигурации Nginx, то есть релоад.

Подобная конфигурация сильно сэкономила ресурсы оперативной памяти и ускорила работу Nginx.

Reload не пройдет!


В Shared-схеме мы избавились от необходимости перезагрузки (релоада) Apache при изменениях в настройках сайтов. Ранее, когда один клиент хотел добавить домен или поменять версию PHP, требовался обязательный релоад Apache, что приводило к задержкам в ответах и негативно сказывалось на производительности сайтов.

Мы избавились от релоадов путем создания динамических конфигураций. Благодаря mpm-itk (модулю Apache), каждый процесс выполняется от отдельного пользователя, что повышает уровень безопасности. Такой способ позволяет передавать из Nginx в Apache2 данные о пользователе и его document_root. Таким образом, Apache не содержит в себе конфигурации сайтов, он получает их динамически, и релоады больше не требуются.


Конфигурация схемы Shared

А как же Docker?


Многие компании перешли на систему, основанную на контейнерах. Timeweb сейчас рассматривает возможность такого перехода. Безусловно, в каждом решении можно найти плюсы и минусы.

Наряду с неоспоримыми преимуществами, в контейнерной системе пользователю предоставлено меньше ресурсов. В Timeweb, благодаря описанной схеме работы хостинга, у пользователя нет ограничения в оперативной памяти. Он получает больше ресурсов, чем в контейнере. Кроме того, у юзера может быть загружено больше модулей Apache.

Timeweb обеспечивает работу около 500 000 сайтов. Мы несем большую ответственность и не вносим мгновенные, неоправданные изменения в сложную архитектуру. Связка Apache & Nginx надежна и проверена временем. Мы, в свою очередь, стараемся достичь максимальной производительности благодаря уникальным конфигурациям.

Для качественной и быстрой работы большого количества сайтов требуется использовать шаблонную и динамическую конфигурацию Apache и Nginx. Она позволяет просто и быстро администрировать большое число однотипных серверов.
Подробнее..

Apache amp Nginx. Связаны одной цепью (2 часть)

21.07.2020 12:10:27 | Автор: admin
На прошлой неделе в первой части этой статьи мы описали, как построена связка Apache и Nginx в Timeweb. Мы очень благодарны читателям за вопросы и активное обсуждение! Сегодня рассказываем, как реализована доступность нескольких версий PHP на одном сервере и почему мы гарантируем безопасность данных нашим клиентам.



Виртуальный хостинг (Shared-хостинг) предполагает, что на одном сервере размещено множество аккаунтов клиентов. На аккаунте одного клиента, как правило, находится несколько сайтов. Сайты работают как на готовых CMS (например, Bitrix), так и на кастомных. Таким образом, технические требования у всех систем разные, поэтому в рамках одного сервера необходимо управлять несколькими версиями PHP.

В качестве основного веб-сервера мы используем Nginx: он принимает все подключения извне и отдает статический контент. Остальные запросы мы проксируем дальше, на веб-сервер Apache. Здесь и начинается магия: для каждой версии PHP запущен отдельный экземпляр Apache, который слушает определенный порт. Этот порт прописывается в виртуальном хосте клиентского сайта.

О работе Shared-схемы можно прочитать более подробно в первой части статьи.


Shared-схема

Важно отметить, что мы ставим пакеты PHP под разные версии, потому что обычно во всех дистрибутивах лежит только одна версия PHP.

Safety first!


Одна из главных задач виртуального хостинга обеспечить безопасность данных клиента. Различные аккаунты, находясь на одном сервере, самостоятельны и независимы. Как это работает?

Файлы сайтов хранятся в домашних каталогах самих пользователей, а в виртуальном хосте веб-серверов указываются нужные пути. При этом важно, чтобы веб-серверы, Nginx и Аpache, получили доступ к конечным файлам определенного клиента, так как веб-сервер запускается только от одного пользователя.

Для Nginx используется патч безопасности, разработанный командой Timeweb: этот патч меняет пользователя на того, который указан в конфигурационном файле веб-сервера.

У других хостинг-провайдеров эта проблема может быть решена, например, через манипуляции с расширенными правами файловой системы (ACL).

Для работы Apache используется модуль мультипроцессинга mpm-itk. Он позволяет запускать каждый VirtualHost с собственным идентификатором пользователя и ID группы.


Таким образом, благодаря операциям, описанным выше, мы получаем безопасную изолированную среду для каждого клиента. При этом мы также решаем задачи масштабирования для Shared-хостинга.

Как реализована связка Apache и Nginx, можно прочитать в первой части нашей статьи. Кроме того, там же описана альтернативная конфигурация через Dedicated-схему.

Если у вас остались вопросы к нашим экспертам, пишите в комментариях. Постараемся на всё ответить или описать решение задачи более подробно в следующих статьях.
Подробнее..

Как сделать nginx безопасным

26.10.2020 12:15:22 | Автор: admin


TL;DR: абсолютно устойчивых систем не существует, поэтому ответ никак. Но можно значительно упростить себе жизнь с помощью Docker-контейнера bunkerized-nginx.
О том, чем он отличается от стандартного образа nginx и что интересного умеет, поговорим под катом.


Сервер в бункере


Вообще у меня слово bunkerized применительно к серверу ассоциируется исключительно с CyberBunker, и здесь эта аналогия в принципе уместна. Французская команда Bunkerity разрабатывает готовые защищённые образы для nginx, mariadb, php и phpmyadmin, обещая защиту от проникновения, ботов и индексаторов, брутфорса и опасных файлов, как владельцы пиратского бункера когда-то гарантировали безопасность и анонимность.

image
Сканеры не видят сервер https://demo-nginx.bunkerity.com/, хотя он доступен в браузере

Реальные фичи


Помимо стандартных плюсов nginx'a в докере мы получаем:
  • Поддержку HTTPS с автоматическим продлением Let's Encrypt,
  • Актуальную веб-защиту: HTTP-заголовки безопасности, php.ini hardening, предотвращение утечек памяти и другое
  • Встроенный межсетевой экран Modsecurity с правилами OWASP Core Rule Set
  • Автоматическую блокировку подозрительных действий через fail2ban
  • Защиту от бот-атак обязательная проверка по капче/кукам/кастомному js (аналог Attack mode в Cloudflare)
  • Блокировку луковичных соединений, прокси, по подозрительному/запрещённому юзер-агенту и даже по стране запроса
  • Автоматическую проверку IP в черном списке DNSBL
  • Защиту от брутфорса (лимит на запросы)
  • Обнаружение опасных/поврежденных файлов с помощью ClamAV
  • Компактную конфигурацию через переменные среды
  • Поддержку нестандартных архитектур вроде arm32v7


Что-то выглядит банально, что-то может показаться излишним (зачем мне пересобирать nginx, если я запускаю контейнер на x86_64?), но благодаря гибкой настройке почти всё можно настроить на свой вкус и под свои нужды.

Запуск


Установка

docker pull bunkerity/bunkerized-nginx


HTTP-сервер с настройками по умолчанию

docker run -p 80:80 -v /path/to/web/files:/www bunkerity/bunkerized-nginx

Файлы раздаются из каталога /www.

HTTPS-сервер с автоматическим управлением Let's Encrypt

docker run -p 80:80 -p 443:443 -v /path/to/web/files:/www -v /where/to/save/certificates:/etc/letsencrypt -e SERVER_NAME=www.yourdomain.com -e AUTO_LETS_ENCRYPT=yes -e REDIRECT_HTTP_TO_HTTPS=yes bunkerity/bunkerized-nginx

Сертификаты хранятся в каталоге /etc/letsencrypt directory. Можно запретить серверу слушать HTTP, добавив LISTEN_HTTP: no. Не забудьте настроить перенаправление, потому что Let's Encrypt нуждается в открытом 80 порту.

Здесь были использованы следующие переменные:
SERVER_NAME FQDN (полное доменное имя) вашего сервера
AUTO_LETS_ENCRYPT автоматически создаёт и обновляет сертификаты Let's Encrypt
REDIRECT_HTTP_TO_HTTPS перенаправляет HTTP на HTTPS (кэп)

Работа в режиме обратного прокси

Собственно конфигурация обратного прокси ложится на пользователя:
  location / {    if ($host = www.website1.com) {      proxy_pass http://192.168.42.10$request_uri;    }      if ($host = www.website2.com) {      proxy_pass http://192.168.42.11$request_uri;    }  }

Все файлы конфигурации (.conf) в каталоге /server-confs будут включены в серверный контекст. Достаточно просто примонтировать том с конфигами к контейнеру:
docker run -p 80:80 -e SERVER_NAME="www.website1.com www.website2.com" -e SERVE_FILES=no -e DISABLE_DEFAULT_SERVER=yes -v /path/to/server/conf:/server-confs bunkerity/bunkerized-nginx

Здесь:
SERVER_NAME список валидных заголовков Host, посылаемых клиентом
SERVE_FILES разрешает (yes) или запрещает (no) nginx раздавать файлы из /www
DISABLE_DEFAULT_SERVER nginx не будет отвечать на запросы, у которых Host не будет находиться в списке SERVER_NAME
Здесь можно найти больше средств гибкой настройки

Работа за обратным прокси

docker run -p 80:80 -v /path/to/web/files:/www -e PROXY_REAL_IP=yes bunkerity/bunkerized-nginx

При включении PROXY_REAL_IP: yes активируется nginx модуль ngx_http_realip_module для получения реального IP клиента из-за прокси.

Обязательная антибот-проверка

docker run -p 80:80 -v /path/to/web/files:/www -e USE_ANTIBOT=captcha bunkerity/bunkerized-nginx

При USE_ANTIBOT: captcha все пользователи при заходе будут вынуждены проходить капчу. Также доступны варианты cookie, javascript, recaptcha. Доки здесь.

Ещё есть примеры в репозитории, здесь.

Заключение


bunkerized-nginx это удобный вариант для тех, кому нужно быстро запустить nginx и не париться в будущем о его безопасности, закрытии уязвимостей и конфиденциальности. Буквально в одну строчку можно запустить готовый контейнер и забыть про него. При этом, несмотря на простой старт, это всё ещё полноценный nginx с его огромнейшим функционалом, что позволяет максимально гибко всё настроить.



На правах рекламы


Эпичные серверы это виртуальные серверы которые прекрасно подойдут для размещения разнообразных сайтов. Сумасшедшая производительность благодаря мощным процессорам семейства AMD EPYC и очень быстрым NVMe дискам Intel. Обязательно закажите!

Подробнее..

Перевод Запуск домашнего веб-сервера без статического IP с помощью Python

13.05.2021 08:09:48 | Автор: admin


Приветствую жителей Хабра!


Задался тут вопросом, как можно обойтись без статического IP для экспериментов в домашних условиях. Наткнулся на вот эту статью.


Если вы хотите развернуть свой вебсервер с доступом извне, а платить провайдеру за статический IP не хотите, то данное решение вполне себе выход, которое можно в дальнейшем подогнать под свои нужды.




Недавно я решил установить веб-сервер на Rasberry Pi, работающий за моим домашним маршрутизатором. У меня есть опыт настройки веб-серверов на виртуальных машинах Linux со статическими IP-адресами, поэтому в настройке сервера apache2 на Pi нет ничего особенного, и в Интернете есть сотни руководств. Проблема только в том, что при изменении внешнего IP вашего роутера сайт сломается! Один из способов решить эту проблему заплатить своему интернет-провайдеру за статический IP-адрес но тогда в чём веселье?!


Во многих своих последних проектах я использовал Google Domains для покупки доменного имени и обычно запускал сайты без собственных гугловских серверов имен на отдельном VPS/облачном сервере. Но для моего домашнего сервера я решил использовать невероятно простую в настройке функцию переадресации IP-адресов поддоменов, чтобы перенаправить базовый домен (@.example.com) на IP-адрес моего маршрутизатора (а затем настроить мой маршрутизатор на перенаправление с http портов на https порты на Pi). Тогда мне пришлось бы вручную проверять, работает ли сайт, и менять IP при необходимости. Очевидно, это не идеальный план, поэтому я решил написать скрипт для автоматизации этого процесса.


На заметку, вы можете найти полный скрипт на гитхабе или импортировать пакет Python с помощью pip install domains-api

Есть много способов проверить ваш внешний IP-адрес, и один из самых простых, которые я нашел, был через ipify API (api.ipify.org), который просто возвращает ваш общедоступный IP-адрес. При использовании Python библиотеки requests запрос выглядит следующим образом:


IP = requests.get('https://api.ipify.org').text

Вот и все, это ваш внешний IP. Легко, правда? Есть много других подобных способов получить внешний IP-адрес, но я считаю, что это самый простой.


Итак, что еще нам нужно для нашего скрипта? Нам нужно где-то хранить IP-адрес, чтобы проверять будущие запросы. Изначально я решил сохранить его в простом текстовом файле. Нам также нужен способ либо уведомить пользователя, либо обновить domain.google.com, либо и то, и другое. Первый вариант довольно легко реализовать с помощью библиотеки электронной почты для Python, поэтому я настроил её для работы с моей учетной записью Gmail следующим образом (вам нужно будет разрешить менее безопасные приложения (less secure apps) в своей учетной записи Google):


from email.message import EmailMessagedef send_notification(new_ip):    msg = EmailMessage()    msg.set_content(f'IP has changed!\nNew IP: {new_ip}')    msg['Subject'] = 'IP CHANGED!'    msg['From'] = GMAIL_USER    msg['To'] = GMAIL_USER    try:        server = smtplib.SMTP_SSL('smtp.gmail.com', 465)        server.ehlo()        server.login(GMAIL_USER, GMAIL_PASSWORD)        server.send_message(msg)        server.close()        log_msg = 'Email notification sent to %s' % GMAIL_USER)        logging.info(log_msg)    except (MessageError, ConnectionError) as e:        log_msg = 'Something went wrong: %s' % e        logging.warning(log_msg)

Одна вещь, которая немного беспокоит здесь, это сохранение вашего пароля от учетки Gmail в переменной, поэтому я решил на этом этапе хотя бы немного зашифровать его, добавив один уровень кодирования к паролю перед его сохранением. Опять же, есть много способов, которые могут предложить более или менее сносную защиту, но я решил, что для моих целей будет в достаточной мере надежно, если пароль будет закодирован в base64 при сохранении на сервере, поэтому я написал следующие функций: одна для кодирования/сохранения пароля и одна для получения/декодирования сохраненного пароля:


def enc_pwd():    pwd = base64.b64encode(getpass("What's your email password?: ").encode("utf-8"))    with open('cred.txt', 'wb') as f:        f.write(pwd)def read_pwd():    if os.path.isfile(f"{CWD}/cred.txt"):        with open(f'{CWD}/cred.txt', 'r') as f:            if f.read():                password = base64.b64decode(pwd).decode('utf-8')                logging.info('Password read successfully')                return password            else:                    enc_pwd()                read_pwd()    else:        enc_pwd()        read_pwd()

Теперь я мог бы заменить константу GMAIL_PASSWORD из функции уведомления на функцию read_pwd() (которая возвращает декодированный пароль), что всяко безопаснее.


Последнее, что мне нужно, чтобы программа стала эффективной, это дописать функцию, которая свяжет всё вместе и сохранит/сравнит IP-адреса:


def check_ip():    if os.path.isfile('ip.txt'):        # Снова проверим предыдущий IP        with open('ip.txt', 'r') as rf:            line = rf.readlines()            if not line:                first_run = True            elif line[0] == IP:                first_run = False                change = False            else:                first_run = False                change = True    else:        first_run = Trueif first_run or change:        # Запишем новый IP в файл        with open('ip.txt', 'w') as wf:            if first_run:                wf.write(IP)            elif change:                wf.write(IP)                # Уведомим пользователя:                send_notification(IP)    time.sleep(21600)       # в окончательной версии на моем боевом веб-сервере я удалил это 6-часовое ожидание и рекурсивный вызов в конце и просто добавил скрипт в crontab для запуска один раз в час. Мне также пришлось сделать все пути к файлам абсолютными, поскольку по умолчанию для crontab рабочий каталог - '/'.    check_ip()

Бум! Это была первая реализация моей идеи. Когда я получаю уведомление по электронной почте, я могу просто изменить правила переадресации на сайте domains.google.com


Стоп, что? Мне все еще нужно это делать самому? Конечно нет, есть способ проще!


Что ж, оказывается, есть два способа автоматизировать это один значительно менее эффективный, но бесконечно более захватывающий, а другой довольно скучный, но бесконечно более эффективный! Я начну, как и сделал, с захватывающего и сложного способа: слышали когда-нибудь о Selenium для автоматизации тестирования веб-сайтов? Я решил написать сценарий, который будет физически входить в домены Google и перемещаться по сайту, чтобы изменить правила для меня, если и когда изменится IP. Однако это приводило к следующим проблемам:


Прежде всего, Google Domains (и другие сервисы Google, такие как Gmail) довольно хорошо обнаруживают автоматизацию браузера и просто не позволяют вам войти в систему через веб-драйвер Selenium. Мне посчастливилось найти этот ответ (есть и другие решения) в Stack Overflow, который предлагал войти в сам Stack Overflow с вашей учетной записью Google, прежде чем переключаться на желаемую службу Google, что действительно работало, пока я не брал под контроль браузер и при необходимости не вводил капчу. Со скриптом, запущенным на моем ноутбуке или десктопе, это замечательно тем более, что после первоначальной проверки Captcha StackOverflow, похоже, больше не проверял тот же компьютер, поэтому после этого веб-драйвер можно было запустить в автономном режиме, но это не подходит для веб-сервера! Хотя я мог заставить веб-драйвер нормально работать с помощью PyVirtualDisplay и Geckodriver (веб-драйвер для Firefox), ему удавалось только один раз попасть на желаемую страницу Мои домены, а в других случаях вообще не мог войти в систему из-за Captcha.



После того, как я провозился с этим и пытался дебежить/работать над этим полдня, я решил, что это безнадежное дело, так как в любом случае всё, что потребуется, это немного изменить одну из страниц, на которой я просматривал информацию, и вся работа скрипта будет нарушена. Поэтому я решил прибегнуть к скучному методу, который до сих пор я по глупости не изучал очень глубоко.


Так уж получилось, что у Google Domains есть API именно для той цели, которая мне нужна. Я изначально отклонил APi, когда не смог заставить его работать с правилами переадресации поддоменов, которые я использовал, не понимая, что API предназначен для изменения правил динамического DNS эта документация стала намного более понятной после того, как я настроил запись динамического DNS вместо стандартного правила Переадресация поддоменов (спасибо Джереми Гейлу за его отличный урок о том, как сделать что-то подобное с помощью простого сценария bash, который помог мне победить эту проблему!)


Раздел Synthetic records на странице конфигурации DNS Google Domains должен выглядеть следующим образом:


Вот как должна выглядеть ваша запись


Выберите Dynamic DNS вместо Subdomain forward.


Затем вы получите автоматически сгенерированные учетные данные, необходимые для аутентификации с помощью API:


Просто нажмите Просмотреть учетные данные, скопируйте и вставьте их в свой запрос API.


Теперь, зная этот метод, я смог значительно уменьшить длину и нагрузку на скрипт! TTL динамического DNS также очень низок, по умолчанию: 1 мин, что сокращает возможное время простоя ещё один дополнительный бонус. Всё, что мне было нужно, это включить вызов API в мою функцию check_ip(), и он сделает все за меня! И эта единственная строка кода (2 с вызовом логирования) выглядит так:


req = requests.post(f'https://<auto_generated_username>:<auto_generated_password>@domains.google.com/nic/update?hostname=@.example.com&myip={IP}')logging.info(req.content)

Я заключил всё в блок try / except, чтобы перехватить любые исключения, и таким образом этот довольно простой, но удивительно полезный скрипт был почти готов. Теперь осталось только добавить его в crontab на моем веб-сервере и дать ему работать, и (теоретически) мне больше не придется беспокоиться о смене IP!


Стоп! Время рефакторинга:


Так, вот, о чем я позаботился! Чтобы сделать этот скрипт универсально доступным, я реорганизовал всё, чтобы использовать более объектно-ориентированный подход, основанный на классах, с классами User и IpChanger. Класс User содержит все учетные данные для входа в smtp и вызова API domains.google, а первый созданный экземпляр вместе с атрибутом previous_ip, для проверки, сохраняется в одном pickle файле (аналогично сериализации в JavaScript); в будущих экземплярах класса User() пользовательский экземпляр создается из pickle, если не указано иное. Класс User также содержит метод уведомления пользователя по электронной почте, который теперь включает условие ошибки на случай сбоя вызова API. Затем класс IpChanger вызывает класс User() каждый раз, когда он создается, а также выполняет все различные проверки и запросы, поэтому вы просто инициализируете класс IpChanger() и всё; он работает! Я также добавил несколько различных вариантов для уведомлений по электронной почте, более конкретную обработку ошибок/логирование, некоторые аргументы/параметры командной строки для управления профилем пользователя, а также смог избавиться от неуклюжей обработки файла ip.txt, сохранив всё в одном пользовательском экземпляре.


Готовый, практически уникальный (!) результат можно увидеть ниже во всей его полноте, а также его можно форкнуть/клонировать с моего GitHub (включая README.md с инструкциями по установке). Я также выпустил его как пакет Python на pypi.org, который вы можете просмотреть здесь или установить обычным способом с помощью: pip install domains-api


Любые вопросы, отзывы или предложения пишите!




Какие ещё методы стабильной работы веб-сервера без статического адреса вы знаете? И как можно доработать это решение?


Полный скрипт (без file_handlers.py):
import osimport sysimport getoptimport base64import smtplibfrom email.message import EmailMessagefrom getpass import getpassfrom itertools import cyclefrom requests import get, postfrom requests.exceptions import ConnectionError as ReqConErrorfrom domains_api.file_handlers import FileHandlersfh = FileHandlers()def get_ip_only():    """Gets current external IP from ipify.org"""    current_ip = get('https://api.ipify.org').text    return current_ipclass User:    BASE_URL = '@domains.google.com/nic/update?hostname='    def __init__(self):        """Create user instance and save it for future changes to API and for email notifications."""        self.domain, self.dns_username, self.dns_password, self.req_url = self.set_credentials()        self.notifications, self.gmail_address, self.gmail_password = self.set_email()        self.outbox = []    def set_credentials(self):        """Set/return attributes for Google Domains credentials"""        self.domain = input("What's your domain? (example.com / subdomain.example.com): ")        self.dns_username = input("What's your autogenerated dns username?: ")        self.dns_password = getpass("What's your autogenerated dns password?: ")        self.req_url = f'https://{self.dns_username}:{self.dns_password}{self.BASE_URL}{self.domain}'        return self.domain, self.dns_username, self.dns_password, self.req_url    def set_email(self):        """Set/return attributes for Gmail credentials if user enables notifications"""        self.notifications = input("Enable email notifications? [Y]all(default); [e]errors only; [n]no: ").lower()        if self.notifications != 'n':            self.gmail_address = input("What's your email address?: ")            self.gmail_password = base64.b64encode(getpass("What's your email password?: ").encode("utf-8"))            if self.notifications != 'e':                self.notifications = 'Y'            return self.notifications, self.gmail_address, self.gmail_password        else:            return 'n', None, None    def send_notification(self, ip=None, msg_type='success', error=None, outbox_msg=None):        """Notify user via email if IP change is made successfully or if API call fails."""        if self.notifications != 'n':            msg = EmailMessage()            msg['From'] = self.gmail_address            msg['To'] = self.gmail_address            if ip and msg_type == 'success' and self.notifications not in {'n', 'e'}:                msg.set_content(f'IP for {self.domain} has changed! New IP: {ip}')                msg['Subject'] = 'IP CHANGED!'            elif msg_type == 'error' and self.notifications != 'n':                msg.set_content(f"Error with {self.domain}'s IPChanger: ({error})!")                msg['Subject'] = 'IPCHANGER ERROR!'            elif outbox_msg:                msg = outbox_msg            try:                server = smtplib.SMTP_SSL('smtp.gmail.com', 465)                server.ehlo()                server.login(self.gmail_address, base64.b64decode(self.gmail_password).decode('utf-8'))                server.send_message(msg)                server.close()                return True            except Exception as e:                log_msg = 'Email notification not sent: %s' % e                fh.log(log_msg, 'warning')                self.outbox.append(msg)                fh.save_user(self)                sys.exit(1)class IPChanger:    ARG_STRING = 'cdehinu:'    ARG_LIST = ['credentials',                'delete_user',                'email',                'help',                'ip',                'notifications',                'user_load=']    def __init__(self, argv=None):        """Check for command line arguments, load/create User instance,        check previous IP address against current external IP, and change via the API if different."""        # Load old user, or create new one:        if os.path.isfile(fh.user_file):            self.user = fh.load_user(fh.user_file)            fh.log('User loaded from pickle', 'debug')        else:            self.user = User()            fh.log('New user created.\n(See `python -m domains_api --help` for help changing/removing the user)', 'info')        self.current_ip = self.get_set_ip()        # Parse command line options:        try:            opts, _args = getopt.getopt(argv, self.ARG_STRING, self.ARG_LIST)        except getopt.GetoptError:            print('''Usage:python/python3 -m domains_api --help''')            sys.exit(2)        if opts:            self.arg_parse(opts)        # Check IPs:        try:            if self.user.previous_ip == self.current_ip:                log_msg = 'Current IP: %s (no change)' % self.user.previous_ip            else:                self.user.previous_ip = self.current_ip                fh.save_user(self.user)                self.domains_api_call()                log_msg = 'Newly recorded IP: %s' % self.user.previous_ip            fh.log(log_msg, 'info')        except AttributeError:            setattr(self.user, 'previous_ip', self.current_ip)            fh.save_user(self.user)            self.domains_api_call()        finally:            if fh.op_sys == 'pos' and os.geteuid() == 0:                fh.set_permissions(fh.user_file)            # Send outbox emails:            if self.user.outbox:                for i in range(len(self.user.outbox)):                    self.user.send_notification(outbox_msg=self.user.outbox.pop(i))                    fh.log('Outbox message sent', 'info')                fh.save_user(self.user)            fh.clear_logs()    def get_set_ip(self):        """Gets current external IP from api.ipify.org and sets self.current_ip"""        try:            return get_ip_only()        except (ReqConError, ConnectionError) as e:            fh.log('Connection Error. Could not reach api.ipify.org', 'warning')            self.user.send_notification(msg_type='error', error=e)    def domains_api_call(self):        """Attempt to change the Dynamic DNS rules via the Google Domains API and handle response codes"""        try:            req = post(f'{self.user.req_url}&myip={self.current_ip}')            response = req.text            log_msg = 'Google Domains API response: %s' % response            fh.log(log_msg, 'info')            # Successful request:            _response = response.split(' ')            if 'good' in _response or 'nochg' in _response:                self.user.send_notification(self.current_ip)            # Unsuccessful requests:            elif response in {'nohost', 'notfqdn'}:                msg = "The hostname does not exist, is not a fully qualified domain" \                      " or does not have Dynamic DNS enabled. The script will not be " \                      "able to run until you fix this. See https://support.google.com/domains/answer/6147083?hl=en-CA" \                      " for API documentation"                fh.log(msg, 'warning')                if input("Recreate the API profile? (Y/n):").lower() != 'n':                    self.user.set_credentials()                    self.domains_api_call()                else:                    self.user.send_notification(self.current_ip, 'error', msg)            else:                fh.log("Could not authenticate with these credentials", 'warning')                if input("Recreate the API profile? (Y/n):").lower() != 'n':                    self.user.set_credentials()                    self.domains_api_call()                else:                    fh.delete_user()                    fh.log('API authentication failed, user profile deleted', 'warning')                    sys.exit(1)        # Local connection related errors        except (ConnectionError, ReqConError) as e:            log_msg = 'Connection Error: %s' % e            fh.log(log_msg, 'warning')            self.user.send_notification(msg_type='error', error=e)    def arg_parse(self, opts):        """Parses command line options: e.g. "python -m domains_api --help" """        for opt, arg in opts:            if opt in {'-i', '--ip'}:                print('''            [Domains API] Current external IP: %s                ''' % get_ip_only())            elif opt in {'-h', '--help'}:                print(                    """        domains-api help manual (command line options):

    You will need your autogenerated Dynamic DNS keys from    https://domains.google.com/registrar/example.com/dns    to create a user profile.    python -m domains_api                    || -run the script normally without arguments    python -m domains_api -h --help          || -show this help manual    python -m domains_api -i --ip            || -show current external IP address    python -m domains_api -c --credentials   || -change API credentials    python -m domains_api -e --email         || -email set up wizard > use to delete email credentials (choose 'n')    python -m domains_api -n --notifications || -toggle email notification settings > will not delete email address    python -m domains_api -u user.file       || (or "--user_load path/to/user.file") -load user from pickle file    python -m domains_api -d --delete_user   || -delete current user profile                                             || User files are stored in "/var/www/domains_api/domains.user""""            )        elif opt in {'-c', '--credentials'}:            self.user.set_credentials(update=True)            self.domains_api_call()            fh.save_user(self.user)            fh.log('API credentials changed', 'info')        elif opt in {'-d', '--delete'}:            fh.delete_user()            fh.log('User deleted', 'info')            print('>>>Run the script without options to create a new user, or '                  '"python3 -m domains_api -u path/to/pickle" to load one from file')        elif opt in {'-e', '--email'}:            self.user.set_email()            fh.save_user(self.user)            fh.log('Notification settings changed', 'info')        elif opt in {'-n', '--notifications'}:            n_options = {'Y': '[all changes]', 'e': '[errors only]', 'n': '[none]'}            options_iter = cycle(n_options.keys())            for option in options_iter:                if self.user.notifications == option:                    break            self.user.notifications = next(options_iter)            fh.save_user(self.user)            log_msg = 'Notification settings changed to %s' % n_options[self.user.notifications]            fh.log(log_msg, 'info')            if self.user.notifications in {'Y', 'e'} and not self.user.gmail_address:                fh.log('No email user set, running email set up wizard...', 'info')                self.user.set_email()                fh.save_user(self.user)        elif opt in {'-u', '--user_load'}:            try:                self.user = fh.load_user(arg)                fh.save_user(self.user)                fh.log('User loaded', 'info')            except FileNotFoundError as e:                fh.log(e, 'warning')                sys.exit(2)        sys.exit()

if name == "main":
IPChanger(sys.argv[1:])


<!--</spoiler>-->
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru