imported>Vix |
imported>Vix |
Строка 1: |
Строка 1: |
| * '''Часть 1'''
| | ''Автор: Уваров А.С. 29.01.2016''<br> |
| <hr>
| | [[Файл:Vsftpd-virtual-user-000.jpg]]<br> |
| Идеальная настройка вашего Golangпроекта
| | Протокол FTP, несмотря на преклонный возраст и серьезные недостатки продолжает широко применяться, во многом благодаря простоте и поддержке со стороны самого широкого спектра устройств и ПО. FTP-сервер является неотъемлемой частью любого веб-сервера, применяется в системах автоматизированного обмена данными. Наиболее часто встающей перед администраторами таких систем задачей является создание сервера с виртуальными пользователями. Сегодня мы расскажем, как это сделать. |
| | |
| [https://yandex.ru/turbo/nuancesprog.ru/s/p/6062/?parent-reqid=1604670340041351-275217593417438871900275-prestable-app-host-sas-web-yp-138&utm_source=turbo_turbo Часть 2], [https://nuancesprog.ru/?p=6340 Часть 3]
| |
| | |
| При запуске нового проекта самые большие трудности у меня всегда вызывала его настройка. Всегда стараешься сделать её «идеальной»:
| |
| * используешь лучшую структуру каталогов, чтобы всё было легко найти и импортирование происходило без проблем;
| |
| * настраиваешь все команды так, чтобы нужные действия выполнялись в один клик или с вводом одной команды;
| |
| * находишь лучший инструмент контроля качества кода, средство форматирования, среду тестирования для используемого в проекте языка и библиотеки…
| |
| | |
| Этот список можно продолжать и продолжать, и всё равно до идеальной настройки будет ещё далеко… Но, по моему скромному мнению, эта настройка для Golang просто лучшая!
| |
| | |
| Она так хорошо себя проявляет отчасти и потому, что основана на существующих проектах, которые вы можете найти здесь и тут.
| |
| | |
| Краткое изложение доступно в моём репозитории — https://github.com/MartinHeinz/go-project-blueprint
| |
| Структура каталогов
| |
| | |
| Первым делом обратимся к структуре каталогов нашего проекта. Здесь у нас несколько файлов верхнего уровня и четыре каталога:
| |
| | |
| '''pkg''' — это пакет Go, который содержит только строку версии global. Меняется на версию из хэша текущей фиксации при проведении сборки;
| |
| '''config''' — конфигурационный каталог, который содержит файлы со всеми необходимыми переменными среды. Вы можете использовать любой тип файла, но я бы рекомендовал файлы YAML: их проще читать;
| |
| '''build''' — в этой директории у нас все скрипты оболочки, необходимые для сборки и тестирования приложения, а также создания отчётов для инструментов анализа кода;
| |
| '''cmd '''— фактический исходный код. По правилам именования исходный каталог называется cmd. Внутри есть ещё один каталог с именем проекта (в нашем случае blueprint). В свою очередь, внутри этого каталога находится main.go, запускающий всё приложение. Также здесь можно найти все остальные исходные файлы, разделённые на модули (подробнее об этом далее).
| |
| | |
| Оказывается, многие предпочитают помещать исходный код в каталоги internal и pkg. Я думаю, что это лишнее: достаточно использовать для этого cmd, где для всего есть своё место.
| |
| | |
| Помимо каталогов, есть ещё большое количество файлов, о которых мы поговорим в статье.
| |
| Модули Go для идеального управления зависимостями
| |
| | |
| В проектах Go используются самые разные стратегии управления зависимостями. Однако с версии 1.11 Go обзавёлся официальным решением. Все наши зависимости приводятся в файле go.mod, в корневом каталоге. Вот как он может выглядеть:
| |
| | |
| module github.com/MartinHeinz/go-project-blueprint
| |
|
| |
| go 1.12
| |
|
| |
| require (
| |
| github.com/spf13/viper v1.4.0
| |
| github.com/stretchr/testify v1.4.0
| |
| )
| |
| | |
| Вы можете спросить: «А как в этот файл включить зависимости?». Очень просто, всего одной командой:
| |
| | |
| go mod vendor
| |
| | |
| Эта команда переустанавливает vendor каталог основного модуля для включения всех пакетов, необходимых для сборки и тестирования каждого пакета модуля исходя из состояния файлов go.mod и исходного кода Go.
| |
| Фактический исходный код и конфигурация
| |
| | |
| И вот наконец мы добрались до исходного кода. Как уже говорилось, исходный код разделён на модули. Модуль представляет собой каталог внутри исходного корневого каталога. В каждом модуле находятся исходные файлы вместе с соответствующими файлами тестов. Например:
| |
| | |
| ./cmd/
| |
| └── blueprint
| |
| ├── apis <- Module
| |
| │ ├── apis_test.go
| |
| │ ├── user.go
| |
| │ └── user_test.go
| |
| ├── daos <- Module
| |
| │ ├── user.go
| |
| │ └── user_test.go
| |
| ├── services <- Module
| |
| │ ├── user.go
| |
| │ └── user_test.go
| |
| ├── config <- Module
| |
| │ └── config.go
| |
| └── main.go
| |
| | |
| Такая структура способствует лучшей читаемости и лёгкости сопровождения кода: он идеально разделён на части, которые проще просматривать. Что касается конфигурации, в этой настройке используем библиотеку конфигураций Go Viper, которая может иметь дело с разными форматами, параметрами командной строки, переменными среды и т.д.
| |
| | |
| Посмотрим, как мы используем этот Viper здесь. Вот пакет config:
| |
| | |
| var Config appConfig
| |
|
| |
| type appConfig struct {
| |
| // Пример переменной, загружаемой в функции LoadConfig
| |
| ConfigVar string
| |
| }
| |
|
| |
| // LoadConfig загружает конфигурацию из файлов
| |
| func LoadConfig(configPaths ...string) error {
| |
| v := viper.New()
| |
| v.SetConfigName("example") // <- имя конфигурационного файла
| |
| v.SetConfigType("yaml")
| |
| v.SetEnvPrefix("blueprint")
| |
| v.AutomaticEnv()
| |
| for _, path := range configPaths {
| |
| v.AddConfigPath(path) // <- // путь для поиска конфигурационного файла в
| |
| }
| |
| if err := v.ReadInConfig(); err != nil {
| |
| return fmt.Errorf("failed to read the configuration file: %s", err)
| |
| }
| |
| return v.Unmarshal(&Config)
| |
| }
| |
| | |
| Он состоит из единственного файла. Объявляет один struct, который содержит все переменные конфигурации и имеет одну функцию LoadConfig, которая загружает конфигурацию. Требуется путь до конфигурационных файлов, в нашем случае используем путь до каталога config, который находится в корневом каталоге проекта и содержит наши YAML файлы. И как их будем использовать? Запустим первым делом в main.go:
| |
| | |
| if err := config.LoadConfig("./config"); err != nil {
| |
| panic(fmt.Errorf("invalid application configuration: %s", err))
| |
| }
| |
| | |
| Простое и быстрое тестирование
| |
| | |
| Что важнее всего после кода? Тесты. Чтобы писать много хороших тестов, нужна настройка, с которой это будет делать легко. Для этого мы используем цель Makefile под названием test, которая собирает и выполняет все тесты в подкаталогах cmd (все файлы с расширением _test.go). Эти тесты кэшируются, так что их запуск происходит только при наличии изменений в соответствующей части кода. Это очень важно: если тесты будут слишком медленными, рано или поздно вы перестанете их запускать и сопровождать. Помимо модульного тестирования, make test помогает следить за общим качеством кода, запуская с каждым тестовым прогоном gofmt и go vet. gofmt способствует правильному форматированию кода, а go vet помогает с помощью эвристических алгоритмов выявлять в коде любые подозрительные конструкции. Вот пример того, что может получиться в результате выполнения:
| |
| | |
| foo@bar:~$ make test
| |
| Running tests:
| |
| ok github.com/MartinHeinz/go-project-blueprint/cmd/blueprint (cached)
| |
| ? github.com/MartinHeinz/go-project-blueprint/cmd/blueprint/config [no test files]
| |
| ? github.com/MartinHeinz/go-project-blueprint/pkg [no test files]
| |
|
| |
| Checking gofmt: FAIL - the following files need to be gofmt'ed:
| |
| cmd/blueprint/main.go
| |
|
| |
| Checking go vet: FAIL
| |
| # github.com/MartinHeinz/go-project-blueprint/cmd/blueprint
| |
| cmd/blueprint/main.go:19:7: assignment copies lock value to l: sync.Mutex
| |
|
| |
| Makefile:157: recipe for target 'test' failed
| |
| make: *** [test] Error 1
| |
|
| |
| Запуск всегда в Docker
| |
|
| |
| Многие говорят, что у них запуск невозможен в облаке, а только на компьютере. Здесь есть простое решение: всегда запускаться в контейнере docker. Делаете ли вы сборку, запускаете ли или тестируете — делайте всё это в контейнере. Кстати, что касается тестирования, make test выполняется тоже только в docker.
| |
| | |
| Посмотрим, как это происходит. Начнём с файлов Dockerfile из корневого каталога проекта: один из них для тестирования (test.Dockerfile), а другой — для запуска приложения (in.Dockerfile):
| |
| | |
| test.Dockerfile — в идеале нам было бы достаточно одного файла Dockerfile для запуска приложения и тестирования. Но во время тестовых прогонов нам может потребоваться внести небольшие изменения в среде выполнения, поэтому у нас здесь есть образ для установки дополнительных инструментов и библиотек. Предположим, например, что мы подключаемся к базе данных. Нам не нужно поднимать весь PostgreSQL-сервер при каждом тестовом прогоне или зависеть от какой-нибудь базы данных на хост-машине. Мы просто используем для тестовых прогонов базу данных в памяти SQLite. И если дополнительные установки не понадобятся нашим тестам, то двоичным данным в SQLite они будут очень даже кстати: устанавливаем gcc и g++, переключаем флажок на CGO_ENABLED, и готово.
| |
| in.Dockerfile — если посмотреть на этот Dockerfile в репозитории, что мы увидим: просто несколько аргументов и копирование конфигурации в образ. Но что здесь происходит? in.Dockerfile используется только из Makefile (заполненного аргументами), когда мы запускаем make container. Давайте теперь обратимся в сам Makefile, Всё, что связано с docker, делает для нас именно он. 👇
| |
| | |
| Связываем всё вместе с помощью Makefile
| |
| | |
| Долгое время Make-файлы казались мне страшными (до этого я сталкивался с ними лишь при работе с кодом C), но на самом деле ничего страшного здесь нет, и их много где можно использовать, в том числе для этого проекта! Посмотрим, какие цели у нас здесь есть в Makefile:
| |
| | |
| make test — первая в рабочем потоке — собранное приложение — создаёт исполняемый двоичный код в каталоге bin:
| |
|
| |
| @echo "making $(OUTBIN)"
| |
| @docker run \ # <- Это `докерный запуск`
| |
| -i \ # скрытая команда
| |
| --rm \ # <- Удаляем контейнер по завершении
| |
| -u $$(id -u):$$(id -g) \ # <- Используем текущего пользователя
| |
| -v $$(pwd):/src \ # <- Подключаем исходную папку
| |
| -w /src \ # <- Устанавливаем рабочий каталог
| |
| -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin \ # <- Подключаем каталоги
| |
| -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin/$(OS)_$(ARCH) \ # с выводом двоичных данных
| |
| -v $$(pwd)/.go/cache:/.cache \
| |
| --env HTTP_PROXY=$(HTTP_PROXY) \
| |
| --env HTTPS_PROXY=$(HTTPS_PROXY) \
| |
| $(BUILD_IMAGE) \
| |
| /bin/sh -c " \ # <- Запускаем скрипт сборки
| |
| ARCH=$(ARCH) \ # (Проверяет на наличие
| |
| OS=$(OS) \ # аргументов, устанавливает
| |
| VERSION=$(VERSION) \ # переменные среды и запускает
| |
| ./build/build.sh \ # `go install`)
| |
| "
| |
| @if ! cmp -s .go/$(OUTBIN) $(OUTBIN); then \ # <- Если двоичные данные изменились
| |
| mv .go/$(OUTBIN) $(OUTBIN); \ # перемещаем их из `.go` в `bin`
| |
| date >$@; \
| |
| fi
| |
| | |
| make test — тестовая — она снова использует почти тот же docker run, только здесь ещё есть скрипт test.sh (покажем только то, что нас интересует):
| |
|
| |
| TARGETS=$(for d in "$@"; do echo ./$d/...; done)
| |
|
| |
| go test -installsuffix "static" ${TARGETS} 2>&1
| |
|
| |
| ERRS=$(find "$@" -type f -name \*.go | xargs gofmt -l 2>&1 || true)
| |
|
| |
| ERRS=$(go vet ${TARGETS} 2>&1 || true)
| |
|
| |
| Эти строчки — важная часть файла. Первая из них собирает тестовые цели, где в качестве параметра указан путь. Вторая строчка запускает тесты и выводит документацию по тестированию ПО. Оставшиеся две строчки запускают gofmt и go vet. Они собирают и выводят ошибки (если таковые имеются).
| |
| | |
| '''make container''' — и, наконец, важнейшая часть — создание развёртываемого контейнера:
| |
| | |
| .container-$(DOTFILE_IMAGE): bin/$(OS)_$(ARCH)/$(BIN) in.Dockerfile
| |
| @sed \
| |
| -e 's|{ARG_BIN}|$(BIN)|g' \
| |
| -e 's|{ARG_ARCH}|$(ARCH)|g' \
| |
| -e 's|{ARG_OS}|$(OS)|g' \
| |
| -e 's|{ARG_FROM}|$(BASEIMAGE)|g' \
| |
| in.Dockerfile > .dockerfile-$(OS)_$(ARCH)
| |
| @docker build -t $(IMAGE):$(TAG) -t $(IMAGE):latest -f .dockerfile-$(OS)_$(ARCH) .
| |
| @docker images -q $(IMAGE):$(TAG) > $@
| |
| | |
| Код для этой цели довольно прост: сначала он подставляет переменные в in.Dockerfile, а затем запускает docker build для получения образа с «изменёнными» и «последними» тегами. И дальше передаёт имя контейнера на стандартный вывод.
| |
| | |
| Теперь, когда у нас есть образ, нужно где-то его хранить. С этим нам помогает make push, который помещает образ в хранилище образов Docker registry.
| |
| '''make ci''' — ещё один способ использовать Makefile — задействовать его в процессах непрерывной интеграции и развёртывания приложений (об этом речь пойдёт далее). Эта цель очень похожа на make test: тоже запускает все тесты и плюс к этому генерирует отчёты о покрытии, которые потом используются как вводная информация при проведении анализа кода.
| |
| make clean — и, наконец, если нам нужно провести очистку проекта, запускаем make clean, который удалит все файлы, сгенерированные предыдущими целями.
| |
| | |
| Остальные цели можно объединить в две группы: первые не так важны для нормального рабочего процесса, а вторые являются лишь частью других целей, поэтому о них можно не упоминать.
| |
| Интеграция и развёртывание ПО для идеальной разработки
| |
| | |
| Завершаем статью важной частью — процессом непрерывной интеграции и развёртывания приложений. Не буду подробно расписывать, что в нем такого — вы и сами прекрасно сможете разобраться (практически в каждой строке есть комментарий, так что всё должно быть понятно):
| |
| | |
| # Matrix build запускает 4 параллельные сборки
| |
| matrix:
| |
| include:
| |
| - language: go # Сборка и тестирование sudo: required
| |
| services:
| |
| - docker
| |
| script:
| |
| - export GO111MODULE=on
| |
| - go mod vendor # Загружаем зависимости - make build # Собираем приложение
| |
| - test -f bin/linux_amd64/blueprint # Тест на наличие двоичных данных, полученных на предыдущем этапе
| |
| - make all-container # Создаём все докерные контейнеры - docker images | grep "^docker.pkg.github.com/martinheinz/go-project-blueprint /blueprint.*__linux_amd64" # Проверяем наличие созданных образов
| |
| - make test # Запускает тесты внутри тестового образа
| |
|
| |
| - language: go # SonarCloud
| |
| addons:
| |
| sonarcloud:
| |
| organization: martinheinz-github token:
| |
| secure:
| |
| "tYsUxue9kLZWb+Y8kwU28j2sa0pq20z2ZvZrbKCN7Sw0WGtODQLaK9tZ94u1Sy02qL5QcabukbENmbvfouzXf4EfaKjDmYH9+Ja22X26MfTLVpaCDTQEGmNyREOFCHpjNXPgDMv1C70By5U+aPWSYF/lehB5rFijwCf7rmTFRNUDeotCTCuWb2dIkrX2i6raVu34SvqqGxKQmmH+NPLe7uKO/wXqH+cWQH1P9oJYeVksNGruw4M0MznUeQHeJQYpTLooxhEEzYiBbkerWGDMwBdZdPQwVrO2b8FEDRw/GWTFoL+FkdVMl4n4lrbO/cQLbPMTGcfupNCuVHh1n8cGp8spMkrfQGtKqvDRuz2tBs0n1PWXCRS6pgZQw/ClLPgi/vVryVRwOabIHSQQLRVhcdp8pkYdyX3aH1EdlIHiJLT6sacS0vJPqZMF/HNsPEoHe4YdiYvx/tcYMU63KQVZzgF4HfQMWy69s1d0RZUqd+wrtHU1DHwnkq1TSe+8nMlbvbmMsm6FVqGistrnVjx4C9TjDWQcjprYU40zCvc1uvoSPimVcaD8ITalCDHlEfoV7wZuisV8+gJzOh9pDZ/joohW7/P3zklGgI2sH7qt62GE4o5UyRArzJC7eIj7Oxx6GdbeEqw09M4rCfR1g5tHWIqVHz5CajvkXkPqrRGu2oI="
| |
| before_script:
| |
| - ./reports.sh # Создаёт каталоги и файлы для отчётов - export GO111MODULE=on
| |
| - go mod vendor # Загружаем зависимости - make ci # Запускаем тесты и генерируем отчёты (см. этап `ci` в Makefile)
| |
| script:
| |
| - sonar-scanner # Запускаем анализ с помощью плагина SonarCloud scanner plugin
| |
|
| |
| - language: go # CodeClimate before_script:
| |
| - ./reports.sh # Создаём каталоги и файлы для отчётов - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter # Загружаем генератор тестовых отчётов CodeClimate
| |
| - chmod +x ./cc-test-reporter # Делаем его исполняемым - ./cc-test-reporter before-build # Уведомляем CodeClimate о готовом отчёте
| |
| script:
| |
| - export GO111MODULE=on
| |
| - go mod vendor # Загружаем зависимости - make ci # Запускаем тесты и генерируем отчёты (см. этап `ci` в Makefile)
| |
| after_script:
| |
| - ./cc-test-reporter after-build -t gocov --exit-code $TRAVIS_TEST_RESULT # Отправляем отчёт в CodeClimate или уведомляем о неудавшейся сборке кодом завершения
| |
|
| |
| - language: go # Помещаем контейнер в хранилище services:
| |
| - docker
| |
| if: branch = master
| |
| script:
| |
| - export GO111MODULE=on
| |
| - go mod vendor # Загружаем зависимости - echo "$DOCKER_PASSWORD" | docker login docker.pkg.github.com -u "$DOCKER_USERNAME" --password-stdin # Подключаемся ко хранилищу GitHub Registry, используя переменные среды Travis
| |
| - make container # Создаём изменённые и последние образы - make push # Помещаем образ в хранилище
| |
|
| |
| notifications:
| |
| email: false
| |
| | |
| Но кое-что можно прояснить.
| |
|
| |
| В этой сборке Travis использована сборка Matrix Build с 4 параллельными заданиями для ускорения всего процесса:
| |
| | |
| Сборка и тестирование: здесь мы проверяем, что приложение работает как надо;
| |
| SonarCloud: здесь мы генерируем отчёты о покрытии и отправляем их на сервер SonarCloud;
| |
| CodeClimate: здесь — как и в предыдущем задании — мы генерируем отчёты о покрытии и отправляем их, только на этот раз в CodeClimate с помощью их генератора тестовых отчётов;
| |
| Push to Registry: и, наконец, помещаем наш контейнер в хранилище GitHub Registry.
| |
| | |
| Заключение
| |
| | |
| Надеюсь, эта статья поможет вам в ваших будущих разработках кода на Go. Все подробности изложены в репозитории.
| |
| | |
| В следующей части узнаем, как на базе этого макета проекта, который мы сегодня выстроили, с лёгкостью создавать интерфейсы RESTful API, тестировать с базой данных в памяти, а также настраивать крутую документацию (а пока можно подсмотреть в ветке rest-api репозитория). 🙂
| |
| | |
| Перевод статьи Martin Heinz: Ultimate Setup for Your Next Golang Project (впервые опубликована на martinheinz.dev).
| |
| Войдите, чтобы отправлять и оценивать комментарии
| |
| Расскажите, что вы об этом думаете?
| |
| Магические методы в Python
| |
| 7 марта 2020
| |
| Создание объекта dict, принимающего только целые и плавающие числа в качестве значений
| |
| | |
| В этом сценарии мы реализуем класс, который создает объекты-словари, принимающие только целые и плавающие значения.
| |
| | |
| При добавлении других типов данных, таких как строки, списки и кортежи, будет появляться исключение, указывающее пользователю, что пользовательский объект dict может принимать только целые и плавающие числа в качестве значений.
| |
| | |
| Для реализации этого процесса используются следующие методы:
| |
| __int__, __setitem__ и __str __
| |
| | |
| Для начала создаем пользовательский класс CustomIntFloat и передаем dict в список наследования аргументов. Это означает, что созданный объект будет вести себя как словарь, за исключением тех мест, в которых это поведение будет выборочно изменено.
| |
| | |
| Затем создаем метод __init__, чтобы сконструировать объект dict CustomIntFloat, который принимает ключ и значение в список аргументов, установленный в тип None по умолчанию. Таким образом, если пользователь создает объект класса CustomIntFloat без передачи ключа или значения, будет сгенерирован пустой dict. Данное условие гласит: если ключ не передан, то параметру ключа по умолчанию присваивается аргумент None, а пустой dict создается путем ссылки на объект CustomIntFloat с атрибутом класса empty_dict.
| |
| | |
| Если пользователь указывает ключ length и соответствующее значение, которое является экземпляром класса int или float, то ключ и значение будут установлены в объекте.
| |
|
| |
|
| Наконец, если пользователь указывает несколько ключей и значений в качестве итерируемых в операторе else, то они будут заархивированы функцией zip и им будет присвоено имя переменной zipped. Выполняем цикл на zipped, чтобы проверить, имеет ли значение тип int или float. Если нет, то будет сгенерировано пользовательское исключение CustomIntFloatError.
| | Про настройку FTP-сервера на базе vsftpd мы уже рассказывали на страницах нашего сайта, недавно существенно дополнив и обновив статью Простой FTP-сервер на базе Ubuntu (vsftpd). В ней мы рассказали каким образом можно ограничить список пользователей, имеющих возможность подключаться к серверу, и их права. Однако локальные пользователи остаются при этом локальными пользователями и несмотря на ограниченные права могут оказаться объектом атаки, направленной на повышение прав, например, через уязвимости в каком-либо ПО. |
| Класс исключения CustomIntFloatError и метод __str__
| |
|
| |
|
| При генерации исключения CustomIntFloatError создается экземпляр класса CustomIntFloatError.
| | Кроме того, ручное редактирование списков и настроек пользователей может оказаться весьма трудоемким делом если их не один-два, а существенно больше. Также иногда возникает ситуация, что светить данные реальных пользователей просто нежелательно, например, FTP используется движком сайта (CMS), связка пользователь-пароль явно прописана в конфиге и теоретически доступна любому работающему с сайтом сотруднику. |
|
| |
|
| Таким образом, этот пользовательский класс исключений нуждается в помощи magic-методов __init__ и __str__. Созданный экземпляр принимает переданное значение и устанавливает его в качестве значения атрибута в классе CustomIntFloatError.
| | В таких ситуациях следует использовать виртуального пользователя, который будет иметь доступ исключительно к данному FTP-ресурсу и учетные данные которого можно смело сообщить всем принимающих участие в работе реальным сотрудникам. |
|
| |
|
| Это означает, что при появлении сообщения об ошибке значение, переданное в __init__ объекта CustomIntFloat, может быть установлено как атрибут (self.value) в классе CustomIntFloatError и с легкостью проверено.
| | В нашей статье мы настроим FTP-сервер для реализации следующей схемы: |
|
| |
|
| Если ввод неверный, то появляется исключение CustomIntFloatError, а объект не создается. Сообщение об ошибке информирует пользователя о том, что допустимыми являются только целые и плавающие значения.
| | [[vsftpd-virtual-user-001.jpg]]<br> |
| | Пусть в нашем распоряжении имеется некий веб-сервер под управлением которого находятся три сайта: blue.lab, green.lab и red.lab, физически расположенные в одноименных директориях. Наша задача создать FTP-сервер с виртуальными пользователями, каждый из которых имеет доступ только к своему сайту. |
|
| |
|
| Аналогичным образом при попытке создать экземпляр объекта z (который был закомментирован) с несколькими ключами и значениями, возникает то же исключение, информирующее пользователя о том, что ‘three’ не является допустимым вводом.
| | Важное замечание, в данной схеме все сайты должны работать от имени веб-сервера (www-data), если вы используете запуск каждого сайта от имени своего (реального) пользователя, то вам лучше настроить FTP-сервер для работы с реальными пользователями или дополнительно обеспечить нужные права доступа для виртуальных пользователей, что может представлять из себя нетривиальную задачу. |
|
| |
|
| # z = CustomIntFloat(key=['a', 'b', 'c'], value=[1, 2, 'three'])
| | Прежде всего установим соглашение: имя виртуального пользователя должно совпадать и именем корневой FTP-папки, в нашем случае папки сайта, а все такие папки должны располагаться в одном месте, т.е. подпадать под шаблон /var/www/$USER. Это позволит в дальнейшем с легкостью манипулировать сайтами и виртуальными пользователями, а также избежать путаницы в именах и настройках. |
| z = CustomIntFloat(key=['a', 'b', 'c'], value=[1, 2, 3])
| |
|
| |
|
| Метод __setitem__
| | Определившись с моделью размещения данных можно переходить к настройкам ПО. Кроме vsftpd нам понадобятся пакеты libpam-pwdfile и apache2-utils. Последний входит в состав одноименного веб-сервера и если у вас уже работает Apache, то устанавливать его не надо. |
|
| |
| __setitem__ — это magic-метод, который вызывается при установке ключа и значения в словаре. Если после создания объекта CustomIntFloat пользователь попытается добавить значение, которое не относится к типу int или float, появится то же исключение CustomIntFloatError. Ниже показано, как установить ключ и значение:
| |
|
| |
|
| x = CustomIntFloat('a', 1)
| | Сама установка предельно проста: |
| print(type(x))
| |
|
| |
| x['b'] = 2.1
| |
| print(x)
| |
| # x['c'] = 'Three'
| |
|
| |
|
| В результате недопустимого ввода возникает исключение CustomIntFloatError:
| | apt-get install vsftpd libpam-pwdfile apache2-utils |
| | После чего перейдем к настройкам FTP-сервера, которые расположены в файле /etc/vsftpd.conf, мы перечислим опции в порядке их нахождения в файле. |
|
| |
|
| Исходный код:
| | Данная опция запускает FTP-сервер как службу |
|
| |
|
| class CustomIntFloatError(Exception): | | listen=YES |
| def __init__(self, value):
| | ниже имеется аналогичная взаимоисключающая опция: |
| self.value = value
| |
|
| |
|
| def __str__(self):
| | listen_ipv6=NO |
| return self.value + ' is not valid\nOnly Integers and floats are valid values \nin CustomIntFloat(dict) '
| | Первая из них обеспечивает поддержку IPv4, вторая сразу IPv6 и IPv4, поэтому должна быть включена только одна из них. |
|
| |
|
| |
| class CustomIntFloat(dict):
| |
|
| |
|
| empty_dict = {}
| | Запрещаем анонимных пользователей: |
|
| |
|
| def __init__(self, key=None, value=None):
| | anonymous_enable=NO |
| if key is None:
| | И разрешаем локальных (и виртуальных) пользователей: |
| CustomIntFloat.empty_dict = {}
| |
|
| |
|
| elif len(key) == 1 and isinstance(value, (int, float)):
| | local_enable=YES |
| dict.__setitem__(self, key, value)
| | Также разрешаем запись: |
| else:
| |
| zipped = zip(key, value)
| |
| for tup in zipped:
| |
| if isinstance(tup[1], (int, float)):
| |
| dict.__setitem__(self, tup[0], tup[1])
| |
| else:
| |
| raise CustomIntFloatError(tup[1])
| |
|
| |
|
| def __setitem__(self, key, value):
| | write_enable=YES |
| if not isinstance(value, (int, float)):
| | и задаем маску для вновь создаваемых файлов и папок: |
| raise CustomIntFloatError(value)
| |
| return dict.__setitem__(self, key, value)
| |
|
| |
|
| Обзор класса CustomIntFloat
| | local_umask=022 |
| | Это обеспечит установку прав 755 на папки и 644 на файлы, т.е. полный доступ только владельцу и чтение для группы и остальных. |
|
| |
|
| С помощью наследования через такие встроенные классы, как dict, можно настраивать поведение через повторную реализацию magic-методов. У этого подхода есть множество преимуществ.
| | Чтобы FTP использовал локальное время сервера, а не GMT установим: |
|
| |
|
| Стоит отметить, что пользователю не нужно изучать новый синтаксис. Он может добавить ключ и значение к объекту dict CustomIntFloat привычным образом. Единственным отличием является выбор допустимых значений типа int и float. Если пользователь указывает любой другой тип, то сообщение об ошибке информирует его об этом и указывает допустимые типы значений.
| | use_localtime=YES |
| Сразу несколько методов
| | И включим лог загружаемых и скачиваемых файлов: |
| Примеры с использованием математических операторов
| |
| __sub__, __add__ и __mul__ (с пользовательским __repr__)
| |
|
| |
|
| С помощью magic-методов можно также воспользоваться математическими операторами в Python. Рассмотрим на примере таких методов, как __add__, __sub__ и __mul__ в созданном нами пользовательском объекте.
| | xferlog_enable=YES |
| | Разрешаем передачу данных через порт 20, требуется для лучшей совместимости с некоторыми клиентами: |
|
| |
|
| Такие операторы, как +, -, / и *, являются полиморфными методами. Как показано ниже, знак плюса (+) является полиморфным и может использоваться для объединения строк, суммирования целых чисел и комбинирования списков. Это возможно благодаря тому, что такие типы, как str, list и int, обладают методом add в соответствующих классах. Python просто преобразует знак + в вызов метода __add__ для объекта, который его вызвал (см. примеры ниже).
| | connect_from_port_20=YES |
| | Задаем путь к логу и его формат: |
|
| |
|
| Это означает, что при включении метода __add__ в класс можно воспользоваться знаком + в объектах.
| | xferlog_file=/var/log/vsftpd.log |
| Применение magic-методов оператора в классе
| | xferlog_std_format=YES |
| | Укажем таймауты: |
|
| |
|
| Создаем класс NumOperations, который генерирует объекты NumOperations. Когда пользователь этого класса передает список в список аргументов __init__, он устанавливается в качестве атрибута в объекте NumOperations и получает название .math_list.
| | idle_session_timeout=600 |
| | data_connection_timeout=120 |
| | Первый задает время отключения бездействующего пользователя, а второй время ожидания возобновления неоконченной передачи. |
|
| |
|
| После создания объекта(ов) NumOperations можно с легкостью использовать magic-методы для работы с ними и передачи математической операции.
| | Для корректной работы с текстовыми данными, в частности с символами переноса строки в разных ОС (CR+LF в Windows, LF в Linux) включим поддержку ASCII, это полезно, если вы скачиваете скрипты или файлы с Linux сервера, правите их в среде Windows и заливаете обратно. В этом случае FTP автоматически будет менять символы переноса строки для соответствия стандартам системы. |
|
| |
|
| Например, magic-метод __sub__ принимает 2 объекта NumOperations, объединяет их списки и просматривает другие соответствующие им списки кортежей. Второй элемент в кортеже вычитается из первого, и это значение добавляется в новый список minuslst и передается в качестве аргумента в конструктор NumOperations.
| | ascii_upload_enable=YES |
| | ascii_download_enable=YES |
| | Обязательно изолируем пользователя в корне его домашней директории и рядом впишем опцию, разрешающую запись в корень: |
|
| |
|
| Теперь он возвращает новый объект NumOperations.
| | chroot_local_user=YES |
| | allow_writeable_chroot=YES |
| | Остальные опции оставляем без изменения. |
|
| |
|
| Эта передача выполняется по методу __sub__. Это означает, что можно воспользоваться оператором минус (-).
| | Ниже добавим необходимые нам настройки. Разрешим пассивный режим и явно укажем диапазон портов: |
|
| |
|
| Magic-метод __repr__ реализуется повторно, чтобы возвращать представление строки списка, установленное в новом объекте. Он был изменен, поэтому когда пользователь печатает выходные данные двух объектов NumOperations, результат будет соответствовать ожиданиям.
| | pasv_enable=YES |
| | pasv_min_port=62000 |
| | pasv_max_port=62999 |
| | Включим виртуальных пользователей: |
|
| |
|
| Ниже представлен список, где элементы были вычтены друг из друга:
| | guest_enable=YES |
| | Укажем от имени какого реального пользователя они будут работать, так как в нашем случае нам надо работать с содержимым веб-сервера, то указываем соответствующего пользователя www-data. |
|
| |
|
| [90, 81, 72, 63, 54]. | | guest_username=www-data |
| | Применяем к виртуальным пользователям настройки локальных, иначе к ним будут применены ограничения анонимных. |
|
| |
|
| Методы __add__ и __mul__ реализуются аналогично __sub__, однако используют списковое включение для сокращения количества строк кода.
| | virtual_use_local_privs=YES |
| Magic-методы вычитания, сложения и умножения были определены для работы с пользовательскими объектами NumOperation.
| | Укажем шаблон для автоматической генерации домашнего каталога виртуального пользователя: |
|
| |
|
| Такое поведение при передаче аналогично таким пакетам анализа данных, как Pandas и Numpy.
| | user_sub_token=$USER |
| | Теперь можно задать сами домашние директории |
|
| |
|
| Методы __add__ и __mul__ также предназначены для работы с двумя объектами NumOperations. Это означает, что пользователь может воспользоваться оператором плюс + и умножением *. Как видно из приведенного ниже примера, q является результатом x * y, который возвращает новый объект NumOperations. При вводе q мы получаем представление строки операции передачи в виде списка.
| | local_root=/var/www/$USER |
| | Также иногда неплохо будет скрыть реальных владельцев содержимого, для этого добавьте: |
|
| |
|
| Исходный код доступен по ссылке на GitHub gist:
| | hide_ids=YES |
| | Если вы используете Ubuntu, то добавьте следующую недокументированную опцию: |
|
| |
|
| class NumOperations(object): | | seccomp_sandbox=NO |
| def __init__(self, math_list):
| | Это позволит избежать ошибки 500 OOPS: prctl PR_SET_SECCOMP failed, которая возникает из некорректной совместной работы vsftpd и системы безопасности seccomp, в Debian данная ошибка не проявляется. |
| self.math_list = math_list
| |
|
| |
| def __sub__(self, other):
| |
| minuslst = []
| |
| zipped = zip(self.math_list, other.math_list)
| |
| for tup in zipped:
| |
| minuslst.append(tup[0] - tup[1])
| |
|
| |
| return NumOperations(minuslst)
| |
|
| |
|
| def __add__(self, other):
| | На этом настройка FTP-сервера закончена, сохраняем файл и переходим к настройке правил аутентификации. Откроем /etc/pam.d/vsftpd, закомментируем в нем все строки и добавим две свои: |
| addlst = [x + y for x, y in zip(self.math_list, other.math_list)]
| |
| return NumOperations(addlst)
| |
|
| |
|
| def __mul__(self, other):
| | auth required pam_pwdfile.so pwdfile /etc/vsftpwd |
| mullst = [x * y for x, y in zip(self.math_list, other.math_list)]
| | account required pam_permit.so |
| return NumOperations(mullst)
| | После этого аутентификация локальных пользователей на FTP-сервере будет невозможна, работать будут только виртуальные. Опция pwdfile /etc/vsftpwd указывает путь к файлу паролей, его нам необходимо будет создать на следующем шаге. |
|
| |
|
| def __repr__(self):
| | Для создания пользователей будем использовать утилиту htpasswd из состава apache2-utils, для создания файла паролей и первого пользователя введите: |
| return str(self.math_list)
| |
|
| |
|
| | htpasswd -c -d /etc/vsftpwd blue.lab |
| | Ключ -c предписывает создать файл паролей, если он существует, то все данные в нем будут очищены, а файл перезаписан, -d задает необходимый для vsftpd алгоритм шифрования хэша пароля. |
|
| |
|
| x = NumOperations([100, 90, 80, 70, 60])
| | Следующие пользователи создаются командой (в нашем примере это пользователь green.lab): |
| y = NumOperations([10, 9, 8, 7, 6])
| |
|
| |
| p = x - y
| |
| z = x + y
| |
| q = x * y
| |
|
| |
| print('Subtraction: ' + str(p))
| |
| print('Addition: ' + str(z))
| |
| print('Multiplication: ' + str(q))
| |
|
| |
|
| Читайте также:
| | htpasswd -d /etc/vsftpwd green.lab |
| | Для удаления пользователя используйте (удаляем red.lab): |
|
| |
|
| Анализ аудиоданных с помощью глубокого обучения и Python (часть 1)
| | htpasswd -D /etc/vsftpwd red.lab |
| Максимальная производительность Pandas Python
| | После чего можно перезапустить службу FTP-сервера и проверить его работу: |
| Продвинутые методы и техники списков в Python
| |
|
| |
|
| Перевод статьи Stephen Fordham: Using Magic Methods in Python
| | service vsftpd restart |
| Войдите, чтобы отправлять и оценивать комментарии
| | Как видим, настроить FTP-сервер с виртуальными пользователями по нашей инструкции (в отличие от многих иных "инструкций" в сети) абсолютно несложно и недолго. Если вы будете внимательно следовать всем нашим рекомендациям, то сложностей у вас возникнуть не должно. |
| Расскажите, что вы об этом думаете?
| |
|
| |
|
| '''ИСТОЧНИК:''' | | '''ИСТОЧНИК:''' |
| <hr> | | <hr> |
| * [https://yandex.ru/turbo/nuancesprog.ru/s/p/5966/ Идеальная настройка вашего Golangпроекта] | | * [https://interface31.ru/tech_it/2016/01/nastraivaem-ftp-server-s-virtual-nymi-pol-zovatelyami-na-baze-vsftpd.html Записки IT специалиста] |
Автор: Уваров А.С. 29.01.2016
Протокол FTP, несмотря на преклонный возраст и серьезные недостатки продолжает широко применяться, во многом благодаря простоте и поддержке со стороны самого широкого спектра устройств и ПО. FTP-сервер является неотъемлемой частью любого веб-сервера, применяется в системах автоматизированного обмена данными. Наиболее часто встающей перед администраторами таких систем задачей является создание сервера с виртуальными пользователями. Сегодня мы расскажем, как это сделать.
Про настройку FTP-сервера на базе vsftpd мы уже рассказывали на страницах нашего сайта, недавно существенно дополнив и обновив статью Простой FTP-сервер на базе Ubuntu (vsftpd). В ней мы рассказали каким образом можно ограничить список пользователей, имеющих возможность подключаться к серверу, и их права. Однако локальные пользователи остаются при этом локальными пользователями и несмотря на ограниченные права могут оказаться объектом атаки, направленной на повышение прав, например, через уязвимости в каком-либо ПО.
Кроме того, ручное редактирование списков и настроек пользователей может оказаться весьма трудоемким делом если их не один-два, а существенно больше. Также иногда возникает ситуация, что светить данные реальных пользователей просто нежелательно, например, FTP используется движком сайта (CMS), связка пользователь-пароль явно прописана в конфиге и теоретически доступна любому работающему с сайтом сотруднику.
В таких ситуациях следует использовать виртуального пользователя, который будет иметь доступ исключительно к данному FTP-ресурсу и учетные данные которого можно смело сообщить всем принимающих участие в работе реальным сотрудникам.
В нашей статье мы настроим FTP-сервер для реализации следующей схемы:
vsftpd-virtual-user-001.jpg
Пусть в нашем распоряжении имеется некий веб-сервер под управлением которого находятся три сайта: blue.lab, green.lab и red.lab, физически расположенные в одноименных директориях. Наша задача создать FTP-сервер с виртуальными пользователями, каждый из которых имеет доступ только к своему сайту.
Важное замечание, в данной схеме все сайты должны работать от имени веб-сервера (www-data), если вы используете запуск каждого сайта от имени своего (реального) пользователя, то вам лучше настроить FTP-сервер для работы с реальными пользователями или дополнительно обеспечить нужные права доступа для виртуальных пользователей, что может представлять из себя нетривиальную задачу.
Прежде всего установим соглашение: имя виртуального пользователя должно совпадать и именем корневой FTP-папки, в нашем случае папки сайта, а все такие папки должны располагаться в одном месте, т.е. подпадать под шаблон /var/www/$USER. Это позволит в дальнейшем с легкостью манипулировать сайтами и виртуальными пользователями, а также избежать путаницы в именах и настройках.
Определившись с моделью размещения данных можно переходить к настройкам ПО. Кроме vsftpd нам понадобятся пакеты libpam-pwdfile и apache2-utils. Последний входит в состав одноименного веб-сервера и если у вас уже работает Apache, то устанавливать его не надо.
Сама установка предельно проста:
apt-get install vsftpd libpam-pwdfile apache2-utils
После чего перейдем к настройкам FTP-сервера, которые расположены в файле /etc/vsftpd.conf, мы перечислим опции в порядке их нахождения в файле.
Данная опция запускает FTP-сервер как службу
listen=YES
ниже имеется аналогичная взаимоисключающая опция:
listen_ipv6=NO
Первая из них обеспечивает поддержку IPv4, вторая сразу IPv6 и IPv4, поэтому должна быть включена только одна из них.
Запрещаем анонимных пользователей:
anonymous_enable=NO
И разрешаем локальных (и виртуальных) пользователей:
local_enable=YES
Также разрешаем запись:
write_enable=YES
и задаем маску для вновь создаваемых файлов и папок:
local_umask=022
Это обеспечит установку прав 755 на папки и 644 на файлы, т.е. полный доступ только владельцу и чтение для группы и остальных.
Чтобы FTP использовал локальное время сервера, а не GMT установим:
use_localtime=YES
И включим лог загружаемых и скачиваемых файлов:
xferlog_enable=YES
Разрешаем передачу данных через порт 20, требуется для лучшей совместимости с некоторыми клиентами:
connect_from_port_20=YES
Задаем путь к логу и его формат:
xferlog_file=/var/log/vsftpd.log
xferlog_std_format=YES
Укажем таймауты:
idle_session_timeout=600
data_connection_timeout=120
Первый задает время отключения бездействующего пользователя, а второй время ожидания возобновления неоконченной передачи.
Для корректной работы с текстовыми данными, в частности с символами переноса строки в разных ОС (CR+LF в Windows, LF в Linux) включим поддержку ASCII, это полезно, если вы скачиваете скрипты или файлы с Linux сервера, правите их в среде Windows и заливаете обратно. В этом случае FTP автоматически будет менять символы переноса строки для соответствия стандартам системы.
ascii_upload_enable=YES
ascii_download_enable=YES
Обязательно изолируем пользователя в корне его домашней директории и рядом впишем опцию, разрешающую запись в корень:
chroot_local_user=YES
allow_writeable_chroot=YES
Остальные опции оставляем без изменения.
Ниже добавим необходимые нам настройки. Разрешим пассивный режим и явно укажем диапазон портов:
pasv_enable=YES
pasv_min_port=62000
pasv_max_port=62999
Включим виртуальных пользователей:
guest_enable=YES
Укажем от имени какого реального пользователя они будут работать, так как в нашем случае нам надо работать с содержимым веб-сервера, то указываем соответствующего пользователя www-data.
guest_username=www-data
Применяем к виртуальным пользователям настройки локальных, иначе к ним будут применены ограничения анонимных.
virtual_use_local_privs=YES
Укажем шаблон для автоматической генерации домашнего каталога виртуального пользователя:
user_sub_token=$USER
Теперь можно задать сами домашние директории
local_root=/var/www/$USER
Также иногда неплохо будет скрыть реальных владельцев содержимого, для этого добавьте:
hide_ids=YES
Если вы используете Ubuntu, то добавьте следующую недокументированную опцию:
seccomp_sandbox=NO
Это позволит избежать ошибки 500 OOPS: prctl PR_SET_SECCOMP failed, которая возникает из некорректной совместной работы vsftpd и системы безопасности seccomp, в Debian данная ошибка не проявляется.
На этом настройка FTP-сервера закончена, сохраняем файл и переходим к настройке правил аутентификации. Откроем /etc/pam.d/vsftpd, закомментируем в нем все строки и добавим две свои:
auth required pam_pwdfile.so pwdfile /etc/vsftpwd
account required pam_permit.so
После этого аутентификация локальных пользователей на FTP-сервере будет невозможна, работать будут только виртуальные. Опция pwdfile /etc/vsftpwd указывает путь к файлу паролей, его нам необходимо будет создать на следующем шаге.
Для создания пользователей будем использовать утилиту htpasswd из состава apache2-utils, для создания файла паролей и первого пользователя введите:
htpasswd -c -d /etc/vsftpwd blue.lab
Ключ -c предписывает создать файл паролей, если он существует, то все данные в нем будут очищены, а файл перезаписан, -d задает необходимый для vsftpd алгоритм шифрования хэша пароля.
Следующие пользователи создаются командой (в нашем примере это пользователь green.lab):
htpasswd -d /etc/vsftpwd green.lab
Для удаления пользователя используйте (удаляем red.lab):
htpasswd -D /etc/vsftpwd red.lab
После чего можно перезапустить службу FTP-сервера и проверить его работу:
service vsftpd restart
Как видим, настроить FTP-сервер с виртуальными пользователями по нашей инструкции (в отличие от многих иных "инструкций" в сети) абсолютно несложно и недолго. Если вы будете внимательно следовать всем нашим рекомендациям, то сложностей у вас возникнуть не должно.
ИСТОЧНИК: