vault_api 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +9 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +124 -0
- data/LICENSE.txt +23 -0
- data/README.md +226 -0
- data/Rakefile +16 -0
- data/bin/vault_conf +43 -0
- data/lib/vault_api/api.rb +29 -0
- data/lib/vault_api/client/entries.rb +129 -0
- data/lib/vault_api/client/paths.rb +19 -0
- data/lib/vault_api/client/policies.rb +75 -0
- data/lib/vault_api/client/secrets.rb +43 -0
- data/lib/vault_api/client/users.rb +66 -0
- data/lib/vault_api/client.rb +16 -0
- data/lib/vault_api/configuration.rb +45 -0
- data/lib/vault_api/connection.rb +18 -0
- data/lib/vault_api/error.rb +48 -0
- data/lib/vault_api/request.rb +65 -0
- data/lib/vault_api/version.rb +5 -0
- data/lib/vault_api.rb +57 -0
- data/vault_api.gemspec +63 -0
- metadata +319 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# VaultApi::Client::Policies
|
4
|
+
module VaultApi
|
5
|
+
class Client
|
6
|
+
module Policies
|
7
|
+
def create_initial_user_policy(username)
|
8
|
+
puts "Creating #{username}_policy"
|
9
|
+
if VaultApi.put_policy("#{username}_policy", policy_json(username))
|
10
|
+
puts "Created #{username}_policy"
|
11
|
+
true
|
12
|
+
else
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def read_policy(username)
|
18
|
+
VaultApi.policy("#{username}_policy")
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_policy(username, path = '', capabilities = [])
|
22
|
+
policy_rules = {}
|
23
|
+
policy_rules[:path] ||= {}
|
24
|
+
policy_rules[:path][path.to_s] ||= {}
|
25
|
+
policy_rules[:path][path.to_s][:capabilities] = capabilities
|
26
|
+
VaultApi.put_policy("#{username}_policy", policy_rules.to_json)
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_policy(username, path = '', capabilities = [])
|
30
|
+
policy = VaultApi.policy("#{username}_policy")
|
31
|
+
policy_rules = JSON.parse(policy.rules).with_indifferent_access
|
32
|
+
policy_rules[:path][path.to_s] ||= {}
|
33
|
+
policy_rules[:path][path.to_s][:capabilities] = capabilities
|
34
|
+
VaultApi.put_policy("#{username}_policy", policy_rules.to_json)
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete_policy(username)
|
38
|
+
VaultApi.delete_policy("#{username}_policy")
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def policy_json(username)
|
44
|
+
{
|
45
|
+
path: {
|
46
|
+
"secret/#{VaultApi.env}/#{username}/*" => {
|
47
|
+
capabilities: %w[create read update delete list]
|
48
|
+
},
|
49
|
+
"#{VaultApi.secret_global_base_path}/*" => {
|
50
|
+
capabilities: %w[read list]
|
51
|
+
},
|
52
|
+
:'secret/*' => {
|
53
|
+
capabilities: %w[read list]
|
54
|
+
},
|
55
|
+
:'auth/token/lookup-self' => {
|
56
|
+
capabilities: %w[read]
|
57
|
+
},
|
58
|
+
:'sys/capabilities-self' => {
|
59
|
+
capabilities: %w[update read]
|
60
|
+
},
|
61
|
+
:'sys/mounts' => {
|
62
|
+
capabilities: %w[read]
|
63
|
+
},
|
64
|
+
:'sys/auth' => {
|
65
|
+
capabilities: %w[read]
|
66
|
+
},
|
67
|
+
"sys/policy/#{username}_policy" => {
|
68
|
+
capabilities: %w[read]
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}.to_json
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
# VaultApi::Client::Secrets
|
6
|
+
module VaultApi
|
7
|
+
class Client
|
8
|
+
module Secrets
|
9
|
+
def secrets(user_name = nil)
|
10
|
+
VaultApi.list(VaultApi.secret_base_path(user_name))
|
11
|
+
end
|
12
|
+
|
13
|
+
def read_secret(config_name, user_name = nil)
|
14
|
+
VaultApi.read("#{VaultApi.secret_base_path(user_name)}/#{config_name}").data
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_secret(config_file_path, user_name = nil)
|
18
|
+
file_basename = File.basename(config_file_path, '.yml')
|
19
|
+
secret_path = "#{VaultApi.secret_base_path(user_name)}/#{file_basename}"
|
20
|
+
|
21
|
+
output_json = JSON.dump(YAML.load_file(config_file_path))
|
22
|
+
obj = JSON.parse(output_json)
|
23
|
+
content = obj[VaultApi.env.to_s]
|
24
|
+
VaultApi.write(secret_path, content)
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_secret(config_file_path, user_name = nil)
|
28
|
+
add_secret(config_file_path, user_name) # overwrites existing file
|
29
|
+
end
|
30
|
+
|
31
|
+
def upload_secrets(config_folder_path, user_name = nil)
|
32
|
+
Dir.chdir config_folder_path
|
33
|
+
Dir.glob('*.yml').each do |file|
|
34
|
+
add_secret("#{config_folder_path}/#{file}", user_name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete_secret(config_name, user_name = nil)
|
39
|
+
VaultApi.delete("#{VaultApi.secret_base_path(user_name)}/#{config_name}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/https'
|
4
|
+
require 'uri'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
# VaultApi::Client::Users
|
8
|
+
module VaultApi
|
9
|
+
class Client
|
10
|
+
module Users
|
11
|
+
def create_user(username)
|
12
|
+
secure_password = SecureRandom.hex(12)
|
13
|
+
|
14
|
+
creds = {
|
15
|
+
'password' => secure_password.to_s,
|
16
|
+
'policies' => "#{username}_policy"
|
17
|
+
}
|
18
|
+
uri = URI.parse("#{VaultApi.address}/v1/#{VaultApi.auth_users_path}/#{username}")
|
19
|
+
|
20
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
21
|
+
http.use_ssl = true
|
22
|
+
|
23
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
24
|
+
request.body = creds.to_json
|
25
|
+
request['X-Vault-Token'] = VaultApi.token.to_s
|
26
|
+
|
27
|
+
http.request(request)
|
28
|
+
|
29
|
+
creds
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_user_with_secret(username)
|
33
|
+
users = VaultApi.list(VaultApi.auth_users_path)
|
34
|
+
|
35
|
+
if users.include? username.to_s
|
36
|
+
puts "Vault user '#{username}' already exists."
|
37
|
+
# exit 1
|
38
|
+
else
|
39
|
+
create_initial_user_policy(username)
|
40
|
+
creds = create_user(username)
|
41
|
+
add_secrets_to_user_from_global(username)
|
42
|
+
|
43
|
+
creds
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_secrets_to_user_from_global(username)
|
48
|
+
global_path = VaultApi.secret_global_base_path
|
49
|
+
secrets = VaultApi.list(global_path)
|
50
|
+
|
51
|
+
secrets.each do |filename|
|
52
|
+
path_admin = "#{global_path}/#{filename}"
|
53
|
+
data = VaultApi.read(path_admin).data
|
54
|
+
user_path = "secret/#{VaultApi.env}/#{username}/#{filename}"
|
55
|
+
VaultApi.write(user_path, data)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete_user(username)
|
60
|
+
VaultApi.delete("/#{VaultApi.auth_users_path}/#{username}")
|
61
|
+
delete_policy(username)
|
62
|
+
delete_path(VaultApi.secret_user_base_path(username))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('api', __dir__)
|
4
|
+
|
5
|
+
module VaultApi
|
6
|
+
# Wrapper for the VaultApi REST API.
|
7
|
+
class Client < API
|
8
|
+
Dir[File.expand_path('client/*.rb', __dir__)].each { |f| require f }
|
9
|
+
|
10
|
+
include VaultApi::Client::Paths
|
11
|
+
include VaultApi::Client::Users
|
12
|
+
include VaultApi::Client::Entries
|
13
|
+
include VaultApi::Client::Secrets
|
14
|
+
include VaultApi::Client::Policies
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module VaultApi
|
4
|
+
module Configuration
|
5
|
+
VALID_OPTIONS_KEYS = %i[
|
6
|
+
address
|
7
|
+
token
|
8
|
+
user
|
9
|
+
password
|
10
|
+
env
|
11
|
+
|
12
|
+
logger
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
# Use the default Faraday adapter.
|
16
|
+
# DEFAULT_ADAPTER = Faraday.default_adapter
|
17
|
+
|
18
|
+
# By default use the main api URL.
|
19
|
+
DEFAULT_ADDRESS = ''
|
20
|
+
|
21
|
+
attr_accessor *VALID_OPTIONS_KEYS
|
22
|
+
|
23
|
+
# Convenience method to allow configuration options to be set in a block
|
24
|
+
def configure
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
|
28
|
+
def options
|
29
|
+
VALID_OPTIONS_KEYS.each_with_object({}) do |key, option|
|
30
|
+
option[key] = send(key)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# When this module is extended, reset all settings.
|
35
|
+
def self.extended(base)
|
36
|
+
base.reset
|
37
|
+
end
|
38
|
+
|
39
|
+
# Reset all configuration settings to default values.
|
40
|
+
def reset
|
41
|
+
self.address = DEFAULT_ADDRESS
|
42
|
+
# self.adapter = DEFAULT_ADAPTER
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'vault'
|
4
|
+
module VaultApi
|
5
|
+
module Connection
|
6
|
+
# private
|
7
|
+
|
8
|
+
def connection
|
9
|
+
if token
|
10
|
+
connection_obj = Vault::Client.new(address: address, token: token)
|
11
|
+
else
|
12
|
+
connection_obj = Vault::Client.new(address: address)
|
13
|
+
connection_obj.auth.userpass(user, password)
|
14
|
+
end
|
15
|
+
connection_obj
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module VaultApi
|
4
|
+
class Error < StandardError
|
5
|
+
def initialize(e)
|
6
|
+
@wrapped_exception = nil
|
7
|
+
|
8
|
+
if e.respond_to?(:backtrace)
|
9
|
+
super(e.message)
|
10
|
+
@wrapped_exception = e
|
11
|
+
else
|
12
|
+
super(e.to_s)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def backtrace
|
17
|
+
if @wrapped_exception
|
18
|
+
@wrapped_exception.backtrace
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
inner = ''
|
26
|
+
inner << " wrapped=#{@wrapped_exception.inspect}" if @wrapped_exception
|
27
|
+
inner << " #{super}" if inner.empty?
|
28
|
+
%(#<#{self.class}#{inner}>)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class ConnectionError < Error; end
|
33
|
+
class AuthorizationError < Error; end
|
34
|
+
class BadRequestError < Error; end
|
35
|
+
class RecordNotFoundError < Error; end
|
36
|
+
|
37
|
+
class TimeoutError < Error; end
|
38
|
+
class NotFoundError < Error; end
|
39
|
+
class SSLError < Error; end
|
40
|
+
class ParseError < Error; end
|
41
|
+
class UnauthorizedError < Error; end
|
42
|
+
|
43
|
+
%i[Error
|
44
|
+
ConnectionError AuthorizationError BadRequestError RecordNotFoundError
|
45
|
+
TimeoutError NotFoundError SSLError ParseError UnauthorizedError].each do |const|
|
46
|
+
Error.const_set(const, VaultApi.const_get(const))
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module VaultApi
|
4
|
+
module Request
|
5
|
+
def list(params)
|
6
|
+
request(:list, params)
|
7
|
+
end
|
8
|
+
|
9
|
+
def read(params)
|
10
|
+
request(:read, params)
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(path, config)
|
14
|
+
request(:write, path, config)
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(params)
|
18
|
+
request(:delete, params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def policy(params)
|
22
|
+
request_sys(:policy, params)
|
23
|
+
end
|
24
|
+
|
25
|
+
def put_policy(params, rules)
|
26
|
+
request_sys(:put_policy, params, rules)
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete_policy(params)
|
30
|
+
request_sys(:delete_policy, params)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def request(method, *params)
|
36
|
+
begin
|
37
|
+
response = case method
|
38
|
+
when :write
|
39
|
+
connection.logical.send(method, params[0], params[1])
|
40
|
+
else
|
41
|
+
connection.logical.send(method, params[0])
|
42
|
+
end
|
43
|
+
rescue StandardError => e
|
44
|
+
raise Error, e
|
45
|
+
end
|
46
|
+
|
47
|
+
response
|
48
|
+
end
|
49
|
+
|
50
|
+
def request_sys(method, *params)
|
51
|
+
begin
|
52
|
+
response = case method
|
53
|
+
when :put_policy
|
54
|
+
connection.sys.send(method, params[0], params[1])
|
55
|
+
else
|
56
|
+
connection.sys.send(method, params[0])
|
57
|
+
end
|
58
|
+
rescue StandardError => e
|
59
|
+
raise Error, e
|
60
|
+
end
|
61
|
+
|
62
|
+
response
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/vault_api.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
require 'active_support/all'
|
5
|
+
|
6
|
+
require 'vault_api/api'
|
7
|
+
require 'vault_api/client'
|
8
|
+
require 'vault_api/version'
|
9
|
+
|
10
|
+
require File.expand_path('vault_api/configuration', __dir__)
|
11
|
+
require File.expand_path('vault_api/api', __dir__)
|
12
|
+
require File.expand_path('vault_api/client', __dir__)
|
13
|
+
require File.expand_path('vault_api/error', __dir__)
|
14
|
+
|
15
|
+
module VaultApi
|
16
|
+
extend Configuration
|
17
|
+
# Alias for VaultApi::Client.new
|
18
|
+
# @return [VaultApi::Client]
|
19
|
+
def self.client(options = {})
|
20
|
+
VaultApi::Client.new(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Delegate to VaultApi::Client
|
24
|
+
def self.method_missing(method, *args, &block)
|
25
|
+
return super unless client.respond_to?(method)
|
26
|
+
|
27
|
+
client.send(method, *args, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.secret_base_path(user_name = nil)
|
31
|
+
if user_name.present?
|
32
|
+
secret_user_base_path(user_name)
|
33
|
+
elsif VaultApi.user.present? # && VaultApi.password.present?
|
34
|
+
secret_user_base_path
|
35
|
+
elsif VaultApi.token.present?
|
36
|
+
secret_global_base_path
|
37
|
+
else
|
38
|
+
''
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.secret_global_base_path
|
43
|
+
"secret/global/#{VaultApi.env}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.secret_user_base_path(user_name = nil)
|
47
|
+
if user_name.present?
|
48
|
+
"secret/#{VaultApi.env}/#{user_name}"
|
49
|
+
else
|
50
|
+
"secret/#{VaultApi.env}/#{VaultApi.user}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.auth_users_path
|
55
|
+
'auth/userpass/users'
|
56
|
+
end
|
57
|
+
end
|
data/vault_api.gemspec
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'vault_api/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'vault_api'
|
9
|
+
spec.version = VaultApi::VERSION
|
10
|
+
spec.authors = ['Sachin S Wagh']
|
11
|
+
spec.email = ['sachinwagh2392@gmail.com']
|
12
|
+
|
13
|
+
spec.summary = 'Client for the vault_api API'
|
14
|
+
spec.description = 'Client for the vault_api API'
|
15
|
+
spec.homepage = ''
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
19
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
20
|
+
if spec.respond_to?(:metadata)
|
21
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
22
|
+
else
|
23
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
24
|
+
'public gem pushes.'
|
25
|
+
end
|
26
|
+
|
27
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
28
|
+
f.match(%r{^(test|spec|features)/})
|
29
|
+
end
|
30
|
+
# spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
31
|
+
|
32
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
33
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
34
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
35
|
+
spec.require_paths = ['lib']
|
36
|
+
|
37
|
+
spec.add_development_dependency 'bundler'
|
38
|
+
spec.add_development_dependency 'byebug'
|
39
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
40
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
41
|
+
spec.add_development_dependency 'rspec-expectations'
|
42
|
+
spec.add_development_dependency 'rspec-mocks'
|
43
|
+
spec.add_development_dependency 'rspec_junit_formatter'
|
44
|
+
|
45
|
+
###
|
46
|
+
spec.add_development_dependency 'awesome_print'
|
47
|
+
spec.add_development_dependency 'pry'
|
48
|
+
spec.add_development_dependency 'rubocop'
|
49
|
+
spec.add_development_dependency 'rubocop-rspec'
|
50
|
+
|
51
|
+
spec.add_runtime_dependency 'activesupport'
|
52
|
+
# spec.add_runtime_dependency 'faraday'
|
53
|
+
# spec.add_runtime_dependency 'faraday_middleware'
|
54
|
+
spec.add_runtime_dependency 'hashie'
|
55
|
+
spec.add_runtime_dependency 'nokogiri'
|
56
|
+
# spec.add_runtime_dependency 'vcr'
|
57
|
+
spec.add_runtime_dependency 'rest-client'
|
58
|
+
spec.add_runtime_dependency 'vcr', '3.0'
|
59
|
+
###
|
60
|
+
|
61
|
+
spec.add_runtime_dependency('vault')
|
62
|
+
spec.add_runtime_dependency('webmock')
|
63
|
+
end
|