Kong / Kong/lua-resty-aws

protocol 'ec2' not implemented — EC2 and related services unusable; query protocol drops list params

Open
#147 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Lua
Stars
35
Forks
28
PR merge metrics
No merged PRs in 30d

Description

## Bug: `protocol 'ec2' not implemented yet` + query protocol silently drops list parameters

### Environment

| | |
|---|---|
| lua-resty-aws | 1.7.2-1 |
| OpenResty | 1.29.2.3-1 |
| Runtime | ECS Fargate (OpenResty/LuaJIT) |

---

### Problem 1 — EC2 service is completely unusable

Any call to a service that uses the `"ec2"` wire protocol (EC2, EC2 Instance Connect, …)
throws a hard error before the request is sent:

/usr/local/share/lua/5.1/resty/aws/request/build.lua:241:
protocol 'ec2' not implemented yet

**Minimal reproduction:**

```lua
local aws_sdk = require("resty.aws")
local aws = aws_sdk({ region = "eu-central-1" })
local ec2 = aws:EC2()
local res, err = ec2:describeInstances({ InstanceIds = { "i-0abc123" } })
-- ↑ always errors, never reaches the network

The protocols table at the top of build.lua already lists ec2 = true (so the
validation guard passes), but the body-encoding section immediately below has only a
stub:

if config.protocol == "ec2" then
error("protocol 'ec2' not implemented yet") -- line 241
```

---
Problem 2 — query protocol silently corrupts list parameters

For services that use "query" protocol (AutoScaling, ELB, SQS, …), list-valued
parameters are stored as raw Lua tables in request.query:
```lua
-- build.lua ~line 196 (location == nil branch for query protocol):
request.query[name] = param_value -- param_value may be a table!
```

When resty.luasocket.http serialises the query table, a Lua array value either
errors or produces a single repeated key (InstanceIds=i-xxx&InstanceIds=i-yyy),
not the .member.N form that AWS query-protocol services require
(InstanceIds.member.1=i-xxx&InstanceIds.member.2=i-yyy).

Minimal reproduction:
```lua
local asg = aws:AutoScaling()
local res, err = asg:describeAutoScalingInstances({
InstanceIds = { "i-0abc1", "i-0abc2" }
})
-- AWS receives InstanceIds=i-0abc1&InstanceIds=i-0abc2 (wrong)
-- instead of InstanceIds.member.1=i-0abc1&InstanceIds.member.2=i-0abc2

This is only masked today because EC2 calls typically fail first (Problem 1), so the
AutoScaling call is never reached.
```
---
Expected behaviour

- aws:EC2():describeInstances({ InstanceIds = {...} }) sends a valid signed POST to
https://ec2..amazonaws.com/ with
Action=DescribeInstances&Version=2016-11-15&InstanceId.1=i-xxx&… in the body.
- aws:AutoScaling():describeAutoScalingInstances({ InstanceIds = {...} }) sends
…&InstanceIds.member.1=i-xxx&InstanceIds.member.2=i-yyy&….

---
Root cause & suggested fix

EC2 protocol — the body encoder needs to be implemented. The ec2 wire protocol
is structurally identical to query (POST + application/x-www-form-urlencoded),
with two differences:

1. Action and Version go in the body, not request.query.
2. List members use the member shape's locationName with a .N suffix (no .member.
infix). For example, DescribeInstances defines InstanceIds with
"locationName": "InstanceId", so the wire key is InstanceId.1, InstanceId.2, …

Suggested replacement for the stub in build.lua:
```lua
if config.protocol == "ec2" then
local body_parts = {
"Action=" .. operation.name,
"Version=" .. config.apiVersion,
}
for bname, bvalue in pairs(body_tbl) do
local mc = ((operation.input or {}).members or {})[bname] or {}
local lname = mc.locationName or bname
if type(bvalue) == "table" then
for i, v in ipairs(bvalue) do
body_parts[#body_parts + 1] = lname .. "." .. i .. "=" .. escape_uri(tostring(v))
end
else
body_parts[#body_parts + 1] = lname .. "=" .. escape_uri(tostring(bvalue))
end
end
table.sort(body_parts)
request.headers["Content-Type"] = "application/x-www-form-urlencoded"
request.body = table.concat(body_parts, "&")

Query protocol list params — serialize tables immediately rather than storing them
raw:

-- location == nil branch, query protocol:
if type(param_value) == "table" then
for i, v in ipairs(param_value) do
request.query[name .. ".member." .. i] = tostring(v)
end
else
request.query[name] = param_value
end
```
Both fixes use only locals already present in build.lua (escape_uri, operation,
body_tbl) and require no new dependencies.

Happy to open a PR with these changes if that would be helpful.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start in build.lua around the query-protocol handling near line 196 and the EC2 stub near line 241, using the two minimal reproductions to trace request construction. Confirm completion when EC2 produces the expected form-encoded Action, Version, and InstanceId.N fields, while query-protocol lists produce name.member.N keys without silently retaining raw tables.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, lua
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.