Tuesday, June 3, 2014

Relaying email with postfix + TLS through gmail

I needed to relay email from appliances in my house, and wanted to use my gmail domain + TLS to do it. Following are my notes from setting up a postfix server to do that job. All email relayed by this server appear to be sourced from the gmail account I created for it.

I wouldn't use this for anything customer-facing, but it's a reasonable way to get messages out of closed environments without worrying about how the messages were sourced, who they appear to be from, will SPF records screw things up, etc...

Create gmail account
I'm using an account named postfix-relay@marget.com. I set that guy up, and gave him a password.

Install Linux somewhere
I'm using a minimal installation of CentOS 6.5 for this project, installed with some automated nonsense I've long used for this sort of thing.

Tweak hostname
 sed -i 's/localhost.localdomain/postfix-relay.marget.com/' /etc/sysconfig/network  

NFS mount my CentOS repository
The next little bit uses automounter to hang my CentOS repository on /CentOS and configure it as a repository. Skip it.
 yum install -y nfs-utils wget tcpdump unzip autofs  
 service rpcbind start  
 service autofs restart  
 ln -s /net/my_nfs_server/path/to/CentOS/ /CentOS  
 cp /etc/yum.repos.d/CentOS-Media.repo /etc/yum.repos.d/CentOS-NFS.repo  
 sed -i -e 's/\/media//' /etc/yum.repos.d/CentOS-NFS.repo  
 sed -i -e 's/c6-media/c6-nfs/' /etc/yum.repos.d/CentOS-NFS.repo  

Update and install packages
 yum update -y  
 yum install -y ntp ntpdate wget unzip openssl-perl setools-console  
 yum install -y telnet cyrus-sasl-md5 cyrus-sasl-plain  

Install and configure a user, sudo and ssh.
 useradd chris -u <myUID>  
 groupmems -g wheel -a chris  
 sed -i 's/# \(\%wheel.*NOPASSWD.*$\)/\1/' /etc/sudoers  
 (echo;echo) | su -c 'ssh-keygen -t rsa' chris  
 wget -O ~chris/.ssh/authorized_keys http://server.where/i_keep_my_public_key.pub  
 chmod 600 ~chris/.ssh/authorized_keys  
 chown chris:chris ~chris/.ssh/authorized_keys  
 sed -i 's/^#*PermitRootLogin.*$/PermitRootLogin no/' /etc/ssh/sshd_config  
 service sshd restart  

The first couple of lines here edit the init script so that service ntpd status dumps peer synchronization info rather than just ntpd is running.
 export NTPQ='[ -x /usr/sbin/ntpq ] \&\& /usr/sbin/ntpq -c peers'
 sed -i "s|[[:space:]]status \$prog$|&\n\t$NTPQ|" /etc/init.d/ntpd
 chkconfig ntpdate on
 chkconfig ntpd on
 service ntpdate start
 service ntpd start
 sleep 5; service ntpd status

Certificate Authority
Postfix needs a certificate for this TLS business to work, but it doesn't need to be from a CA that gmail recognizes. I'm creating a CA just to sign one certificate (well, two: the CA's own certificate, and the certificate postfix will be using). The CA protects its key with a PEM passphrase. I like to use openssl rand -base64 40 to create PEM passphrases. The passphrase will be needed only 4 times (creation/confirmation/CA cert signing/postfix cert signing). Because this is a single-use CA, it's okay to forget the passphrase after we're done here. If you make a mistake in this section and want to start over with the CA stuff, just rm -rf /etc/pki/CA and start over.

The 'challenge password' doesn't matter. These exist so that a certificate holder may prove her identity when requesting revocation of a lost certificate. They prevent spoofing of revocation requests, and don't make sense as far as I'm aware in the context of a root CA certificate.
 # /etc/pki/tls/misc/CA.pl -newca  
 CA certificate filename (or enter to create)  <enter>
 Making CA certificate ...  
 Generating a 2048 bit RSA private key  
 writing new private key to '/etc/pki/CA/private/cakey.pem'  
 Enter PEM pass phrase:  ThisIsMyCApassphrase
 Verifying - Enter PEM pass phrase:   ThisIsMyCApassphrase 
 You are about to be asked to enter information that will be incorporated  
 into your certificate request.  
 What you are about to enter is what is called a Distinguished Name or a DN.  
 There are quite a few fields but you can leave some blank  
 For some fields there will be a default value,  
 If you enter '.', the field will be left blank.  
 Country Name (2 letter code) [XX]:US  
 State or Province Name (full name) []:New Hampshire  
 Locality Name (eg, city) [Default City]:Brookline  
 Organization Name (eg, company) [Default Company Ltd]:Marget Labs  
 Organizational Unit Name (eg, section) []:Basement  
 Common Name (eg, your name or your server's hostname) []:postfix  
 Email Address []:postfix-relay-admin@marget.com  
 Please enter the following 'extra' attributes  
 to be sent with your certificate request  
 A challenge password []: blahblahblah  
 An optional company name []:  <enter>
 Using configuration from /etc/pki/tls/openssl.cnf  
 Enter pass phrase for /etc/pki/CA/private/cakey.pem:   ThisIsMyCApassphrase 
 Check that the request matches the signature  
 Signature ok  
 Certificate Details:  

Display the CA Certificate
Not necessary, just taking a look. /etc/pki/CA/cacert.pem is the CA certificate.
 openssl x509 -in /etc/pki/CA/cacert.pem -noout -text  

Copy the CA certificate into the postfix directory
 cp /etc/pki/CA/cacert.pem /etc/pki/tls/certs/POSTFIX-TRUST.pem  

Create a Certificate Signing Request (CSR)
Input to the CSR creation is the subject name (string below) and a public/private pair. This command creates the keys and the CSR. The keypair (both parts) will be stored in POSTFIX-key.pem, and the CSR will be stored in POSTFIX-req.pem
openssl req -new -nodes -subj '/CN=postfix/O=Marget Labs/C=US/ST=New Hampshire/L=Brookline/emailAddress=postfix@home.marget.com' -keyout POSTFIX-key.pem -out POSTFIX-req.pem  

Display the CSR
Just because we're curious.
openssl req -text -noout -in POSTFIX-req.pem  
Sign the Certificate
The CA, using his private key, which is protected with the PEM passphrase, signs the CSR and creates the certificate postfix will be using. This command will spit out some details from the CSR (what are we being asked to sign?) and prompt for the CA's PEM passphrase (4th use of the passphrase here). The new certificate will land in POSTFIX-cert.pem
openssl ca -days 1093 -out POSTFIX-cert.pem -infiles POSTFIX-req.pem  
Display the Certificate
Just because we're curious.
openssl x509 -in POSTFIX-cert.pem -noout -text  

Delete the CSR
Don't need it anymore.
rm -f POSTFIX-req.pem  

Set permissions on the keys and certificate, move them into place.
 chmod 400 POSTFIX-key.pem  
 chmod 644 POSTFIX-cert.pem  
 mv POSTFIX-key.pem /etc/pki/tls/private  
 mv POSTFIX-cert.pem /etc/pki/tls/certs  

Another trusted CA
Google will also be presenting a certificate. In order to trust google, we need to trust the CA that signed their certificate. Trusting that CA suggests we've seen the CA's certificate and public key. So far, we have not. Get Thawte's CA certificate (with public key inside) and append it to our existing file with trusted CA certificates.
 wget https://www.thawte.com/roots/thawte_Premium_Server_CA.pem  
 cat thawte_Premium_Server_CA.pem >> /etc/pki/tls/certs/POSTFIX-TRUST.pem  
 rm -f thawte_Premium_Server_CA.pem  

What have we accomplished so far?
Surprisingly little, actually. Three files have been contributed to the postfix environment:
  • POSTFIX-TRUST.pem < root certificates belonging to both trusted CAs (ours and thawte's)
  • POSTFIX-key.pem < our private key
  • POSTFIX-cert.pem < our signed certificate
But, those three files make us ready to...

Configure postfix main.cf
This is the first of three postfix config files needing attention. In this file we're making use of the 3 files listed above. Some other bits of interest:
  • The mynetworks directive specifies the list of prefixes from which postfix will accept mail for relaying.
  • The inet_interfaces directive specifies the interfaces on which postfix will listen for mail. It might be appropriate to be specific here if the box is multihomed.
  • The smtp_sasl_password_maps directive specifies the file where we keep the password lookup table (not the plain text file - note the postmap command below.
 cat >> /etc/postfix/main.cf << EOF  
 relayhost = [smtp.gmail.com]:587  
 smtp_use_tls = yes  
 smtp_tls_CAfile = /etc/pki/tls/certs/POSTFIX-TRUST.pem  
 smtp_tls_cert_file = /etc/pki/tls/certs/POSTFIX-cert.pem  
 smtp_tls_key_file = /etc/pki/tls/private/POSTFIX-key.pem  
 smtp_tls_session_cache_database = btree:/var/run/smtp_tls_session_cache  
 smtp_tls_security_level = secure  
 smtp_tls_mandatory_protocols = TLSv1  
 smtp_tls_mandatory_ciphers = high  
 smtp_tls_secure_cert_match = nexthop  
 tls_random_source = dev:/dev/urandom  
 smtp_sasl_auth_enable = yes  
 smtp_sasl_security_options = noanonymous  
 smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd  
 inet_interfaces = all  
 mynetworks = [::1]/128  

 Configure postfix transport file
This file directs postfix about how to deliver certain kinds of messages. In this case we want to send everything to gmail's message submission engine.
 cat >> /etc/postfix/transport << EOF  
 * smtp:smtp.gmail.com:587  

In order to log into gmail, postfix needs to know the password for its account. We enter the password in /etc/postfix/sasl_password, and the postmap command writes it to the lookup table in /etc/postfix/sasl_password.db. Re-run postmap whenever the sasl_password file is edited.
 cat > /etc/postfix/sasl_passwd << EOF  
 [smtp.gmail.com]:587 postfix-relay@marget.com:MyGMailPassword  
 chown root:postfix /etc/postfix/sasl_passwd  
 chmod 640 /etc/postfix/sasl_passwd  
 postmap /etc/postfix/sasl_passwd  

postfix reload will handle most postfix reconfiguration without restarting the whole process. Oh, is postfix even running? I don't know. Heck, lets just do everything:
 postfix reload  
 chkconfig postfix on  
 /etc/init.d/postfix restart  

At this point, postfix should be listening on port 25, and deliver messages to their destination via gmail. And the first bit (sending to gmail) will be secure.


  1. Hi, Sorry if you receive this twice, I have send it but didn't see where to, I have a problem with my gmail relay, I keep on getting this message when trying to send emails.

    SASL authentication failed; server smtp.gmail.com[] said: 534-5.7.14 Please log in via your web browser and?534-5.7.14 then try again.?534-5.7.14 Learn more at?534 5.7.14 https://support.google.com/mail/bin/answer.py?answer=78754 bj7sm8124153wjc.33 - gsmtp

    I hope you can help. thanks in advance


    1. The google page about the error (https://support.google.com/mail/answer/78754) suggests that the password is wrong.

      Good luck!

    2. Hi, thanks for the feedback, I've doubled checked and it is exactly what I type into the sasl_passwd.. this doesn't make sense, but I will remake the file again... this is two day's I'm struggling here..


  2. Hi... ok GOOGLE security blocked the connection... it seams to be working now, I opted for the lesser Secure app connection.

    Thanks you really did help...

  3. As today's reality is living under the propelled innovation, so everybody ought to likewise plan for the most noticeably bad as well. There is an expression that a safety measure is superior to anything cure. Issues don't thumps the entryway and tell that welcome sir/ma'am I am your issue.