Skip to content

Instantly share code, notes, and snippets.

@anjia0532
Last active April 21, 2024 16:36
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save anjia0532/9ebf8011322f43e3f5037bc2af3aeaa6 to your computer and use it in GitHub Desktop.
Save anjia0532/9ebf8011322f43e3f5037bc2af3aeaa6 to your computer and use it in GitHub Desktop.
auto update apisix ssl by acme.sh
#!/usr/bin/env bash
# author anjia0532@gmail.com
# blog https://anjia0532.github.io/
# github https://github.com/anjia0532
# this script depend on jq,check it first
RED='\033[0;31m'
NC='\033[0m' # No Color
if ! [ -x "$(command -v jq)" ]; then
echo -e "${RED}Error: jq is not installed.${NC}" >&2
exit 1
fi
if ! [ -x "$(command -v openssl)" ]; then
echo -e "${RED}Error: openssl is not installed.${NC}" >&2
exit 1
fi
if ! [ -x "$(command -v ~/.acme.sh/acme.sh)" ]; then
echo -e "${RED}Error: acme.sh is not installed.(doc https://github.com/acmesh-official/acme.sh/wiki/How-to-install)${NC}" >&2
exit 1
fi
usage () { echo "Usage : $0 -h <apisix admin host> -p <certificate pem file> -k <certificate private key file> -a <admin api key> -t <print debug info switch off/on,default off>"; }
# parse args
while getopts "h:p:k:a:t:" opts; do
case ${opts} in
h) HOST=${OPTARG} ;;
p) PEM=${OPTARG} ;;
k) KEY=${OPTARG} ;;
a) API_KEY=${OPTARG} ;;
t) DEBUG=${OPTARG} ;;
*) usage; exit;;
esac
done
# those args must be not null
if [ ! "$HOST" ] || [ ! "$PEM" ] || [ ! "$KEY" ] || [ ! "$API_KEY" ]
then
usage
exit 1
fi
# optional args,set default value
[ -z "$DEBUG" ] && DEBUG=off
# print vars key and value when DEBUG eq on
[[ "on" == "$DEBUG" ]] && echo -e "HOST:${HOST} API_KEY:${API_KEY} PEM FILE:${PEM} KEY FILE:${KEY} DEBUG:${DEBUG}"
# get all ssl and filter this one by sni name
cert_content=$(curl --silent --location --request GET "${HOST}/apisix/admin/ssl/" \
--header "X-API-KEY: ${API_KEY}" \
--header 'Content-Type: application/json' | jq "first(.node.nodes[]| select(.value.snis[] | contains(\"$(openssl x509 -in $PEM -noout -text|grep -oP '(?<=DNS:|IP Address:)[^,]+'|sort|head -n1)\")))")
validity_start=$(date --date="$(openssl x509 -startdate -noout -in $PEM|cut -d= -f 2)" +"%s")
validity_end=$(date --date="$(openssl x509 -enddate -noout -in $PEM|cut -d= -f 2)" +"%s")
# create a new ssl when it not exist
if [ -z "$cert_content" ]
then
cert_content="{\"snis\":[],\"status\": 1}"
# read domains from pem file by openssl
snis=$(openssl x509 -in $PEM -noout -text|grep -oP '(?<=DNS:|IP Address:)[^,]+'|sort)
for sni in ${snis[@]} ; do
cert_content=$(echo $cert_content | jq ".snis += [\"$sni\"]")
done
cert_content=$(echo $cert_content | jq ".|.cert = \"$(cat $PEM)\"|.key = \"$(cat $KEY)\"|.validity_start=${validity_start}|.validity_end=${validity_end}")
cert_update_result=$(curl --silent --location --request POST "${HOST}/apisix/admin/ssl/" \
--header "X-API-KEY: ${API_KEY}" \
--header 'Content-Type: application/json' \
--data "$cert_content" )
[[ "on" == "$DEBUG" ]] && echo -e "cert_content: \n${cert_content}\n\ncreate result json:\n\n${cert_update_result}"
else
# get exist ssl id
URI=$(echo $cert_content | jq -r ".key")
ID=$(echo ${URI##*/})
# get exist ssl certificate json , modify cert and key value
cert_content=$(echo $cert_content | jq ".value|.cert = \"$(cat $PEM)\"|.key = \"$(cat $KEY)\"|.id=\"${ID}\"|.update_time=$(date +'%s')|.validity_start=${validity_start}|.validity_end=${validity_end}")
# update apisix ssl
cert_update_result=$(curl --silent --location --request PUT "${HOST}/apisix/admin/ssl/${ID}" \
--header "X-API-KEY: ${API_KEY}" \
--header 'Content-Type: application/json' \
--data "$cert_content" )
[[ "on" == "$DEBUG" ]] && echo -e "cert_content: \n${cert_content}\n\nupdate result json:\n\n${cert_update_result}"
fi
exit 0
@anjia0532
Copy link
Author

anjia0532 commented May 24, 2021

我的脚本,支持检索已有ssl列表是否存在该证书,如果存在则更新,不存在则创建,snis使用OpenSSL读的pem,有效起止时间也是用OpenSSL读的pem

详细步骤,参考我的博客 https://juejin.cn/post/6965778290619449351

@gnjbm1px263cywt7
Copy link

在 docker的neilpang/acme.sh中使用出现问题 neilpang/acme.sh 是基于Alpine Linux打包的
问题一 grep无法使用-P
grep -oP '(?<=DNS:|IP Address:)[^,]+'
修改为
grep -oE '(DNS:|IP Address:)[^,]+' | sed -E 's/DNS:|IP Address://g'

问题二 71行的for无法直接读取字符串变量
修改为使用临时文件的方式

 cert_content="{\"snis\":[],\"status\": 1}"

  # read domains from pem file by openssl
  tmp_file_path="/tmp/`cat /proc/sys/kernel/random/uuid`"
  openssl x509 -in $PEM -noout -text|grep -oE '(DNS:|IP Address:)[^,]+' | sed -E 's/DNS:|IP Address://g' | sort > $tmp_file_path

  while read -r sni ; do
    cert_content=$(echo $cert_content | jq ".snis += [\"$sni\"]")
  done < $tmp_file_path

  rm -f $tmp_file_path

  cert_content=$(echo $cert_content | jq ".|.cert = \"$(cat $PEM)\"|.key = \"$(cat $KEY)\"|.validity_start=${validity_start}|.validity_end=${validity_end}")


  cert_update_result=$(curl --silent --location --request POST "${HOST}/apisix/admin/ssl/" \
   --header "X-API-KEY: ${API_KEY}" \
   --header 'Content-Type: application/json' \
   --data "$cert_content" )

  [[ "on" == "$DEBUG" ]] && echo -e "cert_content: \n${cert_content}\n\ncreate result json:\n\n${cert_update_result}"

@anjia0532
Copy link
Author

我这个脚本是在Ubuntu写的,测试的。Alpine因为自身的gcc或者glibc一类的编译器有问题,经常会导致一些诡异问题(比如 jdk 8某个小版本之后明确不再发布 alpine 版本)。如果不是对容器镜像大小有强迫症的追求,可以试试用 -slim 镜像,兼顾体积和兼容性。(PS我自己用golang写了个基于 acme.sh 的 hook 更新阿里云(证书中心,cdn,dcdn,slb,oss,waf),网关(apisix,kong),腾讯云(ssl,clb,cdn,waf),k8s ingress secret 的小程序,目前没有开源计划,用bash脚本不利于阅读和维护)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment