wwinfrey has asked for the wisdom of the Perl Monks concerning the following question:

I'm building an application that connects to a remote MySQL server using DBD::mysql. The DSN used in the connection uses these options, among others: Normally, when using these options in a DSN, one gives a location to a path on the filesystem. I would rather store these options as scalars. However, this doesn't seem to work:
#!/usr/bin/env perl use strict; use DBI; my $host = 'localhost'; my $port = '3306'; my $username = 'username'; my $password = ''; my $db = 'database'; my $SSL_CA_FILE_STRING = <<"SSLCA"; -----BEGIN CERTIFICATE----- <snip> -----END CERTIFICATE----- SSLCA my $SSL_CLIENT_CERT_STRING = <<"SSL_CLIENT_CERT"; -----BEGIN CERTIFICATE----- <snip> -----END CERTIFICATE----- SSL_CLIENT_CERT my $SSL_CLIENT_KEY_STRING = <<"SSL_CLIENT_KEY"; -----BEGIN PRIVATE KEY----- <snip> -----END PRIVATE KEY----- SSL_CLIENT_KEY open my $ca_file_location, '<', \$SSL_CA_FILE_STRING || die $!; open my $cert_file_location, '<', \$SSL_CLIENT_CERT_STRING || die $!; open my $key_file_location, , '<', \$SSL_CLIENT_KEY_STRING || die $!; my $dsn = "dbi:mysql:database=$db;host=$host;port=$port;" . "mysql_ssl=1;mysql_ssl_ca_file=$ca_file_location;" . "mysql_ssl_client_key=$key_file_location;" . "mysql_ssl_client_cert=$cert_file_location"; my $dbh = DBI->connect( $dsn, $username, $password) . || die;
The same DSN works if I provide filesystem paths. Any suggestions?

Replies are listed 'Best First'.
Re: DBD::mysql w/SSL certs extracted from variables
by NetWallah (Canon) on Dec 09, 2015 at 05:33 UTC
    Not sure if this would work, but it is possible to write the contents of the scalar to a named pipe.

    The reading end of the pipe, a file name, could then be fed into the DBI DSN.

    Sorry - I do not have the infrastructure to test this.

            Our business is run on trust. We trust you will pay in advance.

Re: DBD::mysql w/SSL certs extracted from variables
by kcott (Archbishop) on Dec 09, 2015 at 21:29 UTC

    G'day wwinfrey,

    "The same DSN works if I provide filesystem paths."

    Although you're naming those variables as $xxx_file_location, they're not locations at all: they're filehandles which will stringify to something like GLOB(0xffffffffffff):

    $ perl -wE 'my $x; open my $f, "<", \$x; say $f' GLOB(0x7fb509805480)

    As a basic debugging technique, print questionable strings before using them. In this case, I imagine $dsn contains parts that look like:

    ...mysql_ssl=1;mysql_ssl_ca_file=GLOB(0xffffffffffff);...

    Purely as a suggestion, because I have no way of testing this, you could look at the core module File::Temp.

    — Ken

      Yeah, I usually print my variables out using Data::Dumper, and I think I can see the issue (although, not the solution). I'm passing a filehandle in the DSN string, not a file location, which is why my DSN looks like this:
      $VAR1 = 'dbi:mysql:database=database;host=localhost;port=3306;mysql_ss +l=1;mysql_ssl_ca_file=GLOB(0x1008060e8);mysql_ssl_client_key=GLOB(0x1 +0082e750);mysql_ssl_client_cert=GLOB(0x100806268)';
      as opposed to this:
      $VAR1 = 'dbi:mysql:database=database;host=localhost;port=3306;mysql_ss +l=1;mysql_ssl_ca_file=/path/to/my/ca-cert.pem;mysql_ssl_client_key=/p +ath/to/my/client-key.pem;mysql_ssl_client_cert=/path/to/my/client-cer +t.pem';
      I'm currently using File::Temp, but it writes temp files out to the filesystem, which I'm trying to avoid. Having these certs in the source and present in the running process is one thing. I'm trying to avoid introducing another vector by writing them out to a third location. I'm looking into the named pipe solution, which will be tricky given that we're wrapping this code into a binary using PerlApp, and that it needs to run on Solaris, Linux and Windows.
        I've got this working on the Unix-y platforms, but of course Windows is giving me issues.

        Using File::Temp to write the certs to the filesystem works just fine, but when I try to pass them in as a connection string with $file_temp->filename, the drive letter is stripped and the path is, in general, unusable.

        For example, if I write the cert out using:

        my $mysql_ssl_ca_fh = File::Temp->new( UNLINK => 1, SUFFIX => '.pem'); my $mysql_ssl_ca_file = $mysql_ssl_ca_fh->filename; print $mysql_ssl_ca_fh $SSL_CA_FILE_STRING;

        I'll get a randomly-named file (for the purpose of this post, let's call it "foo.pem") in C:\ that contains the contents of $SSL_CA_FILE_STRING.

        However, when I try to read it back in, like so:

        sub dbconnect { my $engine = shift; my $dsn = q{dbi:mysql:database=database;} . q{host=} . $engine . q{;port=3306} . q{;mysql_ssl=1} . q{;mysql_ssl_ca_file=} . $mysql_ssl_ca_file . q{;mysql_ssl_client_cert=} . $mysql_ssl_cert_file . q{;mysql_ssl_client_key=} . $mysql_ssl_key_file . q{;mysql_ssl_cipher=} . $SSL_CIPHER; my $dbuser = q{supportclient}; my $dbpw = q{}; my $dbh = DBI->connect( $dsn, $dbuser, $dbpw ) || confess; return $dbh; }

        I would expect $mysql_ssl_ca_file to contain the value "C:\foo.pem", but it instead contains "\foo.pem", so perhaps File::Temp isn't compatible with Windows paths. Not that it matters. When I try to debug the DSN connection string using dbish, as I normally do, it doesn't seem to understand Windows paths either, so I'm at a loss.

        Anyone have any suggestions for how I can create a path to a file on a Windows filesystem that will work in a DBD::mysql DSN? It's not clear from the docs (or maybe I'm not looking in the right place) what the expected format is. The only thing that seems to work (dbish-wise) is being in the same directory as the cert files, and not using any paths.