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
Advertisements

How did I setup a wordpress hosting in AWS using nginx+php-fpm

Well, for a long time I have been looking for the perfect WordPress hosting setup. I end up using the combination of nginx, php-fpm and memcached. The other option is using apache and php while the difference is the way these two web server handle request and use php. As far as I understand, nginx has a more simple way of dealing with php using php-fpm and the way it handle modules and caching boosts the performance.

So even though the nginx configuration is like a nightmare I start giving it a try and after so many challenges I end up making it working! Obviously, the apache configuration is much simpler but if you need to satisfy millions of users then nginx can be a better option.

In my experience I used an EC2 t2.medium instance (just for 5 hosts) and Amazon Linux AMI as OS. So now we are going to install the services and modules before starting the configurations.

First we install the followings and create a cache directory for nginx fast-cgi:

yum install nginx
yum install php56-fpm
mkdir -p /var/cache/nginx/
yum install php56-mysqlnd #if needed
yum install memcached #if needed

(I assumed your MySQL is hosted in another server.)

And then we create the root directory for websites with a simple default html file. The nginx process is the owner along appropriate permissions cause no one supposed to change it except us!

mkdir -p /var/nginx/sites/default
echo 'You should not be here!' > /var/nginx/sites/default/index.html
chown -R nginx:nginx /var/nginx/sites/default
chmod -R 701 /var/nginx/sites/default 

and then making the website root directory with right owner and permissions (the ftp-user group contains all users for that group and all of them only are able to access to this directory).

mkdir -p /var/nginx/sites/abcd.com
chown -R nginx:abc-ftp /var/nginx/sites/abcd.com
chmod -R 711 /var/nginx/sites/abcd.com 

Appendix 1 and 3 should be available in proper directory and appendix 2 template should be used for adding additional websites to host.

And finally test the configurations: service nginx configtest

Now it’s time to start the engine! service nginx start; service php-fpm start;

now copy your wordpress source in /var/nginx/sites/abcd.com/ folder and you should be able to browse your website(s) and just use the wizard to setup database connection.

Once you entered the wordpress admin panel you can install W2 Total Cache plugins which can be configured to use memcached for a boost in performance. After all the performance is mostly about caching so maybe in another post I explain more about wordpress caching options.

Please remember to change the user and group in /etc/php-fpm.d/www.conf file:
user = nginx
group = nginx

Continue reading

Join Amazon Linux to Domain Controller

I recently had a case where Linux machines were joined to a domain alongside the windows machines. Domain users and groups were accessible and configurable in Linux machines using Samaba. The good thing about domain configuration was that it simple was possible to assign domain accounts to users and they use it to login in both windows and linux machines.

There is no need to manage users on each linux machine which is a great deal for enterprises. Below I walk you through the linux setup and configuration for using Samba, Kerberos and LDAP.

I strongly suggest to backup your data before doing anything. It is a better idea if you can just use a staging machine to test new setup instead of start running commands on your production environment.

The first step is to install Samba; but since Amazon Linux does not include Samba repository we add it and then install it.

wget http://ftp.sernet.de/pub/samba/3.5/centos/5/sernet-samba.repo
mv sernet-samba.repo /etc/yum.repos.d/
yum install samba -y

And then installing Kerberos:

yum install krb5-workstation -y

Obviously the important part is always configuration! just for reference here is the list of config file you may need to change:

  1. /etc/resolve.conf: contains the name servers and you may want to add the domain nameserver too or else you will not able to join it.
  2. /etc/sysconfig/network: contains the network configuations and you may need to change localhost name to a DQDN (fully qualified domain name) like abcd.mydomain.local
  3. /etc/krb5: contains Kerberos network authentication protocol configurations and for sure you will need to update it.
  4. /etc/samba/smb.conf: contains the Samba configurations and necessary for everything to work.
  5. /etc/openldap/ldap.conf: contains LDAP (lightweight directory access protocol) configurations
  6. /etc/hosts: contains local records of hosts’ ip addresses. You might need to change some host’s ip here specially if you changed hostname.

I have copied my configurations as appendices so you can use them as a working reference.

At the end there is a last piece of configuration that needs to be done before trying to join domain. Here we enable some of the authentication configuration:

authconfig --enablekrb5 --enablewinbind --enablemkhomedir --update

Finally we can join to domain by “net ads” command as follow:

net ads join -W MYDOMAIN.LOCAL -U adadminuser -S ad.mydomain.local

Appendix 1: resolve.cong

search ap-southeast-1.compute.internal mydomain.local
nameserver 10.x.x.x
nameserver 10.1.0.2

Appendix 2: kbr5.conf

# make sure server name is capital.

[logging]
 default = FILE:/var/log/krb5libs.log
 kdc = FILE:/var/log/krb5kdc.log
 admin_server = FILE:/var/log/kadmind.log

[libdefaults]
 default_realm = MYDOMAIN.LOCAL
 dns_lookup_realm = true
 dns_lookup_kdc = true
 ticket_lifetime = 24h
 forwardable = yes

[realms]
 MYDOMAIN.LOCAL = {
 }

[domain_realm]
 .mydomain.local = MYDOMAIN.LOCAL
 mydomain.local = MYDOMAIN.LOCAL

[appdefaults]
 pam = {
   debug = false
   ticket_lifetime = 36000
   renew_lifetime = 36000
   forwardable = true
   krb4_convert = false
 }

Appendix 3: smb.conf

[global]
workgroup = MYDOMAIN
realm = MYDOMAIN.LOCAL
security = ads
idmap uid = 16777216-33554431
idmap gid = 16777216-33554431
template homedir = /home/MYDOMAIN/%U
template shell = /bin/bash
winbind use default domain = true
winbind offline logon = false
server string = Samba Server Version %v

log file = /var/log/samba/log.%m
max log size = 50

passdb backend = tdbsam

load printers = yes
cups options = raw

[homes]
comment = Home Directories
browseable = no
writable = yes

[printers]
comment = All Printers
path = /var/spool/samba
browseable = no
guest ok = no
writable = no
printable = yes

Further info:
http://www.server-world.info/en/note?os=CentOS_7&p=samba&f=3
https://wiki.samba.org/index.php/Setup_a_Samba_AD_Member_Server

Alternative Python installation in EC2

There are case that we can not just upgrade the python to match applications requirement. EC2 instance uses Python 2.7 and I needed to use Python 3.4. Upgrading the Python might cause unexpected effects on Amazon scripts so I decided to have two Pythons without any conflict.

To start, download the desired version. Enter the directory and follow these commands:

./configure --prefix=/usr/local --enable-shared LDFLAGS="-Wl,-rpath /usr/local/lib"
make
make altinstall
/usr/local/bin/python3.4 -V

Create custom service to auto start commands on boot

There are many solutions to automatically execute shell commands during the start-up process. My favorite is using services.

So we are going to create a small script in /etc/init.d/ and use chkconfig to auto start it on boot. We call the service “starter”!

vi /etc/init.d/starter

And add the following code to starter:

#!/bin/bash
# chkconfig: 345 99 10
# description: starting some commands at boot
#
case "$1" in
 'start')
	/usr/local/bin/searchd
	python /opt/deploy/main.py
	echo "started" >> /home/user/service.track
	;;
 'stop')
	killall searchd
	killall python
	;;
esac

And finally set permissions and configure the chkconfig:

chmod +x /etc/init.d/starter
chkconfig starter on

Adding custom metric to Amazon Cloud Watch

The default metrics in AWS cloud watch does not include Memory usage or Storage usage data. In my opinion cloud watch has what it takes to monitor a server (and more). So what we are going to do is adding additional metrics to cloud watch.

At first we need to create a user and grant necessary permissions for (we will use the access-key and secret-key of the user):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "cloudwatch:PutMetricData",
        "ec2:DescribeTags"
      ],
      "Effect": "Allow",
      "Resource": [
        "*"
      ]
    }
  ]
}

Then we need to install the dependencies, download the script and extract it:

sudo yum install -y perl-Switch perl-Sys-Syslog perl-LWP-Protocol-https
wget http://ec2-downloads.s3.amazonaws.com/cloudwatch-samples/CloudWatchMonitoringScripts-v1.1.0.zip
unzip CloudWatchMonitoringScripts-v1.1.0.zip
cd aws-scripts-mon

That is it. Done!

To make sure everything is set we do a little test but without sending data to cloud watch:

mon-put-instance-data.pl --mem-util --disk-space-util --disk-path=/ --verbose --aws-access-key-id=youraccesskey --aws-secret-key=yoursecretkey

Now we should add a cron job to send metric in any interval (we used 5 minutes). Enter the “crontab -e” command and add the following:

*/5 * * * * /home/user/aws-scripts-mon/mon-put-instance-data.pl --aws-access-key-id=youraccesskey --aws-secret-key=yoursecretkey --mem-util --disk-space-util --disk-path=/ --from-cron

And restart the crond service

sudo /etc/init.d/crond restart

*It is notable that you can add other metrics too like what we did: –mem-util is for sending memory usage and –disk-space-util is for sending the storage usage.

Installing and Configuring Open VPN access server on Amazon EC2 instance

Alright, in this post we are going to prepare an openvpn server.

*Just note that openVPN access server comes with 2 user free license only and if you have more than 2 users at the same time you need to buy license (for 99$ per year per user if I am not wrong).

Download the rpm and install it:

sudo yum install -y http://swupdate.openvpn.org/as/openvpn-as-2.0.12-CentOS6.x86_64.rpm 

Once it is installed it might launch auto configuration script, just cancel it cause my experience with installing openVPN default configurations on Amazon EC2 end up with some errors. To avoid that we need to change some configurations in auto-config script:

vi /usr/local/openvpn_as/bin/_ovpn-init

And change the configurations to following (you need to add –distro redhat in two lines cause this script can not detect destro):

/usr/local/openvpn_as/scripts/openvpnas_gen_init --distro redhat
/usr/local/openvpn_as/scripts/openvpnas_gen_init --auto --distro redhat

and finally just run the ovpn initialiser script:

sudo /usr/local/openvpn_as/bin/ovpn-init --ec2 --verbose

and just keep following the wizard like prompts (I know its not windows!!). By default this script will add openvpn user with the password you define in the wizard!
You can later simply login to access server using https://ovpn.yourdomain.com/admin/ for administration.

openvpn

Once you are in admin panel go to “Server Network Settings” and “User Permissions” to change default configurations or add/edit users.