class VaultUpdate
Constants
- VERSION
Public Instance Methods
run()
click to toggle source
# File lib/vault-update.rb, line 15 def run if opts[:history] secret_history.sort_by { |ts, _data| ts }[-history_fetch_size..-1].each do |ts, data| puts "#{Time.at(ts.to_s.to_i)}:".colorize(:green) puts JSON.pretty_generate(data) + "\n\n" end elsif opts[:last] puts JSON.pretty_generate( (secret_history.sort_by { |ts, _data| ts }.last || fail(NoHistoryError))[1] ) elsif opts[:rollback] rollback_secret elsif opts[:current] puts JSON.pretty_generate(vault_read(opts[:path]) || fail(NoValueError)) else update end rescue MissingInputError, TypeError => e raise e unless e.class == TypeError && e.message == 'no implicit conversion of nil into String' Trollop.die 'KEY and VALUE must be provided' rescue NoUpdateError puts 'Nothing to do' exit 0 rescue NoHistoryError puts 'ERROR: '.colorize(:red) + "There is no history for #{opts[:path]}" exit 2 rescue NoValueError puts 'ERROR: '.colorize(:red) + "There is no current value for #{opts[:path]}" exit 3 end
Private Instance Methods
debug?()
click to toggle source
# File lib/vault-update.rb, line 73 def debug? ENV['DEBUG'] end
history_fetch_size()
click to toggle source
# File lib/vault-update.rb, line 48 def history_fetch_size opts[:history] > secret_history.keys.count ? secret_history.keys.count : opts[:history] end
opts()
click to toggle source
# File lib/vault-update.rb, line 143 def opts @opts ||= begin opts = Trollop.options do version "vault-update #{VaultUpdate::VERSION} (c) 2017 Evertrue" banner( "Safely update Vault secrets (with rollbacks and history!)\n\n" \ "Usage:\n" \ " vault-update [options] -p SECRET_PATH KEY VALUE\n" \ "\nEnvironment Variables:\n" \ " VAULT_ADDR (required)\n" \ " VAULT_TOKEN (required)\n" \ "\nOptions:" ) opt :rollback, 'Roll back to previous release', short: 'r' opt :path, 'Secret path to update', short: 'p', required: true, type: String opt :history, 'Show the last N entries of history', short: 's', type: Integer opt :last, 'Show the last value', short: 'l' opt :current, 'Show the current contents of the secret', short: 'c' end fail 'VAULT_ADDR and VAULT_TOKEN must be set' unless ENV['VAULT_ADDR'] && ENV['VAULT_TOKEN'] opts end end
previous_update()
click to toggle source
# File lib/vault-update.rb, line 129 def previous_update @previous_update ||= begin return nil unless (r = secret_history).any? r[r.keys.sort.last] # Return the value with the highest key end end
rollback_secret()
click to toggle source
# File lib/vault-update.rb, line 77 def rollback_secret fail NoHistoryError unless previous_update current_secret_value = vault_read opts[:path] # Update history with {} if empty now secret_history[Time.now.to_i] = (current_secret_value || {}) vault_write "#{opts[:path]}_history", secret_history puts "Writing to #{opts[:path]}:\n".bold + JSON.pretty_generate(previous_update) unless debug? vault_write opts[:path], previous_update end
secret_history()
click to toggle source
# File lib/vault-update.rb, line 136 def secret_history @secret_history ||= begin r = vault_read("#{opts[:path]}_history") r ? r.dup : {} end end
update()
click to toggle source
# File lib/vault-update.rb, line 52 def update update_value = ARGV.pop json_value = true # JSON is optional in the value field, so we have this funny business update_value = ( begin JSON.parse update_value rescue JSON::ParserError json_value = false update_value end ) update_key = ARGV.pop raise(MissingInputError) unless json_value || update_key update_secret(json_value ? update_value : { update_key.to_sym => update_value }) end
update_secret(update_hash)
click to toggle source
# File lib/vault-update.rb, line 89 def update_secret(update_hash) data = if (current_secret_value = vault_read(opts[:path])) current_secret_value = current_secret_value.stringify_keys merged_value = current_secret_value.merge update_hash.stringify_keys if debug? puts "current_secret_value: ".colorize(:blue) + current_secret_value.inspect puts "merged_value: ".colorize(:blue) + merged_value.inspect end fail NoUpdateError if current_secret_value == merged_value secret_history[Time.now.to_i] = current_secret_value vault_write "#{opts[:path]}_history", secret_history current_secret_value else puts "update_hash: ".colorize(:blue) + update_hash.inspect update_hash end puts "data: ".colorize(:blue) + data.inspect if debug? puts "Applying changes to #{opts[:path]}:\n".bold puts Diffy::Diff.new( ( if current_secret_value JSON.pretty_generate(current_secret_value) + "\n" # else "\n" end ), JSON.pretty_generate(data) + "\n" ).to_s(:color) vault_write opts[:path], data end
vault()
click to toggle source
# File lib/vault-update.rb, line 187 def vault @vault ||= Vault::Client.new end
vault_read(path)
click to toggle source
# File lib/vault-update.rb, line 177 def vault_read(path) r = vault.with_retries(Vault::HTTPConnectionError) do |attempt, e| puts "Received exception #{e} from Vault - attempt #{attempt}" if e vault.logical.read(path) end res = r ? r.data : nil puts "Read from #{path}:\n".colorize(:blue) + res.to_json if debug? res end
vault_write(path, data)
click to toggle source
# File lib/vault-update.rb, line 169 def vault_write(path, data) puts "Writing to #{path}:\n".colorize(:blue) + data.inspect if debug? vault.with_retries(Vault::HTTPConnectionError) do |attempt, e| puts "Received exception #{e} from Vault - attempt #{attempt}" if e vault.logical.write(path, data) end end