From 26f350b4e307db15318fac83c73a8ee6b80d37ee Mon Sep 17 00:00:00 2001 From: Alexey Potapenko Date: Fri, 27 Dec 2024 15:50:11 +0300 Subject: [PATCH] play: add TLS options @TarantoolBot document Title: play: add TLS options This patch adds support of the ssl parameters to the `tt play` command by using flags: `sslkeyfile` - path to a private SSL key file, `sslcertfile` - path to an SSL certificate file, `sslcafile` - path to a trusted certificate authorities (CA) file, `sslciphers` - colon-separated (:) list of SSL cipher suites the connection. Closes #1067 --- CHANGELOG.md | 5 ++ cli/checkpoint/lua/play.lua | 33 +++++++++++- cli/cmd/play.go | 35 +++++++++++++ test/integration/play/test_play.py | 52 +++++++++++++++++-- test/integration/play/test_ssl_app/ca.crt | 20 +++++++ .../integration/play/test_ssl_app/generate.sh | 25 +++++++++ test/integration/play/test_ssl_app/init.lua | 23 ++++++++ .../play/test_ssl_app/localhost.crt | 22 ++++++++ .../play/test_ssl_app/localhost.key | 28 ++++++++++ 9 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 test/integration/play/test_ssl_app/ca.crt create mode 100755 test/integration/play/test_ssl_app/generate.sh create mode 100644 test/integration/play/test_ssl_app/init.lua create mode 100644 test/integration/play/test_ssl_app/localhost.crt create mode 100644 test/integration/play/test_ssl_app/localhost.key diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ecd2a5a2..7a28d5b7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * `-e (--executable)`: specify Tarantool executable path. * `-p (--pid)`: specify PID of the dumped process. * `-t (--time)`: specify time of dump (seconds since the Epoch). +- `tt play`: support of the SSL parameters by using next flags: + * `sslkeyfile` - path to a private SSL key file, + * `sslcertfile` - path to an SSL certificate file, + * `sslcafile` - path to a trusted certificate authorities (CA) file, + * `sslciphers` - colon-separated list of SSL cipher suites the connection. ### Changed diff --git a/cli/checkpoint/lua/play.lua b/cli/checkpoint/lua/play.lua index fa2430b45..017feffdf 100644 --- a/cli/checkpoint/lua/play.lua +++ b/cli/checkpoint/lua/play.lua @@ -54,7 +54,33 @@ local function play(positional_arguments, keyword_arguments, opts) log.error('Internal error: empty URI is provided') os.exit(1) end - local remote = netbox.new(uri, opts) + + local remote = nil + if opts.transport ~= nil and opts.transport == "ssl" then + remote = netbox.connect( + { + uri = uri, + params = { + transport = opts.transport, + ssl_cert_file = opts.ssl_cert_file, + ssl_key_file = opts.ssl_key_file, + ssl_ca_file = opts.ssl_ca_file, + ssl_ciphers = opts.ssl_ciphers + } + }, + { + user = opts.user, + password = opts.password + } + ) + else + -- Older versions of the netbox.connect() use specific list + -- of parameters: connect(uri[, opts] | host, port[, opts]) + -- so, to save backward compatibility we shouldn't change + -- anything in the common case. + remote = netbox.new(uri, opts) + end + if not remote:wait_connected() then log.error('Fatal error: no connection to the host "%s"', uri) os.exit(1) @@ -149,6 +175,11 @@ local function main() local opts = { user = os.getenv('TT_CLI_PLAY_USERNAME'), password = os.getenv('TT_CLI_PLAY_PASSWORD'), + transport = os.getenv('TT_CLI_PLAY_TRANSPORT'), + ssl_cert_file = os.getenv('TT_CLI_PLAY_SSL_CERT_FILE'), + ssl_key_file = os.getenv('TT_CLI_PLAY_SSL_KEY_FILE'), + ssl_ca_file = os.getenv('TT_CLI_PLAY_SSL_CA_FILE'), + ssl_ciphers = os.getenv('TT_CLI_PLAY_SSL_CIPHERS'), } play(positional_arguments, keyword_arguments, opts) end diff --git a/cli/cmd/play.go b/cli/cmd/play.go index 3fdf7e082..58f0de611 100644 --- a/cli/cmd/play.go +++ b/cli/cmd/play.go @@ -35,6 +35,15 @@ var ( playUsername string // playPassword contains password flag. playPassword string + // playSslKeyFile is a path to a private SSL key file. + playSslKeyFile string + // playSslCertFile is a path to an SSL certificate file. + playSslCertFile string + // playSslCaFile is a path to a trusted certificate authorities (CA) file. + playSslCaFile string + // playSslCiphers is a colon-separated (:) list of SSL cipher suites the + // connection can use. + playSslCiphers string ) // NewPlayCmd creates a new play command. @@ -56,6 +65,14 @@ func NewPlayCmd() *cobra.Command { playCmd.Flags().StringVarP(&playUsername, "username", "u", "", "username") playCmd.Flags().StringVarP(&playPassword, "password", "p", "", "password") + playCmd.Flags().StringVar(&playSslKeyFile, "sslkeyfile", "", + `path to a private SSL key file`) + playCmd.Flags().StringVar(&playSslCertFile, "sslcertfile", "", + `path to an SSL certificate file`) + playCmd.Flags().StringVar(&playSslCaFile, "sslcafile", "", + `path to a trusted certificate authorities (CA) file`) + playCmd.Flags().StringVar(&playSslCiphers, "sslciphers", "", + `colon-separated (:) list of SSL cipher suites the connection`) playCmd.Flags().Uint64Var(&playFlags.To, "to", playFlags.To, "Show operations ending with the given lsn") playCmd.Flags().StringVar(&playFlags.Timestamp, "timestamp", playFlags.Timestamp, @@ -143,6 +160,24 @@ func internalPlayModule(cmdCtx *cmdcontext.CmdCtx, args []string) error { if playPassword != "" { os.Setenv("TT_CLI_PLAY_PASSWORD", playPassword) } + + if playSslCertFile != "" { + os.Setenv("TT_CLI_PLAY_SSL_CERT_FILE", playSslCertFile) + } + if playSslKeyFile != "" { + os.Setenv("TT_CLI_PLAY_SSL_KEY_FILE", playSslKeyFile) + } + if playSslCaFile != "" { + os.Setenv("TT_CLI_PLAY_SSL_CA_FILE", playSslCaFile) + } + if playSslCiphers != "" { + os.Setenv("TT_CLI_PLAY_SSL_CIPHERS", playSslCiphers) + } + if playSslCertFile != "" || playSslKeyFile != "" || + playSslCaFile != "" || playSslCiphers != "" { + os.Setenv("TT_CLI_PLAY_TRANSPORT", "ssl") + } + os.Setenv("TT_CLI_PLAY_SHOW_SYS", strconv.FormatBool(playFlags.ShowSystem)) // List of spaces is passed to lua play script via environment variable in json format. diff --git a/test/integration/play/test_play.py b/test/integration/play/test_play.py index 2a7896d1e..a2d948338 100644 --- a/test/integration/play/test_play.py +++ b/test/integration/play/test_play.py @@ -8,9 +8,12 @@ try_execute_on_instance) from utils import (BINARY_PORT_NAME, TarantoolTestInstance, control_socket, - create_tt_config, initial_snap, initial_xlog, lib_path, - run_command_and_get_output, run_path, - skip_if_cluster_app_unsupported, wait_file) + create_tt_config, get_tarantool_version, initial_snap, + initial_xlog, lib_path, run_command_and_get_output, + run_path, skip_if_cluster_app_unsupported, + skip_if_tarantool_ce, wait_file) + +tarantool_major_version, tarantool_minor_version = get_tarantool_version() # The name of instance config file within this integration tests. # This file should be in /test/integration/play/test_file/. @@ -301,3 +304,46 @@ def test_play_to_cluster_app(tt_cmd): # Stop the Instance. stop_app(tt_cmd, tmpdir, app_name) shutil.rmtree(tmpdir) + + +@pytest.mark.skipif(tarantool_major_version == 1, + reason="skip TLS test for Tarantool 1.0") +def test_play_to_ssl_app(tt_cmd, tmpdir_with_cfg): + skip_if_tarantool_ce() + + tmpdir = tmpdir_with_cfg + # The test application file. + test_app_path = os.path.join(os.path.dirname(__file__), "test_ssl_app") + # The test file. + empty_file = "empty.lua" + empty_file_path = os.path.join(os.path.dirname(__file__), "test_file", empty_file) + # File to play. + test_xlog_path = os.path.join(os.path.dirname(__file__), "test_file", "test.snap") + + # Copy test data into temporary directory. + shutil.copytree(test_app_path, os.path.join(tmpdir, "test_ssl_app")) + shutil.copy(empty_file_path, os.path.join(tmpdir, "test_ssl_app", empty_file)) + + # Start an instance. + start_app(tt_cmd, tmpdir, "test_ssl_app") + try: + # 'ready' file should be created by application. + file = wait_file(os.path.join(tmpdir, "test_ssl_app"), "ready", []) + assert file != "" + + server = "localhost:3013" + # Connect without SSL options. + ret, output = try_execute_on_instance(tt_cmd, tmpdir, server, empty_file) + assert not ret + assert re.search(r" тип unable to establish connection", output) + + cmd = [tt_cmd, "play", "localhost:3013", test_xlog_path, + "--sslkeyfile=test_ssl_app/localhost.key", + "--sslcertfile=test_ssl_app/localhost.crt", + "--sslcafile=test_ssl_app/ca.crt"] + rc, _ = run_command_and_get_output(cmd, cwd=tmpdir) + assert rc == 0 + + finally: + # Stop the Instance. + stop_app(tt_cmd, tmpdir, "test_ssl_app") diff --git a/test/integration/play/test_ssl_app/ca.crt b/test/integration/play/test_ssl_app/ca.crt new file mode 100644 index 000000000..b417f1665 --- /dev/null +++ b/test/integration/play/test_ssl_app/ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDLzCCAhegAwIBAgIUGCC5S+WF/lwRB18qO0cYhpaorugwDQYJKoZIhvcNAQEL +BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y +MzAyMjAwOTMxMDBaFw00NTA3MjYwOTMxMDBaMCcxCzAJBgNVBAYTAlVTMRgwFgYD +VQQDDA9FeGFtcGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDzqL1jXNzPUr5DwnByEjbXkuIlY+olEZ9EBs1ZRdiLaHeLhcpeBonCp9U8 +itbhg9EHSCAb5aH5niTkG8q4lTXKqD7zxsRMzaDwMIYMDsINE+lFbHu2LNxmUhhZ +97Y1tjD/g71ry2W63eRrAzw6USk5ELMJvXLfa7mzG6Vf2cfRqRxADK6EDGZBSgHF +9O93Jofm4TipYNBeArxf4nsWEF0uxUCqUPQAbsJ2Nd0Fc6lo0hMq6cPRct4OYqPl +dlaGA3XPStEveRANE2W+MkMm3uoSlCa5Ye1EUbXVU6q3MPG8z39e29GxZ+5R9LPw +8AsoeQ7ty+TwdOKrN8AFU6/If46NAgMBAAGjUzBRMB0GA1UdDgQWBBTyx+obVLxg +6IMoDMBGwBlK2AoqxDAfBgNVHSMEGDAWgBTyx+obVLxg6IMoDMBGwBlK2AoqxDAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDVdjxWyKwox/KoO+c3 +mw+txeJxAzfBl89KTemG6rNe4LXX+oK7xh9a2WosqEwUypDSsK9fzg/2QTZCuKHS +TZVNSspQdks/dwun2+yHzl/kJ4Ic8CvIsuaUErh1VLui5vmzrgEtgRargCzNIra4 +Bcrx3dcEyQ90ZMPX7ysGYeVFP3it/hVug1XKE0hGRzZLBmZP3DtrLBXCZppkgLc7 +julZOCSI2L+mCj/pTgWAcItFwq9V2ZOvXON5M4cDeAA5krOYyfgymBjwKrnUKDnk +91kcIuOM04msRzSme/I8RHRfc/p1JFv3Ve92dz9wrpPmrCJTLQQYQkyFa7YBPxsZ +QfXv +-----END CERTIFICATE----- diff --git a/test/integration/play/test_ssl_app/generate.sh b/test/integration/play/test_ssl_app/generate.sh new file mode 100755 index 000000000..a7e245dde --- /dev/null +++ b/test/integration/play/test_ssl_app/generate.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -xeuo pipefail +# An example how-to re-generate testing certificates (because usually +# TLS certificates have expiration dates and some day they will expire). +# +# The instruction is valid for: +# +# $ openssl version +# OpenSSL 3.0.7 1 Nov 2022 (Library: OpenSSL 3.0.7 1 Nov 2022) + +cat < domains.ext +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +subjectAltName = @alt_names +[alt_names] +DNS.1 = localhost +IP.1 = 127.0.0.1 +EOF + +openssl req -x509 -nodes -new -sha256 -days 8192 -newkey rsa:2048 -keyout ca.key -out ca.pem -subj "/C=US/CN=Example-Root-CA" +openssl x509 -outform pem -in ca.pem -out ca.crt + +openssl req -new -nodes -newkey rsa:2048 -keyout localhost.key -out localhost.csr -subj "/C=US/ST=YourState/L=YourCity/O=Example-Certificates/CN=localhost" +openssl x509 -req -sha256 -days 8192 -in localhost.csr -CA ca.pem -CAkey ca.key -CAcreateserial -extfile domains.ext -out localhost.crt diff --git a/test/integration/play/test_ssl_app/init.lua b/test/integration/play/test_ssl_app/init.lua new file mode 100644 index 000000000..7107c2d64 --- /dev/null +++ b/test/integration/play/test_ssl_app/init.lua @@ -0,0 +1,23 @@ +local fiber = require('fiber') +local fio = require('fio') + +box.cfg{ listen = { + uri = 'localhost:3013', + params = { + transport = 'ssl', + ssl_key_file = 'localhost.key', + ssl_cert_file = 'localhost.crt', + ssl_ca_file = 'ca.crt' + } +}} + +box.schema.user.create('test', { password = 'password' , if_not_exists = true }) +box.schema.user.grant('guest','read,write,execute,create,drop','universe') +box.schema.user.grant('test','read,write,execute,create,drop','universe') + +fh = fio.open('ready', {'O_WRONLY', 'O_CREAT'}, tonumber('644',8)) +fh:close() + +while true do + fiber.sleep(5) +end diff --git a/test/integration/play/test_ssl_app/localhost.crt b/test/integration/play/test_ssl_app/localhost.crt new file mode 100644 index 000000000..13b45a748 --- /dev/null +++ b/test/integration/play/test_ssl_app/localhost.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIUC9PNBa3RkQa0KOJXOp+WMG98wTowDQYJKoZIhvcNAQEL +BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y +MzAyMjAwOTMxMDBaFw00NTA3MjYwOTMxMDBaMGcxCzAJBgNVBAYTAlVTMRIwEAYD +VQQIDAlZb3VyU3RhdGUxETAPBgNVBAcMCFlvdXJDaXR5MR0wGwYDVQQKDBRFeGFt +cGxlLUNlcnRpZmljYXRlczESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxmgkVi6OEPT6qXd9/IomN6TdXa5VX5PcPsG5 +iFbTVobDsKAi2EpnhZG8w1XnwRyJdWhdxRM3sUUK8xRuSKfbpBfGuWtrwzW1YzTG +K8zH+4KqpWJZ2OLAN+VEjXoO89iw3Ubi0xCLCc+xD++3scOkEgsX7V5RqAtPwsLX +Nzr+yMRTLefPGvpszDzBuq1/NGlKUobnLb6Liqh5yS8E2o1AwrKbY5mrU6YD5Jg+ +VVEdE33U9jAttq6kr7c40joE4SRwmoBdM+PUPC+tqMeYpent0JzBijaspgNJMjF9 +ybWo15jChExpGUnM0uluQF9tegDRYlsXPHCyZiltOt9rgcOSJQIDAQABo3YwdDAf +BgNVHSMEGDAWgBTyx+obVLxg6IMoDMBGwBlK2AoqxDAJBgNVHRMEAjAAMAsGA1Ud +DwQEAwIE8DAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwHQYDVR0OBBYEFN+G +FCIuGca+p1evNT5cMfbQTeDJMA0GCSqGSIb3DQEBCwUAA4IBAQA2cPuIIoCo6P2f +5Khc1ywP9fXsUeZsikrpidFNTZkx3KuuNqrQvemfGbkxMR/TzAmKWa53dvGvVnW2 +8g6+k4PIFspWTAIJbIY2EuMG4E4bvIRUirqxHxMSuhIlQ7b1ppyzVe/H7JwnLrFm +AMpv30P2EUraKs0BNqGWK+FkL8CpCrYhI0VJ4LmBEIgbn2hyLZC9RShiCn4bmYuv +X1TNe78U1nlFmIogtYJop0AcUun1+S6wyItLc8T4QJ/BiqaAK5cRhr17WRv5e07p +l2EjMFwkJNn5R8eO8ZsaO2jCd45n88jnLEpp4aAd7pLI87DZeVQCQOcYggHvz5Hz +Fh6oBthC +-----END CERTIFICATE----- diff --git a/test/integration/play/test_ssl_app/localhost.key b/test/integration/play/test_ssl_app/localhost.key new file mode 100644 index 000000000..7eeb0b6d4 --- /dev/null +++ b/test/integration/play/test_ssl_app/localhost.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDGaCRWLo4Q9Pqp +d338iiY3pN1drlVfk9w+wbmIVtNWhsOwoCLYSmeFkbzDVefBHIl1aF3FEzexRQrz +FG5Ip9ukF8a5a2vDNbVjNMYrzMf7gqqlYlnY4sA35USNeg7z2LDdRuLTEIsJz7EP +77exw6QSCxftXlGoC0/Cwtc3Ov7IxFMt588a+mzMPMG6rX80aUpShuctvouKqHnJ +LwTajUDCsptjmatTpgPkmD5VUR0TfdT2MC22rqSvtzjSOgThJHCagF0z49Q8L62o +x5il6e3QnMGKNqymA0kyMX3JtajXmMKETGkZSczS6W5AX216ANFiWxc8cLJmKW06 +32uBw5IlAgMBAAECggEAJtXjpurd6/vHxLwa8P+pk2K14cxp8ZdjmPUad9Fm9JzU +WRI/P87wjHiGVkXOY0JOtaiEEjs8v3ogNoxdOeOBXpE42LpqEX/FzXFbCN/AlT5y +YITryUQ5E7fQv1CQ9LIJjIZ/h4jJblY09kWZ0zXUO6PoPxIjMZ1lM14n+iuWC33p +1XyU1iR9H0YDbYgx5oikfB8EXpaKITxu9V5gQ/nHwOm57d9o7Y5lc1wt7IxN7yDa +0CfTN/l9l7l6hnchqX2uG+zDnvPCtZFQneOH4+UBwBXikGcF59bkysn6fW9CwWkm +8B0qiZ68+gpbiAQRhb48JtVobqfOE5oNO0J45IbXUQKBgQDvlZTvYOby6cHVyg57 +C0SNJP/47ivO0yX6jvknWFM1F+XHTk/vTZWYjduYqHqnOdEcLcjg28EfdKIcprjX +SqnBUl7bsx/wywie5sJ9CncV7faeIw8j2f6edaOA77cty9HpwOc2WJ8/H8qTEDnm +HqbpXHvpRtd6D2nJT5Gn21MLSwKBgQDUAEm6PAFe1CD7/nA/+a0uHZJqbLJA/fHF +CeOxPQ6gP5CpyKEB7nMGAV78grmsdlD2tQh66x5DMOIi9MqI+bce2HpH3+3sWx7K +wzbiJPhHTL6DEWPqhqJPORtVOt386JNaNvr6Qq0izEdgShkLl2J9WtrNxL2bGD4l +m/aXhtWCTwKBgGJ9Dj2dizMejxVQu8UvK54OML/nQNEEEd+/eIMJFyODUG0vL0MD +lNSitDw8PjeSV/kKhUKSdAB3VNEMZH30bnZPYzlTmHTHMiMIX7lBXRUBvtjhNq8Z +RUdkurMdWCMWX5OFPkckBUrQydjM2dBUl27lGvcZrSi7P1SHRixHyAqjAoGATbat +UC+e8Pwh+z4SN+F2smj0uz6NOXXdorU1WktfiS7EAPkizGp0j8cA4t+o4KeellFW +gnid51OMEfRaKkwf7Ja+fIqB1Rqx9vIItG2I9doUHEfLsLUZ2qC8fEnQBl3bZj6x +UfwPK6pmn82J0M31tK4Rd0yflLMWVQMPKgyrR9ECgYAylXMnOH3XwtbzvO6b3/oX +38zshS41HaakTvKqQDzNqyyWmx/kUTba9n2Bf1uHTCx1AklPs6/9hgbefKpWL1bb +H0RcJ/jx+vND6KfONQOGSj+g0zpX81c/Nv3meNmVX6yc3lxDvE9Rl6LNdoplPTbg +niex+WQcCjnAau5lC0haBw== +-----END PRIVATE KEY-----