mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2026-08-13 12:33:30 +02:00
Merge pull request #7086 from acmesh-official/dev
Some checks failed
Apache / Apache (push) Has been cancelled
DNS / CheckToken (push) Has been cancelled
DragonFlyBSD / DragonFlyBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
FreeBSD / FreeBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
FreeBSD / FreeBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
GhostBSD / GhostBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
GhostBSD / GhostBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Haiku / Haiku (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Haiku / Haiku (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Linux / Linux (almalinux:latest) (push) Has been cancelled
Linux / Linux (alpine:latest) (push) Has been cancelled
Linux / Linux (archlinux:latest) (push) Has been cancelled
Linux / Linux (debian:latest) (push) Has been cancelled
Linux / Linux (fedora:latest) (push) Has been cancelled
Linux / Linux (gentoo/stage3) (push) Has been cancelled
Linux / Linux (kalilinux/kali) (push) Has been cancelled
Linux / Linux (opensuse/leap:latest) (push) Has been cancelled
Linux / Linux (oraclelinux:8) (push) Has been cancelled
Linux / Linux (ubuntu:latest) (push) Has been cancelled
MacOS / MacOS (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
MidnightBSD / MidnightBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
NetBSD / NetBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Nginx / Nginx (push) Has been cancelled
Omnios / Omnios (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Omnios / Omnios (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenBSD / OpenBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenBSD / OpenBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenIndiana / OpenIndiana (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenIndiana / OpenIndiana (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
PebbleStrict / PebbleStrict (push) Has been cancelled
PebbleStrict / PebbleStrict_IPCert (push) Has been cancelled
Solaris / Solaris (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Solaris / Solaris (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Tribblix / Tribblix (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Tribblix / Tribblix (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, ) (push) Has been cancelled
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, 1, , 172.17.0.1) (push) Has been cancelled
Ubuntu / Ubuntu (ZeroSSL RSA DV SSL CA 2, ZeroSSL ECC DV SSL CA 2, githubtest@acme.sh, ZeroSSL.com, ) (push) Has been cancelled
Windows / Windows (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Build DockerHub / CheckToken (push) Has been cancelled
Shellcheck / ShellCheck (push) Has been cancelled
Shellcheck / shfmt (push) Has been cancelled
DNS / Fail (push) Has been cancelled
DNS / Docker (push) Has been cancelled
DNS / MacOS (push) Has been cancelled
DNS / Windows (push) Has been cancelled
DNS / FreeBSD (push) Has been cancelled
DNS / GhostBSD (push) Has been cancelled
DNS / OpenBSD (push) Has been cancelled
DNS / NetBSD (push) Has been cancelled
DNS / DragonFlyBSD (push) Has been cancelled
DNS / MidnightBSD (push) Has been cancelled
DNS / Solaris (push) Has been cancelled
DNS / Omnios (push) Has been cancelled
DNS / OpenIndiana (push) Has been cancelled
DNS / Tribblix (push) Has been cancelled
DNS / Haiku (push) Has been cancelled
Build DockerHub / build (push) Has been cancelled
Some checks failed
Apache / Apache (push) Has been cancelled
DNS / CheckToken (push) Has been cancelled
DragonFlyBSD / DragonFlyBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
FreeBSD / FreeBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
FreeBSD / FreeBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
GhostBSD / GhostBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
GhostBSD / GhostBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Haiku / Haiku (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Haiku / Haiku (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Linux / Linux (almalinux:latest) (push) Has been cancelled
Linux / Linux (alpine:latest) (push) Has been cancelled
Linux / Linux (archlinux:latest) (push) Has been cancelled
Linux / Linux (debian:latest) (push) Has been cancelled
Linux / Linux (fedora:latest) (push) Has been cancelled
Linux / Linux (gentoo/stage3) (push) Has been cancelled
Linux / Linux (kalilinux/kali) (push) Has been cancelled
Linux / Linux (opensuse/leap:latest) (push) Has been cancelled
Linux / Linux (oraclelinux:8) (push) Has been cancelled
Linux / Linux (ubuntu:latest) (push) Has been cancelled
MacOS / MacOS (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
MidnightBSD / MidnightBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
NetBSD / NetBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Nginx / Nginx (push) Has been cancelled
Omnios / Omnios (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Omnios / Omnios (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenBSD / OpenBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenBSD / OpenBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenIndiana / OpenIndiana (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenIndiana / OpenIndiana (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
PebbleStrict / PebbleStrict (push) Has been cancelled
PebbleStrict / PebbleStrict_IPCert (push) Has been cancelled
Solaris / Solaris (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Solaris / Solaris (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Tribblix / Tribblix (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Tribblix / Tribblix (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, ) (push) Has been cancelled
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, 1, , 172.17.0.1) (push) Has been cancelled
Ubuntu / Ubuntu (ZeroSSL RSA DV SSL CA 2, ZeroSSL ECC DV SSL CA 2, githubtest@acme.sh, ZeroSSL.com, ) (push) Has been cancelled
Windows / Windows (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Build DockerHub / CheckToken (push) Has been cancelled
Shellcheck / ShellCheck (push) Has been cancelled
Shellcheck / shfmt (push) Has been cancelled
DNS / Fail (push) Has been cancelled
DNS / Docker (push) Has been cancelled
DNS / MacOS (push) Has been cancelled
DNS / Windows (push) Has been cancelled
DNS / FreeBSD (push) Has been cancelled
DNS / GhostBSD (push) Has been cancelled
DNS / OpenBSD (push) Has been cancelled
DNS / NetBSD (push) Has been cancelled
DNS / DragonFlyBSD (push) Has been cancelled
DNS / MidnightBSD (push) Has been cancelled
DNS / Solaris (push) Has been cancelled
DNS / Omnios (push) Has been cancelled
DNS / OpenIndiana (push) Has been cancelled
DNS / Tribblix (push) Has been cancelled
DNS / Haiku (push) Has been cancelled
Build DockerHub / build (push) Has been cancelled
sync
This commit is contained in:
commit
f50401a342
33 changed files with 2028 additions and 286 deletions
50
.github/workflows/Apache.yml
vendored
Normal file
50
.github/workflows/Apache.yml
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
name: Apache
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Apache.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Apache.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Apache:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TestingDomain: example.com
|
||||
TEST_ACME_Server: https://localhost:14000/dir
|
||||
HTTPS_INSECURE: 1
|
||||
TEST_LOCAL: 1
|
||||
TEST_CA: "Pebble Intermediate CA"
|
||||
TEST_APACHE: 1
|
||||
CASE: le_test_apache
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat apache2
|
||||
- name: Run Pebble
|
||||
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d
|
||||
- name: Set up Pebble
|
||||
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
|
||||
- name: Set up Apache
|
||||
# Apache serves on 5002, which is the HTTP-01 validation port in
|
||||
# Pebble's default config; acme.sh appends the challenge Alias to
|
||||
# the main config itself
|
||||
run: |
|
||||
echo "Listen 5002" | sudo tee /etc/apache2/ports.conf
|
||||
sudo sed -i "s/\*:80/*:5002/" /etc/apache2/sites-available/000-default.conf
|
||||
sudo apache2ctl configtest
|
||||
sudo systemctl restart apache2
|
||||
curl -s -o /dev/null -w "%{http_code}" -H "Host: example.com" http://127.0.0.1:5002/ | grep -E "200|403|404"
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: cd ../acmetest && sudo --preserve-env ./letest.sh
|
||||
17
.github/workflows/DNS.yml
vendored
17
.github/workflows/DNS.yml
vendored
|
|
@ -26,9 +26,9 @@ jobs:
|
|||
id: step_one
|
||||
run: |
|
||||
if [ "${{secrets.TokenName1}}" ] ; then
|
||||
echo "::set-output name=hasToken::true"
|
||||
echo "hasToken=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::set-output name=hasToken::false"
|
||||
echo "hasToken=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- name: Check the value
|
||||
run: echo ${{ steps.step_one.outputs.hasToken }}
|
||||
|
|
@ -116,7 +116,9 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install tools
|
||||
run: brew install socat
|
||||
run: |
|
||||
brew untap aws/tap || true
|
||||
brew install socat
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
|
|
@ -176,9 +178,14 @@ jobs:
|
|||
C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git
|
||||
shell: cmd
|
||||
- name: Set ENV
|
||||
shell: cmd
|
||||
shell: bash
|
||||
run: |
|
||||
echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV%
|
||||
echo 'PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin' >> "$GITHUB_ENV"
|
||||
# cygwin git sees the runner workspace as owned by another user and
|
||||
# fails with "dubious ownership" (exit 128) in the checkout post step
|
||||
echo 'GIT_CONFIG_COUNT=1' >> "$GITHUB_ENV"
|
||||
echo 'GIT_CONFIG_KEY_0=safe.directory' >> "$GITHUB_ENV"
|
||||
echo 'GIT_CONFIG_VALUE_0=*' >> "$GITHUB_ENV"
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
|
|
|
|||
66
.github/workflows/Nginx.yml
vendored
Normal file
66
.github/workflows/Nginx.yml
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
name: Nginx
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Nginx.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Nginx.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Nginx:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TestingDomain: example.com
|
||||
TEST_ACME_Server: https://localhost:14000/dir
|
||||
HTTPS_INSECURE: 1
|
||||
TEST_LOCAL: 1
|
||||
TEST_CA: "Pebble Intermediate CA"
|
||||
TEST_NGINX: 1
|
||||
CASE: le_test_nginx
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat nginx
|
||||
- name: Run Pebble
|
||||
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d
|
||||
- name: Set up Pebble
|
||||
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
|
||||
- name: Set up nginx
|
||||
# a backend on 8081 plus a site with an aaPanel/BT style
|
||||
# "location ^~ /" proxy block that shadows plain regex locations
|
||||
# (regression for #6125); the site listens on 5002, which is the
|
||||
# HTTP-01 validation port in Pebble's default config
|
||||
run: |
|
||||
sudo tee /etc/nginx/sites-available/default >/dev/null <<'EOF'
|
||||
server {
|
||||
listen 127.0.0.1:8081;
|
||||
location / {
|
||||
default_type text/plain;
|
||||
return 200 "backend";
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 5002 default_server;
|
||||
server_name example.com;
|
||||
location ^~ / {
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
sudo nginx -t
|
||||
sudo systemctl restart nginx
|
||||
curl -s -H "Host: example.com" http://127.0.0.1:5002/ | grep backend
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: cd ../acmetest && sudo --preserve-env ./letest.sh
|
||||
570
acme.sh
570
acme.sh
|
|
@ -918,6 +918,15 @@ _json_decode() {
|
|||
echo "$_j_str"
|
||||
}
|
||||
|
||||
#extract the authorization URLs from an order response on stdin, as a
|
||||
#comma-separated list. The entries are quoted URL strings and a quote cannot
|
||||
#occur inside a URL, so the first '"]' is always the end of the array. A
|
||||
#char-class scan would stop early on the brackets of an IPv6 host
|
||||
#(https://[2001:db8::1]/...). Outputs nothing if the field is missing.
|
||||
_authorizations_from_order() {
|
||||
sed -n 's/.*"authorizations" *: *\[//p' | sed 's/" *\].*//' | tr -d '" '
|
||||
}
|
||||
|
||||
#options file
|
||||
_sed_i() {
|
||||
options="$1"
|
||||
|
|
@ -1179,6 +1188,11 @@ _createkey() {
|
|||
length="$1"
|
||||
f="$2"
|
||||
_debug2 "_createkey for file:$f"
|
||||
if ! _exists "${ACME_OPENSSL_BIN:-openssl}"; then
|
||||
_err "Please install openssl first. ACME_OPENSSL_BIN=$ACME_OPENSSL_BIN"
|
||||
_err "We need openssl to generate keys."
|
||||
return 1
|
||||
fi
|
||||
eccname="$length"
|
||||
if _startswith "$length" "ec-"; then
|
||||
length=$(printf "%s" "$length" | cut -d '-' -f 2-100)
|
||||
|
|
@ -1201,6 +1215,7 @@ _createkey() {
|
|||
|
||||
_debug "Using length $length"
|
||||
|
||||
_new_key_file=""
|
||||
if ! [ -e "$f" ]; then
|
||||
if ! touch "$f" >/dev/null 2>&1; then
|
||||
_f_path="$(dirname "$f")"
|
||||
|
|
@ -1214,6 +1229,7 @@ _createkey() {
|
|||
return 1
|
||||
fi
|
||||
chmod 600 "$f"
|
||||
_new_key_file="1"
|
||||
fi
|
||||
|
||||
if _isEccKey "$length"; then
|
||||
|
|
@ -1222,6 +1238,10 @@ _createkey() {
|
|||
echo "$_opkey" >"$f"
|
||||
else
|
||||
_err "Error encountered for ECC key named $eccname"
|
||||
#do not leave an empty file behind, or the next run would treat the key as existing
|
||||
if [ "$_new_key_file" ]; then
|
||||
rm -f "$f"
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
|
|
@ -1234,6 +1254,10 @@ _createkey() {
|
|||
echo "$_opkey" >"$f"
|
||||
else
|
||||
_err "Error encountered for RSA key of length $length"
|
||||
#do not leave an empty file behind, or the next run would treat the key as existing
|
||||
if [ "$_new_key_file" ]; then
|
||||
rm -f "$f"
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
|
@ -1283,6 +1307,23 @@ _idn() {
|
|||
}
|
||||
|
||||
#_createcsr cn san_list keyfile csrfile conf acmeValidationv1 extendedUsage
|
||||
#cn
|
||||
#The x509 Common Name is limited to 64 characters (RFC 5280 ub-common-name,
|
||||
#enforced by openssl in ASN1_mbstring_ncopy), and an IP address or an empty
|
||||
#name is not usable as CN either. When this rejects the name, _createcsr
|
||||
#omits CN from the CSR subject and the CA takes the identifiers from the
|
||||
#subjectAltName extension (issue 4867).
|
||||
_is_valid_cn() {
|
||||
_cn_v="$1"
|
||||
if [ -z "$_cn_v" ] || [ "${#_cn_v}" -gt 64 ]; then
|
||||
return 1
|
||||
fi
|
||||
if _isIP "$_cn_v"; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
_createcsr() {
|
||||
_debug _createcsr
|
||||
domain="$1"
|
||||
|
|
@ -1346,16 +1387,16 @@ _createcsr() {
|
|||
_csr_cn="$(_idn "$domain")"
|
||||
_debug2 _csr_cn "$_csr_cn"
|
||||
if _contains "$(uname -a)" "MINGW"; then
|
||||
if _isIP "$_csr_cn"; then
|
||||
${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//O=$PROJECT_NAME" -config "$csrconf" -out "$csr"
|
||||
else
|
||||
if _is_valid_cn "$_csr_cn"; then
|
||||
${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr"
|
||||
else
|
||||
${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//O=$PROJECT_NAME" -config "$csrconf" -out "$csr"
|
||||
fi
|
||||
else
|
||||
if _isIP "$_csr_cn"; then
|
||||
${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/O=$PROJECT_NAME" -config "$csrconf" -out "$csr"
|
||||
else
|
||||
if _is_valid_cn "$_csr_cn"; then
|
||||
${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr"
|
||||
else
|
||||
${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/O=$PROJECT_NAME" -config "$csrconf" -out "$csr"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
|
@ -1381,7 +1422,9 @@ _readSubjectFromCSR() {
|
|||
_usage "_readSubjectFromCSR mycsr.csr"
|
||||
return 1
|
||||
fi
|
||||
${ACME_OPENSSL_BIN:-openssl} req -noout -in "$_csrfile" -subject | tr ',' "\n" | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d ' \n'
|
||||
# -config /dev/null: reading a CSR needs no config, but a missing default
|
||||
# openssl.cnf is fatal on some systems (e.g. NetBSD does not install one)
|
||||
${ACME_OPENSSL_BIN:-openssl} req -noout -in "$_csrfile" -subject -config /dev/null | tr ',' "\n" | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d ' \n'
|
||||
}
|
||||
|
||||
#_csrfile
|
||||
|
|
@ -1396,15 +1439,16 @@ _readSubjectAltNamesFromCSR() {
|
|||
_csrsubj="$(_readSubjectFromCSR "$_csrfile")"
|
||||
_debug _csrsubj "$_csrsubj"
|
||||
|
||||
_dnsAltnames="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')"
|
||||
_dnsAltnames="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" -config /dev/null | grep "^ *DNS:.*" | tr -d ' \n')"
|
||||
_debug _dnsAltnames "$_dnsAltnames"
|
||||
|
||||
if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then
|
||||
# escape the wildcard '*' so it is not taken as a regex operator by grep/sed below
|
||||
_excapedAlgnames="$(echo "$_dnsAltnames" | tr '*' '#')"
|
||||
_debug _excapedAlgnames "$_excapedAlgnames"
|
||||
_escapedSubject="$(echo "$_csrsubj" | tr '*' '#')"
|
||||
_debug _escapedSubject "$_escapedSubject"
|
||||
if _contains "$_excapedAlgnames," "DNS:$_escapedSubject,"; then
|
||||
_debug "AltNames contains subject"
|
||||
_excapedAlgnames="$(echo "$_dnsAltnames" | tr '*' '#')"
|
||||
_debug _excapedAlgnames "$_excapedAlgnames"
|
||||
_escapedSubject="$(echo "$_csrsubj" | tr '*' '#')"
|
||||
_debug _escapedSubject "$_escapedSubject"
|
||||
_dnsAltnames="$(echo "$_excapedAlgnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")"
|
||||
_debug _dnsAltnames "$_dnsAltnames"
|
||||
else
|
||||
|
|
@ -1422,7 +1466,7 @@ _readKeyLengthFromCSR() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
_outcsr="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile")"
|
||||
_outcsr="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" -config /dev/null)"
|
||||
_debug2 _outcsr "$_outcsr"
|
||||
if _contains "$_outcsr" "Public Key Algorithm: id-ecPublicKey"; then
|
||||
_debug "ECC CSR"
|
||||
|
|
@ -1503,6 +1547,22 @@ _toPkcs() {
|
|||
|
||||
}
|
||||
|
||||
_toPkcs8() {
|
||||
_cpkcs8="$1"
|
||||
_ckey="$2"
|
||||
pkcs8Password="$3"
|
||||
|
||||
if [ "$pkcs8Password" ]; then
|
||||
${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -v2 aes256 -passout "pass:$pkcs8Password" -in "$_ckey" -out "$_cpkcs8"
|
||||
else
|
||||
${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$_ckey" -out "$_cpkcs8"
|
||||
fi
|
||||
if [ "$?" = "0" ]; then
|
||||
_savedomainconf "Le_PKCS8Password" "$pkcs8Password" "base64"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
#domain [password] [isEcc]
|
||||
toPkcs() {
|
||||
domain="$1"
|
||||
|
|
@ -1524,20 +1584,21 @@ toPkcs() {
|
|||
|
||||
}
|
||||
|
||||
#domain [isEcc]
|
||||
#domain [password] [isEcc]
|
||||
toPkcs8() {
|
||||
domain="$1"
|
||||
pkcs8Password="$2"
|
||||
|
||||
if [ -z "$domain" ]; then
|
||||
_usage "Usage: $PROJECT_ENTRY --to-pkcs8 --domain <domain.tld> [--ecc]"
|
||||
_usage "Usage: $PROJECT_ENTRY --to-pkcs8 --domain <domain.tld> [--password <password>] [--ecc]"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_isEcc="$2"
|
||||
_isEcc="$3"
|
||||
|
||||
_initpath "$domain" "$_isEcc"
|
||||
|
||||
${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$CERT_KEY_PATH" -out "$CERT_PKCS8_PATH"
|
||||
_toPkcs8 "$CERT_PKCS8_PATH" "$CERT_KEY_PATH" "$pkcs8Password"
|
||||
|
||||
if [ "$?" = "0" ]; then
|
||||
_info "Success, $CERT_PKCS8_PATH"
|
||||
|
|
@ -1871,12 +1932,13 @@ _date2time() {
|
|||
if gdate -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then
|
||||
return
|
||||
fi
|
||||
#Omnios
|
||||
if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then
|
||||
#Omnios. Pass the date as argv (sys.argv[1]) instead of interpolating it into
|
||||
#the -c program text, so a quote in the input cannot inject Python code.
|
||||
if python3 -c "import datetime,sys; print(int(datetime.datetime.strptime(sys.argv[1], \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" "$1" 2>/dev/null; then
|
||||
return
|
||||
fi
|
||||
#Omnios
|
||||
if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%dT%H:%M:%SZ\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then
|
||||
if python3 -c "import datetime,sys; print(int(datetime.datetime.strptime(sys.argv[1], \"%Y-%m-%dT%H:%M:%SZ\").replace(tzinfo=datetime.timezone.utc).timestamp()))" "$1" 2>/dev/null; then
|
||||
return
|
||||
fi
|
||||
_err "Cannot parse _date2time $1"
|
||||
|
|
@ -2834,14 +2896,45 @@ __initHome() {
|
|||
_debug "Using default home: $DEFAULT_INSTALL_HOME"
|
||||
LE_WORKING_DIR="$DEFAULT_INSTALL_HOME"
|
||||
fi
|
||||
# Convert a relative --home to an absolute path: later code cd's around
|
||||
# (e.g. installOnline extracts and enters the archive dir), where a
|
||||
# relative path would point into the wrong directory.
|
||||
# https://github.com/acmesh-official/acme.sh/issues/6477
|
||||
case "$LE_WORKING_DIR" in
|
||||
/*) ;;
|
||||
*)
|
||||
if [ -d "$LE_WORKING_DIR" ]; then
|
||||
LE_WORKING_DIR="$(cd "$LE_WORKING_DIR" && pwd)"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
export LE_WORKING_DIR
|
||||
|
||||
if [ -z "$LE_CONFIG_HOME" ]; then
|
||||
LE_CONFIG_HOME="$LE_WORKING_DIR"
|
||||
fi
|
||||
case "$LE_CONFIG_HOME" in
|
||||
/*) ;;
|
||||
*)
|
||||
if [ -d "$LE_CONFIG_HOME" ]; then
|
||||
LE_CONFIG_HOME="$(cd "$LE_CONFIG_HOME" && pwd)"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
_debug "Using config home: $LE_CONFIG_HOME"
|
||||
export LE_CONFIG_HOME
|
||||
|
||||
# Paths with whitespace break the unquoted $_CURL/$_WGET command expansion,
|
||||
# so fail early with a clear error instead of a cryptic curl/wget failure.
|
||||
# https://github.com/acmesh-official/acme.sh/issues/2163
|
||||
case "$LE_WORKING_DIR$LE_CONFIG_HOME" in
|
||||
*" "*)
|
||||
_err "The --home or --config-home path can not contain spaces: '$LE_WORKING_DIR'"
|
||||
_err "Please install $PROJECT_NAME to a path without spaces."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
_DEFAULT_ACCOUNT_CONF_PATH="$LE_CONFIG_HOME/account.conf"
|
||||
|
||||
if [ -z "$ACCOUNT_CONF_PATH" ]; then
|
||||
|
|
@ -2937,6 +3030,11 @@ _initAPI() {
|
|||
return 0
|
||||
fi
|
||||
_err "Cannot init API for $_api_server"
|
||||
if [ "$_api_server" = "$CA_ZEROSSL" ]; then
|
||||
_info "$(__green "If this host is IPv6-only: ZeroSSL currently has no IPv6 endpoint.")"
|
||||
_info "$(__green "Try another CA, e.g.: $PROJECT_ENTRY --set-default-ca --server letsencrypt")"
|
||||
_info "See: $(__green "https://github.com/acmesh-official/acme.sh/issues/6872")"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -3367,9 +3465,14 @@ _setNginx() {
|
|||
fi
|
||||
|
||||
echo "$NGINX_START
|
||||
location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
|
||||
default_type text/plain;
|
||||
return 200 \"\$1.$_thumbpt\";
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
# the ^~ prefix wins over regex-skipping blocks like \"location ^~ /\",
|
||||
# the nested regex location still captures the token as \$1
|
||||
location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
|
||||
default_type text/plain;
|
||||
return 200 \"\$1.$_thumbpt\";
|
||||
}
|
||||
return 404;
|
||||
}
|
||||
#NGINX_START
|
||||
" >>"$FOUND_REAL_NGINX_CONF"
|
||||
|
|
@ -3513,7 +3616,7 @@ _restoreNginx() {
|
|||
done
|
||||
|
||||
_info "Reloading nginx"
|
||||
if ! nginx -s reload >/dev/null; then
|
||||
if ! nginx -s reload >/dev/null 2>&1; then
|
||||
_err "An error occurred while reloading nginx, please open an issue on $PROJECT."
|
||||
return 1
|
||||
fi
|
||||
|
|
@ -3817,10 +3920,10 @@ _on_issue_success() {
|
|||
#account_key_length eab-kid eab-hmac-key
|
||||
registeraccount() {
|
||||
_account_key_length="$1"
|
||||
_eab_id="$2"
|
||||
_eab_kid="$2"
|
||||
_eab_hmac_key="$3"
|
||||
_initpath
|
||||
_regAccount "$_account_key_length" "$_eab_id" "$_eab_hmac_key"
|
||||
_regAccount "$_account_key_length" "$_eab_kid" "$_eab_hmac_key"
|
||||
}
|
||||
|
||||
__calcAccountKeyHash() {
|
||||
|
|
@ -3831,6 +3934,16 @@ __calc_account_thumbprint() {
|
|||
printf "%s" "$jwk" | tr -d ' ' | _digest "sha256" | _url_replace
|
||||
}
|
||||
|
||||
#Reads a comma- or space-separated email list from stdin and prints
|
||||
#the ACME contact list items: "mailto:a@example.com","mailto:b@example.com"
|
||||
_mailto_contacts() {
|
||||
_mc_out=""
|
||||
for _mc_m in $(tr ',' ' '); do
|
||||
_mc_out="$_mc_out,\"mailto:$_mc_m\""
|
||||
done
|
||||
echo "$_mc_out" | cut -c 2-
|
||||
}
|
||||
|
||||
_getAccountEmail() {
|
||||
if [ "$ACCOUNT_EMAIL" ]; then
|
||||
echo "$ACCOUNT_EMAIL"
|
||||
|
|
@ -3850,14 +3963,14 @@ _getAccountEmail() {
|
|||
_regAccount() {
|
||||
_initpath
|
||||
_reg_length="$1"
|
||||
_eab_id="$2"
|
||||
_eab_kid="$2"
|
||||
_eab_hmac_key="$3"
|
||||
_debug3 _regAccount "$_regAccount"
|
||||
_initAPI
|
||||
|
||||
mkdir -p "$CA_DIR"
|
||||
|
||||
if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
|
||||
if [ ! -s "$ACCOUNT_KEY_PATH" ]; then
|
||||
if ! _create_account_key "$_reg_length"; then
|
||||
_err "Error creating account key."
|
||||
return 1
|
||||
|
|
@ -3867,13 +3980,13 @@ _regAccount() {
|
|||
if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
|
||||
_savecaconf CA_EAB_KEY_ID "$_eab_id"
|
||||
if [ "$_eab_kid" ] && [ "$_eab_hmac_key" ]; then
|
||||
_savecaconf CA_EAB_KEY_ID "$_eab_kid"
|
||||
_savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
|
||||
fi
|
||||
_eab_id=$(_readcaconf "CA_EAB_KEY_ID")
|
||||
_eab_kid=$(_readcaconf "CA_EAB_KEY_ID")
|
||||
_eab_hmac_key=$(_readcaconf "CA_EAB_HMAC_KEY")
|
||||
_secure_debug3 _eab_id "$_eab_id"
|
||||
_secure_debug3 _eab_kid "$_eab_kid"
|
||||
_secure_debug3 _eab_hmac_key "$_eab_hmac_key"
|
||||
_email="$(_getAccountEmail)"
|
||||
if [ "$_email" ]; then
|
||||
|
|
@ -3881,7 +3994,7 @@ _regAccount() {
|
|||
fi
|
||||
|
||||
if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then
|
||||
if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then
|
||||
if [ -z "$_eab_kid" ] || [ -z "$_eab_hmac_key" ]; then
|
||||
_info "No EAB credentials found for ZeroSSL, let's obtain them"
|
||||
if [ -z "$_email" ]; then
|
||||
_info "$(__green "$PROJECT_NAME is using ZeroSSL as default CA now.")"
|
||||
|
|
@ -3890,17 +4003,19 @@ _regAccount() {
|
|||
_info "See: $(__green "$_ZEROSSL_WIKI")"
|
||||
return 1
|
||||
fi
|
||||
_eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT)
|
||||
#the ZeroSSL EAB endpoint takes a single address, use the first one
|
||||
_eab_email="$(echo "$_email" | tr ',' ' ' | awk '{print $1}')"
|
||||
_eabresp=$(_post "email=$_eab_email" $_ZERO_EAB_ENDPOINT)
|
||||
if [ "$?" != "0" ]; then
|
||||
_debug2 "$_eabresp"
|
||||
_err "Cannot get EAB credentials from ZeroSSL."
|
||||
return 1
|
||||
fi
|
||||
_secure_debug2 _eabresp "$_eabresp"
|
||||
_eab_id="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')"
|
||||
_secure_debug2 _eab_id "$_eab_id"
|
||||
if [ -z "$_eab_id" ]; then
|
||||
_err "Cannot resolve _eab_id"
|
||||
_eab_kid="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')"
|
||||
_secure_debug2 _eab_kid "$_eab_kid"
|
||||
if [ -z "$_eab_kid" ]; then
|
||||
_err "Cannot resolve _eab_kid"
|
||||
return 1
|
||||
fi
|
||||
_eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')"
|
||||
|
|
@ -3909,12 +4024,12 @@ _regAccount() {
|
|||
_err "Cannot resolve _eab_hmac_key"
|
||||
return 1
|
||||
fi
|
||||
_savecaconf CA_EAB_KEY_ID "$_eab_id"
|
||||
_savecaconf CA_EAB_KEY_ID "$_eab_kid"
|
||||
_savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
|
||||
fi
|
||||
fi
|
||||
if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
|
||||
eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}"
|
||||
if [ "$_eab_kid" ] && [ "$_eab_hmac_key" ]; then
|
||||
eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_kid\",\"url\":\"${ACME_NEW_ACCOUNT}\"}"
|
||||
_debug3 eab_protected "$eab_protected"
|
||||
|
||||
eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace)
|
||||
|
|
@ -3928,6 +4043,10 @@ _regAccount() {
|
|||
|
||||
key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')"
|
||||
_debug3 key_hex "$key_hex"
|
||||
if [ -z "$key_hex" ]; then
|
||||
_err "Cannot base64-decode the eab-hmac-key. Please check the value, and your openssl version."
|
||||
return 1
|
||||
fi
|
||||
|
||||
eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace)
|
||||
_debug3 eab_signature "$eab_signature"
|
||||
|
|
@ -3936,7 +4055,7 @@ _regAccount() {
|
|||
_debug3 externalBinding "$externalBinding"
|
||||
fi
|
||||
if [ "$_email" ]; then
|
||||
email_sg="\"contact\": [\"mailto:$_email\"], "
|
||||
email_sg="\"contact\": [$(echo "$_email" | _mailto_contacts)], "
|
||||
fi
|
||||
regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}"
|
||||
|
||||
|
|
@ -3980,7 +4099,9 @@ _regAccount() {
|
|||
_debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
|
||||
_savecaconf CA_KEY_HASH "$CA_KEY_HASH"
|
||||
|
||||
if [ "$code" = '403' ]; then
|
||||
#RFC 8555 sec 7.3.6 requires 401 for requests from a deactivated account,
|
||||
#but Boulder (Let's Encrypt) historically returns 403. Accept both.
|
||||
if [ "$code" = '403' ] || [ "$code" = '401' ]; then
|
||||
_err "It seems that the account key has been deactivated, please use a new account key."
|
||||
return 1
|
||||
fi
|
||||
|
|
@ -4014,7 +4135,7 @@ updateaccount() {
|
|||
_email="$(_getAccountEmail)"
|
||||
|
||||
if [ "$_email" ]; then
|
||||
updjson='{"contact": ["mailto:'$_email'"]}'
|
||||
updjson='{"contact": ['$(echo "$_email" | _mailto_contacts)']}'
|
||||
else
|
||||
updjson='{"contact": []}'
|
||||
fi
|
||||
|
|
@ -4033,6 +4154,93 @@ updateaccount() {
|
|||
fi
|
||||
}
|
||||
|
||||
#Implement account key rollover
|
||||
updateaccountkey() {
|
||||
_length="$1"
|
||||
_initpath
|
||||
|
||||
if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
|
||||
_err "Account key not found at: $ACCOUNT_KEY_PATH"
|
||||
return 1
|
||||
fi
|
||||
ACCOUNT_KEY_PATH_NEW="$ACCOUNT_KEY_PATH.new"
|
||||
|
||||
_accUri=$(_readcaconf "ACCOUNT_URL")
|
||||
_debug _accUri "$_accUri"
|
||||
|
||||
if [ -z "$_accUri" ]; then
|
||||
_err "The account URL is empty, please run '--update-account' first to update the account info, then try again."
|
||||
return 1
|
||||
fi
|
||||
if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
|
||||
return 1
|
||||
fi
|
||||
_inner_payload="{\"account\": \"$_accUri\", \"oldKey\": $jwk}"
|
||||
|
||||
_initAPI
|
||||
if [ -z "$ACME_KEY_CHANGE" ]; then
|
||||
_err "Server does not expose keyChange url."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_url="$ACME_KEY_CHANGE"
|
||||
if _createkey "$_length" "$ACCOUNT_KEY_PATH_NEW"; then
|
||||
_info "New account key creation OK."
|
||||
else
|
||||
_err "New account key creation error."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! _calcjwk "$ACCOUNT_KEY_PATH_NEW"; then
|
||||
rm -f "$ACCOUNT_KEY_PATH_NEW"
|
||||
return 1
|
||||
fi
|
||||
_inner_protected="{\"url\": \"${_url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
|
||||
_inner_protected64="$(printf "%s" "$_inner_protected" | _base64 | _url_replace)"
|
||||
_inner_payload64="$(printf "%s" "$_inner_payload" | _base64 | _url_replace)"
|
||||
if ! _inner_sig_t="$(printf "%s" "$_inner_protected64.$_inner_payload64" | _sign "$ACCOUNT_KEY_PATH_NEW" "sha256")"; then
|
||||
_err "Sign request failed."
|
||||
rm -f "$ACCOUNT_KEY_PATH_NEW"
|
||||
return 1
|
||||
fi
|
||||
_debug3 _inner_sig_t "$_inner_sig_t"
|
||||
|
||||
_inner_sig="$(printf "%s" "$_inner_sig_t" | _url_replace)"
|
||||
_debug3 _inner_sig "$_inner_sig"
|
||||
|
||||
_body="{\"protected\": \"$_inner_protected64\", \"payload\": \"$_inner_payload64\", \"signature\": \"$_inner_sig\"}"
|
||||
|
||||
if ! _send_signed_request "$_url" "$_body" "" "$ACCOUNT_KEY_PATH"; then
|
||||
_err "Error rotating account key: $response."
|
||||
rm -f "$ACCOUNT_KEY_PATH_NEW"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$code" = '200' ]; then
|
||||
echo "$response" >"$ACCOUNT_JSON_PATH"
|
||||
mv -f "$ACCOUNT_KEY_PATH_NEW" "$ACCOUNT_KEY_PATH"
|
||||
_info "Account key rotation success for $_accUri."
|
||||
elif [ "$code" = "409" ]; then
|
||||
_err "An existing account is using the new key"
|
||||
rm -f "$ACCOUNT_KEY_PATH_NEW"
|
||||
return 1
|
||||
else
|
||||
_err "Account key rollover error: $response"
|
||||
rm -f "$ACCOUNT_KEY_PATH_NEW"
|
||||
return 1
|
||||
fi
|
||||
|
||||
__CACHED_JWK_KEY_FILE=""
|
||||
_calcjwk "$ACCOUNT_KEY_PATH"
|
||||
|
||||
ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)"
|
||||
_info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"
|
||||
|
||||
CA_KEY_HASH="$(__calcAccountKeyHash)"
|
||||
_debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
|
||||
_savecaconf CA_KEY_HASH "$CA_KEY_HASH"
|
||||
}
|
||||
|
||||
#Implement deactivate account
|
||||
deactivateaccount() {
|
||||
_initpath
|
||||
|
|
@ -4060,7 +4268,8 @@ deactivateaccount() {
|
|||
if _send_signed_request "$_accUri" "$_djson" && _contains "$response" '"deactivated"'; then
|
||||
_info "Successfully deactivated account $_accUri."
|
||||
_accid=$(echo "$response" | _egrep_o "\"id\" *: *[^,]*," | cut -d : -f 2 | tr -d ' ,')
|
||||
elif [ "$code" = "403" ]; then
|
||||
elif [ "$code" = "403" ] || [ "$code" = "401" ]; then
|
||||
#RFC 8555 sec 7.3.6: 401 from a deactivated account; Boulder returns 403
|
||||
_info "The account is already deactivated."
|
||||
_accid=$(_getfield "$_accUri" "999" "/")
|
||||
else
|
||||
|
|
@ -4533,16 +4742,25 @@ _match_issuer() {
|
|||
|
||||
#ip
|
||||
_isIPv4() {
|
||||
for seg in $(echo "$1" | tr '.' ' '); do
|
||||
_debug2 seg "$seg"
|
||||
if [ "$(echo "$seg" | tr -d '[0-9]')" ]; then
|
||||
#not all number
|
||||
#splitting must not glob: a "*" segment would match files in cwd
|
||||
set -f
|
||||
_ipv4_saved_ifs="$IFS"
|
||||
IFS='.'
|
||||
# shellcheck disable=SC2086
|
||||
set -- $1
|
||||
IFS="$_ipv4_saved_ifs"
|
||||
set +f
|
||||
if [ $# -ne 4 ]; then
|
||||
return 1
|
||||
fi
|
||||
for _ipv4_seg in "$@"; do
|
||||
_debug2 _ipv4_seg "$_ipv4_seg"
|
||||
case "$_ipv4_seg" in
|
||||
*[!0-9]* | "") return 1 ;;
|
||||
esac
|
||||
if [ "${#_ipv4_seg}" -gt 3 ] || [ "$_ipv4_seg" -gt 255 ]; then
|
||||
return 1
|
||||
fi
|
||||
if [ $seg -ge 0 ] && [ $seg -lt 256 ]; then
|
||||
continue
|
||||
fi
|
||||
return 1
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
|
@ -4743,7 +4961,7 @@ issue() {
|
|||
_debug2 _saved_account_key_hash "$_saved_account_key_hash"
|
||||
|
||||
if [ -z "$ACCOUNT_URL" ] || [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then
|
||||
if ! _regAccount "$_accountkeylength"; then
|
||||
if ! _regAccount "$_accountkeylength" "$_eab_kid" "$_eab_hmac_key"; then
|
||||
_on_issue_err "$_post_hook"
|
||||
return 1
|
||||
fi
|
||||
|
|
@ -4935,7 +5153,7 @@ issue() {
|
|||
#for dns manual mode
|
||||
_savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize"
|
||||
|
||||
_authorizations_seg="$(echo "$response" | _json_decode | _egrep_o '"authorizations" *: *\[[^\[]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
|
||||
_authorizations_seg="$(echo "$response" | _json_decode | _authorizations_from_order)"
|
||||
_debug2 _authorizations_seg "$_authorizations_seg"
|
||||
if [ -z "$_authorizations_seg" ]; then
|
||||
_err "_authorizations_seg not found."
|
||||
|
|
@ -5751,6 +5969,21 @@ $_authorizations_map"
|
|||
fi
|
||||
fi
|
||||
|
||||
# Warn when the scheduled renewal falls after the cert has already expired,
|
||||
# e.g. a 1-day cert from an internal CA combined with the default 30-day
|
||||
# schedule, which computes from the creation date and never looks at
|
||||
# notAfter. Skip the warning for a fixed-date --valid-to: there
|
||||
# Le_NextRenewTime equals the expiry by design and the non-renewable state
|
||||
# was already reported above. https://github.com/acmesh-official/acme.sh/issues/6917
|
||||
if [ -z "$_valid_to" ] || _startswith "$_valid_to" "+"; then
|
||||
_renew_chk_enddate="$(_enddate "$CERT_PATH")"
|
||||
_renew_chk_endtime="$(_ssldate2time "$_renew_chk_enddate")"
|
||||
if [ "$Le_NextRenewTime" ] && [ "$_renew_chk_endtime" ] && [ "$Le_NextRenewTime" -ge "$_renew_chk_endtime" ]; then
|
||||
_info "$(__red "WARNING: the cert expires at $_renew_chk_enddate, BEFORE the next scheduled renewal time $Le_NextRenewTimeStr.")"
|
||||
_info "$(__red "The cert will already be expired when the renewal runs. If your CA issues short-lived certs, use a negative --days value (e.g. --days -1) to renew relative to the expiry time.")"
|
||||
fi
|
||||
fi
|
||||
|
||||
_savedomainconf "Le_NextRenewTimeStr" "$Le_NextRenewTimeStr"
|
||||
_savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"
|
||||
|
||||
|
|
@ -5760,6 +5993,12 @@ $_authorizations_map"
|
|||
_toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$Le_PFXPassword"
|
||||
fi
|
||||
|
||||
#convert to pkcs8
|
||||
Le_PKCS8Password="$(_readdomainconf Le_PKCS8Password)"
|
||||
if [ "$Le_PKCS8Password" ]; then
|
||||
_toPkcs8 "$CERT_PKCS8_PATH" "$CERT_KEY_PATH" "$Le_PKCS8Password"
|
||||
fi
|
||||
|
||||
if [ "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
|
||||
_savedomainconf "Le_RealCertPath" "$_real_cert"
|
||||
_savedomainconf "Le_RealCACertPath" "$_real_ca"
|
||||
|
|
@ -5858,7 +6097,6 @@ renew() {
|
|||
# If the window has started, renew now even if Le_NextRenewTime is in the future.
|
||||
# Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and use only
|
||||
# Le_NextRenewTime for the renewal decision.
|
||||
_ari_should_renew=""
|
||||
if [ "$NO_ARI" = "1" ]; then
|
||||
_debug "NO_ARI=1, skipping ARI suggestedWindow check"
|
||||
elif [ -z "$FORCE" ] && [ -f "$CERT_PATH" ]; then
|
||||
|
|
@ -5869,20 +6107,63 @@ renew() {
|
|||
_ari_end="$(echo "$_ari_resp" | _egrep_o '"end" *: *"[^"]*' | sed 's/.*"//')"
|
||||
_debug "ARI suggestedWindow.start" "$_ari_start"
|
||||
_debug "ARI suggestedWindow.end" "$_ari_end"
|
||||
if [ "$_ari_start" ]; then
|
||||
if [ "$_ari_start" ] && [ "$_ari_end" ]; then
|
||||
_ari_start_t="$(_date2time "$(echo "$_ari_start" | sed 's/\.[0-9]*//')")"
|
||||
_ari_end_t="$(_date2time "$(echo "$_ari_end" | sed 's/\.[0-9]*//')")"
|
||||
_ari_explanation_url="$(echo "$_ari_resp" | _egrep_o '"explanationURL" *: *"[^"]*' | sed 's/.*"//')"
|
||||
_debug "_ari_start_t" "$_ari_start_t"
|
||||
if [ "$_ari_start_t" ] && [ "$(_time)" -ge "$_ari_start_t" ]; then
|
||||
_info "ARI suggestedWindow has started ($(__green "$_ari_start")), proceeding with renewal."
|
||||
_ari_should_renew="1"
|
||||
else
|
||||
_info "ARI suggestedWindow starts at: $(__green "$_ari_start")"
|
||||
_debug "_ari_end_t" "$_ari_end_t"
|
||||
_debug "_ari_explanation_url" "$_ari_explanation_url"
|
||||
_debug "Le_NextRenewTime" "$Le_NextRenewTime"
|
||||
# Update ARI if needed
|
||||
if [ "$_ari_start_t" ] && [ "$_ari_end_t" ] && [ "$Le_NextRenewTime" ] && [ "$_ari_end_t" -gt "$_ari_start_t" ] && ([ "$Le_NextRenewTime" -lt "$_ari_start_t" ] || [ "$Le_NextRenewTime" -gt "$_ari_end_t" ]); then
|
||||
_ari_old_time_str="$Le_NextRenewTimeStr"
|
||||
_info "Current renewal time: $(__green "$_ari_old_time_str")"
|
||||
_ari_window=$(_math "$_ari_end_t" - "$_ari_start_t")
|
||||
_ari_offset=$(_math "$(_time)" % "$_ari_window")
|
||||
Le_NextRenewTime=$(_math "$_ari_start_t" + "$_ari_offset")
|
||||
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
|
||||
_info "ARI suggestedWindow: $(__green "$_ari_start") to $(__green "$_ari_end")"
|
||||
_info "Updating renewal time picked from ARI window: $(__green "$Le_NextRenewTimeStr")"
|
||||
_savedomainconf Le_NextRenewTime "$Le_NextRenewTime"
|
||||
_savedomainconf Le_NextRenewTimeStr "$Le_NextRenewTimeStr"
|
||||
fi
|
||||
if [ "$Le_NextRenewTime" ] && [ "$(_time)" -ge "$Le_NextRenewTime" ]; then
|
||||
_info "ARI suggested renewal has passed ($(__green "$Le_NextRenewTimeStr")), proceeding with renewal."
|
||||
if [ "$_ari_explanation_url" ]; then
|
||||
_info "For more information on this renewal: $(__green "$_ari_explanation_url")"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$FORCE" ] && [ -z "$_ari_should_renew" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then
|
||||
if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then
|
||||
_renew_retry_fixed=""
|
||||
res="0"
|
||||
_ensure_install "$Le_Domain"
|
||||
res="$?"
|
||||
if [ "$Le_DeployHook" ] && [ "$res" = "0" ]; then
|
||||
_ensure_deploy "$Le_Domain"
|
||||
res="$?"
|
||||
fi
|
||||
if [ "$res" != "0" ]; then
|
||||
if [ -z "$_ACME_IN_RENEWALL" ]; then
|
||||
if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then
|
||||
_send_notify "Renew $Le_Domain error" "There is an error." "$NOTIFY_HOOK" 1
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
if [ "$_renew_retry_fixed" ]; then
|
||||
_info "Install/deploy retry succeeded, no renewal is needed."
|
||||
if [ -z "$_ACME_IN_RENEWALL" ]; then
|
||||
if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then
|
||||
_send_notify "Renew $Le_Domain success" "Good, the cert install/deploy retry succeeded." "$NOTIFY_HOOK" 0
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
_info "Skipping. Next renewal time is: $(__green "$Le_NextRenewTimeStr")"
|
||||
_info "Add '$(__red '--force')' to force renewal."
|
||||
if [ -z "$_ACME_IN_RENEWALL" ]; then
|
||||
|
|
@ -5925,11 +6206,8 @@ renew() {
|
|||
fi
|
||||
issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" "$Le_Valid_From" "$Le_Valid_To" "$Le_Certificate_Profile" "$Le_ExtKeyUse"
|
||||
res="$?"
|
||||
if [ "$res" != "0" ]; then
|
||||
return "$res"
|
||||
fi
|
||||
|
||||
if [ "$Le_DeployHook" ]; then
|
||||
if [ "$Le_DeployHook" ] && [ "$res" = "0" ]; then
|
||||
_deploy "$Le_Domain" "$Le_DeployHook"
|
||||
res="$?"
|
||||
fi
|
||||
|
|
@ -5969,6 +6247,10 @@ renewAll() {
|
|||
_set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT}
|
||||
_debug "_set_level" "$_set_level"
|
||||
export _ACME_IN_RENEWALL=1
|
||||
if ! [ -d "$CERT_HOME" ]; then
|
||||
_err "$CERT_HOME is not a directory, please check your configuration."
|
||||
return 1
|
||||
fi
|
||||
for di in "${CERT_HOME}"/*.* "${CERT_HOME}"/*:*; do
|
||||
_debug di "$di"
|
||||
if ! [ -d "$di" ]; then
|
||||
|
|
@ -5977,12 +6259,19 @@ renewAll() {
|
|||
fi
|
||||
d=$(basename "$di")
|
||||
_debug d "$d"
|
||||
_d_ari="$di.ari"
|
||||
_debug _d_ari "$_d_ari"
|
||||
(
|
||||
if _endswith "$d" "$ECC_SUFFIX"; then
|
||||
_isEcc=$(echo "$d" | cut -d "$ECC_SEP" -f 2)
|
||||
d=$(echo "$d" | cut -d "$ECC_SEP" -f 1)
|
||||
fi
|
||||
renew "$d" "$_isEcc" "$_server"
|
||||
rc="$?"
|
||||
if [ "$rc" = "0" ] && [ "$_ari_explanation_url" ]; then
|
||||
echo "$_ari_explanation_url" >"$_d_ari"
|
||||
fi
|
||||
return $rc
|
||||
)
|
||||
rc="$?"
|
||||
_debug "Return code: $rc"
|
||||
|
|
@ -5997,8 +6286,13 @@ renewAll() {
|
|||
_send_notify "Renew $d success" "Good, the cert is renewed." "$NOTIFY_HOOK" 0
|
||||
fi
|
||||
fi
|
||||
_renewal_explanation=""
|
||||
if [ -f "$_d_ari" ]; then
|
||||
_renewal_explanation=" ($(cat "$_d_ari"))"
|
||||
rm -f "$_d_ari"
|
||||
fi
|
||||
|
||||
_success_msg="${_success_msg} $d
|
||||
_success_msg="${_success_msg} $d$_renewal_explanation
|
||||
"
|
||||
elif [ "$rc" = "$RENEW_SKIP" ]; then
|
||||
if [ $_error_level -gt $NOTIFY_LEVEL_SKIP ]; then
|
||||
|
|
@ -6303,6 +6597,45 @@ _deploy() {
|
|||
_info "$(__green Success)"
|
||||
fi
|
||||
done
|
||||
|
||||
_deploy_success_time="$(_time)"
|
||||
_savedomainconf "Le_DeploySuccessTime" "$_deploy_success_time"
|
||||
_savedomainconf "Le_DeploySuccessTimeStr" "$(_time2str "$_deploy_success_time")"
|
||||
}
|
||||
|
||||
_ensure_deploy() {
|
||||
_d="$1"
|
||||
if [ -z "$Le_DeployHook" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -z "$Le_CertCreateTime" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
_deploy_success_time="$(_readdomainconf Le_DeploySuccessTime)"
|
||||
if [ -z "$_deploy_success_time" ]; then
|
||||
_debug "Le_DeploySuccessTime is empty, skip deploy retry check."
|
||||
return 0
|
||||
fi
|
||||
case "$_deploy_success_time$Le_CertCreateTime" in
|
||||
*[!0-9]*)
|
||||
_debug "Le_DeploySuccessTime or Le_CertCreateTime is not a number, skip deploy retry check."
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$_deploy_success_time" -lt "$Le_CertCreateTime" ]; then
|
||||
_info "The cert was created after the last successful deploy, retrying deploy hooks."
|
||||
if _deploy "$_d" "$Le_DeployHook"; then
|
||||
_info "Deploy retry succeeded."
|
||||
_renew_retry_fixed=1
|
||||
return 0
|
||||
fi
|
||||
_err "Deploy retry failed."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#domain hooks
|
||||
|
|
@ -6323,7 +6656,13 @@ deploy() {
|
|||
fi
|
||||
|
||||
_debug2 DOMAIN_CONF "$DOMAIN_CONF"
|
||||
. "$DOMAIN_CONF"
|
||||
# The cert dir may exist without a domain conf (e.g. the conf was deleted, or
|
||||
# the cert was placed here manually). Deploy can still proceed using env-provided
|
||||
# settings, and _savedomainconf below will recreate the conf, so only source it
|
||||
# when present instead of failing on a missing file.
|
||||
if [ -f "$DOMAIN_CONF" ]; then
|
||||
. "$DOMAIN_CONF"
|
||||
fi
|
||||
|
||||
_savedomainconf Le_DeployHook "$_hooks"
|
||||
|
||||
|
|
@ -6456,9 +6795,54 @@ _installcert() {
|
|||
_info "$(__green "Reload successful")"
|
||||
else
|
||||
_err "Reload error for: $_main_domain"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_installcert_success_time="$(_time)"
|
||||
_savedomainconf "Le_InstallCertSuccessTime" "$_installcert_success_time"
|
||||
_savedomainconf "Le_InstallCertSuccessTimeStr" "$(_time2str "$_installcert_success_time")"
|
||||
}
|
||||
|
||||
_ensure_install() {
|
||||
_d="$1"
|
||||
if [ -z "$Le_CertCreateTime" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
_real_cert="$(_readdomainconf Le_RealCertPath)"
|
||||
_real_key="$(_readdomainconf Le_RealKeyPath)"
|
||||
_real_ca="$(_readdomainconf Le_RealCACertPath)"
|
||||
_reload_cmd="$(_readdomainconf Le_ReloadCmd)"
|
||||
_real_fullchain="$(_readdomainconf Le_RealFullChainPath)"
|
||||
if [ -z "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
_installcert_success_time="$(_readdomainconf Le_InstallCertSuccessTime)"
|
||||
if [ -z "$_installcert_success_time" ]; then
|
||||
_debug "Le_InstallCertSuccessTime is empty, skip install retry check."
|
||||
return 0
|
||||
fi
|
||||
case "$_installcert_success_time$Le_CertCreateTime" in
|
||||
*[!0-9]*)
|
||||
_debug "Le_InstallCertSuccessTime or Le_CertCreateTime is not a number, skip install retry check."
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$_installcert_success_time" -lt "$Le_CertCreateTime" ]; then
|
||||
_info "The cert was created after the last successful install, retrying install cert."
|
||||
if _installcert "$_d" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"; then
|
||||
_info "Install cert retry succeeded."
|
||||
_renew_retry_fixed=1
|
||||
return 0
|
||||
fi
|
||||
_err "Install cert retry failed."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
__read_password() {
|
||||
|
|
@ -6505,8 +6889,10 @@ _install_win_taskscheduler() {
|
|||
_info "$PROJECT_NAME will not save your password."
|
||||
_info "Please input your Windows password for: $(__green "$_myname")"
|
||||
_password="$(__read_password)"
|
||||
#SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_randomminute" '/RU' "$_myname" '/RP' "$_password" '/TR' "$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'" >/dev/null
|
||||
echo SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_randomminute" '/RU' "$_myname" '/RP' "$_password" '/TR' "\"$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'\"" | cmd.exe >/dev/null
|
||||
#schtasks.exe /ST requires the HH:mm format, so the minute must be zero-padded (issue 4950)
|
||||
_st_minute="$(printf "%02d" "$_randomminute")"
|
||||
#SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_st_minute" '/RU' "$_myname" '/RP' "$_password" '/TR' "$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'" >/dev/null
|
||||
echo SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_st_minute" '/RU' "$_myname" '/RP' "$_password" '/TR' "\"$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'\"" | cmd.exe >/dev/null
|
||||
echo
|
||||
|
||||
}
|
||||
|
|
@ -6775,7 +7161,7 @@ _deactivate() {
|
|||
_err "Cannot get new order for domain."
|
||||
return 1
|
||||
fi
|
||||
_authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
|
||||
_authorizations_seg="$(echo "$response" | _json_decode | _authorizations_from_order)"
|
||||
_debug2 _authorizations_seg "$_authorizations_seg"
|
||||
if [ -z "$_authorizations_seg" ]; then
|
||||
_err "_authorizations_seg not found."
|
||||
|
|
@ -7069,6 +7455,10 @@ _installalias() {
|
|||
_sed_i "/^export LE_CONFIG_HOME/d" "$_envfile"
|
||||
fi
|
||||
_setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
|
||||
if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion" ]; then
|
||||
#the completion file does nothing when sourced by a non-bash shell
|
||||
_setopt "$_envfile" ". \"$LE_WORKING_DIR/$PROJECT_ENTRY.completion\""
|
||||
fi
|
||||
|
||||
_profile="$(_detect_profile)"
|
||||
if [ "$_profile" ]; then
|
||||
|
|
@ -7186,6 +7576,11 @@ install() {
|
|||
|
||||
_info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY"
|
||||
|
||||
if [ -f "$PROJECT_ENTRY.completion" ]; then
|
||||
cp "$PROJECT_ENTRY.completion" "$LE_WORKING_DIR/"
|
||||
_debug "Installed bash completion to $LE_WORKING_DIR/$PROJECT_ENTRY.completion"
|
||||
fi
|
||||
|
||||
if [ "$_ACME_IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then
|
||||
_installalias "$_c_home"
|
||||
fi
|
||||
|
|
@ -7263,6 +7658,7 @@ uninstall() {
|
|||
_uninstallalias
|
||||
|
||||
rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY"
|
||||
rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion"
|
||||
_info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\". You can remove them by yourself."
|
||||
|
||||
}
|
||||
|
|
@ -7477,6 +7873,7 @@ Commands:
|
|||
-ccr, --create-csr Create CSR, professional use.
|
||||
--create-domain-key Create an domain private key, professional use.
|
||||
--update-account Update account info.
|
||||
--update-account-key Rotate account key.
|
||||
--register-account Register account key.
|
||||
--deactivate-account Deactivate the account.
|
||||
--make-dns-persist-value Print the DNS TXT record(s) to enable persistent DNS validation
|
||||
|
|
@ -7572,6 +7969,7 @@ Parameters:
|
|||
--config-home <directory> Specifies the home dir to save all the configurations.
|
||||
--useragent <string> Specifies the user agent string. it will be saved for future use too.
|
||||
-m, --email <email> Specifies the account email, only valid for the '--install' and '--update-account' command.
|
||||
Multiple emails can be given as a comma-separated list: 'a@example.com,b@example.com'
|
||||
--accountkey <file> Specifies the account key path, only valid for the '--install' command.
|
||||
--days <ndays> Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days.
|
||||
Negative values could be used to specify a number of days relative to the expiration date of the certificate.
|
||||
|
|
@ -7623,7 +8021,7 @@ Parameters:
|
|||
--revoke-reason <0-10> The reason for revocation, can be used in conjunction with the '--revoke' command.
|
||||
See: $_REVOKE_WIKI
|
||||
|
||||
--password <password> Add a password to exported pfx file. Use with --to-pkcs12.
|
||||
--password <password> Add a password to the exported pfx or pkcs8 file. Use with '--to-pkcs12' or '--to-pkcs8'.
|
||||
|
||||
|
||||
"
|
||||
|
|
@ -7653,7 +8051,9 @@ installOnline() {
|
|||
|
||||
cd "$PROJECT_NAME-$_branch"
|
||||
chmod +x $PROJECT_ENTRY
|
||||
if ./$PROJECT_ENTRY --install "$@"; then
|
||||
./$PROJECT_ENTRY --install "$@"
|
||||
_install_rc="$?"
|
||||
if [ "$_install_rc" = "0" ]; then
|
||||
_info "Install success!"
|
||||
fi
|
||||
|
||||
|
|
@ -7661,6 +8061,9 @@ installOnline() {
|
|||
|
||||
rm -rf "$PROJECT_NAME-$_branch"
|
||||
rm -f "$localname"
|
||||
# Propagate the install result so a failed upgrade is not reported as
|
||||
# success. https://github.com/acmesh-official/acme.sh/issues/6477
|
||||
exit "$_install_rc"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -8023,6 +8426,9 @@ _process() {
|
|||
--update-account | --updateaccount)
|
||||
_CMD="updateaccount"
|
||||
;;
|
||||
--update-account-key | --updateaccountkey)
|
||||
_CMD="updateaccountkey"
|
||||
;;
|
||||
--register-account | --registeraccount)
|
||||
_CMD="registeraccount"
|
||||
;;
|
||||
|
|
@ -8560,6 +8966,15 @@ _process() {
|
|||
|
||||
_debug2 LE_WORKING_DIR "$LE_WORKING_DIR"
|
||||
|
||||
# --days and --valid-to are mutually exclusive by design: --valid-to pins
|
||||
# the cert lifetime and the renewal time follows the expiry, so a
|
||||
# creation-based --days schedule can not apply.
|
||||
if [ "$_days" ] && [ "$_valid_to" ]; then
|
||||
_err "--days can not be used together with --valid-to."
|
||||
_err "With --valid-to, the renewal time is derived from the expiry time automatically."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$DEBUG" ]; then
|
||||
version
|
||||
if [ "$_server" ]; then
|
||||
|
|
@ -8607,6 +9022,9 @@ _process() {
|
|||
updateaccount)
|
||||
updateaccount
|
||||
;;
|
||||
updateaccountkey)
|
||||
updateaccountkey "$_accountkeylength"
|
||||
;;
|
||||
deactivateaccount)
|
||||
deactivateaccount
|
||||
;;
|
||||
|
|
@ -8626,7 +9044,7 @@ _process() {
|
|||
toPkcs "$_domain" "$_password" "$_ecc"
|
||||
;;
|
||||
toPkcs8)
|
||||
toPkcs8 "$_domain" "$_ecc"
|
||||
toPkcs8 "$_domain" "$_password" "$_ecc"
|
||||
;;
|
||||
createAccountKey)
|
||||
createAccountKey "$_accountkeylength"
|
||||
|
|
|
|||
341
acme.sh.completion
Normal file
341
acme.sh.completion
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
# Bash completion for acme.sh: https://github.com/acmesh-official/acme.sh
|
||||
#
|
||||
# "acme.sh --install" copies this file to the acme.sh home dir and wires
|
||||
# it into acme.sh.env, so the completion is loaded automatically in new
|
||||
# bash sessions after installation.
|
||||
#
|
||||
# To use it without installing acme.sh, source it from ~/.bashrc, or copy
|
||||
# it to /usr/share/bash-completion/completions/acme.sh
|
||||
#
|
||||
# Zsh users can load it with:
|
||||
# autoload -U +X bashcompinit && bashcompinit
|
||||
# . /path/to/acme.sh.completion
|
||||
|
||||
# This file may also be sourced by non-bash shells via acme.sh.env,
|
||||
# so silently do nothing if the "complete" builtin is not available.
|
||||
if ! command -v complete >/dev/null 2>&1; then
|
||||
return 0 2>/dev/null || exit 0
|
||||
fi
|
||||
|
||||
# Add each word of $1 that starts with $cur to COMPREPLY.
|
||||
# The words are read line by line, so that candidates like a wildcard
|
||||
# domain "*.example.com" are never glob-expanded against the cwd.
|
||||
_acme_sh_add_matches() {
|
||||
local _word
|
||||
while read -r _word; do
|
||||
[ -n "$_word" ] || continue
|
||||
case "$_word" in
|
||||
"$cur"*) COMPREPLY=("${COMPREPLY[@]}" "$_word") ;;
|
||||
esac
|
||||
done <<EOF
|
||||
$(printf '%s\n' "$1" | tr ' ' '\n')
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
_acme_sh_files() {
|
||||
local _file
|
||||
while IFS= read -r _file; do
|
||||
[ -n "$_file" ] || continue
|
||||
COMPREPLY=("${COMPREPLY[@]}" "$_file")
|
||||
done <<EOF
|
||||
$(compgen -f -- "$cur")
|
||||
EOF
|
||||
if command -v compopt >/dev/null 2>&1; then
|
||||
compopt -o filenames 2>/dev/null
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
_acme_sh_dirs() {
|
||||
local _dir
|
||||
while IFS= read -r _dir; do
|
||||
[ -n "$_dir" ] || continue
|
||||
COMPREPLY=("${COMPREPLY[@]}" "$_dir")
|
||||
done <<EOF
|
||||
$(compgen -d -- "$cur")
|
||||
EOF
|
||||
if command -v compopt >/dev/null 2>&1; then
|
||||
compopt -o filenames 2>/dev/null
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Complete the domains that already have a cert: every directory in the
|
||||
# config home that contains a "<domain>.conf" file ("_ecc" suffix stripped).
|
||||
_acme_sh_domains() {
|
||||
local _dir _name _domains=""
|
||||
[ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null
|
||||
for _dir in "$_acme_conf_home"/*/; do
|
||||
[ -d "$_dir" ] || continue
|
||||
_name="${_dir%/}"
|
||||
_name="${_name##*/}"
|
||||
_name="${_name%_ecc}"
|
||||
if [ -f "${_dir}${_name}.conf" ]; then
|
||||
case " $_domains " in
|
||||
*" $_name "*) ;;
|
||||
*) _domains="$_domains $_name" ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
_acme_sh_add_matches "$_domains"
|
||||
}
|
||||
|
||||
# Complete hook names from a subfolder of the acme.sh home dir.
|
||||
# $1: subfolder (dnsapi/deploy/notify), $2: file name prefix or empty.
|
||||
_acme_sh_hooks() {
|
||||
local _file _hooks=""
|
||||
[ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null
|
||||
for _file in "$_acme_home/$1/$2"*.sh; do
|
||||
[ -f "$_file" ] || continue
|
||||
_file="${_file##*/}"
|
||||
_hooks="$_hooks ${_file%.sh}"
|
||||
done
|
||||
_acme_sh_add_matches "$_hooks"
|
||||
}
|
||||
|
||||
_acme_sh_completion() {
|
||||
local cur prev _acme_home _acme_conf_home
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev=""
|
||||
if [ "$COMP_CWORD" -gt 0 ]; then
|
||||
prev="${COMP_WORDS[COMP_CWORD - 1]}"
|
||||
fi
|
||||
_acme_home="${LE_WORKING_DIR:-$HOME/.acme.sh}"
|
||||
_acme_conf_home="${LE_CONFIG_HOME:-$_acme_home}"
|
||||
|
||||
# The first argument is the command.
|
||||
if [ "$COMP_CWORD" -eq 1 ]; then
|
||||
_acme_sh_add_matches "
|
||||
--help
|
||||
--version
|
||||
--install
|
||||
--install-online
|
||||
--uninstall
|
||||
--upgrade
|
||||
--issue
|
||||
--deploy
|
||||
--sign-csr
|
||||
--show-csr
|
||||
--install-cert
|
||||
--renew
|
||||
--renew-all
|
||||
--revoke
|
||||
--remove
|
||||
--list
|
||||
--list-profiles
|
||||
--info
|
||||
--to-pkcs12
|
||||
--to-pkcs8
|
||||
--create-account-key
|
||||
--create-domain-key
|
||||
--create-csr
|
||||
--deactivate
|
||||
--update-account
|
||||
--register-account
|
||||
--deactivate-account
|
||||
--make-dns-persist-value
|
||||
--install-cronjob
|
||||
--uninstall-cronjob
|
||||
--cron
|
||||
--set-notify
|
||||
--set-default-ca
|
||||
--set-default-chain
|
||||
"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Complete the value of the previous option.
|
||||
case "$prev" in
|
||||
-d | --domain | --challenge-alias | --domain-alias)
|
||||
_acme_sh_domains
|
||||
return 0
|
||||
;;
|
||||
--dns)
|
||||
# The dns hook argument is optional, keep completing options if the
|
||||
# current word already looks like one.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_hooks "dnsapi" "dns_"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--deploy-hook)
|
||||
_acme_sh_hooks "deploy" ""
|
||||
return 0
|
||||
;;
|
||||
--notify-hook)
|
||||
_acme_sh_hooks "notify" ""
|
||||
return 0
|
||||
;;
|
||||
--server)
|
||||
_acme_sh_add_matches "letsencrypt letsencrypt_test zerossl sslcom google google_test actalis"
|
||||
return 0
|
||||
;;
|
||||
-k | --keylength | -ak | --accountkeylength)
|
||||
_acme_sh_add_matches "2048 3072 4096 8192 ec-256 ec-384 ec-521"
|
||||
return 0
|
||||
;;
|
||||
--debug)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_add_matches "0 1 2 3"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--log)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_files
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--nginx)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_files
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--auto-upgrade | --always-force-new-domain-key)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_add_matches "0 1"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--log-level)
|
||||
_acme_sh_add_matches "1 2"
|
||||
return 0
|
||||
;;
|
||||
--syslog)
|
||||
_acme_sh_add_matches "0 3 6 7"
|
||||
return 0
|
||||
;;
|
||||
--notify-level)
|
||||
_acme_sh_add_matches "0 1 2 3"
|
||||
return 0
|
||||
;;
|
||||
--notify-mode)
|
||||
_acme_sh_add_matches "0 1"
|
||||
return 0
|
||||
;;
|
||||
--revoke-reason)
|
||||
_acme_sh_add_matches "0 1 2 3 4 5 6 7 8 9 10"
|
||||
return 0
|
||||
;;
|
||||
--cert-file | --key-file | --ca-file | --fullchain-file | --csr | --accountconf | --accountkey | --ca-bundle | --openssl-bin)
|
||||
_acme_sh_files
|
||||
return 0
|
||||
;;
|
||||
-w | --webroot | --home | --cert-home | --config-home | --ca-path)
|
||||
_acme_sh_dirs
|
||||
return 0
|
||||
;;
|
||||
-m | --email | --password | --useragent | --days | --valid-from | --valid-to | --httpport | --tlsport | --local-address | --dnssleep | --pre-hook | --post-hook | --renew-hook | --reloadcmd | --extended-key-usage | -b | --branch | --notify-source | --eab-kid | --eab-hmac-key | --preferred-chain | --cert-profile | --certificate-profile | --dns-persist-ca-name | --dns-persist-days)
|
||||
# These options take a free-form value, offer nothing.
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Complete the parameters.
|
||||
_acme_sh_add_matches "
|
||||
--accountconf
|
||||
--accountkey
|
||||
--accountkeylength
|
||||
--alpn
|
||||
--always-force-new-domain-key
|
||||
--apache
|
||||
--auto-upgrade
|
||||
--branch
|
||||
--ca-bundle
|
||||
--ca-file
|
||||
--ca-path
|
||||
--cert-file
|
||||
--cert-home
|
||||
--cert-profile
|
||||
--challenge-alias
|
||||
--config-home
|
||||
--csr
|
||||
--days
|
||||
--debug
|
||||
--deploy-hook
|
||||
--dns
|
||||
--dns-persist
|
||||
--dns-persist-ca-name
|
||||
--dns-persist-days
|
||||
--dns-persist-wildcard
|
||||
--dnssleep
|
||||
--domain
|
||||
--domain-alias
|
||||
--eab-hmac-key
|
||||
--eab-kid
|
||||
--ecc
|
||||
--email
|
||||
--extended-key-usage
|
||||
--force
|
||||
--force-color
|
||||
--fullchain-file
|
||||
--home
|
||||
--httpport
|
||||
--insecure
|
||||
--key-file
|
||||
--keylength
|
||||
--listen-v4
|
||||
--listen-v6
|
||||
--listraw
|
||||
--local-address
|
||||
--log
|
||||
--log-level
|
||||
--nginx
|
||||
--no-color
|
||||
--no-cron
|
||||
--no-profile
|
||||
--notify-hook
|
||||
--notify-level
|
||||
--notify-mode
|
||||
--notify-source
|
||||
--ocsp-must-staple
|
||||
--openssl-bin
|
||||
--output-insecure
|
||||
--password
|
||||
--post-hook
|
||||
--pre-hook
|
||||
--preferred-chain
|
||||
--reloadcmd
|
||||
--renew-hook
|
||||
--revoke-reason
|
||||
--server
|
||||
--staging
|
||||
--standalone
|
||||
--stateless
|
||||
--stop-renew-on-error
|
||||
--syslog
|
||||
--tlsport
|
||||
--treat-skip-as-success
|
||||
--use-wget
|
||||
--useragent
|
||||
--valid-from
|
||||
--valid-to
|
||||
--webroot
|
||||
--yes-I-know-dns-manual-mode-enough-go-ahead-please
|
||||
"
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _acme_sh_completion acme.sh
|
||||
|
|
@ -194,7 +194,8 @@ __cpanel_parse_response() {
|
|||
printf("%s%s=%s\n", prefix, $2, $3);
|
||||
}
|
||||
}' |
|
||||
sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p'
|
||||
sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p' |
|
||||
sed -e 's/^"//' -e 's/"$//' # YAML double-quotes values starting with '*' (wildcard subdomains)
|
||||
}
|
||||
|
||||
# Load parameter by prefix+name - fallback to default if not set, and save to config
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
#DEPLOY_DOCKER_CONTAINER_LABEL="xxxxxxx"
|
||||
|
||||
#DEPLOY_DOCKER_CONTAINER_KEY_FILE="/path/to/key.pem"
|
||||
#DEPLOY_DOCKER_CONTAINER_KEY_MODE="0640"
|
||||
#DEPLOY_DOCKER_CONTAINER_KEY_OWNER="1000:1000"
|
||||
#DEPLOY_DOCKER_CONTAINER_CERT_FILE="/path/to/cert.pem"
|
||||
#DEPLOY_DOCKER_CONTAINER_CA_FILE="/path/to/ca.pem"
|
||||
#DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/path/to/fullchain.pem"
|
||||
|
|
@ -71,6 +73,18 @@ docker_deploy() {
|
|||
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
|
||||
|
|
@ -112,6 +126,20 @@ docker_deploy() {
|
|||
if ! _docker_cp "$_cid" "$_ckey" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
|
||||
_info "Setting key file owner to $DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
|
||||
if ! _docker_exec "$_cid" chown "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
|
||||
_err "Can not change owner of key file in container"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
|
||||
_info "Setting key file mode to $DEPLOY_DOCKER_CONTAINER_KEY_MODE"
|
||||
if ! _docker_exec "$_cid" chmod "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
|
||||
_err "Can not change mode of key file in container"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
|
||||
|
|
@ -189,10 +217,22 @@ _docker_exec() {
|
|||
_debug2 cjson "$cjson"
|
||||
execid="$(echo "$cjson" | cut -d '"' -f 4)"
|
||||
_debug execid "$execid"
|
||||
ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": false,\"Tty\": false}")"
|
||||
#Detach:true is required for podman's docker-compatible API: with
|
||||
#Detach:false it streams the command output on the connection, so the
|
||||
#non-empty response was misread as an error (issue #4977). The real
|
||||
#result is checked via the exec inspect ExitCode below instead.
|
||||
ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": true,\"Tty\": false}")"
|
||||
_debug2 ejson "$ejson"
|
||||
if [ "$ejson" ]; then
|
||||
_err "$ejson"
|
||||
_et=0
|
||||
ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
|
||||
while _contains "$ijson" "\"Running\":true" && [ "$_et" -lt 10 ]; do
|
||||
sleep 1
|
||||
_et="$(_math "$_et" + 1)"
|
||||
ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
|
||||
done
|
||||
_debug2 ijson "$ijson"
|
||||
if ! echo "$ijson" | _egrep_o "\"ExitCode\": *0[,}]" >/dev/null 2>&1; then
|
||||
_err "docker exec error: $ijson"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ gcore_cdn_deploy() {
|
|||
_request="{\"username\":\"$Le_Deploy_gcore_cdn_username\",\"password\":\"$Le_Deploy_gcore_cdn_password\"}"
|
||||
_debug _request "$_request"
|
||||
export _H1="Content-Type:application/json"
|
||||
_response=$(_post "$_request" "https://api.gcore.com/auth/jwt/login")
|
||||
_response=$(_post "$_request" "https://api.gcore.com/iam/auth/jwt/login")
|
||||
_debug _response "$_response"
|
||||
_regex=".*\"access\":\"\([-._0-9A-Za-z]*\)\".*$"
|
||||
_debug _regex "$_regex"
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@
|
|||
# needing to reload HAProxy. Default is "no".
|
||||
#
|
||||
# Require the socat binary. DEPLOY_HAPROXY_STATS_SOCKET variable uses the socat
|
||||
# address format.
|
||||
# address format. The certificate can be deployed to a comma separated ',' list
|
||||
# of hosts ("TCP4:10.0.0.1:1999,TCP4:10.0.0.2:1999")
|
||||
#
|
||||
# export DEPLOY_HAPROXY_MASTER_CLI="UNIX:/run/haproxy-master.sock"
|
||||
#
|
||||
|
|
@ -193,7 +194,6 @@ haproxy_deploy() {
|
|||
_issuer="${_pem}.issuer"
|
||||
_ocsp="${_pem}.ocsp"
|
||||
_reload="${Le_Deploy_haproxy_reload}"
|
||||
_statssock="${Le_Deploy_haproxy_stats_socket}"
|
||||
|
||||
_info "Deploying PEM file"
|
||||
# Create a temporary PEM file
|
||||
|
|
@ -272,12 +272,18 @@ haproxy_deploy() {
|
|||
_cafile_argument=""
|
||||
fi
|
||||
_debug _cafile_argument "${_cafile_argument}"
|
||||
# if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed
|
||||
# OpenSSL 1.1+ expects -header Host=value (one argument), while
|
||||
# LibreSSL keeps the old two-argument form -header Host value at any
|
||||
# version (3.x/4.x), so it must be detected by name, not by number.
|
||||
_openssl_name=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f1)
|
||||
_openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2)
|
||||
_debug _openssl_name "${_openssl_name}"
|
||||
_debug _openssl_version "${_openssl_version}"
|
||||
_openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1)
|
||||
_openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2)
|
||||
if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
|
||||
if [ "${_openssl_name}" = "LibreSSL" ]; then
|
||||
_header_sep=" "
|
||||
elif [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
|
||||
_header_sep="="
|
||||
else
|
||||
_header_sep=" "
|
||||
|
|
@ -327,62 +333,67 @@ haproxy_deploy() {
|
|||
|
||||
# Update certificate over HAProxy stats socket or master CLI.
|
||||
if _exists socat; then
|
||||
# look for the certificate on the stats socket, to chose between updating or creating one
|
||||
_socat_cert_cmd="echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'"
|
||||
_debug _socat_cert_cmd "${_socat_cert_cmd}"
|
||||
eval "${_socat_cert_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_newcert="1"
|
||||
_info "Creating new certificate '${_pem}' over HAProxy ${_socketname}."
|
||||
# certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate.
|
||||
_socat_crtlist_show_cmd="echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'"
|
||||
_debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}"
|
||||
eval "${_socat_crtlist_show_cmd}"
|
||||
IFS=','
|
||||
for _statssock in ${Le_Deploy_haproxy_stats_socket}; do
|
||||
# look for the certificate on the stats socket, to choose between updating or creating one
|
||||
_socat_cert_cmd="echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'"
|
||||
_debug _socat_cert_cmd "${_socat_cert_cmd}"
|
||||
eval "${_socat_cert_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'"
|
||||
return "${_ret}"
|
||||
_newcert="1"
|
||||
_info "Creating new certificate '${_pem}' over HAProxy ${_socketname}."
|
||||
# certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate.
|
||||
_socat_crtlist_show_cmd="echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'"
|
||||
_debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}"
|
||||
eval "${_socat_crtlist_show_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'"
|
||||
return "${_ret}"
|
||||
fi
|
||||
# create a new certificate
|
||||
_socat_new_cmd="echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'"
|
||||
_debug _socat_new_cmd "${_socat_new_cmd}"
|
||||
eval "${_socat_new_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Couldn't create '${_pem}' in haproxy"
|
||||
return "${_ret}"
|
||||
fi
|
||||
else
|
||||
_info "Update existing certificate '${_pem}' over HAProxy ${_socketname}."
|
||||
fi
|
||||
# create a new certificate
|
||||
_socat_new_cmd="echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'"
|
||||
_debug _socat_new_cmd "${_socat_new_cmd}"
|
||||
eval "${_socat_new_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Couldn't create '${_pem}' in haproxy"
|
||||
return "${_ret}"
|
||||
fi
|
||||
else
|
||||
_info "Update existing certificate '${_pem}' over HAProxy ${_socketname}."
|
||||
fi
|
||||
_socat_cert_set_cmd="echo -e '${_cmdpfx}set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -q 'Transaction created'"
|
||||
_secure_debug _socat_cert_set_cmd "${_socat_cert_set_cmd}"
|
||||
eval "${_socat_cert_set_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Can't update '${_pem}' in haproxy"
|
||||
return "${_ret}"
|
||||
fi
|
||||
_socat_cert_commit_cmd="echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'"
|
||||
_debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}"
|
||||
eval "${_socat_cert_commit_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Can't commit '${_pem}' in haproxy"
|
||||
return ${_ret}
|
||||
fi
|
||||
if [ "${_newcert}" = "1" ]; then
|
||||
# if this is a new certificate, it needs to be inserted into the crt-list`
|
||||
_socat_cert_add_cmd="echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'"
|
||||
_debug _socat_cert_add_cmd "${_socat_cert_add_cmd}"
|
||||
eval "${_socat_cert_add_cmd}"
|
||||
# printf %b, not "echo -e": dash's echo has no -e and sends a literal "-e " to the socket.
|
||||
# "Transaction updated" is replied instead of "created" when an uncommitted transaction exists.
|
||||
_socat_cert_set_cmd="printf '%b\n' '${_cmdpfx}set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -qE 'Transaction (created|updated)'"
|
||||
_secure_debug _socat_cert_set_cmd "${_socat_cert_set_cmd}"
|
||||
eval "${_socat_cert_set_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Can't update '${_pem}' in haproxy"
|
||||
return "${_ret}"
|
||||
fi
|
||||
fi
|
||||
_socat_cert_commit_cmd="echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'"
|
||||
_debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}"
|
||||
eval "${_socat_cert_commit_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Can't commit '${_pem}' in haproxy"
|
||||
return ${_ret}
|
||||
fi
|
||||
if [ "${_newcert}" = "1" ]; then
|
||||
# if this is a new certificate, it needs to be inserted into the crt-list`
|
||||
_socat_cert_add_cmd="echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'"
|
||||
_debug _socat_cert_add_cmd "${_socat_cert_add_cmd}"
|
||||
eval "${_socat_cert_add_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Can't update '${_pem}' in haproxy"
|
||||
return "${_ret}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
_err "'socat' is not available, couldn't update over ${_socketname}"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -296,9 +296,20 @@ panos_deploy() {
|
|||
_err "Unable to generate an API key. The user and pass may be invalid or not authorized to generate a new key. Please check the PANOS_USER and PANOS_PASS credentials and try again"
|
||||
return 1
|
||||
else
|
||||
deployer cert
|
||||
deployer key
|
||||
deployer commit
|
||||
# A commit of a failed import would leave a mismatched cert/key pair
|
||||
# on the firewall and can lock the admin out of the management
|
||||
# interface, see https://github.com/acmesh-official/acme.sh/issues/4716
|
||||
if ! deployer cert; then
|
||||
_err "Cert import failed. Aborting without committing."
|
||||
return 1
|
||||
fi
|
||||
if ! deployer key; then
|
||||
_err "Key import failed. Aborting without committing. Warning: the firewall now has an uncommitted mismatched cert/key pair in its candidate config."
|
||||
return 1
|
||||
fi
|
||||
if ! deployer commit; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$_panos_template_stack" ]; then
|
||||
# try to get job status for 20 times in 30 sec interval
|
||||
i=0
|
||||
|
|
|
|||
|
|
@ -170,10 +170,16 @@ ssh_deploy() {
|
|||
_info "Required commands batched and sent in single call to remote host"
|
||||
fi
|
||||
|
||||
_returnCode=0
|
||||
_deploy_ssh_servers="$DEPLOY_SSH_SERVER"
|
||||
for DEPLOY_SSH_SERVER in $_deploy_ssh_servers; do
|
||||
_ssh_deploy
|
||||
if ! _ssh_deploy; then
|
||||
# in case of an error, remember it, but keep going for the remaining servers
|
||||
_returnCode=1
|
||||
fi
|
||||
done
|
||||
|
||||
return $_returnCode
|
||||
}
|
||||
|
||||
_ssh_deploy() {
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ synology_dsm_deploy() {
|
|||
if [ -n "$error_code" ]; then
|
||||
if [ "$error_code" == "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then
|
||||
_cleardeployconf SYNO_DEVICE_ID
|
||||
_err "Failed to authenticate with SYNO_DEVICE_ID (may expired or invalid), please try again in a new terminal window."
|
||||
_err "Failed to authenticate with SYNO_DEVICE_ID (may be expired or invalid), please try again in a new terminal window."
|
||||
elif [ "$error_code" == "404" ]; then
|
||||
_err "Failed to authenticate with provided 2FA-OTP code, please try again in a new terminal window."
|
||||
elif [ "$error_code" == "406" ]; then
|
||||
|
|
@ -322,8 +322,8 @@ synology_dsm_deploy() {
|
|||
_savedeployconf SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN"
|
||||
_savedeployconf SYNO_LOCAL_HOSTNAME "$SYNO_LOCAL_HOSTNAME"
|
||||
else
|
||||
_savedeployconf SYNO_USERNAME "$SYNO_USERNAME"
|
||||
_savedeployconf SYNO_PASSWORD "$SYNO_PASSWORD"
|
||||
_savedeployconf SYNO_USERNAME "$SYNO_USERNAME" "base64"
|
||||
_savedeployconf SYNO_PASSWORD "$SYNO_PASSWORD" "base64"
|
||||
_savedeployconf SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
|
||||
_savedeployconf SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -37,6 +37,16 @@ dns_acmedns_add() {
|
|||
ACMEDNS_PASSWORD="${ACMEDNS_PASSWORD:-$(_readdomainconf ACMEDNS_PASSWORD)}"
|
||||
ACMEDNS_SUBDOMAIN="${ACMEDNS_SUBDOMAIN:-$(_readdomainconf ACMEDNS_SUBDOMAIN)}"
|
||||
|
||||
#for compatibility: old versions stored ACMEDNS_UPDATE_URL in the account
|
||||
#conf (issue 3899). Do not clear it here: it must stay available for the
|
||||
#other domains that have not migrated to their domain conf yet.
|
||||
if [ -z "$ACMEDNS_BASE_URL" ]; then
|
||||
_acmedns_update_url="$(_readaccountconf_mutable ACMEDNS_UPDATE_URL)"
|
||||
if [ "$_acmedns_update_url" ]; then
|
||||
ACMEDNS_BASE_URL="$(echo "$_acmedns_update_url" | sed 's#/update$##')"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$ACMEDNS_BASE_URL" = "" ]; then
|
||||
ACMEDNS_BASE_URL="https://auth.acme-dns.io"
|
||||
fi
|
||||
|
|
@ -71,7 +81,7 @@ dns_acmedns_add() {
|
|||
data="{\"subdomain\":\"$ACMEDNS_SUBDOMAIN\", \"txt\": \"$txtvalue\"}"
|
||||
|
||||
_debug data "$data"
|
||||
response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST")"
|
||||
response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST" "application/json")"
|
||||
_debug response "$response"
|
||||
|
||||
if ! echo "$response" | grep "\"$txtvalue\"" >/dev/null; then
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ _ali_rest() {
|
|||
ign="$2"
|
||||
mtd="${3:-GET}"
|
||||
|
||||
signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _url_encode upper-hex)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
|
||||
signature=$(printf "%s" "$signature" | _url_encode upper-hex)
|
||||
signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _ali_urlencode_upper)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
|
||||
signature=$(printf "%s" "$signature" | _ali_urlencode_upper)
|
||||
url="$endpoint?Signature=$signature"
|
||||
|
||||
if [ "$mtd" = "GET" ]; then
|
||||
|
|
@ -96,6 +96,20 @@ _ali_rest() {
|
|||
fi
|
||||
}
|
||||
|
||||
# stdin stdout
|
||||
# The Aliyun signature requires percent-encoding with upper-case hex.
|
||||
# Do not use "_url_encode upper-hex" here: this file is also bundled by
|
||||
# third parties (e.g. Proxmox VE proxmox-acme) whose older copies of the
|
||||
# acme.sh function library ignore the upper-hex argument and output
|
||||
# lower-case hex, which invalidates the signature.
|
||||
# https://github.com/acmesh-official/acme.sh/issues/6272
|
||||
_ali_urlencode_upper() {
|
||||
{
|
||||
_url_encode
|
||||
echo
|
||||
} | sed 's/%a/%A/g;s/%b/%B/g;s/%c/%C/g;s/%d/%D/g;s/%e/%E/g;s/%f/%F/g;s/%\(.\)a/%\1A/g;s/%\(.\)b/%\1B/g;s/%\(.\)c/%\1C/g;s/%\(.\)d/%\1D/g;s/%\(.\)e/%\1E/g;s/%\(.\)f/%\1F/g'
|
||||
}
|
||||
|
||||
_ali_nonce() {
|
||||
if [ "$ACME_OPENSSL_BIN" ]; then
|
||||
"$ACME_OPENSSL_BIN" rand -hex 16 2>/dev/null && return 0
|
||||
|
|
|
|||
|
|
@ -139,12 +139,21 @@ _get_autodns_zone() {
|
|||
return 1
|
||||
}
|
||||
|
||||
# Escape the XML special characters (& < > ' ") so that credentials
|
||||
# containing them do not break the request document (issue 5317).
|
||||
_autodns_xml_encode() {
|
||||
sed "s/&/\&/g;s/</\</g;s/>/\>/g;s/'/\'/g;s/\"/\"/g"
|
||||
}
|
||||
|
||||
_build_request_auth_xml() {
|
||||
_autodns_user_xml="$(printf "%s" "$AUTODNS_USER" | _autodns_xml_encode)"
|
||||
_autodns_password_xml="$(printf "%s" "$AUTODNS_PASSWORD" | _autodns_xml_encode)"
|
||||
_autodns_context_xml="$(printf "%s" "$AUTODNS_CONTEXT" | _autodns_xml_encode)"
|
||||
printf "<auth>
|
||||
<user>%s</user>
|
||||
<password>%s</password>
|
||||
<context>%s</context>
|
||||
</auth>" "$AUTODNS_USER" "$AUTODNS_PASSWORD" "$AUTODNS_CONTEXT"
|
||||
</auth>" "$_autodns_user_xml" "$_autodns_password_xml" "$_autodns_context_xml"
|
||||
}
|
||||
|
||||
# Arguments:
|
||||
|
|
|
|||
137
dnsapi/dns_calrissia.sh
Normal file
137
dnsapi/dns_calrissia.sh
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_calrissia_info='Calrissia.be DNS API
|
||||
Site: calrissia.be
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_calrissia
|
||||
Options:
|
||||
CALRISSIA_TOKEN Personal access token
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/6809
|
||||
Author: Ward Hus
|
||||
'
|
||||
|
||||
CALRISSIA_API="https://my.calrissia.com/api"
|
||||
|
||||
dns_calrissia_add() {
|
||||
fulldomain="$1"
|
||||
txtvalue="$2"
|
||||
|
||||
_calrissia_load_token || return 1
|
||||
|
||||
if ! _calrissia_get_root "$fulldomain"; then
|
||||
_err "Unable to find domain in Calrissia account for: $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "domain='$_domain' id='$_domain_id' sub='$_sub_domain'"
|
||||
_info "Adding TXT record for $fulldomain"
|
||||
|
||||
_body="{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120,\"prio\":0}"
|
||||
_response="$(_calrissia_request POST "/domain/$_domain_id/record" "$_body")"
|
||||
|
||||
if ! _contains "$_response" '"id"'; then
|
||||
_err "Failed to create TXT record: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
dns_calrissia_rm() {
|
||||
fulldomain="$1"
|
||||
txtvalue="$2"
|
||||
|
||||
_calrissia_load_token || return 1
|
||||
|
||||
if ! _calrissia_get_root "$fulldomain"; then
|
||||
_err "Unable to find domain in Calrissia account for: $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "domain='$_domain' id='$_domain_id' sub='$_sub_domain'"
|
||||
|
||||
# Look the record up from the API instead of relying on local state.
|
||||
# The record list is embedded in the domain object.
|
||||
_response="$(_calrissia_request GET "/domain/$_domain_id")"
|
||||
_debug2 "Response: $_response"
|
||||
|
||||
# Split the record objects onto separate lines, then match on both the
|
||||
# subdomain name and the TXT value to find the record id to delete.
|
||||
_record_id="$(printf "%s" "$_response" |
|
||||
tr '{}' '\n' |
|
||||
grep "\"name\" *: *\"$_sub_domain\"" |
|
||||
grep "\"content\" *: *\"$txtvalue\"" |
|
||||
_egrep_o '"id" *: *[0-9]+' |
|
||||
_head_n 1 |
|
||||
_egrep_o '[0-9]+')"
|
||||
|
||||
if [ -z "$_record_id" ]; then
|
||||
_info "No matching TXT record found for $fulldomain; nothing to remove"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_info "Removing TXT record id=$_record_id from domain id=$_domain_id"
|
||||
if ! _response="$(_calrissia_request DELETE "/domain/$_domain_id/record/$_record_id")" || _contains "$_response" '"error"'; then
|
||||
_err "Failed to remove TXT record: $_response"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
####################
|
||||
# Private helpers #
|
||||
####################
|
||||
|
||||
_calrissia_load_token() {
|
||||
CALRISSIA_TOKEN="${CALRISSIA_TOKEN:-$(_readaccountconf_mutable CALRISSIA_TOKEN)}"
|
||||
if [ -z "$CALRISSIA_TOKEN" ]; then
|
||||
_err "CALRISSIA_TOKEN is not set. Generate one at https://identity.calrissia.com under API Keys."
|
||||
return 1
|
||||
fi
|
||||
_saveaccountconf_mutable CALRISSIA_TOKEN "$CALRISSIA_TOKEN"
|
||||
}
|
||||
|
||||
# Sets _domain, _domain_id, _sub_domain for a given FQDN.
|
||||
_calrissia_get_root() {
|
||||
_fqdn="$1"
|
||||
|
||||
i=1
|
||||
while true; do
|
||||
_candidate="$(printf "%s" "$_fqdn" | cut -d . -f "$i"-)"
|
||||
[ -z "$_candidate" ] && return 1
|
||||
|
||||
_debug "Trying root domain: $_candidate"
|
||||
_response="$(_calrissia_request GET "/domain?full_domain_name=$_candidate")"
|
||||
_debug2 "Response: $_response"
|
||||
|
||||
_domain_id="$(printf "%s" "$_response" |
|
||||
_egrep_o '"id" *: *[0-9]+' |
|
||||
_head_n 1 |
|
||||
_egrep_o '[0-9]+')"
|
||||
|
||||
if [ -n "$_domain_id" ]; then
|
||||
if [ "$i" = "1" ]; then
|
||||
# The FQDN itself is the zone apex, e.g. a challenge-alias domain.
|
||||
_sub_domain=""
|
||||
else
|
||||
_sub_domain="$(printf "%s" "$_fqdn" | cut -d . -f "1-$((i - 1))")"
|
||||
fi
|
||||
_domain="$_candidate"
|
||||
return 0
|
||||
fi
|
||||
|
||||
i=$((i + 1))
|
||||
done
|
||||
}
|
||||
|
||||
_calrissia_request() {
|
||||
_method="$1"
|
||||
_path="$2"
|
||||
_body="$3"
|
||||
export _H1="Authorization: Bearer $CALRISSIA_TOKEN"
|
||||
export _H2="Accept: application/json"
|
||||
if [ "$_method" = "GET" ]; then
|
||||
_get "$CALRISSIA_API$_path"
|
||||
else
|
||||
_post "$_body" "$CALRISSIA_API$_path" "" "$_method" "application/json"
|
||||
fi
|
||||
}
|
||||
|
|
@ -15,7 +15,8 @@ CN_API="https://beta.api.core-networks.de"
|
|||
######## Public functions #####################
|
||||
|
||||
dns_cn_add() {
|
||||
fulldomain=$1
|
||||
# Core-Networks API requires punycode for IDN domains
|
||||
fulldomain=$(_idn "$1")
|
||||
txtvalue=$2
|
||||
|
||||
if ! _cn_login; then
|
||||
|
|
@ -58,7 +59,8 @@ dns_cn_add() {
|
|||
}
|
||||
|
||||
dns_cn_rm() {
|
||||
fulldomain=$1
|
||||
# Core-Networks API requires punycode for IDN domains
|
||||
fulldomain=$(_idn "$1")
|
||||
txtvalue=$2
|
||||
|
||||
if ! _cn_login; then
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ dns_da_info='DirectAdmin Server API
|
|||
Site: DirectAdmin.com/api.php
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_da
|
||||
Options:
|
||||
DA_Api API Server URL. E.g. "https://remoteUser:remotePassword@da.domain.tld:8443"
|
||||
DA_Api API Server URL. E.g. "https://remoteUser:remotePassword@da.domain.tld:8443". Special characters in the user/password must be percent-encoded, e.g. "@" -> "%40".
|
||||
DA_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept
|
||||
Issues: github.com/TigerP/acme.sh/issues
|
||||
'
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ dns_desec_info='deSEC.io
|
|||
Site: desec.readthedocs.io/en/latest/
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_desec
|
||||
Options:
|
||||
DDNSS_Token API Token
|
||||
DEDYN_TOKEN API Token
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/2180
|
||||
Author: Zheng Qian
|
||||
'
|
||||
|
|
|
|||
|
|
@ -5,14 +5,11 @@ Site: DNSExit.com
|
|||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsexit
|
||||
Options:
|
||||
DNSEXIT_API_KEY API Key
|
||||
DNSEXIT_AUTH_USER Username
|
||||
DNSEXIT_AUTH_PASS Password
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/4719
|
||||
Author: Samuel Jimenez
|
||||
'
|
||||
|
||||
DNSEXIT_API_URL="https://api.dnsexit.com/dns/"
|
||||
DNSEXIT_HOSTS_URL="https://update.dnsexit.com/ipupdate/hosts.jsp"
|
||||
|
||||
######## Public functions #####################
|
||||
#Usage: dns_dnsexit_add _acme-challenge.*.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
|
|
@ -28,20 +25,7 @@ dns_dnsexit_add() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
_debug 'First detect the root zone'
|
||||
if ! _get_root "$fulldomain"; then
|
||||
return 1
|
||||
fi
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
|
||||
if ! _dnsexit_rest "{\"domain\":\"$_domain\",\"add\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":0,\"overwrite\":false}}"; then
|
||||
_err "$response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug2 _response "$response"
|
||||
return 0
|
||||
_dnsexit_zone_op add ',"ttl":0,"overwrite":false'
|
||||
}
|
||||
|
||||
#Usage: fulldomain txtvalue
|
||||
|
|
@ -58,54 +42,43 @@ dns_dnsexit_rm() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
_debug 'First detect the root zone'
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "$response"
|
||||
return 1
|
||||
fi
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
|
||||
if ! _dnsexit_rest "{\"domain\":\"$_domain\",\"delete\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\"}}"; then
|
||||
_err "$response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug2 _response "$response"
|
||||
return 0
|
||||
_dnsexit_zone_op delete ''
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
#_acme-challenge.www.domain.com
|
||||
#returns
|
||||
# _sub_domain=_acme-challenge.www
|
||||
# _domain=domain.com
|
||||
_get_root() {
|
||||
domain=$1
|
||||
# The legacy zone-detection endpoint (update.dnsexit.com/ipupdate/hosts.jsp)
|
||||
# was shut down by DNSExit and now returns 503, and the JSON API offers no
|
||||
# zone-list call. So find the root zone by attempting the actual operation at
|
||||
# each domain level: the API answers "code":0 only when the domain matches a
|
||||
# zone of the account. https://github.com/acmesh-official/acme.sh/issues/6914
|
||||
#Usage: _dnsexit_zone_op <add|delete> <extra-json-fields>
|
||||
_dnsexit_zone_op() {
|
||||
_op="$1"
|
||||
_extra="$2"
|
||||
i=1
|
||||
while true; do
|
||||
_domain=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
_debug h "$_domain"
|
||||
_domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100)
|
||||
_debug _domain "$_domain"
|
||||
if [ -z "$_domain" ]; then
|
||||
_err "Could not find the root zone of $fulldomain in your DNSExit account"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug login "$DNSEXIT_AUTH_USER"
|
||||
_debug password "$DNSEXIT_AUTH_PASS"
|
||||
_debug domain "$_domain"
|
||||
_sub_domain="$(printf "%s" "$fulldomain" | sed "s/\\.$_domain\$//")"
|
||||
if [ "$_sub_domain" = "$fulldomain" ]; then
|
||||
_sub_domain=""
|
||||
fi
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
|
||||
_dnsexit_http "login=$DNSEXIT_AUTH_USER&password=$DNSEXIT_AUTH_PASS&domain=$_domain"
|
||||
|
||||
if _contains "$response" "0=$_domain"; then
|
||||
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
|
||||
return 0
|
||||
else
|
||||
_debug "Go to next level of $_domain"
|
||||
if _dnsexit_rest "{\"domain\":\"$_domain\",\"$_op\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\"$_extra}}"; then
|
||||
if _contains "$response" "\"code\":0" || _contains "$response" "\"code\": 0"; then
|
||||
_debug2 _response "$response"
|
||||
return 0
|
||||
fi
|
||||
_debug "Zone $_domain was not accepted, trying the next level" "$response"
|
||||
fi
|
||||
i=$(_math "$i" + 1)
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
_dnsexit_rest() {
|
||||
|
|
@ -136,27 +109,7 @@ _dnsexit_rest() {
|
|||
return 0
|
||||
}
|
||||
|
||||
_dnsexit_http() {
|
||||
m=GET
|
||||
param="$1"
|
||||
_debug param "$param"
|
||||
_debug get "$DNSEXIT_HOSTS_URL?$param"
|
||||
|
||||
response="$(_get "$DNSEXIT_HOSTS_URL?$param")"
|
||||
|
||||
_debug response "$response"
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "Error $param"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug2 response "$response"
|
||||
return 0
|
||||
}
|
||||
|
||||
get_account_info() {
|
||||
|
||||
DNSEXIT_API_KEY="${DNSEXIT_API_KEY:-$(_readaccountconf_mutable DNSEXIT_API_KEY)}"
|
||||
if test -z "$DNSEXIT_API_KEY"; then
|
||||
DNSEXIT_API_KEY=''
|
||||
|
|
@ -166,23 +119,5 @@ get_account_info() {
|
|||
|
||||
_saveaccountconf_mutable DNSEXIT_API_KEY "$DNSEXIT_API_KEY"
|
||||
|
||||
DNSEXIT_AUTH_USER="${DNSEXIT_AUTH_USER:-$(_readaccountconf_mutable DNSEXIT_AUTH_USER)}"
|
||||
if test -z "$DNSEXIT_AUTH_USER"; then
|
||||
DNSEXIT_AUTH_USER=""
|
||||
_err 'DNSEXIT_AUTH_USER was not exported'
|
||||
return 1
|
||||
fi
|
||||
|
||||
_saveaccountconf_mutable DNSEXIT_AUTH_USER "$DNSEXIT_AUTH_USER"
|
||||
|
||||
DNSEXIT_AUTH_PASS="${DNSEXIT_AUTH_PASS:-$(_readaccountconf_mutable DNSEXIT_AUTH_PASS)}"
|
||||
if test -z "$DNSEXIT_AUTH_PASS"; then
|
||||
DNSEXIT_AUTH_PASS=""
|
||||
_err 'DNSEXIT_AUTH_PASS was not exported'
|
||||
return 1
|
||||
fi
|
||||
|
||||
_saveaccountconf_mutable DNSEXIT_AUTH_PASS "$DNSEXIT_AUTH_PASS"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ Site: DNSimple.com
|
|||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dnsimple
|
||||
Options:
|
||||
DNSimple_OAUTH_TOKEN OAuth Token
|
||||
DNSimple_ACCOUNT_ID Account ID. Optional, only needed when the token can access multiple accounts.
|
||||
Issues: github.com/pho3nixf1re/acme.sh/issues
|
||||
'
|
||||
|
||||
|
|
@ -28,7 +29,7 @@ dns_dnsimple_add() {
|
|||
_saveaccountconf DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN"
|
||||
|
||||
if ! _get_account_id; then
|
||||
_err "failed to retrive account id"
|
||||
_err "failed to retrieve account id"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
|
@ -57,7 +58,7 @@ dns_dnsimple_rm() {
|
|||
fulldomain=$1
|
||||
|
||||
if ! _get_account_id; then
|
||||
_err "failed to retrive account id"
|
||||
_err "failed to retrieve account id"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
|
@ -122,13 +123,16 @@ _get_root() {
|
|||
|
||||
# returns _account_id
|
||||
_get_account_id() {
|
||||
_debug "retrive account id"
|
||||
if ! _dnsimple_rest GET "whoami"; then
|
||||
return 1
|
||||
DNSimple_ACCOUNT_ID="${DNSimple_ACCOUNT_ID:-$(_readaccountconf DNSimple_ACCOUNT_ID)}"
|
||||
if [ "$DNSimple_ACCOUNT_ID" ]; then
|
||||
_saveaccountconf DNSimple_ACCOUNT_ID "$DNSimple_ACCOUNT_ID"
|
||||
_account_id="$DNSimple_ACCOUNT_ID"
|
||||
_debug _account_id "$_account_id"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if _contains "$response" "\"account\":null"; then
|
||||
_err "no account associated with this token"
|
||||
_debug "retrieve account id"
|
||||
if ! _dnsimple_rest GET "whoami"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
|
@ -137,7 +141,25 @@ _get_account_id() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
if _contains "$response" "\"account\":null"; then
|
||||
# the whoami of a user token (dnsimple_u_*) carries no account,
|
||||
# so list the accounts the token can access instead
|
||||
# https://github.com/acmesh-official/acme.sh/issues/6491
|
||||
if ! _dnsimple_rest GET "accounts"; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_account_id=$(printf "%s" "$response" | _egrep_o "\"id\":[^,]*,\"email\":" | cut -d: -f2 | cut -d, -f1)
|
||||
if [ -z "$_account_id" ]; then
|
||||
_err "no account associated with this token"
|
||||
return 1
|
||||
fi
|
||||
if [ "$(echo "$_account_id" | wc -l)" -gt 1 ]; then
|
||||
_err "The token has access to multiple accounts, please pick one and set it explicitly:"
|
||||
_err "export DNSimple_ACCOUNT_ID=<one of: $(echo "$_account_id" | tr '\n' ' ')>"
|
||||
return 1
|
||||
fi
|
||||
_debug _account_id "$_account_id"
|
||||
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -363,17 +363,12 @@ _edgedns_rest() {
|
|||
|
||||
_edgedns_eg_timestamp() {
|
||||
_debug "Generating signature Timestamp"
|
||||
_debug3 "Retriving ntp time"
|
||||
_timeheaders="$(_get "https://www.ntp.org" "onlyheader")"
|
||||
_debug3 "_timeheaders" "$_timeheaders"
|
||||
_ntpdate="$(echo "$_timeheaders" | grep -i "Date:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n")"
|
||||
_debug3 "_ntpdate" "$_ntpdate"
|
||||
_ntpdate="$(echo "${_ntpdate}" | sed -e 's/^[[:space:]]*//')"
|
||||
_debug3 "_NTPDATE" "$_ntpdate"
|
||||
_ntptime="$(echo "${_ntpdate}" | _head_n 1 | cut -d " " -f 5 | tr -d "\r\n")"
|
||||
_debug3 "_ntptime" "$_ntptime"
|
||||
_eg_timestamp=$(date -u "+%Y%m%dT")
|
||||
_eg_timestamp="$(printf "%s%s+0000" "$_eg_timestamp" "$_ntptime")"
|
||||
#Akamai accepts a clock skew of +/-30s, so use the system clock directly.
|
||||
#The previous code fetched the Date header from www.ntp.org, which is not
|
||||
#a reliable time source (it served a wrong time for hours, issue 3973),
|
||||
#cost an extra https round-trip for every API request, and combined the
|
||||
#remote time of day with the LOCAL date, breaking around UTC midnight.
|
||||
_eg_timestamp="$(date -u "+%Y%m%dT%H:%M:%S+0000")"
|
||||
_debug "_eg_timestamp" "$_eg_timestamp"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,12 @@ dns_gd_add() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
if ! _contains "$response" "$txtvalue"; then
|
||||
if _contains "$response" "UNKNOWN_DOMAIN"; then
|
||||
# GoDaddy sometimes returns UNKNOWN_DOMAIN when reading a record back even
|
||||
# though the PUT above succeeded; skip the local readback check and let
|
||||
# acme.sh's own DNS propagation check verify the record was published.
|
||||
_info "GoDaddy API won't allow reading the record back; skipping local verification."
|
||||
elif ! _contains "$response" "$txtvalue"; then
|
||||
_err "TXT record '${txtvalue}' for '${fulldomain}', value wasn't set!"
|
||||
return 1
|
||||
fi
|
||||
|
|
@ -145,8 +150,8 @@ dns_gd_rm() {
|
|||
# _domain=domain.com
|
||||
_get_root() {
|
||||
domain=$1
|
||||
i=2
|
||||
p=1
|
||||
i=1
|
||||
p=0
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
if [ -z "$h" ]; then
|
||||
|
|
@ -154,17 +159,41 @@ _get_root() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
if ! _gd_rest GET "domains/$h"; then
|
||||
return 1
|
||||
# The record name is whatever precedes the candidate zone. Do not assume
|
||||
# _acme-challenge here: with DNS alias mode it can be any name, and the
|
||||
# record may even sit at the zone apex (name "@").
|
||||
if [ "$p" = "0" ]; then
|
||||
_probe_sub="@"
|
||||
else
|
||||
_probe_sub=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
fi
|
||||
|
||||
if _contains "$response" '"code":"NOT_FOUND"'; then
|
||||
_debug "$h not found"
|
||||
else
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
# Probe with the records endpoint instead of "GET domains/$h": since
|
||||
# 2024-05 GoDaddy rejects the domain details call for accounts with
|
||||
# fewer than 10 domains, while record-level calls keep working.
|
||||
# https://github.com/acmesh-official/acme.sh/issues/4487
|
||||
if ! _gd_rest GET "domains/$h/records/TXT/$_probe_sub"; then
|
||||
return 1
|
||||
fi
|
||||
if _startswith "$response" '\['; then
|
||||
_sub_domain="$_probe_sub"
|
||||
_domain="$h"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Some accounts get UNKNOWN_DOMAIN when reading records of a valid zone
|
||||
# even though writes succeed (see issue #6517); fall back to the domain
|
||||
# details call for them.
|
||||
if ! _gd_rest GET "domains/$h"; then
|
||||
return 1
|
||||
fi
|
||||
if _contains "$response" '"domainId"'; then
|
||||
_sub_domain="$_probe_sub"
|
||||
_domain="$h"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_debug "$h not found"
|
||||
p="$i"
|
||||
i=$(_math "$i" + 1)
|
||||
done
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ _hostingde_apiKey() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
# The endpoint is the base URL only; the api path is appended below.
|
||||
# hosting.de's own docs show the full api URL, so strip it if pasted in.
|
||||
# https://github.com/acmesh-official/acme.sh/issues/6896
|
||||
HOSTINGDE_ENDPOINT="$(echo "$HOSTINGDE_ENDPOINT" | sed 's|/api/dns/v1/json||; s|/*$||')"
|
||||
|
||||
_saveaccountconf_mutable HOSTINGDE_APIKEY "$HOSTINGDE_APIKEY"
|
||||
_saveaccountconf_mutable HOSTINGDE_ENDPOINT "$HOSTINGDE_ENDPOINT"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ Options:
|
|||
HUAWEICLOUD_Username Username
|
||||
HUAWEICLOUD_Password Password
|
||||
HUAWEICLOUD_DomainName DomainName
|
||||
HUAWEICLOUD_Region Region. E.g. "cn-north-4". Optional, defaults to "ap-southeast-1".
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/3265
|
||||
'
|
||||
|
||||
iam_api="https://iam.myhuaweicloud.com"
|
||||
dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" # Should work
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
|
|
@ -30,6 +30,7 @@ dns_huaweicloud_add() {
|
|||
HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
|
||||
HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
|
||||
HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_DomainName)}"
|
||||
HUAWEICLOUD_Region="${HUAWEICLOUD_Region:-$(_readaccountconf_mutable HUAWEICLOUD_Region)}"
|
||||
|
||||
# Check information
|
||||
if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then
|
||||
|
|
@ -37,8 +38,11 @@ dns_huaweicloud_add() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
_huaweicloud_region="${HUAWEICLOUD_Region:-ap-southeast-1}"
|
||||
dns_api="https://dns.${_huaweicloud_region}.myhuaweicloud.com"
|
||||
|
||||
unset token # Clear token
|
||||
token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}")"
|
||||
token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}" "${_huaweicloud_region}")"
|
||||
if [ -z "${token}" ]; then # Check token
|
||||
_err "dns_api(dns_huaweicloud): Error getting token."
|
||||
return 1
|
||||
|
|
@ -65,6 +69,9 @@ dns_huaweicloud_add() {
|
|||
_saveaccountconf_mutable HUAWEICLOUD_Username "${HUAWEICLOUD_Username}"
|
||||
_saveaccountconf_mutable HUAWEICLOUD_Password "${HUAWEICLOUD_Password}"
|
||||
_saveaccountconf_mutable HUAWEICLOUD_DomainName "${HUAWEICLOUD_DomainName}"
|
||||
if [ -n "${HUAWEICLOUD_Region}" ]; then
|
||||
_saveaccountconf_mutable HUAWEICLOUD_Region "${HUAWEICLOUD_Region}"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -81,6 +88,7 @@ dns_huaweicloud_rm() {
|
|||
HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
|
||||
HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
|
||||
HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_DomainName)}"
|
||||
HUAWEICLOUD_Region="${HUAWEICLOUD_Region:-$(_readaccountconf_mutable HUAWEICLOUD_Region)}"
|
||||
|
||||
# Check information
|
||||
if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then
|
||||
|
|
@ -88,8 +96,11 @@ dns_huaweicloud_rm() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
_huaweicloud_region="${HUAWEICLOUD_Region:-ap-southeast-1}"
|
||||
dns_api="https://dns.${_huaweicloud_region}.myhuaweicloud.com"
|
||||
|
||||
unset token # Clear token
|
||||
token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}")"
|
||||
token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}" "${_huaweicloud_region}")"
|
||||
if [ -z "${token}" ]; then # Check token
|
||||
_err "dns_api(dns_huaweicloud): Error getting token."
|
||||
return 1
|
||||
|
|
@ -298,6 +309,7 @@ _get_token() {
|
|||
_username=$1
|
||||
_password=$2
|
||||
_domain_name=$3
|
||||
_region_name=$4
|
||||
|
||||
_debug "Getting Token"
|
||||
body="{
|
||||
|
|
@ -318,7 +330,7 @@ _get_token() {
|
|||
},
|
||||
\"scope\": {
|
||||
\"project\": {
|
||||
\"name\": \"ap-southeast-1\"
|
||||
\"name\": \"${_region_name}\"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -307,13 +307,18 @@ _get_root() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
if _contains "$response" "$h"; then
|
||||
# Anchor the match to the XML tag and escape dots so $h is compared
|
||||
# literally: _contains uses grep, which treats "$h" as a regex, and a
|
||||
# bare "g.berlight.de" would match "<string>berlight.de" (the 'g' from
|
||||
# "<string>" plus '.' matching '>'). See issue #5129.
|
||||
_hregex=$(printf "%s" "$h" | sed 's/\./\\./g')
|
||||
if _contains "$response" "<string>$_hregex</string>"; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_domain="$h"
|
||||
return 0
|
||||
fi
|
||||
# IDN fallback: INWX returns Unicode zone names; when $h is ACE/punycode,
|
||||
# encode each zone name via _idn() and compare — no python dependency.
|
||||
# encode each zone name via _idn() and compare -- no python dependency.
|
||||
if _contains "$h" "xn--"; then
|
||||
_zone_unicode=$(printf "%s" "$response" | _egrep_o '<string>[^<]*' |
|
||||
sed 's/<[^>]*>//g' | while IFS= read -r _z; do
|
||||
|
|
|
|||
197
dnsapi/dns_laodc.sh
Normal file
197
dnsapi/dns_laodc.sh
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_laodc_info='LaoDC DNS API Server
|
||||
Site: laodc.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_laodc
|
||||
Options:
|
||||
LaoDC_Key API Key
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/6973
|
||||
Author: @laodc
|
||||
'
|
||||
|
||||
# Usage:
|
||||
# export LaoDC_Key="your-api-key"
|
||||
# acme.sh --issue --dns dns_laodc -d example.la -d *.example.la --dnssleep 120
|
||||
#
|
||||
# The credentials will be saved in ~/.acme.sh/account.conf
|
||||
|
||||
LAODC_VER="0.1.2"
|
||||
LAODC_API_ENDPOINT="https://dns.laodc.com/v1"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
# Usage: dns_laodc_add _acme-challenge.example.la ZPXvna6tBhq7XQMH7_t2WC2sg0F-BdmtmmpUJiK6Ho
|
||||
dns_laodc_add() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
_info "Using LaoDC DNS API"
|
||||
|
||||
_laodc_validate_key || return 1
|
||||
|
||||
_debug "Checking root zone exists for [$fulldomain]"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "Invalid domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
domain_hash=$(echo "$response" | _egrep_o "\"hash\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
|
||||
_debug _domain "$_domain"
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain_hash "$domain_hash"
|
||||
|
||||
_info "Adding acme record"
|
||||
if _laodc_api "POST" "$domain_hash" "$_sub_domain" "$txtvalue"; then
|
||||
if [ "$_code" = "201" ]; then
|
||||
_info "Added, OK"
|
||||
return 0
|
||||
else
|
||||
_err "Add TXT record error, invalid code. Code: $_code"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_err "Add TXT record error."
|
||||
return 1
|
||||
}
|
||||
|
||||
dns_laodc_rm() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
_laodc_validate_key || return 1
|
||||
|
||||
_debug "Checking root zone exists for [$fulldomain]"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "Invalid domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
domain_hash=$(echo "$response" | _egrep_o "\"hash\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
|
||||
_debug _root_domain "$_domain"
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain_hash "$domain_hash"
|
||||
|
||||
_info "Deleting acme record"
|
||||
if _laodc_api "DELETE" "$domain_hash" "$_sub_domain" "$txtvalue"; then
|
||||
if [ "$_code" = "204" ]; then
|
||||
_info "Deleted, OK"
|
||||
return 0
|
||||
else
|
||||
_err "Delete TXT record error, invalid code. Code: $_code"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_err "Delete TXT record error."
|
||||
return 1
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
# _acme-challenge.www.domain.com
|
||||
# returns
|
||||
# _domain=domain.com
|
||||
# _sub_domain=www
|
||||
_get_root() {
|
||||
fqdn=$1
|
||||
p=1
|
||||
i=1
|
||||
|
||||
while true; do
|
||||
h=$(printf "%s" "$fqdn" | cut -d . -f "$i"-100)
|
||||
if [ -z "$h" ]; then
|
||||
return 1 # not valid domain
|
||||
fi
|
||||
|
||||
# Check API if domain exists
|
||||
if _laodc_api "GET" "$h"; then
|
||||
if [ "$_code" = "200" ]; then
|
||||
_domain="$h"
|
||||
|
||||
# DNS alias mode - @ is alias for fqdn
|
||||
_sub_domain=$(printf "%s" "$fqdn" | cut -d . -f 1-"$p")
|
||||
if [ "$i" = "1" ]; then
|
||||
_sub_domain="@"
|
||||
fi
|
||||
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
p="$i"
|
||||
i=$(_math "$i" + 1)
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
_laodc_validate_key() {
|
||||
LaoDC_Key="${LaoDC_Key:-$(_readaccountconf_mutable LaoDC_Key)}"
|
||||
|
||||
if [ -z "$LaoDC_Key" ]; then
|
||||
LaoDC_Key=""
|
||||
_err "You didn't specify a LaoDC API Key yet."
|
||||
_err "Please export LaoDC_Key and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save the api key to the account conf file.
|
||||
_saveaccountconf_mutable LaoDC_Key "$LaoDC_Key"
|
||||
}
|
||||
|
||||
_laodc_api() {
|
||||
method=$1
|
||||
domain=$2
|
||||
subdomain=$3
|
||||
value=$4
|
||||
|
||||
export _H1="Content-Type: application/json"
|
||||
export _H2="User-Agent: acme.sh/$VER laodc-dns-acme-sh/$LAODC_VER"
|
||||
export _H3="Authorization: Bearer $LaoDC_Key"
|
||||
|
||||
case $method in
|
||||
GET)
|
||||
if [ -n "$subdomain" ]; then
|
||||
response="$(_get "$LAODC_API_ENDPOINT/$domain/$subdomain?type=TXT")"
|
||||
else
|
||||
response="$(_get "$LAODC_API_ENDPOINT/$domain")"
|
||||
fi
|
||||
;;
|
||||
POST)
|
||||
# Sanitize value input
|
||||
value=$(printf '%s' "$value" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||
data="{ \"type\": \"TXT\", \"value\": \"$value\", \"ttl\": \"60\" }"
|
||||
response="$(_post "$data" "$LAODC_API_ENDPOINT/$domain/$subdomain" "" "POST" "application/json")"
|
||||
;;
|
||||
DELETE)
|
||||
# Sanitize value input
|
||||
value=$(printf '%s' "$value" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||
data="{ \"type\": \"TXT\", \"value\": \"$value\" }"
|
||||
response="$(_post "$data" "$LAODC_API_ENDPOINT/$domain/$subdomain" "" "DELETE" "application/json")"
|
||||
;;
|
||||
esac
|
||||
|
||||
_ret=$?
|
||||
|
||||
# Unset immediately after request to prevent leaks
|
||||
export _H1=
|
||||
export _H2=
|
||||
export _H3=
|
||||
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "Error $domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
responseHeaders="$(cat "$HTTP_HEADER")"
|
||||
|
||||
if echo "$responseHeaders" | grep -i "Content-Type: *application/json" >/dev/null 2>&1; then
|
||||
response="$(echo "$response" | _json_decode | _normalizeJson)"
|
||||
fi
|
||||
|
||||
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
|
||||
|
||||
_debug "http response code $_code"
|
||||
_debug response "$response"
|
||||
return 0
|
||||
}
|
||||
167
dnsapi/dns_muumuu.sh
Executable file
167
dnsapi/dns_muumuu.sh
Executable file
|
|
@ -0,0 +1,167 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_muumuu_info='muumuu-domain.com
|
||||
Site: muumuu-domain.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_muumuu
|
||||
Options:
|
||||
MUUMUU_PAT Personal Access Token (scopes: domains:read, dns:read, dns:write)
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/7011
|
||||
'
|
||||
|
||||
MUUMUU_API="https://muumuu-domain.com/api/v2"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
dns_muumuu_add() {
|
||||
fulldomain="$(echo "$1" | _lower_case)"
|
||||
txtvalue="$2"
|
||||
|
||||
_info "Using muumuu-domain.com DNS API"
|
||||
_debug fulldomain "$fulldomain"
|
||||
_debug txtvalue "$txtvalue"
|
||||
|
||||
MUUMUU_PAT="${MUUMUU_PAT:-$(_readaccountconf_mutable MUUMUU_PAT)}"
|
||||
if [ -z "$MUUMUU_PAT" ]; then
|
||||
_err "MUUMUU_PAT is not set."
|
||||
_err "Please create a Personal Access Token at https://muumuu-domain.com"
|
||||
_err "with scopes: domains:read, dns:read, dns:write"
|
||||
return 1
|
||||
fi
|
||||
_saveaccountconf_mutable MUUMUU_PAT "$MUUMUU_PAT"
|
||||
|
||||
if ! _muumuu_get_root "$fulldomain"; then
|
||||
_err "Unable to find the root domain for $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
_debug _domain_id "$_domain_id"
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
|
||||
_info "Adding TXT record for ${fulldomain}"
|
||||
body="{\"fqdn\":\"${fulldomain}.\",\"type\":\"TXT\",\"value\":\"${txtvalue}\",\"ttl\":3600}"
|
||||
if _muumuu_rest POST "/me/domains/${_domain_id}/dns-records" "$body"; then
|
||||
if [ "$_muumuu_code" = "201" ]; then
|
||||
_info "TXT record added successfully"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
_err "Failed to add TXT record (HTTP ${_muumuu_code})"
|
||||
return 1
|
||||
}
|
||||
|
||||
dns_muumuu_rm() {
|
||||
fulldomain="$(echo "$1" | _lower_case)"
|
||||
txtvalue="$2"
|
||||
|
||||
_info "Using muumuu-domain.com DNS API"
|
||||
_debug fulldomain "$fulldomain"
|
||||
_debug txtvalue "$txtvalue"
|
||||
|
||||
MUUMUU_PAT="${MUUMUU_PAT:-$(_readaccountconf_mutable MUUMUU_PAT)}"
|
||||
if [ -z "$MUUMUU_PAT" ]; then
|
||||
_err "MUUMUU_PAT is not set."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! _muumuu_get_root "$fulldomain"; then
|
||||
_err "Unable to find the root domain for $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
_debug _domain_id "$_domain_id"
|
||||
|
||||
_info "Looking up TXT record for ${fulldomain}"
|
||||
if ! _muumuu_rest GET "/me/domains/${_domain_id}/dns-records?type=TXT&fqdn=${fulldomain}."; then
|
||||
_err "Failed to list TXT records"
|
||||
return 1
|
||||
fi
|
||||
|
||||
record_id=$(echo "$response" | _egrep_o "\"id\":[0-9]+[^}]*\"value\":\"${txtvalue}\"" | _egrep_o "\"id\":[0-9]+" | _head_n 1 | cut -d: -f2)
|
||||
if [ -z "$record_id" ]; then
|
||||
_info "TXT record not found, nothing to remove"
|
||||
return 0
|
||||
fi
|
||||
_debug record_id "$record_id"
|
||||
|
||||
if _muumuu_rest DELETE "/me/domains/${_domain_id}/dns-records/${record_id}"; then
|
||||
if [ "$_muumuu_code" = "204" ]; then
|
||||
_info "TXT record deleted successfully"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
_err "Failed to delete TXT record (HTTP ${_muumuu_code})"
|
||||
return 1
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
|
||||
# _acme-challenge.www.example.com
|
||||
# sets:
|
||||
# _domain_id MU00000001
|
||||
# _sub_domain _acme-challenge.www
|
||||
# _domain example.com
|
||||
_muumuu_get_root() {
|
||||
domain="$1"
|
||||
i=1
|
||||
p=0
|
||||
h=""
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
if [ -z "$h" ]; then
|
||||
return 1
|
||||
fi
|
||||
if ! _muumuu_rest GET "/me/domains?fqdn=${h}&page-size=1"; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$_muumuu_code" = "401" ] || [ "$_muumuu_code" = "403" ]; then
|
||||
_err "Authentication failed (HTTP ${_muumuu_code}). Check MUUMUU_PAT."
|
||||
return 1
|
||||
fi
|
||||
if _contains "$response" "\"fqdn\":\"${h}\""; then
|
||||
_domain_id=$(echo "$response" | _egrep_o "\"id\":\"MU[0-9]+\"" | _head_n 1 | cut -d: -f2 | tr -d '"')
|
||||
_domain="$h"
|
||||
if [ "$p" = "0" ]; then
|
||||
_sub_domain=""
|
||||
else
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
p="$i"
|
||||
i=$(_math "$i" + 1)
|
||||
done
|
||||
}
|
||||
|
||||
_muumuu_rest() {
|
||||
_muumuu_method="$1"
|
||||
_muumuu_path="$2"
|
||||
_muumuu_data="$3"
|
||||
_muumuu_url="${MUUMUU_API}${_muumuu_path}"
|
||||
|
||||
export _H1="Authorization: Bearer ${MUUMUU_PAT}"
|
||||
export _H2="Content-Type: application/json"
|
||||
export _H3="Accept: application/json"
|
||||
export _H4=""
|
||||
export _H5=""
|
||||
|
||||
_secure_debug2 data "$_muumuu_data"
|
||||
|
||||
if [ "$_muumuu_method" = "GET" ]; then
|
||||
response="$(_get "$_muumuu_url")"
|
||||
else
|
||||
response="$(_post "$_muumuu_data" "$_muumuu_url" "" "$_muumuu_method")"
|
||||
fi
|
||||
_muumuu_ret="$?"
|
||||
_muumuu_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
|
||||
_debug "HTTP code: ${_muumuu_code}"
|
||||
_secure_debug2 response "$response"
|
||||
|
||||
if [ "$_muumuu_ret" != "0" ]; then
|
||||
_err "Error accessing ${_muumuu_url}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
response="$(printf "%s" "$response" | _normalizeJson)"
|
||||
return 0
|
||||
}
|
||||
|
|
@ -186,7 +186,7 @@ _oauth2() {
|
|||
_oauth2_std() {
|
||||
# HTTP Basic Authentication
|
||||
_H1="Authorization: Basic $(echo "$MB_AK:$MB_AS" | _base64)"
|
||||
_H2="Accepts: application/json"
|
||||
_H2="Accept: application/json"
|
||||
export _H1 _H2
|
||||
body="grant_type=client_credentials"
|
||||
|
||||
|
|
@ -210,7 +210,7 @@ _oauth2_std() {
|
|||
}
|
||||
|
||||
_oauth2_github() {
|
||||
_H1="Accepts: application/json"
|
||||
_H1="Accept: application/json"
|
||||
export _H1
|
||||
body="{\"login\":{\"handle\":\"$MB_AK\",\"pass\":\"$MB_AS\",\"floating\":1}}"
|
||||
|
||||
|
|
@ -241,7 +241,7 @@ _mb_rest() {
|
|||
fi
|
||||
|
||||
_H1="Authorization: Bearer $MB_TK"
|
||||
_H2="Accepts: application/json"
|
||||
_H2="Accept: application/json"
|
||||
export _H1 _H2
|
||||
if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ] || [ "$m" = "DELETE" ]; then
|
||||
# body url [needbase64] [POST|PUT|DELETE] [ContentType]
|
||||
|
|
|
|||
|
|
@ -153,10 +153,9 @@ _namecom_get_root() {
|
|||
i=2
|
||||
p=1
|
||||
|
||||
if ! _namecom_rest GET "domains"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Probe each candidate with GetDomain (GET /v4/domains/{domainName}) instead
|
||||
# of listing all domains: the list is paginated at 1000 domains per page, so
|
||||
# larger accounts never found their domain on the first page.
|
||||
# Need to exclude the last field (tld)
|
||||
numfields=$(echo "$domain" | _egrep_o "\." | wc -l)
|
||||
while [ "$i" -le "$numfields" ]; do
|
||||
|
|
@ -166,7 +165,7 @@ _namecom_get_root() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
if _contains "$response" "$host"; then
|
||||
if _namecom_rest GET "domains/$host" && _contains "$response" "\"domainName\":\"$host\""; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_domain="$host"
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@ _get_root() {
|
|||
|
||||
for ITEM in ${domains_list}; do
|
||||
IDN_ITEM=${ITEM}
|
||||
case "${domain}" in
|
||||
*${IDN_ITEM}*)
|
||||
case ".${domain}" in
|
||||
*.${IDN_ITEM}*)
|
||||
_domain="$(_idn "${ITEM}")"
|
||||
_debug _domain "${_domain}"
|
||||
return 0
|
||||
|
|
|
|||
217
dnsapi/dns_wedos.sh
Normal file
217
dnsapi/dns_wedos.sh
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034
|
||||
dns_wedos_info='WEDOS.com
|
||||
Site: wedos.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_wedos
|
||||
Options:
|
||||
WEDOS_Username WAPI login (account email)
|
||||
WEDOS_Wapipass WAPI password
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/7071
|
||||
Author: Jan Forman <jforman@jflab.cz>
|
||||
'
|
||||
|
||||
WEDOS_Api="https://api.wedos.com/wapi/json"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#Usage: dns_wedos_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
dns_wedos_add() {
|
||||
fulldomain=$(echo "$1" | _lower_case)
|
||||
txtvalue=$2
|
||||
|
||||
if ! _wedos_init; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Detecting root zone for $fulldomain"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "Cannot determine root zone for: $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
_debug _domain "$_domain"
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
|
||||
_info "Adding TXT record: $_sub_domain.$_domain"
|
||||
if ! _wedos_request "dns-row-add" "{\"domain\":\"$_domain\",\"name\":\"$_sub_domain\",\"ttl\":\"300\",\"type\":\"TXT\",\"rdata\":\"$txtvalue\"}"; then
|
||||
_err "Failed to add TXT record"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Committing DNS changes for $_domain"
|
||||
if ! _wedos_request "dns-domain-commit" "{\"name\":\"$_domain\"}"; then
|
||||
_err "Failed to commit DNS changes"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#Usage: dns_wedos_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
dns_wedos_rm() {
|
||||
fulldomain=$(echo "$1" | _lower_case)
|
||||
txtvalue=$2
|
||||
|
||||
if ! _wedos_init; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Detecting root zone for $fulldomain"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "Cannot determine root zone for: $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
_debug _domain "$_domain"
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
|
||||
# _get_root leaves the dns-rows-list response for $_domain in $response
|
||||
_debug "Looking up row IDs for TXT value: $txtvalue"
|
||||
_row_ids=$(echo "$response" | tr '{' '\n' | grep -F -- "\"rdata\":\"$txtvalue\"" | grep -F -- "\"name\":\"$_sub_domain\"" | _egrep_o '"ID": *"[0-9]*"' | tr -dc '0-9\n')
|
||||
_debug _row_ids "$_row_ids"
|
||||
|
||||
if [ -z "$_row_ids" ]; then
|
||||
_info "TXT record not found, nothing to remove"
|
||||
return 0
|
||||
fi
|
||||
|
||||
for _row_id in $_row_ids; do
|
||||
_info "Removing TXT record ID $_row_id from $_domain"
|
||||
if ! _wedos_request "dns-row-delete" "{\"domain\":\"$_domain\",\"row_id\":\"$_row_id\"}"; then
|
||||
_err "Failed to delete TXT record"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
_info "Committing DNS changes for $_domain"
|
||||
if ! _wedos_request "dns-domain-commit" "{\"name\":\"$_domain\"}"; then
|
||||
_err "Failed to commit DNS changes"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
|
||||
_wedos_init() {
|
||||
WEDOS_Username="${WEDOS_Username:-$(_readaccountconf_mutable WEDOS_Username)}"
|
||||
WEDOS_Wapipass="${WEDOS_Wapipass:-$(_readaccountconf_mutable WEDOS_Wapipass)}"
|
||||
|
||||
if [ -z "$WEDOS_Username" ] || [ -z "$WEDOS_Wapipass" ]; then
|
||||
WEDOS_Username=""
|
||||
WEDOS_Wapipass=""
|
||||
_err "You didn't specify the WEDOS WAPI credentials yet."
|
||||
_err "Please export WEDOS_Username and WEDOS_Wapipass and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_saveaccountconf_mutable WEDOS_Username "$WEDOS_Username"
|
||||
_saveaccountconf_mutable WEDOS_Wapipass "$WEDOS_Wapipass"
|
||||
return 0
|
||||
}
|
||||
|
||||
# WAPI auth token: sha1(login + sha1(password) + hour), where the hour is
|
||||
# the current hour on the WEDOS servers (Europe/Prague timezone).
|
||||
# The POSIX TZ string is used so no tzdata is required on the client.
|
||||
_wedos_auth() {
|
||||
if [ "$_wedos_utc" ]; then
|
||||
# fallback: WAPI accepts 1 hour of skew, UTC+1 fits both CET and CEST
|
||||
_wedos_hour=$(date -u +%H)
|
||||
_wedos_hour=$(printf '%02d' "$(((${_wedos_hour#0} + 1) % 24))")
|
||||
else
|
||||
_wedos_hour=$(TZ='CET-1CEST,M3.5.0,M10.5.0/3' date +%H)
|
||||
fi
|
||||
_wedos_phash=$(printf '%s' "$WEDOS_Wapipass" | _digest sha1 hex)
|
||||
printf '%s' "${WEDOS_Username}${_wedos_phash}${_wedos_hour}" | _digest sha1 hex
|
||||
}
|
||||
|
||||
#Usage: _wedos_request <command> <data-json>
|
||||
#Returns 0 and sets $response on WAPI code 1000, returns 1 otherwise.
|
||||
_wedos_request() {
|
||||
_wedos_cmd="$1"
|
||||
_wedos_data="$2"
|
||||
|
||||
_wedos_token=$(_wedos_auth)
|
||||
_secure_debug _wedos_token "$_wedos_token"
|
||||
|
||||
_wedos_json="{\"request\":{\"user\":\"$WEDOS_Username\",\"auth\":\"$_wedos_token\",\"command\":\"$_wedos_cmd\",\"data\":$_wedos_data}}"
|
||||
_debug2 "WAPI command: $_wedos_cmd"
|
||||
_debug2 "WAPI data: $_wedos_data"
|
||||
|
||||
# _post sends the global _H1.._H5 headers with every request; clear them so
|
||||
# headers from earlier API calls are not leaked to the WAPI endpoint.
|
||||
export _H1=""
|
||||
export _H2=""
|
||||
export _H3=""
|
||||
export _H4=""
|
||||
export _H5=""
|
||||
|
||||
_wedos_body="request=$(printf '%s' "$_wedos_json" | _url_encode)"
|
||||
response=$(_post "$_wedos_body" "$WEDOS_Api" "" "POST" "application/x-www-form-urlencoded")
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "WAPI request failed for command '$_wedos_cmd'"
|
||||
return 1
|
||||
fi
|
||||
_debug2 "WAPI response: $response"
|
||||
|
||||
_wedos_code=$(echo "$response" | _egrep_o '"code": *[0-9]*' | _head_n 1 | tr -dc '0-9')
|
||||
_debug2 "WAPI result code: $_wedos_code"
|
||||
if [ "$_wedos_code" = "1000" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# some systems ignore the TZ variable (Haiku), sending a wrong auth hour;
|
||||
# retry once with the UTC fallback in _wedos_auth
|
||||
if [ "$_wedos_code" = "2050" ] && [ -z "$_wedos_utc" ]; then
|
||||
_wedos_utc=1
|
||||
_wedos_request "$_wedos_cmd" "$_wedos_data"
|
||||
return $?
|
||||
fi
|
||||
|
||||
# 2050 = bad credentials, 2051 = IP not whitelisted, 2052 = IP blocked
|
||||
if [ "$_wedos_code" = "2050" ] || [ "$_wedos_code" = "2051" ] || [ "$_wedos_code" = "2052" ]; then
|
||||
_wedos_result=$(echo "$response" | _egrep_o '"result": *"[^"]*"' | _head_n 1 | cut -d '"' -f 4)
|
||||
_err "WAPI authentication error $_wedos_code: $_wedos_result"
|
||||
_err "Check WEDOS_Username, WEDOS_Wapipass and the WAPI IP whitelist."
|
||||
_wedos_autherr=1
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "WAPI error for command '$_wedos_cmd': $response"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Determine the registered domain (_domain) and subdomain prefix (_sub_domain)
|
||||
# by walking up the labels and calling dns-rows-list until WAPI accepts one.
|
||||
# _acme-challenge.www.example.co.uk
|
||||
# -> _sub_domain=_acme-challenge.www _domain=example.co.uk
|
||||
# The full domain itself is tried first, so a zone apex (e.g. DNS alias mode
|
||||
# pointing at the registered domain) resolves to an empty _sub_domain.
|
||||
_get_root() {
|
||||
_gr_full="$1"
|
||||
_gr_i=1
|
||||
_wedos_autherr=""
|
||||
while true; do
|
||||
_gr_candidate=$(printf '%s' "$_gr_full" | cut -d . -f "${_gr_i}"-100)
|
||||
_debug2 "Checking zone candidate: $_gr_candidate"
|
||||
if [ -z "$_gr_candidate" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _wedos_request "dns-rows-list" "{\"domain\":\"$_gr_candidate\"}"; then
|
||||
_domain="$_gr_candidate"
|
||||
if [ "$_gr_i" = "1" ]; then
|
||||
_sub_domain=""
|
||||
else
|
||||
_sub_domain=$(printf '%s' "$_gr_full" | cut -d . -f 1-"$((_gr_i - 1))")
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
# auth error hits every candidate, stop the walk
|
||||
if [ "$_wedos_autherr" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_gr_i=$((_gr_i + 1))
|
||||
done
|
||||
}
|
||||
|
|
@ -83,7 +83,43 @@ aws_ses_send() {
|
|||
response="$(aws_rest POST "" "" "$_data")"
|
||||
}
|
||||
|
||||
_use_container_role() {
|
||||
# automatically set if running inside ECS
|
||||
if [ -z "$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" ]; then
|
||||
_debug "No ECS environment variable detected"
|
||||
return 1
|
||||
fi
|
||||
_use_metadata "169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
|
||||
}
|
||||
|
||||
_use_instance_role() {
|
||||
_instance_role_name_url="http://169.254.169.254/latest/meta-data/iam/security-credentials/"
|
||||
|
||||
if _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 401; then
|
||||
_debug "Using IMDSv2"
|
||||
_token_url="http://169.254.169.254/latest/api/token"
|
||||
export _H1="X-aws-ec2-metadata-token-ttl-seconds: 21600"
|
||||
_token="$(_post "" "$_token_url" "" "PUT")"
|
||||
_secure_debug3 "_token" "$_token"
|
||||
if [ -z "$_token" ]; then
|
||||
_debug "Unable to fetch IMDSv2 token from instance metadata"
|
||||
return 1
|
||||
fi
|
||||
export _H1="X-aws-ec2-metadata-token: $_token"
|
||||
fi
|
||||
|
||||
if ! _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 200; then
|
||||
_debug "Unable to fetch IAM role from instance metadata"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_instance_role_name=$(_get "$_instance_role_name_url" "" 1)
|
||||
_debug "_instance_role_name" "$_instance_role_name"
|
||||
_use_metadata "$_instance_role_name_url$_instance_role_name" "$_token"
|
||||
}
|
||||
|
||||
_use_metadata() {
|
||||
export _H1="X-aws-ec2-metadata-token: $2"
|
||||
_aws_creds="$(
|
||||
_get "$1" "" 1 |
|
||||
_normalizeJson |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue