Содержание статьи
Я сторонник идеи, что не бывает абсолютно защищенных систем: атакующие продолжают находить всё более изощренные уязвимости. Добавь к этому человеческий фактор. Программисты, девопсы, любые специалисты могут совершать ошибки. Поэтому с точки зрения защиты, единственное что ты можешь делать — повышать затраты на взлом твоих ресурсов.
Пассворк — это быстрый способ сильно увеличить затраты на взлом твоей системы. При этом, управляя сейфами, директориями и паролями, ты в пару кликов можешь решать серьезные задачи. Например, предоставить временный доступ сторонней команде разработки, а когда они закончат проект, отключить им доступ и заново сгенерировать пароли.
Подготовка Пассворка
Если у твоей конторы нет лицензии Пассворк, первый вопрос — где его взять? Топай на passwork.ru и регайся на триал. Учитывай, что перед тем как отправить данные для доступа, разработчики хотят убедиться, что ты настоящий покупатель. По телефону звонить не будут, а вот почтовый домен точно посмотрят. При запросе пробной версии нужно указать корпоративную почту, то есть не на Gmail, Outlook или Mail.Ru, а на своем домене.
Тебе дадут тестовый стенд и данные для логина.
Зайди в раздел «Аутентификация». Промотай в самый низ к блоку «API-доступ». Cгенерируй API-токены.

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

Система построена по принципу API first — вся работа с Пассворком — это, по сути, работа с API разными способами. Есть утилита passwork-cli, есть контейнер Docker с предустановленной утилитой, есть пакет Python для разработки собственных скриптов. Но можешь писать скрипты на любом языке, главное — это возможность выполнять GET- и POST-запросы.
База API Пассворк
Установим пакет passwork-python для Python, это официальный клиент Passwork:
pip install passwork-python
Теперь можем создать клиента и взаимодействовать с API:
from passwork_client import PassworkClient# Используй данные из шага с генерацией токеновclient = PassworkClient(host="https://passwork.example.com")client.set_tokens("ACCESS_TOKEN", "REFRESH_TOKEN")client.set_master_key("MASTER_KEY")Дальнейшая работа — просто обращение к методам client. Например, client. для создания нового сейфа.
info
Сейф — это группа секретных данных, которая имеет собственные настройки приватности. Создаешь сейф, указываешь права на него, накидываешь в него секретные данные.
Например, у тебя может быть две команды DevOps, и им нужны разные наборы паролей и токенов. Создай несколько сейфов, предоставь каждой из команд доступ к нужному сейфу и получишь прекрасную изоляцию данных.
В примерах кода, можно увидеть что create_vault( принимает два параметра: название vault_name и тип хранилища type_id. Название — это обычная строка, а вот с типом возникают нюансы. Типы сейфов — это еще один способ группировки с заданием прав. Но посмотреть идентификаторы можно только через API.
В технической документации ты найдешь описание API, но давай сами заглянем внутрь passwork_client.. Меня заинтересовал этот импорт:
from .modules.vault_type import VaultTypeВ файле есть подходящая функция: get_vault_types(. Пробуем извлечь данные:
from passwork_client import PassworkClientclient = PassworkClient(host="https://ruxakep.passwork-demo.space/")client.set_tokens("1KqoGku9GYYYlsxj3BcLAvUqxa5ej/9M50/akAUuRvU=", "mXjYIaJUSTCzFa+RwHowNbguv2TF3OofnW7q8Hbb9AU=")client.set_master_key("vznRsji3NiIjkuekQchhcpswkJxJNC2hmEwuEb4hFLphh1Xmxc545XO+mGCrbAlgSYJ9w1qp3ltQyuXqF91pVw==")print(client.get_vault_types())Вот. что пришло в ответ:
{'items': [ { 'id': '685cfa5a3d0ba75d2f041972', 'name': 'Пользовательские сейфы', 'code': 'privateShared', 'isBuiltIn': True, 'creatorAccess': 'admin', 'administratorIds': [], 'allUsersCanCreate': True, 'userGroupCanCreateIds': [], 'userCanCreateIds': [], 'userRoleCanCreateIds': [], 'administrators': [], 'administratorsCount': 0, 'usersAllowedToCreate': None, 'creatorAccessName': 'Администрирование', 'isUsed': True }, { 'id': '685cfa5a3d0ba75d2f041973', 'name': 'Корпоративные сейфы', 'code': 'company', 'isBuiltIn': True, 'creatorAccess': 'admin', 'administratorIds': ['68598c0a132196f6470ed522'], 'allUsersCanCreate': True, 'userGroupCanCreateIds': [], 'userCanCreateIds': [], 'userRoleCanCreateIds': [], 'administrators': [ { 'id': '68598c0a132196f6470ed522', 'login': 'admin', 'fullName': 'admin', 'hasAvatar': False, 'isDeleted': False } ], 'administratorsCount': 1, 'usersAllowedToCreate': None, 'creatorAccessName': 'Администрирование', 'isUsed': False } ], 'totalCount': 2}Теперь не составит проблем, подставить в код нужный vault_id и получить полноценный скрипт добавления секрета в Пассворк.
from passwork_client import PassworkClientclient = PassworkClient(host="https://ruxakep.passwork-demo.space/")client.set_tokens("1KqoGku9GYYYlsxj3BcLAvUqxa5ej/9M50/akAUuRvU=", "mXjYIaJUSTCzFa+RwHowNbguv2TF3OofnW7q8Hbb9AU=")client.set_master_key("vznRsji3NiIjkuekQchhcpswkJxJNC2hmEwuEb4hFLphh1Xmxc545XO+mGCrbAlgSYJ9w1qp3ltQyuXqF91pVw==")vault_types = client.get_vault_types()private_shared = next( (vt for vt in vault_types['items'] if vt['code'] == 'privateShared'), None)if not private_shared: raise ValueError("Vault type with code 'privateShared' not found")print(private_shared['id'])vault_id = client.create_vault(vault_name="Cool Vault", type_id=private_shared['id'])
Теперь у нас есть полноценный механизм для анализа API Пассворк. Причем, большая часть кода хорошо прокомментирована, а методы названы интуитивно понятно.

Добавим в сейф новый пароль. В документации есть пример объекта для создания пароля:
password = { "name": "Service Name", "login": "username", "password": "secure-password", "vaultId": vault_id, "folderId": folder_id, "description": "Описание", "url": "https://service-url.com", "tags": ["tag1", "tag2"], "customs": [ { "name": "Дополнительный логин", "value": "second-username", "type": "text" }, { "name": "Код восстановления", "value": "recovery-code-value", "type": "password" }, { "name": "TOTP", "value": "JBSWY3DPEHPK3PXP", "type": "totp" } ], "attachments": [ { "path": "path/to/file.png", "name": "file.png" } ]}password_id = client.create_password(password)Из JSON понятно, что пароль представляет собой серьезную структуру с большим количеством данных. Слово «пароль» здесь не очень подходит, гораздо удобнее подходит «секрет». Например, что мешает загрузить ключ доступа по SSH или TLS-сертификат? Загрузка файлов есть, создание кастомных полей поддерживается. В секрет можно положить почти любые данные, которые не стоит хранить на сервере.
Создадим минимальный секрет, указав только обязательные данные:
password_data = { "name": "Super Secret", "vaultId": vault_id, "password": "secure-password",}password_id = client.create_item(password_data)
Чтобы получить пароль, используй:
secret = client.get_item(password_id)Теперь у нас в арсенале все, что нужно для написания мощных скриптов и интеграции Пассворк в CI/CD.
Идеи для DevOps
В репозитории Пассворка лежит много хороших примеров скриптов. На их основе легко накидать ротатор паролей или какой‑то другой полезный проект. Но скрипт‑ротатор — штука заезженная, и мне даже стыдно его упоминать. Думаю, будет куда интереснее отправлять уведомления Пассворк в Telegram. Так ты будешь знать о всех событиях в твоем Пассворке.
В библиотеке Пассворка я не нашел подходящей функции для нотификаций. Придется использовать метод call, который выполняет запрос к указанному эндпоинту. Это поможет пройти все процессы шифрования и дешифрования. Можешь юзать и requests, но тогда придется написать много лишнего кода.
Чтобы найти эндпоинт, я запустил проксирование трафика через Burp. Оказалось, что это /.

Алгоритм работы скрипта:
- Получить все уведомления из Пассворка.
- Сравнить
IDуведомлений с теми, которые уже отправлены в телегу. - Если есть новый, отправляем уведомление.
- Добавляем
IDв список отправленных и сохраняем в файл.
Создай бота, который будет нашим информатором. Затем создай файл passwork-notify..
Импорты:
import jsonimport sysimport asynciofrom datetime import datetimefrom passwork_client import PassworkClientfrom telegram import BotБлок констант для работы с Пассворком и ботом телеги:
ACCESS_TOKEN = "..."REFRESH_TOKEN = "..."MASTER_KEY = "..."HOST = "https://ruxakep.passwork-demo.space/"TELEGRAM_TOKEN = "..."TELEGRAM_CHAT_ID = "..."KNOWN_IDS_FILE = "known_notification_ids.json"Создай клиентов:
passwork = PassworkClient(HOST)passwork.set_tokens(ACCESS_TOKEN, REFRESH_TOKEN)passwork.set_master_key(MASTER_KEY)bot = Bot(token=TELEGRAM_TOKEN)Тебе потребуется несколько сервисных функций, чтобы код выглядел красиво и был поддерживаемым:
# Загрузка JSON с уже известными ID уведомленийdef load_known_ids(): try: with open(KNOWN_IDS_FILE, encoding="utf-8") as f: return set(json.load(f)) except (FileNotFoundError, json.JSONDecodeError): return set()# Сохранение обновленного списка с уведомлениямиdef save_known_ids(ids_set): with open(KNOWN_IDS_FILE, "w", encoding="utf-8") as f: json.dump(list(ids_set), f, ensure_ascii=False)# Сервисная функция отправки сообщенийasync def send_notification(chat_id, message): await bot.send_message( chat_id=chat_id, text=message )Но все остальное, как пачку спагетти, закинул в одну функцию:
async def main(): known = load_known_ids() updated = known.copy() try: # Здесь будет магия except Exception as e: print(f"Ошибка: {e}", file=sys.stderr) sys.exit(1)Когда каркас функции готов, накинь мяса в виде вызова метода call:
raw = passwork.call("GET", "/api/v1/notifications")if not raw or "items" not in raw: print("Нет items в ответе", file=sys.stderr) returnЕсли итемов нет, выходим из функции и завершаем работу скрипта. Если итемы есть, запускаем скучный цикл. Задача цикла — отсеить уведомления уже просмотренные и отправленные в бот. Остальные шлем, добавляем в список отправленных и сохраняем в файл:
sent = 0for item in raw["items"]: nid = item.get("id") if not nid or nid in known: continue # Избавься от этого if, if тебе нужны все уведомления if item.get("isViewed", False): updated.add(nid) continue # Формируем простое сообщение text = item.get("text", "—") placeholders = item.get("placeholders", {}) for k, v in placeholders.items(): text = text.replace(f"{{{k}}}", str(v)) actor = item.get("creator", {}).get("fullName", "—") event = item.get("activityLogEvent", "—") ts = item.get("createdAt") time_str = "" if not ts else f" ({datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')})" message = f"Новое уведомление\nID: {nid}\nСобытие: {event}\nОт: {actor}\nТекст: {text}{time_str}" # Вызов новой функции await send_notification(TELEGRAM_CHAT_ID, message) sent += 1 updated.add(nid)# Чтобы работа скрипта не была совсем скучнойif sent > 0: print(f"Отправлено {sent} новых уведомлений") save_known_ids(updated)else: print("Новых непрочитанных уведомлений нет")
Пропиши скрипт в cron, установи адекватный тайминг для запуска и радуйся полному контролю над Пассворком!
Не интересуют все уведомления, а только изменения? Пассворк регистрирует всю историю изменений паролей. В скриптах, можешь получить информацию о конкретном изменении легитимным методом клиента:
snapshot = passwork.get_snapshot(ITEM_ID, SNAPSHOT_ID)Но проблема в том, что тебе нужно знать конкретный идентификатор пароля и снэпшота. Функция удобна, чтобы отправить тебе в сообщении конкретное изменение, которое было выполнено. А вот чтобы получить список снэпшотов, придется использовать метод call и покопаться в запросах с помощью Burp.
Получить список снэпшотов по паролю, можешь через эндпоинт /.

Итемы получишь из /. Цепочка почти замкнулась. Осталось получить список сейфов и циклом обойти их.
Для получения списка сейфов, используй эндпоинт с такими параметрами:
/api/v1/vaults?includeAccessInfo=true&includeFoldersCount=true&includeIsPrivate=true&includePermissions=true
Этот запрос я вытащил из Burp. Он удобен: не нужно перебирать типы сейфов, если твоя задача этого не требует.
Итоговый цикл выглядит примерно так:
# ... прочий код ...vaults_raw = passwork.call("GET", "/api/v1/vaults", payload={ "includeAccessInfo": "true", "includeFoldersCount": "true", "includeIsPrivate": "true", "includePermissions": "true"})vaults = vaults_raw.get("items", []) if isinstance(vaults_raw, dict) else vaults_rawfor vault in vaults: vault_id = vault.get("id") items_raw = passwork.call("GET", f"/api/v1/items", payload={ "vaultId":vault_id }) items = items_raw.get("items", []) if isinstance(items_raw, dict) else items_raw for item in items: snaps_raw = passwork.call("GET", f"/api/v1/items/{item_id}/snapshots") # ... код обработки снапшотов ...По аналогии с предыдущим примером ты легко допишешь код чтобы всегда знать о появлении новых снэпшотов.
CLI
Каждый раз писать скрипт, чтобы протестировать тот или иной метод API, неудобно. Для этих целей есть утилита passwork-cli. Она предназначена для задач DevOps при автоматизации CI/CD. Но и для знакомства со структурой API подходит хорошо.
Когда ты ставил пакет passwork-python, CLI тоже установилась. Но в документации Пассворка предлагают ставить из репозитория Git Verse:
pip install git+ssh://git@gitverse.ru:2222/passwork-ru/passwork-python.git
Или через HTTPS:
pip install git+https://gitverse.ru/passwork-ru/passwork-python.git
info
В Windows, не забудь прописать путь к passwork-cli. Путь к тулзе узнаешь командой: pip .
Скорее всего, в PyPL и в Git Verse версии выкатываются одновременно. Я разницы не заметил и каких‑то сообщений по этому поводу не нашел.
Экспортируй в переменные хост и токены. Это нужно, чтобы ты не вводил каждый раз их при запуске passwork-cli:
export PASSWORK_HOST="https://ruxakep.passwork-demo.space/"export PASSWORK_TOKEN="..."export PASSWORK_MASTER_KEY="..."В Windows:
$env:PASSWORK_HOST="https://ruxakep.passwork-demo.space/"$env:PASSWORK_TOKEN="..."$env:PASSWORK_MASTER_KEY="..."Если не нравится хранение данных в окружении или у тебя специфическая автоматизация, указывай значения при запуске:
passwork-cli api --host <HOST> --token <TOKEN> --refresh-token <REFRESH_TOKEN> --master-key <MASTER_KEY> ...другие параметры...
Найди в исходниках маршрут вроде /. Для клиента используй маршрут без /:
passwork-cli api --method GET --endpoint "v1/users"
Иногда нужно добавить параметры. Используй опцию --params и описание параметров в JSON. Например, можно получить все пароли для определенного вида сейфов:
passwork-cli api --method GET --endpoint "v1/items/search" --params '{"vaultIds":["69aa963c1195f2c8420a4b52"]}'Кроме режима api, у passwork-cli есть еще режимы: get, update и exec. Последний — режим работы наиболее интересный. В нем Пассворк принимает идентификатор пароля (или несколько идентификаторов через запятую) и команду для запуска в системе. Сначала тулза вытащит указанные пароли и выгрузит их в окружение, а затем запустит команду.
Распаковать пароли и вывести переменные окружения:
passwork-cli exec --password-id "..." printenvВ окружении, переменные будут называться так, как пароль называется в Пассворке. Если пароль назван SECRET_LOGIN_ADMIN_DATABASE, после работы тулзы в окружении появится переменная SECRET_LOGIN_ADMIN_DATABASE. В значение переменной помещается пароль! В режиме exec Пассворк не увидит других значений, кроме названия и значения поля password.
Получить несколько переменных подряд можешь не только перечислив их, но и указав --folder или --tag. Директории и теги тоже можно перечислять через запятую.
Сокращаем поверхность атаки
Чтобы попрактиковаться, давай установим Broken Crystals. Это специализированное уязвимое веб‑приложение для обучения и проверки инструментов пентеста. Нам интересна уязвимость Local File Inclusion.
Установка и запуск:
git clone https://github.com/NeuraLegion/brokencrystals
docker compose --file=compose.local.yml up -dХакер без проблем может получить секреты из .. Веб‑приложение уязвимо к Local File Inclusion по эндпоинту http://.
Попробуй такой запрос:
curl.exe "http://localhost:3000/api/file/raw?path=.env"В ответ прилетит файл с кучей данных. Например: DATABASE_PASSWORD, JWT_SECRET_KEY, KEYCLOAK_ADMIN_CLIENT_SECRET, KEYCLOAK_PUBLIC_CLIENT_SECRET и так далее. Непорядок! С такими данными хакер имеет практически полный контроль.
Повысить секьюрность можно разными способами, но мы будем делать это в рамках Пассворка. В качестве примера будет база данных, остальные секреты можешь спрятать по аналогии. Секция базы в конфиге выглядит так:
services: db: image: 'postgres:17-alpine' restart: always healthcheck: interval: 10s retries: 10 test: ['CMD-SHELL', 'pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}'] timeout: 45s environment: POSTGRES_DB: ${DATABASE_SCHEMA} POSTGRES_USER: ${DATABASE_USER} POSTGRES_PASSWORD: ${DATABASE_PASSWORD} ports: - '5432:5432' volumes: - './pg.sql:/docker-entrypoint-initdb.d/pg.sql'Отредактируй файл ., удали из него DATABASE_SCHEMA, DATABASE_USER и DATABASE_PASSWORD. Эти данные перенеси в Пассворк. Создай сейф. В сейфе создай папку, например, bc. В папке создай три секрета. В название помести имя переменной, значение помести в пароль.

Убедись, что данные для подключения к API Пассворк есть в переменных окружения, потом выполни:
passwork-cli exec --folder-id "69adfb16a7750e0b940be8f4" sh -c 'docker compose -f compose.local.yml up -d'После запуска, убедись, что контейнер с базой поднялся:
docker inspect --format='{{.State.Health.Status}}' brokencrystals-db-1
Проверь, что база данных доступна:
psql -h localhost -U bc -d bc -c "SELECT 1;"
Выводы
Пассворк — это один из инструментов, при помощи которых ты можешь повысить не только безопасность, но и комфорт. Пароли надежно защищены и максимально удалены от атакующего. А широкие возможности API позволяют бесконечно совершенствовать свои инструменты управления.
В статье мы сделали всего лишь оповещения, но ничто не мешает сделать полноценную панель управления, через которую в один клик можно будет поменять все пароли, предоставить или забрать доступ.
Еще ты можешь принудительно поменять пароли тем, у кого они слишком слабые для его уровня доступа. Посмотри интересный эндпоинт /. К нему нужно два обращения: первым запускаешь проверку, через время этим же запросом увидишь у кого слабые пароли. Все что тебе нужно, в зависимости от скоринга запускать регенерацию пароля.
Надеюсь материал был полезным для тебя и ты обретешь еще один крутой инструмент для своей компании.
Erid: Реклама. ООО «Пассворк». ИНН 2901311774. Erid: 2SDnjdeaXkW
