EC2 HTTP 웹서버를 HTTPS로 바꾸기
이 작업은 현재 HTTP로 서비스 되고 있는 내 플라스크 서버를 SSL을 적용한 HTTPS 서비스로 바꾸고 적용하는 과정을 정리했다. 무료 인증서 프로젝트인 Letsencript는 정식도메인에서 서비스되는 서버에만 인증서를 발급한다.
현재 가비아로부터 duckhoo.site를 구매해서 Route53을 이용해 도메인서버를 운영하고 있다. 1) infra.duckhoo.site 의 이름으로 현재 내 탄력적 IP로 서비스되고 있는 EC2서버에 정식도메인을 부여 2) HTTPS로 서버가 운영되고 3) 최종적으로 duckhoo.site 클라이언트가 HTTPS 서비스를 지원하는게 최종적 목적지다. (꼭 HTTPS를 해야하는건 아니다. ELB를 활용하면 VPC단에서 HTTPS서비스를 제공할 수 있다.)
적용에 사용할 환경: EC2(linux AMI), EIP, Route53, 정식도메인, cert-bot, S3 호스팅, Cloud Front
Route53에서 새로운 레코드를 만들고 소유중인 도메인의 하위 도메인으로 하나를 지정해줬다. 아래에 입력되는 아이피는 탄력적아이피이며 (Elastic IP) 는 AWS 프리티어에 1개 제공된다.
통신이 정상적으로 된다.
- SSH EC2 접속
- certbot-auto 다운로드 및 실행
$ wget https://dl.eff.org/certbot-auto$ chmod a+x certbot-auto
$ chmod a+x certbot-auto
3. 오토봇을 실행한다. 마지막에 정식도메인을 입력해줘야한다.
$ sudo ./certbot-auto --debug -v --server https://acme-v01.api.letsencrypt.org/directory certonly -d infra.duckhoo.site
4. 옵션을 다 안지정해줘서 정보를 2개 물어본다. 첫번째는 email
두번째는 서비스할 webroot다. flask 내장서버를 쓰고 있어서 뭘 입력해줘야하나 고민하다가 프로젝트 폴더에 cert폴더를 만들어서 입력했다.
Input the webroot for infra.duckhoo.site: (Enter 'c' to cancel): /home/ec2-user/DuckhooGosa-server/cert/
5. 인증서가 발급된 모습
이제 인증서를 들고 서비스를 하도록 코드를 짤 차례다. SSL모듈을 사용하려고 pip install ssl 을 하니 아래 에러가 뜨는데, 알아보니 현재 내 flask는 내장웹서버를 쓰기 때문에 별도의 설치가 필요 없었다. (권장 사항은 아니다 나중에 WSGI 를 붙일예정)
SyntaxError: Missing parentheses in call to ‘print’. Did you mean print(‘looking for’, f)?
개발환경에서 먼저 확인을 해보고 싶어서 로컬부터 openssl로 만든 pem으로 서비스해보기로 했다.
$ pip install pyopenssl$ openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 36500
이렇게하면 100년 유효기간의 키가 생겼다. 제3자의 증명을 위한 cert 지만 자기 스스로가 증명하고 자기가 사용하는 형식이라서 브라우저에서 접근하면 경고창이 보이지만 일단 형식은 갖추었다.
flask-app 에 인증서를 등록시킨다. app.config 의 내용은 각자 프로젝트 환경변수 설정방법에 따라 다르므로 자세한 설명은 생략한다.
개발환경의 API서버는 이제 https 서비스를 시작했다. CRA의 내장서버도 옵션을 켜면 자기가 사인한 허술한 인증서로 https 서비스로 시작된다. ( 참고 링크🔗 )한쪽만 Https가 되면 Mixed Contents 에러가 난다. 개발환경에서 충분히 준비가 되었는지 확인하고 이제 배포 테스트용 클라이언트, 배포 테스트 서버 브랜치를 업데이트시킨다.
File "/usr/lib64/python3.6/ssl.py", line 750, in __init__
self._context.load_cert_chain(certfile, keyfile)
PermissionError: [Errno 13] Permission denied
실행을 시켜보니 이런 에러가 뜬다. 넘겨준 인증서를 읽는데 에러가 났다. 인증서 경로를 가면 경로의 파일은 넘겨받은 파일은 Soft 카피다. 원본과 복사본에 실행권한을 주면 된다. 이때 every 레벨로 줘도 동작은 하지만 보안적으로 권장하지는 않는다. Server를 실행할 유저 및 그룹을 만들어서 그 그룹한테 1(+x)의 권한만 주는 방법을 권장하고 있다.
이제 서버를 동작시켜보니 서명을 기반으로 통신은 잘되는데 크롬에서는 NOT Secure 경고를 하고 있다.
원인을 찾아보니 버킷이름 SSL 프로토콜 약속을 위반한것 같다.
Secure Sockets Layer(SSL)와 함께 가상 호스팅 방식의 버킷을 사용할 경우, SSL 와일드카드 인증서는 마침표가 포함되지 않은 버킷에만 연결됩니다. 이 문제를 해결하려면 HTTP를 사용하거나, 인증서 확인 로직을 직접 작성합니다. 가상 호스팅 방식의 버킷을 사용할 경우, 버킷 이름에 마침표(“.”)를 사용하지 않는 것이 좋습니다.
버킷이름을 ‘test.duckhoo.site’ 로 설정해두었는데 SSL 프로토콜에 위배됐다. 버킷이름을 바꿀 방법은 없을까. 찾아봤는데 AWS CLI 로 복사를 하고 동기화하는 방법만 보이길래 새로 만들었다.
다시 CloudFront 설정을했다. 오리진 도메인만 새로운 버킷으로 고쳐주면 Distribution을 새로 만드는 번거로움을 피할 수 있다.
중간에 SSL통신을 크롬이 인증서를 invalid로 판단해서 Not Secure를 보여주는 문제가 있었는데 이걸 해결한 우여곡절을 확인하고 싶다면 여기로🔗
완성된 SSL 통신을 하는 클라이언트와 서버, 클라이언트가 원칙을 위배한 통신을 하면 Not secure가 뜨게되므로 주의.
검증된 오리진(제3자가 인정한 인증서를 가진 호스트)들이 검증된 통신(SSL)을 지키고 있으면 크롬이 Secure 하다고 인정한다. SSL을 사용하면 통신의 오버헤드는 미미하게 증가 하지만 암호화된 통신을 구현할 수 있다. 자세한 원리는 ‘비대칭 공개키'를 검색.