constancy 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7ada530fb88ee051c876720fd885e77cf72ca7fcd5428a1b67b2ee4af6a8783
4
- data.tar.gz: 55258aafabeb7c9e59fa99761f09ed7840271f4a0c48e621fa46f4c5a62b4059
3
+ metadata.gz: 4f4ece9e3ea596821b57e8e4e2f4e11d70833df49f3480082a4438a1a018a69b
4
+ data.tar.gz: 5cead608d291729245435375774093004032877b5e4e447aa7ebf0ff8a7b0e2a
5
5
  SHA512:
6
- metadata.gz: dea0a26adaa5c4c7497ce11748353c4219549fdff1a4a0035d45ec7620cda19b013d7f85f4d97da4167e8940eebc03ede5717d0a1c04092c1525468721df3b0b
7
- data.tar.gz: f59c3ae823a87ac7e8c47ccea89cbc035a6676025c65b1a42907eae2567d5efee5f3413a145d7a554b827cf6a49d669ccc702947226b5e404a87360f6d605b22
6
+ metadata.gz: 7b40eb6bc0491fce5719d10c44cd11b154f71ea6415a5f5564b116ed7814a8d796deb046cf18e6d74cf17b8da15a5726a094141ae491506d1e5a3739c192f6b6
7
+ data.tar.gz: c45737ecc9a1c1aa92b0bfb46594dbe5ce8be6dac55f4e9efb3e30655b65bec9ec1cccec1895a0805da86c9b8bd6193f5116b11798c3e2a1a24d2f6d3b159d24
data/README.md CHANGED
@@ -20,14 +20,14 @@ synchronize the changes from the filesystem to Consul.
20
20
  local:consul/config => consul:dc1:config/myapp
21
21
  Keys scanned: 80
22
22
 
23
- UPDATE config/myapp/prod/ip-whitelist.json
23
+ UPDATE config/myapp/prod/ip-allowlist.json
24
24
  -------------------------------------------------------------------------------------
25
25
  -["10.8.0.0/16"]
26
26
  +["10.8.0.0/16","10.9.10.0/24"]
27
27
  -------------------------------------------------------------------------------------
28
28
 
29
29
  Keys to update: 1
30
- ~ config/myapp/prod/ip-whitelist.json
30
+ ~ config/myapp/prod/ip-allowlist.json
31
31
 
32
32
  You can also limit your command to specific synchronization targets by using
33
33
  the `--target` flag:
@@ -38,19 +38,19 @@ the `--target` flag:
38
38
  local:consul/config => consul:dc1:config/myapp
39
39
  Keys scanned: 80
40
40
 
41
- UPDATE config/myapp/prod/ip-whitelist.json
41
+ UPDATE config/myapp/prod/ip-allowlist.json
42
42
  -------------------------------------------------------------------------------------
43
43
  -["10.8.0.0/16"]
44
44
  +["10.8.0.0/16","10.9.10.0/24"]
45
45
  -------------------------------------------------------------------------------------
46
46
 
47
47
  Keys to update: 1
48
- ~ config/myapp/prod/ip-whitelist.json
48
+ ~ config/myapp/prod/ip-allowlist.json
49
49
 
50
50
  Do you want to push these changes?
51
51
  Enter 'yes' to continue: yes
52
52
 
53
- UPDATE config/myapp/prod/ip-whitelist.json OK
53
+ UPDATE config/myapp/prod/ip-allowlist.json OK
54
54
 
55
55
  Run `constancy --help` for additional options and commands.
56
56
 
@@ -127,6 +127,9 @@ required. An example `constancy.yml` is below including explanatory comments:
127
127
  # 'none': expect no Consul token (although env vars will be used if they are set)
128
128
  # 'env': expect Consul token to be set in CONSUL_TOKEN or CONSUL_HTTP_TOKEN
129
129
  # 'vault': read Consul token from Vault based on settings in the 'vault' section
130
+ # 'vault.<label>': a named Vault token source, eg `vault.us-east-1` or `vault.dev`
131
+ # NOTE: labels must begin with a letter and may contain only (ASCII) letters,
132
+ # numbers, hyphens, and underscores
130
133
 
131
134
  # the vault section is only necessary if consul.token_source is set to 'vault'
132
135
  vault:
@@ -146,6 +149,15 @@ required. An example `constancy.yml` is below including explanatory comments:
146
149
  # but can be set to something else for static values.
147
150
  consul_token_field: token
148
151
 
152
+ # You can define one or more 'vault.<label>' sections to define alternative Vault
153
+ # token sources for use in individual sync targets.
154
+ vault.other:
155
+ url: https://your.vault.example
156
+ consul_token_path: consul/creds/my-other-role
157
+ vault.dev:
158
+ url: https://dev.vault.example
159
+ consul_token_path: consul/creds/my-dev-role
160
+
149
161
  sync:
150
162
  # sync is an array of hashes of sync target configurations
151
163
  # Fields:
@@ -167,6 +179,10 @@ required. An example `constancy.yml` is below including explanatory comments:
167
179
  # containing a hash of remote keys if this sync target has
168
180
  # type=file. This path is calculated relative to the directory
169
181
  # containing the configuration file.
182
+ # token_source - An alternative token source other than the
183
+ # default. Potential values are the same as for the
184
+ # consul.token_source config value: 'none', 'env', 'vault',
185
+ # or 'vault.<label>'.
170
186
  # delete - Whether or not to delete remote keys that do not exist
171
187
  # in the local filesystem. This inherits the setting from the
172
188
  # `constancy` section, or if not specified, defaults to `false`.
@@ -195,6 +211,7 @@ required. An example `constancy.yml` is below including explanatory comments:
195
211
  type: dir
196
212
  datacenter: dc1
197
213
  path: consul/private
214
+ token_source: vault.dev
198
215
  delete: true
199
216
  - name: yourapp-config
200
217
  prefix: config/yourapp
@@ -354,10 +371,10 @@ Constancy may be partially configured using environment variables:
354
371
  interacting with the API. Otherwise, by default the agent's `acl_token`
355
372
  setting is used implicitly.
356
373
  * `VAULT_ADDR` and `VAULT_TOKEN` - if `consul.token_source` is set to `vault`
357
- these variables are used to authenticate to Vault. If `VAULT_TOKEN` is not
358
- set, Constancy will attempt to read a token from `~/.vault-token`. If the
359
- `url` field is set, it will take priority over the `VAULT_ADDR` environment
360
- variable, but one or the other must be set.
374
+ or `vault.<label>`, these variables are used to authenticate to Vault. If
375
+ `VAULT_TOKEN` is not set, Constancy will attempt to read a token from
376
+ `~/.vault-token`. If the `url` field is set, it will take priority over the
377
+ `VAULT_ADDR` environment variable, but one or the other must be set.
361
378
 
362
379
 
363
380
  ## Roadmap
@@ -4,10 +4,12 @@ require 'erb'
4
4
  require 'imperium'
5
5
  require 'fileutils'
6
6
  require 'ostruct'
7
+ require 'vault'
7
8
  require 'yaml'
8
9
 
9
10
  require_relative 'constancy/version'
10
11
  require_relative 'constancy/config'
12
+ require_relative 'constancy/token_source'
11
13
  require_relative 'constancy/diff'
12
14
  require_relative 'constancy/sync_target'
13
15
 
@@ -8,18 +8,25 @@ class Constancy
8
8
  Constancy::CLI.configure(call_external_apis: false)
9
9
 
10
10
  puts " Config file: #{Constancy.config.config_file}"
11
- puts " Consul URL: #{Constancy.config.consul.url}"
11
+ puts " Consul URL: #{Constancy.config.consul_url}"
12
12
  puts " Verbose: #{Constancy.config.verbose?.to_s.bold}"
13
- if Constancy.config.consul_token_source == "env"
14
- puts
15
- puts "Token Source: CONSUL_TOKEN or CONSUL_HTTP_TOKEN environment variable"
16
-
17
- elsif Constancy.config.consul_token_source == "vault"
13
+ puts
14
+ puts " Defined Consul Token Sources:"
15
+ default_src_name = Constancy.config.default_consul_token_source.name
16
+ srcs = Constancy.config.consul_token_sources
17
+ ( %w( none env ) + ( srcs.keys.sort - %w( none env ) ) ).each do |name|
18
18
  puts
19
- puts "Token Source: Vault"
20
- puts " Vault URL: #{Constancy.config.vault_config.url}"
21
- puts " Token Path: #{Constancy.config.vault_config.consul_token_path}"
22
- puts " Token Field: #{Constancy.config.vault_config.consul_token_field}"
19
+ puts " #{name}:#{ default_src_name == name ? " (DEFAULT)".bold : ""}"
20
+ case name
21
+ when "none"
22
+ puts " uses CONSUL_HTTP_TOKEN or CONSUL_TOKEN env var if available"
23
+ when "env"
24
+ puts " requires CONSUL_HTTP_TOKEN or CONSUL_TOKEN env var"
25
+ when /^vault/
26
+ puts " address: #{srcs[name].vault_addr}"
27
+ puts " path: #{srcs[name].consul_token_path}"
28
+ puts " field: #{srcs[name].consul_token_field}"
29
+ end
23
30
  end
24
31
  puts
25
32
  puts "Sync target defaults:"
@@ -35,16 +42,17 @@ class Constancy
35
42
  else
36
43
  print '*'
37
44
  end
38
- puts " Datacenter: #{target.datacenter}"
39
- puts " Local type: #{target.type == :dir ? 'Directory' : 'Single file'}"
40
- puts " #{target.type == :dir ? " Dir" : "File"} path: #{target.path}"
41
- puts " Prefix: #{target.prefix}"
42
- puts " Autochomp? #{target.chomp?}"
43
- puts " Delete? #{target.delete?}"
45
+ puts " Datacenter: #{target.datacenter}"
46
+ puts " Local type: #{target.type == :dir ? 'Directory' : 'Single file'}"
47
+ puts " #{target.type == :dir ? " Dir" : "File"} path: #{target.path}"
48
+ puts " Prefix: #{target.prefix}"
49
+ puts " Token Source: #{target.token_source.name}"
50
+ puts " Autochomp? #{target.chomp?}"
51
+ puts " Delete? #{target.delete?}"
44
52
  if not target.exclude.empty?
45
- puts " Exclusions:"
53
+ puts " Exclusions:"
46
54
  target.exclude.each do |exclusion|
47
- puts " - #{exclusion}"
55
+ puts " - #{exclusion}"
48
56
  end
49
57
  end
50
58
  puts
@@ -11,6 +11,8 @@ class Constancy
11
11
  class Config
12
12
  CONFIG_FILENAMES = %w( constancy.yml )
13
13
  VALID_CONFIG_KEYS = %w( sync consul vault constancy )
14
+ VALID_VAULT_KEY_PATTERNS = [ %r{^vault\.[A-Za-z][A-Za-z0-9_-]*$}, %r{^vault$} ]
15
+ VALID_CONFIG_KEY_PATTERNS = VALID_VAULT_KEY_PATTERNS
14
16
  VALID_CONSUL_CONFIG_KEYS = %w( url datacenter token_source )
15
17
  VALID_VAULT_CONFIG_KEYS = %w( url consul_token_path consul_token_field )
16
18
  VALID_CONSTANCY_CONFIG_KEYS = %w( verbose chomp delete color )
@@ -18,7 +20,8 @@ class Constancy
18
20
  DEFAULT_CONSUL_TOKEN_SOURCE = "none"
19
21
  DEFAULT_VAULT_CONSUL_TOKEN_FIELD = "token"
20
22
 
21
- attr_accessor :config_file, :base_dir, :consul, :consul_token_source, :sync_targets, :target_whitelist, :call_external_apis, :vault_config
23
+ attr_accessor :config_file, :base_dir, :consul_url, :default_consul_token_source,
24
+ :sync_targets, :target_allowlist, :call_external_apis, :consul_token_sources
22
25
 
23
26
  class << self
24
27
  # discover the nearest config file
@@ -34,6 +37,15 @@ class Constancy
34
37
 
35
38
  dir == "/" ? nil : self.discover(dir: File.dirname(dir))
36
39
  end
40
+
41
+ def only_valid_config_keys!(keylist)
42
+ (keylist - VALID_CONFIG_KEYS).each do |key|
43
+ if not VALID_CONFIG_KEY_PATTERNS.find { |pattern| key =~ pattern }
44
+ raise Constancy::ConfigFileInvalid.new("'#{key}' is not a valid configuration key")
45
+ end
46
+ end
47
+ true
48
+ end
37
49
  end
38
50
 
39
51
  def initialize(path: nil, targets: nil, call_external_apis: true)
@@ -51,7 +63,7 @@ class Constancy
51
63
 
52
64
  self.config_file = File.expand_path(self.config_file)
53
65
  self.base_dir = File.dirname(self.config_file)
54
- self.target_whitelist = targets
66
+ self.target_allowlist = targets
55
67
  self.call_external_apis = call_external_apis
56
68
  parse!
57
69
  end
@@ -72,7 +84,11 @@ class Constancy
72
84
  @use_color
73
85
  end
74
86
 
75
- private
87
+ def parse_vault_token_sources!(raw)
88
+ raw.keys.select { |key| VALID_VAULT_KEY_PATTERNS.find { |pattern| key =~ pattern } }.collect do |key|
89
+ [key, Constancy::VaultTokenSource.new(name: key, config: raw[key])]
90
+ end.to_h
91
+ end
76
92
 
77
93
  def parse!
78
94
  raw = {}
@@ -91,116 +107,40 @@ class Constancy
91
107
  raise Constancy::ConfigFileInvalid.new("Config file must form a hash")
92
108
  end
93
109
 
94
- if (raw.keys - Constancy::Config::VALID_CONFIG_KEYS) != []
95
- raise Constancy::ConfigFileInvalid.new("Only the following keys are valid at the top level of the config: #{Constancy::Config::VALID_CONFIG_KEYS.join(", ")}")
96
- end
110
+ Constancy::Config.only_valid_config_keys!(raw.keys)
111
+
112
+ self.consul_token_sources = {
113
+ "none" => Constancy::PassiveTokenSource.new,
114
+ "env" => Constancy::EnvTokenSource.new,
115
+ }.merge(
116
+ self.parse_vault_token_sources!(raw),
117
+ )
97
118
 
98
119
  raw['consul'] ||= {}
99
120
  if not raw['consul'].is_a? Hash
100
121
  raise Constancy::ConfigFileInvalid.new("'consul' must be a hash")
101
122
  end
102
123
 
103
- if (raw['consul'].keys - Constancy::Config::VALID_CONSUL_CONFIG_KEYS) != []
104
- raise Constancy::ConfigFileInvalid.new("Only the following keys are valid in the consul config: #{Constancy::Config::VALID_CONSUL_CONFIG_KEYS.join(", ")}")
124
+ if (raw['consul'].keys - VALID_CONSUL_CONFIG_KEYS) != []
125
+ raise Constancy::ConfigFileInvalid.new("Only the following keys are valid in the consul config: #{VALID_CONSUL_CONFIG_KEYS.join(", ")}")
105
126
  end
106
127
 
107
- consul_url = raw['consul']['url'] || Constancy::Config::DEFAULT_CONSUL_URL
108
-
109
- # start with a token from the environment, regardless of the token_source setting
110
- consul_token = ENV['CONSUL_HTTP_TOKEN'] || ENV['CONSUL_TOKEN']
111
-
112
- self.consul_token_source = raw['consul']['token_source'] || Constancy::Config::DEFAULT_CONSUL_TOKEN_SOURCE
113
- case self.consul_token_source
114
- when "none"
115
- # nothing to do
116
-
117
- when "env"
118
- if consul_token.nil? or consul_token == ""
119
- raise Constancy::ConsulTokenRequired.new("Consul token_source is set to 'env' but neither CONSUL_TOKEN nor CONSUL_HTTP_TOKEN is set")
120
- end
121
-
122
- when "vault"
123
- require 'vault'
124
-
125
- raw['vault'] ||= {}
126
- if not raw['vault'].is_a? Hash
127
- raise Constancy::ConfigFileInvalid.new("'vault' must be a hash")
128
- end
129
-
130
- if (raw['vault'].keys - Constancy::Config::VALID_VAULT_CONFIG_KEYS) != []
131
- raise Constancy::ConfigFileInvalid.new("Only the following keys are valid in the vault config: #{Constancy::Config::VALID_VAULT_CONFIG_KEYS.join(", ")}")
132
- end
133
-
134
- vault_path = raw['vault']['consul_token_path']
135
- if vault_path.nil? or vault_path == ""
136
- raise Constancy::ConfigFileInvalid.new("vault.consul_token_path must be specified to use vault as a token source")
137
- end
138
-
139
- # prioritize the config file over environment variables for vault address
140
- vault_addr = raw['vault']['url'] || ENV['VAULT_ADDR']
141
- if vault_addr.nil? or vault_addr == ""
142
- raise Constancy::VaultConfigInvalid.new("Vault address must be set in vault.url or VAULT_ADDR")
143
- end
144
-
145
- vault_token = ENV['VAULT_TOKEN']
146
- if vault_token.nil? or vault_token == ""
147
- vault_token_file = File.expand_path("~/.vault-token")
148
- if File.exist?(vault_token_file)
149
- vault_token = File.read(vault_token_file)
150
- else
151
- raise Constancy::VaultConfigInvalid.new("Vault token must be set in ~/.vault-token or VAULT_TOKEN")
128
+ self.consul_url = raw['consul']['url'] || DEFAULT_CONSUL_URL
129
+ srcname = raw['consul']['token_source'] || DEFAULT_CONSUL_TOKEN_SOURCE
130
+ self.default_consul_token_source =
131
+ self.consul_token_sources[srcname].tap do |src|
132
+ if src.nil?
133
+ raise Constancy::ConfigFileInvalid.new("Consul token source '#{consul_token_source}' is not defined")
152
134
  end
153
135
  end
154
136
 
155
- vault_field = raw['vault']['consul_token_field'] || Constancy::Config::DEFAULT_VAULT_CONSUL_TOKEN_FIELD
156
-
157
- self.vault_config = OpenStruct.new(
158
- url: vault_addr,
159
- consul_token_path: vault_path,
160
- consul_token_field: vault_field,
161
- )
162
-
163
- # don't waste time talking to Vault if this is just a config parsing run
164
- if self.call_external_apis
165
- ENV['VAULT_ADDR'] = vault_addr
166
- ENV['VAULT_TOKEN'] = vault_token
167
-
168
- begin
169
- response = Vault.logical.read(vault_path)
170
- consul_token = response.data[vault_field.to_sym]
171
-
172
- if response.lease_id
173
- at_exit {
174
- begin
175
- Vault.sys.revoke(response.lease_id)
176
- rescue => e
177
- # this is fine
178
- end
179
- }
180
- end
181
-
182
- rescue => e
183
- raise Constancy::VaultConfigInvalid.new("Are you logged in to Vault?\n\n#{e}")
184
- end
185
-
186
- if consul_token.nil? or consul_token == ""
187
- raise Constancy::VaultConfigInvalid.new("Could not acquire a Consul token from Vault")
188
- end
189
- end
190
-
191
- else
192
- raise Constancy::ConfigFileInvalid.new("Only the following values are valid for token_source: none, env, vault")
193
- end
194
-
195
- self.consul = Imperium::Configuration.new(url: consul_url, token: consul_token)
196
-
197
137
  raw['constancy'] ||= {}
198
138
  if not raw['constancy'].is_a? Hash
199
139
  raise Constancy::ConfigFileInvalid.new("'constancy' must be a hash")
200
140
  end
201
141
 
202
- if (raw['constancy'].keys - Constancy::Config::VALID_CONSTANCY_CONFIG_KEYS) != []
203
- raise Constancy::ConfigFileInvalid.new("Only the following keys are valid in the 'constancy' config block: #{Constancy::Config::VALID_CONSTANCY_CONFIG_KEYS.join(", ")}")
142
+ if (raw['constancy'].keys - VALID_CONSTANCY_CONFIG_KEYS) != []
143
+ raise Constancy::ConfigFileInvalid.new("Only the following keys are valid in the 'constancy' config block: #{VALID_CONSTANCY_CONFIG_KEYS.join(", ")}")
204
144
  end
205
145
 
206
146
  # verbose: default false
@@ -233,6 +173,7 @@ class Constancy
233
173
 
234
174
  self.sync_targets = []
235
175
  raw['sync'].each do |target|
176
+ token_source = self.default_consul_token_source
236
177
  if target.is_a? Hash
237
178
  target['datacenter'] ||= raw['consul']['datacenter']
238
179
  if target['chomp'].nil?
@@ -241,19 +182,31 @@ class Constancy
241
182
  if target['delete'].nil?
242
183
  target['delete'] = self.delete?
243
184
  end
185
+ if not target['token_source'].nil?
186
+ token_source = self.consul_token_sources[target['token_source']]
187
+ if token_source.nil?
188
+ raise Constancy::ConfigFileInvalid.new("Consul token source '#{target['token_source']}' is not defined")
189
+ end
190
+ target.delete('token_source')
191
+ end
244
192
  end
245
193
 
246
- if not self.target_whitelist.nil?
247
- # unnamed targets cannot be whitelisted
194
+ if not self.target_allowlist.nil?
195
+ # unnamed targets cannot be allowlisted
248
196
  next if target['name'].nil?
249
197
 
250
- # named targets must be on the whitelist
251
- next if not self.target_whitelist.include?(target['name'])
198
+ # named targets must be on the allowlist
199
+ next if not self.target_allowlist.include?(target['name'])
252
200
  end
253
201
 
254
- self.sync_targets << Constancy::SyncTarget.new(config: target, imperium_config: self.consul, base_dir: self.base_dir)
202
+ # only try to fetch consul tokens if we are actually going to do work
203
+ consul_token = if self.call_external_apis
204
+ token_source.consul_token
205
+ else
206
+ ""
207
+ end
208
+ self.sync_targets << Constancy::SyncTarget.new(config: target, consul_url: consul_url, token_source: token_source, base_dir: self.base_dir, call_external_apis: self.call_external_apis)
255
209
  end
256
-
257
210
  end
258
211
  end
259
212
  end
@@ -3,13 +3,13 @@
3
3
  class Constancy
4
4
  class SyncTarget
5
5
  VALID_CONFIG_KEYS = %w( name type datacenter prefix path exclude chomp delete erb_enabled )
6
- attr_accessor :name, :type, :datacenter, :prefix, :path, :exclude, :consul, :erb_enabled
6
+ attr_accessor :name, :type, :datacenter, :prefix, :path, :exclude, :consul, :erb_enabled, :consul_url, :token_source, :call_external_apis
7
7
 
8
8
  REQUIRED_CONFIG_KEYS = %w( prefix )
9
9
  VALID_TYPES = [ :dir, :file ]
10
10
  DEFAULT_TYPE = :dir
11
11
 
12
- def initialize(config:, imperium_config:, base_dir:)
12
+ def initialize(config:, consul_url:, token_source:, base_dir:, call_external_apis: true)
13
13
  if not config.is_a? Hash
14
14
  raise Constancy::ConfigFileInvalid.new("Sync target entries must be specified as hashes")
15
15
  end
@@ -46,7 +46,15 @@ class Constancy
46
46
  @do_delete = false
47
47
  end
48
48
 
49
- self.consul = Imperium::KV.new(imperium_config)
49
+ self.call_external_apis = call_external_apis
50
+ self.consul_url = consul_url
51
+ self.token_source = token_source
52
+ token = if self.call_external_apis
53
+ self.token_source.consul_token
54
+ else
55
+ ""
56
+ end
57
+ self.consul = Imperium::KV.new(Imperium::Configuration.new(url: self.consul_url, token: token))
50
58
  self.erb_enabled = config['erb_enabled']
51
59
  end
52
60
 
@@ -0,0 +1,94 @@
1
+ # This software is public domain. No rights are reserved. See LICENSE for more information.
2
+
3
+ class Constancy
4
+ # use env vars if defined, but otherwise just return an empty string
5
+ class PassiveTokenSource
6
+ def name
7
+ "none"
8
+ end
9
+
10
+ def consul_token
11
+ ENV['CONSUL_HTTP_TOKEN'] || ENV['CONSUL_TOKEN'] || ""
12
+ end
13
+ end
14
+
15
+ # use env vars and raise an error if none is found
16
+ class EnvTokenSource
17
+ def name
18
+ "env"
19
+ end
20
+
21
+ def consul_token
22
+ consul_token = ENV['CONSUL_HTTP_TOKEN'] || ENV['CONSUL_TOKEN']
23
+ if consul_token.nil? or consul_token == ""
24
+ raise Constancy::ConsulTokenRequired.new("Consul token_source was set to 'env' but neither CONSUL_TOKEN nor CONSUL_HTTP_TOKEN is set")
25
+ end
26
+ end
27
+ end
28
+
29
+ class VaultTokenSource
30
+ attr_accessor :name, :vault_addr, :vault_token, :consul_token_path, :consul_token_field
31
+
32
+ def initialize(name:, config:)
33
+ self.name = name
34
+
35
+ config ||= {}
36
+ if not config.is_a? Hash
37
+ raise Constancy::ConfigFileInvalid.new("'#{name}' must be a hash")
38
+ end
39
+
40
+ if (config.keys - Constancy::Config::VALID_VAULT_CONFIG_KEYS) != []
41
+ raise Constancy::ConfigFileInvalid.new("Only the following keys are valid in a vault config: #{Constancy::Config::VALID_VAULT_CONFIG_KEYS.join(", ")}")
42
+ end
43
+
44
+ self.consul_token_path = config['consul_token_path']
45
+ if self.consul_token_path.nil? or self.consul_token_path == ""
46
+ raise Constancy::ConfigFileInvalid.new("consul_token_path must be specified to use '#{name}' as a token source")
47
+ end
48
+
49
+ # prioritize the config file over environment variables for vault address
50
+ self.vault_addr = config['url'] || ENV['VAULT_ADDR']
51
+ if self.vault_addr.nil? or self.vault_addr == ""
52
+ raise Constancy::VaultConfigInvalid.new("Vault address must be set in #{name}.vault_addr or VAULT_ADDR")
53
+ end
54
+
55
+ self.vault_token = ENV['VAULT_TOKEN']
56
+ if self.vault_token.nil? or self.vault_token == ""
57
+ vault_token_file = File.expand_path("~/.vault-token")
58
+ if File.exist?(vault_token_file)
59
+ self.vault_token = File.read(vault_token_file)
60
+ else
61
+ raise Constancy::VaultConfigInvalid.new("Vault token must be set in ~/.vault-token or VAULT_TOKEN")
62
+ end
63
+ end
64
+
65
+ self.consul_token_field = config['consul_token_field'] || Constancy::Config::DEFAULT_VAULT_CONSUL_TOKEN_FIELD
66
+ end
67
+
68
+ def consul_token
69
+ if @consul_token.nil?
70
+ begin
71
+ response = Vault::Client.new(address: self.vault_addr, token: self.vault_token).logical.read(self.consul_token_path)
72
+ @consul_token = response.data[self.consul_token_field.to_sym]
73
+ if response.lease_id
74
+ at_exit {
75
+ begin
76
+ Vault::Client.new(address: self.vault_addr, token: self.vault_token).sys.revoke(response.lease_id)
77
+ rescue => e
78
+ # this is fine
79
+ end
80
+ }
81
+ end
82
+
83
+ rescue => e
84
+ raise Constancy::VaultConfigInvalid.new("Are you logged in to Vault?\n\n#{e}")
85
+ end
86
+
87
+ if @consul_token.nil? or @consul_token == ""
88
+ raise Constancy::VaultConfigInvalid.new("Could not acquire a Consul token from Vault")
89
+ end
90
+ end
91
+ @consul_token
92
+ end
93
+ end
94
+ end
@@ -1,5 +1,5 @@
1
1
  # This software is public domain. No rights are reserved. See LICENSE for more information.
2
2
 
3
3
  class Constancy
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: constancy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Adams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-21 00:00:00.000000000 Z
11
+ date: 2020-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: imperium
@@ -87,6 +87,7 @@ files:
87
87
  - lib/constancy/config.rb
88
88
  - lib/constancy/diff.rb
89
89
  - lib/constancy/sync_target.rb
90
+ - lib/constancy/token_source.rb
90
91
  - lib/constancy/version.rb
91
92
  homepage: https://github.com/daveadams/constancy
92
93
  licenses: