Zero-Configuration SSL CA root bundles in Doctrine DBAL with Symfony

Laptop

Today I found myself deploying a Symfony project to an Azure environment that requires SSL connections to MySQL. I figured this out the hard way when I started getting these errors:

In AbstractMySQLDriver.php line 106:
                                                                                                                             
  An exception occurred in driver: SQLSTATE[HY000] [9002] SSL connection is required. Please specify SSL options and retry.  
                                                                                                                             

In PDOConnection.php line 31:
                                                                                            
  SQLSTATE[HY000] [9002] SSL connection is required. Please specify SSL options and retry.  
                                                                                            

In PDOConnection.php line 27:
                                                                                            
  SQLSTATE[HY000] [9002] SSL connection is required. Please specify SSL options and retry.  

Although StackOverflow had some potential solutions, they were all a bit messy. At a minimum, each solution required you to somehow know and provide the path to the system's CA root bundle in your project - either by hard-coding it (bad idea - it can be different per OS) or by making it a configurable parameter (requires developers to know where that file lives).

A Simple Solution

Thanks to the composer/ca-bundle package, we can fully automate the process of finding that CA root bundle. It intelligently scans the host system for that bundle, or falls back to a bundle included within the package if none can be found.

Install that package by running:

composer install composer/ca-bundle

Once installed, create a new compiler pass in Symfony which will scan through your Doctrine DBAL connections and automagically configure the PDO::MYSQL_ATTR_SSL_CA option for you:

<?php

namespace App\DepenedencyInjection\Compiler;

use Composer\CaBundle\CaBundle;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
 * Registers a CA root bundle with the PDO MySQL driver used by Doctrine DBAL
 *
 * This allows Doctrine to connect to MySQL instances that force SSL encryption, such as Azure's.
 */
final class RegisterCABundleWithPDOMysqlDriverPass implements CompilerPassInterface
{
    /**
     * @inheritDoc
     */
    public function process(ContainerBuilder $container)
    {
        $caPathOrFile = CaBundle::getSystemCaRootBundlePath();

        foreach ($container->getParameter('doctrine.connections') ?? [] as $connectionName) {
            $definition = $container->getDefinition($connectionName);
            $options = $definition->getArgument(0) ?? [];
            $options['driverOptions'][\PDO::MYSQL_ATTR_SSL_CA] = $caPathOrFile;
            $definition->setArgument(0, $options);
        }
    }
}

Lastly, register that compiler pass in your src/Kernel.php file like so:

    // ...

    /**
     * {@inheritDoc}
     */
    protected function build(ContainerBuilder $container)
    {
        $container->addCompilerPass(new RegisterCABundleWithPDOMysqlDriverPass());
    }

    // ...

And you should be good! Don't forget to clear your cache so Symfony recompiles the container.

Was this helpful?

About Colin O'Dell

Colin O'Dell

Colin O'Dell is the Director of Technology at Unleashed Technologies, a web and hosting firm based in Maryland. In addition to being an active member of the PHP League and maintainer of the league/commonmark project, Colin is also a PHP docs contributor, conference speaker, and author of the PHP 7 Migration Guide.