securden 0.0.1
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +78 -0
- data/lib/securden/version.rb +4 -0
- data/lib/securden.rb +420 -0
- metadata +48 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3ab035ae2404108a3ad9234d90687856acdeef487c9c39f1ccc2d9a1d7292022
|
|
4
|
+
data.tar.gz: bc792eae5a82c88107a444d99269c254cfb4a8d743d1e1452b66c9ffdea9a2b2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 257699c10ca6879c21935259df6625c5bdc92036c20efa7b8296ed390a6a9cf8c29191f30325b7dee7558251686e44a9cf22262201668cfde82d242ecf608d8a
|
|
7
|
+
data.tar.gz: af2dd70f75d6f4902a870ee18079145fce2afef24c46a1cc6dcefd9408cd030d34357a2932ef8784d3a50a297325f3eca08441d9f846beed5253de47f5b06300
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 securden
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Securden Chef Plugin
|
|
2
|
+
|
|
3
|
+
The Securden Chef plugin enables seamless integration with Securden Secrets Manager, making it easy to fetch and utilize account details within your Chef recipes. This guide provides a detailed overview of the plugin's features and usage.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add the Securden Chef plugin gem to your Chef environment. Ensure all required dependencies, including Ruby and the Chef client, are properly installed and configured.
|
|
8
|
+
|
|
9
|
+
## Initialization
|
|
10
|
+
|
|
11
|
+
The Securden plugin is initialized with the following parameters:
|
|
12
|
+
- `server_url`: The URL of your Securden Secrets Manager server.
|
|
13
|
+
- `authtoken`: The authentication token required for API access.
|
|
14
|
+
- `certificate`: (Optional) Path to the certificate file. If omitted, the plugin automatically fetches the certificate.
|
|
15
|
+
|
|
16
|
+
## Usage Example
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
require 'Securden'
|
|
20
|
+
|
|
21
|
+
securden = nil
|
|
22
|
+
|
|
23
|
+
ruby_block 'Plugin Initialize Block' do
|
|
24
|
+
block do
|
|
25
|
+
securden = Securden::Account.new(
|
|
26
|
+
server_url: node['securden']['server_url'],
|
|
27
|
+
authtoken: node['securden']['authtoken'],
|
|
28
|
+
certificate: node['securden']['certificate'] # Certificate is optional
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
ruby_block 'Get Account Block' do
|
|
34
|
+
block do
|
|
35
|
+
if securden
|
|
36
|
+
account = securden.get(account_id: node['securden']['account_id'])
|
|
37
|
+
if account
|
|
38
|
+
puts "\nPassword: #{account['password']}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Access
|
|
46
|
+
|
|
47
|
+
Once the account data is fetched, you can access its attributes directly, such as `password`, or any additional fields created in the account.
|
|
48
|
+
|
|
49
|
+
## Example Workflow
|
|
50
|
+
|
|
51
|
+
1. Define necessary `node` attributes, such as `server_url`, `authtoken`, and optionally, `certificate`.
|
|
52
|
+
2. Initialize the Securden plugin using the `Securden::Account.new` method.
|
|
53
|
+
3. Use the `get` method to fetch account details.
|
|
54
|
+
4. Access and utilize the required attributes in your Chef recipes.
|
|
55
|
+
|
|
56
|
+
## Key Features
|
|
57
|
+
|
|
58
|
+
1. **Plugin Initialization**:
|
|
59
|
+
- Simplifies the initialization process with essential parameters (`server_url`, `authtoken`, `certificate`).
|
|
60
|
+
|
|
61
|
+
2. **Fetching Account Data**:
|
|
62
|
+
- Retrieve account details using the `get` method by providing any of the following:
|
|
63
|
+
- `account_id`
|
|
64
|
+
- `account_title`
|
|
65
|
+
- `account_name`
|
|
66
|
+
- Combination of `account_name` and `account_title`
|
|
67
|
+
|
|
68
|
+
3. **Accessing Account Attributes**:
|
|
69
|
+
- Access the fetched account's attributes, such as `password`, or additional fields created in the account.
|
|
70
|
+
|
|
71
|
+
## Additional Notes
|
|
72
|
+
|
|
73
|
+
- Ensure that `server_url` and `authtoken` values are securely stored and not hardcoded in your recipes.
|
|
74
|
+
- The `certificate` parameter is optional; the plugin will automatically fetch it if not provided.
|
|
75
|
+
- Additional fields in the account can be accessed using their respective names.
|
|
76
|
+
|
|
77
|
+
For further details and support, refer to the official Securden documentation.
|
|
78
|
+
|
data/lib/securden.rb
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
require "json"
|
|
3
|
+
require "openssl"
|
|
4
|
+
|
|
5
|
+
module Securden
|
|
6
|
+
# request header constants
|
|
7
|
+
AUTHTOKEN = "authtoken"
|
|
8
|
+
ORG = "org"
|
|
9
|
+
# request constants
|
|
10
|
+
GET = "GET"
|
|
11
|
+
POST = "POST"
|
|
12
|
+
PUT = "PUT"
|
|
13
|
+
DELETE = "DELETE"
|
|
14
|
+
|
|
15
|
+
# log contants
|
|
16
|
+
INFO = "info"
|
|
17
|
+
ERROR = "error"
|
|
18
|
+
WARN = "warn"
|
|
19
|
+
DEBUG = "debug"
|
|
20
|
+
|
|
21
|
+
class Error < StandardError; end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
attr_accessor :server_url, :authtoken, :org, :certificate
|
|
25
|
+
def log(message:, level: )
|
|
26
|
+
message = "[SECURDEN] " + message.to_s
|
|
27
|
+
case level
|
|
28
|
+
when ERROR
|
|
29
|
+
Chef::Log.error(message)
|
|
30
|
+
nil
|
|
31
|
+
when DEBUG
|
|
32
|
+
Chef::Log.debug(message)
|
|
33
|
+
when WARN
|
|
34
|
+
Chef::Log.warn(message)
|
|
35
|
+
when INFO
|
|
36
|
+
Chef::Log.info(message)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class Init
|
|
42
|
+
def initialize(server_url:, authtoken:, org: nil, certificate: nil)
|
|
43
|
+
if server_url.nil? || authtoken.nil?
|
|
44
|
+
Securden.log(message: "Server URL and authtoken are required to initialize Securden", level: ERROR)
|
|
45
|
+
return nil
|
|
46
|
+
end
|
|
47
|
+
unless server_url.start_with?("http://", "https://")
|
|
48
|
+
Securden.log(message: "Invalid server URL. It must begin with either http:// or https://", level: ERROR)
|
|
49
|
+
return nil
|
|
50
|
+
end
|
|
51
|
+
Securden.server_url = server_url
|
|
52
|
+
Securden.authtoken = authtoken
|
|
53
|
+
Securden.org = org unless org.nil? || org.nil?
|
|
54
|
+
Securden.certificate = certificate
|
|
55
|
+
Securden.log(message: "Securden initialized successfully", level: DEBUG)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class Account
|
|
60
|
+
def self.get(account_id: nil, account_name: nil, account_title: nil, account_type: nil)
|
|
61
|
+
begin
|
|
62
|
+
unless Securden.server_url && Securden.authtoken
|
|
63
|
+
Securden.log(message: "Securden has not been initialized. Please initialize before using", level: ERROR)
|
|
64
|
+
return nil
|
|
65
|
+
end
|
|
66
|
+
if account_id.nil? && account_title.nil? && account_name.nil? && account_type.nil?
|
|
67
|
+
Securden.log(message: "Required account attributes Account ID, Account title or Account name", level: ERROR)
|
|
68
|
+
return nil
|
|
69
|
+
end
|
|
70
|
+
Securden.log(message: "Fetching account from Securden", level: DEBUG)
|
|
71
|
+
params = {}
|
|
72
|
+
params["account_id"] = account_id.to_i if account_id
|
|
73
|
+
params["account_title"] = account_title unless account_title.nil?
|
|
74
|
+
params["account_name"] = account_name unless account_name.nil?
|
|
75
|
+
params["account_type"] = account_type unless account_type.nil?
|
|
76
|
+
account = Request.new.raise_request(params, "/secretsmanagement/get_account", GET)
|
|
77
|
+
if account
|
|
78
|
+
if account["message"]
|
|
79
|
+
Securden.log(message: account["message"], level: DEBUG)
|
|
80
|
+
end
|
|
81
|
+
return account
|
|
82
|
+
else
|
|
83
|
+
return nil
|
|
84
|
+
end
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
Securden.log(message: "Failed to fetch account data: #{e.message}", level: ERROR)
|
|
87
|
+
return nil
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.add(account_title: nil, account_name: nil, account_type: nil, password:nil, ipaddress: nil, notes: nil, tags: nil, personal_account: nil, folder_id: nil, account_expiration_date: nil, distinguished_name: nil, account_alias: nil, domain_name: nil)
|
|
92
|
+
begin
|
|
93
|
+
unless Securden.server_url && Securden.authtoken
|
|
94
|
+
Securden.log(message: "Securden has not been initialized. Please initialize before using", level: ERROR)
|
|
95
|
+
return nil
|
|
96
|
+
end
|
|
97
|
+
if account_type.nil?
|
|
98
|
+
Securden.log(message: "Required Account type", level: ERROR)
|
|
99
|
+
return nil
|
|
100
|
+
elsif account_title.nil?
|
|
101
|
+
Securden.log(message: "Required account title", level: ERROR)
|
|
102
|
+
return nil
|
|
103
|
+
end
|
|
104
|
+
Securden.log(message: "Adding account", level: DEBUG)
|
|
105
|
+
params = {}
|
|
106
|
+
params["account_title"] = account_title
|
|
107
|
+
params["account_type"] = account_type
|
|
108
|
+
params["account_name"] = account_name unless account_name.nil?
|
|
109
|
+
params["personal_account"] = personal_account unless personal_account.nil?
|
|
110
|
+
params["ipaddress"] = ipaddress unless ipaddress.nil?
|
|
111
|
+
params["notes"] = notes unless notes.nil?
|
|
112
|
+
params["tags"] = tags unless tags.nil?
|
|
113
|
+
params["folder_id"] = folder_id unless folder_id.nil?
|
|
114
|
+
params["password"] = password unless password.nil?
|
|
115
|
+
params["account_expiration_date"] = account_expiration_date unless account_expiration_date.nil?
|
|
116
|
+
params["distinguished_name"] = distinguished_name unless distinguished_name.nil?
|
|
117
|
+
params["account_alias"] = account_alias unless account_alias.nil?
|
|
118
|
+
params["domain_name"] = domain_name unless domain_name.nil?
|
|
119
|
+
account = Request.new.raise_request(params, "/api/add_account", POST)
|
|
120
|
+
if account
|
|
121
|
+
if account["message"]
|
|
122
|
+
Securden.log(message: account["message"], level: DEBUG)
|
|
123
|
+
end
|
|
124
|
+
return account
|
|
125
|
+
else
|
|
126
|
+
Securden.log(message: "Could not add account", level: ERROR)
|
|
127
|
+
return nil
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def self.edit(account_id: nil, account_title: nil, account_name: nil, account_type: nil, ipaddress: nil, notes: nil, tags: nil, personal_account: nil, folder_id: nil, account_expiration_date: nil, distinguished_name: nil, account_alias: nil, domain_name: nil)
|
|
133
|
+
begin
|
|
134
|
+
unless Securden.server_url && Securden.authtoken
|
|
135
|
+
Securden.log(message: "Securden has not been initialized. Please initialize before using", level: ERROR)
|
|
136
|
+
return nil
|
|
137
|
+
end
|
|
138
|
+
Securden.log(message: "Updating account", level: DEBUG)
|
|
139
|
+
if account_id.nil? || account_type.nil?
|
|
140
|
+
Securden.log(message: "Required account ID and Account type", level: ERROR)
|
|
141
|
+
return nil
|
|
142
|
+
end
|
|
143
|
+
params = {}
|
|
144
|
+
params["account_id"] = account_id
|
|
145
|
+
params["account_type"] = account_type
|
|
146
|
+
params["account_title"] = account_title unless account_title.nil?
|
|
147
|
+
params["account_name"] = account_name unless account_name.nil?
|
|
148
|
+
params["ipaddress"] = ipaddress unless ipaddress.nil?
|
|
149
|
+
params["notes"] = notes unless notes.nil?
|
|
150
|
+
params["tags"] = tags unless tags.nil?
|
|
151
|
+
params["personal_account"] = personal_account unless personal_account.nil?
|
|
152
|
+
params["folder_id"] = folder_id unless folder_id.nil?
|
|
153
|
+
params["account_expiration_date"] = account_expiration_date unless account_expiration_date.nil?
|
|
154
|
+
params["distinguished_name"] = distinguished_name unless distinguished_name.nil?
|
|
155
|
+
params["account_alias"] = account_alias unless account_alias.nil?
|
|
156
|
+
params["domain_name"] = domain_name unless domain_name.nil?
|
|
157
|
+
account = Request.new.raise_request(params, "/api/edit_account", PUT)
|
|
158
|
+
if account
|
|
159
|
+
if account["message"]
|
|
160
|
+
Securden.log(message: account["message"], level: DEBUG)
|
|
161
|
+
end
|
|
162
|
+
return account
|
|
163
|
+
else
|
|
164
|
+
Securden.log(message: "Could not update account", level: ERROR)
|
|
165
|
+
return nil
|
|
166
|
+
end
|
|
167
|
+
rescue StandardError => e
|
|
168
|
+
Securden.log(message: "Error updating account: #{e.message}", level: ERROR)
|
|
169
|
+
return nil
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
class Accounts
|
|
175
|
+
def self.get(account_ids:)
|
|
176
|
+
begin
|
|
177
|
+
if Securden.server_url.nil? || Securden.authtoken.nil? || Securden.server_url.strip.empty? || Securden.authtoken.strip.empty?
|
|
178
|
+
Securden.log(message: "Securden has not been initialized. Please initialize before using", level: ERROR)
|
|
179
|
+
return nil
|
|
180
|
+
end
|
|
181
|
+
params = {}
|
|
182
|
+
if account_ids.nil? || account_ids.empty?
|
|
183
|
+
Securden.log(message: "Required account IDs", level: ERROR)
|
|
184
|
+
return nil
|
|
185
|
+
end
|
|
186
|
+
Securden.log(message: "Fetching accounts from Securden", level: DEBUG)
|
|
187
|
+
params["account_ids"] = account_ids
|
|
188
|
+
accounts = Request.new.raise_request(params, "/secretsmanagement/get_accounts", POST)
|
|
189
|
+
if accounts
|
|
190
|
+
if accounts["message"]
|
|
191
|
+
Securden.log(message: accounts["message"], level: DEBUG)
|
|
192
|
+
end
|
|
193
|
+
return accounts
|
|
194
|
+
else
|
|
195
|
+
Securden.log(message: "Could not fetch accounts", level: ERROR)
|
|
196
|
+
return nil
|
|
197
|
+
end
|
|
198
|
+
rescue StandardError => e
|
|
199
|
+
Securden.log(message: "Error fetching accounts: #{e.message}", level: ERROR)
|
|
200
|
+
return nil
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def self.delete(account_ids:, reason: nil, delete_permanently: nil)
|
|
205
|
+
begin
|
|
206
|
+
if Securden.server_url.nil? || Securden.authtoken.nil?
|
|
207
|
+
Securden.log(message: "Securden has not been initialized. Please initialize before using", level: ERROR)
|
|
208
|
+
return nil
|
|
209
|
+
end
|
|
210
|
+
params = {}
|
|
211
|
+
if account_ids.nil? || account_ids.empty?
|
|
212
|
+
Securden.log(message: "Required account IDs", level: ERROR)
|
|
213
|
+
return nil
|
|
214
|
+
end
|
|
215
|
+
params["account_ids"] = account_ids
|
|
216
|
+
params["reason"] = reason unless reason.nil?
|
|
217
|
+
params["delete_permanently"] = delete_permanently unless delete_permanently.nil?
|
|
218
|
+
Securden.log(message: "Deleting accounts", level: INFO)
|
|
219
|
+
accounts = Request.new.raise_request(params, "/api/delete_accounts", DELETE)
|
|
220
|
+
if accounts
|
|
221
|
+
if accounts["message"]
|
|
222
|
+
Securden.log(message: accounts["message"], level: DEBUG)
|
|
223
|
+
end
|
|
224
|
+
return accounts
|
|
225
|
+
else
|
|
226
|
+
Securden.log(message: "Could not delete accounts", level: ERROR)
|
|
227
|
+
return nil
|
|
228
|
+
end
|
|
229
|
+
rescue StandardError => e
|
|
230
|
+
Securden.log(message: "Error deleting accounts: #{e.message}", level: ERROR)
|
|
231
|
+
return nil
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
class Request
|
|
237
|
+
def raise_request(payload, request_path, method)
|
|
238
|
+
begin
|
|
239
|
+
Securden.log(message: "Raising API request to Securden server", level: DEBUG)
|
|
240
|
+
uri = URI(Securden.server_url + request_path)
|
|
241
|
+
if method == GET
|
|
242
|
+
uri.query = URI.encode_www_form(payload) unless payload.empty?
|
|
243
|
+
end
|
|
244
|
+
http = set_http(uri)
|
|
245
|
+
case method
|
|
246
|
+
when GET
|
|
247
|
+
request = Net::HTTP::Get.new(uri)
|
|
248
|
+
when POST
|
|
249
|
+
request = Net::HTTP::Post.new(uri)
|
|
250
|
+
when PUT
|
|
251
|
+
request = Net::HTTP::Put.new(uri)
|
|
252
|
+
when DELETE
|
|
253
|
+
request = Net::HTTP::Delete.new(uri)
|
|
254
|
+
end
|
|
255
|
+
request = set_header(request)
|
|
256
|
+
if method != GET
|
|
257
|
+
request["Content-Type"] = "application/json"
|
|
258
|
+
end
|
|
259
|
+
request.body = payload.to_json unless payload.nil? || payload.empty?
|
|
260
|
+
response = http.request(request)
|
|
261
|
+
return handle_response(response)
|
|
262
|
+
rescue StandardError => e
|
|
263
|
+
Securden.log(message: "#{e}", level: ERROR)
|
|
264
|
+
return nil
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
protected
|
|
269
|
+
def handle_response(response)
|
|
270
|
+
begin
|
|
271
|
+
status_code = nil
|
|
272
|
+
if response.code && !response.code.strip.empty?
|
|
273
|
+
status_code = response.code.to_i
|
|
274
|
+
else response.status_code && !response.status_code.strip.empty?
|
|
275
|
+
status_code = response.status_code.to_i
|
|
276
|
+
end
|
|
277
|
+
response = JSON.parse(response.body)
|
|
278
|
+
if !status_code.nil? && status_code == 200
|
|
279
|
+
return response
|
|
280
|
+
elsif response['error']
|
|
281
|
+
message = response['error']['message']
|
|
282
|
+
else
|
|
283
|
+
message = response['message']
|
|
284
|
+
end
|
|
285
|
+
Securden.log(message: "#{response['status_code']}: #{message}", level: ERROR)
|
|
286
|
+
return nil
|
|
287
|
+
rescue StandardError => e
|
|
288
|
+
Securden.log(message: "HTTP request failed: #{e.message}", level: ERROR)
|
|
289
|
+
return nil
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def set_header(request)
|
|
294
|
+
request[AUTHTOKEN] = Securden.authtoken
|
|
295
|
+
request[ORG] = Securden.org if Securden.org && !Securden.org.strip.empty?
|
|
296
|
+
request
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def set_http(uri)
|
|
300
|
+
begin
|
|
301
|
+
Securden.log(message: "Setting up HTTP connection.", level: DEBUG)
|
|
302
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
303
|
+
http.use_ssl = uri.scheme == "https"
|
|
304
|
+
if uri.scheme == "https"
|
|
305
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
306
|
+
configure_ssl(http, uri)
|
|
307
|
+
end
|
|
308
|
+
http
|
|
309
|
+
rescue StandardError => e
|
|
310
|
+
Securden.log(message: "Failed to set HTTP connection: #{e.message}", level: ERROR)
|
|
311
|
+
return nil
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def configure_ssl(http, uri)
|
|
316
|
+
begin
|
|
317
|
+
if Securden.server_url.start_with?("https://")
|
|
318
|
+
if Securden.certificate
|
|
319
|
+
handle_ssl_certificate(http)
|
|
320
|
+
else
|
|
321
|
+
Securden.log(message: "No SSL certificate provided. Attempting to fetch from server.", level: DEBUG)
|
|
322
|
+
fetch_and_set_server_certificate(http, uri)
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
rescue StandardError => e
|
|
326
|
+
Securden.log(message: "Failed to configure SSL: #{e.message}", level: ERROR)
|
|
327
|
+
return nil
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def handle_ssl_certificate(http)
|
|
332
|
+
begin
|
|
333
|
+
Securden.log(message: "Adding SSL certificate.", level: INFO)
|
|
334
|
+
if Securden.certificate.start_with?("-----BEGIN CERTIFICATE-----")
|
|
335
|
+
cert_object = OpenSSL::X509::Certificate.new(Securden.certificate)
|
|
336
|
+
http.cert_store = OpenSSL::X509::Store.new
|
|
337
|
+
http.cert_store.add_cert(cert_object)
|
|
338
|
+
elsif File.exist?(Securden.certificate)
|
|
339
|
+
http.ca_file = Securden.certificate
|
|
340
|
+
else
|
|
341
|
+
Securden.log(message: "Invalid certificate value or file path", level: ERROR)
|
|
342
|
+
return nil
|
|
343
|
+
end
|
|
344
|
+
http
|
|
345
|
+
rescue OpenSSL::X509::CertificateError => e
|
|
346
|
+
Securden.log(message: "Invalid certificate format: #{e.message}", level: ERROR)
|
|
347
|
+
return nil
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def verify_certificate_domain(cert, uri)
|
|
352
|
+
cn = cert.subject.to_s.match(/CN=([^\s\/,]+)/i)&.captures&.first
|
|
353
|
+
sans = cert.extensions.select { |ext| ext.oid == 'subjectAltName' }.flat_map { |ext| ext.value.split(', ').grep(/^DNS:/) }.map { |dns| dns.gsub(/^DNS:/, '') }
|
|
354
|
+
cert_domains = [cn, *sans].compact
|
|
355
|
+
cert_domains.any? do |domain|
|
|
356
|
+
if domain.start_with?('*.')
|
|
357
|
+
uri.host.end_with?(domain[2..-1])
|
|
358
|
+
else
|
|
359
|
+
uri.host == domain
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def fetch_and_set_server_certificate(http, uri)
|
|
365
|
+
begin
|
|
366
|
+
Securden.log(message: "Attempting to fetch SSL certificate from #{uri.host}:#{uri.port}", level: DEBUG)
|
|
367
|
+
tcp_socket = TCPSocket.new(uri.host, uri.port || 443)
|
|
368
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
369
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
|
|
370
|
+
ssl_socket.sync_close = true
|
|
371
|
+
ssl_socket.connect
|
|
372
|
+
peer_cert = ssl_socket.peer_cert
|
|
373
|
+
peer_cert_chain = ssl_socket.peer_cert_chain || [peer_cert]
|
|
374
|
+
unless verify_certificate_domain(peer_cert, uri)
|
|
375
|
+
Securden.log(message: "Hostname mismatch: Certificate is for #{peer_cert.subject}, requested #{uri.host}", level: DEBUG)
|
|
376
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
377
|
+
return
|
|
378
|
+
end
|
|
379
|
+
ssl_socket.close
|
|
380
|
+
tcp_socket.close
|
|
381
|
+
http.cert_store = OpenSSL::X509::Store.new
|
|
382
|
+
http.cert_store.set_default_paths
|
|
383
|
+
peer_cert_chain.each do |cert|
|
|
384
|
+
begin
|
|
385
|
+
http.cert_store.add_cert(cert)
|
|
386
|
+
rescue OpenSSL::X509::StoreError => e
|
|
387
|
+
Securden.log(message: "Certificate already in store or invalid: #{e.message}", level: DEBUG)
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
verification_result = http.cert_store.verify(peer_cert_chain.first)
|
|
391
|
+
if verification_result
|
|
392
|
+
Securden.log(message: "Successfully verified certificate", level: DEBUG)
|
|
393
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
394
|
+
else
|
|
395
|
+
Securden.log(message: "Certificate verification failed: #{http.cert_store.error_string}", level: WARN)
|
|
396
|
+
if self_signed?(peer_cert_chain.first)
|
|
397
|
+
Securden.log(message: "Server uses self-signed certificate. Adding to trust store.", level: DEBUG)
|
|
398
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
399
|
+
else
|
|
400
|
+
Securden.log(message: "Untrusted certificate. Disabling SSL verification.", level: WARN)
|
|
401
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
rescue StandardError => e
|
|
406
|
+
Securden.log(message: "Failed to establish secure connection: #{e.message}. Disabling SSL verification", level: WARN)
|
|
407
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
408
|
+
ensure
|
|
409
|
+
ssl_socket&.close rescue nil
|
|
410
|
+
tcp_socket&.close rescue nil
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def self_signed?(cert)
|
|
415
|
+
cert.subject.to_s == cert.issuer.to_s
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
private_constant :Request
|
|
420
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: securden
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Securden
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-04-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description:
|
|
14
|
+
email:
|
|
15
|
+
- devops-support@securden.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- LICENSE.txt
|
|
21
|
+
- README.md
|
|
22
|
+
- lib/securden.rb
|
|
23
|
+
- lib/securden/version.rb
|
|
24
|
+
homepage: https://securden.com
|
|
25
|
+
licenses:
|
|
26
|
+
- Apache-2.0
|
|
27
|
+
metadata: {}
|
|
28
|
+
post_install_message:
|
|
29
|
+
rdoc_options: []
|
|
30
|
+
require_paths:
|
|
31
|
+
- lib
|
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
33
|
+
requirements:
|
|
34
|
+
- - ">="
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '0'
|
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '0'
|
|
42
|
+
requirements: []
|
|
43
|
+
rubygems_version: 3.3.27
|
|
44
|
+
signing_key:
|
|
45
|
+
specification_version: 4
|
|
46
|
+
summary: Leverage the Chef development environment for seamless access to passwords,
|
|
47
|
+
secrets, certificates, and keys stored in Securden.
|
|
48
|
+
test_files: []
|