Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: add tls opts for listen field #28

Merged
merged 3 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions .github/workflows/testing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ jobs:

runs-on: ubuntu-22.04
steps:
- name: Setup tt
run: |
curl -L https://tarantool.io/release/3/installer.sh | bash
sudo apt install -y tt
tt version

- name: Install tarantool ${{ matrix.tarantool }}
if: startsWith(matrix.tarantool, 'debug') != true
uses: tarantool/setup-tarantool@v3
with:
tarantool-version: ${{ matrix.tarantool }}
run: tt install tarantool ${{ matrix.tarantool }} --dynamic
oleg-jukovec marked this conversation as resolved.
Show resolved Hide resolved

- name: Create variables for Tarantool ${{ matrix.tarantool }}
if: startsWith(matrix.tarantool, 'debug')
Expand Down Expand Up @@ -88,12 +92,6 @@ jobs:
path: .rocks/
key: "cache-rocks-${{ matrix.tarantool }}${{ env.VERSION_POSTFIX }}"

- name: Setup tt
run: |
curl -L https://tarantool.io/release/3/installer.sh | bash
sudo apt install -y tt
tt version

- name: Install requirements
run: make deps depname=coverage
if: steps.cache-rocks.outputs.cache-hit != 'true'
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Support TLS for `listen` parameter (#26).

### Fixed

- Update Tarantool dependency to `>=3.0.2` (#25).
Expand Down
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,44 @@ roles_cfg:
enabled: true
```

### TLS support

It is possible to configure `metrics-export-role` with TLS options for
`listen` parameter as well. To enable it, provide at least one of the
following parameters:

* `ssl_cert_file` is a path to the SSL cert file, mandatory;
* `ssl_key_file` is a path to the SSL key file, mandatory;
* `ssl_ca_file` is a path to the SSL CA file, optional;
* `ssl_ciphers` is a colon-separated list of SSL ciphers, optional;
* `ssl_password` is a password for decrypting SSL private key, optional;
* `ssl_password_file` is a SSL file with key for decrypting SSL private key, optional.

See an example below:

```yaml
roles_cfg:
roles.httpd:
default:
listen: 8081
additional:
listen: '127.0.0.1:8082'
roles.metrics-export:
http:
- listen: 8081
ssl_cert_file: "/path/to/ssl_cert_file"
ssl_key_file: "/path/to/ssl_key_file"
ssl_ca_file: "/path/to/ssl_ca_file"
ssl_ciphers: "/path/to/ssl_ciphers"
ssl_password: "/path/to/ssl_password"
ssl_password_file: "/path/to/ssl_password_file"
endpoints:
- path: /metrics
format: json
- path: /metrics/prometheus/
format: prometheus
```

With this configuration, metrics can be obtained on this machine with the
Tarantool instance as follows:

Expand Down
2 changes: 1 addition & 1 deletion metrics-export-role-scm-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ description = {
dependencies = {
"lua >= 5.1",
"tarantool >= 3.0.2",
"http >= 1.5.0",
"http >= 1.7.0",
}

build = {
Expand Down
52 changes: 51 additions & 1 deletion roles/metrics-export.lua
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,54 @@ local function validate_http_node(node)
error("http configuration node must be a table, got " .. type(node), 3)
end

local is_tls = false

if node.ssl_key_file ~= nil then
is_tls = true
if type(node.ssl_key_file) ~= 'string' then
error("ssl_key_file sould be a string, got " .. type(node.ssl_key_file), 3)
end
end
if node.ssl_cert_file ~= nil then
is_tls = true
if type(node.ssl_cert_file) ~= 'string' then
error("ssl_cert_file sould be a string, got " .. type(node.ssl_cert_file), 3)
end
end
if node.ssl_ca_file ~= nil then
is_tls = true
if type(node.ssl_ca_file) ~= 'string' then
error("ssl_ca_file sould be a string, got " .. type(node.ssl_ca_file), 3)
end
end
if node.ssl_ciphers ~= nil then
is_tls = true
if type(node.ssl_ciphers) ~= 'string' then
error("ssl_ciphers_file sould be a string, got " .. type(node.ssl_ciphers), 3)
end
end
if node.ssl_password ~= nil then
is_tls = true
if type(node.ssl_password) ~= 'string' then
error("ssl_password sould be a string, got " .. type(node.ssl_password), 3)
end
end
if node.ssl_password_file ~= nil then
is_tls = true
if type(node.ssl_password_file) ~= 'string' then
error("ssl_password_file sould be a string, got " .. type(node.ssl_password_file), 3)
end
end

if node.server ~= nil then
if type(node.server) ~= 'string' then
error("server configuration sould be a string, got " .. type(node.server), 3)
end

if is_tls then
error("tls options are availabe only with 'listen' parameter", 3)
end

if node.listen ~= nil then
error("it is not possible to provide 'server' and 'listen' blocks simultaneously", 3)
end
Expand Down Expand Up @@ -315,7 +358,14 @@ local function apply_http(conf)
if http_servers[tostring(target.value) .. tostring(target.is_httpd_role)] == nil then
local httpd
if node.listen ~= nil then
httpd = http_server.new(host, port)
httpd = http_server.new(host, port, {
ssl_cert_file = node.ssl_cert_file,
ssl_key_file = node.ssl_key_file,
ssl_ca_file = node.ssl_ca_file,
ssl_ciphers = node.ssl_ciphers,
ssl_password = node.ssl_password,
ssl_password_file = node.ssl_password_file
})
httpd:start()
else
httpd = httpd_role.get_server(target.value)
Expand Down
15 changes: 15 additions & 0 deletions test/entrypoint/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ groups:
format: prometheus
metrics:
enabled: true
- listen: 8087
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/prometheus
format: prometheus
- path: /metrics/json/
format: json
- path: /metrics/observed/prometheus/1
format: prometheus
metrics:
enabled: true
iproto:
listen:
- uri: '127.0.0.1:3313'
Expand Down
38 changes: 27 additions & 11 deletions test/integration/role_test.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
local fio = require('fio')
local json = require('json')
local helpers = require('test.helpers')
local http_client = require('http.client')
local http_client = require('http.client'):new()
local server = require('test.helpers.server')

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

fio.copytree(".rocks", fio.pathjoin(cg.workdir, ".rocks"))
fio.copytree("roles", fio.pathjoin(cg.workdir, "roles"))
fio.copytree(fio.pathjoin("test", "ssl_data"), fio.pathjoin(cg.workdir, "ssl_data"))

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

local function assert_json(uri)
local response = http_client.get(uri)
local function assert_json(uri, tls_opts)
local response = http_client:get(uri, tls_opts)
t.assert_equals(response.status, 200)
t.assert(response.body)

Expand All @@ -53,8 +54,8 @@ local function assert_json(uri)
t.assert(found)
end

local function assert_prometheus(uri)
local response = http_client.get(uri)
local function assert_prometheus(uri, tls_opts)
local response = http_client:get(uri, tls_opts)
t.assert_equals(response.status, 200)
t.assert(response.body)

Expand All @@ -66,11 +67,11 @@ local function assert_prometheus(uri)
t.assert_not(ok)
end

local function assert_observed(host, path)
local function assert_observed(host, path, tls_opts)
-- Trigger observation.
http_client.get(host .. path)
http_client:get(host .. path, tls_opts)

local response = http_client.get(host .. path)
local response = http_client:get(host .. path, tls_opts)
t.assert_equals(response.status, 200)
t.assert(response.body)

Expand All @@ -80,11 +81,11 @@ local function assert_observed(host, path)
t.assert_not(ok)
end

local function assert_not_observed(host, path)
local function assert_not_observed(host, path, tls_opts)
-- Trigger observation.
http_client.get(host .. path)
http_client:get(host .. path, tls_opts)

local response = http_client.get(host .. path)
local response = http_client:get(host .. path, tls_opts)
t.assert_equals(response.status, 200)
t.assert(response.body)

Expand Down Expand Up @@ -121,3 +122,18 @@ g.test_endpoints = function()
assert_not_observed("http://127.0.0.1:8086", "/metrics/prometheus")
assert_observed("http://127.0.0.1:8086", "/metrics/observed/prometheus/1")
end

g.test_endpoint_with_tls = function(cg)
local client_tls_opts = {
ca_file = fio.pathjoin(cg.workdir, 'ssl_data', 'ca.crt'),
ssl_cert = fio.pathjoin(cg.workdir, 'ssl_data', 'client.crt'),
ssl_key = fio.pathjoin(cg.workdir, 'ssl_data', 'client.key'),
}

assert_prometheus("https://localhost:8087/metrics/prometheus", client_tls_opts)
assert_prometheus("https://localhost:8087/metrics/prometheus/", client_tls_opts)
assert_json("https://localhost:8087/metrics/json", client_tls_opts)
assert_json("https://localhost:8087/metrics/json/", client_tls_opts)
assert_not_observed("https://localhost:8087", "/metrics/prometheus", client_tls_opts)
assert_observed("https://localhost:8087", "/metrics/observed/prometheus/1", client_tls_opts)
end
32 changes: 32 additions & 0 deletions test/ssl_data/ca.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIUS4QNdb+cn+rds0qW9fIVYRbcHzIwDQYJKoZIhvcNAQEL
BQAwVTEQMA4GA1UECwwHVW5rbm93bjEQMA4GA1UECgwHVW5rbm93bjEQMA4GA1UE
BwwHVW5rbm93bjEQMA4GA1UECAwHdW5rbm93bjELMAkGA1UEBhMCQVUwIBcNMjQx
MjI1MTg0MjI2WhgPMjEyNDEyMDExODQyMjZaMFUxEDAOBgNVBAsMB1Vua25vd24x
EDAOBgNVBAoMB1Vua25vd24xEDAOBgNVBAcMB1Vua25vd24xEDAOBgNVBAgMB3Vu
a25vd24xCzAJBgNVBAYTAkFVMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
AgEAsK2nCAPG8epLmlLGrGQpW3hPcej6xAtNvOCsYlNpUMFlwZ9NtPphqO2rENaT
h03uk7U2/u84qNYzqEUb0WUlMLAd617nizoAG/EMG68f04eaoO4ykCBEmL4VP4a9
1G/dlfRCgGariYYL4e99Tanw6R2k2WuOGXuuvU0gWJC9lO+oZvtnyzSg6y1of45S
UZhIazFN5rzjtgSgd9cIEjXx+/uwyfn7Oml5TzgMUwWnpZhSMVqhamSyVsXjjmtC
fZikM3ggM/CKMz7RxWySNVowTDXzom8+82NyKhke96auK4BAf+6RtnQN+fyP+sx1
EtGFtpoPMyOkdLcUKXhHkaIauslWXuwE7H9dP7Qvn06kdTbueCXDrq2njHVntNCr
kA4EAWu64zBFD6DV3ITt1Nw0gYwFmzbDEmgXN1jaZHLbUBzo74Pe2IVD3U1ift1g
SYlMnxHui2oTV5RmVYP5qzVZivhN+5BOGx1SgMGgQhLJYPICIblA43VlFTWFto/h
USTviwYvRVf8SnUZmDEq+xZ8gzJnHmlrUtv3cle1EepVoU/Bu16in5hS0CVB7rWq
TERPlRQbs2qbbRwaUElxQPltdP55KzGKPvnCVXu7GQ25c6/iW30tnk70FaFn29JS
IKAefDusDA2MAvjnGJt3Iy4cue382znPdmn85jsZOSc7YhMCAwEAAaNTMFEwHQYD
VR0OBBYEFFifTydC7NEofl++JU6TulBaOV0nMB8GA1UdIwQYMBaAFFifTydC7NEo
fl++JU6TulBaOV0nMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB
ADettaKSOks+iKiHHfXraDcQ31VJEs2ji/NG4FW0aup6w4VuXrzKYwvUc4qWiHAU
3VWz5lMrUQuUiICoFSPo8adLVFa6AyDH8yXL32VX2AUPu8Z2O5ZxY80lg2/o9GbR
Lj3XbDQS0RBp5FQh9Uf1B6iYuLOOVSJtUSNpBLsT0/2x9j/D6BbvXgIiGur5NsdV
c6LrFl10pJNODiGXwRry5ZHiLaybtOu1pkL3/CRB4UDWLOBmg64F+RuZw9/auSLe
BvhTfMhDkhzS33IAjm2+pumpKyfgFCdh2po5pHnqLFeI9P1cZsIcOQG3TZSWlFgG
8SVvmbAwRn1FfWI+IHfuflp4XO138cAiSwKwJ14WeCwZ4SUJudf1MlxBJK9C+q7o
Sgy2r/ZoLwZuTpKK4+3+Yv829JyjY6Cb8u5WBviUOyQdnPGXnuXQDLEwLjvpEuWR
Vqe61vkK0psk+ZZjDzRJylHXq23RzHVstxT2k61SM7yQ6+DyIAhX5oFtDnCsolrU
9Qvh3ASaqPHhVV0VzDe918yO3jqwZI0+CDLkx5T19dNt/+kNnfZ/CG5tWQJUdwl4
B4zhSVhvklLJ4iA/WVcD/P4dAmd5mTtLTQSznoah1C7gQtfx73cdro0jkOKOJKsm
TvhfRTbEDavauO3Bv5FGh4BL2diGVZ442WeGY5yHxqt7
-----END CERTIFICATE-----
52 changes: 52 additions & 0 deletions test/ssl_data/ca.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
Pmmluzy93Kv806lnxw7ajz0PR1iHMtQ=
-----END PRIVATE KEY-----
1 change: 1 addition & 0 deletions test/ssl_data/ca.srl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2317821999DB4EAB2125F25133D863E18B49265A
32 changes: 32 additions & 0 deletions test/ssl_data/client.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIUIxeCGZnbTqshJfJRM9hj4YtJJlowDQYJKoZIhvcNAQEL
BQAwVTEQMA4GA1UECwwHVW5rbm93bjEQMA4GA1UECgwHVW5rbm93bjEQMA4GA1UE
BwwHVW5rbm93bjEQMA4GA1UECAwHdW5rbm93bjELMAkGA1UEBhMCQVUwIBcNMjQx
MjI1MTg0MjI3WhgPMjEyNDEyMDExODQyMjdaMGkxEjAQBgNVBAMMCWxvY2FsaG9z
dDEQMA4GA1UECwwHVW5rbm93bjEQMA4GA1UECgwHVW5rbm93bjEQMA4GA1UEBwwH
VW5rbm93bjEQMA4GA1UECAwHdW5rbm93bjELMAkGA1UEBhMCQVUwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQDlhBwUEYaTWzVTlJiDHDgh4Vqtphqd94tt
TJIhnjPBB0/S5GeZB24+k+qd6lvVlmtWLrXXr69u41M8yXfXOclG9VWzHcKlWIhP
RHr9RR1+9GnOYDpj1hNwpm7YY9JIDjr2RH/PIA8OrlQzxzZn0XZpHslEYOr5EvGe
E7G3Evl6umUjo/pyh1oqKsGTS53xCur5EIY+L5Ar9Yro3upGoW9tLKRPLjjfXe1c
tJ2eKc9h+AKb7Uzq6WC0k5KESEL1Uy6Mm8jXw39vhldi3rR4eB7urExAGoMQ7wwa
np9cg6fNna58vd90MlA/ALNopqaJdkBsKu1csMFE4DtjYk/fpLAdnrjxT0MXRYiq
/hwOXthHvF0iyL8y+4m08cbxIjlxErkGlG/YOvsKnLxPTq1tNi1GYknJKYHllZcv
Fr5KmbRAN12wIUdlheY73qkj6ljrSQYd6Ief0Qtox4OlNsKggp2qC3C8C8iZZHBh
jGkxRzMLy8mdJ8l9i50xauNIoIOx27pYaACxqVTwKnm2Emk3Zu+Dr1ujs29+LfEX
zcesDMeLcvrkIQC1BnUsxcgQp0WYRPJ+xQSCvZM5Yn+TV9uh7wHvKIaqwwuwBYWZ
l7WPhfvAIu6G+6gSnmBVTo8gHgM4qLdYQuS2X1F5VpGvM+UoLk8qwgQmI7cE+kkV
21ZHDyFKGwIDAQABo0IwQDAdBgNVHQ4EFgQUlCjNJOroOKOOD63Khp17EA2WuAQw
HwYDVR0jBBgwFoAUWJ9PJ0Ls0Sh+X74lTpO6UFo5XScwDQYJKoZIhvcNAQELBQAD
ggIBAFoYaOzJmJfm0qZjMQR48cRENn84IJNJ+Fu8qJBnws/4c/G2YGrqVKZMRRD5
2fqbB6O0HzpgN0YPlwtACkjGUZ+zrOm79YdUXNqaAoyRJocRPcDrGFsXhxnvDsOt
y5PeFqSzXtCLe0J7e/GSUjVxbSm5j+GiXh65DBW3Cv3/OBVksGwVnfMn1p4FuXMY
V7FLLqG4i/D/IkFegEO5Qf5R8lKx2DXAZdUe2mK81y12ElOsMVCnxaZ0T8EJ5CPY
lKugyuSz1oNyZ/aOuJKOGwisOu9S6SumuUU2gpUCB0hPyP7BcE27t84jMdCEQJtN
IRcA22AX5VH9x3Yhctl9Y3/RJERbvx5SM40m2hxUWp6tqXQDEQIOPxiAKe4gFshO
1LVSLNaE5s3YTEEmaYCY2mWmMLGiEPgbjc2SBd7J1XcmWqDWJ/I6zX22TnSS95h5
2TZHGjMD4NUs2kXI4v7QxfdNwMe9U+vE4TAVayOBUallpjRuj0UUe7xg4dCdCXtc
6aW72AnFsbs6p9m4HbQA0camVtmzX6RmR7fr6VTKkclVl0+KNZpubUH6bbwlTBmk
xszshS6EXRUzOF+MJhdPcfAZRPcEWPeKBUeQL2Q5rj0Y8nFk/9F6SFE+EzupPM/9
hvzUJMRMei+Sc4eSQqLHdtYBq1FmgXHxn/4J9elofNn91YRk
-----END CERTIFICATE-----
Loading