vault-update 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +33 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +94 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/vault-update +4 -0
- data/lib/vault-update.rb +163 -0
- data/lib/vault-update/version.rb +3 -0
- data/vault-update.gemspec +31 -0
- metadata +143 -0
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
data/.rspec
ADDED
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
data/Gemfile
ADDED
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
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
data/exe/vault-update
ADDED
data/lib/vault-update.rb
ADDED
@@ -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,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: []
|