Skip to content

Commit c665652

Browse files
authored
Merge pull request #49 from scr-oath/mTLS
Added mTLS support for http client.
2 parents d4e40cb + 92aa2da commit c665652

File tree

13 files changed

+585
-87
lines changed

13 files changed

+585
-87
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
go: ['1.14', '1.15', '1.16', '1.17', '1.18']
15+
go: ['1.14', '1.15', '1.16', '1.17', '1.18', '1.19']
1616

1717
services: {}
1818
steps:

http/README.md

Lines changed: 125 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Functions
44

55
- `client()` - returns http client instance for further usage. Avaliable options as table:
6+
67
```
78
proxy="http(s)://<user>:<password>@host:<port>",
89
timeout= 10,
@@ -12,11 +13,22 @@ basic_auth_user = "",
1213
basic_auth_password = "",
1314
headers = {"key"="value"},
1415
debug = false,
16+
17+
# When set, the client will present the client cert for "mTLS"
18+
client_public_cert_pem_file = nil,
19+
client_private_key_pem_file = nil,
20+
21+
# When set, this will be used to verify the server certificate (useful for private enterprise certificate authorities).
22+
# Prefer this over insecure_ssl when possible
23+
root_cas_pem_file = "",
1524
```
25+
1626
- `request(method, url, [data])` - make request userdata.
1727

1828
## Methods
29+
1930
### client
31+
2032
- `do_request(request)` - returns result of request. Avaliable data are: 'body', 'headers', 'code'
2133

2234
## Examples
@@ -30,9 +42,15 @@ local client = http.client()
3042
-- GET
3143
local request = http.request("GET", "http://hostname.com")
3244
local result, err = client:do_request(request)
33-
if err then error(err) end
34-
if not(result.code == 200) then error("code") end
35-
if not(result.body == "xxx.xxx.xxx.xxx") then error("body") end
45+
if err then
46+
error(err)
47+
end
48+
if not (result.code == 200) then
49+
error("code")
50+
end
51+
if not (result.body == "xxx.xxx.xxx.xxx") then
52+
error("body")
53+
end
3654

3755
-- auth basic
3856
local request = http.request("GET", "http://hostname.com")
@@ -44,18 +62,18 @@ local request = http.request("POST", "http://hostname.com/api.json", "{}")
4462
request:header_set("Content-Type", "application/json")
4563

4664
-- with proxy
47-
local client = http.client({proxy="http(s)://login:[email protected]"})
65+
local client = http.client({ proxy = "http(s)://login:[email protected]" })
4866
local request = http.request("POST", "http://hostname.com/api.json", "{}")
4967

5068
-- ignore ssl
51-
local client = http.client({insecure_ssl=true})
69+
local client = http.client({ insecure_ssl = true })
5270
local request = http.request("POST", "http://hostname.com/api.json", "{}")
5371

5472
-- set headers for all request
55-
local client = http.client({ headers={key="value"} })
73+
local client = http.client({ headers = { key = "value" } })
5674

5775
-- set basic auth for all request
58-
local client = http.client({basic_auth_user="admin", basic_auth_password="123456"})
76+
local client = http.client({ basic_auth_user = "admin", basic_auth_password = "123456" })
5977
```
6078

6179
### Server
@@ -64,40 +82,44 @@ local client = http.client({basic_auth_user="admin", basic_auth_password="123456
6482

6583
```lua
6684
local server, err = http.server("127.0.0.1:1113")
67-
if err then error(err) end
85+
if err then
86+
error(err)
87+
end
6888

6989
while true do
70-
local request, response = server:accept() -- lock and wait request
71-
72-
-- print request
73-
print("host:", request.host)
74-
print("method:", request.method)
75-
print("referer:", request.referer)
76-
print("proto:", request.proto)
77-
print("path:", request.path)
78-
print("raw_path:", request.raw_path)
79-
print("raw_query:", request.raw_query)
80-
print("request_uri:", request.request_uri)
81-
print("remote_addr:", request.remote_addr)
82-
print("user_agent: "..request.user_agent)
83-
84-
-- get body
85-
local body, err = request.body()
86-
if err then error(err) end
87-
print("body:", body)
88-
89-
for k, v in pairs(request.headers) do
90-
print("header: ", k, v)
91-
end
92-
for k, v in pairs(request.query) do
93-
print("query params: ", k, "=" ,v)
94-
end
95-
-- write response
96-
response:code(200) -- write header
97-
response:header("content-type", "application/json")
98-
response:write(request.request_uri) -- write data
99-
-- response:redirect("http://google.com")
100-
response:done() -- end response
90+
local request, response = server:accept() -- lock and wait request
91+
92+
-- print request
93+
print("host:", request.host)
94+
print("method:", request.method)
95+
print("referer:", request.referer)
96+
print("proto:", request.proto)
97+
print("path:", request.path)
98+
print("raw_path:", request.raw_path)
99+
print("raw_query:", request.raw_query)
100+
print("request_uri:", request.request_uri)
101+
print("remote_addr:", request.remote_addr)
102+
print("user_agent: " .. request.user_agent)
103+
104+
-- get body
105+
local body, err = request.body()
106+
if err then
107+
error(err)
108+
end
109+
print("body:", body)
110+
111+
for k, v in pairs(request.headers) do
112+
print("header: ", k, v)
113+
end
114+
for k, v in pairs(request.query) do
115+
print("query params: ", k, "=", v)
116+
end
117+
-- write response
118+
response:code(200) -- write header
119+
response:header("content-type", "application/json")
120+
response:write(request.request_uri) -- write data
121+
-- response:redirect("http://google.com")
122+
response:done() -- end response
101123

102124
end
103125
```
@@ -106,7 +128,9 @@ end
106128

107129
```lua
108130
local server, err = http.server("127.0.0.1:1113")
109-
if err then error(err) end
131+
if err then
132+
error(err)
133+
end
110134

111135
server:do_handle_string([[ -- do_handle_file
112136
@@ -124,6 +148,67 @@ response:done()
124148
]]
125149
```
126150

151+
#### Handle variant (multithreaded as function)
152+
153+
```lua
154+
local server, err = http.server("127.0.0.1:1113")
155+
assert(not err, tostring(err))
156+
157+
server:do_handle_function(function(response, request)
158+
response:code(200)
159+
response:write("OK\n")
160+
response:done()
161+
end)
162+
```
163+
164+
#### Listen to an open port and get the address (host:port)
165+
166+
```lua
167+
local server, err = http.server {}
168+
assert(not err, tostring(err))
169+
local addr = server:addr()
170+
```
171+
172+
#### TLS support
173+
174+
```lua
175+
local server, err = http.server {
176+
addr = "127.0.0.1:1113",
177+
178+
-- Setting both of these enables TLS
179+
server_public_cert_pem_file = "test/data/test.cert.pem",
180+
server_private_key_pem_file = "test/data/test.key.pem",
181+
}
182+
assert(not err, tostring(err))
183+
184+
server:do_handle_function(function(response, request)
185+
response:code(200)
186+
response:write("OK\n")
187+
response:done()
188+
end)
189+
```
190+
191+
#### mTLS support (enforce client certs)
192+
193+
```lua
194+
local server, err = http.server {
195+
addr = "127.0.0.1:1113",
196+
client_cas_pem_file = "test/data/test.cert.pem",
197+
client_auth = "RequireAndVerifyClientCert", -- See https://pkg.go.dev/crypto/[email protected]#ClientAuthType
198+
199+
-- Setting both of these enables TLS
200+
server_public_cert_pem_file = "test/data/test.cert.pem",
201+
server_private_key_pem_file = "test/data/test.key.pem",
202+
}
203+
assert(not err, tostring(err))
204+
205+
server:do_handle_function(function(response, request)
206+
response:code(200)
207+
response:write("OK\n")
208+
response:done()
209+
end)
210+
```
211+
127212
#### Serve Static files
128213

129214
```lua

http/api_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ package http_test
22

33
import (
44
"crypto/subtle"
5+
"crypto/tls"
6+
"crypto/x509"
57
"fmt"
68
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
"github.com/vadv/gopher-lua-libs/tests"
711
"golang.org/x/sync/errgroup"
12+
"io"
813
"io/ioutil"
914
"log"
1015
"net/http"
16+
"net/http/httptest"
1117
"strings"
1218
"testing"
1319
"time"
@@ -218,3 +224,51 @@ func TestApi(t *testing.T) {
218224
assert.NoError(t, state.DoFile("./test/test_serve_static.lua"))
219225
})
220226
}
227+
228+
func TestMTLSClient(t *testing.T) {
229+
s := httptest.NewUnstartedServer(http.HandlerFunc(func(writer http.ResponseWriter, r *http.Request) {
230+
_, _ = io.WriteString(writer, "OK\n")
231+
}))
232+
defer s.Close()
233+
serverCert, err := tls.LoadX509KeyPair("test/data/test.cert.pem", "test/data/test.key.pem")
234+
require.NoError(t, err)
235+
caData, err := ioutil.ReadFile("test/data/test.cert.pem")
236+
require.NoError(t, err)
237+
cas := x509.NewCertPool()
238+
cas.AppendCertsFromPEM(caData)
239+
s.TLS = &tls.Config{
240+
Certificates: []tls.Certificate{serverCert},
241+
ClientCAs: cas,
242+
ClientAuth: tls.RequireAndVerifyClientCert,
243+
}
244+
s.StartTLS()
245+
246+
preload := tests.SeveralPreloadFuncs(
247+
lua_http.Preload,
248+
func(L *lua.LState) {
249+
// Pass the httptest server URL as a global, so it can be used for queries.
250+
L.SetGlobal("tURL", lua.LString(s.URL))
251+
},
252+
)
253+
assert.NotZero(t, tests.RunLuaTestFile(t, preload, "test/test_mtls_client.lua"))
254+
}
255+
256+
func TestMTLSServerWithClient(t *testing.T) {
257+
preload := tests.SeveralPreloadFuncs(
258+
lua_http.Preload,
259+
lua_time.Preload,
260+
inspect.Preload,
261+
plugin.Preload,
262+
)
263+
assert.NotZero(t, tests.RunLuaTestFile(t, preload, "test/test_mtls_server_with_client.lua"))
264+
}
265+
266+
func TestServer(t *testing.T) {
267+
preload := tests.SeveralPreloadFuncs(
268+
lua_http.Preload,
269+
lua_time.Preload,
270+
inspect.Preload,
271+
plugin.Preload,
272+
)
273+
assert.NotZero(t, tests.RunLuaTestFile(t, preload, "test/test_server.lua"))
274+
}

0 commit comments

Comments
 (0)