Monthly Archives: January 2017

Using Let’s Encrypt with acme-client on a FreeBSD 11/Apache 2.4

I recently opted to use the Let’s Encrypt project to handle my noncommercial TLS needs.  If this works well, I will probably start pushing commercial clients over to it as well.

I couldn’t find a good guide to setting it up to use multiple domains smoothly on FreeBSD.  This technique should work with a single domain or multiple domains.

About Let’s Encrypt and acme-client

Let’s Encrypt is a project to simplify, automate, and normalize the creation and maintenance of TLS certificates used by web sites.

acme-client is a C program written to interact with the Let’s Encrypt service.  It was written for the OpenBSD project, but is useful on other platforms.  The key argument in its favor is arguably security.  There are few dependencies.

Assumptions

FreeBSD 11 is assumed here, but any recent version should work work.  With some effort these instructions could surely be adapted to several other environments.

I assume you want to configure multiple sites, but this should work for a single site as well. It will simply be multi-site ready (which, to my mind, costs nothing).

I mostly use the default paths in FreeBSD for the configuration of both Apache and acme-client.

Though you don’t strictly need to use Apache to follow most of these instructions, the configuration suggests I make about challenge-response configuration are Apache-specific. Any web daemon that includes aliased folders should permit the same kind of configuration.  If you’re using something besides Apache, you’ll need to adjust accordingly but this should not prevent you from using these instructions.

Even if you do use Apache, I assume you have a configuration where Apache already basically works and is compiled and configured to use encrypted HTTP.  I used a pretty stock installation of Apache 2.4 from /etc/ports/www/apache24.   I use FreeBSD paths and conventions throughout.  FreeBSD by default uses www:www (UID/GID of 80) as the username for httpd.  I tend to configure all my web sites in /usr/vhosts/ these days and create a logs/ folder and htdocs/ folder within.

I use a fairly large number of virtual hosts in Apache.  By habit, I store these in /usr/vhosts/.  I create a new  vhost tree with a one-liner like:

export THISD="mydomain.com"; mkdir -p /usr/vhosts/$THISD/htdocs && mkdir -p /usr/vhosts/$THISD/logs && chown -R 80:80 /usr/vhosts/$THISD

Using this example would create a /usr/vhosts/mydomain.com/ directory with separate folders for logs and web content. Of course, how you organize your folders is entirely up to you.

Overview of what we want

Before I go through the steps, I want to make clear what the end result should look like. You should end up with a directory tree of TLS keys and certificates.  For example:

Certificate file:   /usr/local/etc/ssl/acme/richardfassett.com/cert.pem
Chain file: /usr/local/etc/ssl/acme/richardfassett.com/chain.pem
Full chain: /usr/local/etc/ssl/acme/richardfassett.com/fullchain.pem
Private key: /usr/local/etc/ssl/acme/private/richardfassett.com/privkey.pem

You shouldn’t need to do anything with these files except refer to them in relevant configurations (e.g., Apache, mail, PHP). acme-client will handle that for you.

Optional but highly recommended: the file /usr/local/etc/acme/domains.txt will contain a list of certificates.  Each line in the file should contain the domains for one certificate. For example:

mydomain.com www.mydomain.com
myotherdomain.com www.myotherdomain.com mail.myotherdomain.com

Each line should be unique.  The importance of this file is apparently later when it’s time to write a renewal script.

Step 1: install your software

Make sure you have a working Apache configuration already configured to use TLS. This is pretty easy to do, but there are many ways to do it and it’s generally outside the scope of this guide.

I installed acme-client from ports by running:

cd /usr/ports/security/acme-client && make install

It was not available in with the pkg utility at the time, but in the future hopefully you could run pkg install acme-client.

Step 2: configure Let’s Encrypt challenge (.well-known)

To verify you own the domain, Let’s Encrypt checks a challenge-response routine.  You write some data Let’s Encrypt asks you to write to a certain place on your web server. FreeBSD sets aside /usr/local/www/acme for this purpose, but I ended up using my own folder.

I ran this command:

mkdir -pm750 /usr/local/www/.well-known && chown -R www:www /usr/local/www/.well-known

Then I added this to my httpd.conf file:

<Directory "/usr/local/www/.well-known/">
        Options None
        AllowOverride None
        Require all granted
        Header add Content-Type text/plain
</Directory>

Now I can simply add the line

Alias /.well-known/ /usr/local/www/.well-known/

to relevant an un-encrypted Apache host to set up the challenge-response.  My way of doing this was to configure the non-SSL site like so:

<VirtualHost 23.25.105.194:80>
    ServerName mydomain.com
    RewriteEngine On
    RewriteRule ^/?(.*) http://www.mydomain.com/$1 [R,L]
</VirtualHost>
<VirtualHost 23.25.105.194:80>
    ServerAdmin webmaster@megapipe.net
    DocumentRoot "/usr/vhosts/mydomain.com/htdocs/"
    ServerName www.mydomain.com
    ServerAlias www.mydomain.com

    # share well-known for renewal via Let's Encrypt!
    Alias /.well-known/ /usr/local/www/.well-known/

    # Anything that isn't going to domain.org/.well-known gets forwarded to the https site
    RewriteEngine on
    RewriteCond %{REQUEST_URI} !^/.well-known
    RewriteRule (.*) https://www.mydomain.com/$1 [R=301,L]

    ErrorLog "/usr/vhosts/mydomain.com/logs/error_log"
    <Directory "/usr/vhosts/mydomain.com/htdocs/">
        AllowOverride All
    </Directory>
    <IfModule mod_log_config.c>
        CustomLog "|/usr/local/sbin/rotatelogs -l /usr/vhosts/mydomain.com/logs/access_log-%Y-%m-%d.log 86400" combined
    </IfModule>
</VirtualHost>

The first <VirtualHost> entry tells Apache to simply forward anything from mydomain.com to www.mydomain.com.

The second entry tells it to forward everything from http://www.mydomain.com to https://www.mydomain.com/ except the query to the .well-known folder where Let’s Encrypt will look for its challenge response.  This way I never really have an unencrypted site running, except for the minimal amount I need to in order to configure acme-client.

Step 3: run the acme-client to get a basic configuration

Configure your first domain with a command like

export DS="domain.org www.domain.org"; \
 acme-client -mvnNC /usr/local/www/.well-known/acme-challenge/ \
 $DS && echo $DS >> /usr/local/etc/acme/domains.txt

Note: I’m doing a little inline scripting to simplify the steps necessary to creating the configuration.  If you run this command twice, you may need to edit /usr/local/etc/acme/domains.txt and remove any duplicate entries.  In the event that there is a problem issuing the certificate, this should prevent the domains from being added to the domains.txt file (but check anyway).  Also, if you are skipping creating a renewal script or find yourself needing to run the command a second time after correcting your configuration, you can simplify this command to acme-client -mvnNC /usr/local/www/.well-known/acme-challenge/  domain.org www.domain.org

If all goes well, acme-client will do some initial configuration and create your first keys.

To issue another certificate, .e.g, for another domain, use a command like

export DS="anotherdomain.org www.anotherdomain.org"; \
 acme-client -mvnNC /usr/local/www/.well-known/acme-challenge/ $DS \
 && echo $DS >> /usr/local/etc/acme/domains.txt

Note: there are fewer parameters this time. We dropped the  As above, you can simplify this further if you are skipping the renewal script:

Step 4: configuring Apache vhost to use the TLS certificate

Once your certficate is successfully installed you can configure Apache’s secure HTTP sites. We’ve already configured the insecure sites to forward to the secure sites above (except for the .well-known folder), so now we just need to add the secure sites.

This is completely optional, but what I like to do is forward mydomain.com to www.mydomain.com:

<VirtualHost 23.25.105.194:443>
   ServerName mydomain.com
   SSLEngine on
   SSLCertificateFile  /usr/local/etc/ssl/acme/mydomain.com/cert.pem
   SSLCertificateKeyFile /usr/local/etc/ssl/acme/private/mydomain.com/privkey.pem
   SSLCertificateChainFile /usr/local/etc/ssl/acme/mydomain.com/fullchain.pem
   RewriteEngine On
   RewriteRule ^/?(.*) https://www.mydomain.com/$1 [R,L]
</VirtualHost>

The actual configuration for mydomain.com is:

<VirtualHost 23.25.105.194:443>
    ServerAdmin webmaster@megapipe.net
    DocumentRoot "/usr/vhosts/mydomain.com/htdocs/"
    ServerName www.mydomain.com
    ServerAlias www.mydomain.com
    <IfModule mod_rewrite.c>
        RewriteEngine on
    </IfModule>

    SSLEngine on
    SSLCertificateFile  /usr/local/etc/ssl/acme/mydomain.com/cert.pem
    SSLCertificateKeyFile /usr/local/etc/ssl/acme/private/mydomain.com/privkey.pem
    SSLCertificateChainFile /usr/local/etc/ssl/acme/mydomain.com/fullchain.pem

    # ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9001/usr/vhosts/mydomain.com/htdocs/$1 timeout=120

    ErrorLog "/usr/vhosts/mydomain.com/logs/error_log"
    <Directory "/usr/vhosts/mydomain.com/htdocs/">
        AllowOverride All
    </Directory>
    <IfModule mod_log_config.c>
        CustomLog "|/usr/local/sbin/rotatelogs -l /usr/vhosts/mydomain.com/logs/access_log-%Y-%m-%d.log 86400" combined
    </IfModule>
</VirtualHost>

Step 5: maintain your certificates

Checking to see if your certificate needs to be renewed is as simple as running:

acme-client -mv mydomain.com www.mydomain.com

As I understand it, the certificate should update if it’s older than 60 days. You can force the certificate to update early with a command like:

acme-client -Fmv mydomain.com www.mydomain.com

(-F parameter forces the update even if it’s too soon. To either of the above commands you could also add the -b parameter, which will force current certificates to backup if there is a change.)

There are several ways you could auto-renew.

Option 1: crontab

If you just have one domain, or only a few, maybe you can live with a crontab entries instead of writing a script.  If that’s the case, you might not need the domains.txt file.

This would have acme-client run every Sunday, Wednesday, and Friday at 9pm and renew if necessary.

0 21 * * 0,3,5 /usr/local/bin/acme-client -m mydomain.com www.mydomain.com 2>&1

You can have one entry for every domain.

Option 2: custom script

 

#!/bin/sh

###
#
# This script was adapted from letskencrypt.sh by Bernard Spil
# See https://brnrd.eu/security/2016-12-30/acme-client.html
#
# To initially configure a new domain, configure
# apache properly and use a command like:
#
###

# Define location of dirs and files
DOMAINSFILE="/usr/local/etc/acme/domains.txt"
CHALLENGEDIR="/usr/local/www/.well-known/acme-challenge"
SSLDIR="/usr/local/etc/ssl"

# is changed to 1 if any domains expired
CHECKEXPIRATION=0

# Check for account key and create dir and key (-n) if required
if [ ! -f "/usr/local/etc/acme/privkey.pem" ] ; then
   EXTRAARGS="${EXTRAARGS} -n"
fi

# Loop through the domains.txt file with lines like
# example.org www.example.org img.example.org
cat ${DOMAINSFILE} | while read domain subdomains ; do

    # directory where cert.pem, fullchain.pem and chain.pem are
    # saved (${SSLDIR}/acme/${domain} should be FreeBSD default) 
    # when -m is is invoked
   CERTDIR="${SSLDIR}/acme/${domain}"

    # Define the name of the private key
    #       (${SSLDIR}/acme/private/${domain}/privkey.pem should
    #       be default with the -m option invoked)
    DOMAINKEY="${SSLDIR}/acme/private/${domain}/privkey.pem"

    # acme-client returns RC=2 when certificates 
    # weren't changed; use set +e to capture the return code
    set +e
    # Renew the key and certs if required
    acme-client -mvb -C "${CHALLENGEDIR}" \
                     -k "${DOMAINKEY}" \
                     -c "${CERTDIR}" \
                     ${EXTRAARGS} ${domain} ${subdomains} RC=$?

   # now that we have the return code, set script to exit if 
   # nonzero is returned
   set -e

   # if anything is expired, we'll want to do something 
   # (e.g., restart HTTPS)
   if [ $RC -ne 2 ] ; then
        CHECKEXPIRATION=1
   fi
done

if [ "$CHECKEXPIRATION" -ne "0" ] ; then
        service apache24 restart
fi

 

Compile custom lftp for CentOS 7.x

I use lftp to do a lot of backups.  rsync would be my preferred solution, but when I have to back up a large repository of files from a Windows machine rsync isn’t an option.

I’ve found Red Hat or CentOS keeps lftp woefully out of date.   I found some annoying bugs in the in older versions (specifically it chokes on some Windows files with unusual names and spacing).  This is how how installed lftp for CentOS 7.

Step 1: get your build environment and dependencies

yum install rpm-build ncurses-devel gnutls-devel readline-devel

You may have other dependencies, but those are the ones that seem to typically be missing.

Step 2: create a spec file

I am putting the build treen in /root/rpmbuild. The spec file contains build information.  Run

mkdir -p /root/rpmbuild/SPECS/ && nano /root/rpmbuild/SPECS/lftp.spec

And then create the following file:

Summary:    A sophisticated file transfer program
Name:        lftp                                                                                                                                                                                                
Version:    4.7.5
Release:    3%{?dist}
License:    GPLv3+
Group:        Applications/Internet
Source0:    ftp://ftp.yar.ru/lftp/lftp-%{version}.tar.xz
URL:        http://lftp.yar.ru/
BuildRoot:    %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRequires:    ncurses-devel, gnutls-devel, pkgconfig, readline-devel, gettext
%description
LFTP is a sophisticated ftp/http file transfer program. Like bash, it has job
control and uses the readline library for input. It has bookmarks, built-in
mirroring, and can transfer several files in parallel. It is designed with
reliability in mind.
%package scripts
Summary:    Scripts for lftp
Group:        Applications/Internet
Requires:    lftp >= %{version}-%{release}
BuildArch:    noarch
%description scripts
Utility scripts for use with lftp.
%prep
%setup -q
#sed -i.rpath -e '/lftp_cv_openssl/s|-R.*lib||' configure
sed -i.norpath -e \
   '/sys_lib_dlsearch_path_spec/s|/usr/lib |/usr/lib /usr/lib64 /lib64 |' \
   configure
%build
%configure --with-modules --disable-static --with-gnutls --without-openssl --with-debug
make %{?_smp_mflags}
%install
rm -rf $RPM_BUILD_ROOT
export tagname=CC
make DESTDIR=$RPM_BUILD_ROOT INSTALL='install -p' install
chmod 0755 $RPM_BUILD_ROOT%{_libdir}/lftp/*
chmod 0755 $RPM_BUILD_ROOT%{_libdir}/lftp/%{version}/*.so
iconv -f ISO88591 -t UTF8 NEWS -o NEWS.tmp
touch -c -r NEWS NEWS.tmp
mv NEWS.tmp NEWS
# Remove files from $RPM_BUILD_ROOT that we aren't shipping.
#rm $RPM_BUILD_ROOT%{_libdir}/lftp/%{version}/*.la
rm $RPM_BUILD_ROOT%{_libdir}/liblftp-jobs.la
rm $RPM_BUILD_ROOT%{_libdir}/liblftp-tasks.la
rm $RPM_BUILD_ROOT%{_libdir}/liblftp-jobs.so
rm $RPM_BUILD_ROOT%{_libdir}/liblftp-tasks.so
%find_lang %{name}
%clean
rm -rf $RPM_BUILD_ROOT
%post -p /sbin/ldconfig
%postun -p /sbin/ldconfig
%files -f %{name}.lang
%defattr(-,root,root,-)
%doc BUGS COPYING ChangeLog FAQ FEATURES README* NEWS THANKS TODO
%config(noreplace) %{_sysconfdir}/lftp.conf
%{_bindir}/*
%{_mandir}/*/*
%dir %{_libdir}/lftp
%dir %{_libdir}/lftp/%{version}
%{_libdir}/lftp/%{version}/cmd-torrent.so
%{_libdir}/lftp/%{version}/cmd-mirror.so
%{_libdir}/lftp/%{version}/cmd-sleep.so
%{_libdir}/lftp/%{version}/liblftp-network.so
%{_libdir}/lftp/%{version}/liblftp-pty.so
%{_libdir}/lftp/%{version}/proto-file.so
%{_libdir}/lftp/%{version}/proto-fish.so
%{_libdir}/lftp/%{version}/proto-ftp.so
%{_libdir}/lftp/%{version}/proto-http.so
%{_libdir}/lftp/%{version}/proto-sftp.so
%{_libdir}/liblftp-jobs.so.*
%{_libdir}/liblftp-tasks.so.*
%files scripts
%defattr(-,root,root,-)
%{_datadir}/lftp

You can download a spec file for lftp here, but you will need to make some modifications.  The version needs to be changed to the version you downloaded, and I removed references to distro-specific patches. (I actually have no idea what these patches do, but they are several versions old.)

Step 3: download the source

In my example, I downloaded lftp version 4.7.5.

mkdir -p /root/rpmbuild/SOURCES/ && cd /root/rpmbuild/SOURCES/ && wget http://lftp.yar.ru/ftp/lftp-4.7.5.tar.xz

Step 4: build

rpmbuild -ba lftp.spec

Step 5: install

rpm -ivvh /root/rpmbuild/RPMS/x86_64/lftp-4.7.5-3.el7.centos.x86_64.rpm

You can use the command rpm -qa lftp to confirmation successful installation.

Resuscitating my blog: miscellaneous musings, politics & urbanism roundup

I’ve been slowly reviving my personal blog.  I set most of the posts to draft status to be re-edited before I release them back into the wild. I’ve stayed somewhat active on social media over the years, but mostly haven’t felt like blogging, letting this blog largely expire for a while.  I have a fairly large amount of content I’ve written going back to 2006, with a lull after 2011.  I am slowly re-editing it to update links.

Looking back on this content, there is so much that probably would only warrant a tweet now that I blogged about at one time. A post on New York newsstands? Well, it’s still cool, but it seems so 2008 to make a wistful blog post about that. Much of the political stuff is almost like a time capsule, but I find it amusing to read. Remember how the Gang of Four cretins derailed New York politics for so long? I kind of miss Richard Ravitch. I even miss David Paterson a little.

I never thought much of Mike Bloomberg’s authoritarian politics, but sometimes I found myself defending his policies. I thought congestion pricing was a good idea, for instance, and was always amazed at how people would bend over backwards to oppose it. Even the MTA was ambivalent about it, and as I remember the local transit unions were completely silent.  I always found automobile casualties futile and needless; the way we structure our society around cars makes us less safe. Of course, maybe in some of my musings on the subject, such as this one from 2006, I should have put more thought into mitigating the downsides, whether they were real or merely perceived.

I wrote a lot on transit and urbanism, sometimes just little details I found interesting. Some of my thoughts on urbanism were often too abbreviated but I think quite prescient too. This commentary on San Francisco’s transit center. Since I wrote that, many projects like that have opened in New York and they are still unfortunate.  I’ve been on the road a lot the past few years, so I’d probably like to talk about urbanism more. Meanwhile, complaints about every little fare increase were a constant theme in New York over the years.

I meant to put more into the post at the time, but have a time capsule about Kirsten Gallibrand, whose policy focus shifted greatly after David Paterson appointed her to the U.S. Senate to replace Hillary Clinton. Then, as now, little thought about transit.

Ten years ago I was paying a lot more attention to infrastructure than I am nowadays. But it’s unfortunate how much infrastructure could have been.  Transit on the Tappan Zee? Commuter rail to Stewart Airport?  This post mentions how transit was almost guaranteed on the Tappan Zee Bridge.  Well, muscle car aficionado Andrew Cuomo killed that a few years later.

I sometimes wrote, haphazardly, about higher education.  Elliot Spitzer of all people had some lofty ambitions for improving the state of New York’s higher education. I think since I wrote that I have taken a step back and reconsidered how higher education should have a role in our society. The decade since I published that post has seen Occupy Wall Street, student debt crises, ever-higher tuition fees, and of course the fiery campaign of Bernie Sanders at least somewhat centered on free higher education.