Deploying SMTP server using Postfix and OpenDKIM

Days ago we got some issue with Amazon SES and decide to make our own SMTP relay service. I tried the combinations of sendmail with dim-milter but for some reason I could not make it work so I start another server from scratch which did work this time. This post mainly focus on configuring OpenDKIM as Postfix is fairly straightforward.

The first thing to do is disabling sendmail which is the default SMTP client on Amazon Linux:

service sendmail stop
chkconfig sendmail off

And then installing Postfix and configuring it:

yum install postfix
cp /etc/postfix/main.cf /etc/postfix/main.cf.original 
vi /etc/postfix/main.cf (appendix 01)

Configuration mainly involves adding DKIM settings (such as milter socket info) and modifying receptions restrictions. In the new setting we define sender_access file to contain the list of senders who can relay through our SMTP service.

cat "DOMAIN.COM OK" >> /etc/postfix/sender_access
postmap /etc/postfix/sender_access

Now we are done with Postfix so it is better to check by sending a test mail.

service postfix start
chkconfig postfix on

The main concern of this post, DKIM, is to cryptographically validate the sender is really from that domain (i.e. domain.com). The validation process starts with the SMTP server signing the email using its private key and then the destination mail server tries to match the private key with the public key obtained from senders’ claimed domain DNS. Once the private and public key matched then it means the SMTP server is from the domain it claims to be.

To install the DKIM we need some API from sendmail and openssl:

yum install sendmail-devel openssl-devel

openDKIM is not available in default repository so we will add it and then install it:

rpm -Uvh http://mirror.pnl.gov/epel/6/i386/epel-release-6-8.noarch.rpm
yum --disablerepo=* --enablerepo=epel install opendkim

Once installation finished we proceed to configurations. There are 3 main files to configure: opendkim.conf contains the main configs such as address of signing table file, key table file and the socket address to listen. The key table file contains the list of keys. The signing table defines which domains should be signed by which key. You will also need to add trusted IP addresses of senders in /etc/opendkim/TrustedHosts to grant them access to SMTP.

cp /etc/opendkim.conf /etc/opendkim.conf.original
vi /etc/opendkim.conf (appendix 02)
vi /etc/opendkim/KeyTable  (appendix 03)
vi /etc/opendkim/SigningTable (appendix 04)

Now we need to generate a pair of keys (private and public) which the public key will be added into DNS records of send domain:

mkdir /etc/opendkim/keys/DOMAIN.COM
opendkim-genkey -D /etc/opendkim/keys/DOMAIN.COM/ -d DOMAIN.COM -s default
mv /etc/opendkim/keys/DOMAIN.COM/default.private /etc/opendkim/keys/DOMAIN.COM/default
chown -R opendkim:opendkim /etc/opendkim/keys/DOMAIN.COM
cat /etc/opendkim/keys/DOMAIN.COM/default.txt

Then start the service:

service opendkim start
chkconfig opendkim on

Finally, you have to add a TXT record in your DNS dashboard. The record name should be default._domainkey.DOMAIN.COM and it should contains something like the following (based on the /etc/opendkim/keys/DOMAIN.COM/default.txt):
“v=DKIM1; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHY7Zl+n3SUldTYRUEU1BErHkKN0Ya52gazp1R7FA7vN5RddPxW/sO9JVRLiWg6iAE4hxBp42YKfxOwEnxPADbBuiELKZ2ddxo2aDFAb9U/lp47k45u5i2T1AlEBeurUbdKh7Nypq4lLMXC2FHhezK33BuYR+3L7jxVj7FATylhwIDAQAB”

/etc/postfix/main.cf (appendix 01)

smtpd_milters           = inet:127.0.0.1:2525
non_smtpd_milters       = $smtpd_milters
milter_default_action   = accept

smtpd_error_sleep_time = 1s
smtpd_soft_error_limit = 10
smtpd_hard_error_limit = 20

smtpd_recipient_restrictions = 
	 permit_mynetworks
	 check_sender_access hash:/etc/postfix/sender_access
	 reject_unauth_destination

queue_directory = /var/spool/postfix
command_directory = /usr/sbin
daemon_directory = /usr/libexec/postfix
data_directory = /var/lib/postfix
mail_owner = postfix

inet_interfaces = all
inet_protocols = all

mydestination = $myhostname, localhost.$mydomain, localhost
unknown_local_recipient_reject_code = 550

alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases

debug_peer_level = 2
debugger_command =
	 PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
	 ddd $daemon_directory/$process_name $process_id & sleep 5

sendmail_path = /usr/sbin/sendmail.postfix
newaliases_path = /usr/bin/newaliases.postfix
mailq_path = /usr/bin/mailq.postfix
setgid_group = postdrop
html_directory = no

manpage_directory = /usr/share/man
sample_directory = /usr/share/doc/postfix-2.6.6/samples
readme_directory = /usr/share/doc/postfix-2.6.6/README_FILES

/etc/opendkim.conf (appendix 02)

Canonicalization        relaxed/relaxed
ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts
InternalHosts           refile:/etc/opendkim/TrustedHosts
KeyTable                refile:/etc/opendkim/KeyTable
LogWhy                  Yes
MinimumKeyBits          1024
Mode                    sv
PidFile                 /var/run/opendkim/opendkim.pid
SigningTable            refile:/etc/opendkim/SigningTable
Socket                  inet:2525@127.0.0.1
Syslog                  Yes
SyslogSuccess           Yes
TemporaryDirectory      /var/tmp
UMask                   022
UserID                  opendkim:opendkim

/etc/opendkim/KeyTable (appendix 03)

default._domainkey.DOMAIN.COM:default:/etc/opendkim/keys/DOMAIN.COM/default

/etc/opendkim/SigningTable (appendix 04)

*@DOMAIN.COM default._domainkey.DOMAIN.COM

Deploying and Configuring ELK (elasticsearch,logstash,kibana)

It gave me headache to make the combination of elasticsearch, logstash, kibana and logstash-forwarder work togather properly. The main problems I faced was to compile the go code for logstash-forwarder and the x509v3 self-signed certificate for logstash.

You will need to get the following files:

elasticsearch-1.4.2.tar.gz
logstash-1.5.0.beta1.tar.gz
kibana-4.0.0-beta3.tar.gz

Use “tar xvf file.tar.gz” to extract them.

Elasticsearch and Kibana have config file so we just need to edit, but for logstash create config/logstash.yml file inside.

mkdir logstash-1.5.0.beta1/config/
touch logstash-1.5.0.beta1/config/logstash.yml
mkdir logstash-1.5.0.beta1/

Edit all these file according to the contents you will find in appendix 1,2 and 3.

vi logstash-1.5.0.beta1/config/logstash.yml
vi elasticsearch-1.4.2/config/elasticsearch.yml
vi kibana-4.0.0-beta3/config/kibana.yml

Then we need to create a certificate and private key for logstash:

mkdir cert
cd cert/
touch ssl.conf (use appendix 5 content)
openssl req -x509 -batch -nodes -newkey rsa:2048 -keyout server.key -out server.crt -config ssl.conf -days 1825

And finally run them

elasticsearch-1.4.2/bin/elasticsearch
kibana-4.0.0-beta3/bin/kibana
logstash-1.5.0.beta1/bin/logstash agent -f ~/logstash-1.5.0.beta1/config/logstash.yml

Deploying logstash_forwarder:

To deploy logstash forwarder we need to intsall go and gem-fpm. We are basically creating a rpm or deb installer file.

yum install golang ruby ruby-devel rubygems
git clone https://github.com/elasticsearch/logstash-forwarder.git
cd logstash-forwarder
go build
gem install fpm
make rpm
sudo rpm -ivh logstash-forwarder-*.x86_64.rpm

Once it is installed we need to deal with keys:

sudo cp server.key /usr/local/etc/logstash-forwarder/server.key
sudo cp server.crt /usr/local/etc/logstash-forwarder/server.crt
sudo openssl x509 -in server.crt -text >> /etc/pki/tls/certs/ca-bundle.crt

To configure logstash forwarder create a file and copy the contents of appendix 4. We used /etc/logstash_forwarder.yml file.

And then run it:

/opt/logstash-forwarder/bin/logstash-forwarder -config /etc/logstash_forwarder.yml

Appendix1: Elasticsearch configuration to be added

script.disable_dynamic: true
network.host: localhost
http.cors.allow-origin: "/.*/"
http.cors.enabled: true

Appendix2: Logstash configuration

input {
  lumberjack {
    port => 5000
    type => "logs"
    ssl_certificate => "~/cert/server.crt"
    ssl_key => "~/cert/server.key"
  }
}

filter {
  if [type] == "syslog" {
    grok {
      match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" }
      add_field => [ "received_at", "%{@timestamp}" ]
      add_field => [ "received_from", "%{host}" ]
    }
    syslog_pri { }
    date {
      match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
    }
  }
}

output {
  elasticsearch { host => localhost }
  stdout { codec => rubydebug }
}

Appendix3: Kibana configuration to be edited

port: 8080
host: "10.0.20.7"
elasticsearch: "http://localhost:9200

Appendix4: Logstash_forwarder configurations

{
    "network": {
        "servers": [ "10.0.0.1:5000" ],
        "ssl certificate": "/usr/local/etc/logstash-forwarder/server.crt",
        "ssl key": "/usr/local/etc/logstash-forwarder/server.key",
        "timeout": 15
    },
    
    "files": [
        {
        "paths": [
        "/var/log/syslog",
        "/var/log/auth.log"
        ],
        "fields": { "type": "syslog" }
        },
        {
        "paths": [
        "/var/log/httpd/*.log"
        ],
        "fields": { "type": "apache" }
        }
    ]
    
}

Appendix 5: OpenSSL configuration file for creating certificate:

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
C = TG
ST = Togo
L =  Lome
O = Private company
CN = *

[v3_req]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:TRUE
subjectAltName = @alt_names

[alt_names]
DNS.1 = *
DNS.2 = *.*
DNS.3 = *.*.*
DNS.4 = *.*.*.*
DNS.5 = *.*.*.*.*
DNS.6 = *.*.*.*.*.*
DNS.7 = *.*.*.*.*.*.*
IP.1 = 10.0.0.1
IP.2 = 10.0.0.2
IP.3 = 127.0.0.1

Appendix 6: Alternatives for /etc/pki/tls/certs/ca-bundle.crt

/etc/ssl/certs/ca-certificates.crt
/etc/pki/tls/certs/ca-bundle.crt
/etc/ssl/ca-bundle.pem
/etc/ssl/cert.pem
/usr/local/share/certs/ca-root-nss.crt