(작성 중...)

보통 메일 서버를 직접 구축하는 짓은 하지 말라고들 한다.
스팸/바이러스 메일을 걸러내는 작업, 내 메일이 스팸처리가 되지 않게 하기 위한 작업, 메일을 중단 없이 수신할 수 있도록 지원 등등...
물론 직접 구축하는 작업이 아무나 할 수 있는게 아니기도 하고.

그래서 개인 도메인을 이용한 메일을 사용하기 위해선 유/무료의 기업용 서비스를 이용하거나 메일건 같은 서비스를 이용한다.
무료는 거의 없어지는 추세에 다음 스마트워크나 메일건 정도 남아있지만 제약들이 좀 불편했고, 유료도 서비스 자체나 과금 정책 등도 마음에 안드는 구석들이 하나씩 있어서 어떻게 하면 좋을까 고민하던 중, 어느정도 손이 가지만 타협할만한 방법이 있는 것 같아서 사용 및 설정을 해보았다.

일반 메일 서버 구축을 하듯이 Postfix + Dovecot 을 설정하지만, 실제 외부와 통신은 AWS SES를 거쳐서 진행하는 방식으로 구성을 하였다.
참고로 이미 이런 형태로 제공하는 가이드 등이 존재하지만, 다양한 계정으로의 수신을 지원하지 않아서 그 부분 등을 고려하여 세팅을 진행하였다.


AWS SES

AWS Simple Email Service 라고, 메일의 송수신을 지원하는 서비스이다.
다만 일반적인 메일 서비스처럼 지원을 하는건 아니고 송/수신이 조금씩 다르다.

일반적인 메일 서비스처럼 바로 사용할 수 있는건 아니지만(그런 목적으론 유료의 AWS WorkMail이 있다)
송신 기준으로 메일 전송에 필요한 각종 서버 설정을 하지 않고 AWS의 신뢰도에 기댈 수 있고, 수신 기준으로도 AWS가 일단 안정적으로 받아주고 시작하는게 좋아보여서 사용하기로 하였다. (바이러스 검사나 스팸 체크도 해주지만 내가 후처리를 따로 해줘야 한다.)

사용 방식 / 비용

먼저 송신은 초당, 일당 전송 제한이 있으며
AWS API를 이용하여 전송(월 62,000건 무료 후 과금)을 하거나, AWS IAM 사용자를 이용한 SMTP로 전송이 가능하며,
1000건당 $0.1 및 용량 기준으로 GB당 $0.12 가 과급된다.

후자의 SMTP 방식을 이용하면 일반 메일 클라이언트에 등록해서 사용할 수 있긴 한데, IAM을 사용하기 때문에 사용자 아이디/비밀번호를 내가 설정할 수도 없고 권한 부여도 까다롭다.

수신은 정한 규칙에 따라 AWS 람다를 실행하거나, S3에 저장하거나, SNS 토픽을 발행하는 등의 방식으로 이루어진다.
이 또한 1000건당 $0.1 (1000건 무로) / 256KB 1 chunk 기준으로 chunk 1000건당 $0.09 과금된다.
작성일 기준으로 한국 리전은 수신을 지원하지 않아 us-east-1 에서 사용하였음.

AWS SES 설정

AWS 에서 메일 송신을 위해선
1. 도메인 혹은 메일 주소 인증
2. 도메인의 경우 DKIM 등 설정
3. 외부로 메일 전송을 위한 Sandbox 해제 요청
작업을 해야하고,

수신을 위해선 MX레코드 설정이 필요하다.
이런 부분은 다른 사람들이 쓴 글이나 AWS 가이드에도 설명이 잘 되어있으니 생략.

이 후 Rule set 설정, Lambda, S3, SNS 등 설정은 분량 관계상 별도 포스팅으로. (작성 후 링크 예정)


Postfix

SMTP 프로토콜을 이용하여 메일을 수발신 할때 사용하는 프로그램. MTA (Mail Transfer Agent) 의 한 종류이다.
예전엔 sendmail 이 사용되었으나 요즘엔 Postfix를 가장 많이 사용한다.

Postfix 기능들

이 프로그램이 하는 역할이 겉으로 보기엔 단순 메일 수발신이지만 자세히 보면 좀 다양하다. 적절히 필요한 것만 취하면 됨.

1. 서버 내에서 내/외부로 메일 전송시 처리
    -> 안 써서 필요없음

2. 외부 클라이언트에서 서버를 통하여 메일 전송 (SMTP Submission)
    -> AWS의 SMTP를 사용해도 되는데, 메일 주소(사용자)별 인증을 쓰고 싶어서 이용.

3. 외부 클라이언트로 전송할때 사용자 인증 / 메일 검증 등
    -> 인증은 Postfix 자체적으로 할 수 있게 설정도 가능한데, 후술할 Dovecot과 연결하여 Dovecot의 인증을 사용하는 방법도 가능하다. 다 따로 구현하면 관리가 번거로워지니 이 방법을 사용할 예정.

4. 내 도메인으로 전송된 메일을 수신
    -> 나의 경우 수신을 AWS가 해주기 때문에 불필요한 기능이지만, 발송 과정에서 Postfix를 사용하고 있고 내 도메인으로 전송하는 경우가 발생할 수 있기 때문에 적절한 설정을 해줘야한다. (예시로 a@naver.com 에서 b@naver.com 으로 메일을 보내는 경우.)
        기본적으론 서버 내 해당하는 경로에 메일 원문을 저장하게 되어 있지만, 난 Dovecot 으로 전송하도록 설정할 예정

5. 메일 수신시 사용자 존재 여부 확인 / alias (다른 주소로 수신하는 기능)
    -> 메일 수신자에 따라 실질적으로 어떤 계정이 해당 메일을 받을지 결정하는 기능..
        support@도메인 으로 전송하면 CS팀에 속한 직원이 모두 해당 메일을 받는다거나.. 하는 목적으로 사용된다.
        난 내 도메인으로 오는 전체 메일을 내가 사용할 한두개 계정으로 받기 위해 사용할건데, 메일 수신을 Postfix만 하는게 아니라서 Postfix 와 Lambda 양쪽에 동일하게 작동하도록 구성해주어야 한다.

6. 다른 서버가 전송하는 메일을 다른 서버로 릴레이
    -> 보안 상의 이유 등으로 보통 비활성화

 

Postfix 설정

분량 관계상 포스팅을 분리하여 작성 (작성 후 링크 예정)


Dovecot

메일 박스를 관리하며 POP3, IMAP 등 프로토콜을 지원하여 다른 프로그램에서 메일을 받아갈 수 있도록 지원한다.
SMTP Submission 기능도 지원하는 것 같긴 하던데, 어차피 Postfix도 필요할 것 같아서 사용하지 않았음.

MTA(Postfix)가 메일을 수신해서 폴더에 저장하면 Dovecot이 알아서 그걸 읽는 방법도 있고,
MTA가 다시 Dovecot으로 LDA(Local Delivery Agent)나 LMTP(SMTP 개량 프로토콜)를 통해 보내줄 수도 있다.
예전엔 LDA를 썼었는데 최근엔 설정 편의성과 성능 때문에 LMTP를 더 많이 쓴다고 한다.

나의 경우 역시 Postfix - LMTP -> Dovecot 로 설정을 하였고,
AWS SES에서 수신한 메일도 AWS SES -> S3 -> SNS -> Lambda - LMTP -> Dovecot 으로 구성을 하였다.
보통 Lambda에서 Dovecot의 메일박스로 파일을 바로 전송시키는 방법을 사용하는데, 이러면 폴더 구조 같은걸 내가 신경 써줘야해서 난 수신자/alias 구분만 Lambda에서 하고 LMTP로 Dovecot에 떠넘겼음.

Dovecot 설정

역시 분량 관계상 포스팅 분리하여 작성 (작성 후 링크 예정)


사용 포트 / TLS

Dovecot 에서 사용할 POP3 와 IMAP 은 TLS 암호화가 추가된 버전인 POP3S(995/tcp), IMAPS(993/tcp) 만 사용할 예정이다.
내부 메일 전달을 위한 LMTP는 Postfix만 쓸거면 unix socket으로 해도 되지만, Lambda에서 사용해야 하니 적당히 임의로 2525/tcp 포트도 추가로 사용.

사용자가 메일 전송을 위해 Postfix에 접속할 때는 SMTPS(tcp/465) 대신 SMTP + STARTTLS 을 사용할거고, 25/tcp 가 OUTBOUND로 차단되는 경우가 많기 때문에, 587/tcp 포트를 사용한다.

즉 587, 993, 995 3개 포트는 모두 열려있어야 하며, 2525 포트는 내부에서 사용할 수 있게만 열려있으면 된다.

TLS 연결을 위해 인증서를 발급받아야 하는데, Let's encrypt(certbot) 을 이용한 웹서버용 인증서를 그대로 사용할 수 있다.
이 인증서의 발급 방법은 생략.
발급된 경로만 잘 확인하여 후에 설정할 때 적절한 경로를 넣어주면 된다.

이 후 발급된 인증서를 Dovecot / Postfix 각각 설정에 적절히 넣어주면 된다.


전체 구성도

아직 시작도 안했지만 벌써부터 복잡한, 전체 구성도는 아래와 같다.

DB와 메일 서버는 동일 서버여도 되지만 난 분리되어 있기 때문에 이미지상 분리해뒀다.

이미지에서 볼 수 있다시피 Lambda가 DB와 EC2 서버에 각각 접근해야 하는데, DB는 당연하고 LMTP도 Public 오픈을 권장하지 않기 때문에 Lambda를 private subnet에 넣고 동일 VPC 네트워크 내에 사설망을 이용하도록 구성하였음.


나중에 사용자 인증을 LDAP 으로 사용한다거나.. 등도 고민 중인데..... 갈길이 멀다.....ㅠ

AWS Lambda 에서 Python 을 쓰면서, AWS 서버에 설치되지 않은 패키지를 사용할 때에는 함수에 포함해서 업로드를 해줘야한다.

 

pip -t 옵션으로 패키지 다운로드 경로를 지정해서 람다 함수 루트에 패키지를 다운받아주면 되지만,

이 경우 디렉토리가 지저분해짐.

 

해결책은 당연하게도, 임의의 폴더를 만들어서 패키지를 다운받고, 해당 경로를 환경변수에 추가해주면 된다.

mkdir packages
# python3 -m pip install [package name] -t ./packages/
python3 -m pip install -r requirements.txt -t ./packages/

 

이 후 Python 핸들러 함수에서 환경변수 추가하기 위한 코드를 파일 최상단에 삽입

import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'packages'))

 

이렇게 하고 packages 폴더는 잊어버리거나 .gitignore 등에 추가해주면 된다.

 

+ 배포 스크립트

AWS Lambda 로 업로드 할때, 난 아래와 같은 스크립트를 만들어서 사용함. (2zip.sh)

 

동작 방식은

1. 몇몇 예외 파일을 제외하고 압축파일 생성

2. aws-cli 를 이용하여 업로드 후

3. 해당 압축파일 삭제

 

#!/bin/bash
LAMBDA_FUNC="Lambda 함수 이름"
zip -rq lambda.zip . -x *__pycache__* 2zip.sh env.sh requirements.txt CONFIG
aws lambda update-function-code --function-name $LAMBDA_FUNC --zip-file fileb://lambda.zip > /dev/null 2>&1
rm lambda.zip

-x 옵션의 인자 등은 상황과 필요에 따라 적절하게 수정을 해주자.

내가 만든 Lambda 함수에서는 AWS RDS 에 접근도 필요했고, 외부 인터넷으로의 접근도 필요했다.

RDS SG(Security Group) 설정상 Lambda 를 VPC 내에 뒀는데, 이러면 Lambda 에서 외부 인터넷으로 접근이 안된다..!


현재 내가 찾은 해결법들은 꼼수 포함 4가지.

1. RDS의 SG설정을 전체 허용, 혹은 Amazon EC2 IP 목록 허용한다.

SG를 전체허용한다면 Lambda 를 VPC 내에 두는 이유가 사라진다.


후자는 Lambda 가 있는 리전의 Amazon EC2 IP 들을 RDS 의 SG 에 때려박아두는 방법.


Amazon 이 사용하는 IP 주소 목록은

https://ip-ranges.amazonaws.com/ip-ranges.json

이 주소에서 JSON 형태로 확인할 수 있다.


관련 설명은 https://docs.aws.amazon.com/ko_kr/general/latest/gr/aws-ip-ranges.html 여기 참고.



대충 위의 접어둔 파이썬 코드를 이용해서,

ap-northeast-2 region 의 EC2 service ip prefix 만 가져온 다음에, 이걸 Security Group 에 넣으면 해결.


Source 에 쉼표로 구분된 값을 넣고 저장하면 알아서 분리된다.


2. AWS에서 제공하는 Managed NAT Gateway를 사용한다.

 VPC 에 Managed NAT Gateway 를 생성하여, VPC 내의 Private subnet 이 외부 인터넷 접속이 가능해주게끔 구성하는 방법

다만 이 NAT Gateway 비용이 월 $40가 넘는다...


3. 외부 인터넷으로 접근하는 함수, VPC 내에서 DB에 접근하는 함수 두개를 만들어 함께 사용한다.

Lambda 비용이 두배로 들겠지만, 어차피 1백만회를 초과할 것 같지도 않고, 초과해도 NAT Gateway 비용보다는 쌀 것 같달까...


다만, VPC 설정이 된 람다함수는 다른 람다함수를 호출할 수 없다. 람다 호출 자체가 외부 인터넷을 사용하는 셈이라..

VPC 밖의 함수를 A(외부 인터넷 연결 가능), VPC 안의 함수를 B 라고 한다면,

항상 A 호출 -> A에서 B를 호출하여 DB작업 -> A에서 결과를 받아서 후처리 -> 반환

의 형태를 취해야한다. 경우에 따라 상관없을 수도 있지만 비효율적으로 될 수도 있는 방법.


4번의 방법을 몰랐기 때문에 난 이렇게 구성해뒀다.


4. ec2 로 NAT instance 를 직접 구축한다.

AWS 에 문서가 있다. -> VPC NAT Instances


후에 시간있을때 적용해볼 예정.

위 글에선 미리 만들어진 AMI를 사용하는데, 나는 기존 Ubuntu Instance 를 어떻게 활용해볼 예정이다.


+ Recent posts