Upgrading legacy Ruby & Rails installs to support TLS 1.2

I recently had a legacy machine running an ancient Ruby 1.8.7 app which I needed to upgrade to support TLS 1.2. A few bespoke requirements made it infeasible to upgrade the core OS to a more modern version, but fortunately, it was relatively straightforward to get it back to a servicable state. While it would be vastly preferable to rebuild the application for a more modern stack, it wasn’t feasible in this case, so this is the next-best option.

The biggest issue with upgrading these kinds of machines is the ancient OpenSSL versions linked against tools like curl and wget, which can make it difficult to retrieve files from remote locations for upgrading! Our path forward is SCP.

The tl;dr here is that we’re going to:

  1. scp tarballs of the openssl and curl sources to the target machine
  2. Build and install them, with Curl pointed to our newly-installed OpenSSL
  3. Use the upgraded Curl to bootstrap (or ugprade RVM)
  4. Use RVM to install an RVM-specific OpenSSL
  5. Use RVM to install Ruby with our new RVM OpenSSL install
  6. Custom-compile the Passenger agent.

Let’s get started.

Replacing Curl

Curl’s a fundamental tool, one that we take for granted far too often. Old versions which only support TLS 1.0 can’t communicate with large parts of the web now, so our first order of business is to get a working communications line out to the rest of the web.

I grabbed the OpenSSL 1.0.2o tarball and the curl 7.6.0 tarball locally, then scp’d them up to the target machine:

scp curl-7.60.0.tar.gz user@target:~
scp openssl-1.0.2o.tar.gz user@target:~

Then on my target machine, as root:

mv /home/user/curl-7.60.0.tar.gz /usr/local/src
mv /home/user/openssl-1.0.2o.tar.gz /usr/local/src
cd /usr/local/src
tar -xzf curl-7.60.0.tar.gz
tar -xzf openssl-1.0.2o.tar.gz

Time to compile OpenSSL:

cd openssl-1.0.2o
./configure --prefix=/opt/openssl-1.0.2o
make && make install

And Curl:

cd ../curl-7.60.0
PKG_CONFIG_PATH=/opt/openssl-1.0.2o/lib/pkgconfig ./configure
make && make install

Verify that we have the right version:

$ curl -V
curl 7.60.0 (i686-pc-linux-gnu) libcurl/7.60.0 OpenSSL/1.0.2o zlib/1.2.3
Release-Date: 2018-05-16
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP UnixSockets HTTPS-proxy

Great! We have a working lifeline to the outside world. Let’s make sure we can speak TLS 1.2:

$ curl --tlsv1.2 -I https://google.com/
HTTP/1.1 301 Moved Permanently

Success!

Getting and installing RVM

Now that we have Curl, we can install RVM via the usual mechanism:

\curl -sSL https://get.rvm.io | bash

Relog your session, and then you have RVM ready:

rvm pkg install openssl

This will custom-compile OpenSSL for RVM to use with the Ruby versions it installs. Then we just install Ruby:

rvm install ree --with-openssl-dir=/usr/local/rvm/usr

And test it:

rvm use ree
ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION'
OpenSSL 1.0.1i 6 Aug 2014

Passenger

Finally, we needed a updated passenger install. This is slightly fiddly because:

  1. Newer gem versions won’t work on Ruby 1.8.7
  2. Ruby 1.8.7 ships with a version of Rubygems that doesn’t realize that
  3. Passenger on Ruby 1.8.7 won’t boot up apps with newer versions of Rubygems.

We’ll work around this by doing the install with a newer Rubygems, then downgrading it for compatibility.

First, upgrade Rubygems:

gem install rubygems-update
update_rubygems

Then install Passenger:

gem install passenger -v 5.3.2

We’ll need to custom-compile Passenger’s agent against our custom OpenSSL version:

EXTRA_PRE_LDFLAGS="-L/usr/local/rvm/lib" EXTRA_CXXFLAGS="-I/usr/local/rvm/include" EXTRA_CFLAGS="-I/usr/local/rvm/include" passenger-install-nginx-module

Finally, downgrade Rubygems:

rvm rubygems latest-1.8

Restart nginx and you should be up and running.