From 7b5be6578cf6b545bc94dd3bcce73a60ff524304 Mon Sep 17 00:00:00 2001 From: themilchenko Date: Sat, 1 Nov 2025 17:36:40 +0300 Subject: [PATCH 1/3] api: use ordinary tls with ca_file parameter By default server checked server and client certificates which should do with mTLS configuration. Since it is not expected behaviour, after the patch `ca_file` configuration won't ask for client certificates authorization. Closes #217 --- CHANGELOG.md | 1 + http/server.lua | 2 +- test/integration/http_tls_enabled_test.lua | 4 ---- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bd5034..2fb0d9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Do not recreate server if it's address and port were not changed (#219). - Server doesn't change after updating parameters on config reload (#216). +- **Breaking change**: Mutual TLS with `ca_file` option enabled (#217). ## [1.8.0] - 2025-07-07 diff --git a/http/server.lua b/http/server.lua index 1296b3c..91d6a36 100644 --- a/http/server.lua +++ b/http/server.lua @@ -1328,7 +1328,7 @@ local function create_ssl_ctx(host, port, opts) ) end - sslsocket.ctx_set_verify(ctx, 0x01 + 0x02) + sslsocket.ctx_set_verify(ctx, 0x00) end if opts.ssl_ciphers ~= nil then diff --git a/test/integration/http_tls_enabled_test.lua b/test/integration/http_tls_enabled_test.lua index 36dfe15..92eebe7 100644 --- a/test/integration/http_tls_enabled_test.lua +++ b/test/integration/http_tls_enabled_test.lua @@ -96,10 +96,6 @@ local client_test_cases = { ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), }, - request_opts = { - ssl_cert = fio.pathjoin(ssl_data_dir, 'client.crt'), - ssl_key = fio.pathjoin(ssl_data_dir, 'client.key'), - }, }, test_client_password_key_missing = { ssl_opts = { From ebd6fdf0c72ec795b7cca963fdd075e15a88b650 Mon Sep 17 00:00:00 2001 From: themilchenko Date: Wed, 5 Nov 2025 12:58:51 +0300 Subject: [PATCH 2/3] api: support `ssl_verify_client` option This patch allows to set a new `ssl_verify_client` option. It uses in pair with `ssl_ca_file` option and needs for client validation. It could have following values: * `off` (default one) means that no client's certs will be verified; * `on` means that server will verify client's certs; * `optional` means that server will verify client's certs only if it exist. This set of options was was built on top of the NGINX API (https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_verify_client). In effect, this option forces the server to work with mutual TLS. Part of #207 --- CHANGELOG.md | 2 + README.md | 4 ++ http/server.lua | 20 +++++- http/sslsocket.lua | 8 +++ test/helpers.lua | 10 +++ test/integration/http_tls_enabled_test.lua | 67 +++++++++++++++++++ .../http_tls_enabled_validate_test.lua | 6 ++ test/ssl_data/bad_client.crt | 32 +++++++++ test/ssl_data/bad_client.key | 52 ++++++++++++++ 9 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 test/ssl_data/bad_client.crt create mode 100644 test/ssl_data/bad_client.key diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fb0d9f..027e4b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Added +- `ssl_verify_client` option (#207). + ## Changed ## Fixed diff --git a/README.md b/README.md index 1d90ceb..69500bc 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,10 @@ httpd = require('http.server').new(host, port[, { options } ]) * `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. + * `ssl_verify_client` is an option that allows to verify client. It has following values: + * `off` (default) means that no client's certs will be verified; + * `on` means that server will verify client's certs; + * `optional` means that server will verify client's certs only if it exist. ## Using routes diff --git a/http/server.lua b/http/server.lua index 91d6a36..fa24e46 100644 --- a/http/server.lua +++ b/http/server.lua @@ -1296,6 +1296,12 @@ local function url_for_httpd(httpd, name, args, query) end end +local VERIFY_CLIENT_OPTS = { + off = sslsocket.SET_VERIFY_FLAGS.SSL_VERIFY_NONE, + optional = sslsocket.SET_VERIFY_FLAGS.SSL_VERIFY_PEER, + on = bit.bor(sslsocket.SET_VERIFY_FLAGS.SSL_VERIFY_PEER, sslsocket.SET_VERIFY_FLAGS.SSL_VERIFY_FAIL_IF_NO_PEER), +} + local function create_ssl_ctx(host, port, opts) local ok, ctx = pcall(sslsocket.ctx, sslsocket.tls_server_method()) if ok ~= true then @@ -1328,7 +1334,11 @@ local function create_ssl_ctx(host, port, opts) ) end - sslsocket.ctx_set_verify(ctx, 0x00) + local set_verify_flag = ( + opts.ssl_verify_client and VERIFY_CLIENT_OPTS[opts.ssl_verify_client] or + VERIFY_CLIENT_OPTS.off + ) + sslsocket.ctx_set_verify(ctx, set_verify_flag) end if opts.ssl_ciphers ~= nil then @@ -1383,6 +1393,12 @@ local function validate_ssl_opts(opts) errorf("%s option must be a string", key) end + if key == 'ssl_verify_client' then + if VERIFY_CLIENT_OPTS[value] == nil then + errorf('%q option not exists. Available options: "on", "off", "optional"', value) + end + end + if string.find(key, 'file') ~= nil and fio.path.exists(value) ~= true then errorf("file %q not exists", value) end @@ -1429,6 +1445,7 @@ local exports = { ssl_password_file = options.ssl_password_file, ssl_ca_file = options.ssl_ca_file, ssl_ciphers = options.ssl_ciphers, + ssl_verify_client = options.ssl_verify_client, }) local default = { @@ -1499,6 +1516,7 @@ local exports = { ssl_password_file = self.options.ssl_password_file, ssl_ca_file = self.options.ssl_ca_file, ssl_ciphers = self.options.ssl_ciphers, + ssl_verify_client = self.options.ssl_verify_client, }) return sslsocket.tcp_server(host, port, handler, timeout, ssl_ctx) end diff --git a/http/sslsocket.lua b/http/sslsocket.lua index 950196b..345d6ea 100644 --- a/http/sslsocket.lua +++ b/http/sslsocket.lua @@ -56,6 +56,12 @@ pcall(ffi.cdef, [[ const void *needle, size_t needlelen); ]]) +local SET_VERIFY_FLAGS = { + SSL_VERIFY_NONE = 0x00, + SSL_VERIFY_PEER = 0x01, + SSL_VERIFY_FAIL_IF_NO_PEER = 0x02, +} + local function slice_wait(timeout, starttime) if timeout == nil then return nil @@ -452,6 +458,8 @@ local function tcp_server(host, port, handler, timeout, sslctx) end return { + SET_VERIFY_FLAGS = SET_VERIFY_FLAGS, + tls_server_method = tls_server_method, ctx = ctx, diff --git a/test/helpers.lua b/test/helpers.lua index 44936ce..bfc6425 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -12,6 +12,16 @@ helpers.base_host = '127.0.0.1' helpers.base_uri = ('http://%s:%s'):format(helpers.base_host, helpers.base_port) helpers.tls_uri = ('https://%s:%s'):format('localhost', helpers.base_port) +local is_tarantool1 = luatest_utils.version_ge( + luatest_utils.get_tarantool_version(), + luatest_utils.version(1, 0, 0) +) + +helpers.CONNECTION_REFUSED_ERR_MSG = "Failure when receiving data from the peer: Connection refused" +if is_tarantool1 then + helpers.CONNECTION_REFUSED_ERR_MSG = "Failure when receiving data from the peer" +end + helpers.get_testdir_path = function() local path = os.getenv('LUA_SOURCE_DIR') or './' return fio.pathjoin(path, 'test') diff --git a/test/integration/http_tls_enabled_test.lua b/test/integration/http_tls_enabled_test.lua index 92eebe7..478ae5e 100644 --- a/test/integration/http_tls_enabled_test.lua +++ b/test/integration/http_tls_enabled_test.lua @@ -145,6 +145,73 @@ local client_test_cases = { }, expected_err_msg = "curl: Problem with the local SSL certificate", }, + test_verify_client_optional_with_certs_valid = { + ssl_opts = { + ssl_verify_client = 'optional', + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + }, + request_opts = { + ssl_cert = fio.pathjoin(ssl_data_dir, 'client.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'client.key'), + }, + }, + test_verify_client_optional_with_certs_invalid = { + ssl_opts = { + ssl_verify_client = 'optional', + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + }, + request_opts = { + ssl_cert = fio.pathjoin(ssl_data_dir, 'bad_client.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'bad_client.key'), + }, + expected_err_msg = helpers.CONNECTION_REFUSED_ERR_MSG, + }, + test_verify_client_optional_withouts_certs = { + ssl_opts = { + ssl_verify_client = 'optional', + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + }, + }, + test_verify_client_on_valid = { + ssl_opts = { + ssl_verify_client = 'on', + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + }, + request_opts = { + ssl_cert = fio.pathjoin(ssl_data_dir, 'client.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'client.key'), + }, + }, + test_verify_client_on_invalid = { + ssl_opts = { + ssl_verify_client = 'on', + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + }, + request_opts = { + ssl_cert = fio.pathjoin(ssl_data_dir, 'bad_client.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'bad_client.key'), + }, + expected_err_msg = helpers.CONNECTION_REFUSED_ERR_MSG, + }, + test_verify_client_on_certs_missing = { + ssl_opts = { + ssl_verify_client = 'on', + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + }, + expected_err_msg = helpers.CONNECTION_REFUSED_ERR_MSG, + }, } for name, tc in pairs(client_test_cases) do diff --git a/test/integration/http_tls_enabled_validate_test.lua b/test/integration/http_tls_enabled_validate_test.lua index 0439fa6..ea4835f 100644 --- a/test/integration/http_tls_enabled_validate_test.lua +++ b/test/integration/http_tls_enabled_validate_test.lua @@ -105,6 +105,12 @@ local test_cases = { }, expected_err_msg = "ssl_ciphers option must be a string", }, + ssl_verify_client_incorrect_value = { + opts = { + ssl_verify_client = "unknown", + }, + expected_err_msg = '"unknown" option not exists. Available options: "on", "off", "optional"' + }, } for name, case in pairs(test_cases) do diff --git a/test/ssl_data/bad_client.crt b/test/ssl_data/bad_client.crt new file mode 100644 index 0000000..e097781 --- /dev/null +++ b/test/ssl_data/bad_client.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIUdML0W9aabPXYExbeWFU4c5s7/ZYwDQYJKoZIhvcNAQEL +BQAwVTEQMA4GA1UECwwHVW5rbm93bjEQMA4GA1UECgwHVW5rbm93bjEQMA4GA1UE +BwwHVW5rbm93bjEQMA4GA1UECAwHdW5rbm93bjELMAkGA1UEBhMCQVUwIBcNMjUx +MTAxMTM1NzM4WhgPMjEyNTEwMDgxMzU3MzhaMGkxEjAQBgNVBAMMCWxvY2FsaG9z +dDEQMA4GA1UECwwHVW5rbm93bjEQMA4GA1UECgwHVW5rbm93bjEQMA4GA1UEBwwH +VW5rbm93bjEQMA4GA1UECAwHdW5rbm93bjELMAkGA1UEBhMCQVUwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC9AKdee4oztHzNrfDvf0wrijKPaHCKtrwz +7vegGUiP6cQRLn9RO+OHnvZOxii/QdCtFAZHDLyhd5pwzyiJxh6iup2teOkmE9jQ +RdkFkZifJe5hzuRvZKj0Wdqe0T2bNzlNH2ejDrTD+I2n1N33pDcg7OIzm7w/FyV7 +HdXOU52cOrwcCv+5OdG8qxr7KunrD/Es5HMr3YkNeEk6PNAZeKIFHEmiIZoavfcZ +v1Ks78jRNh1/FehgM1lrCvhAF7S+u3NTKoMRutLMMNJ67ag9bwVbeYgxtXFLwFr/ +GBx+K1xnG9rsI6TiC48OoYBKSgFXmDLu27scgtIlbdlcMJBX4ElpTiLcPvo62UoC +TaImArBFaiFsO3QeG4Db6i20zXrlpTWJMDTq06Uk9zScpHGlFDLsLt3Ptk7hw9wf +kU/vMO/GAgU/ShQbTK/Cw0ZodTpAcCyyH60owx4ynBd+XgHEa3jbG2MAOsPtbgUL +OnboFUkwtwvKN+M647aD8OLQWGCgNOQM05MDe4BJnFf9yQEU0gyWQVT6n3hhgV3Z +RWZ4nrEz7qJf4ay+kvLrvP7jdELMmb2p+HATdzeiAb6jpIsmse6x/DfL96qc1j8G +H9P60W9oE0ISR+7Fy15Y+Wqov/WnrpbCIA/yw6JdjDRb+4qjY2+BmpGIwKWKND5I +C33s6oCh5wIDAQABo0IwQDAdBgNVHQ4EFgQUpiDiWDD57qUoauGYNJjODpqGr3Iw +HwYDVR0jBBgwFoAUfete7UEBQwYIC4dsESdN4ryLsZIwDQYJKoZIhvcNAQELBQAD +ggIBAGLBf4N8956edEl1o1lDyCk/NSdwk0th2LGZMh7WRLpGwh+Qwf42x5INjcBd +p8y2E6Avx5rtSDBgEqt7c1/Ug0aKlNxgu588MtBIaAy6PeyfPK1yjWE5MwSOFONn +qHcPKc92eixyZyv9BAC2PiqskFzTDAEQ78n1TBH4pgVurfoSybYOZcCy4nS6ug77 +HNNVKoGX2TQUclC4ZToywYextggZALZEL+xxNz8Xt+1ak6GLhOFfxvGJU43lY4dd +PDiueObzeFrLkHq/Tt0l9p7glV0DHW4MC3R8w5fKIUyXcXGBX+UB2ewFaiDHsWCt +NiuSbWtOkAnt6I5I8h2exbC1mWbUzUTyMV9zfYmwXJrvtN5DiBhBOXODZns1ZWck +AKgyOhma8Uey7VaDuhNbobHh7eRhkD1qqX2+OQAHX8z19vGTWmwyCqjhuNdFd1uu +PLaGrqo8e1GrdrwcZeVrSI9Cp15zi4LmLnG8YiDG5RkoIR6h3VKNmZsq8/U/7ouS +pyx5TRsY9s2v37dTJIFEmeUuG4GDW4EG2DDtJ75qXlipfCHLe4BT0cSKYCAXpJ1v +aqUYzPPmiP9q1wYfIkuNjJ3AGwPGoaCpBOMFrul8XU3Qk+kjXfVw3ZD6wR20iL86 +46UvD+VktOt6EZTOGjTdEjlyOzvRQdqqMIIiVNRx7gT8TlrN +-----END CERTIFICATE----- diff --git a/test/ssl_data/bad_client.key b/test/ssl_data/bad_client.key new file mode 100644 index 0000000..a99e586 --- /dev/null +++ b/test/ssl_data/bad_client.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +4xz9jwcKbEzsHdwVB+OoRs47xcWsd923 +-----END PRIVATE KEY----- From e5839406da93c990b174ed7bf05ff4e0bb7e4db7 Mon Sep 17 00:00:00 2001 From: themilchenko Date: Wed, 5 Nov 2025 16:16:42 +0300 Subject: [PATCH 3/3] roles: support `ssl_verify_client` option Since http server supports a new `ssl_verify_client` option it is necessary to support it in role api as well. This patch introduces a new config parameter in httpd role with the same `ssl_verify_client` name. Closes #207 --- README.md | 1 + roles/httpd.lua | 1 + test/integration/httpd_role_test.lua | 56 ++++++++++++++++++++++++++++ test/unit/httpd_role_test.lua | 18 +++++++++ 4 files changed, 76 insertions(+) diff --git a/README.md b/README.md index 69500bc..f79aa6e 100644 --- a/README.md +++ b/README.md @@ -604,6 +604,7 @@ roles_cfg: ssl_ciphers: "cipher1:cipher2" ssl_password: "password" ssl_password_file: "path/to/ssl/password" + ssl_verify_client: "off" ``` This role accepts a server by name from a config and creates a route to return diff --git a/roles/httpd.lua b/roles/httpd.lua index 0fae3e0..adb0402 100644 --- a/roles/httpd.lua +++ b/roles/httpd.lua @@ -104,6 +104,7 @@ local function parse_params(node) ssl_password_file = node.ssl_password_file, ssl_ca_file = node.ssl_ca_file, ssl_ciphers = node.ssl_ciphers, + ssl_verify_client = node.ssl_verify_client, } end diff --git a/test/integration/httpd_role_test.lua b/test/integration/httpd_role_test.lua index 12f5739..cb5c43e 100644 --- a/test/integration/httpd_role_test.lua +++ b/test/integration/httpd_role_test.lua @@ -255,3 +255,59 @@ g.test_enable_tls_on_config_reload = function(cg) local resp = http_client:get('http://localhost:13000/ping') t.assert_equals(resp.status, 444, 'response not 444') end + +g.test_ssl_verify_client = function(cg) + t.skip_if(not cg.params.use_tls, 'tls config required') + + local cfg = table.copy(tls_config) + + cfg.groups['group-001'].replicasets['replicaset-001'].roles_cfg['roles.httpd'].default + .ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt') + cfg.groups['group-001'].replicasets['replicaset-001'].roles_cfg['roles.httpd'].default + .ssl_verify_client = "on" + treegen.write_file(cg.server.chdir, 'config.yaml', yaml.encode(cfg)) + local _, err = cg.server:eval("require('config'):reload()") + t.assert_not(err) + + t.assert_error_msg_contains(helpers.CONNECTION_REFUSED_ERR_MSG, function() + http_client:get('https://localhost:13000/ping', { + ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt') + }) + end) + + local resp = http_client:get('https://localhost:13000/ping', { + ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + ssl_cert = fio.pathjoin(ssl_data_dir, 'client.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'client.key'), + }) + t.assert_equals(resp.status, 200, 'response not 200') + t.assert_equals(resp.body, 'pong') + + cfg.groups['group-001'].replicasets['replicaset-001'].roles_cfg['roles.httpd'].default + .ssl_verify_client = "optional" + treegen.write_file(cg.server.chdir, 'config.yaml', yaml.encode(cfg)) + _, err = cg.server:eval("require('config'):reload()") + t.assert_not(err) + + t.assert_error_msg_contains(helpers.CONNECTION_REFUSED_ERR_MSG, function() + http_client:get('https://localhost:13000/ping', { + ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + ssl_cert = fio.pathjoin(ssl_data_dir, 'bad_client.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'bad_client.key'), + }) + end) + + resp = http_client:get('https://localhost:13000/ping', { + ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + ssl_cert = fio.pathjoin(ssl_data_dir, 'client.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'client.key'), + }) + t.assert_equals(resp.status, 200, 'response not 200') + t.assert_equals(resp.body, 'pong') +end + +g.after_test('test_ssl_verify_client', function(cg) + treegen.write_file(cg.server.chdir, 'config.yaml', yaml.encode(tls_config)) + local _, err = cg.server:eval("require('config'):reload()") + t.assert_not(err) +end) diff --git a/test/unit/httpd_role_test.lua b/test/unit/httpd_role_test.lua index 48cf837..1321ea6 100644 --- a/test/unit/httpd_role_test.lua +++ b/test/unit/httpd_role_test.lua @@ -226,6 +226,24 @@ local validation_cases = { }, }, err = "log_requests option should be a string", + }, + ["ssl_verify_client_invalid_type"] = { + cfg = { + server = { + listen = "localhost:123", + ssl_verify_client = 1, + } + }, + err = "ssl_verify_client option must be a string", + }, + ["ssl_verify_client_invalid_value"] = { + cfg = { + server = { + listen = "localhost:123", + ssl_verify_client = "unknown", + } + }, + err = '"unknown" option not exists. Available options: "on", "off", "optional"', } }