Last active
December 6, 2024 05:59
-
-
Save MagnusOxlund/72bed00bda6e7ab6a8132b9ae839aafc to your computer and use it in GitHub Desktop.
Certbot renewal hook priority
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Script that tests the order and priority of Certbot's four renewal hook types: | |
# * Command line argument hooks | |
# * Certificate renewal configuration hooks | |
# * Global configuration hooks | |
# * Renewal directory hooks | |
# The script has not been verified to be safe. Use at your own risk. | |
# -------------- | |
# WARNING PROMPT | |
# -------------- | |
echo "Warning: this test will erase your global Certbot config file at /etc/letsencrypt/cli.ini as well as files in /etc/letsencrypt/renewal-hooks/" | |
echo "Do you wish to continue?(yes/no)" | |
read input | |
if [ "$input" != "yes" ]; then | |
exit 1 | |
fi | |
# ------------------ | |
# CERT SELECT PROMPT | |
# ------------------ | |
ls /etc/letsencrypt/renewal/ | grep -oP '.*\.conf' | sed 's/\.conf$//' | |
echo "-----" | |
read -p "Select a certificate from the list above to use for testing: " certName | |
# ----------- | |
# DEFINITIONS | |
# ----------- | |
# Test environment | |
basicSafetyCheck() { | |
if [ "$certName" == "" ]; then | |
echo "No test certificate set. Aborting." | |
exit 1 | |
fi | |
if [ ! -f "$localConfig" ]; then | |
echo "Certificate doesn't exist. Aborting." | |
exit 1 | |
fi | |
checkLocalConfig | grep -q "no hooks in local config" | |
if [ $? == 1 ]; then | |
echo "Test certificate's renewal config may not contain hooks. Aborting" | |
exit 1 | |
fi | |
echo -e "commencing test using $certName for" $(certbot --version) | |
} | |
prepareTestEnv() { | |
echo "-- preparing test environment --" | |
checkLocalConfig | |
createLocalConfigBackup | |
checkGlobalConfig | |
checkDirHooks | |
echo "-- environment ready -- " | |
echo -e "\n\n\n" | |
} | |
resetTestEnv() { | |
echo "-- resetting test environment --" | |
removeGlobalConfig | |
removeDirHooks | |
restoreLocalConfig | |
checkGlobalConfig | |
checkDirHooks | |
checkLocalConfig | |
removeLocalConfigBackup | |
echo "-- environment reset --" | |
} | |
# CLI hooks | |
renewSansHooks() { | |
echo "running renew without hooks" | |
sudo certbot renew --cert-name $certName --dry-run --run-deploy-hooks --non-interactive | |
} | |
renewWithCLIHooks() { | |
echo "running renew with CLI hooks" | |
sudo certbot renew --cert-name $certName --dry-run --run-deploy-hooks --non-interactive --pre-hook "echo CLI pre" --post-hook "echo CLI post" --deploy-hook "echo CLI deploy" | |
} | |
renewWithCLIHooksEmptyStr() { | |
echo "running renew with CLI hooks set to empty strings" | |
sudo certbot renew --cert-name $certName --dry-run --run-deploy-hooks --non-interactive --pre-hook "" --post-hook "" --deploy-hook "" | |
} | |
renewWithCLIHooksDirHooks() { | |
echo "running renew with CLI hooks set to directory hooks" | |
sudo certbot renew --cert-name $certName --dry-run --run-deploy-hooks --non-interactive --pre-hook "$dirHookPre" --post-hook "$dirHookPost" --deploy-hook "$dirHookDeploy" | |
} | |
# Local/global config hook keys | |
configHooks=( | |
"pre_hook" | |
"post_hook" | |
"deploy_hook" | |
"renew_hook" | |
"pre-hook" | |
"post-hook" | |
"deploy-hook" | |
"renew-hook" | |
) | |
# Local config hooks | |
localConfig="/etc/letsencrypt/renewal/$certName.conf" | |
createLocalConfigBackup() { | |
echo "creating local config backup" | |
sudo cp $localConfig $localConfig.backup | |
} | |
restoreLocalConfig() { | |
echo "restoring from local config" | |
sudo cp $localConfig.backup $localConfig | |
} | |
removeLocalConfigBackup() { | |
echo "removing local config backup" | |
[ -f $localConfig.backup ] && sudo rm $localConfig.backup | |
} | |
checkLocalConfig() { | |
echo "checking local config" | |
local foundHooks=0 | |
for hook in "${configHooks[@]}"; do | |
if grep -q "$hook" "$localConfig"; then | |
foundHooks=$((foundHooks + 1)) | |
fi | |
done | |
if [ $foundHooks -eq 0 ]; then | |
echo "no hooks in local config" | |
else | |
echo "hooks in local config:" | |
for hook in "${configHooks[@]}"; do | |
grep --color=never "$hook" "$localConfig" | sed -e 's/^/ /'; | |
done | |
fi | |
} | |
setLocalConfigHooks() { | |
echo "setting local config hooks" | |
sudo certbot reconfigure --cert-name $certName --run-deploy-hooks --non-interactive --pre-hook "echo local pre" --post-hook "echo local post" --deploy-hook "echo local deploy" | |
} | |
setLocalConfigHooksEmptyStr() { | |
echo "setting local config hooks to empty strings" | |
sudo certbot reconfigure --cert-name $certName --run-deploy-hooks --non-interactive --pre-hook "" --post-hook "" --deploy-hook "" | |
} | |
setLocalConfigHooksDirHooks() { | |
echo "setting local config hooks" | |
sudo certbot reconfigure --cert-name $certName --run-deploy-hooks --non-interactive --pre-hook "$dirHookPre" --post-hook "$dirHookPost" --deploy-hook "$dirHookDeploy" | |
} | |
# Global config hooks | |
globalConfig="/etc/letsencrypt/cli.ini" | |
setGlobalConfigHooks() { | |
echo "creating global config" | |
sudo touch "$globalConfig" | |
sudo chmod 777 "$globalConfig" | |
sudo echo "pre-hook = \"echo global pre\"" | sudo tee -a "$globalConfig" 1> /dev/null | |
sudo echo "post-hook = \"echo global post\"" | sudo tee -a "$globalConfig" 1> /dev/null | |
sudo echo "deploy-hook = \"echo global deploy\"" | sudo tee -a "$globalConfig" 1> /dev/null | |
} | |
setGlobalConfigHooksDirHooks() { | |
echo "creating global config with hooks pointing to directory hooks" | |
sudo touch "$globalConfig" | |
sudo chmod 777 "$globalConfig" | |
sudo echo "pre-hook = \"$dirHookPre\"" | sudo tee -a "$globalConfig" 1> /dev/null | |
sudo echo "post-hook = \"$dirHookPost\"" | sudo tee -a "$globalConfig" 1> /dev/null | |
sudo echo "deploy-hook = \"$dirHookDeploy\"" | sudo tee -a "$globalConfig" 1> /dev/null | |
} | |
checkGlobalConfig() { | |
echo "checking global config" | |
if [ -f "$globalConfig" ]; then | |
local foundHooks=0 | |
for hook in "${configHooks[@]}"; do | |
if grep -q "$hook" "$globalConfig"; then | |
foundHooks=$((foundHooks + 1)) | |
fi | |
done | |
if [ $foundHooks -eq 0 ]; then | |
echo "no hooks in global config" | |
else | |
echo "hooks in global config:" | |
for hook in "${configHooks[@]}"; do | |
grep --color=never "$hook" "$globalConfig" | sed -e 's/^/ /'; | |
done | |
fi | |
else | |
echo "global config doesn't exist" | |
fi | |
} | |
removeGlobalConfig() { | |
echo "removing global config" | |
[ -f "$globalConfig" ] && sudo rm "$globalConfig" | |
} | |
# Directory hooks | |
dirHookPre="/etc/letsencrypt/renewal-hooks/pre/pre.sh" | |
dirHookPost="/etc/letsencrypt/renewal-hooks/post/post.sh" | |
dirHookDeploy="/etc/letsencrypt/renewal-hooks/deploy/deploy.sh" | |
dirHookPreContent="echo dir pre;" | |
dirHookPostContent="echo dir post;" | |
dirHookDeployContent="echo dir deploy;" | |
setDirHooks() { | |
echo "setting dir hooks" | |
sudo touch "$dirHookPre" | |
sudo touch "$dirHookPost" | |
sudo touch "$dirHookDeploy" | |
sudo chmod 777 "$dirHookPre" | |
sudo chmod 777 "$dirHookPost" | |
sudo chmod 777 "$dirHookDeploy" | |
sudo echo "$dirHookPreContent" | sudo tee "$dirHookPre" 1> /dev/null | |
sudo echo "$dirHookPostContent" | sudo tee "$dirHookPost" 1> /dev/null | |
sudo echo "$dirHookDeployContent" | sudo tee "$dirHookDeploy" 1> /dev/null | |
} | |
checkDirHooks() { | |
echo "checking dir hooks" | |
if [ -f "$dirHookPre" -a -f "$dirHookPost" -a -f "$dirHookDeploy" ]; then | |
if grep -q "$dirHookPreContent" "$dirHookPre" && grep -q "$dirHookPostContent" "$dirHookPost" && grep -q "$dirHookDeployContent" "$dirHookDeploy"; then | |
echo "all dir hooks are in place" | |
else | |
grep -q "$dirHookPreContent" "$dirHookPre" || echo "dir pre-hook corrupt or empty" | |
grep -q "$dirHookPostContent" "$dirHookPost" || echo "dir post-hook corrupt or empty" | |
grep -q "$dirHookDeployContent" "$dirHookDeploy" || echo "dir deploy-hook corrupt or empty" | |
fi | |
elif [ ! -f "$dirHookPre" -a ! -f "$dirHookPost" -a ! -f "$dirHookDeploy" ]; then | |
echo "no dir hooks exist"; | |
else | |
[ -f "$dirHookPre" ] || sudo echo "pre.sh is missing" | |
[ -f "$dirHookPost" ] || sudo echo "post.sh is missing" | |
[ -f "$dirHookDeploy" ] || sudo echo "deploy.sh is missing" | |
fi | |
} | |
removeDirHooks() { | |
echo "removing dir hooks" | |
[ -f "$dirHookPre" ] && sudo rm "$dirHookPre" | |
[ -f "$dirHookPost" ] && sudo rm "$dirHookPost" | |
[ -f "$dirHookDeploy" ] && sudo rm "$dirHookDeploy" | |
} | |
# ----------------- | |
# TEST DEFINITIONS | |
# ----------------- | |
testBaseline() { | |
echo "-- testing baseline renew command without any hooks --" | |
checkLocalConfig | |
checkGlobalConfig | |
checkDirHooks | |
echo -e "\n========== test core begin ==========" | |
renewSansHooks | |
echo -e "========== test core end ==========\n" | |
checkLocalConfig | |
checkGlobalConfig | |
checkDirHooks | |
echo -e "-- baseline test complete --\n\n\n" | |
} | |
# CLI test | |
testCLI() { | |
echo "-- testing CLI hooks --" | |
echo -e "\n========== test core begin ==========" | |
renewWithCLIHooks | |
echo -e "========== test core end ==========\n" | |
checkLocalConfig | |
echo -e "-- CLI test complete --\n\n\n" | |
} | |
# Local config tests | |
testLocal() { | |
echo "-- testing local config hooks --" | |
setLocalConfigHooks | |
checkLocalConfig | |
echo -e "\n========== test core begin ==========" | |
renewSansHooks | |
echo -e "========== test core end ==========\n" | |
restoreLocalConfig | |
checkLocalConfig | |
echo -e "-- local config test complete --\n\n\n" | |
} | |
testCLIvsLocal() { | |
echo "-- testing CLI vs local config hooks --" | |
setLocalConfigHooks | |
checkLocalConfig | |
echo -e "\n========== test core begin ==========" | |
renewWithCLIHooks | |
echo -e "========== test core end ==========\n" | |
checkLocalConfig | |
restoreLocalConfig | |
checkLocalConfig | |
echo -e "-- CLI vs local config test complete --\n\n\n" | |
} | |
# Global config tests | |
testGlobal() { | |
echo "-- testing global config hooks --" | |
setGlobalConfigHooks | |
checkGlobalConfig | |
echo -e "\n========== test core begin ==========" | |
renewSansHooks | |
echo -e "========== test core end ==========\n" | |
removeGlobalConfig | |
checkGlobalConfig | |
echo -e "-- global config test complete --\n\n\n" | |
} | |
testClIvsGlobal() { | |
echo "-- testing CLI vs global config hooks --" | |
setGlobalConfigHooks | |
checkGlobalConfig | |
echo -e "\n========== test core begin ==========" | |
renewWithCLIHooks | |
echo -e "========== test core end ==========\n" | |
checkLocalConfig | |
removeGlobalConfig | |
checkGlobalConfig | |
echo -e "-- CLI vs global config test complete --\n\n\n" | |
} | |
testLocalvsGlobal() { | |
echo "-- testing local config vs global config hooks --" | |
setGlobalConfigHooks | |
checkGlobalConfig | |
setLocalConfigHooks | |
checkLocalConfig | |
echo -e "\n========== test core begin ==========" | |
renewSansHooks | |
echo -e "========== test core end ==========\n" | |
removeGlobalConfig | |
checkGlobalConfig | |
restoreLocalConfig | |
checkLocalConfig | |
echo -e "-- local config vs global config test complete --\n\n\n" | |
} | |
testCLIvsLocalvsGlobal() { | |
echo "-- testing CLI vs local config vs global config hooks --" | |
setGlobalConfigHooks | |
checkGlobalConfig | |
setLocalConfigHooks | |
checkLocalConfig | |
echo -e "\n========== test core begin ==========" | |
renewWithCLIHooks | |
echo -e "========== test core end ==========\n" | |
checkLocalConfig | |
removeGlobalConfig | |
checkGlobalConfig | |
restoreLocalConfig | |
checkLocalConfig | |
echo -e "-- CLI vs local config vs global config test complete --\n\n\n" | |
} | |
# Directory hook tests | |
testDir() { | |
echo "-- testing dir hooks --" | |
setDirHooks | |
checkDirHooks | |
echo -e "\n========== test core begin ==========" | |
renewSansHooks | |
echo -e "========== test core end ==========\n" | |
removeDirHooks | |
checkDirHooks | |
echo -e "-- dir test complete --\n\n\n" | |
} | |
testCLIvsDir() { | |
echo "-- testing CLI vs dir hooks --" | |
setDirHooks | |
checkDirHooks | |
echo -e "\n========== test core begin ==========" | |
renewWithCLIHooks | |
echo -e "========== test core end ==========\n" | |
checkLocalConfig | |
removeDirHooks | |
checkDirHooks | |
echo -e "-- CLI vs dir test complete --\n\n\n" | |
} | |
testLocalVsDir() { | |
echo "-- testing local config vs dir hooks --" | |
setDirHooks | |
checkDirHooks | |
setLocalConfigHooks | |
checkLocalConfig | |
echo -e "\n========== test core begin ==========" | |
renewSansHooks | |
echo -e "========== test core end ==========\n" | |
restoreLocalConfig | |
checkLocalConfig | |
removeDirHooks | |
checkDirHooks | |
echo -e "-- local config vs dir test complete --\n\n\n" | |
} | |
testGlobalVsDir() { | |
echo "-- testing global config vs dir hooks --" | |
setDirHooks | |
checkDirHooks | |
setGlobalConfigHooks | |
checkGlobalConfig | |
echo -e "\n========== test core begin ==========" | |
renewSansHooks | |
echo -e "========== test core end ==========\n" | |
removeGlobalConfig | |
checkGlobalConfig | |
removeDirHooks | |
checkDirHooks | |
echo -e "-- global config vs dir test complete --\n\n\n" | |
} | |
testCLIvsLocalVsGlobalVsDir() { | |
echo "-- testing CLI vs local config vs global config vs dir hooks --" | |
setDirHooks | |
checkDirHooks | |
setGlobalConfigHooks | |
checkGlobalConfig | |
setLocalConfigHooks | |
checkLocalConfig | |
echo -e "\n========== test core begin ==========" | |
renewWithCLIHooks | |
echo -e "========== test core end ==========\n" | |
checkLocalConfig | |
restoreLocalConfig | |
checkLocalConfig | |
removeGlobalConfig | |
checkGlobalConfig | |
removeDirHooks | |
checkDirHooks | |
echo -e "-- CLI vs local config vs global config vs dir test complete --\n\n\n" | |
} | |
testDirNoRepeatCLI() { | |
echo "-- testing directory hook "no repeat" policy against CLI hooks--" | |
setDirHooks | |
checkDirHooks | |
echo -e "\n========== test core begin ==========" | |
renewWithCLIHooksDirHooks | |
echo -e "========== test core end ==========\n" | |
removeDirHooks | |
checkDirHooks | |
echo -e "-- dir "no repeat" against CLI test complete --\n\n\n" | |
} | |
testDirNoRepeatLocal() { | |
echo "-- testing directory hook "no repeat" policy against local config hooks--" | |
setDirHooks | |
checkDirHooks | |
setLocalConfigHooksDirHooks | |
checkLocalConfig | |
echo -e "\n========== test core begin ==========" | |
renewSansHooks | |
echo -e "========== test core end ==========\n" | |
restoreLocalConfig | |
checkLocalConfig | |
removeDirHooks | |
checkDirHooks | |
echo -e "-- dir "no repeat" against local test complete --\n\n\n" | |
} | |
testDirNoRepeatGlobal() { | |
echo "-- testing directory hook "no repeat" policy against global config hooks--" | |
setDirHooks | |
checkDirHooks | |
setGlobalConfigHooksDirHooks | |
checkGlobalConfig | |
echo -e "\n========== test core begin ==========" | |
renewSansHooks | |
echo -e "========== test core end ==========\n" | |
removeGlobalConfig | |
checkGlobalConfig | |
removeDirHooks | |
checkDirHooks | |
echo -e "-- dir "no repeat" against global test complete --\n\n\n" | |
} | |
# ----------- | |
# TESTING | |
# ----------- | |
basicSafetyCheck | |
prepareTestEnv | |
testBaseline | |
testCLI | |
testLocal | |
testCLIvsLocal | |
testGlobal | |
testClIvsGlobal | |
testLocalvsGlobal | |
testCLIvsLocalvsGlobal | |
testDir | |
testCLIvsDir | |
testLocalVsDir | |
testGlobalVsDir | |
testCLIvsLocalVsGlobalVsDir | |
testDirNoRepeatCLI | |
testDirNoRepeatLocal | |
testDirNoRepeatGlobal | |
resetTestEnv |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is the test result for Certbot 3.0.1:
As documented in the Certbot manual, directory hooks always run and always run first.
The override priority among the remaining three hook methods is:
Surprisingly, hooks defined in the global Certbot config file (
cli.ini
) override local certificate renewal config hooks. This means that specificity (defined asCLI > local > global
) does not determine the override priority.