{"id":13626228,"url":"https://github.com/C3S/passtore","last_synced_at":"2025-04-16T14:32:52.511Z","repository":{"id":178126703,"uuid":"661388253","full_name":"C3S/passtore","owner":"C3S","description":"Advanced `pass` management for teams","archived":false,"fork":false,"pushed_at":"2023-07-28T17:37:07.000Z","size":160,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-11-08T16:42:49.328Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/C3S.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-07-02T17:28:12.000Z","updated_at":"2023-07-02T18:19:30.000Z","dependencies_parsed_at":null,"dependency_job_id":"ebe13d70-7901-4634-8181-313d13f02d72","html_url":"https://github.com/C3S/passtore","commit_stats":null,"previous_names":["c3s/passtore"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C3S%2Fpasstore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C3S%2Fpasstore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C3S%2Fpasstore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C3S%2Fpasstore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/C3S","download_url":"https://codeload.github.com/C3S/passtore/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249250820,"owners_count":21237961,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T21:02:13.388Z","updated_at":"2025-04-16T14:32:52.197Z","avatar_url":"https://github.com/C3S.png","language":"Shell","funding_links":[],"categories":["Shell"],"sub_categories":[],"readme":"\u003c!--\ntitle: \"`passtore.sh`\"\nsubtitle: Advanced `pass` management for teams\nnodate: true\ntoc: true\nlang: en\nlatex_engine: pdflatex\noutput:\n  # pdf_document:\n  bookdown::pdf_document2:\n    toc: true\n    latex_engine: pdflatex\n    template: C3S_standard_doc.latex\n    df_print: kable\nshortname: \"`passtore.sh`\"\nabstract: |\n  This script might be for you if you want to use [`pass`](https://passwordstore.org) for password management in a team that is so large\n  that not everyone should be able to see everything. It is not a replacement for `pass` or [`QtPass`](https://qtpass.org), but\n  an additional management tool, intended not to be used by all of the team but some designated password admins.\n--\u003e\n\n# `passtore.sh` -- Advanced `pass` management for teams\n\nThis script might be for you if you want to use [`pass`](https://passwordstore.org) for password management in a team that is so large\nthat not everyone should be able to see everything. It is not a replacement for `pass` or [`QtPass`](https://qtpass.org), but\nan additional management tool, intended not to be used by all of the team but some designated password admins.\n\n## Background\n\nEncrypting passwords using OpenPGP is a straight forward approach, especially if you use OpenPGP already in your team.\nWe looked at `pass` and found it is a very nice tool for password management, so we examined how we could use it\nin our team. It stores passwords in separate files, using [`GnuPG`](https://gnupg.org) for encryption.\n\nBy default, `pass` uses `~/.password-store` as the root directory for personal passwords and configures your OpenPGP\nencryption key in a file called `.gpg-id`. But you can also create subdirectories with different `.gpg-id` files to encrypt\npasswords for different OpenPGP keys. A `.gpg-id` file recursively defines the keys used, unless it is overruled by\nanother `.gpg-id` file in a directory below.\n\nYou can even switch between multiple root directories, i.\u0026#8239;e., different password stores. In [`QtPass`](https://qtpass.org)\nthese are called *profiles*. An example `pass` password store could look like this:\n\n```\nstore_root_dir/\n    .gpg-id               # with key IDs of Alice and Bob\n    alice/\n        .gpg-id           # only key ID of Alice\n        matrix.gpg        # encrypted for Alice\n        server1.gpg       # encrypted for Alice\n        server2.gpg       # encrypted for Alice\n    bob/\n        .gpg-id           # only key ID of Bob\n        mastodon.gpg      # encrypted for Bob\n        matrix.gpg        # encrypted for Bob\n    seafile/\n        team_library.gpg  # encrypted for Alice and Bob\n```\n\nIn this example, the `team_library.gpg` was encrypted for both Alice and Bob because the `seafile` directory doesn't\nhave it's own `.gpg-id` file, so `pass` uses the one from the nearest parent directory.\n\nIf you use `pass` to update the key list in a `.gpg-id` file, already encrypted passwords usually get re-encrypted\nreflecting the new key configuration.\n\n\n### The problem\n\nAs long as you use `pass` only for yourself, all is good as it is. But as soon as you need individual permissions for users\nto access password files, you need more control over the `.gpg-id` files in your directory structure. They are simple\ntext files, so theoretically each user with write access could add any new keys to them and hope that during the next\nround of re-encryption they'll gain access to previously hidden passwords. In our example above, what if Bob, or anyone else for\nthat matter, decided to silently add his key ID to `store_root_dir/alice/.gpg-id` and wait? As long as Alice doesn't notice, she'd\nexpose all new or changed passwords to anyone with access to these additional OpenPGP keys.\n\n`pass` already partially has a solution for that, in that it supports setting an environment variable defining a key\nthat is used to *sign* `.gpg-id` files. If a signature is found, it will be validated before (re-)encryption.\nHowever, team members must actually have the environment variable set correctly, `pass` won't complain if it's missing\nans silently accept any `.gpg-id` file.\n\n\n### How to solve this\n\nWhat we're missing is a means to verify that\n\n* all directories are supposed to exist\n* each directory has it's own `.gpg-id` file so there's no accidental privilege escalation\n* each `.gpg-id` file contains the intended key IDs (no more, no less)\n* each `.gpg-id` file is validly signed with a defined key\n* the encryption of each password file exactly matches the keys from its respective `.gpg-id` file\n\nWe also want to have easy access to the configuration, e.\u0026#8239;g.\n\n* quickly list all OpenPGP keys or users that are known\n* quickly show all keys/users that have access to given directories\n* get an overview of all defined directories\n\n\n## Our implementation\n\nThe script `passtore.sh` provides these features. It uses\n\n* a YAML configuration file to define\n  * each user with email and OpenPGP key\n  * groups of users, used for access control\n  * all directories with standards for password generation and access groups\n  * the keys used for signing\n* as well as a local configuration file to define\n  * at least one profile, i.\u0026#8239;e., the root directory of your password store, its YAML configuration file etc.\n  * the same list of keys used for signing, once more\n  \nWe call these (sub)directories »stores« because they all use their own `.gpg-id` file. In other contexts, their root\ndirectory is also sometimes referred to as »the password store«, please let this not confuse you.\n\nAddressing the problem above, the one thing that must be ensured is the validity of signatures of the `.gpg-id` files,\nas well as of the YAML configuration. Since the YAML configuration sets the keys used for signing and should be included\nin the distributed password store, it was not enough to sign it, because an attacker could replace those keys and sign\nthe config afterwards. That is why the local configuration has a copy of the signing key definition, and during its\nchecks `passtore.sh` also ensures that both are identical. An attacker would therefore also have to replace the key\nIDs on all machines that use the script in order to not get caught.\n\n\n### Read-write vs. read-only stores\n\nTo further limit the risk of an attack, `passtore.sh` is designed to support the use of two password store root directories.\nYou can think of them as one being the actual password store (it is supposed to be read-only for everyone except\npassword admins) and the other a turnstile for temporary use (with read-write access for everyone). The latter\ncan be used by non-admins to provide new password files. `passtore.sh` can check their integrity and then move\nthem to the read-only area. Both are expected to use the same directory structure as defined by the YAML file.\n\nThe fact that the actual password store can only be read but not changed by normal team members already limits the\nnumber of people who'd be able to compromise its integrity. Consequently, the team needs to appoint one or more\nmembers to do the task of maintaining the read-only store (»password admins«). They don't need read access to actual\npasswords, but they use `passtore.sh` to verify the integrity of the privilege structure the team has decided on.\n\nIf the idea of a globally writable password store immediately scares you, be aware that that's all you would have if\nyou simply used `pass` for a team. Using an additional read-write root directory is not mandatory, though. Obviously,\nit comes with all the risks we're trying to avoid, if only for short periods of time and a somewhat higher probability\nof being noticed (see [Remaining issues]). You can use alternative channels to send password files to the password admins.\n\n`passtore.sh` can also generate new passwords in a given store and encrypt them for the intended group of people,\nwhich could be done by a password admin directly so that passwords never exist outside the read-only area.\nThis, on the other hand, might give that admin access to passwords during creation.\n\n**Note:** `passtore.sh` itself does *not* enforce the read-write status of your directories! It is assumed that *you*\nmake sure that the password store is read-only or read-write for parts of your team. There are various ways to do\nthat (e.\u0026#8239;g., ACLs, read-only mounted file systems, read-only [gitolite](https://gitolite.com/) repositories,\nread-only [Seafile](https://seafile.com) libraries etc.). Choose a solution that fits your individual infrastructure.\n\n\n### Dependencies\n\nThe script needs these tools to work properly:\n\n* [`gpg`](https://gnupg.org) for OpenPGP stuff\n* [`pass`](https://passwordstore.org) for password store management\n* [`yq`](https://mikefarah.gitbook.io/yq) for YAML support\n\nThere's also two versions of the script available, one called `passtore.sh` and the other `passtore_static.sh`.\nThe `passtore.sh` script was written using [`bash_script_skeleton.sh`](https://github.com/unDocUMeantIt/bash_script_skeleton)\nand therefore uses its function library dynamically. That is, if you want to use `passtore.sh` directly,\na recent version of `bash_script_skeleton.sh` must be in your path. If you don't want to install `bash_script_skeleton.sh`\nyou can use `passtore_static.sh` instead of `passtore.sh`, which is the same script but with all the bash\nfunctions that it uses from `bash_script_skeleton.sh` statically copied into the file. It is therefore a much larger\nscript and these functions will not get updated unless a new static version is being released. The static script might be\nmore transparent for a code review.\n\n\n### Installation\n\nIf your system meets all the dependency requirements, simply copy `passtore.sh` or `passtore_static.sh` to a directory in\nyour `PATH`.\n\nAlternatively, you can clone this repository to be able to get updates and fixes, and use a symlink. E.\u0026#8239;g., if `~/bin` is in your `PATH`:\n\n```bash\n# replace YOUR_DESIRED_LOCATION with the path you want to clone to\ngit clone https://github.com/C3S/passtore.git \"${YOUR_DESIRED_LOCATION}\"\nchmod +x \"${YOUR_DESIRED_LOCATION}\"/*.sh\nln -s \"${YOUR_DESIRED_LOCATION}\"/*.sh ~/bin\n```\n\nTo look for updates from time to time:\n\n```bash\ncd \"${YOUR_DESIRED_LOCATION}\"\ngit pull\n```\n\n### Initial setup\n\nNote: If you call `passtore.sh` (or `passtore_static.sh`, we'll only use `passtore.sh` from now on) without any arguments, it will show\na usage message.\n\n#### Configuration file\n\nWhen `passtore.sh` is run for the first time, it initiates a local configuration file. Common for `bash_script_skeleton.sh`\nscripts, you can then call\n\n\n```bash\npasstore.sh --config\n```\n\nto open the configuration file for editing. It contains comments which hopefully explain what needs to be configured.\n\nThe configuration supports profiles, i.\u0026#8239;e., you can manage multiple password stores with `passtore.sh`, like one for your team\nand a second one just for you personally. Profiles are defined by several arrays containing key value pairs. Each array configures\na different aspect, like the root directory of the read-only and read-write stores, the path to the YAML configuration, or the\nOpenPGP key IDs for signing. The keys in an array are always the names for a profile, they must therefore be consistent in all\narrays. That is, if you want to use three profiles, each array must have exactly three key value pairs with the keys always\nusing the same three names.\n\n\n#### YAML configuration\n\nThe second configuration that needs to be set up is the YAML file. This needs to be done per profile. `passtore.sh`\ncan initiate a commented example configuration file as a starting point. Just call\n\n```bash\n# replace PROFILE with a profile name from the local configuration file\npasstore.sh -p \"${PROFILE}\" -y\n```\n\nto have it generated and opened for editing. After editing, don't forget to call\n\n```bash\npasstore.sh -p \"${PROFILE}\" -Y\n```\n\n(with a capital `Y`) to sign the YAML file.\n\nIt has a `keys` section (with subsections `users` and `groups`), a `defaults` section (currently only used for key generation settings),\nand a `stores` section to define password directories and who should have access.\n\nEach section starts with a short description of what you should put there. Note that it is mandatory to keep the auto-generated group\ncalled `passadmin`, the example file also explains this in a comment: The OpenPGP keys of the `passadmin` members are used as the valid\nsigning keys. Their key IDs must therefore match the signing keys in the local configuration file. So please adjust the members of this\ngroup, but you must not remove it.\n\nAccess to stores can only be given to groups, to simplify the script. If you want to grant access to individual users, create a group\nfor each user as a workaround.\n\n**Note:** If you keep the YAML configuration in the read-only store, non-admin users are able to run integrity checks on the stores, too.\nThat's a good thing.\n\n\n### Workflow\n\n`passtore.sh` reads the local configuration file to learn about defined profiles. When you select a profile (`-p` flag), the script knows\nwhich YAML configuration to use. Of the read-only (and read-write, if used) store, only the root directory must preexist and be writable\nby members of the `passadmin` group. They can create the correct directory structure with `passtore.sh`, which calls `pass init` with the\nparticular configuration it fetches from the YAML file.\n\nThis means that before you touch anything in those root directories, you should always configure it in the YAML file *first*.\n\n\n#### Example: A new password for the social media team\n\nLet's assume you have a social media team which just registered a new Mastodon account. It now wants to store the login credentials in\nthe read-only store, in a profile that is called `company`. If you were among the `passadmin`s, your first task would be to add a new subdirectory\nto the YAML configuration. So you'd open an editor:\n\n```bash\npasstore.sh -p company -y\n```\n\nAnd then add something like this to the `stores` section:\n\n```yaml\n  - name: social media\n    path: social_media\n    groups:\n      read:\n        - social media\n\n  - name: mastodon\n    path: social_media/mastodon\n    groups:\n      read:\n        - social media\n```\n\nThis defines both the `social_media` directory and its subdirectory `social_media/mastodon`, relative to the root directories. We set up both\nto ensure that each has a `.gpg-id` file so there's never confusion about who has access to what. You have given read access to a group\ncalled `social media` which must be defined among the `groups`, of course.\n\nNow that you've changed the YAML file, you must sign it with your OpenPGP key, otherwise the next integrity check will indicate that\nsomeone tempered with the configuration:\n\n```bash\npasstore.sh -p company -Y\n```\n\nThe next step would be to initiate the directories in both read-write and read-only root directories. This will also write the `.gpg-id` files\nand have you sign them:\n\n```bash\npasstore.sh -p company -s \"social media\" -i # read-only\npasstore.sh -p company -s \"social media\" -w # read-write\npasstore.sh -p company -s \"mastodon\" -i # read-only\npasstore.sh -p company -s \"mastodon\" -w # read-write\n```\n\nThe social media team can now use `pass` or similar to add its new password file to `social_media/mastodon` in the writable location.\nThey should be aware that this is probably the most vulnerable step, as an attacker with access to the read-write directory with perfect\ntiming could steal a password at this point (see [Remaining issues] below). So before encrypting their password, it seems advisable to\ndouble check the contents of the respective `.gpg-id` file.\n\nOnce that's finished, here comes the most interesting part, as you will now run an integrity check on the read-write directory:\n\n```bash\npasstore.sh -p company -R\n```\n\nIf all went as expected, it runs without warnings or errors. But if, for example, the password file was encrypted for the wrong keys,\nyou'll know now.\n\nWith all checks successfully completed, you can move the password file to the actual read-only location:\n\n```bash\npasstore.sh -p company -M \"mastodon\"\n```\n\nAnd finally, also run an integrity check in the full read-only directory:\n\n```bash\npasstore.sh -p company -C\n```\n\n## Some final remarks\n\nWe actually didn't expect the script to become so large, otherwise we'd probably have gone for a different programming language.\nBut for now it's what it is.\n\n\n### Remaining issues\n\nAs long as you can trust the `passadmin`s and there's no leaked or weak OpenPGP keys, we assume that `pass` combined with\n`passtore.sh` makes it possible to securely manage passwords in team settings. However, we can think of at least one\nweak point that this approach still has: There's kind of a race condition between a `passadmin` providing the `.gpg-id` and\nusers using it for encryption. Here's what a potential attack could look like, if the attacker has access to the read-write\ndirectory:\n\n#. Wait for the `passadmin` to provide a `.gpg-id` file\n#. Replace it with an unsigned one that has the attackers key appended to it\n#. Wait for the user to encrypt new passwords with it\n#. Copy the encrypted password file\n#. Restore the replaced `.gpg-id` and re-encrypt the password for only the intended users\n\nThe attacker now has a decryptable copy of the password, and if the `passadmin` first checks the integrity at this point,\nnothing dubious would come up. It is therefore crucial to either carefully check the `.gpg-id` before encrypting, or\nuse a more secure channel for providing new passwords in the first place. The use of the writable location has the benefit\nof being directly usable for all team members with a `QtPass` profile or similar.\n\n\n## Contributing\n\nTo ask for help, report bugs, suggest feature improvements, or discuss the global\ndevelopment, please use the issue tracker.\n\n\n### Branches\n\nPlease note that all development happens in the `develop` branch. Pull requests against the `main`\nbranch will be rejected, as it is reserved for the current stable release.\n\n\n## Licence\n\nCopyright 2023 m.eik michalke \u003cmeik.michalke@c3s.cc\u003e.\n\nThese scripts are free software: you can redistribute them and/or modify\nthem under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThese scripts are distributed in the hope that they will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with these scripts.  If not, see \u003chttps://www.gnu.org/licenses/\u003e.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FC3S%2Fpasstore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FC3S%2Fpasstore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FC3S%2Fpasstore/lists"}