vault-update 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 64568df3de6bce221ef106e106b16122acaa532b
4
+ data.tar.gz: 232e7b2384632d90d9d84e500214e4023dc864fa
5
+ SHA512:
6
+ metadata.gz: 96ad1c2b7b6091ccc4ac8d662198bd724d47f4e19b6589330b7cf2a33945690349ae74db74c62e1bfbc606e09ad765532a2d6c07f18d37a856a74cdbe48b39a2
7
+ data.tar.gz: cbd564b8111d938cfb0f7bf445f45db1b2e332e2f249dcb9173bd8e302ab3e2276ebbfebf6a839f593b9cafd82fd8161362f6c52019898786c4602ef74b7edaf
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ LATEST_CHANGELOG.md
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,33 @@
1
+ Metrics/LineLength:
2
+ Max: 100
3
+
4
+ Metrics/MethodLength:
5
+ Max: 50
6
+
7
+ Metrics/ClassLength:
8
+ Max: 300
9
+
10
+ # Lining up the attributes of some methods is advantageous for readability
11
+ Style/SpaceBeforeFirstArg:
12
+ Enabled: false
13
+
14
+ Style/ExtraSpacing:
15
+ Enabled: false
16
+
17
+ Style/SpaceAroundOperators:
18
+ Enabled: false
19
+
20
+ Documentation:
21
+ Enabled: false
22
+
23
+ Metrics/CyclomaticComplexity:
24
+ Max: 12
25
+
26
+ Metrics/PerceivedComplexity:
27
+ Max: 12
28
+
29
+ Metrics/AbcSize:
30
+ Max: 50
31
+
32
+ Style/FileName:
33
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.13.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in vault-update.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Eric Herot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # VaultUpdate
2
+
3
+ A tool to safely update Vault. Existing data is stored in history (which means rollbacks are supported). Diffs are printed. Individual keys can be updated at once.
4
+
5
+ # Installation
6
+
7
+ Install it yourself:
8
+
9
+ ```
10
+ $ gem install vault-update
11
+ ```
12
+
13
+ # Usage
14
+
15
+ First, ensure that the `VAULT_ADDR` and `VAULT_TOKEN` environment variables are set, then...
16
+
17
+ The basic summary:
18
+
19
+ ```
20
+ $ vault-update --help
21
+ Safely update Vault secrets (with rollbacks and history!)
22
+
23
+ Usage:
24
+ vault-update [options] -p SECRET_PATH KEY VALUE
25
+
26
+ Environment Variables:
27
+ VAULT_ADDR (required)
28
+ VAULT_TOKEN (required)
29
+
30
+ Options:
31
+ -r, --rollback Roll back to previous release
32
+ -p, --path=<s> Secret path to update
33
+ -s, --history=<i> Show the last N entries of history
34
+ -l, --last Show the last value
35
+ -h, --help Show this message
36
+ ```
37
+
38
+ ## Write a string value to a key
39
+
40
+ ```
41
+ $ vault-update -p secret/example mykey myvalue
42
+ Applying changes to secret/example:
43
+
44
+ -null
45
+ +{
46
+ + "mykey": "myvalue"
47
+ +}
48
+ ```
49
+
50
+ ## Roll the secret back to its previous value
51
+
52
+ ```
53
+ $ vault-update -p secret/example -r
54
+ Writing to secret/example:
55
+ {"mykey":"myvalue"}
56
+ ```
57
+
58
+
59
+ ## Show the current contents of the secret
60
+
61
+ ```
62
+ $ vault-update -p secret/example -c
63
+ {
64
+ "mykey": "myvalue"
65
+ }
66
+ ```
67
+
68
+ ## Show the previous value (but do not roll back)
69
+
70
+ ```
71
+ $ vault-update -p secret/example -l
72
+ {
73
+ "mykey": "oldvalue"
74
+ }
75
+ ```
76
+
77
+ ## Show the last N history entries
78
+
79
+ ```
80
+ $ vault-update -p secret/example -s 2
81
+ 2016-10-26 17:14:56 -0400:
82
+ {
83
+ "mykey": "reallyoldvalue"
84
+ }
85
+
86
+ 2016-10-26 17:15:03 -0400:
87
+ {
88
+ "mykey": "oldvalue"
89
+ }
90
+ ```
91
+
92
+ # License
93
+
94
+ The gem is available as open source under the terms of the Apache license.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'vault/update'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require 'pry'
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/vault-update ADDED
@@ -0,0 +1,4 @@
1
+ require 'vault-update/version'
2
+ require 'vault-update'
3
+
4
+ VaultUpdate.new.run
@@ -0,0 +1,163 @@
1
+ require 'vault-update/version'
2
+ require 'vault'
3
+ require 'trollop'
4
+ require 'json'
5
+ require 'diffy'
6
+
7
+ class MissingInputError < StandardError; end
8
+ class NoHistoryError < StandardError; end
9
+ class NoUpdateError < StandardError; end
10
+
11
+ class VaultUpdate
12
+ def run
13
+ if opts[:history]
14
+ secret_history.sort_by { |ts, _data| ts }[-history_fetch_size..-1].each do |ts, data|
15
+ puts "#{Time.at(ts.to_s.to_i)}:"
16
+ puts JSON.pretty_generate(data) + "\n\n"
17
+ end
18
+ elsif opts[:last]
19
+ puts JSON.pretty_generate(secret_history.sort_by { |ts, _data| ts }.last[1])
20
+ elsif opts[:rollback]
21
+ rollback_secret
22
+ elsif opts[:current]
23
+ puts JSON.pretty_generate(vault_read(opts[:path]))
24
+ else
25
+ update
26
+ end
27
+ rescue MissingInputError, TypeError => e
28
+ raise e unless e.class == TypeError && e.message == 'no implicit conversion of nil into String'
29
+ Trollop.die 'KEY and VALUE must be provided'
30
+ rescue NoUpdateError
31
+ puts 'Nothing to do'
32
+ exit 0
33
+ rescue NoHistoryError
34
+ puts "ERROR: There is no history for #{opts[:path]}"
35
+ exit 2
36
+ end
37
+
38
+ private
39
+
40
+ def history_fetch_size
41
+ opts[:history] > secret_history.count ? secret_history.count : opts[:history]
42
+ end
43
+
44
+ def update
45
+ update_value = ARGV.pop
46
+
47
+ # JSON is optional in the value field, so we have this funny business
48
+ update_value = (
49
+ begin
50
+ JSON.parse update_value
51
+ rescue JSON::ParserError
52
+ update_value
53
+ end
54
+ )
55
+
56
+ update_key = ARGV.pop
57
+
58
+ raise(MissingInputError) unless update_key && update_value
59
+
60
+ update_secret update_key.to_sym => update_value
61
+ end
62
+
63
+ def debug?
64
+ ENV['DEBUG']
65
+ end
66
+
67
+ def rollback_secret
68
+ raise NoHistoryError unless previous_update
69
+ current_secret_value = vault_read opts[:path]
70
+
71
+ # Update history with {} if empty now
72
+ secret_history[Time.now.to_i] = (current_secret_value || {})
73
+ vault_write "#{opts[:path]}_history", secret_history
74
+
75
+ puts "Writing to #{opts[:path]}:\n#{previous_update.to_json}" unless debug?
76
+ vault_write opts[:path], previous_update
77
+ end
78
+
79
+ def update_secret(update_hash)
80
+ data =
81
+ if (current_secret_value = vault_read opts[:path])
82
+ secret_history[Time.now.to_i] = current_secret_value
83
+ vault_write "#{opts[:path]}_history", secret_history
84
+ current_secret_value.merge(update_hash)
85
+ else
86
+ update_hash
87
+ end
88
+
89
+ if debug?
90
+ puts "current_secret_value: #{current_secret_value}"
91
+ puts "update_hash: #{update_hash}"
92
+ end
93
+
94
+ raise NoUpdateError if current_secret_value == data
95
+
96
+ puts "Applying changes to #{opts[:path]}:\n\n"
97
+ puts Diffy::Diff.new(
98
+ JSON.pretty_generate(current_secret_value) + "\n", # What to do if no existing content
99
+ JSON.pretty_generate(data) + "\n"
100
+ ).to_s(:color)
101
+
102
+ vault_write opts[:path], data
103
+ end
104
+
105
+ def previous_update
106
+ @previous_update ||= begin
107
+ return nil unless (r = secret_history).any?
108
+ r[r.keys.sort.last] # Return the value with the highest key
109
+ end
110
+ end
111
+
112
+ def secret_history
113
+ @secret_history ||= begin
114
+ r = vault_read("#{opts[:path]}_history")
115
+ r ? r.dup : {}
116
+ end
117
+ end
118
+
119
+ def opts
120
+ @opts ||= begin
121
+ opts = Trollop.options do
122
+ banner(
123
+ "Safely update Vault secrets (with rollbacks and history!)\n\n" \
124
+ "Usage:\n" \
125
+ " vault-update [options] -p SECRET_PATH KEY VALUE\n" \
126
+ "\nEnvironment Variables:\n" \
127
+ " VAULT_ADDR (required)\n" \
128
+ " VAULT_TOKEN (required)\n" \
129
+ "\nOptions:"
130
+ )
131
+ opt :rollback, 'Roll back to previous release', short: 'r'
132
+ opt :path, 'Secret path to update', short: 'p', required: true, type: String
133
+ opt :history, 'Show the last N entries of history', short: 's', type: Integer
134
+ opt :last, 'Show the last value', short: 'l'
135
+ opt :current, 'Show the current contents of the secret', short: 'c'
136
+ end
137
+ raise 'VAULT_ADDR and VAULT_TOKEN must be set' unless ENV['VAULT_ADDR'] && ENV['VAULT_TOKEN']
138
+ opts
139
+ end
140
+ end
141
+
142
+ def vault_write(path, data)
143
+ puts "Writing to #{path}:\n#{data.inspect}" if debug?
144
+ vault.with_retries(Vault::HTTPConnectionError) do |attempt, e|
145
+ puts "Received exception #{e} from Vault - attempt #{attempt}" if e
146
+ vault.logical.write(path, data)
147
+ end
148
+ end
149
+
150
+ def vault_read(path)
151
+ r = vault.with_retries(Vault::HTTPConnectionError) do |attempt, e|
152
+ puts "Received exception #{e} from Vault - attempt #{attempt}" if e
153
+ vault.logical.read(path)
154
+ end
155
+ res = r ? r.data : nil
156
+ puts "Read from #{path}:\n#{res.to_json}" if debug?
157
+ res
158
+ end
159
+
160
+ def vault
161
+ @vault ||= Vault::Client.new
162
+ end
163
+ end
@@ -0,0 +1,3 @@
1
+ class VaultUpdate
2
+ VERSION = '1.0.2'.freeze
3
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'vault-update/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'vault-update'
8
+ spec.version = VaultUpdate::VERSION
9
+ spec.authors = ['Eric Herot']
10
+ spec.email = ['devops@evertrue.com']
11
+
12
+ spec.summary = 'Safely updates a Vault secret while also keeping history.'
13
+ # spec.description = 'TODO: Write a longer description or delete this line.'
14
+ spec.homepage = 'https://evertrue.github.io'
15
+ spec.license = 'Apache'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_dependency 'diffy'
25
+ spec.add_dependency 'trollop'
26
+ spec.add_dependency 'vault'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.13'
29
+ spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'rspec', '~> 3.0'
31
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vault-update
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Eric Herot
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-10-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: diffy
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: trollop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: vault
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ description:
98
+ email:
99
+ - devops@evertrue.com
100
+ executables:
101
+ - vault-update
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".rubocop.yml"
108
+ - ".travis.yml"
109
+ - Gemfile
110
+ - LICENSE.txt
111
+ - README.md
112
+ - Rakefile
113
+ - bin/console
114
+ - bin/setup
115
+ - exe/vault-update
116
+ - lib/vault-update.rb
117
+ - lib/vault-update/version.rb
118
+ - vault-update.gemspec
119
+ homepage: https://evertrue.github.io
120
+ licenses:
121
+ - Apache
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.5.1
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Safely updates a Vault secret while also keeping history.
143
+ test_files: []