Сообщения в pass - консольный менеджер паролей

pass - консольный менеджер паролей

** Для повышения количества контента в сети делаю репост статьи из моего блога, оригинал здесь https://emunix.org/post/pass-cli/ **

Pass - это небольшой bash-скрипт хранящий пароли в обычных текстовых файлах зашифрованных с помощью GPG. Файлы можно организовывать в директории, при этом имя файла является названием сайта или ресурса для которого файл хранит пароль. Файлы имеют очень простой формат: первая строка содержит только пароль, все остальные строки содержат любые другие данные. Например, файл github.com.gpg может иметь такое содержимое:
md3rTks3!=
login: Maria
url: https://github.com/login 
email: maria@example.com
Указывать поля вида login: и url: именно в таком формате не обязательно, но подобные метаданные позволяют менеджеру паролей автоматически подставлять ваш логин на веб-сайтах.

Очень простой формат файла и организации хранения паролей дает нам большие возможности: мы можем просматривать и редактировать пароли без установленной программы pass (достаточно иметь установленные GnuPG и любой текстовый редактор), мы можем писать свои скрипты для работы с паролями, мы можем хранить все изменения паролей в системе контроля версий git и синхронизировать пароли на разных компьютерах через неё же. Также pass имеет большую поддержку со стороны сообщества: существуют реализации программы для Android и iOS, плагины для веб-браузеров Chrome и Firefox, графические клиенты для Windows, Mac и Linux, расширения для Alfred, dmenu, rofi и Emacs, скрипты для импорта паролей из других приложений.

В этой статье мы рассмотрим именно консольную версию, доступную на сайте https://www.passwordstore.org/

Установка

Установить программу вы можете с помощью пакетного менеджера своего дистрибутива, например:

- для Ubuntu \ Debian: sudo apt-get install pass
- для ArchLinux: pacman -S pass
- для macOS: brew install pass


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

Далее пишем в терминале pass init your@e-mail.ru, где вместо your@e-mail.ru вам необходимо написать адрес электронной почты, который вы указывали при создании gpg-ключа (здесь e-mail используется в качестве gpg-id).

Эта команда создаст директорию ~/.password-store в которой будут храниться ваши пароли и запомнит каким ключом их шифровать (вы также можете использовать разные gpg ключи для разных поддиректорий, для этого есть флаг -s: pass init -s поддиректория second@key-id.com)

Если вы хотите, чтобы директория с паролями хранилась в git-репозитории, то выполните команду pass git init. После этого при каждом создании\изменении паролей pass будет автоматически делать коммит в репозиторий.

Использование

Чтобы добавить пароль выполите команду pass insert email/your@email.ru и введите пароль который хотите сохранить.

Эта команда создаст поддиректорию email в хранилище паролей, добавит в неё зашифрованый текстовый файл your@email.ru.gpg, в котором сохранит введённый вами пароль.

Если вам нужно по-быстрому сгенерировать пароль, то можете написать pass generate youtube.com 15. Эта команда сгенерирует новый пароль длинной 15 символов, запишет его в файл youtube.com.gpg и покажет на экране. Если вы добавите опцию -n, то пароль будет состоять только из букв и цифр (без специальных символов), если добавите опцию -c, то пароль сразу будет скопирован в буфер обмена.

Ранее я писал, что в файле может содержаться не только пароль, но и другая текстовая информация (например логин или просто какие-то заметки). Для того что бы записать несколько строк в файл вы можете использовать опции –multiline или -m: pass insert -m web/github.com. Напоминаю: пароль вводится в первой строке, а все остальные данные уже после него на отдельных строках (см. пример в начале статьи). Что бы закончить ввод и сохранить данные нажмите Ctrl+d.

Изменить файл с паролем можно командой pass edit web/github.com. При этом откроется редактор указанный в переменной окружения EDITOR. Этой командой также можно добавлять в хранилище новые пароли, если вам удобнее делать это через текстовый редактор.

Для удаления файла с паролем используется команда pass rm web/github.com, а для переименования pass mv старое_название новое_название.

Для того чтобы показать пароль на экране просто введите pass web/github.com. При этом, чтобы не вводить длинные названия сайтов, вы можете пользоваться автодополнением bash нажимая клавишу Tab. Используйте флаг -c чтобы сразу скопировать пароль в буфер обмена: pass -с web/github.com.

Посмотреть какие вообще есть записи в хранилище можно просто введя pass (а для просмотра записей в отдельной директории pass имя_директории).

Вы можете искать файлы по части названия pass find строка_поиска и, более того, вы можете искать внутри всех файлов с паролями pass grep строка_поиска (правда это действие довольно медленное).


Версионирование и синхронизация
Выше я писал, что если вы выполните команду pass git init, то программа создаст внутри хранилища паролей git-репозиторий и будет автоматически коммитить все изменения. А это означает, что из коробки у нас появляется синхроницация паролей на разных компьютерах.

Вы можете использовать любые команды git, написав перед ними слово pass. Это нужно, чтобы перед их вызовом вам не приходилось каждый раз переходить в директорию ~/.password-store.

Добавляем адрес репозитория на вашем сервере pass git remote add origin your-remote-server.com:pass-store.

Забираем с него файлы с паролями pass git pull.

Сохраняем добавленные или изменённые пароли на сервер pass git push.

Заключение

Как видите pass очень простая, но мощная утилита, следующая философии unix. Часто её ругают за то, что названия файлов с паролями хранятся в открытом виде (а они обычно представляют собой названия сайтов на которых вы зарегистрированы). Но лично мне нравится настолько простой формат хранения паролей.
btimofeev to All (2020-08-31 18:36:10) [ссылка]

Re: pass - консольный менеджер паролей

Ответ на сообщение
> Часто её ругают за то, что названия файлов с паролями хранятся в открытом виде (а они обычно представляют собой названия сайтов на которых вы зарегистрированы). Но лично мне нравится настолько простой формат хранения паролей.
Существует расширение, которое позволяет хранить дерево сайтов в "гробнице" - https://github.com/roddhjav/pass-tomb#readme

---

А ещё есть расширение, которое позволяет использовать pass для двухфакторного входа - OTP (правда, смысл двухфакторки теряется) - https://github.com/tadfisher/pass-otp#readme
У яндекса тоже есть OTP, но у них свой - YAOTP - с дополнительным ключом - пин-кодом. Для него использую https://github.com/tujh2/yaGotp

Для себя я писал отдельный скрипт, который работает как клиент только для чтения хранилища pass. Одной командой он копирует пароль, а другой командой парсит и копирует поле "login: blabla" или "email: gg@gg.ru", если не найдено первое.
tuple to btimofeev (2024-09-30 16:21:22) [ссылка]

Re: pass - консольный менеджер паролей

Ответ на сообщение
tuple> Для себя я писал отдельный скрипт, который работает как клиент только для чтения хранилища pass. Одной командой он копирует пароль, а другой командой парсит и копирует поле "login: blabla" или "email: gg@gg.ru", если не найдено первое.
Я использую скрипт https://github.com/carnager/rofi-pass , который позволяет искать и копировать логины\пароли через rofi (https://github.com/davatorium/rofi )
P.S. Edited: 2024-10-01 15:39:40
btimofeev to tuple (2024-10-01 14:39:06) [ссылка]

Re: pass - консольный менеджер паролей

Ответ на сообщение
btimofeev> Я использую скрипт https://github.com/carnager/rofi-pass , который позволяет искать и копировать логины\пароли через rofi (https://github.com/davatorium/rofi )
С помощью rofi работает у меня сторонний скрипт bemoji для выбора эмодзи из UTF-8: https://github.com/marty-oehme/bemoji .

Кстати, вот сам скрипт "pypassmenu" на текущий момент. Пусть под лицензией UNLICENSE. Далёк от идеала, но может кто вдохновится или будет интересно.
#!/usr/bin/env python

import os
import re
import sys
import logging
from time import sleep
from pathlib import Path
from subprocess import Popen, PIPE
from argparse import ArgumentParser


class PassFile:
    def __init__(self, name: str, content: str):
        self.name = name
        self._lines: list[str] = content.split('\n')
        self._fields: dict[str, str] = {}
        for line in self._lines:
            m = re.match(r'(.+): (.+)', line)
            if m:
                self._fields[m.group(1)] = m.group(2)
        logging.info(f'Created PassFile instance of {name}')
        logging.debug(f'PassFile fields: {self._fields}')

    @property
    def password(self) -> str:
        return self._lines[0]

    def _get_field(self, field: str) -> str:
        return self._fields[field]

    @property
    def email(self) -> str:
        try:
            logging.debug('Trying to find "email" field')
            return self._get_field('email')
        except KeyError:
            logging.debug('Falling back to "e-mail" field')
            return self._get_field('e-mail')

    @property
    def login(self) -> str:
        try:
            logging.debug('Trying to find "login" field')
            return self._get_field('login')
        except KeyError:
            logging.debug('Falling back to e(-)mail field')
            return self.email


def fetch_passfiles() -> dict[str,Path]:
    store_path = Path(os.getenv('PASSWORD_STORE_DIR') or
                      os.getenv('HOME') + '/.password-store')
    gpg_files = list(store_path.glob('**/*.gpg'))
    gpg_files_dict = {}
    for filepath in gpg_files:
        relative_filepath = str(filepath.relative_to(store_path))
        gpg_files_dict[re.sub(r'\.gpg$', '', relative_filepath)] = filepath
    return gpg_files_dict

def choose_passfile_with_dmenu(gpg_files_dict: dict[str,Path]) -> tuple[str, Path]:
    p = Popen(['dmenu'], stdout=PIPE, stdin=PIPE, text=True)
    index = '\n'.join([key for key in gpg_files_dict])
    stdout = p.communicate(input=index)[0]
    choosen_filename = stdout.strip()
    return choosen_filename, gpg_files_dict[choosen_filename]

def fetch_passfile_by_path(filename: str, filepath: Path) -> PassFile:
    p = Popen(['gpg', '-d', '--quiet', str(filepath)], stdout=PIPE, text=True)
    return PassFile(filename, p.stdout.read().strip())

def copy_to_clipboard(text: str):
    p = Popen(['xclip', '-selection', 'clipboard'], stdin=PIPE, text=True)
    p.communicate(input=text)
    logging.info('Clipboard was written')
    logging.debug(f'Clipboard content: {text}')

def clear_clipboard():
    logging.debug('Clipboard cleared!')
    copy_to_clipboard('')

def notify(header: str, body: str):
    Popen(['notify-send', header, body])
    logging.info('Notification sent')
    logging.debug(f'Notification content:\n{header=}\n{body=}')

def main():
    parser = ArgumentParser(prog='pypassmenu',
                            description='Improved passmenu written in python')
    supported_fields = ( 'password', 'login', 'email', )
    parser.add_argument('field', type=str, choices=supported_fields,
                        default=supported_fields[0], help='Obtained field')
    args = parser.parse_args()
    
    # logging.basicConfig(level=logging.DEBUG)

    filename, filepath = choose_passfile_with_dmenu(fetch_passfiles())
    pass_ = fetch_passfile_by_path(filename, filepath)
    important_value = 'something'
    try:
        match args.field:
            case 'password':
                important_value = pass_.password
            case 'login':
                important_value = pass_.login
            case 'email':
                important_value = pass_.email
    except KeyError:
        notify('Не удалось найти поле',
               f'pypassmenu: выбранное поле не найдено в {pass_.name}')
        sys.exit(1)
    copy_to_clipboard(important_value)
    sleep(20)
    clear_clipboard()

if __name__ == '__main__':
    main()
tuple to btimofeev (2024-10-19 08:42:54) [ссылка]