constancy 0.4.0 → 0.5.0

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 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: