diff --git a/.drone.star b/.drone.star index 087d37cbe..4b9a72e9d 100644 --- a/.drone.star +++ b/.drone.star @@ -75,6 +75,27 @@ def _find_package_b2_command(source_dir, generator): '--generator="{}" '.format(generator) +def _make_entrypoint(db): + if db.startswith('mysql:'): + # MySQL generic. Sanitize UNIX socket permissions and launch the server with the adequate TLS files + res = "chown -R mysql:mysql /var/run/mysqld && /usr/local/bin/docker-entrypoint.sh mysqld " + \ + "--ssl-ca=/tls/ca-cert.pem " + \ + "--ssl-cert=/tls/server-cert.pem " + \ + "--ssl-key=/tls/server-key.pem " + if db.startswith('mysql:8.'): + # v8.x needs this flag to enable mysql_native_password + res += "--mysql-native-password=ON" + else: + # MariaDB changed the default socket path, so we provide it explicitly + res = "chown -R mysql:mysql /var/run/mysqld && /usr/local/bin/docker-entrypoint.sh mariadbd " + \ + "--ssl-ca=/tls/ca-cert.pem " + \ + "--ssl-cert=/tls/server-cert.pem " + \ + "--ssl-key=/tls/server-key.pem " + \ + "--socket=/var/run/mysqld/mysqld.sock" + + return res + + def _pipeline( name, image, @@ -85,6 +106,20 @@ def _pipeline( disable_aslr=False ): steps = [] + + # Volumes, common to all steps + volumes = [ + { + "name": "mysql-socket", + "path": "/var/run/mysqld" + }, + { + "name": "tls-certificates", + "path": "/tls" + } + ] if db != None else [] + + # Disable ASLR if disable_aslr: steps.append({ "name": "Disable ASLR", @@ -93,15 +128,60 @@ def _pipeline( "privileged": True, "commands": ["echo 0 | tee /proc/sys/kernel/randomize_va_space"] }) + + # Set up the database and certificates + cert_dir = "C:\\\\ssl\\\\" if os == "windows" else "/tls/" + if os == "windows": + # Generate certificates + steps.append({ + "name": "Generate certificates", + "image": image, + "pull": "if-not-exists", + "commands": [ + "python tools/ci/gen-certificates.py {}".format(cert_dir) + ] + }) + + elif db != None: + # Generate certificates + steps.append({ + "name": "Generate certificates", + "image": image, + "pull": "if-not-exists", + "volumes": volumes, + "commands": [ + "python tools/ci/gen-certificates.py {}".format(cert_dir) + ] + }) + + # Database step + steps.append({ + "name": "mysql", + "image": db, + "pull": "if-not-exists", + "detach": True, + "environment": { + "MYSQL_ALLOW_EMPTY_PASSWORD": "1", + "MYSQL_ROOT_PASSWORD": "" + }, + "entrypoint": [ + "/bin/bash", + "-c", + _make_entrypoint(db) + ], + "volumes": volumes + }) + + # Run the build steps.append({ "name": "Build and run", "image": image, "pull": "if-not-exists", "privileged": arch == "arm64", # TSAN tests fail otherwise (personality syscall) - "volumes":[{ - "name": "mysql-socket", - "path": "/var/run/mysqld" - }] if db != None else [], + "volumes": volumes, + "environment": { + "BOOST_MYSQL_CA_CERTIFICATE": cert_dir + "ca-cert.pem" + }, "commands": [command] }) @@ -119,18 +199,16 @@ def _pipeline( }, "node": {}, "steps": steps, - "services": [{ - "name": "mysql", - "image": "ghcr.io/anarthal/cpp-ci-containers/{}".format(db), - "volumes": [{ + "volumes": [ + { "name": "mysql-socket", - "path": "/var/run/mysqld" - }] - }] if db != None else [], - "volumes": [{ - "name": "mysql-socket", - "temp": {} - }] if db != None else [] + "temp": {} + }, + { + "name": "tls-certificates", + "temp": {} + } + ] } @@ -149,7 +227,7 @@ def linux_b2( valgrind=0, arch='amd64', fail_if_no_openssl=1, - db='mysql-8_4_1:1', + db='mysql:8.4.1', ): command = _b2_command( source_dir='$(pwd)', @@ -201,7 +279,7 @@ def windows_b2( def linux_cmake( name, image, - db='mysql-8_4_1:1', + db='mysql:8.4.1', build_shared_libs=0, cmake_build_type='Debug', cxxstd='20', @@ -270,7 +348,7 @@ def bench(name): '--server-host=mysql ' + \ '--connection-pool-iters=1 ' + \ '--protocol-iters=1 ' - return _pipeline(name=name, image=_image('build-bench:1'), os='linux', command=command, db='mysql-8_4_1:1') + return _pipeline(name=name, image=_image('build-bench:1'), os='linux', command=command, db='mysql:8.4.1') def docs(name): @@ -286,8 +364,8 @@ def docs(name): def main(ctx): return [ # CMake Linux - linux_cmake('Linux CMake MySQL 5.x', _image('build-gcc14:1'), db='mysql-5_7_41:1', build_shared_libs=0), - linux_cmake('Linux CMake MariaDB', _image('build-gcc14:1'), db='mariadb-11_4_2:1', build_shared_libs=1), + linux_cmake('Linux CMake MySQL 5.x', _image('build-gcc14:1'), db='mysql:5.7.41', build_shared_libs=0), + linux_cmake('Linux CMake MariaDB', _image('build-gcc14:1'), db='mariadb:11.4.2', build_shared_libs=1), linux_cmake('Linux CMake cmake 3.8', _image('build-cmake3_8:3'), cxxstd='11', install_test=0), linux_cmake('Linux CMake gcc Release', _image('build-gcc14:1'), cmake_build_type='Release'), linux_cmake('Linux CMake gcc MinSizeRel', _image('build-gcc14:1'), cmake_build_type='MinSizeRel'), @@ -311,14 +389,14 @@ def main(ctx): # Ubuntu 24.04: gcc13, clang 18 linux_b2('Linux B2 clang-4', _image('build-clang4:1'), toolset='clang-4', cxxstd='14'), linux_b2('Linux B2 clang-5-honly-dbg', _image('build-clang5:1'), toolset='clang-5', cxxstd='14', separate_compilation=0), - linux_b2('Linux B2 clang-6', _image('build-clang5:1'), toolset='clang-5', cxxstd='14'), + linux_b2('Linux B2 clang-6', _image('build-clang6:1'), toolset='clang-6', cxxstd='14'), linux_b2('Linux B2 clang-7', _image('build-clang7:2'), toolset='clang-7', cxxstd='14,17'), linux_b2('Linux B2 clang-8', _image('build-clang8:2'), toolset='clang-8', cxxstd='14', variant='debug', address_sanitizer=1, undefined_sanitizer=1), linux_b2('Linux B2 clang-9', _image('build-clang9:2'), toolset='clang-9', cxxstd='17', variant='release'), linux_b2('Linux B2 clang-10', _image('build-clang10:2'), toolset='clang-10', cxxstd='17,20', variant='debug'), linux_b2('Linux B2 clang-11', _image('build-clang11:2'), toolset='clang-11', cxxstd='20'), linux_b2('Linux B2 clang-12', _image('build-clang12:2'), toolset='clang-12', cxxstd='20', variant='debug', stdlib='libc++', address_sanitizer=1, undefined_sanitizer=1), - linux_b2('Linux B2 clang-13', _image('build-clang13:1'), toolset='clang-13', cxxstd='20', db='mysql-9_4_0:1'), + linux_b2('Linux B2 clang-13', _image('build-clang13:1'), toolset='clang-13', cxxstd='20', db='mysql:9.4.0'), linux_b2('Linux B2 clang-14', _image('build-clang14:1'), toolset='clang-14', cxxstd='20', variant='debug'), linux_b2('Linux B2 clang-15', _image('build-clang15:1'), toolset='clang-15', cxxstd='20', variant='debug'), linux_b2('Linux B2 clang-16', _image('build-clang16:1'), toolset='clang-16', cxxstd='20', variant='debug', address_sanitizer=1, undefined_sanitizer=1), @@ -338,7 +416,7 @@ def main(ctx): linux_b2('Linux B2 gcc-10', _image('build-gcc10:1'), toolset='gcc-10', cxxstd='17'), linux_b2('Linux B2 gcc-11', _image('build-gcc11:1'), toolset='gcc-11', cxxstd='20'), linux_b2('Linux B2 gcc-12', _image('build-gcc12:1'), toolset='gcc-12', cxxstd='20,23', variant='debug'), - linux_b2('Linux B2 gcc-13', _image('build-gcc13:1'), toolset='gcc-13', cxxstd='20', db='mysql-9_4_0:1'), + linux_b2('Linux B2 gcc-13', _image('build-gcc13:1'), toolset='gcc-13', cxxstd='20', db='mysql:9.4.0'), linux_b2('Linux B2 gcc-14', _image('build-gcc14:1'), toolset='gcc-14', cxxstd='23'), linux_b2('Linux B2 gcc-15', _image('build-gcc15:1'), toolset='gcc-15', cxxstd='23'), linux_b2('Linux B2 gcc-sanit', _image('build-gcc14:1'), toolset='gcc-14', cxxstd='23', variant='debug', address_sanitizer=1, undefined_sanitizer=1), diff --git a/.github/workflows/build-code.yml b/.github/workflows/build-code.yml index 4446cb9a4..f3d58a742 100644 --- a/.github/workflows/build-code.yml +++ b/.github/workflows/build-code.yml @@ -10,16 +10,15 @@ name: Build on: push: branches: [develop, master] - tags: ['*'] + tags: ["*"] pull_request: workflow_dispatch: - jobs: osx: runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: | unlink /usr/local/bin/python || echo "/usr/local/bin/python not found" ln -s /usr/local/bin/python3 /usr/local/bin/python diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1b7411701..4d18d7b5c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,27 +17,26 @@ on: jobs: coverage: runs-on: ubuntu-latest - container: - image: ghcr.io/anarthal/cpp-ci-containers/build-gcc14-lcov:1 - volumes: - - /var/run/mysqld:/var/run/mysqld - services: - mysql: - image: ghcr.io/anarthal/cpp-ci-containers/mysql-8_4_1:1 - ports: - - 3306:3306 - volumes: - - /var/run/mysqld:/var/run/mysqld steps: - name: Fetch code - uses: actions/checkout@v4 + uses: actions/checkout@v6 + + - name: Generate certificates + run: python tools/ci/gen-certificates.py /tmp/mysql-tls + + - name: Start containers + uses: hoverkraft-tech/compose-action@v2.6.0 + with: + compose-file: ./tools/ci/docker-compose.yml + env: + BUILDER_IMAGE: ghcr.io/anarthal/cpp-ci-containers/build-gcc14-lcov:1 - name: Build code run: | - python tools/ci/main.py \ - --source-dir=$(pwd) \ + docker exec builder python /boost-mysql/tools/ci/main.py \ + --source-dir=/boost-mysql \ b2 \ - --server-host=mysql \ + --server-host=localhost \ --toolset=gcc \ --cxxstd=20 \ --variant=debug \ @@ -47,29 +46,29 @@ jobs: - name: Generate coverage reports shell: bash run: | - cd ~/boost-root/bin.v2 - lcov \ + docker exec builder lcov \ --rc branch_coverage=0 \ --rc geninfo_unexecuted_blocks=1 \ --ignore-errors mismatch \ --gcov-tool gcov-14 \ - --directory . \ + --directory /root/boost-root/bin.v2 \ --capture \ --output-file all.info - lcov \ + docker exec builder lcov \ --rc branch_coverage=0 \ --output-file coverage.info \ --extract all.info '*/boost/mysql*' - sed "s|^SF:$HOME/boost-root/|SF:include/|g" coverage.info > $GITHUB_WORKSPACE/coverage.info + docker exec builder sed -i "s|^SF:/root/boost-root/|SF:include/|g" coverage.info + docker exec builder mv coverage.info /boost-mysql/coverage.info - name: Upload coverage reports - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v6 with: verbose: true fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} plugins: noop # Don't run gcov again, codecov doesn't know about the filtering we perform - file: coverage.info + files: coverage.info disable_search: true # Don't upload unwanted files disable_file_fixes: true # Default fixes make reports unusable diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index e9aa8ce95..4668e320e 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -10,7 +10,7 @@ name: fuzz on: push: branches: [develop, master] - tags: ['*'] + tags: ["*"] pull_request: workflow_dispatch: schedule: @@ -19,43 +19,46 @@ on: jobs: fuzz: runs-on: ubuntu-latest - container: - image: ghcr.io/anarthal/cpp-ci-containers/build-clang18:1 - volumes: - - /var/run/mysqld:/var/run/mysqld - services: - mysql: - image: ghcr.io/anarthal/cpp-ci-containers/mysql-8_4_1:1 - ports: - - 3306:3306 - volumes: - - /var/run/mysqld:/var/run/mysqld steps: - name: Fetch code - uses: actions/checkout@v4 + uses: actions/checkout@v6 + + - name: Generate certificates + run: python tools/ci/gen-certificates.py /tmp/mysql-tls + + - name: Start containers + uses: hoverkraft-tech/compose-action@v2.6.0 + with: + compose-file: ./tools/ci/docker-compose.yml + env: + BUILDER_IMAGE: ghcr.io/anarthal/cpp-ci-containers/build-clang18:1 - name: Restore corpus - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: /tmp/corpus.tar.gz key: corpus-${{ github.run_id }} restore-keys: corpus- - + # Note: this will take care of using the corpus and updating it - name: Build and run the fuzzer run: | - python tools/ci/main.py \ - --source-dir=$(pwd) \ - fuzz \ - --server-host=mysql + docker exec builder python /boost-mysql/tools/ci/main.py \ + --source-dir=/boost-mysql \ + fuzz + + - name: Copy crashes from container + if: always() + run: | + docker exec builder bash -c 'cp /root/boost-root/crash-* /root/boost-root/leak-* /root/boost-root/timeout-* /boost-mysql/ || true' - name: Archive any crashes as an artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: always() with: name: crashes path: | - ~/boost-root/crash-* - ~/boost-root/leak-* - ~/boost-root/timeout-* + crash-* + leak-* + timeout-* if-no-files-found: ignore diff --git a/example/2_simple/tls_certificate_verification.cpp b/example/2_simple/tls_certificate_verification.cpp index 893c231e5..4482cae15 100644 --- a/example/2_simple/tls_certificate_verification.cpp +++ b/example/2_simple/tls_certificate_verification.cpp @@ -30,7 +30,6 @@ #include #include -#include #include #include #include @@ -42,35 +41,12 @@ namespace mysql = boost::mysql; namespace asio = boost::asio; -// The CA file that signed the server's certificate -constexpr const char CA_PEM[] = R"%(-----BEGIN CERTIFICATE----- -MIIDZzCCAk+gAwIBAgIUWznm2UoxXw3j7HCcp9PpiayTvFQwDQYJKoZIhvcNAQEL -BQAwQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAMBgNVBAoM -BW15c3FsMQ4wDAYDVQQDDAVteXNxbDAgFw0yMDA0MDQxNDMwMjNaGA8zMDE5MDgw -NjE0MzAyM1owQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAM -BgNVBAoMBW15c3FsMQ4wDAYDVQQDDAVteXNxbDCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAN0WYdvsDb+a0TxOGPejcwZT0zvTrf921mmDUlrLN1Z0hJ/S -ydgQCSD7Q+6za4lTFZCXcvs52xvvS2gfC0yXyYLCT/jA4RQRxuF+/+w1gDWEbGk0 -KzEpsBuKrEIvEaVdoS78SxInnW/aegshdrRRocp4JQ6KHsZgkLTxSwPfYSUmMUo0 -cRO0Q/ak3VK8NP13A6ZFvZjrBxjS3cSw9HqilgADcyj1D4EokvfI1C9LrgwgLlZC -XVkjjBqqoMXGGlnXOEK+pm8bU68HM/QvMBkb1Amo8pioNaaYgqJUCP0Ch0iu1nUU -HtsWt6emXv0jANgIW0oga7xcT4MDGN/M+IRWLTECAwEAAaNTMFEwHQYDVR0OBBYE -FNxhaGwf5ePPhzK7yOAKD3VF6wm2MB8GA1UdIwQYMBaAFNxhaGwf5ePPhzK7yOAK -D3VF6wm2MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAoeJCAX -IDCFoAaZoQ1niI6Ac/cds8G8It0UCcFGSg+HrZ0YujJxWIruRCUG60Q2OAbEvn0+ -uRpTm+4tV1Wt92WFeuRyqkomozx0g4CyfsxGX/x8mLhKPFK/7K9iTXM4/t+xQC4f -J+iRmPVsMKQ8YsHYiWVhlOMH9XJQiqERCB2kOKJCH6xkaF2k0GbM2sGgbS7Z6lrd -fsFTOIVx0VxLVsZnWX3byE9ghnDR5jn18u30Cpb/R/ShxNUGIHqRa4DkM5la6uZX -W1fpSW11JBSUv4WnOO0C2rlIu7UJWOROqZZ0OsybPRGGwagcyff2qVRuI2XFvAMk -OzBrmpfHEhF6NDU= ------END CERTIFICATE----- -)%"; - // The main coroutine asio::awaitable coro_main( std::string_view server_hostname, std::string_view username, - std::string_view password + std::string_view password, + std::string_view ca_cert_path ) { //[section_connection_establishment_tls_options @@ -83,11 +59,9 @@ asio::awaitable coro_main( // Load a trusted CA, which was used to sign the server's certificate. // This will allow the signature verification to succeed in our example. - // You will have to run your MySQL server with the test certificates - // located under $BOOST_MYSQL_ROOT/tools/ssl/ // If you want to use your system's trusted CAs, use // ssl::context::set_default_verify_paths() instead of this function. - ssl_ctx.add_certificate_authority(asio::buffer(CA_PEM)); + ssl_ctx.load_verify_file(std::string(ca_cert_path)); // We expect the server certificate's common name to be "mysql". // If it's not, the certificate will be rejected and handshake or connect will fail. @@ -123,9 +97,9 @@ asio::awaitable coro_main( void main_impl(int argc, char** argv) { - if (argc != 4) + if (argc != 5) { - std::cerr << "Usage: " << argv[0] << " \n"; + std::cerr << "Usage: " << argv[0] << " \n"; exit(1); } @@ -135,7 +109,7 @@ void main_impl(int argc, char** argv) // Launch our coroutine asio::co_spawn( ctx, - [=] { return coro_main(argv[3], argv[1], argv[2]); }, + [=] { return coro_main(argv[3], argv[1], argv[2], argv[4]); }, // If any exception is thrown in the coroutine body, rethrow it. [](std::exception_ptr ptr) { if (ptr) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index de38d85f7..df6f9478c 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -15,6 +15,12 @@ else() set(SERVER_HOST "127.0.0.1") endif() +if(DEFINED ENV{BOOST_MYSQL_CA_CERTIFICATE}) + set(CA_CERTIFICATE $ENV{BOOST_MYSQL_CA_CERTIFICATE}) +else() + set(CA_CERTIFICATE "/opt/ci-tls-mysql/ca-cert.pem") +endif() + add_library(boost_mysql_examples_common INTERFACE) target_link_libraries( boost_mysql_examples_common @@ -85,7 +91,7 @@ add_simple_example(batch_inserts_generic ARGS ${SERVER_HOST} PYTHON_RUNN add_simple_example(patch_updates ARGS ${SERVER_HOST} PYTHON_RUNNER run_patch_updates.py) add_simple_example(dynamic_filters ARGS ${SERVER_HOST} PYTHON_RUNNER run_dynamic_filters.py) add_simple_example(disable_tls ARGS ${REGULAR_ARGS}) -add_simple_example(tls_certificate_verification ARGS ${REGULAR_ARGS}) +add_simple_example(tls_certificate_verification ARGS ${REGULAR_ARGS} ${CA_CERTIFICATE}) add_simple_example(metadata ARGS ${REGULAR_ARGS}) add_simple_example(prepared_statements ARGS ${REGULAR_ARGS} "HGS") add_simple_example(pipeline ARGS ${REGULAR_ARGS} "HGS") diff --git a/example/Jamfile b/example/Jamfile index 56d641c20..fdddf58c5 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -17,6 +17,12 @@ if $(hostname) = "" hostname = "127.0.0.1" ; } +local ca_certificate = [ os.environ BOOST_MYSQL_CA_CERTIFICATE ] ; +if $(ca_certificate) = "" +{ + ca_certificate = "/opt/ci-tls-mysql/ca-cert.pem" ; +} + # Builds and run an example rule run_example ( example_name : @@ -86,7 +92,7 @@ run_example dynamic_filters : 2_simple/dynamic_filters.cpp run_example disable_tls : 2_simple/disable_tls.cpp : $(regular_args) ; run_example tls_certificate_verification : 2_simple/tls_certificate_verification.cpp - : $(regular_args) ; + : $(regular_args) $(ca_certificate) ; run_example metadata : 2_simple/metadata.cpp : $(regular_args) ; run_example prepared_statements : 2_simple/prepared_statements.cpp diff --git a/test/integration/include/test_integration/server_ca.hpp b/test/integration/include/test_integration/server_ca.hpp deleted file mode 100644 index 7ced5d3e7..000000000 --- a/test/integration/include/test_integration/server_ca.hpp +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_SERVER_CA_HPP -#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_SERVER_CA_HPP - -namespace boost { -namespace mysql { -namespace test { - -// The CA file that signed the server's certificate -constexpr const char CA_PEM[] = R"%(-----BEGIN CERTIFICATE----- -MIIDZzCCAk+gAwIBAgIUWznm2UoxXw3j7HCcp9PpiayTvFQwDQYJKoZIhvcNAQEL -BQAwQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAMBgNVBAoM -BW15c3FsMQ4wDAYDVQQDDAVteXNxbDAgFw0yMDA0MDQxNDMwMjNaGA8zMDE5MDgw -NjE0MzAyM1owQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAM -BgNVBAoMBW15c3FsMQ4wDAYDVQQDDAVteXNxbDCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAN0WYdvsDb+a0TxOGPejcwZT0zvTrf921mmDUlrLN1Z0hJ/S -ydgQCSD7Q+6za4lTFZCXcvs52xvvS2gfC0yXyYLCT/jA4RQRxuF+/+w1gDWEbGk0 -KzEpsBuKrEIvEaVdoS78SxInnW/aegshdrRRocp4JQ6KHsZgkLTxSwPfYSUmMUo0 -cRO0Q/ak3VK8NP13A6ZFvZjrBxjS3cSw9HqilgADcyj1D4EokvfI1C9LrgwgLlZC -XVkjjBqqoMXGGlnXOEK+pm8bU68HM/QvMBkb1Amo8pioNaaYgqJUCP0Ch0iu1nUU -HtsWt6emXv0jANgIW0oga7xcT4MDGN/M+IRWLTECAwEAAaNTMFEwHQYDVR0OBBYE -FNxhaGwf5ePPhzK7yOAKD3VF6wm2MB8GA1UdIwQYMBaAFNxhaGwf5ePPhzK7yOAK -D3VF6wm2MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAoeJCAX -IDCFoAaZoQ1niI6Ac/cds8G8It0UCcFGSg+HrZ0YujJxWIruRCUG60Q2OAbEvn0+ -uRpTm+4tV1Wt92WFeuRyqkomozx0g4CyfsxGX/x8mLhKPFK/7K9iTXM4/t+xQC4f -J+iRmPVsMKQ8YsHYiWVhlOMH9XJQiqERCB2kOKJCH6xkaF2k0GbM2sGgbS7Z6lrd -fsFTOIVx0VxLVsZnWX3byE9ghnDR5jn18u30Cpb/R/ShxNUGIHqRa4DkM5la6uZX -W1fpSW11JBSUv4WnOO0C2rlIu7UJWOROqZZ0OsybPRGGwagcyff2qVRuI2XFvAMk -OzBrmpfHEhF6NDU= ------END CERTIFICATE----- -)%"; - -} // namespace test -} // namespace mysql -} // namespace boost - -#endif diff --git a/test/integration/test/handshake.cpp b/test/integration/test/handshake.cpp index 819f1569e..193809f11 100644 --- a/test/integration/test/handshake.cpp +++ b/test/integration/test/handshake.cpp @@ -26,8 +26,11 @@ #include #include +#include +#include #include #include +#include #include #include @@ -39,7 +42,6 @@ #include "test_common/source_location.hpp" #include "test_integration/any_connection_fixture.hpp" #include "test_integration/connect_params_builder.hpp" -#include "test_integration/server_ca.hpp" #include "test_integration/server_features.hpp" #include "test_integration/tcp_connection_fixture.hpp" @@ -51,6 +53,22 @@ namespace data = boost::unit_test::data; namespace { +// Retrieves the CA certificate that signed the server's certificate +std::string read_ca_pem() +{ + auto path = safe_getenv("BOOST_MYSQL_CA_CERTIFICATE", "/opt/ci-tls-mysql/ca-cert.pem"); + std::ifstream ifs(path); + if (!ifs) + throw std::system_error(errno, std::system_category(), "Failed to open " + std::string(path)); + return std::string(std::istreambuf_iterator(ifs), std::istreambuf_iterator()); +} + +string_view get_ca_pem() +{ + static std::string res = read_ca_pem(); + return res; +} + BOOST_AUTO_TEST_SUITE(test_handshake) // Handshake is the most convoluted part of MySQL protocol, @@ -362,7 +380,7 @@ BOOST_AUTO_TEST_CASE(certificate_valid) // Setup asio::ssl::context ssl_ctx(asio::ssl::context::tls_client); ssl_ctx.set_verify_mode(boost::asio::ssl::verify_peer); - ssl_ctx.add_certificate_authority(boost::asio::buffer(CA_PEM)); + ssl_ctx.add_certificate_authority(boost::asio::buffer(get_ca_pem())); any_connection_fixture fix(ssl_ctx); // Connect works @@ -390,7 +408,7 @@ BOOST_AUTO_TEST_CASE(custom_certificate_verification_success) // Setup asio::ssl::context ssl_ctx(asio::ssl::context::tls_client); ssl_ctx.set_verify_mode(boost::asio::ssl::verify_peer); - ssl_ctx.add_certificate_authority(boost::asio::buffer(CA_PEM)); + ssl_ctx.add_certificate_authority(boost::asio::buffer(get_ca_pem())); ssl_ctx.set_verify_callback(boost::asio::ssl::host_name_verification("mysql")); any_connection_fixture fix(ssl_ctx); @@ -405,7 +423,7 @@ BOOST_AUTO_TEST_CASE(custom_certificate_verification_error) // Setup asio::ssl::context ssl_ctx(asio::ssl::context::tls_client); ssl_ctx.set_verify_mode(boost::asio::ssl::verify_peer); - ssl_ctx.add_certificate_authority(boost::asio::buffer(CA_PEM)); + ssl_ctx.add_certificate_authority(boost::asio::buffer(get_ca_pem())); ssl_ctx.set_verify_callback(boost::asio::ssl::host_name_verification("host.name")); any_connection_fixture fix(ssl_ctx); @@ -422,7 +440,7 @@ BOOST_FIXTURE_TEST_CASE(tcp_ssl_connection_, io_context_fixture) // Setup asio::ssl::context ssl_ctx(asio::ssl::context::tls_client); ssl_ctx.set_verify_mode(boost::asio::ssl::verify_peer); - ssl_ctx.add_certificate_authority(boost::asio::buffer(CA_PEM)); + ssl_ctx.add_certificate_authority(boost::asio::buffer(get_ca_pem())); ssl_ctx.set_verify_callback(boost::asio::ssl::host_name_verification("host.name")); tcp_ssl_connection conn(ctx, ssl_ctx); auto params = connect_params_builder().build_hparams(); diff --git a/tools/ci/docker-compose.yml b/tools/ci/docker-compose.yml new file mode 100644 index 000000000..7a81dc3de --- /dev/null +++ b/tools/ci/docker-compose.yml @@ -0,0 +1,36 @@ +# +# Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +# Used in GitHub Actions CIs + +services: + mysql: + image: mysql:8.4.1 + network_mode: host + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: "1" + MYSQL_ROOT_PASSWORD: "" + volumes: + - /tmp/mysql-tls:/tls + - /var/run/mysqld:/var/run/mysqld + command: > + /bin/bash -c 'chown -R mysql:mysql /var/run/mysqld && \ + /usr/local/bin/docker-entrypoint.sh mysqld \ + --mysql-native-password=ON \ + --ssl-ca=/tls/ca-cert.pem \ + --ssl-cert=/tls/server-cert.pem \ + --ssl-key=/tls/server-key.pem + ' + builder: + container_name: builder + image: ${BUILDER_IMAGE} + network_mode: host + tty: true + volumes: + - ../../:/boost-mysql + - /tmp/mysql-tls:/opt/ci-tls-mysql + - /var/run/mysqld:/var/run/mysqld diff --git a/tools/ci/gen-certificates.py b/tools/ci/gen-certificates.py new file mode 100755 index 000000000..b95f2cb48 --- /dev/null +++ b/tools/ci/gen-certificates.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See +# accompanying file LICENSE.txt) +# + +# Generates the ca and certificates used for CI testing. +# Usage: python gen-certificates.py [output-dir] + +import os +import subprocess +import sys +import stat + + +def _run_openssl(*args: str) -> None: + print(' +', *args) + subprocess.run(['openssl', *args], check=True) + + +def main() -> None: + output_dir = sys.argv[1] if len(sys.argv) > 1 else '/opt/ci-tls-mysql' + os.makedirs(output_dir, exist_ok=True) + os.chdir(output_dir) + + ca_key = os.path.join(output_dir, 'ca-key.pem') + ca_crt = os.path.join(output_dir, 'ca-cert.pem') + server_key = os.path.join(output_dir, 'server-key.pem') + server_csr = os.path.join(output_dir, 'server.csr') + server_crt = os.path.join(output_dir, 'server-cert.pem') + + # CA private key + _run_openssl('genpkey', '-algorithm', 'RSA', '-out', ca_key, '-pkeyopt', 'rsa_keygen_bits:2048') + + # CA certificate + _run_openssl( + 'req', '-x509', '-new', '-nodes', '-key', ca_key, '-sha256', + '-days', '20000', '-out', ca_crt, + '-subj', '/C=ES/O=Boost.MySQL CI CA/OU=IT/CN=boost-mysql-ci-ca', + ) + + # Server private key + _run_openssl('genpkey', '-algorithm', 'RSA', '-out', server_key, '-pkeyopt', 'rsa_keygen_bits:2048') + + # Server certificate + _run_openssl( + 'req', '-new', '-key', server_key, '-out', server_csr, + '-subj', '/C=ES/O=Boost.MySQL CI CA/OU=IT/CN=mysql', + ) + _run_openssl( + 'x509', '-req', '-in', server_csr, '-CA', ca_crt, '-CAkey', ca_key, + '-CAcreateserial', '-out', server_crt, '-days', '20000', '-sha256', + ) + os.remove(server_csr) + os.remove(ca_key) + + # Required when running with Docker because of mismatched user IDs + read_only = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH # 444 + for name in os.listdir(output_dir): + os.chmod(os.path.join(output_dir, name), read_only) + + +if __name__ == '__main__': + main() diff --git a/tools/osx-ci.cnf b/tools/osx-ci.cnf index 146021e23..11d9d493c 100644 --- a/tools/osx-ci.cnf +++ b/tools/osx-ci.cnf @@ -7,9 +7,9 @@ [mysqld] socket=/var/run/mysqld/mysqld.sock -ssl-ca=/etc/ssl/certs/mysql/ca-cert.pem -ssl-cert=/etc/ssl/certs/mysql/server-cert.pem -ssl-key=/etc/ssl/certs/mysql/server-key.pem +ssl-ca=/tmp/mysql-tls/ca-cert.pem +ssl-cert=/tmp/mysql-tls/server-cert.pem +ssl-key=/tmp/mysql-tls/server-key.pem [client] socket=/var/run/mysqld/mysqld.sock \ No newline at end of file diff --git a/tools/setup_db_osx.sh b/tools/setup_db_osx.sh index 6ec0b1436..e05eaae53 100644 --- a/tools/setup_db_osx.sh +++ b/tools/setup_db_osx.sh @@ -13,10 +13,13 @@ brew install mysql@8.0 export PATH="/opt/homebrew/opt/mysql@8.0/bin:$PATH" +# Generate the certificates +mkdir -p /tmp/mysql-tls +python tools/ci/gen-certificates.py /tmp/mysql-tls +export BOOST_MYSQL_CA_CERTIFICATE=/tmp/mysql-tls/ca-cert.pem + # Copy config files and set up paths cp tools/osx-ci.cnf ~/.my.cnf -sudo mkdir -p /etc/ssl/certs/mysql/ -sudo cp tools/ssl/*.pem /etc/ssl/certs/mysql/ sudo mkdir -p /var/run/mysqld/ sudo chmod 777 /var/run/mysqld/ diff --git a/tools/ssl/ca-cert.pem b/tools/ssl/ca-cert.pem deleted file mode 100644 index ee0018c55..000000000 --- a/tools/ssl/ca-cert.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDZzCCAk+gAwIBAgIUWznm2UoxXw3j7HCcp9PpiayTvFQwDQYJKoZIhvcNAQEL -BQAwQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAMBgNVBAoM -BW15c3FsMQ4wDAYDVQQDDAVteXNxbDAgFw0yMDA0MDQxNDMwMjNaGA8zMDE5MDgw -NjE0MzAyM1owQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAM -BgNVBAoMBW15c3FsMQ4wDAYDVQQDDAVteXNxbDCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAN0WYdvsDb+a0TxOGPejcwZT0zvTrf921mmDUlrLN1Z0hJ/S -ydgQCSD7Q+6za4lTFZCXcvs52xvvS2gfC0yXyYLCT/jA4RQRxuF+/+w1gDWEbGk0 -KzEpsBuKrEIvEaVdoS78SxInnW/aegshdrRRocp4JQ6KHsZgkLTxSwPfYSUmMUo0 -cRO0Q/ak3VK8NP13A6ZFvZjrBxjS3cSw9HqilgADcyj1D4EokvfI1C9LrgwgLlZC -XVkjjBqqoMXGGlnXOEK+pm8bU68HM/QvMBkb1Amo8pioNaaYgqJUCP0Ch0iu1nUU -HtsWt6emXv0jANgIW0oga7xcT4MDGN/M+IRWLTECAwEAAaNTMFEwHQYDVR0OBBYE -FNxhaGwf5ePPhzK7yOAKD3VF6wm2MB8GA1UdIwQYMBaAFNxhaGwf5ePPhzK7yOAK -D3VF6wm2MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAoeJCAX -IDCFoAaZoQ1niI6Ac/cds8G8It0UCcFGSg+HrZ0YujJxWIruRCUG60Q2OAbEvn0+ -uRpTm+4tV1Wt92WFeuRyqkomozx0g4CyfsxGX/x8mLhKPFK/7K9iTXM4/t+xQC4f -J+iRmPVsMKQ8YsHYiWVhlOMH9XJQiqERCB2kOKJCH6xkaF2k0GbM2sGgbS7Z6lrd -fsFTOIVx0VxLVsZnWX3byE9ghnDR5jn18u30Cpb/R/ShxNUGIHqRa4DkM5la6uZX -W1fpSW11JBSUv4WnOO0C2rlIu7UJWOROqZZ0OsybPRGGwagcyff2qVRuI2XFvAMk -OzBrmpfHEhF6NDU= ------END CERTIFICATE----- diff --git a/tools/ssl/server-cert.pem b/tools/ssl/server-cert.pem deleted file mode 100644 index a4e4e705b..000000000 --- a/tools/ssl/server-cert.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDDTCCAfUCAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCQVUxEzARBgNV -BAgMClNvbWUtU3RhdGUxDjAMBgNVBAoMBW15c3FsMQ4wDAYDVQQDDAVteXNxbDAg -Fw0yMDA0MDQxNDMxMTJaGA8zMDE5MDgwNjE0MzExMlowVTELMAkGA1UEBhMCQVUx -EzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMg -UHR5IEx0ZDEOMAwGA1UEAwwFbXlzcWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQDMG6j417e444WQxqJTqYRldtXde2hYFzZd5owzrWW6f2dff8Hu+WcW -ozrulmyiRx3E3PRUwT6XbjVRMJdThoE8IgLC16Q95rEGStxRh/fLjyLBf8626HDW -5d3T1N008lMKFy7MXVrdsSFcIQRYkmuTN7T2YXfvdS2Y5cx9oHw3hhNF+jfFBYEP -UhtnvKbdVK7oezsULr8CjfyJWAcubODWTOny5yO/v2sI7X00M8qc/zCtB6I30rlI -xPYs2nCYw+l89tm/XyJ7ayT7aE2jcR0Tnz0hB46jUxbESR+0aw8RiluFT0ec/Nxy -y4AWd/inx3LJpzbgIzvlxCmJDoIohJeFAgMBAAEwDQYJKoZIhvcNAQELBQADggEB -ACVcNZdiTpCcd2Ic0SS3DdGeNP+0hyL6xx/7KLMT7FuLtHHMzwzTwwQCXmhZIhj8 -0gwzOsBR8YzYIM/TJnYN35e813lv0elfz2wlwOf4yWrdllj8ymfzaUbvaML/Z5qy -vnMhnobsd2lgUD5ousfLdga7ueIKL1wE8TNJRIkrlObQgf3UOOMHBO707Y64FC5x -qbIvhriMQJSCF+EPmqeLirzPg8LLvUzM6enuuos+I3/WxkntCHO1ONYSl//h2P2E -Yoigb6S89lAFPiuXL1fmtaJN7ALRKWu5k1mundDePG+RhVDVuWuVHyfc5dMBH6DK -v4mGZ5Ie3C9dDM2qaz4fo+w= ------END CERTIFICATE----- diff --git a/tools/ssl/server-key.pem b/tools/ssl/server-key.pem deleted file mode 100644 index c62c74857..000000000 --- a/tools/ssl/server-key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAzBuo+Ne3uOOFkMaiU6mEZXbV3XtoWBc2XeaMM61lun9nX3/B -7vlnFqM67pZsokcdxNz0VME+l241UTCXU4aBPCICwtekPeaxBkrcUYf3y48iwX/O -tuhw1uXd09TdNPJTChcuzF1a3bEhXCEEWJJrkze09mF373UtmOXMfaB8N4YTRfo3 -xQWBD1IbZ7ym3VSu6Hs7FC6/Ao38iVgHLmzg1kzp8ucjv79rCO19NDPKnP8wrQei -N9K5SMT2LNpwmMPpfPbZv18ie2sk+2hNo3EdE589IQeOo1MWxEkftGsPEYpbhU9H -nPzccsuAFnf4p8dyyac24CM75cQpiQ6CKISXhQIDAQABAoIBAQCksGzW3LhRZsQO -3Td9afp6JDjMTRcUfSZQ/gWCbRb4NHSkieFhgbu8eFjEyns9NUS/48kB2is25KYA -rMRtkMoWSxsPPBA6IjoUabL71koK5aOVnhqdW1AxFai3k7opTp3SNoJ8Q5dd6d6R -B9MJ5JsIXpqVcm/jtxjjlgg6FZQk94gqoskRbekih+QidRplFkeGDRDWpHz5621T -92PSWu7C0au3Q+iMhaUeeKnWeIYNyCFWYGHxbJStBW0r1Xy6tkTJIBuOQN1JVT2S -f196xqBnEj+kkWLV6rrZNqLrptP1PjcKxRGjKoYxlL1F/51FuJSpTiSnf42hJYCp -TkS0ViABAoGBAPUxFwqPl9uw/dnugFztR9P/j0RRlcHEtsz4eK4CSVKKhRLiwMoB -yKQ1B6fDshTdFRoNaMyByZ1NzsEsvIcYk9LOPtEy2kTsgVK+EW8QFkxIgJtZEy1r -0nPV4a/latzyvjNl5xIdAgldxrpQEg7BAO7GVDnrPWETZ2TJV1Vq7u0FAoGBANUa -9B8+7AHGMiRMkyKKHJh340FVqlKH7XVvsoooCiTi0olUPNB70sAleOMTi5BiWnwZ -6z734eLFAHWsD8XyRQE6vW7NSqNqQ2g9Mv8i7tyn3P4AQwqpEXrdvklM+nc7clKu -agIHFfeVeB0YmLXmq26z6m3dGQeqsEKMDlM5iwiBAoGBAN32UqV82DxJPYTMI+f7 -5cpEz61JLgj7y4BCbv0XlMjkHRO7skss0jXUy9lTjyLUAQZUnUqFM77zcPfvR7wE -w81SaAt5vZ4ne+srpRyls4nbGJGJUZMMyLeUJ3rUdKkQFp7w4P3ExNM10XFYiwBQ -OEfvws+r5SS8LB1RJ35sD18BAoGBAMgOaab7luuDeIcDLA18wqOPyNQI68BWwuFA -Xse8FunR1fv+DKlb1Nl1VCs4qgh9jJx8aI/QfUo5ztipEpWtfoJM9pESQENw+p7c -9Qb3cG3NWHVLIaTcWwCRMpX1ohxUvlpISlRk+oZW10/ZS2NYjQ9771P8AAdmgdm3 -SatvlcoBAoGANyp6/KwFBNEwoFVGWUjIZ2GFH6lcgMjvX52gDN+aqhyN/vdR+rG6 -fekp/e9ef8VbPZS/nhZ+5TyhVKZ9Cw/4rDXvhNv7ir7wSGYs85Ry1n+ltQtNW3rV -UZ0UdRlp8sLvKT5gqba1wVaYW76Y1PZiMltzaldYuVKqYIheoibxlm4= ------END RSA PRIVATE KEY-----