Skip to content
Snippets Groups Projects
Commit 64902055 authored by Alexander Turenko's avatar Alexander Turenko Committed by Alexander Turenko
Browse files

config: forbid incorrect/senseless advertise URIs

The following values shouldn't be accepted as an advertise URI:

* Not an URI.
* Comma separated list of URIs.
* An URI with IPv4/IPv6 INADDR_ANY host (`0.0.0.0` or `::`).
* An URI with zero TCP port.

Part of #8810

NO_DOC=the old behavior was not released, the documentation request will
       be registered manually
NO_CHANGELOG=see NO_DOC
parent cd90b91c
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,7 @@ local schema = require('internal.config.utils.schema')
local tarantool = require('tarantool')
local compat = require('compat')
local uuid = require('uuid')
local urilib = require('uri')
-- List of annotations:
--
......@@ -118,6 +119,47 @@ local function validate_uuid_str(data, w)
end
end
-- Accepts an uri object (one produced by urilib.parse()).
--
-- Performs several checks regarding ability to use the URI to
-- create a client socket. IOW, to call connect() on it.
--
-- The function returns `true` if the URI is OK to connect and
-- `false, err` otherwise.
--
-- If the URI doesn't fit the given criteria an error is raised.
-- The function returns nothing otherwise.
--
-- The following checks are performed:
--
-- * INADDR_ANY IPv4 address (0.0.0.0) or in6addr_any IPv6 address
-- (::) in the host part of the URI.
--
-- It means 'bind to all interfaces' for the bind() call, but it
-- has no meaning at the connect() call on a client.
-- * Zero TCP port (service part of the URI).
--
-- It means 'bind to a random free port' for the bind() call,
-- but it has no meaning at the connect() call on a client.
local function uri_is_suitable_to_connect(uri)
assert(uri ~= nil)
if uri.ipv4 == '0.0.0.0' then
return false, 'INADDR_ANY (0.0.0.0) cannot be used to create ' ..
'a client socket'
end
if uri.ipv6 == '::' then
return false, 'in6addr_any (::) cannot be used to create a client ' ..
'socket'
end
if uri.service == '0' then
return false, 'An URI with zero port cannot be used to create ' ..
'a client socket'
end
return true
end
return schema.new('instance_config', schema.record({
config = schema.record({
version = schema.enum({
......@@ -398,6 +440,23 @@ return schema.new('instance_config', schema.record({
advertise = schema.scalar({
type = 'string',
default = box.NULL,
validate = function(data, w)
-- Substitute variables with placeholders to don't
-- confuse the URI parser with the curly brackets.
data = data:gsub('{{ *.- *}}', 'placeholder')
local uri, err = urilib.parse(data)
if uri == nil then
if data:find(',') then
w.error('A single URI is expected, not a list of URIs')
end
w.error('Unable to parse an URI: %s', err)
end
local ok, err = uri_is_suitable_to_connect(uri)
if not ok then
w.error(err)
end
end,
}),
threads = schema.scalar({
type = 'integer',
......
......@@ -310,6 +310,130 @@ g.test_iproto = function()
t.assert_equals(res, exp)
end
-- Verify iproto.advertise validation, bad cases.
for case_name, case in pairs({
incorrect_uri = {
advertise = ':3301',
exp_err_msg = table.concat({
'[instance_config] iproto.advertise',
'Unable to parse an URI',
'Incorrect URI',
'expected host:service or /unix.socket'
}, ': '),
},
multiple_uris = {
advertise = 'localhost:3301,localhost:3302',
exp_err_msg = table.concat({
'[instance_config] iproto.advertise',
'A single URI is expected, not a list of URIs',
}, ': '),
},
inaddr_any_ipv4 = {
advertise = '0.0.0.0:3301',
exp_err_msg = table.concat({
'[instance_config] iproto.advertise',
'INADDR_ANY (0.0.0.0) cannot be used to create a client socket',
}, ': '),
},
inaddr_any_ipv4_user = {
advertise = 'user@0.0.0.0:3301',
exp_err_msg = table.concat({
'[instance_config] iproto.advertise',
'INADDR_ANY (0.0.0.0) cannot be used to create a client socket',
}, ': '),
},
inaddr_any_ipv4_user_pass = {
advertise = 'user:pass@0.0.0.0:3301',
exp_err_msg = table.concat({
'[instance_config] iproto.advertise',
'INADDR_ANY (0.0.0.0) cannot be used to create a client socket',
}, ': '),
},
inaddr_any_ipv6 = {
advertise = '[::]:3301',
exp_err_msg = table.concat({
'[instance_config] iproto.advertise',
'in6addr_any (::) cannot be used to create a client socket',
}, ': '),
},
inaddr_any_ipv6_user = {
advertise = 'user@[::]:3301',
exp_err_msg = table.concat({
'[instance_config] iproto.advertise',
'in6addr_any (::) cannot be used to create a client socket',
}, ': '),
},
inaddr_any_ipv6_user_pass = {
advertise = 'user:pass@[::]:3301',
exp_err_msg = table.concat({
'[instance_config] iproto.advertise',
'in6addr_any (::) cannot be used to create a client socket',
}, ': '),
},
zero_port = {
advertise = 'localhost:0',
exp_err_msg = table.concat({
'[instance_config] iproto.advertise',
'An URI with zero port cannot be used to create a client socket',
}, ': '),
},
zero_port_user = {
advertise = 'user@localhost:0',
exp_err_msg = table.concat({
'[instance_config] iproto.advertise',
'An URI with zero port cannot be used to create a client socket',
}, ': '),
},
zero_port_user_pass = {
advertise = 'user:pass@localhost:0',
exp_err_msg = table.concat({
'[instance_config] iproto.advertise',
'An URI with zero port cannot be used to create a client socket',
}, ': '),
},
}) do
g[('test_bad_iproto_advertise_%s'):format(case_name)] = function()
t.assert_error_msg_equals(case.exp_err_msg, function()
instance_config:validate({
iproto = {
advertise = case.advertise,
},
})
end)
end
end
-- Successful cases for iproto.advertise.
for case_name, case in pairs({
inet_socket = {
advertise = 'localhost:3301',
},
inet_socket_user = {
advertise = 'user@localhost:3301',
},
inet_socket_user_pass = {
advertise = 'user:pass@localhost:3301',
},
unix_socket = {
advertise = 'unix/:/foo/bar.iproto',
},
unix_socket_user = {
advertise = 'user@unix/:/foo/bar.iproto',
},
unix_socket_user_pass = {
advertise = 'user:pass@unix/:/foo/bar.iproto',
},
}) do
g[('test_good_iproto_advertise_%s'):format(case_name)] = function()
assert(case.advertise ~= nil)
instance_config:validate({
iproto = {
advertise = case.advertise,
},
})
end
end
g.test_database = function()
local iconfig = {
database = {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment