寫這篇之前做了蠻多測試實驗,運用許多觀念整合成屬於自己的自動化服務。想在GitLab Page加入免費SSL憑證,又不想手動懶得3個月更新SSL,那就趕緊來參考這篇教你如何在GitLab Page自動更新SSL憑證吧!
Hugo on GitLab Page
注意:本篇是使用Hogo作為示範
Prepare
-
請至GitLab User Settings > Access Tokens
產生一個具有api
scope權限的token(含read_write_repository)
-
請至GitLab Project > Settings > CI/CD > Variables
- add
GITLAB_API_TOKEN
變數,值為剛剛1.
產生的 Access Tokens
- add
RENEW_DAYS_THRESHOLD
變數,值為30
流程圖
- 建立
第一個
gitlab pipeline job, 取得/計算SSL憑證剩餘天數
- 判斷剩餘天數,若
< 30
時,則會進行SSL憑證更新動作
- certbot : 使用
http
challenges 方法
- 觸發
auth.sh
- 取得
$CERTBOT_VALIDATION
(驗證內容), $CERTBOT_TOKEN
(檔名) 變數
- git commit push
static/.well-known/acme-challenge/$CERTBOT_TOKEN
→ 然後此處會觸發第二個
gitlab pipeline進行 gitlab page更新
- wait for .well-known page to become successful (
200
)
- 跟Let’s Encrypt說已經有
/.well-known/acme-challenge/$CERTBOT_TOKEN
這個頁面,請他來確認
- 回傳
fullchain.pem
, privkey.pem
… 參考
- 並
更新
gitlab page domain settings 的 SSL private和public key
- 觸發
cleanup.sh
, 將 .well-known/
page 刪除 (commit update → trigger pipeline → gitlab page update)
Step1 : Add letsencrypt renew job to yaml
.gitlab-ci.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
|
letsencrypt-renew:
image: scottchayaa/alpine-certbot:3.7
variables:
GITLAB_API_TOKEN: $GITLAB_API_TOKEN
RENEW_DAYS_THRESHOLD: $RENEW_DAYS_THRESHOLD
DOMAIN: "yourdomain.com"
script:
- git config --global user.name $GITLAB_USER_LOGIN
- git config --global user.email $GITLAB_USER_EMAIL
- chmod +x ./letsencrypt/*.sh
- ./letsencrypt/renew.sh
only:
- schedules
|
- 1.1. 參考別人寫的文章後,因image速度需求,自己改寫成alpine版本image : scottchayaa/alpine-certbot
- 1.2. 只允許schedules執行此job → 後面會提到如何在GitLab設定Schedules
- 1.3. git config 設定user資訊 → 後面auth.sh、cleanup.sh會使用到
- 1.4. alpine:3.7的certbot version為0.19版,功能正常。但如果要在alpine:3.8安裝certbot,則會出現以下錯誤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
Traceback (most recent call last):
File "/usr/bin/certbot", line 6, in <module>
from pkg_resources import load_entry_point
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 3086, in <module>
@_call_aside
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 3070, in _call_aside
f(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 3099, in _initialize_master_working_set
working_set = WorkingSet._build_master()
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 576, in _build_master
return cls._build_from_requirements(__requires__)
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 589, in _build_from_requirements
dists = ws.resolve(reqs, Environment())
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 783, in resolve
raise VersionConflict(dist, req).with_context(dependent_req)
pkg_resources.ContextualVersionConflict: (idna 2.7 (/usr/lib/python2.7/site-packages), Requirement.parse('idna<2.7,>=2.5'), set(['requests']))
|
目前官方還沒有修正此問題,個人研判是build certbot時使用的python版本或套件出了問題,如果有修正再回來更新此問題
Step2 : Run certbot script
letsencrypt/renew.sh
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
|
#!/bin/sh
end_epoch=$(date -d "$(echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -enddate -noout | cut -d'=' -f2)" "+%s")
current_epoch=$(date "+%s")
days_diff=$((($end_epoch - $current_epoch) / 60 / 60 / 24))
if [ $days_diff -lt $RENEW_DAYS_THRESHOLD ]; then
echo "============================"
echo "Certificate is $days_diff days old, renewing now."
echo "============================"
certbot certonly \
--preferred-challenges http \
--manual \
--agree-tos \
--eff-email \
-m "$GITLAB_USER_EMAIL" \
-d "$DOMAIN" \
--manual-public-ip-logging-ok \
--manual-auth-hook ./letsencrypt/auth.sh \
--manual-cleanup-hook ./letsencrypt/cleanup.sh
echo "============================"
echo "Certbot finished. Updating GitLab Pages domains."
echo "============================"
curl --request PUT --header "PRIVATE-TOKEN: $GITLAB_API_TOKE" --form "certificate=@/etc/letsencrypt/live/$DOMAIN/fullchain.pem" --form "key=@/etc/letsencrypt/live/$DOMAIN/privkey.pem" https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/pages/domains/$DOMAIN
else
echo "============================"
echo "Certificate still valid for $days_diff days, no renewal required."
echo "============================"
fi
|
- 2.1.
days_diff
: 運算$DOMAIN SSL的剩餘時間,與$RENEW_DAYS_THRESHOLD比較,若小於30天
擇進行renew ssl
- 2.2. certbot 指令請參考 : Certbot command-line options
- 2.3. preferred-challenges使用http方式驗證,會詢問
/.well-known/acme-challenge/
裡面有沒有指定的CERTBOT_TOKEN檔案
- 2.4.
--manual-auth-hook
: 驗證測試前的事件;--manual-auth-hook
: 驗證測試後的事件
- 2.5. Certbot指令完成後,會在
/etc/letsencrypt/live/$DOMAIN
產生fullchain.pem
和privkey.pem
,我們透過GitLab API方式Update pages domain
- 2.6. certbot hooks流程圖 :
letsencrypt/auth.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#!/bin/sh
echo -e "---\npermalink: /.well-known/acme-challenge/$CERTBOT_TOKEN/\n---\n$CERTBOT_VALIDATION" > ./ssl.html
git add ./ssl.html
git commit -m "GitLab runner - Add certbot challenge file for certificate renew"
git push https://$GITLAB_USER_LOGIN:$GITLAB_API_TOKE@gitlab.com/$CI_PROJECT_PATH.git HEAD:master
interval_sec=10
max_tries=30
n_tries=0
while [ $n_tries -le $max_tries ]
do
status_code=$(curl -s -o /dev/null -I -w "%{http_code}" https://$DOMAIN/.well-known/acme-challenge/$CERTBOT_TOKEN/)
if [[ $status_code -eq 200 ]]; then
echo $status_code
exit 0
fi
n_tries=$((n_tries+1))
sleep $interval_sec
done
exit 1
|
- 2.7. 因為是使用jekyll,所以我們產生ssl.html,裡面塞permalink導向/.well-known/acme-challenge/$CERTBOT_TOKEN
- 2.8. git add ssl.html 更新gitlab page → 至少要有2個以上的gitlab-runner
- 2.9. 注意curl驗證200的連結務必最後要加'/',否則如果只有
.../$CERTBOT_TOKEN
則會得到return status code 302
letsencrypt/cleanup.sh
1
2
3
4
5
|
#!/bin/sh
git rm ./ssl.html
git commit -m "GitLab runner - Removed certbot challenge file"
git push https://$GITLAB_USER_LOGIN:$GITLAB_API_TOKE@gitlab.com/$CI_PROJECT_PATH.git HEAD:master
exit 0
|
- 2.10. git rm ssl.html 更新gitlab page → certbot驗證完成後刪除ssl.html
Step3 : Set gitlab-ci schedule for the letsencrypt-renew job
GitLab Project > CI/CD > Schedules > New schedule
其他
1
2
3
4
5
6
7
8
|
# 顯示SSL開始/結束時間
$ echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -dates
notBefore=Mar 31 00:38:23 2019 GMT
notAfter=Jun 29 00:38:23 2019 GMT
# 顯示SSL結束時間
$ echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -enddate
notAfter=Jun 29 00:38:23 2019 GMT
|
後記
後來 Gitlab 在 2019.07.22 發佈 12.1 版更新
Get automatic HTTPS certs for Pages using Let’s Encrypt
官方已經在 GitLab pages 增加了 Let’s Encrypt SSL 自動更新功能
操作步驟很簡單 :
- Settings > Pages
- 點選
Details
- 點選
Edit
- 打開開關
Automatic certificate management using Let's Encrypt:
PS : 所以我之前做的那些都是多餘的 XD…..
References