settings_reader-vault_resolver 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1a6da72b08435eb9eae6bff284c09d0ab717de2e73e3bfea7e4bdc255b1bf2b
4
- data.tar.gz: 2003918d4ad830d9dc4ab0127ec0e970eb2994e66e42ce8fb3f3936e1bb2e476
3
+ metadata.gz: 7a9aaa9ade4b4a390d408f4b29bed51bcf1c85c8345cb9434497bd565bd302a2
4
+ data.tar.gz: '0955dde971303ab6899e9beae5e9b276ef7209b9f9cf3e4769c875a2245619bf'
5
5
  SHA512:
6
- metadata.gz: 4d4618260512414b329893c4651f1594398f34a5d54d94b0afe0989aa57f54208d85c16dae2f43fd0df3a0842f40c05e87202477a0c04dba9c6c0341f401f108
7
- data.tar.gz: 6880bd9a5166bd7a01c2444fb4ce520a724c6df1ea6b4ec3ab1e844d4216602cd80b3d04b7d3b40dccbc3d9d3c7e808b54a818e2dadf78f9c9c400df4ea379dd
6
+ metadata.gz: 69a39ecbcf836c8116531c35b5ed9ea386595182aae4b7afb947d85709071a7dde75763759145e12c031eaff9ee8f85c13dcf55b1e08f2c763813bcb67dd9e2b
7
+ data.tar.gz: 6732daeeec4a32a63b218926d96974aec750c41abc3097f2ef9cfad58fa7c46626d2df434c147f28a7d7178682d2c3938324c53d5f0e30a5835a85f3fb3e7f8b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.2]
4
+ ### Fixes
5
+ - Fix lost secret data after lease renewal
6
+ - Fix exception when getting value from secret with nil data
7
+
8
+ ## [0.4.1]
9
+ ### Changes
10
+ - Broader entry secret data access to allow retrieval of secret attributes
11
+
12
+ ### Fixes
13
+ - Fix exception when retrieving authenticating via k8s endpoint
14
+
15
+ ### New features
16
+ - Retry secret retrieval and renewal
17
+ - Capture more vault exceptions including connectivity errors
18
+ - Introduce vault engine adapter concept
19
+ - Separate kv, database, and auth engine logic
20
+
21
+ ## [0.4.0]
22
+ ### Breaking changes
23
+ - Reworked authentication helpers interface
24
+
25
+ ### New features
26
+ - Retry secret retrieval and renewal
27
+ - Capture more vault exceptions including connectivity errors
28
+ - Introduce vault engine adapter concept
29
+ - Separate kv, database, and auth engine logic
30
+
3
31
  ## [0.3.0]
4
32
  ### Breaking changes
5
33
  - Require configuration before use
@@ -45,8 +73,11 @@
45
73
  - Secrets caching
46
74
  - Automatic secrets lease renewal
47
75
 
48
- [Unreleased]: https://github.com/matic-insurance/settings_reader-vault_resolver/compare/0.3.0...HEAD
49
- [0.2.4]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.3.0
76
+ [Unreleased]: https://github.com/matic-insurance/settings_reader-vault_resolver/compare/0.4.2...HEAD
77
+ [0.4.2]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.4.2
78
+ [0.4.1]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.4.1
79
+ [0.4.0]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.4.0
80
+ [0.3.0]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.3.0
50
81
  [0.2.4]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.2.4
51
82
  [0.2.3]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.2.3
52
83
  [0.2.2]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.2.2
@@ -36,6 +36,10 @@ module SettingsReader
36
36
  @secrets.each_value(&block)
37
37
  end
38
38
 
39
+ def clear_all
40
+ @secrets = {}
41
+ end
42
+
39
43
  private
40
44
 
41
45
  def cache_key(address)
@@ -6,6 +6,10 @@ module SettingsReader
6
6
  # Default: Logger.new(STDOUT, level: Logger::ERROR)
7
7
  attr_accessor :logger
8
8
 
9
+ # How many times to retry retrieval of the secret
10
+ # Default: 2
11
+ attr_accessor :retrieval_retries
12
+
9
13
  # How often do we check if secret lease is about to expire
10
14
  # Default: 60seconds
11
15
  attr_accessor :lease_refresh_interval
@@ -14,6 +18,10 @@ module SettingsReader
14
18
  # Default: 300seconds
15
19
  attr_accessor :lease_renew_delay
16
20
 
21
+ # How many times to retry renew of the secret
22
+ # Default: 4
23
+ attr_accessor :lease_renew_retries
24
+
17
25
  # Block to be executed when lease is refreshed
18
26
  # Default: empty proc
19
27
  attr_accessor :lease_renew_success_listener
@@ -24,8 +32,10 @@ module SettingsReader
24
32
 
25
33
  def initialize
26
34
  @logger = Logger.new($stdout, level: Logger::ERROR)
35
+ @retrieval_retries = 2
27
36
  @lease_refresh_interval = 60
28
37
  @lease_renew_delay = 300
38
+ @lease_renew_retries = 4
29
39
  @lease_renew_error_listener = proc {}
30
40
  @lease_renew_success_listener = proc {}
31
41
  end
@@ -40,6 +50,22 @@ module SettingsReader
40
50
  timer_task.execute
41
51
  timer_task
42
52
  end
53
+
54
+ def vault_engines
55
+ @vault_engines ||= [
56
+ SettingsReader::VaultResolver::Engines::KV2.new(self),
57
+ SettingsReader::VaultResolver::Engines::Database.new(self),
58
+ SettingsReader::VaultResolver::Engines::Auth.new(self)
59
+ ]
60
+ end
61
+
62
+ def vault_engine_for(address)
63
+ unless (engine = vault_engines.detect { |e| e.retrieves?(address) })
64
+ raise SettingsReader::VaultResolver::Error, "Unknown engine for #{address}"
65
+ end
66
+
67
+ engine
68
+ end
43
69
  end
44
70
  end
45
71
  end
@@ -0,0 +1,64 @@
1
+ module SettingsReader
2
+ module VaultResolver
3
+ module Engines
4
+ # Abstract interface for Vault Backends
5
+ class Abstract
6
+ include Logging
7
+
8
+ attr_reader :config
9
+
10
+ def initialize(config)
11
+ @config = config
12
+ end
13
+
14
+ def retrieves?(_address)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def get(address)
19
+ return unless (vault_secret = get_secret_with_retries(address))
20
+
21
+ wrap_secret(address, vault_secret)
22
+ rescue Vault::VaultError => e
23
+ raise SettingsReader::VaultResolver::Error, e.message
24
+ end
25
+
26
+ def renew(entry)
27
+ return unless entry.leased?
28
+
29
+ new_secret = renew_lease_with_retries(entry)
30
+ entry.update_renewed(new_secret)
31
+ true
32
+ rescue Vault::VaultError => e
33
+ raise SettingsReader::VaultResolver::Error, e.message
34
+ end
35
+
36
+ protected
37
+
38
+ def get_secret_with_retries(address)
39
+ Vault.with_retries(Vault::HTTPConnectionError, attempts: config.retrieval_retries) do
40
+ get_secret(address)
41
+ end
42
+ end
43
+
44
+ def renew_lease_with_retries(address)
45
+ Vault.with_retries(Vault::HTTPConnectionError, attempts: config.lease_renew_retries) do
46
+ renew_lease(address)
47
+ end
48
+ end
49
+
50
+ def get_secret(address)
51
+ raise NotImplementedError
52
+ end
53
+
54
+ def renew_lease(entry)
55
+ raise NotImplementedError
56
+ end
57
+
58
+ def wrap_secret(address, secret)
59
+ SettingsReader::VaultResolver::Entry.new(address, secret)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,36 @@
1
+ module SettingsReader
2
+ module VaultResolver
3
+ module Engines
4
+ # Adapter to retrieve / renew auth tokens
5
+ class Auth < Abstract
6
+ MOUNT = 'auth'.freeze
7
+ K8S_AUTH = 'kubernetes/login'.freeze
8
+
9
+ def retrieves?(address)
10
+ address.mount == MOUNT
11
+ end
12
+
13
+ protected
14
+
15
+ def get_secret(address)
16
+ return k8s_auth(address) if address.path == K8S_AUTH
17
+
18
+ raise SettingsReader::VaultResolver::Error, "Unsupported auth backed for #{address}"
19
+ end
20
+
21
+ def renew_lease(_entry)
22
+ secret = Vault.client.auth_token.renew_self
23
+ secret&.auth
24
+ end
25
+
26
+ private
27
+
28
+ def k8s_auth(address)
29
+ options = { route: address.options['route'], service_token_path: address.options['service_token_path'] }
30
+ secret = Vault.auth.kubernetes(address.options['role'], **options)
31
+ secret&.auth
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ module SettingsReader
2
+ module VaultResolver
3
+ module Engines
4
+ # Adapter to retrieve / renew secret from database engine
5
+ class Database < Abstract
6
+ MOUNT = 'database'.freeze
7
+
8
+ def retrieves?(address)
9
+ address.mount == MOUNT
10
+ end
11
+
12
+ private
13
+
14
+ def get_secret(address)
15
+ debug { "Fetching new database secret at: #{address}" }
16
+ Vault.logical.read(address.full_path)
17
+ rescue Vault::HTTPClientError => e
18
+ return nil if e.message.include?('* unknown role')
19
+
20
+ raise e
21
+ end
22
+
23
+ def renew_lease(entry)
24
+ Vault.sys.renew(entry.lease_id)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ module SettingsReader
2
+ module VaultResolver
3
+ module Engines
4
+ # Adapter to retrieve / renew secret from kv2 engine
5
+ class KV2 < Abstract
6
+ MOUNT = 'secret'.freeze
7
+
8
+ def retrieves?(address)
9
+ address.mount == MOUNT
10
+ end
11
+
12
+ def renew(_entry)
13
+ # KV secrets are static. Nothing to do
14
+ end
15
+
16
+ private
17
+
18
+ def get_secret(address)
19
+ debug { "Fetching new kv secret at: #{address}" }
20
+ Vault.kv(address.mount).read(address.path)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -2,18 +2,19 @@ module SettingsReader
2
2
  module VaultResolver
3
3
  # Wrapper around vault secret object
4
4
  class Entry
5
- attr_reader :address, :secret
5
+ attr_reader :address, :secret, :data
6
6
 
7
7
  MONTH = 30 * 60 * 60
8
8
 
9
9
  def initialize(address, secret)
10
10
  @address = address
11
11
  @secret = secret
12
+ @data = extract_data(secret)
12
13
  @lease_started = Time.now
13
14
  end
14
15
 
15
16
  def leased?
16
- @secret.lease_id && lease_duration.positive?
17
+ @secret.renewable?
17
18
  end
18
19
 
19
20
  def expired?
@@ -28,29 +29,36 @@ module SettingsReader
28
29
  @lease_started + lease_duration - Time.now
29
30
  end
30
31
 
31
- def renew
32
- return unless leased?
32
+ def lease_id
33
+ @secret.lease_id
34
+ end
33
35
 
34
- @secret = Vault.sys.renew(@secret.lease_id)
36
+ def update_renewed(new_secret)
37
+ @secret = new_secret
38
+ @data = @data.merge(extract_data(new_secret).compact)
35
39
  @lease_started = Time.now
36
- true
37
- rescue Vault::HTTPClientError => e
38
- raise SettingsReader::VaultResolver::Error, e.message
39
40
  end
40
41
 
41
42
  def value_for(attribute)
42
- secret.data[attribute.to_sym]
43
+ return data[attribute.to_sym] if data.key?(attribute.to_sym)
44
+ return secret.public_send(attribute) if secret.respond_to?(attribute)
45
+
46
+ nil
43
47
  end
44
48
 
45
49
  def to_s
46
50
  address.to_s
47
51
  end
48
52
 
49
- private
50
-
51
53
  def lease_duration
52
54
  @secret.lease_duration.to_i
53
55
  end
56
+
57
+ private
58
+
59
+ def extract_data(secret)
60
+ secret.respond_to?(:data) && secret.data ? secret.data : {}
61
+ end
54
62
  end
55
63
  end
56
64
  end
@@ -0,0 +1,23 @@
1
+ require_relative '../patches/authenticate'
2
+
3
+ module SettingsReader
4
+ module VaultResolver
5
+ module Helpers
6
+ # Helps with authentication using different schemes
7
+ class VaultAuthentication
8
+ FAKE_RESOLVER_PATH = 'vault/authentication'.freeze
9
+
10
+ def authenticate_via_k8s(role, route: nil, service_token_path: nil)
11
+ params = URI.encode_www_form({ role: role, route: route, service_token_path: service_token_path }.compact)
12
+ resolver.resolve("vault://auth/kubernetes/login?#{params}#client_token", FAKE_RESOLVER_PATH)
13
+ end
14
+
15
+ private
16
+
17
+ def resolver
18
+ @resolver = SettingsReader::VaultResolver.resolver
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -13,6 +13,7 @@ module SettingsReader
13
13
 
14
14
  def initialize(config)
15
15
  @config = config
16
+ @engines = config.vault_engines
16
17
  end
17
18
 
18
19
  def resolvable?(value, _path)
@@ -29,32 +30,12 @@ module SettingsReader
29
30
  entry&.value_for(address.attribute)
30
31
  end
31
32
 
32
- # Resolve KV secret
33
- def kv_secret(address)
34
- debug { "Fetching new kv secret at: #{address}" }
35
- Vault.kv(address.mount).read(address.path)
36
- rescue Vault::HTTPClientError => e
37
- raise SettingsReader::VaultResolver::Error, e.message
38
- end
39
-
40
- def database_secret(address)
41
- debug { "Fetching new database secret at: #{address}" }
42
- Vault.logical.read(address.full_path)
43
- rescue Vault::HTTPClientError => e
44
- return nil if e.message.include?('* unknown role')
45
-
46
- raise SettingsReader::VaultResolver::Error, e.message
47
- end
48
-
49
33
  private
50
34
 
51
35
  def fetch_entry(address)
52
36
  cache.fetch(address) do
53
37
  info { "Retrieving new secret at: #{address}" }
54
- if (secret = address.mount == DATABASE_MOUNT ? database_secret(address) : kv_secret(address))
55
- debug { "Retrieved secret at: #{address}" }
56
- SettingsReader::VaultResolver::Entry.new(address, secret)
57
- end
38
+ config.vault_engine_for(address).get(address)
58
39
  end
59
40
  rescue StandardError => e
60
41
  error { "Error retrieving secret: #{address}: #{e.message}" }
@@ -1,7 +1,7 @@
1
1
  module Vault
2
2
  # Monkey patch to support k8s authenticaiton. Taken from https://github.com/hashicorp/vault-ruby/pull/202
3
3
  class Authenticate < Request
4
- def kubernetes(role, route = nil, service_token_path = nil)
4
+ def kubernetes(role, route: nil, service_token_path: nil)
5
5
  route ||= '/v1/auth/kubernetes/login'
6
6
  service_token_path ||= '/var/run/secrets/kubernetes.io/serviceaccount/token'
7
7
 
@@ -27,11 +27,11 @@ module SettingsReader
27
27
  end
28
28
 
29
29
  def refresh_entry(entry)
30
- return unless entry.leased? && entry.expires_in < config.lease_renew_delay
30
+ return unless entry.expires_in < config.lease_renew_delay
31
31
 
32
32
  Concurrent::Promise.execute do
33
33
  debug { "Refreshing lease for #{entry}. Expires in: #{entry.expires_in}" }
34
- entry.renew
34
+ config.vault_engine_for(entry.address).renew(entry)
35
35
  info { "Lease renewed for #{entry}. Expires in: #{entry.expires_in}" }
36
36
  entry
37
37
  rescue StandardError => e
@@ -1,5 +1,5 @@
1
1
  module SettingsReader
2
2
  module VaultResolver
3
- VERSION = '0.3.0'.freeze
3
+ VERSION = '0.4.2'.freeze
4
4
  end
5
5
  end
@@ -2,15 +2,19 @@ require 'logger'
2
2
  require 'concurrent/timer_task'
3
3
 
4
4
  require 'settings_reader'
5
- require 'settings_reader/vault_resolver/version'
6
- require 'settings_reader/vault_resolver/logging'
7
- require 'settings_reader/vault_resolver/configuration'
8
- require 'settings_reader/vault_resolver/address'
9
- require 'settings_reader/vault_resolver/entry'
10
- require 'settings_reader/vault_resolver/cache'
11
- require 'settings_reader/vault_resolver/refresher'
12
- require 'settings_reader/vault_resolver/refresher_observer'
13
- require 'settings_reader/vault_resolver/instance'
5
+ require_relative 'vault_resolver/version'
6
+ require_relative 'vault_resolver/logging'
7
+ require_relative 'vault_resolver/configuration'
8
+ require_relative 'vault_resolver/address'
9
+ require_relative 'vault_resolver/entry'
10
+ require_relative 'vault_resolver/engines/abstract'
11
+ require_relative 'vault_resolver/engines/auth'
12
+ require_relative 'vault_resolver/engines/kv2'
13
+ require_relative 'vault_resolver/engines/database'
14
+ require_relative 'vault_resolver/cache'
15
+ require_relative 'vault_resolver/refresher'
16
+ require_relative 'vault_resolver/refresher_observer'
17
+ require_relative 'vault_resolver/instance'
14
18
 
15
19
  module SettingsReader
16
20
  # Singleton for lease renewals and secrets cache
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: settings_reader-vault_resolver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Volodymyr Mykhailyk
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-29 00:00:00.000000000 Z
11
+ date: 2022-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -83,8 +83,12 @@ files:
83
83
  - lib/settings_reader/vault_resolver/address.rb
84
84
  - lib/settings_reader/vault_resolver/cache.rb
85
85
  - lib/settings_reader/vault_resolver/configuration.rb
86
+ - lib/settings_reader/vault_resolver/engines/abstract.rb
87
+ - lib/settings_reader/vault_resolver/engines/auth.rb
88
+ - lib/settings_reader/vault_resolver/engines/database.rb
89
+ - lib/settings_reader/vault_resolver/engines/kv2.rb
86
90
  - lib/settings_reader/vault_resolver/entry.rb
87
- - lib/settings_reader/vault_resolver/helpers/k8s_auth.rb
91
+ - lib/settings_reader/vault_resolver/helpers/vault_authentication.rb
88
92
  - lib/settings_reader/vault_resolver/instance.rb
89
93
  - lib/settings_reader/vault_resolver/logging.rb
90
94
  - lib/settings_reader/vault_resolver/patches/authenticate.rb
@@ -1,43 +0,0 @@
1
- require_relative '../patches/authenticate'
2
-
3
- module SettingsReader
4
- module VaultResolver
5
- module Helpers
6
- # Helps with Vault authentication using kubernetes login
7
- class K8sAuth
8
- def call(role)
9
- secret = Vault.auth.kubernetes(role)
10
-
11
- cache_token_for_renewal(secret)
12
- secret
13
- end
14
-
15
- private
16
-
17
- def cache_token_for_renewal(secret)
18
- address = SettingsReader::VaultResolver::Address.new('vault://v1/auth/token/lookup-self')
19
- entry = SettingsReader::VaultResolver::AuthEntry.new(address, secret)
20
- SettingsReader::VaultResolver.cache.save(entry)
21
- entry
22
- end
23
- end
24
- end
25
-
26
- # Helps with auth token lease renewal
27
- class AuthEntry < SettingsReader::VaultResolver::Entry
28
- def leased?
29
- true
30
- end
31
-
32
- def lease_duration
33
- @secret.auth.lease_duration
34
- end
35
-
36
- def renew
37
- Vault.client.auth_token.renew_self
38
- @lease_started = Time.now
39
- true
40
- end
41
- end
42
- end
43
- end