How To Install and Configure DKIM with Postfix on Ubuntu Xenial 16.04 LTS

Used this tutorial: https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-dkim-with-postfix-on-debian-wheezy

It has a bug so this is with the correction for me. In this tutorial replace numbeo.com with your domain and use your personal gmail email address.

Install dkim:

sudo apt-get update
sudo apt-get dist-upgrade

Install OpenDKIM and it’s dependencies:

sudo apt-get install opendkim opendkim-tools

Let’s start with the main configuration file:

sudo joe /etc/opendkim.conf

It should have this content:

root@condor1796 /etc # cat opendkim.conf
OversignHeaders		From
TrustAnchorFile /usr/share/dns/root.key

AutoRestart Yes
AutoRestartRate 10/1h
UMask 002
Syslog yes
SyslogSuccess Yes
LogWhy Yes

Canonicalization relaxed/simple

ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
InternalHosts refile:/etc/opendkim/TrustedHosts
KeyTable /etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable

Mode sv
PidFile /var/run/opendkim/opendkim.pid
SignatureAlgorithm rsa-sha256

UserID opendkim:opendkim

Socket inet:12301@localhost
SignHeaders From,Sender,To,CC,Subject,Message-Id,Date,List-Unsubscribe,List-Unsubscribe-Post

Connect the milter to Postfix:
sudo nano /etc/default/opendkim

Add the following line, edit the port number only if a custom one is used:
SOCKET="inet:12301@localhost"

Configure postfix to use this milter and limit the number of transactions per domain:
sudo nano /etc/postfix/main.cf

add this content:

milter_protocol = 2
milter_default_action = accept
smtpd_milters = inet:localhost:12301
non_smtpd_milters = inet:localhost:12301
smtp_destination_concurrency_limit = 2
smtp_destination_rate_delay = 1s
smtp_extra_recipient_limit = 10
default_destination_concurrency_limit=2
default_destination_rate_delay=1s

Create a directory structure that will hold the trusted hosts, key tables, signing tables and crypto keys:

sudo mkdir /etc/opendkim 
sudo mkdir /etc/opendkim/keys

Specify trusted hosts:

sudo nano /etc/opendkim/TrustedHosts
*.numbeo.com
127.0.0.1
localhost
209.126.119.66

Create a key table:

sudo nano /etc/opendkim/KeyTable

A key table contains each selector/domain pair and the path to their private key.

mail._domainkey.numbeo.com numbeo.com:mail:/etc/opendkim/keys/numbeo.com/mail.private

Create a signing table:

sudo nano /etc/opendkim/SigningTable

This file is used for declaring the domains/email addresses and their selectors.

*@numbeo.com mail._domainkey.numbeo.com

Generate the public and private keys

Change to the keys directory:

cd /etc/opendkim/keys

Create a separate folder for the domain to hold the keys:

sudo mkdir numbeo.com
cd numbeo.com

Generate the keys:

sudo opendkim-genkey -s mail -d numbeo.com

-s specifies the selector and -d the domain, this command will create two files, mail.private is our private key and mail.txt contains the public key.

Change the owner of the private key to opendkim:

sudo chown opendkim:opendkim mail.private
 Use mail.txt file to add TXT DNS record for mail._domainkey numbeo.com

It should look like this:

root@condor1796 /etc #  dig mail._domainkey.numbeo.com TXT
;; ANSWER SECTION:
mail._domainkey.numbeo.com. 5660 IN	TXT	"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPqBmXSBbSXdmDIOqriDqI7/HJz1AqJNjK+Jqd0EQHEmXS5BHBtfr28ur4+m/7JjooE98DB29mbZBtua8dpwKtA5HetCDxdb5mMIjUDRo2wiSYCQ2wxLFwzATGHLa/N9LhDNQJCmZFoEWBFVhOFyrl8jOEuSCVpEPyXtKdYSZcHwIDAQAB"

Restart Postfix and OpenDKIM:

sudo service postfix restart
sudo service opendkim restart

Check now if this Opendkim config is working:

root@condor1796 /etc # sudo -u opendkim opendkim-testkey -vvvv 
opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: record 0 for 'mail._domainkey.numbeo.com' retrieved
opendkim-testkey: checking key 'mail._domainkey.numbeo.com'
opendkim-testkey: key mail._domainkey.numbeo.com: OK
opendkim-testkey: key mail._domainkey.numbeo.com not secure
opendkim-testkey: 1 key checked; 1 pass, 0 fail

If opendkim config is working check it’s integration with postfix by sending a mail to gmail:

mail -s "test subject" mladen.adamovic@gmail.com < /etc/opendkim.conf

That’s it, you shall now see in the email source as received on gmail:

dkim=pass

Configure Tomcat with HTTPS/SSL on Ubuntu 16.04 LTS (Xenial) using Letsencrypt

This tutorial is different from other Tutorials as of August 2015 since it closes other ports, doesn’t use secondary web server for letsencrypt config, tomcat is configured with it’s special script, using a tomcat from its source and uses tomcat native with APR.

This tutorial is complete, from fresh server install (i.e. Ubuntu Minimal as installed on dedicated server) to the working server.

The base for this tutorial is the following:

Configure glassfish as non root user on Ubuntu: https://www.nabisoft.com/tutorials/glassfish/installing-glassfish-41-on-ubuntu

Configure letsencrypt and tomcat/jira using non-native connector: http://blog.ivantichy.cz/blogpost/view/74

Configure tomcat with APR: http://www.sheroz.com/pages/blog/installing-apache-tomcat-native-linux-ubuntu-1204.html

Set locale

sudo locale-gen "en_US.UTF-8"
sudo dpkg-reconfigure locales

Put to file /etc/environment:

LC_ALL=en_US.UTF-8
LANG=en_US.UTF-8

Update environment

source /etc/environment

Install Java

apt-get install joe nano command-not-found python-software-properties software-properties-common
add-apt-repository ppa:webupd8team/java
apt-get update
apt-get install oracle-java8-installer
# nano /etc/environment

After opening with the text editor, we’ll need to add the following line into the bottom of the file.

JAVA_HOME="/usr/lib/jvm/java-8-oracle"

Once, the line is added, we’ll need to reload file.

source /etc/environment

check if java is working with java –version

Setup user going to run Tomcat

For legacy reasons, I call this user glassfish 😉

#Add a new user called glassfish sudo adduser --home /home/glassfish --system --shell /bin/bash glassfish #add a new group for glassfish administration sudo groupadd glassfishadm

Close ports and setup port forwarding

Since Tomcat process will be running as non-root user for security concerns, let’s create

iptables.rules file with the following:

#!/bin/bash # ATTENTION: flush/delete all existing rules iptables -F ################################################################ # set the default policy for each of the pre-defined chains ################################################################ iptables -P INPUT ACCEPT iptables -P OUTPUT ACCEPT iptables -P FORWARD DROP 
# allow establishment of connections initialised by my outgoing packets iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT # accept anything on localhost iptables -A INPUT -i lo -j ACCEPT ################################################################ #individual ports tcp ################################################################ iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j ACCEPT iptables -A INPUT -p tcp --dport 8080 -j ACCEPT iptables -A INPUT -p tcp --dport 8181 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT #uncomment next line to enable AdminGUI on port 4848: #iptables -A INPUT -p tcp --dport 4848 -j ACCEPT ################################################################ #slow down the amount of ssh connections by the same ip address: #wait 60 seconds if 3 times failed to connect ################################################################ iptables -I INPUT -p tcp -i eth0 --dport 22 -m state --state NEW -m recent --name sshprobe --set -j ACCEPT iptables -I INPUT -p tcp -i eth0 --dport 22 -m state --state NEW -m recent --name sshprobe --update --seconds 60 --hitcount 3 --rttl -j DROP #drop everything else iptables -A INPUT -j DROP ################################################################ #Redirection Rules ################################################################ #1. redirection rules (allowing forwarding from localhost) iptables -t nat -A OUTPUT -o lo -p tcp --dport 80 -j REDIRECT --to-port 8080 iptables -t nat -A OUTPUT -o lo -p tcp --dport 443 -j REDIRECT --to-port 8181 #2. redirection http iptables -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 8080 #3. redirection https iptables -t nat -A PREROUTING -p tcp -m tcp --dport 443 -j REDIRECT --to-ports 8181 ################################################################ #save the rules somewhere and make sure #our rules get loaded if the ubuntu server is restarted ################################################################ iptables-save > /etc/my-iptables.rules iptables-restore < /etc/my-iptables.rules #List Rules to see what we have now iptables -L
Make this file executable
chmod +x iptables.rules
Invoke ./iptables.rules than create File: /etc/network/if-pre-up.d/iptablesload with the content:
#!/bin/sh /sbin/iptables-restore < /etc/my-iptables.rules exit 0
 File: /etc/network/if-post-down.d/iptablessave
#!/bin/sh /sbin/iptables-save -c > /etc/my-iptables.rules if [ -f /etc/iptables.downrules ]; then    /sbin/iptables-restore < /etc/iptables.downrules fi exit 0
 Set these files as executable
sudo chmod +x /etc/network/if-post-down.d/iptablessave sudo chmod +x /etc/network/if-pre-up.d/iptablesload
You can reboot to see with iptables -L if the new config is OK

Download Tomcat from source and install it

su glassfish

As a user glassfish download Tomcat from source

cd ~
wget http://www-eu.apache.org/dist/tomcat/tomcat-8/v8.5.5/bin/apache-tomcat-8.5.5.tar.gz
tar zxvf apache-tomcat-8.5.5.tar.gz 
exit

Exit the user glassfish. Create an init script like this (as a root):

root@condor1796 ~ # cat /etc/init.d/tomcat
#! /bin/sh
### BEGIN INIT INFO
# Provides: tomcat
# Required-Start: $local_fs $network
# Required-Stop: $local_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Tomcat
# Description: Tomcat webserver running as non root user
### END INIT INFO
#to prevent some possible problems
export AS_JAVA=/usr/lib/jvm/java-8-oracle

#GLASSFISHPATH=/home/glassfish/bin
CATALINA_HOME=/home/glassfish/apache-tomcat-8.5.5
export CATALINA_OPTS="$CATALINA_OPTS -Xmx3500m -Djdk.tls.ephemeralDHKeySize=2048"

case "$1" in
start)
echo "starting tomcat from $CATALINA_HOME"
sudo -u glassfish $CATALINA_HOME/bin/catalina.sh start
;;
restart)
$0 stop
#pause here is needed since Tomcat stop is apparently working in the background
sleep 0.1s
killall -9 java
$0 start
;;
stop)
echo "stopping tomcat from $CATALINA_HOME"
sudo -u glassfish $CATALINA_HOME/bin/catalina.sh stop
;;
*)
echo $"usage: $0 {start|stop|restart}"
exit 3
;;
esac
:

Set this script running in run levels:

sudo chmod a+x /etc/init.d/tomcat #configure Glassfish for autostart on ubuntu boot
sudo update-rc.d tomcat defaults #if apache2 is installed: #stopping apache2 sudo /etc/init.d/apache2 stop #removing apache2 from autostart update-rc.d -f apache2 remove

Get SSL certificate using Letsencrypt

Following https://certbot.eff.org/lets-encrypt/ubuntuxenial-other

Add multiverse repository to Ubuntu :

sudo apt-get update
sudo apt-get install software-properties-common
sudo add-apt-repository universe
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot

Run letsencrypt to generate a certificate if you already don’t have a running server

certbot certonly --manual -d online-utility.org -d www.online-utility.org --agree-tos --email mladen.adamovic@gmail.com

If you have a running server, read below about ACME support servlet and script for auto renewals but don’t forget after you finished certificates to make it readable by other users since user glassfish is going to need it.

Make certificated readable by other users:

chmod o+rx /etc/letsencrypt
chmod o+rx /etc/letsencrypt/archive 
chmod o+rx /etc/letsencrypt/archive/online-utility.org
chmod o+rx /etc/letsencrypt/live

Also, you might need a script which fixes file permissions fix_letsencrypt_chmod.sh:

#!/bin/bash
chmod o+rx /etc/letsencrypt
chmod -R o+rx /etc/letsencrypt/*

You might need to run this if letsencrypt (certbot) messes up file rights for other users (glassfish in this case).

Setup Tomcat Native (APR) to enable a native connector

This part is from http://www.sheroz.com/pages/blog/installing-apache-tomcat-native-linux-ubuntu-1204.html

Install needed files

apt-get install build-essential libapr1 libapr1-dev
apt-get install make gcc openssl libssl-dev

Install APR package, from http://apr.apache.org/

wget http://www-us.apache.org/dist/apr/apr-1.5.2.tar.gz
tar zxvf apr-1.5.2.tar.gz
cd apr-1.5.2
sudo ./configure 
sudo make 
sudo make install

You should see the compiled file as

/usr/local/apr/lib/libapr-1.a

Download, compile and install Tomcat Native source package tomcat-native

wget http://www-us.apache.org/dist/tomcat/tomcat-connectors/native/1.2.8/source/tomcat-native-1.2.8-src.tar.gz
tar -xzf tomcat-native-1.2.8-src.tar.gz
cd tomcat-native-1.2.8-src/native
./configure --with-apr=/usr/local/apr --with-java-home=$JAVA_HOME --prefix=/home/glassfish/apache-tomcat-9.0.68
make make install

Create or edit the $CATALINA_HOME/bin/setenv.sh file with following line:

export LD_LIBRARY_PATH='$LD_LIBRARY_PATH:/usr/local/apr/lib'

6. Restart tomcat and enjoy the desired result.

7. Configure Tomcat with APR

<Connector port="8181" protocol="org.apache.coyote.http11.Http11AprProtocol"
           SSLCertificateFile="/etc/letsencrypt/live/online-utility.org/cert.pem"
           SSLCertificateKeyFile="/etc/letsencrypt/live/online-utility.org/privkey.pem"
           SSLCertificateChainFile="/etc/letsencrypt/live/online-utility.org/chain.pem"
           SSLVerifyClient="optional" SSLProtocol="TLSv1+TLSv1.1+TLSv1.2"
           connectionTimeout="20000" acceptCount="30000"
           acceptorThreadCount="2" 
           compression="on" maxConnections="50000" maxThreads="400"
           compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,image/svg+xml,image/svg" 
           useSendfile="false"
           maxHttpHeaderSize="16392" SSLEnabled="true"
           enableLookups="false" 
           scheme="https" secure="true"   clientAuth="false"  useBodyEncodingForURI="true"  
/>

Enable HSTS in Tomcat

uncomment and edit lines in ~glassfish/apache-tomcat/conf/web.xml:

<filter>
        <filter-name>httpHeaderSecurity</filter-name>
        <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
        <init-param>
           <param-name>hstsMaxAgeSeconds</param-name>
           <param-value>31536000</param-value>
        </init-param>
        <async-supported>true</async-supported>
    </filter>

<filter-mapping>
        <filter-name>httpHeaderSecurity</filter-name>
        <url-pattern>/*</url-pattern>
        <url-pattern>*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
</filter-mapping>

Configure HTTP redirect application with support to ACME challenge

I’m using a web app with the one server which forwards HTTP to HTTPS but accepts ACME challenge:

package redirect;

import commons.FilesOperations;
import commons.UsualHtmlUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author mladen
 */
@WebServlet(name = "RedirectToHttpsWithAcme", urlPatterns = {"/*", "/"})
public class RedirectToHttpsWithAcme extends HttpServlet {

  /**
   * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  protected void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    String requestUrl = request.getRequestURL().toString();
    if (requestUrl.contains(".well-known/acme-challenge/")) {
      int indexFilename = requestUrl.lastIndexOf("/") + 1;
      boolean wasError = true;
      if (indexFilename > 0 && indexFilename < requestUrl.length()) {
        String filename = requestUrl.substring(indexFilename);
        File existingFile = new File("/tmp/letsencrypt/public_html/.well-known/acme-challenge/" +  filename);
        if (existingFile.exists()) {
          response.setContentType("text/plain");
          OutputStream out = response.getOutputStream();
          FileInputStream in = new FileInputStream(existingFile);
          FilesOperations.inputStreamToOutputStream(in, out);
          wasError = false;
        }
      }
      if (wasError) {
        throw new ServletException("invalid requestUrl " + requestUrl);
      }
    } else {
      response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
      int indexOfSlash = requestUrl.indexOf("//");
      if (indexOfSlash > 0) {
        String redirectUrl = "https:" + requestUrl.substring(indexOfSlash);
        String queryString = request.getQueryString();
        if (queryString != null && queryString.length() > 0) {
          redirectUrl += "?" + UsualHtmlUtils.encodeURL(queryString);
        }
        response.setHeader("Location", redirectUrl);
      } else {
        throw new ServletException("invalid requestUrl " + requestUrl);
      }

    }
  }

  // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
  /**
   * Handles the HTTP <code>GET</code> method.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    processRequest(request, response);
  }

  /**
   * Handles the HTTP <code>POST</code> method.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    processRequest(request, response);
  }

  /**
   * Returns a short description of the servlet.
   *
   * @return a String containing servlet description
   */
  @Override
  public String getServletInfo() {
    return "Short description";
  }// </editor-fold>

}

It’s important to have Tomcat configured with this application (I call it appBaseRedirectToHttpsWithAcme) on port 8080 (since port 80 is forwarded to port 8080) in our iptables configuration:

<Service name="CatalinaHttp">
   <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000" acceptCount="30000"
               acceptorThreadCount="2" 
               compression="on" maxConnections="50000" maxThreads="100"
               compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,image/svg+xml,image/svg" 
               useSendfile="false" />
   <Engine name="CatalinaHttp" defaultHost="localhost">
      <Host name="www.online-utility.org"  appBase="appBaseRedirectToHttpsWithAcme"  unpackWARs="true" autoDeploy="true">
            <Alias>www.online-utility.org</Alias>
            <Alias>new.online-utility.org</Alias>
            <Alias>online-utility.org</Alias>
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="http_www_online_utility_org_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
    </Service>


Setup a script for letsencrypt auto renewals

Lets call this script renew_cert.sh

#!/bin/bash
letsencrypt certonly --webroot --webroot-path /tmp/letsencrypt/public_html -d online-utility.org -d www.online-utility.org -d ww2.online-utility.org -d new.online-utility.org --agree-tos --email mladen.adamovic@
if [ $? != 0 ]; then
   date | mail -s "Lets encrypt renew certificate fails for online-utility.org" mladen.adamovic@gmail.com
else
   /root/fix_letsencrypt_chmod.sh
   /etc/init.d/tomcat restart
fi

Don’t forget to make it executable:

chmod +x renew_cert.sh

Ensure this script is working as intended:

./renew_cert.sh

When it is working, it is time to put it into a cron job:

crontab -e

add the line to run it each month:

# m h  dom mon dow   command
0 10 1 * * /root/renew_cert.sh

Increase limits for heavy-duty websites

If you are going to have a large number of connections per second, don’t forget to add to /etc/security/limits.conf

* soft nofile 1000000
* hard nofile 1000000
root soft nofile 1000000
root hard nofile 1000000

Optional addons

Don’t forget other Linux stuff you might want like

apt-get install smartmontools
apt-get install postfix

If you are going to send email from Tomcat, don’t forget to add javax.mail.jar to /lib

Also to configure postfix to use letsencrypt certificates, please look up: https://www.upcloud.com/support/secure-postfix-using-lets-encrypt/

If you seriously want to send emails, you might need DKIM support (these private/public keys will be different from letsencrypt keys, it’s self-signed: https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-dkim-with-postfix-on-debian-wheezy )

Also, limit the number of emails send by adding to /etc/postfix/main.cf :

smtp_destination_concurrency_limit = 2
smtp_destination_rate_delay = 1s
smtp_extra_recipient_limit = 10

default_destination_concurrency_limit=2
default_destination_rate_delay=1s

Rember to setup a monitoring, for that purposes I’m using self-monitoring script as a cron job.

Congratulations

Everything should be setup now. For help, I’d suggest ask on askubuntu.com and Tomcat mailing list (depending of the actual) problem.