Posts come up very frequently with people coming up with various workarounds and hacks to support team workflows.
This month there have already been two threads requesting solutions for this: https://lists.zx2c4.com/pipermail/password-store/2016-November/002463.html https://lists.zx2c4.com/pipermail/password-store/2016-November/002482.html For many it is necessary to maintain several passdb folders for personal, family, work, projects, etc. Maintaining this typically requires each of these with its own repo, users, and git origins. One could come up with custom scripts/wrappers to support this (and many do), but then those scripts must be signed and distributed and updated properly with all members in a group which makes adoption of pass that much more difficult. I feel this really should be solved in pass itself and clearly others agree. The pass maintainer has also expressed interest in in-tree solutions to this problem but the last submission attempt fizzled out. https://lists.zx2c4.com/pipermail/password-store/2015-April/001455.html ## Subtree Pattern ## Currently the only workflow I have found that is natively supported by pass today is using subtrees. I have been using the following approach for over a year with 3 repos and 2 teams. While it does work, it is fairly cumbersome. Setup: ``` pass init ABCDE12345678901 pass git init pass git remote add origin [email protected]:lrvick/passdb.git pass git remote add -f project [email protected]:project/passdb.git pass git remote add -f company [email protected]:company/passdb.git cd ~/.password-store git subtree add --prefix Project someproject master --squash git subtree add --prefix Company someproject master --squash ``` Add/Edit ``` pass generate Project/somepass 50 pass rm Personal/somepass pass edit Work/somepass ``` Sync to/from respective origins ``` pass git subtree push origin master pass git subtree pull --prefix=Project project master --squash pass git subtree pull --prefix=Company company master --squash pass git subtree push --prefix=Project project master pass git subtree push --prefix=Company company master ``` The above also has some particularly painful side effects when enforcing commit signing + upstream merges. In practice this has resulted in us having to tap a yubikey to sign once for every file in the repo... ## Submodule Pattern ## With the attached patch for submodule support the above could become substantially more manageable. Personal Setup: ``` pass init ABCDE12345678901 pass git init git remote add origin [email protected]:lrvick/passdb.git ``` Existing shared repo setup: ``` pass git submodule add [email protected]:project/passdb.git Project pass git submodule add [email protected]:work/passdb.git Work pass git submodule foreach "git checkout origin master" ``` Add/Edit ``` pass generate Project/somepass 50 pass rm Personal/somepass pass edit Work/somepass ``` Sync to/from respective origins ``` pass git push pass git submodule foreach "git pull" pass git submodule foreach "git push" ``` ## Design and Implementation ## This change makes `pass` commit to the innermost Git repository (as identified by the presence of a `.git` file/folder) that contains a file, when it is changed. It was designed to minimize the number of changes to `pass` and not break existing usages. A few extra tests have been supplied for the new workflow. Whenever a new commit is performed (upon add/edit/remove), `GIT_DIR` and `GIT_WORK_TREE` are set to the relevant directories. It is worth mentioning this patchset has already undergone a few rounds of testing, bug-fixing, and code review here: https://github.com/lrvick/password-store/pull/1 More input (or a merge!) would be very appreciated. -- Lance R. Vick __________________________________________________ Cell - 650.686.8819 IRC - [email protected] Website - http://lrvick.net PGP Key - http://lrvick.net/0x36C8AAA9.asc __________________________________________________
submodules.patch.sig
Description: PGP signature
From 19ecdb6007896273103a91a9dcc5223f7ef4bc62 Mon Sep 17 00:00:00 2001 From: "Lance R. Vick" <[email protected]> Date: Fri, 7 Oct 2016 01:18:23 -0700 Subject: [PATCH 1/7] multiple git repo support --- src/password-store.sh | 34 ++++++++++++++++++++++++++++++---- tests/t0600-git-multidir-tests.sh | 30 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) create mode 100755 tests/t0600-git-multidir-tests.sh diff --git a/src/password-store.sh b/src/password-store.sh index 63be840..5351073 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -17,14 +17,30 @@ X_SELECTION="${PASSWORD_STORE_X_SELECTION:-clipboard}" CLIP_TIME="${PASSWORD_STORE_CLIP_TIME:-45}" GENERATED_LENGTH="${PASSWORD_STORE_GENERATED_LENGTH:-25}" -export GIT_DIR="${PASSWORD_STORE_GIT:-$PREFIX}/.git" export GIT_WORK_TREE="${PASSWORD_STORE_GIT:-$PREFIX}" +export GIT_DIR="$GIT_WORK_TREE/.git" # # BEGIN helper functions # - +git_set_dir() { + local gwt="${PASSWORD_STORE_GIT:-$PREFIX}"; gwt="${gwt%/}" + [[ "$1" == "." ]] && return + [[ "$1" == "$gwt" ]] && return + if [ "$1" != "${1#/}" ]; then + local dir="$1" + else + local dir="$gwt/$1" + fi + if [[ -d "$dir/.git" ]]; then + export GIT_WORK_TREE="$dir" + export GIT_DIR="$dir/.git" + else + git_set_dir "$(dirname "$dir")" + fi +} git_add_file() { + git_set_dir "$1" [[ -d $GIT_DIR ]] || return git add "$1" || return [[ -n $(git status --porcelain "$1") ]] || return @@ -450,6 +466,7 @@ cmd_generate() { [[ ! $length =~ ^[0-9]+$ ]] && die "Error: pass-length \"$length\" must be a number." mkdir -p -v "$PREFIX/$(dirname "$path")" set_gpg_recipients "$(dirname "$path")" + git_set_dir "$path" local passfile="$PREFIX/$path.gpg" [[ $inplace -eq 0 && $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?" @@ -500,6 +517,7 @@ cmd_delete() { [[ $force -eq 1 ]] || yesno "Are you sure you would like to delete $path?" rm $recursive -f -v "$passfile" + git_set_dir "$path" if [[ -d $GIT_DIR && ! -e $passfile ]]; then git rm -qr "$passfile" git_commit "Remove $path from store." @@ -555,10 +573,18 @@ cmd_copy_move() { cmd_git() { if [[ $1 == "init" ]]; then + if [[ $2 ]]; then + local dir="$GIT_WORK_TREE/${@: -1}" + echo "init at $dir" + if [[ -d "$dir" ]]; then + export GIT_WORK_TREE="$dir" + export GIT_DIR="$GIT_WORK_TREE/.git" + fi + fi git "$@" || exit 1 - git_add_file "$PREFIX" "Add current contents of password store." + git_add_file "$GIT_WORK_TREE" "Add current contents of password store." - echo '*.gpg diff=gpg' > "$PREFIX/.gitattributes" + echo '*.gpg diff=gpg' > "$GIT_WORK_TREE/.gitattributes" git_add_file .gitattributes "Configure git repository for gpg file diff." git config --local diff.gpg.binary true git config --local diff.gpg.textconv "$GPG -d ${GPG_OPTS[*]}" diff --git a/tests/t0600-git-multidir-tests.sh b/tests/t0600-git-multidir-tests.sh new file mode 100755 index 0000000..46d9c46 --- /dev/null +++ b/tests/t0600-git-multidir-tests.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +test_description='Test multiple git repos' +cd "$(dirname "$0")" +. ./setup.sh + +test_expect_success 'Test init multiple repos' ' + "$PASS" init -p personal $KEY1 && + "$PASS" git init personal && + "$PASS" init -p work $KEY2 && + "$PASS" git init work && + [[ -d $PASSWORD_STORE_DIR/work/.git ]] && + [[ -d $PASSWORD_STORE_DIR/personal/.git ]] +' + +test_expect_success 'Test alter passwords across multiple repos' ' + "$PASS" generate personal/cred1 50 && + "$PASS" generate work/sub/dir/cred2 50 && + "$PASS" rm personal/cred1 && + [[ ! -e $PASSWORD_STORE_DIR/personal/cred1.gpg ]] && + [[ -e $PASSWORD_STORE_DIR/work/sub/dir/cred2.gpg ]] && + export GIT_WORK_TREE="$PASSWORD_STORE_DIR/personal" && + export GIT_DIR="$GIT_WORK_TREE/.git" && + [[ "$(git rev-list --all --count)" == "4" ]] && + export GIT_WORK_TREE="$PASSWORD_STORE_DIR/work" && + export GIT_DIR="$GIT_WORK_TREE/.git" && + [[ "$(git rev-list --all --count)" == "3" ]] +' + +test_done From 7182055b9b363f8ee0df312c561803adba8cc427 Mon Sep 17 00:00:00 2001 From: "Lance R. Vick" <[email protected]> Date: Wed, 12 Oct 2016 01:15:17 -0700 Subject: [PATCH 2/7] handle submodules --- src/password-store.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/password-store.sh b/src/password-store.sh index 5351073..2e4e311 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -32,7 +32,7 @@ git_set_dir() { else local dir="$gwt/$1" fi - if [[ -d "$dir/.git" ]]; then + if [[ -d "$dir/.git" ]] || [[ -f "$dir/.git" ]]; then export GIT_WORK_TREE="$dir" export GIT_DIR="$dir/.git" else @@ -41,14 +41,12 @@ git_set_dir() { } git_add_file() { git_set_dir "$1" - [[ -d $GIT_DIR ]] || return git add "$1" || return [[ -n $(git status --porcelain "$1") ]] || return git_commit "$2" } git_commit() { local sign="" - [[ -d $GIT_DIR ]] || return [[ $(git config --bool --get pass.signcommits) == "true" ]] && sign="-S" git commit $sign -m "$1" } @@ -588,6 +586,8 @@ cmd_git() { git_add_file .gitattributes "Configure git repository for gpg file diff." git config --local diff.gpg.binary true git config --local diff.gpg.textconv "$GPG -d ${GPG_OPTS[*]}" + elif [[ $1 == "submodule" ]]; then + git -C "$GIT_WORK_TREE" "$@" elif [[ -d $GIT_DIR ]]; then tmpdir nowarn #Defines $SECURE_TMPDIR. We don't warn, because at most, this only copies encrypted files. export TMPDIR="$SECURE_TMPDIR" From 0a0c758f409c84bd2f0a711ec8eb3ceff7838fb1 Mon Sep 17 00:00:00 2001 From: "Lance R. Vick" <[email protected]> Date: Wed, 12 Oct 2016 02:04:53 -0700 Subject: [PATCH 3/7] pass rm with submodules --- src/password-store.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/password-store.sh b/src/password-store.sh index 2e4e311..fb227d8 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -516,7 +516,7 @@ cmd_delete() { rm $recursive -f -v "$passfile" git_set_dir "$path" - if [[ -d $GIT_DIR && ! -e $passfile ]]; then + if [[ ! -e $passfile ]]; then git rm -qr "$passfile" git_commit "Remove $path from store." fi From 9f198dae4b964f4ac1e2b5f8ee30e721ceb9e1ad Mon Sep 17 00:00:00 2001 From: "Lance R. Vick" <[email protected]> Date: Wed, 12 Oct 2016 02:05:03 -0700 Subject: [PATCH 4/7] submodule tests --- tests/t0700-git-submodule-tests.sh | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 tests/t0700-git-submodule-tests.sh diff --git a/tests/t0700-git-submodule-tests.sh b/tests/t0700-git-submodule-tests.sh new file mode 100755 index 0000000..3881455 --- /dev/null +++ b/tests/t0700-git-submodule-tests.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +test_description='Test multiple git repos' +cd "$(dirname "$0")" +. ./setup.sh + +test_expect_success 'Test init multiple submodules' ' + "$PASS" init $KEY1 && + "$PASS" git init && + "$PASS" git submodule add ./ personal && + "$PASS" git submodule add ./ work && + [[ -f $PASSWORD_STORE_DIR/work/.git ]] && + [[ -f $PASSWORD_STORE_DIR/personal/.git ]] +' + +test_expect_success 'Test alter passwords across multiple submodules' ' + "$PASS" generate personal/cred1 50 && + "$PASS" generate work/sub/dir/cred2 50 && + "$PASS" rm personal/cred1 && + [[ ! -e $PASSWORD_STORE_DIR/personal/cred1.gpg ]] && + [[ -e $PASSWORD_STORE_DIR/work/sub/dir/cred2.gpg ]] && + export GIT_WORK_TREE="$PASSWORD_STORE_DIR/personal" && + export GIT_DIR="$GIT_WORK_TREE/.git" && + git log >> /tmp/gitlog.txt && + [[ "$(git rev-list --all --count)" == "4" ]] && + export GIT_WORK_TREE="$PASSWORD_STORE_DIR/work" && + export GIT_DIR="$GIT_WORK_TREE/.git" && + [[ "$(git rev-list --all --count)" == "3" ]] +' + +test_done From 97c264be2f688b346a5b6a4653831ebadccfad99 Mon Sep 17 00:00:00 2001 From: "Lance R. Vick" <[email protected]> Date: Wed, 12 Oct 2016 03:45:14 -0700 Subject: [PATCH 5/7] fix remaining explicit directory checks that break submodules --- src/password-store.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/password-store.sh b/src/password-store.sh index fb227d8..a228ae3 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -32,7 +32,7 @@ git_set_dir() { else local dir="$gwt/$1" fi - if [[ -d "$dir/.git" ]] || [[ -f "$dir/.git" ]]; then + if [[ -e "$dir/.git" ]]; then export GIT_WORK_TREE="$dir" export GIT_DIR="$dir/.git" else @@ -291,7 +291,7 @@ cmd_init() { if [[ $# -eq 1 && -z $1 ]]; then [[ ! -f "$gpg_id" ]] && die "Error: $gpg_id does not exist and so cannot be removed." rm -v -f "$gpg_id" || exit 1 - if [[ -d $GIT_DIR ]]; then + if [[ -e $GIT_DIR ]]; then git rm -qr "$gpg_id" git_commit "Deinitialize ${gpg_id}${id_path:+ ($id_path)}." fi @@ -557,7 +557,7 @@ cmd_copy_move() { mv $interactive -v "$old_path" "$new_path" || exit 1 [[ -e "$new_path" ]] && reencrypt_path "$new_path" - if [[ -d $GIT_DIR && ! -e $old_path ]]; then + if [[ -e $GIT_DIR && ! -e $old_path ]]; then git rm -qr "$old_path" git_add_file "$new_path" "Rename ${1} to ${2}." fi @@ -588,7 +588,7 @@ cmd_git() { git config --local diff.gpg.textconv "$GPG -d ${GPG_OPTS[*]}" elif [[ $1 == "submodule" ]]; then git -C "$GIT_WORK_TREE" "$@" - elif [[ -d $GIT_DIR ]]; then + elif [[ -e $GIT_DIR ]]; then tmpdir nowarn #Defines $SECURE_TMPDIR. We don't warn, because at most, this only copies encrypted files. export TMPDIR="$SECURE_TMPDIR" git "$@" From 472b9b91def1f9c51f815404c304fcd149e230e2 Mon Sep 17 00:00:00 2001 From: "Lance R. Vick" <[email protected]> Date: Wed, 12 Oct 2016 03:46:15 -0700 Subject: [PATCH 6/7] fix test naming --- tests/t0600-git-multidir-tests.sh | 30 ------------------------------ tests/t0600-git-multirepo-tests.sh | 30 ++++++++++++++++++++++++++++++ tests/t0700-git-submodule-tests.sh | 2 +- 3 files changed, 31 insertions(+), 31 deletions(-) delete mode 100755 tests/t0600-git-multidir-tests.sh create mode 100755 tests/t0600-git-multirepo-tests.sh diff --git a/tests/t0600-git-multidir-tests.sh b/tests/t0600-git-multidir-tests.sh deleted file mode 100755 index 46d9c46..0000000 --- a/tests/t0600-git-multidir-tests.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -test_description='Test multiple git repos' -cd "$(dirname "$0")" -. ./setup.sh - -test_expect_success 'Test init multiple repos' ' - "$PASS" init -p personal $KEY1 && - "$PASS" git init personal && - "$PASS" init -p work $KEY2 && - "$PASS" git init work && - [[ -d $PASSWORD_STORE_DIR/work/.git ]] && - [[ -d $PASSWORD_STORE_DIR/personal/.git ]] -' - -test_expect_success 'Test alter passwords across multiple repos' ' - "$PASS" generate personal/cred1 50 && - "$PASS" generate work/sub/dir/cred2 50 && - "$PASS" rm personal/cred1 && - [[ ! -e $PASSWORD_STORE_DIR/personal/cred1.gpg ]] && - [[ -e $PASSWORD_STORE_DIR/work/sub/dir/cred2.gpg ]] && - export GIT_WORK_TREE="$PASSWORD_STORE_DIR/personal" && - export GIT_DIR="$GIT_WORK_TREE/.git" && - [[ "$(git rev-list --all --count)" == "4" ]] && - export GIT_WORK_TREE="$PASSWORD_STORE_DIR/work" && - export GIT_DIR="$GIT_WORK_TREE/.git" && - [[ "$(git rev-list --all --count)" == "3" ]] -' - -test_done diff --git a/tests/t0600-git-multirepo-tests.sh b/tests/t0600-git-multirepo-tests.sh new file mode 100755 index 0000000..46d9c46 --- /dev/null +++ b/tests/t0600-git-multirepo-tests.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +test_description='Test multiple git repos' +cd "$(dirname "$0")" +. ./setup.sh + +test_expect_success 'Test init multiple repos' ' + "$PASS" init -p personal $KEY1 && + "$PASS" git init personal && + "$PASS" init -p work $KEY2 && + "$PASS" git init work && + [[ -d $PASSWORD_STORE_DIR/work/.git ]] && + [[ -d $PASSWORD_STORE_DIR/personal/.git ]] +' + +test_expect_success 'Test alter passwords across multiple repos' ' + "$PASS" generate personal/cred1 50 && + "$PASS" generate work/sub/dir/cred2 50 && + "$PASS" rm personal/cred1 && + [[ ! -e $PASSWORD_STORE_DIR/personal/cred1.gpg ]] && + [[ -e $PASSWORD_STORE_DIR/work/sub/dir/cred2.gpg ]] && + export GIT_WORK_TREE="$PASSWORD_STORE_DIR/personal" && + export GIT_DIR="$GIT_WORK_TREE/.git" && + [[ "$(git rev-list --all --count)" == "4" ]] && + export GIT_WORK_TREE="$PASSWORD_STORE_DIR/work" && + export GIT_DIR="$GIT_WORK_TREE/.git" && + [[ "$(git rev-list --all --count)" == "3" ]] +' + +test_done diff --git a/tests/t0700-git-submodule-tests.sh b/tests/t0700-git-submodule-tests.sh index 3881455..35eab55 100755 --- a/tests/t0700-git-submodule-tests.sh +++ b/tests/t0700-git-submodule-tests.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -test_description='Test multiple git repos' +test_description='Test git submodules' cd "$(dirname "$0")" . ./setup.sh From 0c840499f2c98427576e52a9d32d9e778e5d813d Mon Sep 17 00:00:00 2001 From: "Lance R. Vick" <[email protected]> Date: Fri, 25 Nov 2016 20:17:39 -0800 Subject: [PATCH 7/7] wrap init to not expect git to be already initialized --- src/password-store.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/password-store.sh b/src/password-store.sh index a228ae3..b1b7097 100755 --- a/src/password-store.sh +++ b/src/password-store.sh @@ -301,11 +301,15 @@ cmd_init() { printf "%s\n" "$@" > "$gpg_id" local id_print="$(printf "%s, " "$@")" echo "Password store initialized for ${id_print%, }${id_path:+ ($id_path)}" - git_add_file "$gpg_id" "Set GPG id to ${id_print%, }${id_path:+ ($id_path)}." + if [[ -e $GIT_DIR ]]; then + git_add_file "$gpg_id" "Set GPG id to ${id_print%, }${id_path:+ ($id_path)}." + fi fi reencrypt_path "$PREFIX/$id_path" - git_add_file "$PREFIX/$id_path" "Reencrypt password store using new GPG id ${id_print%, }${id_path:+ ($id_path)}." + if [[ -e $GIT_DIR ]]; then + git_add_file "$PREFIX/$id_path" "Reencrypt password store using new GPG id ${id_print%, }${id_path:+ ($id_path)}." + fi } cmd_show() { @@ -573,7 +577,6 @@ cmd_git() { if [[ $1 == "init" ]]; then if [[ $2 ]]; then local dir="$GIT_WORK_TREE/${@: -1}" - echo "init at $dir" if [[ -d "$dir" ]]; then export GIT_WORK_TREE="$dir" export GIT_DIR="$GIT_WORK_TREE/.git"
_______________________________________________ Password-Store mailing list [email protected] https://lists.zx2c4.com/mailman/listinfo/password-store
