committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
201 changed files with 8252 additions and 2366 deletions
-
3.github/workflows/DNS.yml
-
2.github/workflows/DragonFlyBSD.yml
-
4.github/workflows/FreeBSD.yml
-
4.github/workflows/Linux.yml
-
2.github/workflows/MacOS.yml
-
2.github/workflows/NetBSD.yml
-
4.github/workflows/Omnios.yml
-
4.github/workflows/OpenBSD.yml
-
2.github/workflows/PebbleStrict.yml
-
4.github/workflows/Solaris.yml
-
4.github/workflows/Ubuntu.yml
-
2.github/workflows/Windows.yml
-
17.github/workflows/dockerhub.yml
-
13.github/workflows/pr_dns.yml
-
2.github/workflows/pr_notify.yml
-
60.github/workflows/wiki-monitor.yml
-
12Dockerfile
-
835acme.sh
-
88deploy/ali_cdn.sh
-
88deploy/ali_dcdn.sh
-
13deploy/docker.sh
-
1deploy/exim4.sh
-
141deploy/haproxy.sh
-
98deploy/kemplm.sh
-
19deploy/panos.sh
-
120deploy/proxmoxbs.sh
-
11deploy/routeros.sh
-
200deploy/ruckus.sh
-
111deploy/strongswan.sh
-
422deploy/synology_dsm.sh
-
177deploy/truenas.sh
-
325deploy/truenas_ws.sh
-
145deploy/unifi.sh
-
106deploy/vault.sh
-
2deploy/vsftpd.sh
-
500deploy/zyxel_gs1900.sh
-
35dnsapi/dns_1984hosting.sh
-
28dnsapi/dns_acmedns.sh
-
18dnsapi/dns_acmeproxy.sh
-
169dnsapi/dns_active24.sh
-
21dnsapi/dns_ad.sh
-
153dnsapi/dns_ali.sh
-
185dnsapi/dns_alviy.sh
-
20dnsapi/dns_anx.sh
-
23dnsapi/dns_artfiles.sh
-
19dnsapi/dns_arvan.sh
-
20dnsapi/dns_aurora.sh
-
27dnsapi/dns_autodns.sh
-
55dnsapi/dns_aws.sh
-
18dnsapi/dns_azion.sh
-
192dnsapi/dns_azure.sh
-
281dnsapi/dns_beget.sh
-
21dnsapi/dns_bookmyname.sh
-
29dnsapi/dns_bunny.sh
-
25dnsapi/dns_cf.sh
-
17dnsapi/dns_clouddns.sh
-
24dnsapi/dns_cloudns.sh
-
17dnsapi/dns_cn.sh
-
15dnsapi/dns_conoha.sh
-
18dnsapi/dns_constellix.sh
-
26dnsapi/dns_cpanel.sh
-
18dnsapi/dns_curanet.sh
-
47dnsapi/dns_cyon.sh
-
41dnsapi/dns_da.sh
-
21dnsapi/dns_ddnss.sh
-
20dnsapi/dns_desec.sh
-
25dnsapi/dns_df.sh
-
26dnsapi/dns_dgon.sh
-
21dnsapi/dns_dnsexit.sh
-
21dnsapi/dns_dnshome.sh
-
20dnsapi/dns_dnsimple.sh
-
17dnsapi/dns_dnsservices.sh
-
22dnsapi/dns_doapi.sh
-
13dnsapi/dns_domeneshop.sh
-
18dnsapi/dns_dp.sh
-
18dnsapi/dns_dpi.sh
-
14dnsapi/dns_dreamhost.sh
-
18dnsapi/dns_duckdns.sh
-
16dnsapi/dns_durabledns.sh
-
25dnsapi/dns_dyn.sh
-
25dnsapi/dns_dynu.sh
-
28dnsapi/dns_dynv6.sh
-
23dnsapi/dns_easydns.sh
-
163dnsapi/dns_edgecenter.sh
-
25dnsapi/dns_edgedns.sh
-
28dnsapi/dns_euserv.sh
-
12dnsapi/dns_exoscale.sh
-
39dnsapi/dns_fornex.sh
-
19dnsapi/dns_freedns.sh
-
105dnsapi/dns_freemyip.sh
-
21dnsapi/dns_gandi_livedns.sh
-
10dnsapi/dns_gcloud.sh
-
18dnsapi/dns_gcore.sh
-
20dnsapi/dns_gd.sh
-
18dnsapi/dns_geoscaling.sh
-
17dnsapi/dns_googledomains.sh
-
23dnsapi/dns_he.sh
-
45dnsapi/dns_he_ddns.sh
-
18dnsapi/dns_hetzner.sh
-
18dnsapi/dns_hexonet.sh
@ -0,0 +1,60 @@ |
|||||
|
name: Notify via Issue on Wiki Edit |
||||
|
|
||||
|
on: |
||||
|
gollum: |
||||
|
|
||||
|
jobs: |
||||
|
notify: |
||||
|
runs-on: ubuntu-latest |
||||
|
steps: |
||||
|
- name: Checkout wiki repository |
||||
|
uses: actions/checkout@v4 |
||||
|
with: |
||||
|
repository: ${{ github.repository }}.wiki |
||||
|
path: wiki |
||||
|
|
||||
|
- name: Generate wiki change message |
||||
|
run: | |
||||
|
actor="${{ github.actor }}" |
||||
|
sender_url=$(jq -r '.sender.html_url' "$GITHUB_EVENT_PATH") |
||||
|
page_name=$(jq -r '.pages[0].page_name' "$GITHUB_EVENT_PATH") |
||||
|
page_sha=$(jq -r '.pages[0].sha' "$GITHUB_EVENT_PATH") |
||||
|
page_url=$(jq -r '.pages[0].html_url' "$GITHUB_EVENT_PATH") |
||||
|
page_action=$(jq -r '.pages[0].action' "$GITHUB_EVENT_PATH") |
||||
|
now="$(date '+%Y-%m-%d %H:%M:%S')" |
||||
|
|
||||
|
cd wiki |
||||
|
prev_sha=$(git rev-list $page_sha^ -- "$page_name.md" | head -n 1) |
||||
|
if [ -n "$prev_sha" ]; then |
||||
|
git diff $prev_sha $page_sha -- "$page_name.md" > ../wiki.diff || echo "(No diff found)" > ../wiki.diff |
||||
|
else |
||||
|
echo "(no diff)" > ../wiki.diff |
||||
|
fi |
||||
|
cd .. |
||||
|
{ |
||||
|
echo "Wiki edited" |
||||
|
echo -n "User: " |
||||
|
echo "[$actor]($sender_url)" |
||||
|
echo "Time: $now" |
||||
|
echo "Page: [$page_name]($page_url) (Action: $page_action)" |
||||
|
echo "" |
||||
|
echo "----" |
||||
|
echo "### diff:" |
||||
|
echo '```diff' |
||||
|
cat wiki.diff |
||||
|
echo '```' |
||||
|
} > wiki-change-msg.txt |
||||
|
|
||||
|
- name: Create issue to notify Neilpang |
||||
|
uses: peter-evans/create-issue-from-file@v5 |
||||
|
with: |
||||
|
title: "Wiki edited" |
||||
|
content-filepath: ./wiki-change-msg.txt |
||||
|
assignees: Neilpang |
||||
|
env: |
||||
|
TZ: Asia/Shanghai |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
835
acme.sh
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,88 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
# shellcheck disable=SC2034,SC2154 |
||||
|
|
||||
|
# Script to create certificate to Alibaba Cloud CDN |
||||
|
# |
||||
|
# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun |
||||
|
# |
||||
|
# This deployment required following variables |
||||
|
# export Ali_Key="ALIACCESSKEY" |
||||
|
# export Ali_Secret="ALISECRETKEY" |
||||
|
# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi |
||||
|
# |
||||
|
# To specify the CDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates |
||||
|
# export DEPLOY_ALI_CDN_DOMAIN="cdn.example.com" |
||||
|
# If you have multiple CDN domains using the same certificate, just |
||||
|
# export DEPLOY_ALI_CDN_DOMAIN="cdn1.example.com cdn2.example.com" |
||||
|
# |
||||
|
# For DCDN, see ali_dcdn deploy hook |
||||
|
|
||||
|
Ali_CDN_API="https://cdn.aliyuncs.com/" |
||||
|
|
||||
|
ali_cdn_deploy() { |
||||
|
_cdomain="$1" |
||||
|
_ckey="$2" |
||||
|
_ccert="$3" |
||||
|
_cca="$4" |
||||
|
_cfullchain="$5" |
||||
|
|
||||
|
_debug _cdomain "$_cdomain" |
||||
|
_debug _ckey "$_ckey" |
||||
|
_debug _ccert "$_ccert" |
||||
|
_debug _cca "$_cca" |
||||
|
_debug _cfullchain "$_cfullchain" |
||||
|
|
||||
|
# Load dnsapi/dns_ali.sh to reduce the duplicated codes |
||||
|
# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276 |
||||
|
dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)" |
||||
|
# shellcheck source=/dev/null |
||||
|
if ! . "$dnsapi_ali"; then |
||||
|
_err "Error loading file $dnsapi_ali. Please check your API file and try again." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_prepare_ali_credentials || return 1 |
||||
|
|
||||
|
_getdeployconf DEPLOY_ALI_CDN_DOMAIN |
||||
|
if [ "$DEPLOY_ALI_CDN_DOMAIN" ]; then |
||||
|
_savedeployconf DEPLOY_ALI_CDN_DOMAIN "$DEPLOY_ALI_CDN_DOMAIN" |
||||
|
else |
||||
|
DEPLOY_ALI_CDN_DOMAIN="$_cdomain" |
||||
|
fi |
||||
|
|
||||
|
# read cert and key files and urlencode both |
||||
|
_cert=$(_url_encode upper-hex <"$_cfullchain") |
||||
|
_key=$(_url_encode upper-hex <"$_ckey") |
||||
|
|
||||
|
_debug2 _cert "$_cert" |
||||
|
_debug2 _key "$_key" |
||||
|
|
||||
|
## update domain ssl config |
||||
|
for domain in $DEPLOY_ALI_CDN_DOMAIN; do |
||||
|
_set_cdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key" |
||||
|
if _ali_rest "Set CDN domain SSL certificate for $domain" "" POST; then |
||||
|
_info "Domain $domain certificate has been deployed successfully" |
||||
|
fi |
||||
|
done |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
# domain pub pri |
||||
|
_set_cdn_domain_ssl_certificate_query() { |
||||
|
endpoint=$Ali_CDN_API |
||||
|
query='' |
||||
|
query=$query'AccessKeyId='$Ali_Key |
||||
|
query=$query'&Action=SetCdnDomainSSLCertificate' |
||||
|
query=$query'&CertType=upload' |
||||
|
query=$query'&DomainName='$1 |
||||
|
query=$query'&Format=json' |
||||
|
query=$query'&SSLPri='$3 |
||||
|
query=$query'&SSLProtocol=on' |
||||
|
query=$query'&SSLPub='$2 |
||||
|
query=$query'&SignatureMethod=HMAC-SHA1' |
||||
|
query=$query"&SignatureNonce=$(_ali_nonce)" |
||||
|
query=$query'&SignatureVersion=1.0' |
||||
|
query=$query'&Timestamp='$(_timestamp) |
||||
|
query=$query'&Version=2018-05-10' |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
# shellcheck disable=SC2034,SC2154 |
||||
|
|
||||
|
# Script to create certificate to Alibaba Cloud DCDN |
||||
|
# |
||||
|
# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun |
||||
|
# |
||||
|
# This deployment required following variables |
||||
|
# export Ali_Key="ALIACCESSKEY" |
||||
|
# export Ali_Secret="ALISECRETKEY" |
||||
|
# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi |
||||
|
# |
||||
|
# To specify the DCDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates |
||||
|
# export DEPLOY_ALI_DCDN_DOMAIN="dcdn.example.com" |
||||
|
# If you have multiple CDN domains using the same certificate, just |
||||
|
# export DEPLOY_ALI_DCDN_DOMAIN="dcdn1.example.com dcdn2.example.com" |
||||
|
# |
||||
|
# For regular CDN, see ali_cdn deploy hook |
||||
|
|
||||
|
Ali_DCDN_API="https://dcdn.aliyuncs.com/" |
||||
|
|
||||
|
ali_dcdn_deploy() { |
||||
|
_cdomain="$1" |
||||
|
_ckey="$2" |
||||
|
_ccert="$3" |
||||
|
_cca="$4" |
||||
|
_cfullchain="$5" |
||||
|
|
||||
|
_debug _cdomain "$_cdomain" |
||||
|
_debug _ckey "$_ckey" |
||||
|
_debug _ccert "$_ccert" |
||||
|
_debug _cca "$_cca" |
||||
|
_debug _cfullchain "$_cfullchain" |
||||
|
|
||||
|
# Load dnsapi/dns_ali.sh to reduce the duplicated codes |
||||
|
# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276 |
||||
|
dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)" |
||||
|
# shellcheck source=/dev/null |
||||
|
if ! . "$dnsapi_ali"; then |
||||
|
_err "Error loading file $dnsapi_ali. Please check your API file and try again." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_prepare_ali_credentials || return 1 |
||||
|
|
||||
|
_getdeployconf DEPLOY_ALI_DCDN_DOMAIN |
||||
|
if [ "$DEPLOY_ALI_DCDN_DOMAIN" ]; then |
||||
|
_savedeployconf DEPLOY_ALI_DCDN_DOMAIN "$DEPLOY_ALI_DCDN_DOMAIN" |
||||
|
else |
||||
|
DEPLOY_ALI_DCDN_DOMAIN="$_cdomain" |
||||
|
fi |
||||
|
|
||||
|
# read cert and key files and urlencode both |
||||
|
_cert=$(_url_encode upper-hex <"$_cfullchain") |
||||
|
_key=$(_url_encode upper-hex <"$_ckey") |
||||
|
|
||||
|
_debug2 _cert "$_cert" |
||||
|
_debug2 _key "$_key" |
||||
|
|
||||
|
## update domain ssl config |
||||
|
for domain in $DEPLOY_ALI_DCDN_DOMAIN; do |
||||
|
_set_dcdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key" |
||||
|
if _ali_rest "Set DCDN domain SSL certificate for $domain" "" POST; then |
||||
|
_info "Domain $domain certificate has been deployed successfully" |
||||
|
fi |
||||
|
done |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
# domain pub pri |
||||
|
_set_dcdn_domain_ssl_certificate_query() { |
||||
|
endpoint=$Ali_DCDN_API |
||||
|
query='' |
||||
|
query=$query'AccessKeyId='$Ali_Key |
||||
|
query=$query'&Action=SetDcdnDomainSSLCertificate' |
||||
|
query=$query'&CertType=upload' |
||||
|
query=$query'&DomainName='$1 |
||||
|
query=$query'&Format=json' |
||||
|
query=$query'&SSLPri='$3 |
||||
|
query=$query'&SSLProtocol=on' |
||||
|
query=$query'&SSLPub='$2 |
||||
|
query=$query'&SignatureMethod=HMAC-SHA1' |
||||
|
query=$query"&SignatureNonce=$(_ali_nonce)" |
||||
|
query=$query'&SignatureVersion=1.0' |
||||
|
query=$query'&Timestamp='$(_timestamp) |
||||
|
query=$query'&Version=2018-01-15' |
||||
|
} |
||||
@ -0,0 +1,98 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
|
||||
|
#Here is a script to deploy cert to a Kemp Loadmaster. |
||||
|
|
||||
|
#returns 0 means success, otherwise error. |
||||
|
|
||||
|
#DEPLOY_KEMP_TOKEN="token" |
||||
|
#DEPLOY_KEMP_URL="https://kemplm.example.com" |
||||
|
|
||||
|
######## Public functions ##################### |
||||
|
|
||||
|
#domain keyfile certfile cafile fullchain |
||||
|
kemplm_deploy() { |
||||
|
_domain="$1" |
||||
|
_key_file="$2" |
||||
|
_cert_file="$3" |
||||
|
_ca_file="$4" |
||||
|
_fullchain_file="$5" |
||||
|
|
||||
|
_debug _domain "$_domain" |
||||
|
_debug _key_file "$_key_file" |
||||
|
_debug _cert_file "$_cert_file" |
||||
|
_debug _ca_file "$_ca_file" |
||||
|
_debug _fullchain_file "$_fullchain_file" |
||||
|
|
||||
|
if ! _exists jq; then |
||||
|
_err "jq not found" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
# Rename wildcard certs, kemp accepts only alphanumeric names so we delete '*.' from filename |
||||
|
_kemp_domain=$(echo "${_domain}" | sed 's/\*\.//') |
||||
|
_debug _kemp_domain "$_kemp_domain" |
||||
|
|
||||
|
# Read config from saved values or env |
||||
|
_getdeployconf DEPLOY_KEMP_TOKEN |
||||
|
_getdeployconf DEPLOY_KEMP_URL |
||||
|
|
||||
|
_debug DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL" |
||||
|
_secure_debug DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN" |
||||
|
|
||||
|
if [ -z "$DEPLOY_KEMP_TOKEN" ]; then |
||||
|
_err "Kemp Loadmaster token is not found, please define DEPLOY_KEMP_TOKEN." |
||||
|
return 1 |
||||
|
fi |
||||
|
if [ -z "$DEPLOY_KEMP_URL" ]; then |
||||
|
_err "Kemp Loadmaster URL is not found, please define DEPLOY_KEMP_URL." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
# Save current values |
||||
|
_savedeployconf DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN" |
||||
|
_savedeployconf DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL" |
||||
|
|
||||
|
# Check if certificate is already installed |
||||
|
_info "Check if certificate is already present" |
||||
|
_list_request="{\"cmd\": \"listcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\"}" |
||||
|
_debug3 _list_request "${_list_request}" |
||||
|
_kemp_cert_count=$(HTTPS_INSECURE=1 _post "${_list_request}" "${DEPLOY_KEMP_URL}/accessv2" | jq -r '.cert[] | .name' | grep -c "${_kemp_domain}") |
||||
|
_debug2 _kemp_cert_count "${_kemp_cert_count}" |
||||
|
|
||||
|
_kemp_replace_cert=1 |
||||
|
if [ "${_kemp_cert_count}" -eq 0 ]; then |
||||
|
_kemp_replace_cert=0 |
||||
|
_info "Certificate does not exist on Kemp Loadmaster" |
||||
|
else |
||||
|
_info "Certificate already exists on Kemp Loadmaster" |
||||
|
fi |
||||
|
_debug _kemp_replace_cert "${_kemp_replace_cert}" |
||||
|
|
||||
|
# Upload new certificate to Kemp Loadmaster |
||||
|
_kemp_upload_cert=$(_mktemp) |
||||
|
cat "${_fullchain_file}" "${_key_file}" | base64 | tr -d '\n' >"${_kemp_upload_cert}" |
||||
|
|
||||
|
_info "Uploading certificate to Kemp Loadmaster" |
||||
|
_add_data=$(cat "${_kemp_upload_cert}") |
||||
|
_add_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"${_add_data}\"}" |
||||
|
_debug3 _add_request "${_add_request}" |
||||
|
_kemp_post_result=$(HTTPS_INSECURE=1 _post "${_add_request}" "${DEPLOY_KEMP_URL}/accessv2") |
||||
|
_retval=$? |
||||
|
_debug2 _kemp_post_result "${_kemp_post_result}" |
||||
|
if [ "${_retval}" -eq 0 ]; then |
||||
|
_kemp_post_status=$(echo "${_kemp_post_result}" | jq -r '.status') |
||||
|
_kemp_post_message=$(echo "${_kemp_post_result}" | jq -r '.message') |
||||
|
if [ "${_kemp_post_status}" = "ok" ]; then |
||||
|
_info "Upload successful" |
||||
|
else |
||||
|
_err "Upload failed: ${_kemp_post_message}" |
||||
|
fi |
||||
|
else |
||||
|
_err "Upload failed" |
||||
|
_retval=1 |
||||
|
fi |
||||
|
|
||||
|
rm "${_kemp_upload_cert}" |
||||
|
|
||||
|
return $_retval |
||||
|
} |
||||
@ -0,0 +1,120 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
|
||||
|
# Deploy certificates to a proxmox backup server using the API. |
||||
|
# |
||||
|
# Environment variables that can be set are: |
||||
|
# `DEPLOY_PROXMOXBS_SERVER`: The hostname of the proxmox backup server. Defaults to |
||||
|
# _cdomain. |
||||
|
# `DEPLOY_PROXMOXBS_SERVER_PORT`: The port number the management interface is on. |
||||
|
# Defaults to 8007. |
||||
|
# `DEPLOY_PROXMOXBS_USER`: The user we'll connect as. Defaults to root. |
||||
|
# `DEPLOY_PROXMOXBS_USER_REALM`: The authentication realm the user authenticates |
||||
|
# with. Defaults to pam. |
||||
|
# `DEPLOY_PROXMOXBS_API_TOKEN_NAME`: The name of the API token created for the |
||||
|
# user account. Defaults to acme. |
||||
|
# `DEPLOY_PROXMOXBS_API_TOKEN_KEY`: The API token. Required. |
||||
|
|
||||
|
proxmoxbs_deploy() { |
||||
|
_cdomain="$1" |
||||
|
_ckey="$2" |
||||
|
_ccert="$3" |
||||
|
_cca="$4" |
||||
|
_cfullchain="$5" |
||||
|
|
||||
|
_debug _cdomain "$_cdomain" |
||||
|
_debug2 _ckey "$_ckey" |
||||
|
_debug _ccert "$_ccert" |
||||
|
_debug _cca "$_cca" |
||||
|
_debug _cfullchain "$_cfullchain" |
||||
|
|
||||
|
# "Sane" defaults. |
||||
|
_getdeployconf DEPLOY_PROXMOXBS_SERVER |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_SERVER" ]; then |
||||
|
_target_hostname="$_cdomain" |
||||
|
else |
||||
|
_target_hostname="$DEPLOY_PROXMOXBS_SERVER" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_SERVER "$DEPLOY_PROXMOXBS_SERVER" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_SERVER "$_target_hostname" |
||||
|
|
||||
|
_getdeployconf DEPLOY_PROXMOXBS_SERVER_PORT |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_SERVER_PORT" ]; then |
||||
|
_target_port="8007" |
||||
|
else |
||||
|
_target_port="$DEPLOY_PROXMOXBS_SERVER_PORT" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_SERVER_PORT "$DEPLOY_PROXMOXBS_SERVER_PORT" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_SERVER_PORT "$_target_port" |
||||
|
|
||||
|
# Complete URL. |
||||
|
_target_url="https://${_target_hostname}:${_target_port}/api2/json/nodes/localhost/certificates/custom" |
||||
|
_debug TARGET_URL "$_target_url" |
||||
|
|
||||
|
# More "sane" defaults. |
||||
|
_getdeployconf DEPLOY_PROXMOXBS_USER |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_USER" ]; then |
||||
|
_proxmoxbs_user="root" |
||||
|
else |
||||
|
_proxmoxbs_user="$DEPLOY_PROXMOXBS_USER" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_USER "$DEPLOY_PROXMOXBS_USER" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_USER "$_proxmoxbs_user" |
||||
|
|
||||
|
_getdeployconf DEPLOY_PROXMOXBS_USER_REALM |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_USER_REALM" ]; then |
||||
|
_proxmoxbs_user_realm="pam" |
||||
|
else |
||||
|
_proxmoxbs_user_realm="$DEPLOY_PROXMOXBS_USER_REALM" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_USER_REALM "$DEPLOY_PROXMOXBS_USER_REALM" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_USER_REALM "$_proxmoxbs_user_realm" |
||||
|
|
||||
|
_getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_NAME" ]; then |
||||
|
_proxmoxbs_api_token_name="acme" |
||||
|
else |
||||
|
_proxmoxbs_api_token_name="$DEPLOY_PROXMOXBS_API_TOKEN_NAME" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME "$DEPLOY_PROXMOXBS_API_TOKEN_NAME" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_API_TOKEN_NAME "$_proxmoxbs_api_token_name" |
||||
|
|
||||
|
# This is required. |
||||
|
_getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_KEY" ]; then |
||||
|
_err "API key not provided." |
||||
|
return 1 |
||||
|
else |
||||
|
_proxmoxbs_api_token_key="$DEPLOY_PROXMOXBS_API_TOKEN_KEY" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY "$DEPLOY_PROXMOXBS_API_TOKEN_KEY" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_API_TOKEN_KEY "$_proxmoxbs_api_token_key" |
||||
|
|
||||
|
# PBS API Token header value. Used in "Authorization: PBSAPIToken". |
||||
|
_proxmoxbs_header_api_token="${_proxmoxbs_user}@${_proxmoxbs_user_realm}!${_proxmoxbs_api_token_name}:${_proxmoxbs_api_token_key}" |
||||
|
_debug2 "Auth Header" "$_proxmoxbs_header_api_token" |
||||
|
|
||||
|
# Ugly. I hate putting heredocs inside functions because heredocs don't |
||||
|
# account for whitespace correctly but it _does_ work and is several times |
||||
|
# cleaner than anything else I had here. |
||||
|
# |
||||
|
# This dumps the json payload to a variable that should be passable to the |
||||
|
# _psot function. |
||||
|
_json_payload=$( |
||||
|
cat <<HEREDOC |
||||
|
{ |
||||
|
"certificates": "$(tr '\n' ':' <"$_cfullchain" | sed 's/:/\\n/g')", |
||||
|
"key": "$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')", |
||||
|
"node":"localhost", |
||||
|
"restart":true, |
||||
|
"force":true |
||||
|
} |
||||
|
HEREDOC |
||||
|
) |
||||
|
_debug2 Payload "$_json_payload" |
||||
|
|
||||
|
_info "Push certificates to server" |
||||
|
export HTTPS_INSECURE=1 |
||||
|
export _H1="Authorization: PBSAPIToken=${_proxmoxbs_header_api_token}" |
||||
|
_post "$_json_payload" "$_target_url" "" POST "application/json" |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,200 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
|
||||
|
# Here is a script to deploy cert to Ruckus ZoneDirector / Unleashed. |
||||
|
# |
||||
|
# Public domain, 2024, Tony Rielly <https://github.com/ms264556> |
||||
|
# |
||||
|
# ```sh |
||||
|
# acme.sh --deploy -d ruckus.example.com --deploy-hook ruckus |
||||
|
# ``` |
||||
|
# |
||||
|
# Then you need to set the environment variables for the |
||||
|
# deploy script to work. |
||||
|
# |
||||
|
# ```sh |
||||
|
# export RUCKUS_HOST=myruckus.example.com |
||||
|
# export RUCKUS_USER=myruckususername |
||||
|
# export RUCKUS_PASS=myruckuspassword |
||||
|
# |
||||
|
# acme.sh --deploy -d myruckus.example.com --deploy-hook ruckus |
||||
|
# ``` |
||||
|
# |
||||
|
# returns 0 means success, otherwise error. |
||||
|
|
||||
|
######## Public functions ##################### |
||||
|
|
||||
|
#domain keyfile certfile cafile fullchain |
||||
|
ruckus_deploy() { |
||||
|
_cdomain="$1" |
||||
|
_ckey="$2" |
||||
|
_ccert="$3" |
||||
|
_cca="$4" |
||||
|
_cfullchain="$5" |
||||
|
_err_code=0 |
||||
|
|
||||
|
_debug _cdomain "$_cdomain" |
||||
|
_debug _ckey "$_ckey" |
||||
|
_debug _ccert "$_ccert" |
||||
|
_debug _cca "$_cca" |
||||
|
_debug _cfullchain "$_cfullchain" |
||||
|
|
||||
|
_getdeployconf RUCKUS_HOST |
||||
|
_getdeployconf RUCKUS_USER |
||||
|
_getdeployconf RUCKUS_PASS |
||||
|
|
||||
|
if [ -z "$RUCKUS_HOST" ]; then |
||||
|
_debug "Using _cdomain as RUCKUS_HOST, please set if not correct." |
||||
|
RUCKUS_HOST="$_cdomain" |
||||
|
fi |
||||
|
|
||||
|
if [ -z "$RUCKUS_USER" ]; then |
||||
|
_err "Need to set the env variable RUCKUS_USER" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if [ -z "$RUCKUS_PASS" ]; then |
||||
|
_err "Need to set the env variable RUCKUS_PASS" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_savedeployconf RUCKUS_HOST "$RUCKUS_HOST" |
||||
|
_savedeployconf RUCKUS_USER "$RUCKUS_USER" |
||||
|
_savedeployconf RUCKUS_PASS "$RUCKUS_PASS" |
||||
|
|
||||
|
_debug RUCKUS_HOST "$RUCKUS_HOST" |
||||
|
_debug RUCKUS_USER "$RUCKUS_USER" |
||||
|
_secure_debug RUCKUS_PASS "$RUCKUS_PASS" |
||||
|
|
||||
|
export ACME_HTTP_NO_REDIRECTS=1 |
||||
|
|
||||
|
_info "Discovering the login URL" |
||||
|
_get "https://$RUCKUS_HOST" >/dev/null |
||||
|
_login_url="$(_response_header 'Location')" |
||||
|
if [ -n "$_login_url" ]; then |
||||
|
_login_path=$(echo "$_login_url" | sed 's|https\?://[^/]\+||') |
||||
|
if [ -z "$_login_path" ]; then |
||||
|
# redirect was to a different host |
||||
|
_err "Connection failed: redirected to a different host. Configure Unleashed with a Preferred Master or Management Interface." |
||||
|
return 1 |
||||
|
fi |
||||
|
fi |
||||
|
|
||||
|
if [ -z "${_login_url}" ]; then |
||||
|
_err "Connection failed: couldn't find login page." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_base_url=$(dirname "$_login_url") |
||||
|
_login_page=$(basename "$_login_url") |
||||
|
|
||||
|
if [ "$_login_page" = "index.html" ]; then |
||||
|
_err "Connection temporarily unavailable: Unleashed Rebuilding." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if [ "$_login_page" = "wizard.jsp" ]; then |
||||
|
_err "Connection failed: Setup Wizard not complete." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_info "Login" |
||||
|
_username_encoded="$(printf "%s" "$RUCKUS_USER" | _url_encode)" |
||||
|
_password_encoded="$(printf "%s" "$RUCKUS_PASS" | _url_encode)" |
||||
|
_login_query="$(printf "%s" "username=${_username_encoded}&password=${_password_encoded}&ok=Log+In")" |
||||
|
_post "$_login_query" "$_login_url" >/dev/null |
||||
|
|
||||
|
_login_code="$(_response_code)" |
||||
|
if [ "$_login_code" = "200" ]; then |
||||
|
_err "Login failed: incorrect credentials." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_info "Collect Session Cookie" |
||||
|
_H1="Cookie: $(_response_cookie)" |
||||
|
export _H1 |
||||
|
_info "Collect CSRF Token" |
||||
|
_H2="X-CSRF-Token: $(_response_header 'HTTP_X_CSRF_TOKEN')" |
||||
|
export _H2 |
||||
|
|
||||
|
if _isRSA "$_ckey" >/dev/null 2>&1; then |
||||
|
_debug "Using RSA certificate." |
||||
|
else |
||||
|
_info "Verifying ECC certificate support." |
||||
|
|
||||
|
_ul_version="$(_get_unleashed_version)" |
||||
|
if [ -z "$_ul_version" ]; then |
||||
|
_err "Your controller doesn't support ECC certificates. Please deploy an RSA certificate." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_ul_version_major="$(echo "$_ul_version" | cut -d . -f 1)" |
||||
|
_ul_version_minor="$(echo "$_ul_version" | cut -d . -f 2)" |
||||
|
if [ "$_ul_version_major" -lt "200" ]; then |
||||
|
_err "ZoneDirector doesn't support ECC certificates. Please deploy an RSA certificate." |
||||
|
return 1 |
||||
|
elif [ "$_ul_version_minor" -lt "13" ]; then |
||||
|
_err "Unleashed $_ul_version_major.$_ul_version_minor doesn't support ECC certificates. Please deploy an RSA certificate or upgrade to Unleashed 200.13+." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_debug "ECC certificates OK for Unleashed $_ul_version_major.$_ul_version_minor." |
||||
|
fi |
||||
|
|
||||
|
_info "Uploading certificate" |
||||
|
_post_upload "uploadcert" "$_cfullchain" |
||||
|
|
||||
|
_info "Uploading private key" |
||||
|
_post_upload "uploadprivatekey" "$_ckey" |
||||
|
|
||||
|
_info "Replacing certificate" |
||||
|
_replace_cert_ajax='<ajax-request action="docmd" comp="system" updater="rid.0.5" xcmd="replace-cert" checkAbility="6" timeout="-1"><xcmd cmd="replace-cert" cn="'$RUCKUS_HOST'"/></ajax-request>' |
||||
|
_post "$_replace_cert_ajax" "$_base_url/_cmdstat.jsp" >/dev/null |
||||
|
|
||||
|
_info "Rebooting" |
||||
|
_cert_reboot_ajax='<ajax-request action="docmd" comp="worker" updater="rid.0.5" xcmd="cert-reboot" checkAbility="6"><xcmd cmd="cert-reboot" action="undefined"/></ajax-request>' |
||||
|
_post "$_cert_reboot_ajax" "$_base_url/_cmdstat.jsp" >/dev/null |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
_response_code() { |
||||
|
_egrep_o <"$HTTP_HEADER" "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n" | _egrep_o "^[0-9]*" |
||||
|
} |
||||
|
|
||||
|
_response_header() { |
||||
|
grep <"$HTTP_HEADER" -i "^$1:" | cut -d ':' -f 2- | tr -d "\r\n\t " |
||||
|
} |
||||
|
|
||||
|
_response_cookie() { |
||||
|
_response_header 'Set-Cookie' | sed 's/;.*//' |
||||
|
} |
||||
|
|
||||
|
_get_unleashed_version() { |
||||
|
_post '<ajax-request action="getstat" comp="system"><sysinfo/></ajax-request>' "$_base_url/_cmdstat.jsp" | _egrep_o "version-num=\"[^\"]*\"" | cut -d '"' -f 2 |
||||
|
} |
||||
|
|
||||
|
_post_upload() { |
||||
|
_post_action="$1" |
||||
|
_post_file="$2" |
||||
|
|
||||
|
_post_boundary="----FormBoundary$(date "+%s%N")" |
||||
|
|
||||
|
_post_data="$({ |
||||
|
printf -- "--%s\r\n" "$_post_boundary" |
||||
|
printf -- "Content-Disposition: form-data; name=\"u\"; filename=\"%s\"\r\n" "$_post_action" |
||||
|
printf -- "Content-Type: application/octet-stream\r\n\r\n" |
||||
|
printf -- "%s\r\n" "$(cat "$_post_file")" |
||||
|
|
||||
|
printf -- "--%s\r\n" "$_post_boundary" |
||||
|
printf -- "Content-Disposition: form-data; name=\"action\"\r\n\r\n" |
||||
|
printf -- "%s\r\n" "$_post_action" |
||||
|
|
||||
|
printf -- "--%s\r\n" "$_post_boundary" |
||||
|
printf -- "Content-Disposition: form-data; name=\"callback\"\r\n\r\n" |
||||
|
printf -- "%s\r\n" "uploader_$_post_action" |
||||
|
|
||||
|
printf -- "--%s--\r\n\r\n" "$_post_boundary" |
||||
|
})" |
||||
|
|
||||
|
_post "$_post_data" "$_base_url/_upload.jsp?request_type=xhr" "" "" "multipart/form-data; boundary=$_post_boundary" >/dev/null |
||||
|
} |
||||
@ -0,0 +1,325 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
|
||||
|
# TrueNAS deploy script for SCALE/CORE using websocket |
||||
|
# It is recommend to use a wildcard certificate |
||||
|
# |
||||
|
# Websocket Documentation: https://www.truenas.com/docs/api/scale_websocket_api.html |
||||
|
# |
||||
|
# Tested with TrueNAS Scale - Electric Eel 24.10 |
||||
|
# Changes certificate in the following services: |
||||
|
# - Web UI |
||||
|
# - FTP |
||||
|
# - iX Apps |
||||
|
# |
||||
|
# The following environment variables must be set: |
||||
|
# ------------------------------------------------ |
||||
|
# |
||||
|
# # API KEY |
||||
|
# # Use the folowing URL to create a new API token: <TRUENAS_HOSTNAME OR IP>/ui/apikeys |
||||
|
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI" |
||||
|
# |
||||
|
|
||||
|
### Private functions |
||||
|
|
||||
|
# Call websocket method |
||||
|
# Usage: |
||||
|
# _ws_response=$(_ws_call "math.dummycalc" "'{"x": 4, "y": 5}'") |
||||
|
# _info "$_ws_response" |
||||
|
# |
||||
|
# Output: |
||||
|
# {"z": 9} |
||||
|
# |
||||
|
# Arguments: |
||||
|
# $@ - midclt arguments for call |
||||
|
# |
||||
|
# Returns: |
||||
|
# JSON/JOBID |
||||
|
_ws_call() { |
||||
|
_debug "_ws_call arg1" "$1" |
||||
|
_debug "_ws_call arg2" "$2" |
||||
|
_debug "_ws_call arg3" "$3" |
||||
|
if [ $# -eq 3 ]; then |
||||
|
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3") |
||||
|
fi |
||||
|
if [ $# -eq 2 ]; then |
||||
|
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2") |
||||
|
fi |
||||
|
if [ $# -eq 1 ]; then |
||||
|
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1") |
||||
|
fi |
||||
|
_debug "_ws_response" "$_ws_response" |
||||
|
printf "%s" "$_ws_response" |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
# Upload certificate with webclient api |
||||
|
_ws_upload_cert() { |
||||
|
|
||||
|
/usr/bin/env python - <<EOF |
||||
|
|
||||
|
import sys |
||||
|
|
||||
|
from truenas_api_client import Client |
||||
|
with Client() as c: |
||||
|
|
||||
|
### Login with API key |
||||
|
print("I:Trying to upload new certificate...") |
||||
|
ret = c.call("auth.login_with_api_key", "${DEPLOY_TRUENAS_APIKEY}") |
||||
|
if ret: |
||||
|
### upload certificate |
||||
|
with open('$1', 'r') as file: |
||||
|
fullchain = file.read() |
||||
|
with open('$2', 'r') as file: |
||||
|
privatekey = file.read() |
||||
|
ret = c.call("certificate.create", {"name": "$3", "create_type": "CERTIFICATE_CREATE_IMPORTED", "certificate": fullchain, "privatekey": privatekey, "passphrase": ""}, job=True) |
||||
|
print("R:" + str(ret["id"])) |
||||
|
sys.exit(0) |
||||
|
else: |
||||
|
print("R:0") |
||||
|
print("E:_ws_upload_cert error!") |
||||
|
sys.exit(7) |
||||
|
|
||||
|
EOF |
||||
|
|
||||
|
return $? |
||||
|
|
||||
|
} |
||||
|
|
||||
|
# Check argument is a number |
||||
|
# Usage: |
||||
|
# |
||||
|
# Output: |
||||
|
# n/a |
||||
|
# |
||||
|
# Arguments: |
||||
|
# $1 - Anything |
||||
|
# |
||||
|
# Returns: |
||||
|
# 0: true |
||||
|
# 1: false |
||||
|
_ws_check_jobid() { |
||||
|
case "$1" in |
||||
|
[0-9]*) |
||||
|
return 0 |
||||
|
;; |
||||
|
esac |
||||
|
return 1 |
||||
|
} |
||||
|
|
||||
|
# Wait for job to finish and return result as JSON |
||||
|
# Usage: |
||||
|
# _ws_result=$(_ws_get_job_result "$_ws_jobid") |
||||
|
# _new_certid=$(printf "%s" "$_ws_result" | jq -r '."id"') |
||||
|
# |
||||
|
# Output: |
||||
|
# JSON result of the job |
||||
|
# |
||||
|
# Arguments: |
||||
|
# $1 - JobID |
||||
|
# |
||||
|
# Returns: |
||||
|
# n/a |
||||
|
_ws_get_job_result() { |
||||
|
while true; do |
||||
|
sleep 2 |
||||
|
_ws_response=$(_ws_call "core.get_jobs" "[[\"id\", \"=\", $1]]") |
||||
|
if [ "$(printf "%s" "$_ws_response" | jq -r '.[]."state"')" != "RUNNING" ]; then |
||||
|
_ws_result="$(printf "%s" "$_ws_response" | jq '.[]."result"')" |
||||
|
_debug "_ws_result" "$_ws_result" |
||||
|
printf "%s" "$_ws_result" |
||||
|
_ws_error="$(printf "%s" "$_ws_response" | jq '.[]."error"')" |
||||
|
if [ "$_ws_error" != "null" ]; then |
||||
|
_err "Job $1 failed:" |
||||
|
_err "$_ws_error" |
||||
|
return 7 |
||||
|
fi |
||||
|
break |
||||
|
fi |
||||
|
done |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
######################## |
||||
|
### Public functions ### |
||||
|
######################## |
||||
|
|
||||
|
# truenas_ws_deploy |
||||
|
# |
||||
|
# Deploy new certificate to TrueNAS services |
||||
|
# |
||||
|
# Arguments |
||||
|
# 1: Domain |
||||
|
# 2: Key-File |
||||
|
# 3: Certificate-File |
||||
|
# 4: CA-File |
||||
|
# 5: FullChain-File |
||||
|
# Returns: |
||||
|
# 0: Success |
||||
|
# 1: Missing API Key |
||||
|
# 2: TrueNAS not ready |
||||
|
# 3: Not a JobID |
||||
|
# 4: FTP cert error |
||||
|
# 5: WebUI cert error |
||||
|
# 6: Job error |
||||
|
# 7: WS call error |
||||
|
# |
||||
|
truenas_ws_deploy() { |
||||
|
_domain="$1" |
||||
|
_file_key="$2" |
||||
|
_file_cert="$3" |
||||
|
_file_ca="$4" |
||||
|
_file_fullchain="$5" |
||||
|
_debug _domain "$_domain" |
||||
|
_debug _file_key "$_file_key" |
||||
|
_debug _file_cert "$_file_cert" |
||||
|
_debug _file_ca "$_file_ca" |
||||
|
_debug _file_fullchain "$_file_fullchain" |
||||
|
|
||||
|
########## Environment check |
||||
|
|
||||
|
_info "Checking environment variables..." |
||||
|
_getdeployconf DEPLOY_TRUENAS_APIKEY |
||||
|
# Check API Key |
||||
|
if [ -z "$DEPLOY_TRUENAS_APIKEY" ]; then |
||||
|
_err "TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable." |
||||
|
return 1 |
||||
|
fi |
||||
|
_secure_debug2 DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY" |
||||
|
_info "Environment variables: OK" |
||||
|
|
||||
|
########## Health check |
||||
|
|
||||
|
_info "Checking TrueNAS health..." |
||||
|
_ws_response=$(_ws_call "system.ready" | tr '[:lower:]' '[:upper:]') |
||||
|
_ws_ret=$? |
||||
|
if [ $_ws_ret -gt 0 ]; then |
||||
|
_err "Error calling system.ready:" |
||||
|
_err "$_ws_response" |
||||
|
return $_ws_ret |
||||
|
fi |
||||
|
|
||||
|
if [ "$_ws_response" != "TRUE" ]; then |
||||
|
_err "TrueNAS is not ready." |
||||
|
_err "Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME and DEPLOY_TRUENAS_PROTOCOL." |
||||
|
_err "Verify API key." |
||||
|
return 2 |
||||
|
fi |
||||
|
_savedeployconf DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY" |
||||
|
_info "TrueNAS health: OK" |
||||
|
|
||||
|
########## System info |
||||
|
|
||||
|
_info "Gather system info..." |
||||
|
_ws_response=$(_ws_call "system.info") |
||||
|
_truenas_version=$(printf "%s" "$_ws_response" | jq -r '."version"') |
||||
|
_info "TrueNAS version: $_truenas_version" |
||||
|
|
||||
|
########## Gather current certificate |
||||
|
|
||||
|
_info "Gather current WebUI certificate..." |
||||
|
_ws_response="$(_ws_call "system.general.config")" |
||||
|
_ui_certificate_id=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"') |
||||
|
_ui_certificate_name=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."name"') |
||||
|
_info "Current WebUI certificate ID: $_ui_certificate_id" |
||||
|
_info "Current WebUI certificate name: $_ui_certificate_name" |
||||
|
|
||||
|
########## Upload new certificate |
||||
|
|
||||
|
_info "Upload new certificate..." |
||||
|
_certname="acme_$(_utc_date | tr -d '\-\:' | tr ' ' '_')" |
||||
|
_info "New WebUI certificate name: $_certname" |
||||
|
_debug _certname "$_certname" |
||||
|
_ws_out=$(_ws_upload_cert "$_file_fullchain" "$_file_key" "$_certname") |
||||
|
|
||||
|
echo "$_ws_out" | while IFS= read -r LINE; do |
||||
|
case "$LINE" in |
||||
|
I:*) |
||||
|
_info "${LINE#I:}" |
||||
|
;; |
||||
|
D:*) |
||||
|
_debug "${LINE#D:}" |
||||
|
;; |
||||
|
E*) |
||||
|
_err "${LINE#E:}" |
||||
|
;; |
||||
|
*) ;; |
||||
|
|
||||
|
esac |
||||
|
done |
||||
|
|
||||
|
_new_certid=$(echo "$_ws_out" | grep 'R:' | cut -d ':' -f 2) |
||||
|
|
||||
|
_info "New certificate ID: $_new_certid" |
||||
|
|
||||
|
########## FTP |
||||
|
|
||||
|
_info "Replace FTP certificate..." |
||||
|
_ws_response=$(_ws_call "ftp.update" "{\"ssltls_certificate\": $_new_certid}") |
||||
|
_ftp_certid=$(printf "%s" "$_ws_response" | jq -r '."ssltls_certificate"') |
||||
|
if [ "$_ftp_certid" != "$_new_certid" ]; then |
||||
|
_err "Cannot set FTP certificate." |
||||
|
_debug "_ws_response" "$_ws_response" |
||||
|
return 4 |
||||
|
fi |
||||
|
|
||||
|
########## ix Apps (SCALE only) |
||||
|
|
||||
|
_info "Replace app certificates..." |
||||
|
_ws_response=$(_ws_call "app.query") |
||||
|
for _app_name in $(printf "%s" "$_ws_response" | jq -r '.[]."name"'); do |
||||
|
_info "Checking app $_app_name..." |
||||
|
_ws_response=$(_ws_call "app.config" "$_app_name") |
||||
|
if [ "$(printf "%s" "$_ws_response" | jq -r '."network" | has("certificate_id")')" = "true" ]; then |
||||
|
_info "App has certificate option, setup new certificate..." |
||||
|
_info "App will be redeployed after updating the certificate." |
||||
|
_ws_jobid=$(_ws_call "app.update" "$_app_name" "{\"values\": {\"network\": {\"certificate_id\": $_new_certid}}}") |
||||
|
_debug "_ws_jobid" "$_ws_jobid" |
||||
|
if ! _ws_check_jobid "$_ws_jobid"; then |
||||
|
_err "No JobID returned from websocket method." |
||||
|
return 3 |
||||
|
fi |
||||
|
_ws_result=$(_ws_get_job_result "$_ws_jobid") |
||||
|
_ws_ret=$? |
||||
|
if [ $_ws_ret -gt 0 ]; then |
||||
|
return $_ws_ret |
||||
|
fi |
||||
|
_debug "_ws_result" "$_ws_result" |
||||
|
_info "App certificate replaced." |
||||
|
else |
||||
|
_info "App has no certificate option, skipping..." |
||||
|
fi |
||||
|
done |
||||
|
|
||||
|
########## WebUI |
||||
|
|
||||
|
_info "Replace WebUI certificate..." |
||||
|
_ws_response=$(_ws_call "system.general.update" "{\"ui_certificate\": $_new_certid}") |
||||
|
_changed_certid=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"') |
||||
|
if [ "$_changed_certid" != "$_new_certid" ]; then |
||||
|
_err "WebUI certificate change error.." |
||||
|
return 5 |
||||
|
else |
||||
|
_info "WebUI certificate replaced." |
||||
|
fi |
||||
|
_info "Restarting WebUI..." |
||||
|
_ws_response=$(_ws_call "system.general.ui_restart") |
||||
|
_info "Waiting for UI restart..." |
||||
|
sleep 6 |
||||
|
|
||||
|
########## Certificates |
||||
|
|
||||
|
_info "Deleting old certificate..." |
||||
|
_ws_jobid=$(_ws_call "certificate.delete" "$_ui_certificate_id") |
||||
|
if ! _ws_check_jobid "$_ws_jobid"; then |
||||
|
_err "No JobID returned from websocket method." |
||||
|
return 3 |
||||
|
fi |
||||
|
_ws_result=$(_ws_get_job_result "$_ws_jobid") |
||||
|
_ws_ret=$? |
||||
|
if [ $_ws_ret -gt 0 ]; then |
||||
|
return $_ws_ret |
||||
|
fi |
||||
|
|
||||
|
_info "Have a nice day...bye!" |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,500 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
|
||||
|
# Deploy certificates to Zyxel GS1900 series switches |
||||
|
# |
||||
|
# This script uses the https web administration interface in order |
||||
|
# to upload updated certificates to Zyxel GS1900 series switches. |
||||
|
# Only a few models have been tested but untested switches from the |
||||
|
# same model line may work as well. If you test and confirm a switch |
||||
|
# as working please submit a pull request updating this compatibility |
||||
|
# list! |
||||
|
# |
||||
|
# Known Issues: |
||||
|
# 1. This is a consumer grade switch and is a bit underpowered |
||||
|
# the longer the RSA key size the slower your switch web UI |
||||
|
# will be. RSA 2048 will work, RSA 4096 will work but you may |
||||
|
# experience performance problems. |
||||
|
# 2. You must use RSA certificates. The switch will reject EC-256 |
||||
|
# and EC-384 certificates in firmware 2.80 |
||||
|
# See: https://community.zyxel.com/en/discussion/21506/bug-cannot-import-ssl-cert-on-gs1900-8-and-gs1900-24e-firmware-v2-80/ |
||||
|
# |
||||
|
# Current GS1900 Switch Compatibility: |
||||
|
# GS1900-8 - Working as of firmware V2.80 |
||||
|
# GS1900-8HP - Untested |
||||
|
# GS1900-10HP - Untested |
||||
|
# GS1900-16 - Untested |
||||
|
# GS1900-24 - Untested |
||||
|
# GS1900-24E - Working as of firmware V2.80 |
||||
|
# GS1900-24EP - Untested |
||||
|
# GS1900-24HP - Untested |
||||
|
# GS1900-48 - Untested |
||||
|
# GS1900-48HP - Untested |
||||
|
# |
||||
|
# Prerequisite Setup Steps: |
||||
|
# 1. Install at least firmware V2.80 on your switch |
||||
|
# 2. Enable HTTPS web management on your switch |
||||
|
# |
||||
|
# Usage: |
||||
|
# 1. Ensure the switch has firmware V2.80 or later. |
||||
|
# 2. Ensure the switch has HTTPS management enabled. |
||||
|
# 3. Set the appropriate environment variables for your environment. |
||||
|
# |
||||
|
# DEPLOY_ZYXEL_SWITCH - The switch hostname. (Default: _cdomain) |
||||
|
# DEPLOY_ZYXEL_SWITCH_USER - The webadmin user. (Default: admin) |
||||
|
# DEPLOY_ZYXEL_SWITCH_PASSWORD - The webadmin password for the switch. |
||||
|
# DEPLOY_ZYXEL_SWITCH_REBOOT - If "1" reboot after update. (Default: "0") |
||||
|
# |
||||
|
# 4. Run the deployment plugin: |
||||
|
# acme.sh --deploy --deploy-hook zyxel_gs1900 -d example.com |
||||
|
# |
||||
|
# returns 0 means success, otherwise error. |
||||
|
|
||||
|
#domain keyfile certfile cafile fullchain |
||||
|
zyxel_gs1900_deploy() { |
||||
|
_zyxel_gs1900_minimum_firmware_version="v2.80" |
||||
|
|
||||
|
_cdomain="$1" |
||||
|
_ckey="$2" |
||||
|
_ccert="$3" |
||||
|
_cca="$4" |
||||
|
_cfullchain="$5" |
||||
|
|
||||
|
_debug _cdomain "$_cdomain" |
||||
|
_debug2 _ckey "$_ckey" |
||||
|
_debug _ccert "$_ccert" |
||||
|
_debug _cca "$_cca" |
||||
|
_debug _cfullchain "$_cfullchain" |
||||
|
|
||||
|
_getdeployconf DEPLOY_ZYXEL_SWITCH |
||||
|
_getdeployconf DEPLOY_ZYXEL_SWITCH_USER |
||||
|
_getdeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD |
||||
|
_getdeployconf DEPLOY_ZYXEL_SWITCH_REBOOT |
||||
|
|
||||
|
if [ -z "$DEPLOY_ZYXEL_SWITCH" ]; then |
||||
|
DEPLOY_ZYXEL_SWITCH="$_cdomain" |
||||
|
fi |
||||
|
|
||||
|
if [ -z "$DEPLOY_ZYXEL_SWITCH_USER" ]; then |
||||
|
DEPLOY_ZYXEL_SWITCH_USER="admin" |
||||
|
fi |
||||
|
|
||||
|
if [ -z "$DEPLOY_ZYXEL_SWITCH_PASSWORD" ]; then |
||||
|
DEPLOY_ZYXEL_SWITCH_PASSWORD="1234" |
||||
|
fi |
||||
|
|
||||
|
if [ -z "$DEPLOY_ZYXEL_SWITCH_REBOOT" ]; then |
||||
|
DEPLOY_ZYXEL_SWITCH_REBOOT="0" |
||||
|
fi |
||||
|
|
||||
|
_savedeployconf DEPLOY_ZYXEL_SWITCH "$DEPLOY_ZYXEL_SWITCH" |
||||
|
_savedeployconf DEPLOY_ZYXEL_SWITCH_USER "$DEPLOY_ZYXEL_SWITCH_USER" |
||||
|
_savedeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD "$DEPLOY_ZYXEL_SWITCH_PASSWORD" |
||||
|
_savedeployconf DEPLOY_ZYXEL_SWITCH_REBOOT "$DEPLOY_ZYXEL_SWITCH_REBOOT" |
||||
|
|
||||
|
_debug DEPLOY_ZYXEL_SWITCH "$DEPLOY_ZYXEL_SWITCH" |
||||
|
_debug DEPLOY_ZYXEL_SWITCH_USER "$DEPLOY_ZYXEL_SWITCH_USER" |
||||
|
_secure_debug DEPLOY_ZYXEL_SWITCH_PASSWORD "$DEPLOY_ZYXEL_SWITCH_PASSWORD" |
||||
|
_debug DEPLOY_ZYXEL_SWITCH_REBOOT "$DEPLOY_ZYXEL_SWITCH_REBOOT" |
||||
|
|
||||
|
_zyxel_switch_base_uri="https://${DEPLOY_ZYXEL_SWITCH}" |
||||
|
|
||||
|
_info "Beginning to deploy to a Zyxel GS1900 series switch at ${_zyxel_switch_base_uri}." |
||||
|
_zyxel_gs1900_deployment_precheck || return $? |
||||
|
|
||||
|
_zyxel_gs1900_should_update |
||||
|
if [ "$?" != "0" ]; then |
||||
|
_info "The switch already has our certificate installed. No update required." |
||||
|
return 0 |
||||
|
else |
||||
|
_info "The switch does not yet have our certificate installed." |
||||
|
fi |
||||
|
|
||||
|
_info "Logging into the switch web interface." |
||||
|
_zyxel_gs1900_login || return $? |
||||
|
|
||||
|
_info "Validating the switch is compatible with this deployment process." |
||||
|
_zyxel_gs1900_validate_device_compatibility || return $? |
||||
|
|
||||
|
_info "Uploading the certificate." |
||||
|
_zyxel_gs1900_upload_certificate || return $? |
||||
|
|
||||
|
if [ "$DEPLOY_ZYXEL_SWITCH_REBOOT" = "1" ]; then |
||||
|
_info "Rebooting the switch." |
||||
|
_zyxel_gs1900_trigger_reboot || return $? |
||||
|
fi |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
_zyxel_gs1900_deployment_precheck() { |
||||
|
# Initialize the keylength if it isn't already |
||||
|
if [ -z "$Le_Keylength" ]; then |
||||
|
Le_Keylength="" |
||||
|
fi |
||||
|
|
||||
|
if _isEccKey "$Le_Keylength"; then |
||||
|
_info "Warning: Zyxel GS1900 switches are not currently known to work with ECC keys!" |
||||
|
_info "You can continue, but your switch may reject your key." |
||||
|
elif [ -n "$Le_Keylength" ] && [ "$Le_Keylength" -gt "2048" ]; then |
||||
|
_info "Warning: Your RSA key length is greater than 2048!" |
||||
|
_info "You can continue, but you may experience performance issues in the web administration interface." |
||||
|
fi |
||||
|
|
||||
|
# Check the server for some common failure modes prior to authentication and certificate upload in order to avoid |
||||
|
# sending a certificate when we may not want to. |
||||
|
test_login_response=$(_post "username=test&password=test&login=true;" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" 2>&1) |
||||
|
test_login_page_exitcode="$?" |
||||
|
_debug3 "Test Login Response: ${test_login_response}" |
||||
|
if [ "$test_login_page_exitcode" -ne "0" ]; then |
||||
|
if { [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "60" ]; } || { [ "${ACME_USE_WGET:-0}" = "1" ] && [ "$test_login_page_exitcode" = "5" ]; }; then |
||||
|
_err "The SSL certificate at $_zyxel_switch_base_uri could not be validated." |
||||
|
_err "Please double check your hostname, port, and that you are actually connecting to your switch." |
||||
|
_err "If the problem persists then please ensure that the certificate is not self-signed, has not" |
||||
|
_err "expired, and matches the switch hostname. If you expect validation to fail then you can disable" |
||||
|
_err "certificate validation by running with --insecure." |
||||
|
return 1 |
||||
|
elif [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "56" ]; then |
||||
|
_debug3 "Intentionally ignore curl exit code 56 in our precheck" |
||||
|
else |
||||
|
_err "Failed to submit the initial login attempt to $_zyxel_switch_base_uri." |
||||
|
return 1 |
||||
|
fi |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
_zyxel_gs1900_login() { |
||||
|
# Login to the switch and set the appropriate auth cookie in _H1 |
||||
|
username_encoded=$(printf "%s" "$DEPLOY_ZYXEL_SWITCH_USER" | _url_encode) |
||||
|
password_encoded=$(_zyxel_gs1900_password_obfuscate "$DEPLOY_ZYXEL_SWITCH_PASSWORD" | _url_encode) |
||||
|
|
||||
|
login_response=$(_post "username=${username_encoded}&password=${password_encoded}&login=true;" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n') |
||||
|
auth_response=$(_post "authId=${login_response}&login_chk=true" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n') |
||||
|
if [ "$auth_response" != "OK" ]; then |
||||
|
_err "Login failed due to invalid credentials." |
||||
|
_err "Please double check the configured username and password and try again." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
sessionid=$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'HTTPS_XSSID=[^;]*;' | tr -d ';') |
||||
|
_secure_debug2 "sessionid" "$sessionid" |
||||
|
|
||||
|
export _H1="Cookie: $sessionid" |
||||
|
_secure_debug2 "_H1" "$_H1" |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
_zyxel_gs1900_validate_device_compatibility() { |
||||
|
# Check the switches model and firmware version and throw errors |
||||
|
# if this script isn't compatible. |
||||
|
device_info_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=12" | tr -d '\n') |
||||
|
|
||||
|
model_name=$(_zyxel_gs1900_get_model "$device_info_html") |
||||
|
_debug2 "model_name" "$model_name" |
||||
|
if [ -z "$model_name" ]; then |
||||
|
_err "Could not find the switch model name." |
||||
|
_err "Please re-run with --debug and report a bug." |
||||
|
return $? |
||||
|
fi |
||||
|
|
||||
|
if ! expr "$model_name" : "GS1900-" >/dev/null; then |
||||
|
_err "Switch is an unsupported model: $model_name" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
firmware_version=$(_zyxel_gs1900_get_firmware_version "$device_info_html") |
||||
|
_debug2 "firmware_version" "$firmware_version" |
||||
|
if [ -z "$firmware_version" ]; then |
||||
|
_err "Could not find the switch firmware version." |
||||
|
_err "Please re-run with --debug and report a bug." |
||||
|
return $? |
||||
|
fi |
||||
|
|
||||
|
_debug2 "_zyxel_gs1900_minimum_firmware_version" "$_zyxel_gs1900_minimum_firmware_version" |
||||
|
minimum_major_version=$(_zyxel_gs1900_parse_major_version "$_zyxel_gs1900_minimum_firmware_version") |
||||
|
_debug2 "minimum_major_version" "$minimum_major_version" |
||||
|
minimum_minor_version=$(_zyxel_gs1900_parse_minor_version "$_zyxel_gs1900_minimum_firmware_version") |
||||
|
_debug2 "minimum_minor_version" "$minimum_minor_version" |
||||
|
|
||||
|
_debug2 "firmware_version" "$firmware_version" |
||||
|
firmware_major_version=$(_zyxel_gs1900_parse_major_version "$firmware_version") |
||||
|
_debug2 "firmware_major_version" "$firmware_major_version" |
||||
|
firmware_minor_version=$(_zyxel_gs1900_parse_minor_version "$firmware_version") |
||||
|
_debug2 "firmware_minor_version" "$firmware_minor_version" |
||||
|
|
||||
|
_ret=0 |
||||
|
if [ "$firmware_major_version" -lt "$minimum_major_version" ]; then |
||||
|
_ret=1 |
||||
|
elif [ "$firmware_major_version" -eq "$minimum_major_version" ] && [ "$firmware_minor_version" -lt "$minimum_minor_version" ]; then |
||||
|
_ret=1 |
||||
|
fi |
||||
|
|
||||
|
if [ "$_ret" != "0" ]; then |
||||
|
_err "Unsupported firmware version $firmware_version. Please upgrade to at least version $_zyxel_gs1900_minimum_firmware_version." |
||||
|
fi |
||||
|
|
||||
|
return $? |
||||
|
} |
||||
|
|
||||
|
_zyxel_gs1900_should_update() { |
||||
|
# Get the remote certificate serial number |
||||
|
_remote_cert=$(${ACME_OPENSSL_BIN:-openssl} s_client -showcerts -connect "${DEPLOY_ZYXEL_SWITCH}:443" 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p') |
||||
|
_debug3 "_remote_cert" "$_remote_cert" |
||||
|
|
||||
|
_remote_cert_serial=$(printf "%s" "${_remote_cert}" | ${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial) |
||||
|
_debug2 "_remote_cert_serial" "$_remote_cert_serial" |
||||
|
|
||||
|
# Get our certificate serial number |
||||
|
_our_cert_serial=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial <"${_ccert}") |
||||
|
_debug2 "_our_cert_serial" "$_our_cert_serial" |
||||
|
|
||||
|
[ "${_remote_cert_serial}" != "${_our_cert_serial}" ] |
||||
|
} |
||||
|
|
||||
|
_zyxel_gs1900_upload_certificate() { |
||||
|
# Generate a PKCS12 certificate with a temporary password since the web interface |
||||
|
# requires a password be present. Then upload that certificate. |
||||
|
temp_cert_password=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 64) |
||||
|
_secure_debug2 "temp_cert_password" "$temp_cert_password" |
||||
|
|
||||
|
temp_pkcs12="$(_mktemp)" |
||||
|
_debug2 "temp_pkcs12" "$temp_pkcs12" |
||||
|
_toPkcs "$temp_pkcs12" "$_ckey" "$_ccert" "$_cca" "$temp_cert_password" |
||||
|
if [ "$?" != "0" ]; then |
||||
|
_err "Failed to generate a pkcs12 certificate." |
||||
|
_err "Please re-run with --debug and report a bug." |
||||
|
|
||||
|
# ensure the temporary certificate file is cleaned up |
||||
|
[ -f "${temp_pkcs12}" ] && rm -f "${temp_pkcs12}" |
||||
|
|
||||
|
return $? |
||||
|
fi |
||||
|
|
||||
|
# Load the upload page |
||||
|
upload_page_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5914" | tr -d '\n') |
||||
|
|
||||
|
# Get the first instance of XSSID from the upload page |
||||
|
form_xss_value=$(printf "%s" "$upload_page_html" | _egrep_o 'name="XSSID"\s*value="[^"]+"' | sed 's/^.*="\([^"]\{1,\}\)"$/\1/g' | head -n 1) |
||||
|
_secure_debug2 "form_xss_value" "$form_xss_value" |
||||
|
|
||||
|
_info "Generating the certificate upload request" |
||||
|
upload_post_request="$(_mktemp)" |
||||
|
upload_post_boundary="---------------------------$(date +%Y%m%d%H%M%S)" |
||||
|
|
||||
|
{ |
||||
|
printf -- "--%s\r\n" "${upload_post_boundary}" |
||||
|
printf "Content-Disposition: form-data; name=\"XSSID\"\r\n\r\n%s\r\n" "${form_xss_value}" |
||||
|
printf -- "--%s\r\n" "${upload_post_boundary}" |
||||
|
printf "Content-Disposition: form-data; name=\"http_file\"; filename=\"temp_pkcs12.pfx\"\r\n" |
||||
|
printf "Content-Type: application/pkcs12\r\n\r\n" |
||||
|
cat "${temp_pkcs12}" |
||||
|
printf "\r\n" |
||||
|
printf -- "--%s\r\n" "${upload_post_boundary}" |
||||
|
printf "Content-Disposition: form-data; name=\"pwd\"\r\n\r\n%s\r\n" "${temp_cert_password}" |
||||
|
printf -- "--%s\r\n" "${upload_post_boundary}" |
||||
|
printf "Content-Disposition: form-data; name=\"cmd\"\r\n\r\n%s\r\n" "31" |
||||
|
printf -- "--%s\r\n" "${upload_post_boundary}" |
||||
|
printf "Content-Disposition: form-data; name=\"sysSubmit\"\r\n\r\n%s\r\n" "Import" |
||||
|
printf -- "--%s--\r\n" "${upload_post_boundary}" |
||||
|
} >"${upload_post_request}" |
||||
|
|
||||
|
_info "Upload certificate to the switch" |
||||
|
|
||||
|
# Unfortunately we cannot rely upon the switch response across switch models |
||||
|
# to return a consistent body return - so we cannot inspect the result of this |
||||
|
# upload to determine success. |
||||
|
upload_response=$(_zyxel_upload_pkcs12 "${upload_post_request}" "${upload_post_boundary}" 2>&1) |
||||
|
_debug3 "Upload response: ${upload_response}" |
||||
|
rm "${upload_post_request}" |
||||
|
|
||||
|
# Pause for a few seconds to give the switch a chance to process the certificate |
||||
|
# For some reason I've found this to be necessary on my GS1900-24E |
||||
|
_debug2 "Waiting 4 seconds for the switch to process the newly uploaded certificate." |
||||
|
sleep "4" |
||||
|
|
||||
|
# Check to see whether or not our update was successful |
||||
|
_ret=0 |
||||
|
_zyxel_gs1900_should_update |
||||
|
if [ "$?" != "0" ]; then |
||||
|
_info "The certificate was updated successfully" |
||||
|
else |
||||
|
_ret=1 |
||||
|
_err "The certificate upload does not appear to have worked." |
||||
|
_err "The remote certificate does not match the certificate we tried to upload." |
||||
|
_err "Please re-run with --debug 2 and review for unexpected errors. If none can be found please submit a bug." |
||||
|
fi |
||||
|
|
||||
|
# ensure the temporary files are cleaned up |
||||
|
[ -f "${temp_pkcs12}" ] && rm -f "${temp_pkcs12}" |
||||
|
|
||||
|
return $_ret |
||||
|
} |
||||
|
|
||||
|
# make the certificate upload request using either |
||||
|
# --data binary with @ for file access in CURL |
||||
|
# or using --post-file for wget to ensure we upload |
||||
|
# the pkcs12 without getting tripped up on null bytes |
||||
|
# |
||||
|
# Usage _zyxel_upload_pkcs12 [body file name] [post boundary marker] |
||||
|
_zyxel_upload_pkcs12() { |
||||
|
bodyfilename="$1" |
||||
|
multipartformmarker="$2" |
||||
|
_post_url="${_zyxel_switch_base_uri}/cgi-bin/httpuploadcert.cgi" |
||||
|
httpmethod="POST" |
||||
|
_postContentType="multipart/form-data; boundary=${multipartformmarker}" |
||||
|
|
||||
|
if [ -z "$httpmethod" ]; then |
||||
|
httpmethod="POST" |
||||
|
fi |
||||
|
_debug $httpmethod |
||||
|
_debug "_post_url" "$_post_url" |
||||
|
_debug2 "bodyfilename" "$bodyfilename" |
||||
|
_debug2 "_postContentType" "$_postContentType" |
||||
|
|
||||
|
_inithttp |
||||
|
|
||||
|
if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then |
||||
|
_CURL="$_ACME_CURL" |
||||
|
if [ "$HTTPS_INSECURE" ]; then |
||||
|
_CURL="$_CURL --insecure " |
||||
|
fi |
||||
|
if [ "$httpmethod" = "HEAD" ]; then |
||||
|
_CURL="$_CURL -I " |
||||
|
fi |
||||
|
_debug "_CURL" "$_CURL" |
||||
|
|
||||
|
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data-binary "@${bodyfilename}" "$_post_url")" |
||||
|
|
||||
|
_ret="$?" |
||||
|
if [ "$_ret" != "0" ]; then |
||||
|
_err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret" |
||||
|
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then |
||||
|
_err "Here is the curl dump log:" |
||||
|
_err "$(cat "$_CURL_DUMP")" |
||||
|
fi |
||||
|
fi |
||||
|
elif [ "$_ACME_WGET" ]; then |
||||
|
_WGET="$_ACME_WGET" |
||||
|
if [ "$HTTPS_INSECURE" ]; then |
||||
|
_WGET="$_WGET --no-check-certificate " |
||||
|
fi |
||||
|
_debug "_WGET" "$_WGET" |
||||
|
|
||||
|
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-file="${bodyfilename}" "$_post_url" 2>"$HTTP_HEADER")" |
||||
|
|
||||
|
_ret="$?" |
||||
|
if [ "$_ret" = "8" ]; then |
||||
|
_ret=0 |
||||
|
_debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later." |
||||
|
fi |
||||
|
if [ "$_ret" != "0" ]; then |
||||
|
_err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret" |
||||
|
fi |
||||
|
if _contains "$_WGET" " -d "; then |
||||
|
# Demultiplex wget debug output |
||||
|
cat "$HTTP_HEADER" >&2 |
||||
|
_sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER" |
||||
|
fi |
||||
|
# remove leading whitespaces from header to match curl format |
||||
|
_sed_i 's/^ //g' "$HTTP_HEADER" |
||||
|
else |
||||
|
_ret="$?" |
||||
|
_err "Neither curl nor wget have been found, cannot make $httpmethod request." |
||||
|
fi |
||||
|
_debug "_ret" "$_ret" |
||||
|
printf "%s" "$response" |
||||
|
return $_ret |
||||
|
} |
||||
|
|
||||
|
_zyxel_gs1900_trigger_reboot() { |
||||
|
# Trigger a reboot via the management reboot page in the web ui |
||||
|
reboot_page_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5888" | tr -d '\n') |
||||
|
reboot_xss_value=$(printf "%s" "$reboot_page_html" | _egrep_o 'name="XSSID"\s*value="[^"]+"' | sed 's/^.*="\([^"]\{1,\}\)"$/\1/g') |
||||
|
_secure_debug2 "reboot_xss_value" "$reboot_xss_value" |
||||
|
|
||||
|
reboot_response_html=$(_post "XSSID=${reboot_xss_value}&cmd=5889&sysSubmit=Reboot" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi" '' "POST" "application/x-www-form-urlencoded") |
||||
|
reboot_message=$(printf "%s" "$reboot_response_html" | tr -d '\t\r\n\v\f' | _egrep_o "Rebooting now...") |
||||
|
|
||||
|
if [ -z "$reboot_message" ]; then |
||||
|
_err "Failed to trigger switch reboot!" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
# password |
||||
|
_zyxel_gs1900_password_obfuscate() { |
||||
|
# Return the password obfuscated via the same method used by the |
||||
|
# switch's web UI login process |
||||
|
echo "$1" | awk '{ |
||||
|
encoded = ""; |
||||
|
password = $1; |
||||
|
allowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
||||
|
len = length($1); |
||||
|
pwi = length($1); |
||||
|
|
||||
|
for (i=1; i <= (321 - pwi); i++) |
||||
|
{ |
||||
|
if (0 == i % 5 && pwi > 0) |
||||
|
{ |
||||
|
encoded = (encoded)(substr(password, pwi--, 1)); |
||||
|
} |
||||
|
else if (i == 123) |
||||
|
{ |
||||
|
if (len < 10) |
||||
|
{ |
||||
|
encoded = (encoded)(0); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
encoded = (encoded)(int(len / 10)); |
||||
|
} |
||||
|
} |
||||
|
else if (i == 289) |
||||
|
{ |
||||
|
encoded = (encoded)(len % 10) |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
encoded = (encoded)(substr(allowed, int(rand() * length(allowed)), 1)) |
||||
|
} |
||||
|
} |
||||
|
printf("%s", encoded); |
||||
|
}' |
||||
|
} |
||||
|
|
||||
|
# html label |
||||
|
_zyxel_html_table_lookup() { |
||||
|
# Look up a value in the html representing the status page of the switch |
||||
|
# when provided with the html of the page and the label (i.e. "Model Name:") |
||||
|
html="$1" |
||||
|
label=$(printf "%s" "$2" | tr -d ' ') |
||||
|
lookup_result=$(printf "%s" "$html" | tr -d "\t\r\n\v\f" | sed 's/<tr>/\n<tr>/g' | sed 's/<td[^>]*>/<td>/g' | tr -d ' ' | grep -i "$label" | sed "s/<tr><td>$label<\/td><td>\([^<]\{1,\}\)<\/td><\/tr>/\1/i") |
||||
|
printf "%s" "$lookup_result" |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
# html |
||||
|
_zyxel_gs1900_get_model() { |
||||
|
html="$1" |
||||
|
model_name=$(_zyxel_html_table_lookup "$html" "Model Name:") |
||||
|
printf "%s" "$model_name" |
||||
|
} |
||||
|
|
||||
|
# html |
||||
|
_zyxel_gs1900_get_firmware_version() { |
||||
|
html="$1" |
||||
|
firmware_version=$(_zyxel_html_table_lookup "$html" "Firmware Version:" | _egrep_o "V[^.]+.[^(]+") |
||||
|
printf "%s" "$firmware_version" |
||||
|
} |
||||
|
|
||||
|
# version_number |
||||
|
_zyxel_gs1900_parse_major_version() { |
||||
|
printf "%s" "$1" | sed 's/^V\([0-9]\{1,\}\).\{1,\}$/\1/gi' |
||||
|
} |
||||
|
|
||||
|
# version_number |
||||
|
_zyxel_gs1900_parse_minor_version() { |
||||
|
printf "%s" "$1" | sed 's/^.\{1,\}\.\([0-9]\{1,\}\)$/\1/gi' |
||||
|
} |
||||
@ -0,0 +1,185 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
# shellcheck disable=SC2034 |
||||
|
dns_alviy_info='Alviy.com |
||||
|
Site: Alviy.com |
||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_alviy |
||||
|
Options: |
||||
|
Alviy_token API token. Get it from the https://cloud.alviy.com/token |
||||
|
Issues: github.com/acmesh-official/acme.sh/issues/5115 |
||||
|
' |
||||
|
|
||||
|
Alviy_Api="https://cloud.alviy.com/api/v1" |
||||
|
|
||||
|
######## Public functions ##################### |
||||
|
|
||||
|
#Usage: dns_alviy_add _acme-challenge.www.domain.com "content" |
||||
|
dns_alviy_add() { |
||||
|
fulldomain=$1 |
||||
|
txtvalue=$2 |
||||
|
|
||||
|
Alviy_token="${Alviy_token:-$(_readaccountconf_mutable Alviy_token)}" |
||||
|
if [ -z "$Alviy_token" ]; then |
||||
|
Alviy_token="" |
||||
|
_err "Please specify Alviy token." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
#save the api key and email to the account conf file. |
||||
|
_saveaccountconf_mutable Alviy_token "$Alviy_token" |
||||
|
|
||||
|
_debug "First detect the root zone" |
||||
|
if ! _get_root "$fulldomain"; then |
||||
|
_err "invalid domain" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_debug _sub_domain "$_sub_domain" |
||||
|
_debug _domain "$_domain" |
||||
|
|
||||
|
_debug "Getting existing records" |
||||
|
if _alviy_txt_exists "$_domain" "$fulldomain" "$txtvalue"; then |
||||
|
_info "This record already exists, skipping" |
||||
|
return 0 |
||||
|
fi |
||||
|
|
||||
|
_add_data="{\"content\":\"$txtvalue\",\"type\":\"TXT\"}" |
||||
|
_debug2 _add_data "$_add_data" |
||||
|
_info "Adding record" |
||||
|
if _alviy_rest POST "zone/$_domain/domain/$fulldomain/" "$_add_data"; then |
||||
|
_debug "Checking updated records of '${fulldomain}'" |
||||
|
|
||||
|
if ! _alviy_txt_exists "$_domain" "$fulldomain" "$txtvalue"; then |
||||
|
_err "TXT record '${txtvalue}' for '${fulldomain}', value wasn't set!" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
else |
||||
|
_err "Add txt record error, value '${txtvalue}' for '${fulldomain}' was not set." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_sleep 10 |
||||
|
_info "Added TXT record '${txtvalue}' for '${fulldomain}'." |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
#fulldomain |
||||
|
dns_alviy_rm() { |
||||
|
fulldomain=$1 |
||||
|
txtvalue=$2 |
||||
|
|
||||
|
Alviy_token="${Alviy_token:-$(_readaccountconf_mutable Alviy_token)}" |
||||
|
|
||||
|
_debug "First detect the root zone" |
||||
|
if ! _get_root "$fulldomain"; then |
||||
|
_err "invalid domain" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_debug _sub_domain "$_sub_domain" |
||||
|
_debug _domain "$_domain" |
||||
|
|
||||
|
if ! _alviy_txt_exists "$_domain" "$fulldomain" "$txtvalue"; then |
||||
|
_info "The record does not exist, skip" |
||||
|
return 0 |
||||
|
fi |
||||
|
|
||||
|
_add_data="" |
||||
|
uuid=$(echo "$response" | tr "{" "\n" | grep "$txtvalue" | tr "," "\n" | grep uuid | cut -d \" -f4) |
||||
|
# delete record |
||||
|
_debug "Delete TXT record for '${fulldomain}'" |
||||
|
if ! _alviy_rest DELETE "zone/$_domain/record/$uuid" "{\"confirm\":1}"; then |
||||
|
_err "Cannot delete empty TXT record for '$fulldomain'" |
||||
|
return 1 |
||||
|
fi |
||||
|
_info "The record '$fulldomain'='$txtvalue' deleted" |
||||
|
} |
||||
|
|
||||
|
#################### Private functions below ################################## |
||||
|
#_acme-challenge.www.domain.com |
||||
|
#returns |
||||
|
# _sub_domain=_acme-challenge.www |
||||
|
# _domain=domain.com |
||||
|
_get_root() { |
||||
|
domain=$1 |
||||
|
i=3 |
||||
|
a="init" |
||||
|
while [ -n "$a" ]; do |
||||
|
a=$(printf "%s" "$domain" | cut -d . -f $i-) |
||||
|
i=$((i + 1)) |
||||
|
done |
||||
|
n=$((i - 3)) |
||||
|
h=$(printf "%s" "$domain" | cut -d . -f $n-) |
||||
|
if [ -z "$h" ]; then |
||||
|
#not valid |
||||
|
_alviy_rest GET "zone/$domain/" |
||||
|
_debug "can't get host from $domain" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if ! _alviy_rest GET "zone/$h/"; then |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if _contains "$response" '"code":"NOT_FOUND"'; then |
||||
|
_debug "$h not found" |
||||
|
else |
||||
|
s=$((n - 1)) |
||||
|
_sub_domain=$(printf "%s" "$domain" | cut -d . -f -$s) |
||||
|
_domain="$h" |
||||
|
return 0 |
||||
|
fi |
||||
|
return 1 |
||||
|
} |
||||
|
|
||||
|
_alviy_txt_exists() { |
||||
|
zone=$1 |
||||
|
domain=$2 |
||||
|
content_data=$3 |
||||
|
_debug "Getting existing records" |
||||
|
|
||||
|
if ! _alviy_rest GET "zone/$zone/domain/$domain/TXT/"; then |
||||
|
_info "The record does not exist" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if ! _contains "$response" "$3"; then |
||||
|
_info "The record has other value" |
||||
|
return 1 |
||||
|
fi |
||||
|
# GOOD code return - TRUE function |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
_alviy_rest() { |
||||
|
method=$1 |
||||
|
path="$2" |
||||
|
content_data="$3" |
||||
|
_debug "$path" |
||||
|
|
||||
|
export _H1="Authorization: Bearer $Alviy_token" |
||||
|
export _H2="Content-Type: application/json" |
||||
|
|
||||
|
if [ "$content_data" ] || [ "$method" = "DELETE" ]; then |
||||
|
_debug "data ($method): " "$content_data" |
||||
|
response="$(_post "$content_data" "$Alviy_Api/$path" "" "$method")" |
||||
|
else |
||||
|
response="$(_get "$Alviy_Api/$path")" |
||||
|
fi |
||||
|
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" |
||||
|
if [ "$_code" = "401" ]; then |
||||
|
_err "It seems that your api key or secret is not correct." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if [ "$_code" != "200" ]; then |
||||
|
_err "API call error ($method): $path Response code $_code" |
||||
|
fi |
||||
|
if [ "$?" != "0" ]; then |
||||
|
_err "error on rest call ($method): $path. Response:" |
||||
|
_err "$response" |
||||
|
return 1 |
||||
|
fi |
||||
|
_debug2 response "$response" |
||||
|
return 0 |
||||
|
} |
||||
@ -0,0 +1,281 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
# shellcheck disable=SC2034 |
||||
|
dns_beget_info='Beget.com |
||||
|
Site: Beget.com |
||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_beget |
||||
|
Options: |
||||
|
BEGET_User API user |
||||
|
BEGET_Password API password |
||||
|
Issues: github.com/acmesh-official/acme.sh/issues/6200 |
||||
|
Author: ARNik <arnik@arnik.ru> |
||||
|
' |
||||
|
|
||||
|
Beget_Api="https://api.beget.com/api" |
||||
|
|
||||
|
#################### Public functions #################### |
||||
|
|
||||
|
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
||||
|
# Used to add txt record |
||||
|
dns_beget_add() { |
||||
|
fulldomain=$1 |
||||
|
txtvalue=$2 |
||||
|
_debug "dns_beget_add() $fulldomain $txtvalue" |
||||
|
fulldomain=$(echo "$fulldomain" | _lower_case) |
||||
|
|
||||
|
Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" |
||||
|
Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" |
||||
|
|
||||
|
if [ -z "$Beget_Username" ] || [ -z "$Beget_Password" ]; then |
||||
|
Beget_Username="" |
||||
|
Beget_Password="" |
||||
|
_err "You must export variables: Beget_Username, and Beget_Password" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
#save the credentials to the account conf file. |
||||
|
_saveaccountconf_mutable Beget_Username "$Beget_Username" |
||||
|
_saveaccountconf_mutable Beget_Password "$Beget_Password" |
||||
|
|
||||
|
_info "Prepare subdomain." |
||||
|
if ! _prepare_subdomain "$fulldomain"; then |
||||
|
_err "Can't prepare subdomain." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_info "Get domain records" |
||||
|
data="{\"fqdn\":\"$fulldomain\"}" |
||||
|
res=$(_api_call "$Beget_Api/dns/getData" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't get domain records." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_info "Add new TXT record" |
||||
|
data="{\"fqdn\":\"$fulldomain\",\"records\":{" |
||||
|
data=${data}$(_parce_records "$res" "A") |
||||
|
data=${data}$(_parce_records "$res" "AAAA") |
||||
|
data=${data}$(_parce_records "$res" "CAA") |
||||
|
data=${data}$(_parce_records "$res" "MX") |
||||
|
data=${data}$(_parce_records "$res" "SRV") |
||||
|
data=${data}$(_parce_records "$res" "TXT") |
||||
|
data=$(echo "$data" | sed 's/,$//') |
||||
|
data=${data}'}}' |
||||
|
|
||||
|
str=$(_txt_to_dns_json "$txtvalue") |
||||
|
data=$(_add_record "$data" "TXT" "$str") |
||||
|
|
||||
|
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't change domain records." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
# Usage: fulldomain txtvalue |
||||
|
# Used to remove the txt record after validation |
||||
|
dns_beget_rm() { |
||||
|
fulldomain=$1 |
||||
|
txtvalue=$2 |
||||
|
_debug "dns_beget_rm() $fulldomain $txtvalue" |
||||
|
fulldomain=$(echo "$fulldomain" | _lower_case) |
||||
|
|
||||
|
Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" |
||||
|
Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" |
||||
|
|
||||
|
_info "Get current domain records" |
||||
|
data="{\"fqdn\":\"$fulldomain\"}" |
||||
|
res=$(_api_call "$Beget_Api/dns/getData" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't get domain records." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_info "Remove TXT record" |
||||
|
data="{\"fqdn\":\"$fulldomain\",\"records\":{" |
||||
|
data=${data}$(_parce_records "$res" "A") |
||||
|
data=${data}$(_parce_records "$res" "AAAA") |
||||
|
data=${data}$(_parce_records "$res" "CAA") |
||||
|
data=${data}$(_parce_records "$res" "MX") |
||||
|
data=${data}$(_parce_records "$res" "SRV") |
||||
|
data=${data}$(_parce_records "$res" "TXT") |
||||
|
data=$(echo "$data" | sed 's/,$//') |
||||
|
data=${data}'}}' |
||||
|
|
||||
|
str=$(_txt_to_dns_json "$txtvalue") |
||||
|
data=$(_rm_record "$data" "$str") |
||||
|
|
||||
|
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't change domain records." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
#################### Private functions below #################### |
||||
|
|
||||
|
# Create subdomain if needed |
||||
|
# Usage: _prepare_subdomain [fulldomain] |
||||
|
_prepare_subdomain() { |
||||
|
fulldomain=$1 |
||||
|
|
||||
|
_info "Detect the root zone" |
||||
|
if ! _get_root "$fulldomain"; then |
||||
|
_err "invalid domain" |
||||
|
return 1 |
||||
|
fi |
||||
|
_debug _domain_id "$_domain_id" |
||||
|
_debug _sub_domain "$_sub_domain" |
||||
|
_debug _domain "$_domain" |
||||
|
|
||||
|
if [ -z "$_sub_domain" ]; then |
||||
|
_debug "$fulldomain is a root domain." |
||||
|
return 0 |
||||
|
fi |
||||
|
|
||||
|
_info "Get subdomain list" |
||||
|
res=$(_api_call "$Beget_Api/domain/getSubdomainList") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't get subdomain list." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if _contains "$res" "\"fqdn\":\"$fulldomain\""; then |
||||
|
_debug "Subdomain $fulldomain already exist." |
||||
|
return 0 |
||||
|
fi |
||||
|
|
||||
|
_info "Subdomain $fulldomain does not exist. Let's create one." |
||||
|
data="{\"subdomain\":\"$_sub_domain\",\"domain_id\":$_domain_id}" |
||||
|
res=$(_api_call "$Beget_Api/domain/addSubdomainVirtual" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't create subdomain." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_debug "Cleanup subdomen records" |
||||
|
data="{\"fqdn\":\"$fulldomain\",\"records\":{}}" |
||||
|
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_debug "Can't cleanup $fulldomain records." |
||||
|
fi |
||||
|
|
||||
|
data="{\"fqdn\":\"www.$fulldomain\",\"records\":{}}" |
||||
|
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_debug "Can't cleanup www.$fulldomain records." |
||||
|
fi |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
# Usage: _get_root _acme-challenge.www.domain.com |
||||
|
#returns |
||||
|
# _sub_domain=_acme-challenge.www |
||||
|
# _domain=domain.com |
||||
|
# _domain_id=32436365 |
||||
|
_get_root() { |
||||
|
fulldomain=$1 |
||||
|
i=1 |
||||
|
p=1 |
||||
|
|
||||
|
_debug "Get domain list" |
||||
|
res=$(_api_call "$Beget_Api/domain/getList") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't get domain list." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
while true; do |
||||
|
h=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) |
||||
|
_debug h "$h" |
||||
|
|
||||
|
if [ -z "$h" ]; then |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if _contains "$res" "$h"; then |
||||
|
_domain_id=$(echo "$res" | _egrep_o "\"id\":[0-9]*,\"fqdn\":\"$h\"" | cut -d , -f1 | cut -d : -f2) |
||||
|
if [ "$_domain_id" ]; then |
||||
|
if [ "$h" != "$fulldomain" ]; then |
||||
|
_sub_domain=$(echo "$fulldomain" | cut -d . -f 1-"$p") |
||||
|
else |
||||
|
_sub_domain="" |
||||
|
fi |
||||
|
_domain=$h |
||||
|
return 0 |
||||
|
fi |
||||
|
return 1 |
||||
|
fi |
||||
|
p="$i" |
||||
|
i=$(_math "$i" + 1) |
||||
|
done |
||||
|
return 1 |
||||
|
} |
||||
|
|
||||
|
# Parce DNS records from json string |
||||
|
# Usage: _parce_records [j_str] [record_name] |
||||
|
_parce_records() { |
||||
|
j_str=$1 |
||||
|
record_name=$2 |
||||
|
res="\"$record_name\":[" |
||||
|
res=${res}$(echo "$j_str" | _egrep_o "\"$record_name\":\[.*" | cut -d '[' -f2 | cut -d ']' -f1) |
||||
|
res=${res}"]," |
||||
|
echo "$res" |
||||
|
} |
||||
|
|
||||
|
# Usage: _add_record [data] [record_name] [record_data] |
||||
|
_add_record() { |
||||
|
data=$1 |
||||
|
record_name=$2 |
||||
|
record_data=$3 |
||||
|
echo "$data" | sed "s/\"$record_name\":\[/\"$record_name\":\[$record_data,/" | sed "s/,\]/\]/" |
||||
|
} |
||||
|
|
||||
|
# Usage: _rm_record [data] [record_data] |
||||
|
_rm_record() { |
||||
|
data=$1 |
||||
|
record_data=$2 |
||||
|
echo "$data" | sed "s/$record_data//g" | sed "s/,\+/,/g" | |
||||
|
sed "s/{,/{/g" | sed "s/,}/}/g" | |
||||
|
sed "s/\[,/\[/g" | sed "s/,\]/\]/g" |
||||
|
} |
||||
|
|
||||
|
_txt_to_dns_json() { |
||||
|
echo "{\"ttl\":600,\"txtdata\":\"$1\"}" |
||||
|
} |
||||
|
|
||||
|
# Usage: _api_call [api_url] [input_data] |
||||
|
_api_call() { |
||||
|
api_url="$1" |
||||
|
input_data="$2" |
||||
|
|
||||
|
_debug "_api_call $api_url" |
||||
|
_debug "Request: $input_data" |
||||
|
|
||||
|
# res=$(curl -s -L -D ./http.header \ |
||||
|
# "$api_url" \ |
||||
|
# --data-urlencode login=$Beget_Username \ |
||||
|
# --data-urlencode passwd=$Beget_Password \ |
||||
|
# --data-urlencode input_format=json \ |
||||
|
# --data-urlencode output_format=json \ |
||||
|
# --data-urlencode "input_data=$input_data") |
||||
|
|
||||
|
url="$api_url?login=$Beget_Username&passwd=$Beget_Password&input_format=json&output_format=json" |
||||
|
if [ -n "$input_data" ]; then |
||||
|
url=${url}"&input_data=" |
||||
|
url=${url}$(echo "$input_data" | _url_encode) |
||||
|
fi |
||||
|
res=$(_get "$url") |
||||
|
|
||||
|
_debug "Reply: $res" |
||||
|
echo "$res" |
||||
|
} |
||||
|
|
||||
|
# Usage: _is_api_reply_ok [api_reply] |
||||
|
_is_api_reply_ok() { |
||||
|
_contains "$1" '^{"status":"success","answer":{"status":"success","result":.*}}$' |
||||
|
} |
||||
@ -0,0 +1,163 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
# shellcheck disable=SC2034 |
||||
|
dns_edgecenter_info='EdgeCenter.ru |
||||
|
Site: EdgeCenter.ru |
||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_edgecenter |
||||
|
Options: |
||||
|
EDGECENTER_API_KEY API Key |
||||
|
Issues: github.com/acmesh-official/acme.sh/issues/6313 |
||||
|
Author: Konstantin Ruchev <konstantin.ruchev@edgecenter.ru> |
||||
|
' |
||||
|
|
||||
|
EDGECENTER_API="https://api.edgecenter.ru" |
||||
|
DOMAIN_TYPE= |
||||
|
DOMAIN_MASTER= |
||||
|
|
||||
|
######## Public functions ##################### |
||||
|
|
||||
|
#Usage: dns_edgecenter_add _acme-challenge.www.domain.com "TXT_RECORD_VALUE" |
||||
|
dns_edgecenter_add() { |
||||
|
fulldomain="$1" |
||||
|
txtvalue="$2" |
||||
|
|
||||
|
_info "Using EdgeCenter DNS API" |
||||
|
|
||||
|
if ! _dns_edgecenter_init_check; then |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_debug "Detecting root zone for $fulldomain" |
||||
|
if ! _get_root "$fulldomain"; then |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
subdomain="${fulldomain%."$_zone"}" |
||||
|
subdomain=${subdomain%.} |
||||
|
|
||||
|
_debug "Zone: $_zone" |
||||
|
_debug "Subdomain: $subdomain" |
||||
|
_debug "TXT value: $txtvalue" |
||||
|
|
||||
|
payload='{"resource_records": [ { "content": ["'"$txtvalue"'"] } ], "ttl": 60 }' |
||||
|
_dns_edgecenter_http_api_call "post" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" "$payload" |
||||
|
|
||||
|
if _contains "$response" '"error":"rrset is already exists"'; then |
||||
|
_debug "RRSet exists, merging values" |
||||
|
_dns_edgecenter_http_api_call "get" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" |
||||
|
current="$response" |
||||
|
newlist="" |
||||
|
for v in $(echo "$current" | sed -n 's/.*"content":\["\([^"]*\)"\].*/\1/p'); do |
||||
|
newlist="$newlist {\"content\":[\"$v\"]}," |
||||
|
done |
||||
|
newlist="$newlist{\"content\":[\"$txtvalue\"]}" |
||||
|
putdata="{\"resource_records\":[${newlist}]} |
||||
|
" |
||||
|
_dns_edgecenter_http_api_call "put" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" "$putdata" |
||||
|
_info "Updated existing RRSet with new TXT value." |
||||
|
return 0 |
||||
|
fi |
||||
|
|
||||
|
if _contains "$response" '"exception":'; then |
||||
|
_err "Record cannot be added." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_info "TXT record added successfully." |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
#Usage: dns_edgecenter_rm _acme-challenge.www.domain.com "TXT_RECORD_VALUE" |
||||
|
dns_edgecenter_rm() { |
||||
|
fulldomain="$1" |
||||
|
txtvalue="$2" |
||||
|
|
||||
|
_info "Removing TXT record for $fulldomain" |
||||
|
|
||||
|
if ! _dns_edgecenter_init_check; then |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if ! _get_root "$fulldomain"; then |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
subdomain="${fulldomain%."$_zone"}" |
||||
|
subdomain=${subdomain%.} |
||||
|
|
||||
|
_dns_edgecenter_http_api_call "delete" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" |
||||
|
|
||||
|
if [ -z "$response" ]; then |
||||
|
_info "TXT record deleted successfully." |
||||
|
else |
||||
|
_info "TXT record may not have been deleted: $response" |
||||
|
fi |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
#################### Private functions below ################################## |
||||
|
|
||||
|
_dns_edgecenter_init_check() { |
||||
|
EDGECENTER_API_KEY="${EDGECENTER_API_KEY:-$(_readaccountconf_mutable EDGECENTER_API_KEY)}" |
||||
|
if [ -z "$EDGECENTER_API_KEY" ]; then |
||||
|
_err "EDGECENTER_API_KEY was not exported." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_saveaccountconf_mutable EDGECENTER_API_KEY "$EDGECENTER_API_KEY" |
||||
|
export _H1="Authorization: APIKey $EDGECENTER_API_KEY" |
||||
|
|
||||
|
_dns_edgecenter_http_api_call "get" "dns/v2/clients/me/features" |
||||
|
if ! _contains "$response" '"id":'; then |
||||
|
_err "Invalid API key." |
||||
|
return 1 |
||||
|
fi |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
_get_root() { |
||||
|
domain="$1" |
||||
|
i=1 |
||||
|
while true; do |
||||
|
h=$(printf "%s" "$domain" | cut -d . -f "$i"-) |
||||
|
if [ -z "$h" ]; then |
||||
|
return 1 |
||||
|
fi |
||||
|
_dns_edgecenter_http_api_call "get" "dns/v2/zones/$h" |
||||
|
if ! _contains "$response" 'zone is not found'; then |
||||
|
_zone="$h" |
||||
|
return 0 |
||||
|
fi |
||||
|
i=$((i + 1)) |
||||
|
done |
||||
|
return 1 |
||||
|
} |
||||
|
|
||||
|
_dns_edgecenter_http_api_call() { |
||||
|
mtd="$1" |
||||
|
endpoint="$2" |
||||
|
data="$3" |
||||
|
|
||||
|
export _H1="Authorization: APIKey $EDGECENTER_API_KEY" |
||||
|
|
||||
|
case "$mtd" in |
||||
|
get) |
||||
|
response="$(_get "$EDGECENTER_API/$endpoint")" |
||||
|
;; |
||||
|
post) |
||||
|
response="$(_post "$data" "$EDGECENTER_API/$endpoint")" |
||||
|
;; |
||||
|
delete) |
||||
|
response="$(_post "" "$EDGECENTER_API/$endpoint" "" "DELETE")" |
||||
|
;; |
||||
|
put) |
||||
|
response="$(_post "$data" "$EDGECENTER_API/$endpoint" "" "PUT")" |
||||
|
;; |
||||
|
*) |
||||
|
_err "Unknown HTTP method $mtd" |
||||
|
return 1 |
||||
|
;; |
||||
|
esac |
||||
|
|
||||
|
_debug "HTTP $mtd response: $response" |
||||
|
return 0 |
||||
|
} |
||||
@ -0,0 +1,105 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
# shellcheck disable=SC2034 |
||||
|
dns_freemyip_info='FreeMyIP.com |
||||
|
Site: FreeMyIP.com |
||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_freemyip |
||||
|
Options: |
||||
|
FREEMYIP_Token API Token |
||||
|
Issues: github.com/acmesh-official/acme.sh/issues/6247 |
||||
|
Author: Recolic Keghart <root@recolic.net>, @Giova96 |
||||
|
' |
||||
|
|
||||
|
FREEMYIP_DNS_API="https://freemyip.com/update?" |
||||
|
|
||||
|
################ Public functions ################ |
||||
|
|
||||
|
#Usage: dns_freemyip_add fulldomain txtvalue |
||||
|
dns_freemyip_add() { |
||||
|
fulldomain="$1" |
||||
|
txtvalue="$2" |
||||
|
|
||||
|
_info "Add TXT record $txtvalue for $fulldomain using freemyip.com api" |
||||
|
|
||||
|
FREEMYIP_Token="${FREEMYIP_Token:-$(_readaccountconf_mutable FREEMYIP_Token)}" |
||||
|
if [ -z "$FREEMYIP_Token" ]; then |
||||
|
FREEMYIP_Token="" |
||||
|
_err "You don't specify FREEMYIP_Token yet." |
||||
|
_err "Please specify your token and try again." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
#save the credentials to the account conf file. |
||||
|
_saveaccountconf_mutable FREEMYIP_Token "$FREEMYIP_Token" |
||||
|
|
||||
|
if _is_root_domain_published "$fulldomain"; then |
||||
|
_err "freemyip API don't allow you to set multiple TXT record for the same subdomain!" |
||||
|
_err "You must apply certificate for only one domain at a time!" |
||||
|
_err "====" |
||||
|
_err "For example, aaa.yourdomain.freemyip.com and bbb.yourdomain.freemyip.com and yourdomain.freemyip.com ALWAYS share the same TXT record. They will overwrite each other if you apply multiple domain at the same time." |
||||
|
_debug "If you are testing this workflow in github pipeline or acmetest, please set TEST_DNS_NO_SUBDOMAIN=1 and TEST_DNS_NO_WILDCARD=1" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
# txtvalue must be url-encoded. But it's not necessary for acme txt value. |
||||
|
_freemyip_get_until_ok "${FREEMYIP_DNS_API}token=$FREEMYIP_Token&domain=$fulldomain&txt=$txtvalue" 2>&1 |
||||
|
return $? |
||||
|
} |
||||
|
|
||||
|
#Usage: dns_freemyip_rm fulldomain txtvalue |
||||
|
dns_freemyip_rm() { |
||||
|
fulldomain="$1" |
||||
|
txtvalue="$2" |
||||
|
|
||||
|
_info "Delete TXT record $txtvalue for $fulldomain using freemyip.com api" |
||||
|
|
||||
|
FREEMYIP_Token="${FREEMYIP_Token:-$(_readaccountconf_mutable FREEMYIP_Token)}" |
||||
|
if [ -z "$FREEMYIP_Token" ]; then |
||||
|
FREEMYIP_Token="" |
||||
|
_err "You don't specify FREEMYIP_Token yet." |
||||
|
_err "Please specify your token and try again." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
#save the credentials to the account conf file. |
||||
|
_saveaccountconf_mutable FREEMYIP_Token "$FREEMYIP_Token" |
||||
|
|
||||
|
# Leave the TXT record as empty or "null" to delete the record. |
||||
|
_freemyip_get_until_ok "${FREEMYIP_DNS_API}token=$FREEMYIP_Token&domain=$fulldomain&txt=" 2>&1 |
||||
|
return $? |
||||
|
} |
||||
|
|
||||
|
################ Private functions below ################ |
||||
|
_get_root() { |
||||
|
_fmi_d="$1" |
||||
|
|
||||
|
echo "$_fmi_d" | rev | cut -d '.' -f 1-3 | rev |
||||
|
} |
||||
|
|
||||
|
# There is random failure while calling freemyip API too fast. This function automatically retry until success. |
||||
|
_freemyip_get_until_ok() { |
||||
|
_fmi_url="$1" |
||||
|
for i in $(seq 1 8); do |
||||
|
_debug "HTTP GET freemyip.com API '$_fmi_url', retry $i/8..." |
||||
|
_get "$_fmi_url" | tee /dev/fd/2 | grep OK && return 0 |
||||
|
_sleep 1 # DO NOT send the request too fast |
||||
|
done |
||||
|
_err "Failed to request freemyip API: $_fmi_url . Server does not say 'OK'" |
||||
|
return 1 |
||||
|
} |
||||
|
|
||||
|
# Verify in public dns if domain is already there. |
||||
|
_is_root_domain_published() { |
||||
|
_fmi_d="$1" |
||||
|
_webroot="$(_get_root "$_fmi_d")" |
||||
|
|
||||
|
_info "Verifying '""$_fmi_d""' freemyip webroot (""$_webroot"") is not published yet" |
||||
|
for i in $(seq 1 3); do |
||||
|
_debug "'$_webroot' ns lookup, retry $i/3..." |
||||
|
if [ "$(_ns_lookup "$_fmi_d" TXT)" ]; then |
||||
|
_debug "'$_webroot' already has a TXT record published!" |
||||
|
return 0 |
||||
|
fi |
||||
|
_sleep 10 # Give it some time to propagate the TXT record |
||||
|
done |
||||
|
return 1 |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
# shellcheck disable=SC2034 |
||||
|
dns_he_ddns_info='Hurricane Electric HE.net DDNS |
||||
|
Site: dns.he.net |
||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_he_ddns |
||||
|
Options: |
||||
|
HE_DDNS_KEY The DDNS key |
||||
|
Issues: https://github.com/acmesh-official/acme.sh/issues/5238 |
||||
|
Author: Markku Leiniö |
||||
|
' |
||||
|
|
||||
|
HE_DDNS_URL="https://dyn.dns.he.net/nic/update" |
||||
|
|
||||
|
######## Public functions ##################### |
||||
|
|
||||
|
#Usage: dns_he_ddns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
||||
|
dns_he_ddns_add() { |
||||
|
fulldomain=$1 |
||||
|
txtvalue=$2 |
||||
|
HE_DDNS_KEY="${HE_DDNS_KEY:-$(_readaccountconf_mutable HE_DDNS_KEY)}" |
||||
|
if [ -z "$HE_DDNS_KEY" ]; then |
||||
|
HE_DDNS_KEY="" |
||||
|
_err "You didn't specify a DDNS key for accessing the TXT record in HE API." |
||||
|
return 1 |
||||
|
fi |
||||
|
#Save the DDNS key to the account conf file. |
||||
|
_saveaccountconf_mutable HE_DDNS_KEY "$HE_DDNS_KEY" |
||||
|
|
||||
|
_info "Using Hurricane Electric DDNS API" |
||||
|
_debug fulldomain "$fulldomain" |
||||
|
_debug txtvalue "$txtvalue" |
||||
|
|
||||
|
response="$(_post "hostname=$fulldomain&password=$HE_DDNS_KEY&txt=$txtvalue" "$HE_DDNS_URL")" |
||||
|
_info "Response: $response" |
||||
|
_contains "$response" "good" && return 0 || return 1 |
||||
|
} |
||||
|
|
||||
|
# dns_he_ddns_rm() is not doing anything because the API call always updates the |
||||
|
# contents of the existing record (that the API key gives access to). |
||||
|
|
||||
|
dns_he_ddns_rm() { |
||||
|
fulldomain=$1 |
||||
|
_debug "Delete TXT record called for '${fulldomain}', not doing anything." |
||||
|
return 0 |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue