Lets’ Encrypt SSL 와일드카드 인증서 발급/갱신

isul   
   조회 11662   추천 2    

https://isulnara.com/wp/archives/1581 (185)

얼마 전부터 Lets’ Encrypt에서 와일드카드 인증서를 지원하기  시작했습니다.

https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579

기존에는 메인 도메인과 필요한 서브 도메인 모두에 대해서 인증서를 발급받아야 되었는데요..

이젠 메인 도메인과 와일드카드 인증서 2개만 받으면 됩니다.

즉, 기존에는 example.com, a.example.com, b.example.com, c.example.com와 같이 메인 도메인과 필요한 모든 서브 도메인에 대해서 인증서가 필요했지만..

이젠 example.com과 *.example.com의 인증서만 받으면 된다는 것입니다.

 

약 2년 전에 제 블로그에 Lets’ Encrypt SSL 인증서 받는 방법에 대해서 글을 올린 적이 있습니다.

https://isulnara.com/wp/archives/1209

해당 게시글에서 웹 서버의 “/.well-known” 설정하는 것과 관련하여 많은 분들이 문의를 주셨고 어려움이 있었습니다.

이번에 소개하는 방법은 웹 서버의 “/.well-known” 설정을 할 필요가 없습니다. 와일드카드 인증서를 발급받기 위해서는 ACME v2 프로토콜을 지원하는 클라이언트를 사용해야 됩니다. 이 말은 DNS의 TXT 레코드를 사용해야 된다는 것을 의미합니다.

 

이 팁을 따라 하면 최초 한 번만 설치해두면 이후 자동으로 인증서를 갱신해줍니다. 그리고 접속자가 아주 많은 상황이 아니라면 모두 무료로 가능합니다.

참고로 SSL 인증서를 발급받고 나서 기억을 더듬어 작성한 것이라 일부 누락된 내용이 있을 수도 있으니 문제 발생 시 댓글 남겨주시기 바랍니다.

이 팁은 Synology NAS에서 인증서를 발급받는 방법을 설명하지만 다른 시스템에도 거의 그대로 적용 가능합니다.

 

1. 준비사항

  • Bootstrap – 여기서는 따로 설명하지 않겠습니다. 검색하여 각 모델에 맞는 것으로 직접 설치 바랍니다.
  • Python 패키지 – [패키지 센터-설정-패키지 소스]에 “http://packages.synocommunity.com/”를 추가,  [일반-신뢰 수준]에 “모든 게시자” 설정 후 패키지 센터의 커뮤니티 탭에 있는 Python 설치
  • Python Module 패키지
  • Telnet or SSH 활성화 – DSM 관리자 페이지에서 활성화합니다.
  • 인증서를 발급받고자 하는 도메인
  • AWS(Amazon Web Services) 계정 – 결제 신용카드 등록까지 필요

 

2. 사용 기술 및 서비스

  • dehydrated – 인증서 발급/갱신 클라이언트 프로그램
  • 아마존 Lightsail – DNS 서비스(3개 도메인까지 무료, 월 3백만 쿼리까지 무료)
  • AWS CLI (명령 줄 인터페이스, Command Line Interface)

 

3. 각종 툴/라이브러리 설치

먼저 이 팁에서 사용하는 각종 툴을 설치합니다.




 $ ipkg install bash git bind
  • bash: 인증서를 발급/갱신하는 쉘 스크립트에서 사용
  • git: dehydrated 소스코드를 내려받기 위해서 사용
  • bind: 도메인의 TXT 레코드 값을 확인하기 위해서 사용

 

4. Amazon Lightsail 설정 및 네임서버 변경

와일드카드 인증서를 발급받으려면 도메인의 TXT 레코드를 추가해야 됩니다. dehydrated 프로그램이 인증서 발급 과정 중 자동으로 TXT 레코드를 갱신하고, 그 값을 확인하는 과정이 있습니다. 그래서 이 팁에서는 API로 TXT 레코드를 추가/갱신할 수 있는 Amazon Lightsail 서비스를 이용합니다. 여러 DNS 서비스를 찾아보았는데 일반적인 환경에서는 무료로 사용할 수 있고 API도 지원해서 Lightsail로 선정했습니다.

아래는 Amazon Lightsail 사이트에 있는 내용입니다.

Amazon Lightsail이란 무엇입니까?

Amazon Lightsail은 간단한 VPS(가상 프라이빗 서버) 솔루션이 필요한 개발자, 소규모 비즈니스, 학생 및 다른 사용자가 AWS를 시작할 수 있는 가장 쉬운 방법입니다. Lightsail은 개발자에게 클라우드에서 웹사이트와 웹 애플리케이션을 배포하고 관리할 수 있는 컴퓨팅, 스토리지 및 네트워킹 용량 및 기능을 제공합니다. Lightsail에는 프로젝트를 빠르게 시작하는 데 필요한 모든 것(가상 머신, SSD 기반 스토리지, 데이터 전송, DNS 관리, 고정 IP)이 포함되어 있으며 고객은 이러한 서비스를 저렴하고 예측 가능한 월간 요금으로 사용할 수 있습니다.

 

Lightsail DNS 관리 비용은 얼마나 됩니까?

Lightsail 내에서 DNS 관리 비용은 무료입니다. 최대 3개의 DNS 영역과 각 DNS 영역에 대해 원하는 만큼 많은 수의 레코드를 만들 수 있습니다. 또한, 이러한 영역에 대해 매달 3백만 건의 DNS 쿼리가 사용할 수 있습니다. 처음 주어진 월별 3백만 개의 쿼리 횟수를 초과하면 DNS 쿼리 1백만 개당 0.40 USD의 요금이 청구됩니다.

 

저는 DNS 쿼리가 월평균 20만  건 정도 되어서 무료 쿼터 내에서 충분히 이용할 수 있습니다. DNS 쿼리의 경우 캐시의 영향이 있어서 직접 계산하기는 어렵고, DNS 서비스 업체에서 해당 정보를 제공해주는지 확인해보세요.

 

이제 DNS 서비스를 이용하기 위해서 Amazon Lightsail 사이트에 접속합니다. 먼저 아마존 웹서비스(AWS)에 가입되어 있어야 됩니다.

https://lightsail.aws.amazon.com/ls/webapp/home/networking

위 사이트에 접속하여 “DNS 영역 생성” 버튼을 눌러 도메인을 입력하고 진행합니다.

그리고, 기존에 사용하던 DNS 레코드를 그대로 모두 입력합니다. DNS 서비스를 이전하기 전에 미리 설정해두는 것이 좋습니다.

DNS 레코드를 모두 설정한 후 화면 제일 아래에 표시되는 “이름 서버”를 자신이 도메인을 등록한 업체의 웹사이트에 접속해서 네임서버의 정보에 모두 입력합니다.

네임서버 정보가 변경되기까지 서비스 업체별로 시간이 조금 다릅니다. DNS 레코드에 새로운 서브도메인을 하나 추가해두고 ping 테스트를 해보면 변경되었는지 쉽게 확인할 수 있습니다.

 

이 팁에서는 필요치 않지만 참고로 Lightsail의 VPS를 저렴한 가격에 이용할 수 있습니다. 아래는 리눅스 인스턴스의 경우 비용입니다.

https://isulnara.com/wordpress/wp-content/uploads/2018/03/lightsail-768x295.png 768w, https://isulnara.com/wordpress/wp-content/uploads/2018/03/lightsail.png 811w" sizes="(max-width: 600px) 100vw, 600px">

 

5. AWSCLI 설치

먼저 패키지에서 Python Module을 설치합니다. awscli를 사용하기 위해 필요합니다. 참고로 전 Python 2.7 패키지와 Python Module 패키지를 설치했습니다.

그리고, 아래 명령을 차례로 입력해서 awscli를 설치합니다.


 
$ /usr/bin/wget https://pypi.python.org/packages/5f/ad/1fde06877a8d7d5c9b60ef
f7de2d452f639916ae1d48f0b8f97bf97e570a/distribute-0.7.3.zip#md5=c6c59594a7b180af
57af8a0cc0cf5b4a
$ unzip distribute-0.7.3.zip
$ cd distribute-0.7.3/
$ python setup.py install
$ easy_install pip
$ pip install awscli --upgrade --user
 

/root/.profile 파일의 PATH에 “~/.local/bin“을 추가합니다.


 
PATH=/opt/bin:/opt/sbin:~/.local/bin:$PATH
export PATH
 

아래 명령으로 쉘에 바로 적용합니다. 또는 터미널에 재접속합니다.


 
$ source ~/.profile
 


아래 명령으로 awscli가 제대로 설치되었는지 확인합니다.


 
$ aws --version
aws-cli/1.14.63 Python/2.7.9 Linux/3.10.35 botocore/1.9.16
 

 

6. AWS 액세스키 발급 및 설정

awscli가 AWS Lightsail에 접근할 수 있도록 액세스 키를 생성합니다. 아래 URL에 접속하여

https://lightsail.aws.amazon.com/ls/webapp/account/advanced
고급 – IAM 콘솔로 이동 – 보안 자격 증명으로 계속 – 액세스 키(액세스 키 ID 및 보안 액세스 키) – 새 액세스 키 만들기를 해서 생성합니다.

터미널로 접속하여 아래 명령을 내려서 생성한 액세스 키를 등록합니다. region에는 us-east-1로 입력하고, output formatjson으로 입력합니다.


 
$ aws configure
AWS Access Key ID [None]: AQIXXXXXXXXXXBQ
AWS Secret Access Key [None]: Va5XXXXXXXXXXCt
Default region name [None]: us-east-1
Default output format [None]: json
 

다시 설정하고자 할 경우 aws configure 명령을 다시 입력하면 됩니다.

이제 아래 명령으로 awscli가 제대로 동작하는지 테스트합니다. 도메인 이름에는 자신의 도메인을 입력하세요.


 
$ aws lightsail get-domain --domain-name example.com
 

5단계에서 설정한 DNS 레코드가 JSON 형식으로 모두 표시되면 제대로 된 것입니다.

 

참고로 A 레코드의 값을 변경하려면 아래 명령을 내리면 됩니다. 유동아이피 환경에서 활용하면 됩니다.


 
$ aws lightsail update-domain-entry --domain-name example.com --domain-entry '{"type":"A","isAlias":false,"target":"123.456.789.123","id":"-1794602113","name":"example.com"}'
 

* DNS 관련 명령어 외에는 되도록 사용하지 않는 것이 좋습니다. 일부 서비스의 경우 비용이 발생될 수 있습니다.

 

7. dehydrated 설치 및 설정

인증서 발급/갱신 클라이언트 프로그램인 dehydrated를 설치합니다. (설치 디렉토리는 원하는대로 변경하면 됩니다.)


 
$ mkdir /volume1/system/usr/local
$ cd /volume1/system/usr/local
$ git clone https://github.com/lukas2511/dehydrated
 

 

이제 아래 URL에서 Amazon Lightsail과 연동하기 위한 스크립트를 받습니다.

https://github.com/isul/dehydrated-hook-lightsail

(위 스크립트는 제가 만든 것이니 혹시나 동작에 문제가 있을 경우 댓글 남겨주시기 바랍니다)


 
$ cd /volume1/system/usr/local/dehydrated
$ mkdir hooks
$ git clone https://github.com/isul/dehydrated-hook-lightsail hooks/lightsail
$ cp hooks/lightsail/domains.txt ./
$ cp hooks/lightsail/run.sh ./
$ cp docs/examples/config ./
 

위 명령은 dehydrated 디렉토리 아래의 hooks/lightsail에 Amazon Lightsail과 연동을 위한 훅 스크립트를 내려받고, dehydrated 디렉토리에 각종 설정 파일과 와일드카드 인증서 발급을 위한 스크립트를 복사하는 것입니다.

 

dehydrated 디렉토리 내에 있는 domains.txt에 자신의 도메인을 설정합니다.


 
example.com *.example.com > example.com
 

 

dehydrated 디렉토리 내에 있는 run.sh에서 아래 설정을 변경합니다. (dehydrated 디렉토리 경로와 자신의 도메인을 설정합니다.)


 
# Path to a directory containing dehydrated
DEHYDRATED_DIR=/volume1/system/usr/local/dehydrated
 
# your domain
DOMAIN=example.com
 

 

dehydrated 디렉토리 내에 있는 config 파일을 아래와 같이 변경합니다.


 
# Which challenge should be used? Currently http-01 and dns-01 are supported
#CHALLENGETYPE="http-01"
CHALLENGETYPE="dns-01"
 
#HOOK=
HOOK=${BASEDIR}/hooks/lightsail/hook-lightsail.sh
 
# E-mail to use during the registration (default: )
CONTACT_EMAIL=you@example.com
 

 

혹시나 인증서 발급 후 인증서를 사용하는 웹서버 등을 재시작할 경우에는 아래 파일을 수정합니다.

/volume1/system/usr/local/dehydrated/hooks/lightsail/hook-lightsail.sh

위 파일의 deploy_cert() 함수 중 126 라인부터 수정합니다. 아래의 경우 시놀로지 나스의 haproxy 패키지를 사용하는 경우입니다.


 
cat ${FULLCHAINFILE} ${KEYFILE} > ${FULLCHAINFILE}.${DOMAIN}
echo "Saved certificate to ${FULLCHAINFILE}.${DOMAIN}"
 
if [[ $DOMAIN != *"*."* ]]; then
  echo "Restarting haproxy..."
  killall haproxy
  /usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/var/haproxy.cfg -p /usr/local/haproxy/var/haproxy.pid
fi
 

haproxy 패키지를 사용할 경우 위 설정과 추가로 아래 명령으로 심볼릭 링크를 생성해두어야 됩니다. (example.com은 자신의 도메인으로 변경 필요)


 
$ rm -rf /usr/local/haproxy/var/crt/default.pem
$ ln -s /volume1/system/usr/local/dehydrated/certs/example.com/fullchain.pem.example.com /usr/local/haproxy/var/crt/default.pem
 

 

 

8. 인증서 발급

아래 명령을 실행합니다. (최초 1회만 필요)


 
$ cd /volume1/system/usr/local/dehydrated
$ ./dehydrated --register --accept-terms
 

 

이제 아래 명령으로 인증서를 발급받습니다.


 
$ cd /volume1/system/usr/local/dehydrated
$ ./run.sh
 

run.sh 스크립트는 위 7번 과정에서 https://github.com/isul/dehydrated-hook-lightsail hooks/lightsail에서 받은 것을 복사한 것으로, 하나의 도메인의 경우 dehydrated -c 명령으로 인증서를 발급받으면 되는데요.. 기본 도메인과 와일드카드 도메인을 하나의 인증서로 받으려고 run.sh를 사용했습니다.

 

위 명령 실행 결과 아래와 같은 내용이 표시되면 인증서 발급이 제대로 된 것입니다.


 
$ ./run.sh
# INFO: Using main config file /volume1/system/usr/local/dehydrated/config
Processing *.isulnara.com
+ Creating new directory /volume1/system/usr/local/dehydrated/certs/isulnara.com ...
+ Signing domains...
+ Generating private key...
+ Generating signing request...
+ Requesting new certificate order from CA...
+ Received 1 authorizations URLs from the CA
+ Handling authorization for isulnara.com
+ 1 pending challenge(s)
+ Deploying challenge tokens...
Creating TXT record(_acme-challenge.isulnara.com) for isulnara.com...
{
"operation": {
"status": "Succeeded",
"resourceType": "Domain",
"isTerminal": true,
"statusChangedAt": 1522582047.5880001,
"location": {
"availabilityZone": "all",
"regionName": "global"
},
"operationType": "CreateDomainEntry",
"resourceName": "isulnara.com",
"id": "1ad32d75-d92a-414a-af0c-7941d932f997",
"createdAt": 1522582047.5880001
}
}
+ Settling down for 20s...
Token value(rCTJTgcf6XOI-niyfWl7tmAiTX5jluyXGZkJbJ27dKo) is valid!
+ Responding to challenge for isulnara.com authorization...
+ Challenge is valid!
+ Requesting certificate...
+ Checking certificate...
+ Done!
+ Creating fullchain.pem...
Saved certificate to /volume1/system/usr/local/dehydrated/certs/isulnara.com/fullchain.pem.*.isulnara.com
+ Done!
# INFO: Using main config file /volume1/system/usr/local/dehydrated/config
Processing isulnara.com with alternative names: *.isulnara.com
+ Checking domain name(s) of existing cert... changed!
+ Domain name(s) are not matching!
+ Names in old certificate: *.isulnara.com
+ Configured names: *.isulnara.com isulnara.com
+ Forcing renew.
+ Checking expire date of existing cert...
+ Valid till Jun 30 10:27:52 2018 GMT (Longer than 30 days). Ignoring because renew was forced!
+ Signing domains...
+ Generating private key...
+ Generating signing request...
+ Requesting new certificate order from CA...
+ Received 2 authorizations URLs from the CA
+ Handling authorization for isulnara.com
+ Found valid authorization for isulnara.com
+ Handling authorization for isulnara.com
+ 1 pending challenge(s)
+ Deploying challenge tokens...
Updating TXT record(oesyg5Jd5LmoRLI-ubLvjKMl0H6rYSpsefEiSnQNlGc) for isulnara.com...
{
"operations": [
{
"status": "Succeeded",
"resourceType": "Domain",
"isTerminal": true,
"statusChangedAt": 1522582094.8970001,
"location": {
"availabilityZone": "all",
"regionName": "global"
},
"operationType": "DeleteDomainEntry",
"resourceName": "isulnara.com",
"id": "fd3ee9d5-ab8d-404d-cb0e-2bd14f6f5e02",
"createdAt": 1522582094.8970001
},
{
"status": "Succeeded",
"resourceType": "Domain",
"isTerminal": true,
"statusChangedAt": 1522582094.9389999,
"location": {
"availabilityZone": "all",
"regionName": "global"
},
"operationType": "CreateDomainEntry",
"resourceName": "isulnara.com",
"id": "e0688724-50c0-4b6d-a1be-8d1840a15db8",
"createdAt": 1522582094.9389999
}
]
}
+ Settling down for 20s...
+ Settling down for 20s...
Token value(oesyg5Jd5LmoRLI-ubLvjKMl0H6rYSpsefEiSnQNlGc) is valid!
+ Responding to challenge for isulnara.com authorization...
+ Challenge is valid!
+ Requesting certificate...
+ Checking certificate...
+ Done!
+ Creating fullchain.pem...
Saved certificate to /volume1/system/usr/local/dehydrated/certs/isulnara.com/fullchain.pem.isulnara.com
Restarting haproxy...
+ Done!
Deleting TXT record(_acme-challenge.isulnara.com) for isulnara.com...
{
"operation": {
"status": "Succeeded",
"resourceType": "Domain",
"isTerminal": true,
"statusChangedAt": 1522582144.112,
"location": {
"availabilityZone": "all",
"regionName": "global"
},
"operationType": "DeleteDomainEntry",
"resourceName": "isulnara.com",
"id": "9dd0a7ab-fe00-4b96-b19a-41524e78f0d7",
"createdAt": 1522582144.112
}
}
 

이제 인증서 발급이 완료되었습니다. 아래 위치에 인증서 파일이 생성되어 있습니다.

/volume1/system/usr/local/dehydrated/certs/example.com

 

혹시나 인증서 발급에 실패할 경우 여러 번 재시도하면 Let’s encrypt의 요청 수 제한에 걸려서 일정 기간 동안 테스트가 불가능하니 config 파일에서 CA 설정을 아래처럼 변경하여 테스트하기 바랍니다. 테스트 후 발급에 성공하면 다시 주석 처리하고 재발급 받으면 됩니다.


 
# Path to certificate authority (default: https://acme-v02.api.letsencrypt.org/directory)
#CA="https://acme-v02.api.letsencrypt.org/directory"
# Staging URL
CA="https://acme-staging-v02.api.letsencrypt.org/directory"
 

인증서를 다시 발급 받으려면 아래 명령으로 관련 데이터를 모두 삭제하고 재시도하면 됩니다.


 
$ cd /volume1/system/usr/local/dehydrated
$ rm -rf ./accounts/*
$ rm -rf ./certs/*
 

 

이제 인증서를 웹 서버에서 사용하도록 설정합니다. 이 부분은 각자 알아서 설정하기 바랍니다.

참고로 저는 haproxy를 사용 중이며 7번 과정에서 deploy_cert() 함수에 자동 적용되도록 설정하였습니다.

 

자신의 웹사이트에 https로 접속해서 인증서가 제대로 표시되는지 확인합니다. 크롬으로 접속하면 아래처럼 표시됩니다.

유효기간은 90일이고, “주체 대체 이름”에 기본 도메인과 와일드카드 도메인 두 개가 모두 표시되면 정상 발급된 것입니다.

기본 도메인 외에 서브 도메인으로 접속해서 인증서가 유효한 것으로 표시되는지도 확인하시기 바랍니다.

 

90일 후 인증서가 만료되기 때문에 cron 등의 스케줄러를 사용해서 run.sh가 주기적으로 실행되도록 설정합니다.

 

이상입니다.





제목Page 2/15
04-12   13983   황진우
04-05   14390   박문형
04-02   11663   isul
03-23   13413   김현린
03-07   15748   pain
03-01   15983   제온프로
02-20   17610   무아
02-13   20565   간장게장
01-01   26882   catstyle
2017-12   29405   차넷컴퓨터
2017-12   26990   츄네다
2017-12   28311   차넷컴퓨터
2017-11   28698   NGC
2017-11   30628   엠브리오
2017-09   35385   PPC허인구
2017-09   35343   이선호
2017-08   36583   DDAYs
2017-07   41529   세벌쉭
2017-07   39988   세벌쉭
2017-07   37532   세벌쉭