commit 40a1679bda2bb170af4208c258afb9b123219a83 Author: Zhong Lufan Date: Tue Dec 31 18:44:21 2019 +0800 Init commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..37cfad7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a44f633 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode/ +.idea/ +.DS_Store diff --git a/AliyunOpenAPI.sh b/AliyunOpenAPI.sh new file mode 100644 index 0000000..fc1f48e --- /dev/null +++ b/AliyunOpenAPI.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +for c in openssl curl python3; do + if ! command -v ${c} > /dev/null; then + echo "${c}: command not found" + exit 127 + fi +done + +_AliAccessKeyId=$(printenv AliAccessKeyId) +_AliAccessKeySecret=$(printenv AliAccessKeySecret) +_Format=JSON +_SignatureMethod=HMAC-SHA1 +_SignatureVersion=1.0 +_urlencode_pycode="from sys import stdin;from urllib.parse import quote;print(quote(stdin.read(), '-_.~'))" + +# aliapi_rpc +aliapi_rpc() { + [[ $# -lt 6 ]] && return 66 + # 公共查询参数键 + local _ali_common_key=( + "Format" + "AccessKeyId" + "SignatureMethod" + "Timestamp" + "SignatureVersion" + "SignatureNonce" + "Version" + "Action" + ) + # 公共查询参数值 + local _ali_common_value=( + "$_Format" + "$_AliAccessKeyId" + "$_SignatureMethod" + "$(_ali_sign_timestamp)" + "$_SignatureVersion" + "$(_ali_sign_nonce)" + "$3" + "$4" + ) + local _ali_custom_key=() _ali_custom_value=() # 自定义查询参数键值 + read -r -a _ali_custom_key <<< "$5" + read -r -a _ali_custom_value <<< "$6" + local _ali_key=() _ali_value=() # 合并查询键值 + read -r -a _ali_key <<< "${_ali_common_key[*]} ${_ali_custom_key[*]}" + read -r -a _ali_value <<< "${_ali_common_value[*]} ${_ali_custom_value[*]}" + local _http_host=$1 _http_method=$2 + local _query_str="" + local _key _value + for (( i = 0; i < ${#_ali_key[@]}; ++i )); do + _key=${_ali_key[$i]} + _value=${_ali_value[$i]} + [[ $(grep -E "^.+\(\)$" <<< "$_value") == "$_value" ]] && _value=$(${_value//()/}) # 参数值如果是以 () 结束,代表需要执行命令获取值。 + _value=$(_urlencode "$_value") + _query_str+="$_key=$_value&" + done + local _ali_signature + _ali_signature=$(_ali_sign "$_http_method" "$_query_str") + _query_str+="Signature=$(_urlencode "$_ali_signature")" + local _curl_out _result_code _http_url="https://${_http_host}/?${_query_str}" + _curl_out=$(mktemp) + _result_code=$(curl -L -s -X "$_http_method" -o "$_curl_out" --write-out "%{http_code}" "$_http_url" || echo $?) + cat "$_curl_out" && echo + rm -f "$_curl_out" + [[ ${_result_code} -eq 200 ]] && return 0 || return 1 +} + +_ali_sign() { + local _http_method _str _query_str _sign_str + _http_method=$1 + _str=$(echo -n "$2" | tr "&" "\n" | sort) + _query_str=$(echo -n "$_str" | tr "\n" "&") + _sign_str="${_http_method}&$(_urlencode "/")&$(_urlencode "$_query_str")" + echo -n "$_sign_str" | openssl sha1 -hmac "${_AliAccessKeySecret}&" -binary | openssl base64 -e +} + +_ali_sign_timestamp() { + TZ="UTC" date "+%FT%TZ" +} + +_ali_sign_nonce() { + date "+%s%N" +} + +_urlencode() { + echo -n "$1" | python3 -c "$_urlencode_pycode" +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..79017a4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-2020 Zhong Lufan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..40642e5 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +Aliyun OpenAPI Shell SDK +-- +# 介绍 + +这是一个非官方的阿里云 OpenAPI Shell SDK,目的是方便 Shell 脚本直接调用阿里云 OpenAPI,主要是实现了自动计算签名。 + +虽然阿里云官方有 [AliyunCLI](https://github.com/aliyun/aliyun-cli),可以方便的在 Shell 环境下调用阿里云 OpenAPI。不过某些 API (比如 SSL 证书) 它并不支持,所以我就想写一个可能是最好用的 Shell SDK。 + +这个 SDK 理论上支持所有阿里云 RPC OpenAPI,RESTful OpenAPI 暂不支持,因为我暂时没用到,以后可能会考虑支持。 + +# 依赖 + +SDK 主要依赖于 `curl`, `openssl`, `python3` + +其中 `python3` 用于 `urlencode`,因为纯 Shell 实现的 `urlencode` 可用性较低,索性就直接调用 Python 实现,内部的 Python 代码不兼容 Python2,只能运行于 Python3。 + +# 使用 + +使用起来非常简单,只需要在你的 Shell 脚本顶部导出`AliAccessKeyId` 和 `AliAccessKeySecret` 环境变量,然后引用 `AliyunOpenAPI.sh` 即可。 + +```bash +#!/usr/bin/env bash +# 务必使用 export 导出 +export AliAccessKeyId="" # 此处替换为你的阿里云 AliAccessKeyId +export AliAccessKeySecret="" # 此处替换为你的阿里云 AliAccessKeySecret + +. AliyunOpenAPI.sh + +# 自定义 GET 参数的键值顺序要一一对应,而且不能包含空格。 +# 自定义值支持自定义函数,如果你需要包含空格或者读取文件等操作,可以声明一个自定义函数,然后按照此格式填写:函数名(),就比如下面这样。 +# SDK 在处理值的时候会自动执行自定义函数,但是如果自定义函数不存在则会导致获取值失败。 + +get_show_size() { + echo 50 +} + +# 自定义 GET 参数的键 +ali_custom_key=( + "CurrentPage" + "ShowSize" +) +# 自定义 GET 参数的值 +ali_custom_value=( + "1" + "get_show_size()" +) +# 获取阿里云 SSL 证书列表 +# aliapi_rpc 的函数签名如下 +# aliapi_rpc +aliapi_rpc "cas.aliyuncs.com" "GET" "2018-07-13" "DescribeUserCertificateList" "${ali_custom_key[*]}" "${ali_custom_value[*]}" +# 可以通过 $? 是否等于 0 来判断是否执行成功(HTTP CODE == 200) +# 执行成功返回 JSON 格式的结果,执行失败返回 HTTP CODE 或 curl 的退出代码。 +if [[ $? -eq 0 ]]; then + # 执行成功 +else + # 执行失败 +fi + +``` + +更多使用方法可以参考 `example` 下的示例 + +如果你有好的示例,欢迎提交 [PR](https://github.com/Hill-98/aliyun-openapi-shell-sdk/pulls) + +如果你有问题 / BUG 要反馈,也欢迎提交 [Issue](https://github.com/Hill-98/aliyun-openapi-shell-sdk/issues) diff --git a/example/UpdateSSLCert.sh b/example/UpdateSSLCert.sh new file mode 100644 index 0000000..39c77e4 --- /dev/null +++ b/example/UpdateSSLCert.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +# 可用于 acme.sh 的 renewHook 脚本,可以自动更新阿里云 SSL 证书并更新对应 CDN 域名,然后删除对应域名旧的证书。 +# 每次 API 的执行都会检测是否失败,如果失败,会中断脚本执行并返回自定义错误代码 + +# acme.sh 导出的环境变量 +ENV_NAME=( + "CERT_PATH" + "CERT_KEY_PATH" + "CA_CERT_PATH" + "CERT_FULLCHAIN_PATH" + "Le_Domain" +) +# 检查环境变量是否存在 +for value in "${ENV_NAME[@]}" ; do + printenv "$value" > /dev/null || exit 1 +done + +# 获取证书自定义函数 +get_cert() { + sed -e "/^$/d" "$(printenv CERT_FULLCHAIN_PATH)" # 使用 sed 删除掉证书文件的空行 +} +# 获取密钥自定义函数 +get_key() { + cat "$(printenv CERT_KEY_PATH)" +} + +# 导出 AliAccessKeyId 和 AliAccessKeySecret +export AliAccessKeyId="" +export AliAccessKeySecret="" + +DOMAIN=$(printenv Le_Domain) +CERT_NAME="${DOMAIN}-$(date +%s)" # 证书名称 +# 需要更新证书的 CDN 域名列表 +DOMAIN_LIST=( + "example.example.com" +) +# shellcheck disable=SC1091 +. ../AliyunOpenAPI.sh + +ali_custom_name=( + "CurrentPage" + "ShowSize" +) +# 获取第一页的 50 个结果,如果你的证书列表条目较多,可以考虑增加获取数量。 +ali_custom_value=( + "1" + "50" +) +# 获取证书列表 +result=$(aliapi_rpc "cas.aliyuncs.com" "GET" "2018-07-13" "DescribeUserCertificateList" "${ali_custom_name[*]}" "${ali_custom_value[*]}" || exit 101) +# 使用 jq 处理返回的 JSON 数据并提取出匹配当前证书域名的证书列表的 ID,用于稍后的删除旧证书操作。 +cert_list=$(echo "$result" | jq -cr ".CertificateList|map(select(.common == \"${DOMAIN}\"))|map(.id)|.[]") +ali_custom_name=( + "Cert" + "Key" + "Name" +) +# 使用自定义函数获取证书和密钥,保证内容可以被安全的传递过去 +ali_custom_value=( + "get_cert()" + "get_key()" + "$CERT_NAME" +) +# 上传新的证书 +aliapi_rpc "cas.aliyuncs.com" "GET" "2018-07-13" "CreateUserCertificate" "${ali_custom_name[*]}" "${ali_custom_value[*]}" || exit 102 +# 设置 CDN 域名列表使用新的证书 +for domain in "${DOMAIN_LIST[@]}"; do + ali_custom_name=( + "DomainName" + "ServerCertificateStatus" + "CertName" + "CertType" + ) + ali_custom_value=( + "$domain" + "on" + "$CERT_NAME" + "cas" + ) + aliapi_rpc "cdn.aliyuncs.com" "GET" "2018-05-10" "SetDomainServerCertificate" "${ali_custom_name[*]}" "${ali_custom_value[*]}" || _exit 103 "Set cdn domain cert fail: $domain" +done +# 删除旧的证书 +for id in ${cert_list}; do + ali_custom_name=( + "CertId" + ) + ali_custom_value=( + "$id" + ) + aliapi_rpc "cas.aliyuncs.com" "GET" "2018-07-13" "DeleteUserCertificate" "${ali_custom_name[*]}" "${ali_custom_value[*]}" || _exit 104 "Delete old cert fail: $id" +done