moss

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit a0ad2a13fb1d3372885810185bc8b4b1b485eaae
parent c697fa517c2d1daf2752b3f369731ee67212cd0a
Author: Daniel Barlow <dan@telent.net>
Date:   Fri, 13 Aug 2021 17:29:42 +0100

add "moss rebuild", re-encrypts all the secrets

Diffstat:
MREADME.md | 2--
Mfeatures/moss.feature | 10+++++++++-
Mfeatures/step_definitions/steps.rb | 32++++++++++++++++++++++++++++----
Mmoss.rb | 11+++++++++++
4 files changed, 48 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md @@ -179,9 +179,7 @@ security requirements, look elsewhere. users on your machine - ## TO DO * copy to clipboard -* support re-encrypting a subtree when recipients change * warn or fail if iffy store permissions? diff --git a/features/moss.feature b/features/moss.feature @@ -25,7 +25,7 @@ Feature: command-line password management Scenario: it overwrites secrets when forced Given I am using a temporary password store - When I store a secret for "home/ebay" with content "first" + And there is a secret for "home/ebay" with content "first" And I force store a secret for "home/ebay" with content "second" Then "home/ebay.age" plaintext is "second" @@ -143,3 +143,11 @@ Feature: command-line password management When I run "moss cat" Then it shows a usage message for "cat" + + Scenario: I can re-encrypt files + Given I am using a temporary password store + And there is a secret for "home/ebay" with content "horsebattery" + And there is a secret for "home/catfish" with content "chocolate" + When I add a recipient for the identity "partner.key" + And I re-encrypt the store + Then I can decrypt "home/catfish" with key "partner.key" to "chocolate" diff --git a/features/step_definitions/steps.rb b/features/step_definitions/steps.rb @@ -61,6 +61,11 @@ Then('{string} does not exist') do |path| expect(store_path(path)).not_to exist end +# yes, this step is a Given and a When with different text +Given("there is a secret for {string} with content {string}") do |name, content| + @stored_secret_name = name + shell "echo -n #{content} | #{MOSS} insert #{name.inspect}" +end When("I store a secret for {string} with content {string}") do |name, content| @stored_secret_name = name @@ -94,12 +99,19 @@ Then("{string} plaintext is {string}") do |name, expected| expect(plaintext).to eq expected end -Then("I can decrypt it with key {string} to {string}") do |keyfile, expected| - path_name = store_path(@stored_secret_name + '.age') +def decrypt(secret_name, keyfile) + path_name = store_path(secret_name + '.age') contents = File.read(path_name) expect(contents).to match /AGE ENCRYPTED FILE/ - plaintext = shell "age -i fixtures/keys/#{keyfile} -d #{path_name.to_s.inspect}" - expect(plaintext).to eq expected + shell "age -i fixtures/keys/#{keyfile} -d #{path_name.to_s.inspect}" +end + +Then("I can decrypt it with key {string} to {string}") do |keyfile, expected| + expect(decrypt(@stored_secret_name, keyfile)).to eq expected +end + +Then("I can decrypt {string} with key {string} to {string}") do |secret_name, keyfile, expected| + expect(decrypt(secret_name, keyfile)).to eq expected end Then("I cannot decrypt it with key {string}") do |keyfile| @@ -154,6 +166,14 @@ Given("there are recipient files in different subtrees") do |table| end end +When('I add a recipient for the identity {string}') do |keyfile| + add_to_store(".recipients", + store_path(".recipients").read + + "\n" + + recipient_for_identity("fixtures/keys/#{keyfile}")) +end + + Given("the store is version-controlled") do shell "cd #{ENV["MOSS_HOME"]}/store && git init --initial-branch=main" end @@ -226,3 +246,7 @@ Then("it shows a usage message for {string}") do |command| expect(@exit_status).to be > 0 expect(@i_see).to match /usage: moss #{command} \<file\>/ end + +When('I re-encrypt the store') do + @i_see = shell "#{MOSS} rebuild" +end diff --git a/moss.rb b/moss.rb @@ -127,6 +127,13 @@ class Moss pathname.delete end + def rebuild_store(verbose:) + secrets.each do |name| + warn name if verbose + write_secret(name, read_secret(name), overwrite: true) + end + end + def secrets Dir[store.join("**/*.age")].map { |n| Pathname.new(n).relative_path_from(store).sub_ext("").to_s @@ -341,6 +348,10 @@ def cli MOSS.remove_secret(secret) end + command :rebuild, "re-encrypt all secrets in the store (for use e.g. after changing recipients)" do |quiet: false| + MOSS.rebuild_store(verbose: ! quiet) + end + command :git, "perform git operation in store" do |* git_command| MOSS.git_operation(git_command) end