dsv-sdk 0.0.1.pre.SNAPSHOT

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 202d144ecdbd1258d681b6fb2af3bed61324742348466746f432d38d1860b0bc
4
+ data.tar.gz: acb8f986eec3d37aaa0a01424865b5fd08aa7a04f3a1308c4bcecbcb8850b6ad
5
+ SHA512:
6
+ metadata.gz: 89c1554fce29a350a0f46f94a36c54a69de9f4fe5185682d9df457fcba608d054b3276dc62ae756fda9e05bf84e39111a4d2d6e22550cc119acd46b7b8dd76df
7
+ data.tar.gz: 7074d4be9fa997ef7935f03d9c347de6163c9c8aa5272d49e08500b6a71ab5e4e63c5fd7c5c3c45a8ab0289cce90e8a4f63e2cebcafe0ccf5d3f59d41acf62b0
@@ -0,0 +1,33 @@
1
+ class Vault::Client
2
+ CLIENTS_RESOURCE = "clients".freeze
3
+
4
+ # Fetch desired client information from the specified Vault
5
+ #
6
+ # @param vault [Vault] The initialized Vault
7
+ # @param id [String] The ID of the client
8
+ #
9
+ # @return client [Hash]
10
+ def self.fetch(vault, id)
11
+ @vault = vault
12
+ client = @vault.accessResource("GET", CLIENTS_RESOURCE, id, nil)
13
+ end
14
+
15
+ # Mark the client as ready to be removed
16
+ #
17
+ # @param vault [Vault] The initialized Vault
18
+ # @param id [String] The ID of the client
19
+ def self.delete(vault, id)
20
+ vault.accessResource("DELETE", CLIENTS_RESOURCE, id, nil, nil)
21
+ end
22
+
23
+ # Create the client for the desired Vault
24
+ #
25
+ # @param vault [Vault] The initialized Vault
26
+ # @param role_Name [String] Name of the role to create
27
+ def self.create(vault, role_name)
28
+ client_data = {
29
+ role: role_name
30
+ }
31
+ vault.accessResource("POST", CLIENTS_RESOURCE, "/", client_data)
32
+ end
33
+ end
data/lib/vault/role.rb ADDED
@@ -0,0 +1,16 @@
1
+ require './lib/vault.rb'
2
+ class Vault::Role
3
+ ROLES_RESOURCE = "roles".freeze
4
+
5
+ # Return the specified role
6
+ #
7
+ # @param vault [Vault] The initialized Vault
8
+ # @param name [String] Name of the role
9
+ #
10
+ # @return role [Hash]
11
+ def self.fetch(vault, name)
12
+ @vault = vault
13
+
14
+ role = @vault.accessResource("GET", ROLES_RESOURCE, name, nil)
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+
2
+ # Utilized to fetch secrets from an initialzed +Vault+
3
+ class Vault::Secret
4
+ SECRETS_RESOURCE = "secrets".freeze
5
+
6
+ # Fetch secrets from the server
7
+ #
8
+ # @param vault [Server]
9
+ # @param path [String] path to the secret
10
+ #
11
+ # @return [Hash] of the secret
12
+ def self.fetch(vault, path)
13
+ @vault = vault
14
+
15
+ @secret = @vault.accessResource("GET", SECRETS_RESOURCE, path, nil)
16
+ end
17
+ end
@@ -0,0 +1,183 @@
1
+ require 'json'
2
+ require 'faraday'
3
+ require 'logger'
4
+
5
+ ##
6
+ # If we're in the +test+ environment just dump logs to Null
7
+ # Otherwise, use stdout
8
+ if ENV['test']
9
+ $logger = Logger.new(IO::NULL)
10
+ else
11
+ $logger = Logger.new(STDOUT)
12
+ end
13
+
14
+ ##
15
+ # Invoked when the API returns an +API_AccessDenied+ error
16
+ class AccessDeniedException < StandardError; end;
17
+ class ResourceNotFoundException < StandardError; end;
18
+ class InvalidConfigurationException < StandardError; end;
19
+ class InvalidCredentialsException < StandardError; end;
20
+ class InvalidMethodTypeException < StandardError; end;
21
+ class UnrecognizedResourceException < StandardError; end;
22
+
23
+ class Vault
24
+ DEFAULT_URL_TEMPLATE = "https://%s.secretsvaultcloud.%s/v1/%s%s"
25
+ DEFAULT_TLD = "com"
26
+
27
+ # Initialize a +Vault+ object with provided configuration.
28
+ #
29
+ # @param config [Hash]
30
+ # - client_id: ''
31
+ # - client_secret: ''
32
+ # - tld: 'com'
33
+ # - tenant: ''
34
+ def initialize(config = nil)
35
+
36
+ if config
37
+ @configuration = config.collect{|k,v| [k.to_s, v]}.to_h
38
+ else
39
+ @configuration = {}
40
+
41
+ @configuration['client_id'] = ENV['DSV_CLIENT_ID']
42
+ @configuration['client_secret'] = ENV['DSV_CLIENT_SECRET']
43
+ @configuration['tenant'] = ENV['DSV_TENANT']
44
+ @configuration['tld'] = ENV['DSV_TLD']
45
+ end
46
+
47
+ if @configuration['client_id'].nil? || @configuration['client_secret'].nil?
48
+ $logger.error("Must provide client_id and client_secret")
49
+ raise InvalidConfigurationException
50
+ end
51
+
52
+ $logger.debug("Vault is configured for client_id: #{@configuration['client_id']}")
53
+ end
54
+
55
+ # Helper method to access a resource via API
56
+ #
57
+ # @param method [String] HTTP Request Method
58
+ # @param resource [String] The resource type to invoke
59
+ # @param path [String] The API Path to request
60
+ # @param input [String] Input to send via POST/PUT body
61
+ #
62
+ # @return [Hash] - return Hash of JSON contents
63
+ #
64
+ # - +AccessDeniedException+ is raised if the server responds with an +API_AccessDenied+ error
65
+ # - +InvalidMethodTypeException+ is raised if a method other than +["GET", "POST", "PUT", "DELETE"]+ is provided
66
+ # - +UnrecognizedResourceException+ is raised if a resource other than +["clients", "roles", "secrets"]+ is requested
67
+ def accessResource(method, resource, path, input, parse_json=true)
68
+ unless ["GET", "POST", "PUT", "DELETE"].include?(method.upcase)
69
+ $logger.error "Invalid request method: #{method}"
70
+ raise InvalidMethodTypeException
71
+ end
72
+
73
+ unless ["clients", "roles", "secrets"].include? resource
74
+ message = "unrecognized resource"
75
+ $logger.debug "#{message}, #{resource}"
76
+
77
+ raise UnrecognizedResourceException
78
+ end
79
+
80
+ body = ""
81
+
82
+ unless input.nil?
83
+ body = input.to_json
84
+ end
85
+
86
+ accessToken = getAccessToken
87
+
88
+ # Yikes, normally not a fan of metaprogramming
89
+ # We first ensured that `method` is legit to prevent
90
+ # arbitrary method invocation
91
+ url = urlFor(resource, path)
92
+ $logger.debug "Sending request to: #{url}"
93
+ resp = Faraday.send(method.downcase, url) do | req |
94
+ req.headers['Authorization'] = "Bearer #{accessToken}"
95
+ req.body = body unless body.empty?
96
+
97
+ if ["POST", "PUT"].include?(method.upcase)
98
+ req.headers['Content-Type'] = 'application/json'
99
+ end
100
+ end
101
+
102
+ data = resp.body
103
+
104
+ return data unless parse_json
105
+
106
+ begin
107
+ hash = JSON.parse(data)
108
+
109
+ if hash['errorCode'] == "API_AccessDenied"
110
+ raise AccessDeniedException
111
+ end
112
+
113
+ if hash['message'] == "Invalid permissions"
114
+ raise AccessDeniedException
115
+ end
116
+
117
+ if hash['code'] == 404
118
+ raise ResourceNotFoundException
119
+ end
120
+
121
+ rescue JSON::ParserError => e
122
+ $logger.error "Error parsing JSON: #{e.to_s}"
123
+ raise e
124
+ end
125
+
126
+ return hash
127
+ end
128
+
129
+ # Query API for OAuth token
130
+ #
131
+ # - +InvalidCredentialsException+ is returned if the provided credentials are not valid
132
+ #
133
+ # @return [String]
134
+ def getAccessToken
135
+ grantRequest = {
136
+ grant_type: "client_credentials",
137
+ client_id: @configuration['client_id'],
138
+ client_secret: @configuration['client_secret']
139
+ }.to_json
140
+
141
+ url = urlFor("token")
142
+
143
+ $logger.debug "calling #{url} with client_id #{@configuration['client_id']}"
144
+
145
+ response = Faraday.post(
146
+ url,
147
+ grantRequest,
148
+ "Content-Type" => "application/json"
149
+ )
150
+
151
+ unless response.status == 200
152
+ $logger.debug "grant response error: #{response.body}"
153
+ raise InvalidCredentialsException
154
+ end
155
+
156
+ begin
157
+ grant = JSON.parse(response.body)
158
+ return grant['accessToken']
159
+ rescue JSON::ParserError => e
160
+ $logger.error "Error parsing JSON: #{e.to_s}"
161
+ raise e
162
+ end
163
+ end
164
+
165
+ # Generate the URL for a specific request.
166
+ # This factors in several configuration options including:
167
+ # - +tenant+
168
+ # - +tld+
169
+ #
170
+ # @param resource [String] The specific API resource to request
171
+ # @param path [String] The path to the resource
172
+ #
173
+ # @return [String] The generated URL for the request
174
+ def urlFor(resource, path=nil)
175
+
176
+ if path != nil
177
+ path = "/#{path.delete_prefix("/")}"
178
+ end
179
+
180
+ sprintf(DEFAULT_URL_TEMPLATE, @configuration['tenant'], @configuration['tld'] || DEFAULT_TLD, resource, path)
181
+ end
182
+
183
+ end
data/lib/vault.rb ADDED
@@ -0,0 +1,7 @@
1
+ class Vault
2
+ require 'vault/vault'
3
+
4
+ require 'vault/client'
5
+ require 'vault/secret'
6
+ require 'vault/role'
7
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dsv-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.pre.SNAPSHOT
5
+ platform: ruby
6
+ authors:
7
+ - John Poulin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-04-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.7'
27
+ description: The Thycotic DevOps Secrets Vault SDK for Ruby
28
+ email: john.m.poulin@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/vault.rb
34
+ - lib/vault/client.rb
35
+ - lib/vault/role.rb
36
+ - lib/vault/secret.rb
37
+ - lib/vault/vault.rb
38
+ homepage: https://rubygems.org/gems/hola
39
+ licenses:
40
+ - Apache-2.0
41
+ metadata:
42
+ github_repo: ssh://github.com/forced-request/dsv-ruby-sdk
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">"
55
+ - !ruby/object:Gem::Version
56
+ version: 1.3.1
57
+ requirements: []
58
+ rubygems_version: 3.0.3
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: dsv-sdk
62
+ test_files: []