From d3a20b3369b8438afd87b734758b9727cb182a2a Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Tue, 20 Aug 2013 22:27:32 -0400 Subject: [PATCH] initial commit of some preliminary notes --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++ ec2/new_volume.sh | 6 +++++ ec2/start_instance.sh | 18 +++++++++++++ scripts/mail.sh | 55 ++++++++++++++++++++++++++++++++++++++ scripts/new_volume.sh | 6 +++++ scripts/system.sh | 27 +++++++++++++++++++ tools/editconf.py | 37 ++++++++++++++++++++++++++ tools/get_ubuntu_ami.py | 26 ++++++++++++++++++ 8 files changed, 233 insertions(+) create mode 100644 README.md create mode 100644 ec2/new_volume.sh create mode 100644 ec2/start_instance.sh create mode 100644 scripts/mail.sh create mode 100644 scripts/new_volume.sh create mode 100644 scripts/system.sh create mode 100755 tools/editconf.py create mode 100644 tools/get_ubuntu_ami.py diff --git a/README.md b/README.md new file mode 100644 index 00000000..169979f2 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +Mail in a Box +============= + +One-click deployment of your own mail server and personal cloud (so to speak). + +This draws heavily on Sovereign by Alex Payne (https://github.com/al3x/sovereign) and the "NSA-proof your email in 2 hours" blog post by Drew Crawford (http://sealedabstract.com/code/nsa-proof-your-e-mail-in-2-hours/). + +Deploying to EC2 from the command line +-------------------------------------- + +Sign up for Amazon Web Services. + +Create an Access Key at https://console.aws.amazon.com/iam/home?#security_credential. Download the key and save the information somewhere secure. + +Set up your environment and paste in the two parts of your access key that you just downloaded: + + sudo apt-get install ec2-api-tools + + export AWS_ACCESS_KEY=your_access_key_id + export AWS_SECRET_KEY=your_secret_key + export EC2_URL=ec2.us-east-1.amazonaws.com + export AWS_AZ=us-east-1a + +The first time around, create a new volume (disk drive) to store your stuff. + + source ec2/new_volume.sh + +If you want to reuse an existing volume: + + export VOLUME_ID=...your existing volume id... + +Here we're using the Ubuntu 13.04 amd64 instance-store-backed AMI in the us-east region. You can select another at http://cloud-images.ubuntu.com/locator/ec2/. + +Generate a new "keypair" (if you don't have one) that will let you SSH into your machine after you start it: + + ec2addkey mykey > mykey.pem + chmod go-rw mykey.pem + +Then launch a new instance. We're creating a m1.small instance --- it's the smallest instance that can use an instance-store-backed AMI. So charges will start to apply. + + source ec2/start_instance.sh + +It will wait until the instance is available. + +Log in: + + ssh -i mykey.pem ubuntu@$INSTANCE_IP + +Set up: + + + logout + +Terminate your instance with: + + ec2-terminate-instances $INSTANCE + + diff --git a/ec2/new_volume.sh b/ec2/new_volume.sh new file mode 100644 index 00000000..da88cb0b --- /dev/null +++ b/ec2/new_volume.sh @@ -0,0 +1,6 @@ +export VOLUME_SIZE=1 # in GiB (2^30 bytes) +ec2-create-volume -s $VOLUME_SIZE -z $AWS_AZ > volume.info +export VOLUME_ID=`cat volume.info | awk {'print $2'}` +export VOLUME_IS_NEW=1 +echo Created new volume: $VOLUME_ID + diff --git a/ec2/start_instance.sh b/ec2/start_instance.sh new file mode 100644 index 00000000..64c8eb86 --- /dev/null +++ b/ec2/start_instance.sh @@ -0,0 +1,18 @@ +export AMI=`curl http://cloud-images.ubuntu.com/locator/ec2/releasesTable | python3 tools/get_ubunut_ami.py us-east-1 13.04 amd64 instance-store` +ec2run $AMI -k mykey -t m1.small -z $AWS_AZ | tee instance.info +export INSTANCE=`cat instance.info | grep INSTANCE | awk {'print $2'}` +sleep 5 +while [ 1 ] +do + export INSTANCE_IP=`ec2-describe-instances $INSTANCE | grep INSTANCE | awk {'print $14'}` + if [ -z "$INSTANCE_IP" ] + then + echo "Waiting for $INSTANCE to start..." + else + exit + fi + sleep 6 +done + +echo New instance started: $INSTANCE_IP + diff --git a/scripts/mail.sh b/scripts/mail.sh new file mode 100644 index 00000000..946be7ec --- /dev/null +++ b/scripts/mail.sh @@ -0,0 +1,55 @@ +# Configures a postfix SMTP server. + +sudo DEBIAN_FRONTEND=noninteractive apt-get install -y postfix postgrey + +# TLS configuration +sudo tools/editconf.py /etc/postfix/main.cf \ + smtpd_tls_auth_only=yes \ + smtp_tls_security_level=may \ + smtp_tls_loglevel=2 \ + smtpd_tls_received_header=yes + +# authorization via dovecot +sudo tools/editconf.py /etc/postfix/main.cf \ + smtpd_sasl_type=dovecot \ + smtpd_sasl_path=private/auth \ + smtpd_sasl_auth_enable=yes \ + smtpd_recipient_restrictions=permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination + +sudo tools/editconf.py /etc/postfix/main.cf mydestination=localhost + +# message delivery is directly to dovecot +sudo tools/editconf.py /etc/postfix/main.cf virtual_transport=lmtp:unix:private/dovecot-lmtp + +# domain and user table is configured in a Sqlite3 database +sudo tools/editconf.py /etc/postfix/main.cf \ + virtual_mailbox_domains=sqlite:/etc/postfix/virtual-mailbox-domains.cf \ + virtual_mailbox_maps=sqlite:/etc/postfix/virtual-mailbox-maps.cf \ + virtual_alias_maps=sqlite:/etc/postfix/virtual-alias-maps.cf \ + local_recipient_maps=\$virtual_mailbox_maps + +db_path=/home/ubuntu/storage/mail.sqlite + +sudo su root -c "cat > /etc/postfix/virtual-mailbox-domains.cf" << EOF; +dbpath=$db_path +query = SELECT 1 FROM users WHERE email LIKE '@%s' +EOF + +sudo su root -c "cat > /etc/postfix/virtual-mailbox-maps.cf" << EOF; +dbpath=$db_path +query = SELECT 1 FROM users WHERE email='%s' +EOF + +sudo su root -c "cat > /etc/postfix/virtual-alias-maps.cf" << EOF; +dbpath=$db_path +query = SELECT destination FROM aliases WHERE source='%s' +EOF + +# re-start postfix +sudo service postfix restart + +# allow ports in the firewall +sudo ufw allow smtpd +sudo ufw allow submission + + diff --git a/scripts/new_volume.sh b/scripts/new_volume.sh new file mode 100644 index 00000000..8ab1f1f7 --- /dev/null +++ b/scripts/new_volume.sh @@ -0,0 +1,6 @@ +mkdir storage + +# mount volume + +echo "CREATE TABLE users (email text, password text);" | sqlite3 /home/ubuntu/storage/mail.sqlite; + diff --git a/scripts/system.sh b/scripts/system.sh new file mode 100644 index 00000000..9ae5ee0e --- /dev/null +++ b/scripts/system.sh @@ -0,0 +1,27 @@ +# Base system configuration. + +sudo apt-get update +sudo apt-get -y upgrade + +# Basic packages. + +sudo apt-get -y install sqlite3 + +# Turn on basic services: +# +# ntp: keeps the system time correct +# +# fail2ban: scans log files for repeated failed login attempts and blocks the remote IP at the firewall +# +# These services don't need further configuration and are started immediately after installation. + +sudo apt-get install -y ntp fail2ban + +# Turn on the firewall. First allow incoming SSH, then turn on the firewall. Additional open +# ports will be set up in the scripts that set up those services. +sudo ufw allow ssh +sudo ufw allow domain +sudo ufw allow http +sudo ufw allow https +sudo ufw --force enable + diff --git a/tools/editconf.py b/tools/editconf.py new file mode 100755 index 00000000..0dd21a52 --- /dev/null +++ b/tools/editconf.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 + +import sys, re + +# sanity check +if len(sys.argv) < 3: + print("usage: python3 editconf.py /etc/file.conf NAME=VAL [NAME=VAL ...]") + sys.exit(1) + +# parse command line arguments +filename = sys.argv[1] +settings = sys.argv[2:] + +# create the new config file in memory +found = set() +buf = "" +for line in open(filename): + for i in range(len(settings)): + name, val = settings[i].split("=", 1) + if re.match("\s*" + re.escape(name) + "\s*=", line): + buf += "#" + line + if i in found: break # we've already set the directive + buf += name + "=" + val + "\n" + found.add(i) + break + else: + # did not match any setting name + buf += line + +for i in range(len(settings)): + if i not in found: + buf += settings[i] + "\n" + +# Write out the new file. +with open(filename, "w") as f: + f.write(buf) + diff --git a/tools/get_ubuntu_ami.py b/tools/get_ubuntu_ami.py new file mode 100644 index 00000000..3cd64974 --- /dev/null +++ b/tools/get_ubuntu_ami.py @@ -0,0 +1,26 @@ +import sys, json, re + +# Arguments: +region, version, arch, instance_type = sys.argv[1:] + +# Read bytes from stdin. +dat = sys.stdin.read() + +# Be flexible. The Ubuntu AMI list is invalid JSON by having a comma +# following the last element in a list. +dat = re.sub(r",(\s*)\]", r"\1]", dat) + +# Parse JSON. +dat = json.loads(dat) + +for item in dat["aaData"]: + if item[0] == region and item[2] == version and item[3] == arch and item[4] == instance_type: + ami_link = item[6] + + # The field comes in the form of ami-id + ami_link = re.sub(r"<.*?>", "", ami_link) + + print(ami_link) + break + +