settings_reader-vault_resolver 0.1.1 → 0.2.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 +4 -4
- data/.github/workflows/linters.yml +52 -0
- data/.github/workflows/main.yml +1 -0
- data/.rubocop.yml +16 -0
- data/.simplecov +2 -2
- data/CHANGELOG.md +15 -3
- data/Gemfile +9 -1
- data/Gemfile.lock +20 -19
- data/README.md +14 -15
- data/Rakefile +3 -3
- data/bin/console +3 -3
- data/lib/settings_reader/vault_resolver/address.rb +4 -3
- data/lib/settings_reader/vault_resolver/cache.rb +2 -1
- data/lib/settings_reader/vault_resolver/entry.rb +8 -0
- data/lib/settings_reader/vault_resolver/helpers/k8s_auth.rb +43 -0
- data/lib/settings_reader/vault_resolver/instance.rb +64 -0
- data/lib/settings_reader/vault_resolver/logging.rb +40 -0
- data/lib/settings_reader/vault_resolver/patches/authenticate.rb +21 -0
- data/lib/settings_reader/vault_resolver/refresher.rb +12 -2
- data/lib/settings_reader/vault_resolver/version.rb +1 -1
- data/lib/settings_reader/vault_resolver.rb +20 -8
- data/settings_reader-vault_resolver.gemspec +5 -13
- metadata +15 -108
- data/lib/settings_reader/resolvers/vault.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16ca7880831b27b05c24651940a1dab796b792f7d98229f2f01e229ffcee5662
|
4
|
+
data.tar.gz: ff8e434b0512ce66772e8f99b16761ea51597614b195abb91889a5543ba88267
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1476c785e7369a2a2f3d4ee47a149cfd3c1fc2ebf333a94a1ec158c564584995ced9392fa08e1be9ec16ba166c0f20899414e02622473a4d0058ef5d70abce2
|
7
|
+
data.tar.gz: 8c1a40bd635b8e9de7193114618a0669e3c9d3ffabae250ff5efc416a31636c199584bb7e474373ead29bde9e85e281adbba0e7ba123358e1729d0e599bf17bf
|
@@ -0,0 +1,52 @@
|
|
1
|
+
name: "linters"
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ main ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ main ]
|
8
|
+
schedule:
|
9
|
+
- cron: '30 0 * * 1'
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
rubocop:
|
13
|
+
runs-on: ubuntu-latest
|
14
|
+
|
15
|
+
steps:
|
16
|
+
- name: Checkout
|
17
|
+
uses: actions/checkout@v2
|
18
|
+
- name: Set up Ruby
|
19
|
+
uses: ruby/setup-ruby@v1
|
20
|
+
with:
|
21
|
+
ruby-version: 2.5
|
22
|
+
bundler-cache: true
|
23
|
+
- name: Run rubocop
|
24
|
+
run: bundle exec rubocop --parallel
|
25
|
+
|
26
|
+
code-ql:
|
27
|
+
name: Analyze
|
28
|
+
runs-on: ubuntu-latest
|
29
|
+
permissions:
|
30
|
+
actions: read
|
31
|
+
contents: read
|
32
|
+
security-events: write
|
33
|
+
|
34
|
+
strategy:
|
35
|
+
fail-fast: false
|
36
|
+
matrix:
|
37
|
+
language: [ 'ruby' ]
|
38
|
+
|
39
|
+
steps:
|
40
|
+
- name: Checkout repository
|
41
|
+
uses: actions/checkout@v2
|
42
|
+
|
43
|
+
- name: Initialize CodeQL
|
44
|
+
uses: github/codeql-action/init@v1
|
45
|
+
with:
|
46
|
+
languages: ${{ matrix.language }}
|
47
|
+
|
48
|
+
- name: Autobuild
|
49
|
+
uses: github/codeql-action/autobuild@v1
|
50
|
+
|
51
|
+
- name: Perform CodeQL Analysis
|
52
|
+
uses: github/codeql-action/analyze@v1
|
data/.github/workflows/main.yml
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
AllCops:
|
2
|
+
NewCops: enable
|
3
|
+
SuggestExtensions: false
|
4
|
+
TargetRubyVersion: 2.5
|
5
|
+
|
6
|
+
Gemspec/RequireMFA:
|
7
|
+
Enabled: false
|
8
|
+
|
9
|
+
Style/FrozenStringLiteralComment:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
Metrics/BlockLength:
|
13
|
+
IgnoredMethods: ['describe', 'context']
|
14
|
+
|
15
|
+
Layout/LineLength:
|
16
|
+
Max: 120
|
data/.simplecov
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,16 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
-
## [0.1
|
3
|
+
## [0.2.1]
|
4
|
+
### Fixes
|
5
|
+
- Use default k8s auth route without namespace
|
6
|
+
|
7
|
+
## [0.2.0]
|
8
|
+
### New features
|
9
|
+
- Better integration with parent gem
|
10
|
+
- Support of k8s authentication method
|
11
|
+
- Additional tests and fixes related to Vault permissions
|
12
|
+
|
13
|
+
## [0.1.1]
|
4
14
|
### Fixes
|
5
15
|
- Fix CI release
|
6
16
|
- Add changelog
|
@@ -11,7 +21,9 @@
|
|
11
21
|
- Secrets caching
|
12
22
|
- Automatic secrets lease renewal
|
13
23
|
|
14
|
-
[Unreleased]: https://github.com/matic-insurance/settings_reader-vault_resolver/compare/0.
|
15
|
-
[0.1
|
24
|
+
[Unreleased]: https://github.com/matic-insurance/settings_reader-vault_resolver/compare/0.2.1...HEAD
|
25
|
+
[0.2.1]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.2.1
|
26
|
+
[0.2.0]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.2.0
|
27
|
+
[0.1.1]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.1.1
|
16
28
|
[0.1.0]: https://github.com/matic-insurance/settings_reader-vault_resolver/commits/0.1.0
|
17
29
|
|
data/Gemfile
CHANGED
@@ -1,4 +1,12 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in settings_reader-vault_resolver.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
gem 'codecov'
|
7
|
+
gem 'rake'
|
8
|
+
gem 'rspec'
|
9
|
+
gem 'rubocop'
|
10
|
+
gem 'rubocop-rspec'
|
11
|
+
gem 'simplecov'
|
12
|
+
gem 'timecop'
|
data/Gemfile.lock
CHANGED
@@ -3,6 +3,7 @@ PATH
|
|
3
3
|
specs:
|
4
4
|
settings_reader-vault_resolver (0.0.0)
|
5
5
|
concurrent-ruby (~> 1.1)
|
6
|
+
settings_reader (~> 0.1)
|
6
7
|
vault (~> 0.16)
|
7
8
|
|
8
9
|
GEM
|
@@ -18,11 +19,11 @@ GEM
|
|
18
19
|
diff-lcs (1.5.0)
|
19
20
|
docile (1.4.0)
|
20
21
|
parallel (1.21.0)
|
21
|
-
parser (3.1.
|
22
|
+
parser (3.1.1.0)
|
22
23
|
ast (~> 2.4.1)
|
23
|
-
rainbow (3.
|
24
|
+
rainbow (3.1.1)
|
24
25
|
rake (13.0.6)
|
25
|
-
regexp_parser (2.2.
|
26
|
+
regexp_parser (2.2.1)
|
26
27
|
rexml (3.2.5)
|
27
28
|
rspec (3.10.0)
|
28
29
|
rspec-core (~> 3.10.0)
|
@@ -37,20 +38,21 @@ GEM
|
|
37
38
|
diff-lcs (>= 1.2.0, < 2.0)
|
38
39
|
rspec-support (~> 3.10.0)
|
39
40
|
rspec-support (3.10.3)
|
40
|
-
rubocop (
|
41
|
+
rubocop (1.25.1)
|
41
42
|
parallel (~> 1.10)
|
42
|
-
parser (>=
|
43
|
+
parser (>= 3.1.0.0)
|
43
44
|
rainbow (>= 2.2.2, < 4.0)
|
44
|
-
regexp_parser (>= 1.8)
|
45
|
+
regexp_parser (>= 1.8, < 3.0)
|
45
46
|
rexml
|
46
|
-
rubocop-ast (>=
|
47
|
+
rubocop-ast (>= 1.15.1, < 2.0)
|
47
48
|
ruby-progressbar (~> 1.7)
|
48
|
-
unicode-display_width (>= 1.4.0, <
|
49
|
-
rubocop-ast (1.
|
50
|
-
parser (>= 3.
|
49
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
50
|
+
rubocop-ast (1.16.0)
|
51
|
+
parser (>= 3.1.1.0)
|
51
52
|
rubocop-rspec (1.32.0)
|
52
53
|
rubocop (>= 0.60.0)
|
53
54
|
ruby-progressbar (1.11.0)
|
55
|
+
settings_reader (0.1.0)
|
54
56
|
simplecov (0.21.2)
|
55
57
|
docile (~> 1.1)
|
56
58
|
simplecov-html (~> 0.11)
|
@@ -58,7 +60,7 @@ GEM
|
|
58
60
|
simplecov-html (0.12.3)
|
59
61
|
simplecov_json_formatter (0.1.3)
|
60
62
|
timecop (0.9.4)
|
61
|
-
unicode-display_width (1.
|
63
|
+
unicode-display_width (2.1.0)
|
62
64
|
vault (0.16.0)
|
63
65
|
aws-sigv4
|
64
66
|
|
@@ -66,15 +68,14 @@ PLATFORMS
|
|
66
68
|
ruby
|
67
69
|
|
68
70
|
DEPENDENCIES
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
rubocop
|
74
|
-
rubocop-rspec (~> 1.32.0)
|
71
|
+
codecov
|
72
|
+
rake
|
73
|
+
rspec
|
74
|
+
rubocop
|
75
|
+
rubocop-rspec
|
75
76
|
settings_reader-vault_resolver!
|
76
|
-
simplecov
|
77
|
-
timecop
|
77
|
+
simplecov
|
78
|
+
timecop
|
78
79
|
|
79
80
|
BUNDLED WITH
|
80
81
|
2.1.4
|
data/README.md
CHANGED
@@ -26,20 +26,13 @@ At the load of application when initializing `settings_reader`:
|
|
26
26
|
Vault.address = 'http://127.0.0.1:8200'
|
27
27
|
Vault.token = 'MY_SUPER_SECRET_TOKEN'
|
28
28
|
|
29
|
-
#
|
30
|
-
SettingsReader.
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
config.value_resolvers = [
|
36
|
-
SettingsReader::Resolvers::Vault,
|
37
|
-
SettingsReader::Resolvers::Env,
|
38
|
-
]
|
29
|
+
#Load Settings Reader and configure resolver
|
30
|
+
AppSettings = SettingsReader.load do |config|
|
31
|
+
# ... Other configurations
|
32
|
+
|
33
|
+
# Add vault resolver as one of resolvers
|
34
|
+
config.resolvers << SettingsReader::VaultResolver.resolver
|
39
35
|
end
|
40
|
-
|
41
|
-
#Load Settings
|
42
|
-
APP_SETTINGS = SettingsReader.load
|
43
36
|
```
|
44
37
|
|
45
38
|
### Usage
|
@@ -51,16 +44,22 @@ Assuming your settings has following structure:
|
|
51
44
|
app:
|
52
45
|
name: 'MyCoolApp'
|
53
46
|
hostname: 'http://localhost:3001'
|
54
|
-
|
47
|
+
static_secret: 'vault://secret/apps/my_cool_app#app_secret'
|
48
|
+
dynamic_secret: 'vault://database/creds/app-db#username'
|
55
49
|
```
|
56
50
|
|
57
51
|
When requesting `app/secret` from `SettingsReader` it will resolve in Vault as:
|
58
52
|
|
59
53
|
```ruby
|
60
|
-
secret =
|
54
|
+
secret = AppSettings.get('app/static_secret')
|
61
55
|
# Gem will read `vault://secret/app#secret` from YAML
|
62
56
|
# Gem will resolve value in Vault using Vault.kv('secret').read('apps/my_cool_app')
|
63
57
|
# Gem will return `app_secret` attribute from the secret resolved above
|
58
|
+
|
59
|
+
db_user = AppSettings.get('app/dynamic_secret')
|
60
|
+
# Gem will request dynamic credentials from `vault://database/creds/app-db` and cache them
|
61
|
+
# Gem will renew lease on retrieved credentials 3 minutes prior lease expiration from vault
|
62
|
+
# Gem will return `username` attribute from dynamic secret
|
64
63
|
```
|
65
64
|
|
66
65
|
## Development
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'settings_reader/vault_resolver'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +10,5 @@ require "settings_reader/vault_resolver"
|
|
10
10
|
# require "pry"
|
11
11
|
# Pry.start
|
12
12
|
|
13
|
-
require
|
13
|
+
require 'irb'
|
14
14
|
IRB.start(__FILE__)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module SettingsReader
|
2
2
|
module VaultResolver
|
3
|
+
# Parsing of vault address
|
3
4
|
class Address
|
4
5
|
def initialize(uri)
|
5
6
|
@uri = URI.parse(uri)
|
@@ -10,11 +11,11 @@ module SettingsReader
|
|
10
11
|
end
|
11
12
|
|
12
13
|
def path
|
13
|
-
@uri.path
|
14
|
+
@uri.path.delete_prefix('/')
|
14
15
|
end
|
15
16
|
|
16
17
|
def full_path
|
17
|
-
"#{mount}#{path}"
|
18
|
+
"#{mount}#{@uri.path}"
|
18
19
|
end
|
19
20
|
|
20
21
|
def attribute
|
@@ -22,7 +23,7 @@ module SettingsReader
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def options
|
25
|
-
URI
|
26
|
+
URI.decode_www_form(@uri.query || '').to_h
|
26
27
|
end
|
27
28
|
|
28
29
|
def to_s
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module SettingsReader
|
2
2
|
module VaultResolver
|
3
|
+
# Wrapper around vault secret object
|
3
4
|
class Entry
|
4
5
|
attr_reader :address, :secret
|
6
|
+
|
5
7
|
MONTH = 30 * 60 * 60
|
6
8
|
|
7
9
|
def initialize(address, secret)
|
@@ -32,12 +34,18 @@ module SettingsReader
|
|
32
34
|
@secret = Vault.sys.renew(@secret.lease_id)
|
33
35
|
@lease_started = Time.now
|
34
36
|
true
|
37
|
+
rescue Vault::HTTPClientError => e
|
38
|
+
raise SettingsReader::VaultResolver::Error, e.message
|
35
39
|
end
|
36
40
|
|
37
41
|
def value_for(attribute)
|
38
42
|
secret.data[attribute.to_sym]
|
39
43
|
end
|
40
44
|
|
45
|
+
def to_s
|
46
|
+
address.to_s
|
47
|
+
end
|
48
|
+
|
41
49
|
private
|
42
50
|
|
43
51
|
def lease_duration
|
@@ -0,0 +1,43 @@
|
|
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
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'vault'
|
2
|
+
|
3
|
+
module SettingsReader
|
4
|
+
module VaultResolver
|
5
|
+
# Resolver class for Settings Reader
|
6
|
+
class Instance
|
7
|
+
include Logging
|
8
|
+
|
9
|
+
IDENTIFIER = 'vault://'.freeze
|
10
|
+
DATABASE_MOUNT = 'database'.freeze
|
11
|
+
|
12
|
+
def resolvable?(value, _path)
|
13
|
+
return unless value.respond_to?(:start_with?)
|
14
|
+
|
15
|
+
value.start_with?(IDENTIFIER)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Expect value in format `vault://mount/path/to/secret?attribute_name`
|
19
|
+
def resolve(value, _path)
|
20
|
+
debug { "Resolving Vault secret at #{value}" }
|
21
|
+
address = SettingsReader::VaultResolver::Address.new(value)
|
22
|
+
entry = fetch_entry(address)
|
23
|
+
entry&.value_for(address.attribute)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Resolve KV secret
|
27
|
+
def kv_secret(address)
|
28
|
+
debug { "Fetching new kv secret at: #{address}" }
|
29
|
+
Vault.kv(address.mount).read(address.path)
|
30
|
+
rescue Vault::HTTPClientError => e
|
31
|
+
error { "Error retrieving secret at: #{address}: #{e.message}" }
|
32
|
+
raise SettingsReader::VaultResolver::Error, e.message
|
33
|
+
end
|
34
|
+
|
35
|
+
def database_secret(address)
|
36
|
+
debug { "Fetching new database secret at: #{address}" }
|
37
|
+
Vault.logical.read(address.full_path)
|
38
|
+
rescue Vault::HTTPClientError => e
|
39
|
+
error { "Error retrieving database secret: #{address}: #{e.message}" }
|
40
|
+
return nil if e.message.include?('* unknown role')
|
41
|
+
|
42
|
+
raise SettingsReader::VaultResolver::Error, e.message
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def fetch_entry(address)
|
48
|
+
cache.fetch(address) do
|
49
|
+
info { "Retrieving new secret at: #{address}" }
|
50
|
+
if (secret = address.mount == DATABASE_MOUNT ? database_secret(address) : kv_secret(address))
|
51
|
+
debug { "Retrieved secret at: #{address}" }
|
52
|
+
SettingsReader::VaultResolver::Entry.new(address, secret)
|
53
|
+
else
|
54
|
+
debug { "Secret not retrieved: #{address}" }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def cache
|
60
|
+
SettingsReader::VaultResolver.cache
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SettingsReader
|
2
|
+
module VaultResolver
|
3
|
+
# Methods for centralized logging
|
4
|
+
module Logging
|
5
|
+
def debug(&block)
|
6
|
+
logger.debug do
|
7
|
+
"[VaultResolver] #{block.call}"
|
8
|
+
end
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def info(&block)
|
13
|
+
logger.info do
|
14
|
+
"[VaultResolver] #{block.call}"
|
15
|
+
end
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def warn(&block)
|
20
|
+
logger.warn do
|
21
|
+
"[VaultResolver] #{block.call}"
|
22
|
+
end
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def error(&block)
|
27
|
+
logger.error do
|
28
|
+
"[VaultResolver] #{block.call}"
|
29
|
+
end
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def logger
|
36
|
+
@logger ||= SettingsReader::VaultResolver.logger
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Vault
|
2
|
+
# Monkey patch to support k8s authenticaiton. Taken from https://github.com/hashicorp/vault-ruby/pull/202
|
3
|
+
class Authenticate < Request
|
4
|
+
def kubernetes(role, route = nil, service_token_path = nil)
|
5
|
+
route ||= '/v1/auth/kubernetes/login'
|
6
|
+
service_token_path ||= '/var/run/secrets/kubernetes.io/serviceaccount/token'
|
7
|
+
|
8
|
+
payload = {
|
9
|
+
role: role,
|
10
|
+
jwt: File.read(service_token_path)
|
11
|
+
}
|
12
|
+
|
13
|
+
json = client.post(route, JSON.fast_generate(payload))
|
14
|
+
|
15
|
+
secret = Secret.decode(json)
|
16
|
+
client.token = secret.auth.client_token
|
17
|
+
|
18
|
+
secret
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
module SettingsReader
|
2
2
|
module VaultResolver
|
3
|
+
# Vault Lease refresher task
|
3
4
|
class Refresher
|
4
|
-
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
DEFAULT_RENEW_DELAY = 200
|
5
8
|
REFRESH_INTERVAL = 60
|
6
9
|
|
7
10
|
def initialize(cache)
|
@@ -10,19 +13,26 @@ module SettingsReader
|
|
10
13
|
|
11
14
|
def refresh
|
12
15
|
@cache.entries.each do |entry|
|
16
|
+
debug { "Checking lease for #{entry}. Leased?: #{entry.leased?}. Expires in: #{entry.expires_in}s" }
|
13
17
|
next unless entry.leased?
|
14
18
|
next unless entry.expires_in < DEFAULT_RENEW_DELAY
|
15
19
|
|
20
|
+
info { "Refreshing lease for #{entry}. Expires in: #{entry.expires_in}" }
|
16
21
|
entry.renew
|
22
|
+
info { "Lease renewed for #{entry}. Expires in: #{entry.expires_in}" }
|
23
|
+
rescue SettingsReader::VaultResolver::Error => e
|
24
|
+
error { "Error refreshing lease for #{entry}: #{e.message}" }
|
25
|
+
# Continue renewal.
|
17
26
|
end
|
18
27
|
end
|
19
28
|
|
20
29
|
def self.refresh_task(cache)
|
21
30
|
refresher = self
|
22
31
|
Concurrent::TimerTask.new(execution_interval: refresher::REFRESH_INTERVAL) do
|
32
|
+
info { 'Refreshing Vault leases' }
|
23
33
|
refresher.new(cache).refresh
|
24
34
|
end
|
25
35
|
end
|
26
36
|
end
|
27
37
|
end
|
28
|
-
end
|
38
|
+
end
|
@@ -1,12 +1,17 @@
|
|
1
|
-
require '
|
2
|
-
require
|
3
|
-
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
1
|
+
require 'logger'
|
2
|
+
require 'concurrent/timer_task'
|
3
|
+
|
4
|
+
require 'settings_reader'
|
5
|
+
require 'settings_reader/vault_resolver/version'
|
6
|
+
require 'settings_reader/vault_resolver/logging'
|
7
|
+
require 'settings_reader/vault_resolver/address'
|
8
|
+
require 'settings_reader/vault_resolver/entry'
|
9
|
+
require 'settings_reader/vault_resolver/cache'
|
10
|
+
require 'settings_reader/vault_resolver/refresher'
|
11
|
+
require 'settings_reader/vault_resolver/instance'
|
8
12
|
|
9
13
|
module SettingsReader
|
14
|
+
# Singleton for lease renewals and secrets cache
|
10
15
|
module VaultResolver
|
11
16
|
class Error < StandardError; end
|
12
17
|
|
@@ -14,6 +19,13 @@ module SettingsReader
|
|
14
19
|
attr_accessor :cache, :refresher_timer_task
|
15
20
|
end
|
16
21
|
|
22
|
+
def self.logger
|
23
|
+
return @logger if @logger
|
24
|
+
return @logger = Rails.logger if defined? Rails
|
25
|
+
|
26
|
+
@logger = Logger.new($stdout, level: Logger::INFO)
|
27
|
+
end
|
28
|
+
|
17
29
|
def self.setup_cache
|
18
30
|
self.cache ||= SettingsReader::VaultResolver::Cache.new
|
19
31
|
end
|
@@ -24,7 +36,7 @@ module SettingsReader
|
|
24
36
|
end
|
25
37
|
|
26
38
|
def self.resolver
|
27
|
-
SettingsReader::VaultResolver::
|
39
|
+
SettingsReader::VaultResolver::Instance.new
|
28
40
|
end
|
29
41
|
|
30
42
|
setup_cache
|
@@ -4,7 +4,7 @@ Gem::Specification.new do |spec|
|
|
4
4
|
spec.name = 'settings_reader-vault_resolver'
|
5
5
|
spec.version = SettingsReader::VaultResolver::VERSION
|
6
6
|
spec.authors = ['Volodymyr Mykhailyk']
|
7
|
-
spec.email = ['
|
7
|
+
spec.email = ['volodymyr.mykhailyk@gmail.com']
|
8
8
|
|
9
9
|
spec.summary = 'Settings Reader plugin to resolve values using in Hashicorp Vault'
|
10
10
|
spec.description = 'This gem works as a resolver for `settings_reader` gem. \
|
@@ -12,15 +12,15 @@ Gem::Specification.new do |spec|
|
|
12
12
|
with support of dynamic secrets and lease renewal.'
|
13
13
|
spec.homepage = 'https://github.com/matic-insurance/settings_reader-vault_resolver'
|
14
14
|
spec.license = 'MIT'
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 2.
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
|
16
16
|
|
17
17
|
spec.metadata['homepage_uri'] = spec.homepage
|
18
18
|
spec.metadata['source_code_uri'] = spec.homepage
|
19
|
-
spec.metadata['changelog_uri'] = spec.homepage
|
19
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
20
20
|
|
21
21
|
# Specify which files should be added to the gem when it is released.
|
22
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
-
spec.files
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
24
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|local)/}) }
|
25
25
|
end
|
26
26
|
spec.bindir = 'exe'
|
@@ -28,14 +28,6 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.require_paths = ['lib']
|
29
29
|
|
30
30
|
spec.add_dependency 'concurrent-ruby', '~> 1.1'
|
31
|
+
spec.add_dependency 'settings_reader', '~> 0.1'
|
31
32
|
spec.add_dependency 'vault', '~> 0.16'
|
32
|
-
|
33
|
-
spec.add_development_dependency 'bundler', '~> 2.0'
|
34
|
-
spec.add_development_dependency 'codecov', '~> 0.4'
|
35
|
-
spec.add_development_dependency 'rake', '~> 13.0'
|
36
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
37
|
-
spec.add_development_dependency 'rubocop', '~> 0.66'
|
38
|
-
spec.add_development_dependency 'rubocop-rspec', '~> 1.32.0'
|
39
|
-
spec.add_development_dependency 'simplecov', '~> 0.16'
|
40
|
-
spec.add_development_dependency 'timecop', '~> 0.9'
|
41
33
|
end
|
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.
|
4
|
+
version: 0.2.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-
|
11
|
+
date: 2022-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -25,144 +25,48 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: settings_reader
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0.
|
33
|
+
version: '0.1'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0.
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: bundler
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '2.0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '2.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: codecov
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0.4'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0.4'
|
40
|
+
version: '0.1'
|
69
41
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '13.0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '13.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
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: rubocop
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0.66'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - "~>"
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '0.66'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: rubocop-rspec
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - "~>"
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: 1.32.0
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - "~>"
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: 1.32.0
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: simplecov
|
42
|
+
name: vault
|
127
43
|
requirement: !ruby/object:Gem::Requirement
|
128
44
|
requirements:
|
129
45
|
- - "~>"
|
130
46
|
- !ruby/object:Gem::Version
|
131
47
|
version: '0.16'
|
132
|
-
type: :
|
48
|
+
type: :runtime
|
133
49
|
prerelease: false
|
134
50
|
version_requirements: !ruby/object:Gem::Requirement
|
135
51
|
requirements:
|
136
52
|
- - "~>"
|
137
53
|
- !ruby/object:Gem::Version
|
138
54
|
version: '0.16'
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: timecop
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - "~>"
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0.9'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - "~>"
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0.9'
|
153
55
|
description: |-
|
154
56
|
This gem works as a resolver for `settings_reader` gem. \
|
155
57
|
Any value with matching format will be resolved using Vault \
|
156
58
|
with support of dynamic secrets and lease renewal.
|
157
59
|
email:
|
158
|
-
-
|
60
|
+
- volodymyr.mykhailyk@gmail.com
|
159
61
|
executables: []
|
160
62
|
extensions: []
|
161
63
|
extra_rdoc_files: []
|
162
64
|
files:
|
65
|
+
- ".github/workflows/linters.yml"
|
163
66
|
- ".github/workflows/main.yml"
|
164
67
|
- ".gitignore"
|
165
68
|
- ".rspec"
|
69
|
+
- ".rubocop.yml"
|
166
70
|
- ".simplecov"
|
167
71
|
- CHANGELOG.md
|
168
72
|
- CODE_OF_CONDUCT.md
|
@@ -174,11 +78,14 @@ files:
|
|
174
78
|
- bin/console
|
175
79
|
- bin/setup
|
176
80
|
- docker-compose.yml
|
177
|
-
- lib/settings_reader/resolvers/vault.rb
|
178
81
|
- lib/settings_reader/vault_resolver.rb
|
179
82
|
- lib/settings_reader/vault_resolver/address.rb
|
180
83
|
- lib/settings_reader/vault_resolver/cache.rb
|
181
84
|
- lib/settings_reader/vault_resolver/entry.rb
|
85
|
+
- lib/settings_reader/vault_resolver/helpers/k8s_auth.rb
|
86
|
+
- lib/settings_reader/vault_resolver/instance.rb
|
87
|
+
- lib/settings_reader/vault_resolver/logging.rb
|
88
|
+
- lib/settings_reader/vault_resolver/patches/authenticate.rb
|
182
89
|
- lib/settings_reader/vault_resolver/refresher.rb
|
183
90
|
- lib/settings_reader/vault_resolver/version.rb
|
184
91
|
- settings_reader-vault_resolver.gemspec
|
@@ -197,7 +104,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
197
104
|
requirements:
|
198
105
|
- - ">="
|
199
106
|
- !ruby/object:Gem::Version
|
200
|
-
version: 2.
|
107
|
+
version: 2.5.0
|
201
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
202
109
|
requirements:
|
203
110
|
- - ">="
|
@@ -1,50 +0,0 @@
|
|
1
|
-
require 'vault'
|
2
|
-
|
3
|
-
module SettingsReader
|
4
|
-
module Resolver
|
5
|
-
class Vault
|
6
|
-
IDENTIFIER = 'vault://'.freeze
|
7
|
-
DATABASE_MOUNT = 'database'.freeze
|
8
|
-
|
9
|
-
def resolvable?(value, _path)
|
10
|
-
return unless value.respond_to?(:start_with?)
|
11
|
-
|
12
|
-
value.start_with?(IDENTIFIER)
|
13
|
-
end
|
14
|
-
|
15
|
-
# Expect value in format `vault://mount/path/to/secret?attribute_name`
|
16
|
-
def resolve(value, _path)
|
17
|
-
address = SettingsReader::VaultResolver::Address.new(value)
|
18
|
-
entry = fetch_entry(address)
|
19
|
-
entry&.value_for(address.attribute)
|
20
|
-
end
|
21
|
-
|
22
|
-
#Resolve KV secret
|
23
|
-
def kv_secret(address)
|
24
|
-
::Vault.kv(address.mount).read(address.path)
|
25
|
-
end
|
26
|
-
|
27
|
-
def database_secret(address)
|
28
|
-
::Vault.logical.read(address.full_path)
|
29
|
-
rescue ::Vault::HTTPClientError => e
|
30
|
-
raise unless e.message.include?('* unknown role')
|
31
|
-
|
32
|
-
nil
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def fetch_entry(address)
|
38
|
-
cache.fetch(address) do
|
39
|
-
if (secret = address.mount == DATABASE_MOUNT ? database_secret(address) : kv_secret(address))
|
40
|
-
SettingsReader::VaultResolver::Entry.new(address, secret)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def cache
|
46
|
-
SettingsReader::VaultResolver.cache
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|