train-rest 0.4.2 → 0.5.0
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/lib/train-rest/auth_handler/awsv4.rb +119 -0
- data/lib/train-rest/auth_handler/hmac-signature.rb +39 -0
- data/lib/train-rest/auth_handler.rb +26 -1
- data/lib/train-rest/connection.rb +28 -12
- data/lib/train-rest/errors.rb +6 -0
- data/lib/train-rest/version.rb +1 -1
- data/lib/train-rest.rb +5 -1
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73a28c27d6e8385d3d8356c363847ddae1380ff0d0e75d6f919f64cefb315637
|
4
|
+
data.tar.gz: 1b0140ce7079d48ac070b05607acc1b142c987cd598a7f60fdc28ff3025dc3f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 293e8e03b838b888108eb25ac164080f76aa0291420bcd04feb8503245c3149ff3d03257ad01df3ea3d2fae018094a0ec85c1e4a3b052b1a07c3cc0db69b0244
|
7
|
+
data.tar.gz: a816054a1e6db4bd24134249adaedd933b16b9796c3cfde7afa90f4e66ea70d9cdf744bea124ee2e93b383d6ad4b666e4bdd2a4ae363fa09056bad3eb85ada6f
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'aws-sigv4'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require_relative "../auth_handler"
|
5
|
+
|
6
|
+
module TrainPlugins
|
7
|
+
module Rest
|
8
|
+
class AWSV4 < AuthHandler
|
9
|
+
VALID_CREDENTIALS = %w[
|
10
|
+
access_keys
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
SIGNED_HEADERS = %w[
|
14
|
+
content-type host x-amz-date x-amz-target
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
def check_options
|
18
|
+
options[:credentials] ||= "access_keys"
|
19
|
+
|
20
|
+
unless VALID_CREDENTIALS.include? credentials
|
21
|
+
raise ArgumentError.new("Invalid type of credentials: #{credentials}")
|
22
|
+
end
|
23
|
+
|
24
|
+
if access_keys?
|
25
|
+
raise ArgumentError.new('Missing `access_key` credential') unless access_key
|
26
|
+
raise ArgumentError.new('Missing `secret_access_key` credential') unless secret_access_key
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def signature_based?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def process(payload: "", headers: {}, url: "", method: nil)
|
35
|
+
headers.merge! ({
|
36
|
+
'Accept-Encoding' => 'identity',
|
37
|
+
'User-Agent' => "train-rest/#{TrainPlugins::Rest::VERSION}",
|
38
|
+
'Content-Type' => 'application/x-amz-json-1.0'
|
39
|
+
})
|
40
|
+
|
41
|
+
signed_headers = headers.select do |name, _value|
|
42
|
+
SIGNED_HEADERS.include? name.downcase
|
43
|
+
end
|
44
|
+
|
45
|
+
@url = url
|
46
|
+
|
47
|
+
signature = signer(url).sign_request(
|
48
|
+
http_method: method.to_s.upcase,
|
49
|
+
url: url,
|
50
|
+
headers: signed_headers,
|
51
|
+
body: payload.to_json
|
52
|
+
)
|
53
|
+
|
54
|
+
{
|
55
|
+
headers: headers.merge(signature.headers)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def process_error(error)
|
60
|
+
raise AuthenticationError.new("Authentication failed: #{error.response.to_s.chop}") if error.response.code == 401
|
61
|
+
raise BadRequest.new("Bad request: #{error.response.to_s.chop}") if error.response.code == 400
|
62
|
+
|
63
|
+
message = JSON.parse(error.response.to_s)
|
64
|
+
|
65
|
+
raise AuthenticationError.new(message["message"] || message["__type"])
|
66
|
+
rescue JSON::ParserError => e
|
67
|
+
raise AuthenticationError.new(error.response.to_s)
|
68
|
+
end
|
69
|
+
|
70
|
+
def access_key
|
71
|
+
options[:access_key] || ENV['AWS_ACCESS_KEY_ID']
|
72
|
+
end
|
73
|
+
|
74
|
+
def region(url = default_url)
|
75
|
+
url.delete_prefix('https://').split('.').at(1)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def credentials
|
81
|
+
options[:credentials]
|
82
|
+
end
|
83
|
+
|
84
|
+
def default_url
|
85
|
+
options[:endpoint]
|
86
|
+
end
|
87
|
+
|
88
|
+
def access_keys?
|
89
|
+
credentials == 'access_keys'
|
90
|
+
end
|
91
|
+
|
92
|
+
def secret_access_key
|
93
|
+
options[:secret_access_key] || ENV['AWS_SECRET_ACCESS_KEY']
|
94
|
+
end
|
95
|
+
|
96
|
+
def service(url)
|
97
|
+
url.delete_prefix('https://').split('.').at(0)
|
98
|
+
end
|
99
|
+
|
100
|
+
def signer(url)
|
101
|
+
Aws::Sigv4::Signer.new(
|
102
|
+
service: service(url),
|
103
|
+
region: region(url),
|
104
|
+
|
105
|
+
**signer_credentials
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
def signer_credentials
|
110
|
+
if access_keys?
|
111
|
+
{
|
112
|
+
access_key_id: access_key,
|
113
|
+
secret_access_key: secret_access_key
|
114
|
+
}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative "../auth_handler"
|
2
|
+
|
3
|
+
module TrainPlugins
|
4
|
+
module Rest
|
5
|
+
# Authentication via HMAC Signature.
|
6
|
+
class HmacSignature < AuthHandler
|
7
|
+
def check_options
|
8
|
+
raise ArgumentError.new("Need :hmac_secret for HMAC signatures") unless options[:hmac_secret]
|
9
|
+
|
10
|
+
options[:header] ||= "X-Signature"
|
11
|
+
options[:digest] ||= "SHA256"
|
12
|
+
end
|
13
|
+
|
14
|
+
def hmac_secret
|
15
|
+
options[:hmac_secret]
|
16
|
+
end
|
17
|
+
|
18
|
+
def digest
|
19
|
+
options[:digest]
|
20
|
+
end
|
21
|
+
|
22
|
+
def header
|
23
|
+
options[:header]
|
24
|
+
end
|
25
|
+
|
26
|
+
def signature_based?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def process(payload: "", headers: {}, url: "", method: nil)
|
31
|
+
{
|
32
|
+
headers: {
|
33
|
+
header => OpenSSL::HMAC.hexdigest(digest, hmac_secret, payload)
|
34
|
+
}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -67,6 +67,30 @@ module TrainPlugins
|
|
67
67
|
{ headers: auth_headers }
|
68
68
|
end
|
69
69
|
|
70
|
+
# This Auth Handler will need payload, URI and headers, e.g. for signatures.
|
71
|
+
#
|
72
|
+
# @return [Boolean]
|
73
|
+
def signature_based?
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return headers based on payload signing.
|
78
|
+
#
|
79
|
+
# @param [Hash] data different types of data for processing
|
80
|
+
# @option data [String] :payload contents of the message body
|
81
|
+
# @option data [Hash] :headers existing headers to the request
|
82
|
+
# @option data [String] :url URL which will be requested
|
83
|
+
# @option data [Symbol] :method Method to execute
|
84
|
+
# @returns [Hash]
|
85
|
+
def process(payload: "", headers: {}, url: "", method: nil)
|
86
|
+
{}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Allow processing errors related to authentication.
|
90
|
+
#
|
91
|
+
# @param [RestClient::Exception] error raw error data
|
92
|
+
def process_error(_error); end
|
93
|
+
|
70
94
|
class << self
|
71
95
|
private
|
72
96
|
|
@@ -77,7 +101,8 @@ module TrainPlugins
|
|
77
101
|
# @see https://github.com/chef/chef/blob/main/lib/chef/mixin/convert_to_class_name.rb
|
78
102
|
def convert_to_snake_case(str)
|
79
103
|
str = str.dup
|
80
|
-
str.gsub!(/[A-Z]
|
104
|
+
str.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
105
|
+
str.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
81
106
|
str.downcase!
|
82
107
|
str.sub!(/^\_/, "")
|
83
108
|
str
|
@@ -114,6 +114,21 @@ module TrainPlugins
|
|
114
114
|
# Merge override headers + request specific headers
|
115
115
|
parameters[:headers].merge!(override_headers || {})
|
116
116
|
parameters[:headers].merge!(headers)
|
117
|
+
|
118
|
+
# Merge payload based headers (e.g. signature-based auth)
|
119
|
+
if auth_handler.signature_based?
|
120
|
+
auth_signature = auth_handler.process(
|
121
|
+
payload: data,
|
122
|
+
headers: parameters[:headers],
|
123
|
+
url: parameters[:url],
|
124
|
+
method: method
|
125
|
+
)
|
126
|
+
|
127
|
+
parameters[:headers].merge! auth_signature[:headers]
|
128
|
+
else
|
129
|
+
parameters[:headers].merge! auth_parameters[:headers]
|
130
|
+
end
|
131
|
+
|
117
132
|
parameters.compact!
|
118
133
|
|
119
134
|
logger.info format("[REST] => %s", parameters.to_s) if options[:debug_rest]
|
@@ -121,6 +136,9 @@ module TrainPlugins
|
|
121
136
|
|
122
137
|
logger.info format("[REST] <= %s", response.to_s) if options[:debug_rest]
|
123
138
|
transform_response(response, json_processing)
|
139
|
+
|
140
|
+
rescue RestClient::Exception => error
|
141
|
+
auth_handler.process_error(error)
|
124
142
|
end
|
125
143
|
|
126
144
|
# Allow switching generic handlers for an API-specific one.
|
@@ -144,12 +162,21 @@ module TrainPlugins
|
|
144
162
|
options[:auth_type]
|
145
163
|
end
|
146
164
|
|
165
|
+
attr_writer :auth_handler
|
166
|
+
|
147
167
|
# Auth Handlers-faced API
|
148
168
|
|
149
169
|
def auth_parameters
|
150
170
|
auth_handler.auth_parameters
|
151
171
|
end
|
152
172
|
|
173
|
+
def auth_handler
|
174
|
+
desired_handler = auth_handler_classes.detect { |handler| handler.name == auth_type.to_s }
|
175
|
+
raise NameError.new(format("Authentication handler %s not found", auth_type.to_s)) unless desired_handler
|
176
|
+
|
177
|
+
@auth_handler ||= desired_handler.new(self)
|
178
|
+
end
|
179
|
+
|
153
180
|
private
|
154
181
|
|
155
182
|
def global_parameters
|
@@ -160,8 +187,6 @@ module TrainPlugins
|
|
160
187
|
headers: options[:headers],
|
161
188
|
}
|
162
189
|
|
163
|
-
params.merge!(auth_parameters)
|
164
|
-
|
165
190
|
params
|
166
191
|
end
|
167
192
|
|
@@ -184,23 +209,14 @@ module TrainPlugins
|
|
184
209
|
:basic if options[:username] && options[:password]
|
185
210
|
end
|
186
211
|
|
187
|
-
attr_writer :auth_handler
|
188
|
-
|
189
212
|
def auth_handler_classes
|
190
|
-
AuthHandler.descendants
|
213
|
+
::TrainPlugins::Rest::AuthHandler.descendants
|
191
214
|
end
|
192
215
|
|
193
216
|
def auth_handlers
|
194
217
|
auth_handler_classes.map { |handler| handler.name.to_sym }
|
195
218
|
end
|
196
219
|
|
197
|
-
def auth_handler
|
198
|
-
desired_handler = auth_handler_classes.detect { |handler| handler.name == auth_type.to_s }
|
199
|
-
raise NameError.new(format("Authentication handler %s not found", auth_type.to_s)) unless desired_handler
|
200
|
-
|
201
|
-
@auth_handler ||= desired_handler.new(self)
|
202
|
-
end
|
203
|
-
|
204
220
|
def login
|
205
221
|
logger.info format("REST Login via %s authentication handler", auth_type.to_s) unless %i{anonymous basic}.include? auth_type
|
206
222
|
|
data/lib/train-rest/version.rb
CHANGED
data/lib/train-rest.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
1
|
libdir = File.dirname(__FILE__)
|
2
2
|
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
3
3
|
|
4
|
+
require "train-rest/errors"
|
4
5
|
require "train-rest/version"
|
5
6
|
|
6
7
|
require "train-rest/transport"
|
7
8
|
require "train-rest/connection"
|
8
9
|
|
10
|
+
require "train-rest/auth_handler"
|
11
|
+
require "train-rest/auth_handler/awsv4"
|
9
12
|
require "train-rest/auth_handler/anonymous"
|
10
13
|
require "train-rest/auth_handler/authtype-apikey"
|
11
|
-
require "train-rest/auth_handler/header"
|
12
14
|
require "train-rest/auth_handler/basic"
|
13
15
|
require "train-rest/auth_handler/bearer"
|
16
|
+
require "train-rest/auth_handler/header"
|
17
|
+
require "train-rest/auth_handler/hmac-signature"
|
14
18
|
require "train-rest/auth_handler/redfish"
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: train-rest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Heinen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-09-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sigv4
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: train-core
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -120,11 +134,14 @@ files:
|
|
120
134
|
- lib/train-rest/auth_handler.rb
|
121
135
|
- lib/train-rest/auth_handler/anonymous.rb
|
122
136
|
- lib/train-rest/auth_handler/authtype-apikey.rb
|
137
|
+
- lib/train-rest/auth_handler/awsv4.rb
|
123
138
|
- lib/train-rest/auth_handler/basic.rb
|
124
139
|
- lib/train-rest/auth_handler/bearer.rb
|
125
140
|
- lib/train-rest/auth_handler/header.rb
|
141
|
+
- lib/train-rest/auth_handler/hmac-signature.rb
|
126
142
|
- lib/train-rest/auth_handler/redfish.rb
|
127
143
|
- lib/train-rest/connection.rb
|
144
|
+
- lib/train-rest/errors.rb
|
128
145
|
- lib/train-rest/transport.rb
|
129
146
|
- lib/train-rest/version.rb
|
130
147
|
homepage: https://github.com/tecracer-chef/train-rest
|