Skip to content

Commit fc1e257

Browse files
feat: better errors, simpler code. (#39)
* fixed commit * added schema config params. * Update Readme.md * Update sigv4.lua * Update sigv4.lua
1 parent 0a9c230 commit fc1e257

File tree

5 files changed

+104
-62
lines changed

5 files changed

+104
-62
lines changed

Readme.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,19 @@ default = false
5050
required = false
5151

5252
sign_query - Controls if the signature will be sent in the header or in the query. By default, header is used, if enabled will sign the query.
53-
type = boolean
53+
type = "boolean"
5454
required = true
5555
default = false
56+
57+
preserve_auth_header - Controls if the bearer token will be passed to the upstream
58+
type = "boolean"
59+
required = true
60+
default = true
61+
62+
preserve_auth_header_key - The header key where the bearer token will be saved and passed to the upstream. works only if 'preserve_auth_header' parameter above is set to true.
63+
type = "string"
64+
required = true
65+
default = "x-authorization"
5666
```
5767

5868
## Using multiple Lambdas with the same Kong Service
@@ -74,7 +84,7 @@ There are two things necessary to make a custom plugin work in Kong:
7484
The easiest way to install the plugin is using `luarocks`.
7585

7686
```sh
77-
luarocks install https://github.com/LEGO/kong-aws-request-signing/raw/main/rocks/kong-aws-request-signing-1.0.4-3.all.rock
87+
luarocks install https://github.com/LEGO/kong-aws-request-signing/raw/main/rocks/kong-aws-request-signing-1.0.5-3.all.rock
7888
```
7989

8090
You can substitute `1.0.0-3` in the command above with any other version you want to install.
@@ -99,6 +109,20 @@ plugins:
99109
pluginName: aws-request-signing
100110
```
101111
112+
## Signing requests containing a body
113+
114+
In case of requests contanining a body, the plugin is highly reliant on the nginx configuration, because it neets to access the body to sign it.
115+
The behaviour is controlled by the following Kong configuration parameters:
116+
117+
```text
118+
nginx_http_client_max_body_size
119+
nginx_http_client_body_buffer_size
120+
```
121+
122+
[Kong docs reference.](https://docs.konghq.com/gateway/latest/reference/configuration/#nginx_http_client_body_buffer_size)
123+
124+
The default value for max body size is `0`, which means unlimited, so consider setting the `nginx_http_client_body_buffer_size` as high as you consider reasonable, as requests containing a bigger body, will fail.
125+
102126
## AWS Setup required
103127

104128
1. You have a [Lambda function](https://eu-west-1.console.aws.amazon.com/lambda/home?region=eu-west-1#) deployed with `Function URL` enabled and Auth type : `AWS_IAM` or you have an S3 bucket with public access disabled.

kong-aws-request-signing-1.0.4-3.rockspec renamed to kong-aws-request-signing-1.0.5-3.rockspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
local plugin_name = "aws-request-signing"
22
local package_name = "kong-" .. plugin_name
3-
local package_version = "1.0.4"
3+
local package_version = "1.0.5"
44
local rockspec_revision = "3"
55

66
local github_account_name = "LEGO"

kong/plugins/aws-request-signing/handler.lua

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,6 @@ local error = error
66
local type = type
77
local json = require "cjson"
88

9-
10-
local get_raw_body = kong.request.get_raw_body
11-
12-
local set_headers = kong.service.request.set_headers
13-
local set_raw_query = kong.service.request.set_raw_query
14-
159
local IAM_CREDENTIALS_CACHE_KEY_PATTERN = "plugin.aws-request-signing.iam_role_temp_creds.%s"
1610
local AWSLambdaSTS = {}
1711

@@ -52,6 +46,7 @@ if _TEST then
5246
end
5347

5448
local function get_iam_credentials(sts_conf, refresh, return_sts_error)
49+
local generic_error = "Error fetching STS credentials. Enable 'return_sts_error' in config for details."
5550
local iam_role_cred_cache_key = string.format(IAM_CREDENTIALS_CACHE_KEY_PATTERN, sts_conf.RoleArn)
5651

5752
if refresh then
@@ -73,7 +68,7 @@ local function get_iam_credentials(sts_conf, refresh, return_sts_error)
7368
local resError = json.decode(errJson)
7469
return kong.response.exit(resError.sts_status, { message = resError.message, stsResponse = resError.sts_body })
7570
else
76-
return kong.response.exit(401, {message = 'Error fetching STS credentials!'})
71+
return kong.response.exit(401, {message = generic_error})
7772
end
7873
end
7974

@@ -93,7 +88,7 @@ local function get_iam_credentials(sts_conf, refresh, return_sts_error)
9388
local resError = json.decode(errJson)
9489
return kong.response.exit(resError.sts_status, { message = resError.message, stsResponse = resError.sts_body })
9590
else
96-
return kong.response.exit(401, {message = 'Error fetching STS credentials!'})
91+
return kong.response.exit(401, {message = generic_error})
9792
end
9893
end
9994
kong.log.debug("expiring key , invalidated iam_cache and fetched fresh credentials!")
@@ -112,7 +107,13 @@ function AWSLambdaSTS:access(conf)
112107

113108
if service == nil then
114109
kong.log.err("Unable to retrieve bound service!")
115-
return kong.response.exit(500, { message = "Internal error 1!" })
110+
return kong.response.exit(500, { message = "The plugin must be bound to a service!" })
111+
end
112+
113+
if conf.preserve_auth_header then
114+
kong.service.request.set_headers({
115+
[conf.preserve_auth_header_key] = request_headers.authorization
116+
})
116117
end
117118

118119
if conf.override_target_protocol then
@@ -139,18 +140,30 @@ function AWSLambdaSTS:access(conf)
139140
-- we only send those two headers for signing
140141
local upstream_headers = {
141142
host = final_host,
142-
["x-authorization"] = request_headers.authorization
143+
-- those will be nill thus we only pass the host on requests without body
144+
["content-length"] = request_headers["content-length"],
145+
["content-type"] = request_headers["content-type"]
143146
}
144147

145148
-- removing the authorization, we either do not need it or we set it again later.
146149
kong.service.request.clear_header("authorization")
147150

148-
local opts = {
151+
-- might fail if too big. is controlled by the folowing nginx params:
152+
-- nginx_http_client_max_body_size
153+
-- nginx_http_client_body_buffer_size
154+
local req_body, get_body_err = kong.request.get_raw_body()
155+
156+
if get_body_err or req_body == nil then
157+
kong.log.err(get_body_err)
158+
return kong.response.exit(400, { message = "Request body exceeds size limit and cannot be used by plugins." })
159+
end
160+
161+
local sigv4_opts = {
149162
region = conf.aws_region,
150163
service = conf.aws_service,
151164
method = kong.request.get_method(),
152165
headers = upstream_headers,
153-
body = get_raw_body(),
166+
body = req_body,
154167
path = ngx.var.upstream_uri,
155168
host = final_host,
156169
port = service.port,
@@ -161,20 +174,21 @@ function AWSLambdaSTS:access(conf)
161174
sign_query = conf.sign_query
162175
}
163176

164-
local request, err = sigv4(opts)
165-
if err then
166-
return error(err)
177+
local signed_request, sigv4_err = sigv4(sigv4_opts)
178+
if sigv4_err then
179+
kong.log.err(sigv4_err)
180+
return error(sigv4_err)
167181
end
168182

169-
if not request then
183+
if not signed_request then
170184
return kong.response.exit(500, { message = "Unable to SIGV4 the request!" })
171185
end
172186

173-
set_headers(request.headers)
174-
set_raw_query(request.query)
187+
kong.service.request.set_headers(signed_request.headers)
188+
kong.service.request.set_raw_query(signed_request.query)
175189
end
176190

177191
AWSLambdaSTS.PRIORITY = 110
178-
AWSLambdaSTS.VERSION = "1.0.4"
192+
AWSLambdaSTS.VERSION = "1.0.5"
179193

180194
return AWSLambdaSTS

kong/plugins/aws-request-signing/schema.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ return {
5656
required = true,
5757
default = false,
5858
} },
59+
{ preserve_auth_header = {
60+
type = "boolean",
61+
required = true,
62+
default = true,
63+
} },
64+
{ preserve_auth_header_key = {
65+
type = "string",
66+
required = true,
67+
default = "x-authorization",
68+
} }
5969
}
6070
},
6171
}

kong/plugins/aws-request-signing/sigv4.lua

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -91,27 +91,19 @@ local function canonicalise_query_string(query)
9191
end
9292

9393
local function get_canonical_headers(headers)
94-
local canonical_headers, signed_headers do
95-
-- We structure this code in a way so that we only have to sort once.
96-
canonical_headers, signed_headers = {}, {}
97-
local i = 0
98-
for name, value in pairs(headers) do
99-
if value then -- ignore headers with 'false', they are used to override defaults
100-
i = i + 1
101-
local name_lower = name:lower()
102-
signed_headers[i] = name_lower
103-
canonical_headers[name_lower] = pl_string.strip(value)
104-
end
105-
end
106-
table.sort(signed_headers)
107-
for j=1, i do
108-
local name = signed_headers[j]
109-
local value = canonical_headers[name]
110-
canonical_headers[j] = name .. ":" .. value .. "\n"
111-
end
112-
signed_headers = table.concat(signed_headers, ";", 1, i)
113-
canonical_headers = table.concat(canonical_headers, nil, 1, i)
94+
local signed_headers_arr = {}
95+
local canonical_headers = ""
96+
97+
-- sorting all header names after inserting in an array
98+
for header_key in pairs(headers) do table.insert(signed_headers_arr, header_key:lower()) end
99+
table.sort(signed_headers_arr)
100+
101+
-- going over the sorted array and adding the header and header values to the canonical headers
102+
local signed_headers = table.concat(signed_headers_arr, ";", 1)
103+
for _, header_key in pairs(signed_headers_arr) do
104+
canonical_headers = canonical_headers .. header_key .. ":" .. pl_string.strip(headers[header_key]) .. "\n"
114105
end
106+
115107
return {
116108
canonical_headers = canonical_headers,
117109
signed_headers = signed_headers
@@ -136,7 +128,7 @@ local function prepare_awsv4_request(opts)
136128
local secret_key = opts.secret_key
137129

138130
local request_headers = opts.headers or {}
139-
local request_payload = opts.body
131+
local request_body = opts.body
140132
local request_query = opts.query
141133

142134
local timestamp = ngx.time()
@@ -146,6 +138,9 @@ local function prepare_awsv4_request(opts)
146138
local canonical_uri = canonicalise_path(opts.path, service)
147139
local credential_scope = date .. "/" .. region .. "/" .. service .. "/aws4_request"
148140

141+
142+
local bodyHash = to_hex(hash(request_body))
143+
149144
-- If the "standard" port is not in use, the port should be added to the Host header
150145
local host_header do
151146
if port == 443 or port == 80 then
@@ -154,56 +149,54 @@ local function prepare_awsv4_request(opts)
154149
host_header = string.format("%s:%d", host, port)
155150
end
156151
end
152+
157153
request_headers["host"] = host_header
154+
request_headers["x-amz-content-sha256"] = bodyHash
155+
156+
local expiresInSeconds = 300
158157

159158
if not opts.sign_query then
160159
request_headers["x-amz-date"] = request_date
161160
request_headers["x-amz-security-token"] = opts.session_token
162161
if service == "s3" then
163-
request_headers["x-amz-expires"] = "300"
164-
request_headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"
162+
request_headers["x-amz-expires"] = expiresInSeconds .. ""
165163
end
166164
end
167165

168-
local transformed_headers = get_canonical_headers(request_headers)
166+
local canonical_headers = get_canonical_headers(request_headers)
169167

170168
if opts.sign_query then
171-
local expires = ""
169+
local expires_query_param = ""
172170
if service == "s3" then
173-
expires = "&X-Amz-Expires=300"
171+
expires_query_param = "&X-Amz-Expires=" .. expiresInSeconds
174172
end
175173

176-
request_query = request_query .. "&X-Amz-Security-Token=" .. url_encode(opts.session_token)
177-
.. expires
174+
request_query = request_query
175+
.. "&X-Amz-Security-Token=" .. url_encode(opts.session_token)
176+
.. expires_query_param
178177
.. "&X-Amz-Date=" .. request_date
179178
.. "&X-Amz-Algorithm="..ALGORITHM
180179
.. "&X-Amz-Credential=" .. access_key .. "/" .. credential_scope
181-
.. "&X-Amz-SignedHeaders=" .. transformed_headers.signed_headers
180+
.. "&X-Amz-SignedHeaders=" .. canonical_headers.signed_headers
182181
end
183182

184183
request_query = removeCharFromStart(request_query, "&")
185184
local canonical_querystring = canonicalise_query_string(request_query)
186185

187186
-- Task 1: Create a Canonical Request For Signature Version 4
188187
-- http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
189-
local bodyHash = to_hex(hash(request_payload or ""))
190-
if service == "s3" then
191-
bodyHash = "UNSIGNED-PAYLOAD"
192-
end
193-
194188
local canonical_request =
195189
request_method .. '\n' ..
196190
canonical_uri .. '\n' ..
197191
(canonical_querystring or "") .. '\n' ..
198-
transformed_headers.canonical_headers .. '\n' ..
199-
transformed_headers.signed_headers .. '\n' ..
192+
canonical_headers.canonical_headers .. '\n' ..
193+
canonical_headers.signed_headers .. '\n' ..
200194
bodyHash
201195

202196
local hashed_canonical_request = to_hex(hash(canonical_request))
203197

204198
-- Task 2: Create a String to Sign for Signature Version 4
205199
-- http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
206-
207200
local string_to_sign =
208201
ALGORITHM .. '\n' ..
209202
request_date .. '\n' ..
@@ -221,10 +214,11 @@ local function prepare_awsv4_request(opts)
221214
if opts.sign_query then
222215
request_query = request_query .. "&X-Amz-Signature=" .. signature
223216
else
224-
request_headers["authorization"] = ALGORITHM
217+
local auth_header = ALGORITHM
225218
.. " Credential=" .. access_key .. "/" .. credential_scope
226-
.. ", SignedHeaders=" .. transformed_headers.signed_headers
219+
.. ", SignedHeaders=" .. canonical_headers.signed_headers
227220
.. ", Signature=" .. signature
221+
request_headers["authorization"] = auth_header
228222
end
229223

230224
return {
@@ -233,4 +227,4 @@ local function prepare_awsv4_request(opts)
233227
}
234228
end
235229

236-
return prepare_awsv4_request
230+
return prepare_awsv4_request

0 commit comments

Comments
 (0)