2020/02/12 - [Back-end/Python] - [Pyftpdlib] FTP 서버 만들기 (1) - 설치 및 실행
2020/02/12 - [Back-end/Python] - [Pyftpdlib] FTP 서버 만들기 (2) - 사용자 인증 설정
2020/02/18 - [Back-end/Python] - [Pyftpdlib] FTP 서버 만들기 (3) - 사용자 패스워드 암호화
0. 서론
 지금까지 사용자가 접근할 수 있는 폴더를 지정하고 권한을 부여하고 패스워드를 암호화하면서 보안을 강화시켜 보았다. 하지만, 사용자가 서버와 데이터를 교환할 때 중간에 해커가 가로채간다면 그대로 데이터가 유출될 것이다. 
 HTTP도 이런 취약점이 있었지만 SSL/TLS를 결합하여 서버와 클라이언트가 데이터를 교환할 때 암호화된 패킷으로 주고받는 HTTPS를 내놓으면서 해소되었다. HTTPS처럼 FTP도 SSL/TLS를 결합한 FTPS가 존재한다. SFTP와 혼동할 수 있는데 SFTP는 SSH(Secure Shell)을 이용한 Linux의 유사 FTP이다. FTP는 21번 포트를 사용하고 SFTP는 SSH의 22번 포트를 사용한다.
 그래서, 이번 포스트에서는 SSL/TLS 자체 인증서를 발급받고, pyftpdlib을 이용하여 FTPS를 설정하는 방법에 대해서 알아보도록 하겠다.
1. SSL/TLS 자체 인증서 발급
1-0. 시작하기전에...
- [OpenSSL] SSL/TLS 인증서 발급받기 포스트에서 한번 다루었던 내용이므로 자세한 내용은 해당 포스틀 참조.
1-1. 서버 개인키(Private Key) 생성
openssl genrsa -aes256 -out private.key 2048
1-2. 서버 공개키(Public Key) 생성
openssl rsa -in private.key -pubout -out public.key
1-3. 인증 서명 요청서(CSR, Certificate Signing Request) 생성
openssl req -new -key private.key -out private.csr
1-4. 인증 기관(CA, Certificate Authority) 키 생성
openssl genrsa -aes256 -out root.ca.key 2048
1-5. 인증 기관의 인증 서명 요청서(CSR) 생성
openssl req -x509 -new -nodes -key root.ca.key -days 3650 -out root.ca.pem
1-6. 자체 인증 기관을 통한 인증서(CRT, Certificate) 발급
openssl x509 -req -in private.csr -CA root.ca.pem -CAkey root.ca.key -CAcreateserial -out private.crt -days 3650
2. FPTS 서버
2-1. 스크립트 작성
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | #ftp_server_tls.py import os import sys from hashlib import md5 from pyftpdlib.authorizers import DummyAuthorizer, AuthenticationFailed from pyftpdlib.handlers import TLS_FTPHandler from pyftpdlib.servers import FTPServer FTP_HOST = '0.0.0.0' FTP_PORT = 9021 FTP_ADMIN_DIR = os.path.join(os.getcwd(), 'storage') FTP_USERS_DIR = os.path.join(os.getcwd(), 'storage/users') KEYFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "certs", "private.key")) CERTFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "certs", "private.crt")) class DummyMD5Authorizer(DummyAuthorizer):     def validate_authentication(self, username, password, handler):         if sys.version_info >= (3, 0):             password = password.encode('utf-8')         hash = md5(password).hexdigest()         try:             if self.user_table[username]['pwd'] != hash:                 raise KeyError         except KeyError:             raise AuthenticationFailed def main():     authorizer = DummyMD5Authorizer()     authorizer.add_user('admin', 'c93ccd78b2076528346216b3b2f701e6', FTP_ADMIN_DIR, perm='elradfmwMT')     authorizer.add_user('user', 'b5b73fae0d87d8b4e2573105f8fbe7bc', FTP_USERS_DIR, perm='elr')     handler = TLS_FTPHandler     handler.banner = "Dochi's FTPS Server."     handler.keyfile = KEYFILE     handler.certfile = CERTFILE     handler.authorizer = authorizer     handler.passive_ports = range(60000, 65535)     address = (FTP_HOST, FTP_PORT)     server = FTPServer(address, handler)     server.max_cons = 256     server.max_cons_per_ip = 5     server.serve_forever() if __name__ == '__main__':     main() | cs | 
- 7 ln: SSL/TLS FTP서버를 실행하기 위한 FTPHandler.
- 16~17 ln: 위에서 발급받은 개인키와 인증서의 경로.
- 36 ln: TLS_FTPHandler 객체 생성.
- 39~40 ln: 개인키와 인증서를 등록.
2-2. 실행
$ python ftp_server_tls.py
2-3. FTPS서버 실행 결과

- 서버 실행시 인증서의 패스워드를 입력하는 프롬프트가 실행됨.
  * Enter PEM pass phrase: [패스워드 입력]
- 인증이 완료되면 'starting FTP+SSL server' 문구와 함께 FTPS 서버가 실행됨.
- 접속 프로토콜이 [ ftp:// ]에서 [ ftps:// ]로 변경됨.
  * FileZilla Client와 같은 일부 FTP Client에서는 [ ftpes:// ]로 프로토콜 사용.
2-4. FTP서버에 SSL/TLS 인증서 적용 확인

1) SSL/TLS 인증서 확인 명령어 실행.
   $ openssl s_client -connect localhost:9021 -state -debug -starttls ftp
2) SSL/TLS 인증서가 정상적으로 적용된 것을 확인할 수 있음.
* 이 명령어가 기본적으로 HTTPS 인증서를 확인하는 명령어 인데, FTP에 쓰다보니 HandShake에서 계속 오류가 발생하였다.
* FTPS를 확인하려면 반드시 [ -starttls ftp ] 옵션을 적용해주자.
* HTTPS가 적용된 웹사이트라면 다 확인해볼 수 있다. (HTTPS는 기본 포트로 443을 사용.)
* openssl s_client -connect google.com:443 -state -debug
2-5. FPTS 서버 접속 결과

- FTP 명령어를 통해 접속하면 기존과 비교해서 달라진 점이 없음.
  * HTTPS사이트를 HTTP로 접속해도 알아서 HTTPS로 접속되는 것처럼
  * FTP로 접속해도 FTPS로 접속된 상태임.
- FileZilla와 같은 다른 FTP Client를 사용해보자.


- FileZilla Client를 통해 [ ftpes://localhost:9021 ]에 접속하면 위와 같이 '인증서 검증' 알림창이 뜸.
- 알림창에서 확인을 누르면 [ TLS 연결 수립 ] 로그와 함께 접속에 성공함.
마치며
- 사용자 패스워드 암호화에 이어 FTPS까지 설정해두니 든든하다.
- 그리고 지금은 자체 인증서를 통해서 FTPS를 설정했지만, 기회가 된다면 유료로 구매해서 써보는게 좋을것 같다.

- 예제를 만들다가 중간에 다른 옵션을 추가했었는데, 그것 때문에 FileZilla에서 접속하면 [ -15: An unexpeted TLS packet was received. ] 오류가 발생했다.
- TLS_FTPHandler에 DTP를 적용하려면, TLS_DTPHandler를 사용해야하는데, ThrottledDTPHandler를 적용해서 오류가 발생했다.
'Back-end > Python' 카테고리의 다른 글
| [GraphQL] 무작정 시작하기 (1) - Schema & Query (0) | 2020.04.13 | 
|---|---|
| [SMTP] Python으로 메일 발송 하기. (With. 첨부파일 ) (1) | 2020.02.22 | 
| [Pyftpdlib] FTP 서버 만들기 (3) - 사용자 패스워드 암호화 (0) | 2020.02.18 | 
| [Pyftpdlib] FTP 서버 만들기 (2) - 사용자 인증 설정 (0) | 2020.02.12 | 
| [Pyftpdlib] FTP 서버 만들기 (1) - 설치 및 실행 (0) | 2020.02.12 | 
 
										
									 
										
									 
										
									 
										
									
댓글