Skip to content

Commit 1ff7617

Browse files
committed
api: add tls opts for listen field
Since `metrics-export-role` support `listen` parameter, it is neccessary to add tls opts for it. After the patch the following options `ssl_*` were added. See an example as well: ```yaml roles: [roles.metrics-export] roles_cfg: roles.metrics-export: http: - listen: 8081 ssl_key_file: "ssl_data/server.enc.key" ssl_cert_file: "ssl_data/server.crt" ssl_ca_file: "ssl_data/ca.crt" ssl_ciphers: "ECDHE-RSA-AES256-GCM-SHA384" ssl_password_file: "ssl_data/passwords" endpoints: - path: /metrics/json format: json - path: /metrics/prometheus/ format: prometheus ``` Closes #26
1 parent 641ea88 commit 1ff7617

19 files changed

+799
-19
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Support TLS for `listen` parameter (#26).
13+
1214
### Fixed
1315

1416
- Update Tarantool dependency to `>=3.0.2` (#25).

README.md

+38
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,44 @@ roles_cfg:
185185
enabled: true
186186
```
187187

188+
### TLS support
189+
190+
It is possible to configure `metrics-export-role` with TLS options for
191+
`listen` parameter as well. To enable it, provide at least one of the
192+
following parameters:
193+
194+
* `ssl_cert_file` is a path to the SSL cert file, mandatory;
195+
* `ssl_key_file` is a path to the SSL key file, mandatory;
196+
* `ssl_ca_file` is a path to the SSL CA file, optional;
197+
* `ssl_ciphers` is a colon-separated list of SSL ciphers, optional;
198+
* `ssl_password` is a password for decrypting SSL private key, optional;
199+
* `ssl_password_file` is a SSL file with key for decrypting SSL private key, optional.
200+
201+
See an example below:
202+
203+
```yaml
204+
roles_cfg:
205+
roles.httpd:
206+
default:
207+
listen: 8081
208+
additional:
209+
listen: '127.0.0.1:8082'
210+
roles.metrics-export:
211+
http:
212+
- listen: 8081
213+
ssl_cert_file: "/path/to/ssl_cert_file"
214+
ssl_key_file: "/path/to/ssl_key_file"
215+
ssl_ca_file: "/path/to/ssl_ca_file"
216+
ssl_ciphers: "/path/to/ssl_ciphers"
217+
ssl_password: "/path/to/ssl_password"
218+
ssl_password_file: "/path/to/ssl_password_file"
219+
endpoints:
220+
- path: /metrics
221+
format: json
222+
- path: /metrics/prometheus/
223+
format: prometheus
224+
```
225+
188226
With this configuration, metrics can be obtained on this machine with the
189227
Tarantool instance as follows:
190228

roles/metrics-export.lua

+51-1
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,54 @@ local function validate_http_node(node)
171171
error("http configuration node must be a table, got " .. type(node), 3)
172172
end
173173

174+
local is_tls = false
175+
176+
if node.ssl_key_file ~= nil then
177+
is_tls = true
178+
if type(node.ssl_key_file) ~= 'string' then
179+
error("ssl_key_file sould be a string, got " .. type(node.ssl_key_file), 3)
180+
end
181+
end
182+
if node.ssl_cert_file ~= nil then
183+
is_tls = true
184+
if type(node.ssl_cert_file) ~= 'string' then
185+
error("ssl_cert_file sould be a string, got " .. type(node.ssl_cert_file), 3)
186+
end
187+
end
188+
if node.ssl_ca_file ~= nil then
189+
is_tls = true
190+
if type(node.ssl_ca_file) ~= 'string' then
191+
error("ssl_ca_file sould be a string, got " .. type(node.ssl_ca_file), 3)
192+
end
193+
end
194+
if node.ssl_ciphers ~= nil then
195+
is_tls = true
196+
if type(node.ssl_ciphers) ~= 'string' then
197+
error("ssl_ciphers_file sould be a string, got " .. type(node.ssl_ciphers), 3)
198+
end
199+
end
200+
if node.ssl_password ~= nil then
201+
is_tls = true
202+
if type(node.ssl_password) ~= 'string' then
203+
error("ssl_password sould be a string, got " .. type(node.ssl_password), 3)
204+
end
205+
end
206+
if node.ssl_password_file ~= nil then
207+
is_tls = true
208+
if type(node.ssl_password_file) ~= 'string' then
209+
error("ssl_password_file sould be a string, got " .. type(node.ssl_password_file), 3)
210+
end
211+
end
212+
174213
if node.server ~= nil then
175214
if type(node.server) ~= 'string' then
176215
error("server configuration sould be a string, got " .. type(node.server), 3)
177216
end
178217

218+
if is_tls then
219+
error("tls options are availabe only with 'listen' parameter", 3)
220+
end
221+
179222
if node.listen ~= nil then
180223
error("it is not possible to provide 'server' and 'listen' blocks simultaneously", 3)
181224
end
@@ -315,7 +358,14 @@ local function apply_http(conf)
315358
if http_servers[tostring(target.value) .. tostring(target.is_httpd_role)] == nil then
316359
local httpd
317360
if node.listen ~= nil then
318-
httpd = http_server.new(host, port)
361+
httpd = http_server.new(host, port, {
362+
ssl_cert_file = node.ssl_cert_file,
363+
ssl_key_file = node.ssl_key_file,
364+
ssl_ca_file = node.ssl_ca_file,
365+
ssl_ciphers = node.ssl_ciphers,
366+
ssl_password = node.ssl_password,
367+
ssl_password_file = node.ssl_password_file
368+
})
319369
httpd:start()
320370
else
321371
httpd = httpd_role.get_server(target.value)

test/entrypoint/config.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ groups:
4949
format: prometheus
5050
metrics:
5151
enabled: true
52+
- listen: 8087
53+
ssl_key_file: "ssl_data/server.enc.key"
54+
ssl_cert_file: "ssl_data/server.crt"
55+
ssl_ca_file: "ssl_data/ca.crt"
56+
ssl_ciphers: "ECDHE-RSA-AES256-GCM-SHA384"
57+
ssl_password_file: "ssl_data/passwords"
58+
endpoints:
59+
- path: /metrics/prometheus
60+
format: prometheus
61+
- path: /metrics/json/
62+
format: json
63+
- path: /metrics/observed/prometheus/1
64+
format: prometheus
65+
metrics:
66+
enabled: true
5267
iproto:
5368
listen:
5469
- uri: '127.0.0.1:3313'

test/integration/role_test.lua

+27-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
local fio = require('fio')
22
local json = require('json')
33
local helpers = require('test.helpers')
4-
local http_client = require('http.client')
4+
local http_client = require('http.client'):new()
55
local server = require('test.helpers.server')
66

77
local t = require('luatest')
@@ -17,6 +17,7 @@ g.before_each(function(cg)
1717

1818
fio.copytree(".rocks", fio.pathjoin(cg.workdir, ".rocks"))
1919
fio.copytree("roles", fio.pathjoin(cg.workdir, "roles"))
20+
fio.copytree(fio.pathjoin("test", "ssl_data"), fio.pathjoin(cg.workdir, "ssl_data"))
2021

2122
cg.router = server:new({
2223
config_file = fio.abspath(fio.pathjoin('test', 'entrypoint', 'config.yaml')),
@@ -36,8 +37,8 @@ g.after_each(function(cg)
3637
fio.rmtree(cg.workdir)
3738
end)
3839

39-
local function assert_json(uri)
40-
local response = http_client.get(uri)
40+
local function assert_json(uri, tls_opts)
41+
local response = http_client:get(uri, tls_opts)
4142
t.assert_equals(response.status, 200)
4243
t.assert(response.body)
4344

@@ -53,8 +54,8 @@ local function assert_json(uri)
5354
t.assert(found)
5455
end
5556

56-
local function assert_prometheus(uri)
57-
local response = http_client.get(uri)
57+
local function assert_prometheus(uri, tls_opts)
58+
local response = http_client:get(uri, tls_opts)
5859
t.assert_equals(response.status, 200)
5960
t.assert(response.body)
6061

@@ -66,11 +67,11 @@ local function assert_prometheus(uri)
6667
t.assert_not(ok)
6768
end
6869

69-
local function assert_observed(host, path)
70+
local function assert_observed(host, path, tls_opts)
7071
-- Trigger observation.
71-
http_client.get(host .. path)
72+
http_client:get(host .. path, tls_opts)
7273

73-
local response = http_client.get(host .. path)
74+
local response = http_client:get(host .. path, tls_opts)
7475
t.assert_equals(response.status, 200)
7576
t.assert(response.body)
7677

@@ -80,11 +81,11 @@ local function assert_observed(host, path)
8081
t.assert_not(ok)
8182
end
8283

83-
local function assert_not_observed(host, path)
84+
local function assert_not_observed(host, path, tls_opts)
8485
-- Trigger observation.
85-
http_client.get(host .. path)
86+
http_client:get(host .. path, tls_opts)
8687

87-
local response = http_client.get(host .. path)
88+
local response = http_client:get(host .. path, tls_opts)
8889
t.assert_equals(response.status, 200)
8990
t.assert(response.body)
9091

@@ -121,3 +122,18 @@ g.test_endpoints = function()
121122
assert_not_observed("http://127.0.0.1:8086", "/metrics/prometheus")
122123
assert_observed("http://127.0.0.1:8086", "/metrics/observed/prometheus/1")
123124
end
125+
126+
g.test_endpoint_with_tls = function(cg)
127+
local client_tls_opts = {
128+
ca_file = fio.pathjoin(cg.workdir, 'ssl_data', 'ca.crt'),
129+
ssl_cert = fio.pathjoin(cg.workdir, 'ssl_data', 'client.crt'),
130+
ssl_key = fio.pathjoin(cg.workdir, 'ssl_data', 'client.key'),
131+
}
132+
133+
assert_prometheus("https://localhost:8087/metrics/prometheus", client_tls_opts)
134+
assert_prometheus("https://localhost:8087/metrics/prometheus/", client_tls_opts)
135+
assert_json("https://localhost:8087/metrics/json", client_tls_opts)
136+
assert_json("https://localhost:8087/metrics/json/", client_tls_opts)
137+
assert_not_observed("https://localhost:8087", "/metrics/prometheus", client_tls_opts)
138+
assert_observed("https://localhost:8087", "/metrics/observed/prometheus/1", client_tls_opts)
139+
end

test/ssl_data/ca.crt

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-----BEGIN CERTIFICATE-----
2+
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
32+
-----END CERTIFICATE-----

test/ssl_data/ca.key

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCwracIA8bx6kua
3+
UsasZClbeE9x6PrEC0284KxiU2lQwWXBn020+mGo7asQ1pOHTe6TtTb+7zio1jOo
4+
RRvRZSUwsB3rXueLOgAb8Qwbrx/Th5qg7jKQIESYvhU/hr3Ub92V9EKAZquJhgvh
5+
731NqfDpHaTZa44Ze669TSBYkL2U76hm+2fLNKDrLWh/jlJRmEhrMU3mvOO2BKB3
6+
1wgSNfH7+7DJ+fs6aXlPOAxTBaelmFIxWqFqZLJWxeOOa0J9mKQzeCAz8IozPtHF
7+
bJI1WjBMNfOibz7zY3IqGR73pq4rgEB/7pG2dA35/I/6zHUS0YW2mg8zI6R0txQp
8+
eEeRohq6yVZe7ATsf10/tC+fTqR1Nu54JcOuraeMdWe00KuQDgQBa7rjMEUPoNXc
9+
hO3U3DSBjAWbNsMSaBc3WNpkcttQHOjvg97YhUPdTWJ+3WBJiUyfEe6LahNXlGZV
10+
g/mrNVmK+E37kE4bHVKAwaBCEslg8gIhuUDjdWUVNYW2j+FRJO+LBi9FV/xKdRmY
11+
MSr7FnyDMmceaWtS2/dyV7UR6lWhT8G7XqKfmFLQJUHutapMRE+VFBuzapttHBpQ
12+
SXFA+W10/nkrMYo++cJVe7sZDblzr+JbfS2eTvQVoWfb0lIgoB58O6wMDYwC+OcY
13+
m3cjLhy57fzbOc92afzmOxk5JztiEwIDAQABAoICABSX+Ss2/X5/N9bCJUQ83JE7
14+
4c6+QFSPmL0WVyGS5WizUkASaIVa1f1RzqnEySdxTwjKi6GFks4jQZwwigCLUJ1v
15+
Od2Qj16sIQ0guK+VZxlJ6h0uBpjEGhrPtTxVYVUcwPBUq1e6H+6EwGfSeYGO+HTD
16+
rs5k+ghAYWrRTZ7lKCPvF2sBjOSjusoO3epYVYILRQ2xjooBpG039thhKSCuRwl/
17+
GelBCSaS2sAAIXef5h2oNpRoIv56xErXACI+oF5xZ1pUezRyqjk07lCbyiML4ytO
18+
8poRa34FLm53xSNKu2x4o4wF69gfiO2Foeay5EaQQ4y4QmNMmUO9f00iJv1zrre7
19+
RpWC6MeMKs8oPSueZAEw+6aH2yzE4XvBMZnzbHrQl2E32e487i+W5PwZ7sWWIrpy
20+
A3cTKF2BXnBYRjWKiviisRk0Ok/+oq7aG8AaqttUmAnIFGk0drmxVJNADrZ62qrh
21+
j6UtMiqg8BNlT5/buu6Vta9O57dpcggozG3GiYbxMSCFNopEBb/bfZ3q90Rq1zwA
22+
897u2WJzPXpuMLVVdiXjhbaXrfEQFKpwi3AMBaD1StBVyWrigy1dw/WsKgH4pvUt
23+
RC8Va/yi0eYSMZTPafBs7G+NgyfANHh8Zs49mQTu6I6tSgo34WApiBvKL0+SmxOM
24+
xHN930fYMXc/Ng+SrY95AoIBAQDjF8oY2YBNANJgcyHOW/Ho+NaZ6VdcY16VafoF
25+
vMqNpnPZAY/0KU4pChYcXofSMDGznCAqIMisXV5VkQCzEfSYjeFtVyqolDuuo89t
26+
mmrQGIjkPRV9vkdUbRhEOCXtxuEzGAj4xhaotPk2QnG8uuvG5Jh949ZSFUapPPk1
27+
VZz8IoYhvr7SIt8ekhRIF+e5B1OazZrTxGrwkbiRq4pdaTjIzOKJQvjZBDV1pGn2
28+
74aX7YeZbHgVmyJfhTYfJRYSRLbIL/LjDVr3i9+1Ce3pjrC6uW12ZilkuKfFFV4H
29+
CT22rFsZnWf4+NEDdFykScd1vejfYIFRdQHeHTktzs1jNk49AoIBAQDHKwRwB9nR
30+
KeE192jWuVRA9ki4zXwlQZxovh5bUpAdMjpjVSdwX5Esk0YGm5UghsoBkWyeQ20Y
31+
/g4BW6BhkQR2qHjHsSBXc8MbFq5q0H5Tv+MaFQQWF0L8TauvJuCMRJ89Zsij0Z28
32+
rTO3oq0Am3d1kg65kdsh2xjRGTvL6FDFgHQKgeb8WRrd+E3ExklIW1HV3p69JvrM
33+
q0ZNL/vmN6pBdyOE5JHNKB1gbhQyDlozx7UwGDP+xaZLREGJt7QuNR1hGkHwFN3h
34+
45aDtnacatejqar4YO8WqXXOrEhcr4Q+A6dJpQbgi0jq8yRyZff1AXrV10phcNFC
35+
RTyhEcJNlUaPAoIBAQDSYzvMgdSngldlG8T6FZyspbzLoq5Y3YbHDgOgRSOyz78M
36+
ELJ5FbtfsgSCC+HxDM0/BSmXXgAMEARRaaEoRT5CB1ANqG/Q9mPEi+akOCc020YX
37+
ja/Xau1Sfi+5I/ufql0ApQfK0lozulYXur78hn/hJ+9O0kHAkg2AxQhsLQDfZmy1
38+
3q2SqNPk0pkPoXYAqZT/GfSStKoObjJ8Ylwx5kXBYm+NkwpQo+GTN9sj9wzOvDSg
39+
IymteqgBrrxRZl5oVliwZhuz1q+sH14Fr0lG0/dPRnLu+f7nXVuw7vbJtfoCvvM3
40+
a+jjdEDP3oHlgqTTpDmWmSW1fZ1ZVeGfWrRVcf5RAoIBAQCTiDf3TLl6iM08jpJo
41+
TEwu+sCPtBb7+ggERqByAUyjc+twXUmjogcFv+olRuZGe9HzK2gMK+IKm1aAhwDc
42+
hPGLe+xL79cHMMcbr8dvdBW/r+poDZ1DR+PkxRwh2GiJPuO+Nf716nYvpxUiOCxy
43+
wLbSrmid6X8rKwLNESYMO4BpbGeIyQTzdIXEWwQweLkcEhkilY98if1J0q12y86K
44+
kD1b1QbIkA+4qrhoD+KB2cPTi2GZyLPrSzmNk3gArcWYXNvwa+TgFHTvuQhrdKOT
45+
5kjqAOqWpic04D5V46SOk60fytEGamoXttVCxO0AmKv+HySAdsOwPkOkFWl93ovR
46+
sHvdAoIBAEcN4q7nbnuBXO1vhHo0VRbb8Lv8C4lQLin4vlmCggvo2ng0AnEOWILU
47+
DyWaqSvrs6pTYpO9XAP4HFucrUGORjJa83u3c/xKwhergBjj47WlXDeRUZ67c1u8
48+
JA76Uf6rGv7V/FHE5DyvDE4MjlXOf5fX7iBhDHcGKddmuUHd0gEqo2aUKcei/R0v
49+
xNigs87A7An4B+0pqbJdCw8oiduCcuLmYaaXwSiWf1Nr9Rotq1ZNfYTPlSIIGVcc
50+
NxE+2i2Fjms7GdnVWfpovRxlbAskqF6M5BJ28t9UJJOCO/qcApjjolawbsOeLVjG
51+
Pmmluzy93Kv806lnxw7ajz0PR1iHMtQ=
52+
-----END PRIVATE KEY-----

test/ssl_data/ca.srl

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2317821999DB4EAB2125F25133D863E18B49265A

test/ssl_data/client.crt

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-----BEGIN CERTIFICATE-----
2+
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
32+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)