]>
Commit | Line | Data |
---|---|---|
623d615c MA |
1 | #!/bin/bash |
2 | ||
3 | ################################################################################ | |
4 | # ACME.sh 3rd party deploy plugin for Synology DSM | |
5 | ################################################################################ | |
6 | # Authors: Brian Hartvigsen (creator), https://github.com/tresni | |
7 | # Martin Arndt (contributor), https://troublezone.net/ | |
db3f131d | 8 | # Updated: 2023-07-03 |
623d615c MA |
9 | # Issues: https://github.com/acmesh-official/acme.sh/issues/2727 |
10 | ################################################################################ | |
11 | # Usage: | |
12 | # 1. export SYNO_Username="adminUser" | |
13 | # 2. export SYNO_Password="adminPassword" | |
14 | # Optional exports (shown values are the defaults): | |
15 | # - export SYNO_Certificate="" to replace a specific certificate via description | |
16 | # - export SYNO_Scheme="http" | |
17 | # - export SYNO_Hostname="localhost" | |
18 | # - export SYNO_Port="5000" | |
19 | # - export SYNO_Device_Name="CertRenewal" - required for skipping 2FA-OTP | |
20 | # - export SYNO_Device_ID="" - required for skipping 2FA-OTP | |
21 | # 3. acme.sh --deploy --deploy-hook synology_dsm -d example.com | |
22 | ################################################################################ | |
4635dacf | 23 | # Dependencies: |
623d615c MA |
24 | # - jq & curl |
25 | ################################################################################ | |
26 | # Return value: | |
27 | # 0 means success, otherwise error. | |
28 | ################################################################################ | |
555e0de9 | 29 | |
623d615c | 30 | ########## Public functions #################################################### |
555e0de9 BH |
31 | #domain keyfile certfile cafile fullchain |
32 | synology_dsm_deploy() { | |
555e0de9 BH |
33 | _cdomain="$1" |
34 | _ckey="$2" | |
35 | _ccert="$3" | |
36 | _cca="$4" | |
37 | ||
38 | _debug _cdomain "$_cdomain" | |
39 | ||
623d615c | 40 | # Get username & password, but don't save until we authenticated successfully |
12593410 BH |
41 | _getdeployconf SYNO_Username |
42 | _getdeployconf SYNO_Password | |
43 | _getdeployconf SYNO_Create | |
db3f131d MA |
44 | _getdeployconf SYNO_DID |
45 | _getdeployconf SYNO_TOTP_SECRET | |
623d615c MA |
46 | _getdeployconf SYNO_Device_Name |
47 | _getdeployconf SYNO_Device_ID | |
3a7c7fe4 | 48 | if [ -z "${SYNO_Username:-}" ] || [ -z "${SYNO_Password:-}" ]; then |
555e0de9 BH |
49 | _err "SYNO_Username & SYNO_Password must be set" |
50 | return 1 | |
51 | fi | |
623d615c MA |
52 | if [ -n "${SYNO_Device_Name:-}" ] && [ -z "${SYNO_Device_ID:-}" ]; then |
53 | _err "SYNO_Device_Name set, but SYNO_Device_ID is empty" | |
54 | return 1 | |
55 | fi | |
555e0de9 BH |
56 | _debug2 SYNO_Username "$SYNO_Username" |
57 | _secure_debug2 SYNO_Password "$SYNO_Password" | |
623d615c MA |
58 | _debug2 SYNO_Create "$SYNO_Create" |
59 | _debug2 SYNO_Device_Name "$SYNO_Device_Name" | |
60 | _secure_debug2 SYNO_Device_ID "$SYNO_Device_ID" | |
555e0de9 | 61 | |
623d615c | 62 | # Optional scheme, hostname & port for Synology DSM |
12593410 BH |
63 | _getdeployconf SYNO_Scheme |
64 | _getdeployconf SYNO_Hostname | |
65 | _getdeployconf SYNO_Port | |
555e0de9 | 66 | |
623d615c MA |
67 | # Default values for scheme, hostname & port |
68 | # Defaulting to localhost & http, because it's localhost… | |
555e0de9 BH |
69 | [ -n "${SYNO_Scheme}" ] || SYNO_Scheme="http" |
70 | [ -n "${SYNO_Hostname}" ] || SYNO_Hostname="localhost" | |
71 | [ -n "${SYNO_Port}" ] || SYNO_Port="5000" | |
12593410 BH |
72 | _savedeployconf SYNO_Scheme "$SYNO_Scheme" |
73 | _savedeployconf SYNO_Hostname "$SYNO_Hostname" | |
74 | _savedeployconf SYNO_Port "$SYNO_Port" | |
555e0de9 BH |
75 | _debug2 SYNO_Scheme "$SYNO_Scheme" |
76 | _debug2 SYNO_Hostname "$SYNO_Hostname" | |
77 | _debug2 SYNO_Port "$SYNO_Port" | |
78 | ||
db3f131d | 79 | # Get the certificate description, but don't save it until we verify it's real |
555e0de9 | 80 | _getdeployconf SYNO_Certificate |
694194be | 81 | _debug SYNO_Certificate "${SYNO_Certificate:-}" |
555e0de9 | 82 | |
dcb51683 | 83 | # shellcheck disable=SC1003 # We are not trying to escape a single quote |
74a4a788 BH |
84 | if printf "%s" "$SYNO_Certificate" | grep '\\'; then |
85 | _err "Do not use a backslash (\) in your certificate description" | |
86 | return 1 | |
87 | fi | |
88 | ||
555e0de9 BH |
89 | _base_url="$SYNO_Scheme://$SYNO_Hostname:$SYNO_Port" |
90 | _debug _base_url "$_base_url" | |
91 | ||
cc692854 T |
92 | _debug "Getting API version" |
93 | response=$(_get "$_base_url/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query&query=SYNO.API.Auth") | |
94 | api_version=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"maxVersion" *: *\([0-9]*\).*/\1/p') | |
95 | _debug3 response "$response" | |
96 | _debug3 api_version "$api_version" | |
97 | ||
623d615c | 98 | # Login, get the session ID & SynoToken from JSON |
52a168b9 | 99 | _info "Logging into $SYNO_Hostname:$SYNO_Port" |
52b81608 BH |
100 | encoded_username="$(printf "%s" "$SYNO_Username" | _url_encode)" |
101 | encoded_password="$(printf "%s" "$SYNO_Password" | _url_encode)" | |
0d7b8316 MA |
102 | |
103 | otp_code="" | |
db3f131d MA |
104 | # START - DEPRECATED, only kept for legacy compatibility reasons |
105 | if [ -n "$SYNO_TOTP_SECRET" ]; then | |
106 | _info "WARNING: Usage of SYNO_TOTP_SECRET is deprecated!" | |
107 | _info " See synology_dsm.sh script or ACME.sh Wiki page for details:" | |
108 | _info " https://github.com/acmesh-official/acme.sh/wiki/Synology-NAS-Guide" | |
109 | DEPRECATED_otp_code="" | |
110 | if _exists oathtool; then | |
111 | DEPRECATED_otp_code="$(oathtool --base32 --totp "${SYNO_TOTP_SECRET}" 2>/dev/null)" | |
112 | else | |
113 | _err "oathtool could not be found, install oathtool to use SYNO_TOTP_SECRET" | |
114 | return 1 | |
115 | fi | |
cc692854 | 116 | |
db3f131d MA |
117 | if [ -n "$SYNO_DID" ]; then |
118 | _H1="Cookie: did=$SYNO_DID" | |
119 | export _H1 | |
120 | _debug3 H1 "${_H1}" | |
121 | fi | |
122 | ||
0d7b8316 | 123 | response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$DEPRECATED_otp_code&device_name=certrenewal&device_id=$SYNO_DID" "$_base_url/webapi/auth.cgi?enable_syno_token=yes") |
db3f131d MA |
124 | _debug3 response "$response" |
125 | # END - DEPRECATED, only kept for legacy compatibility reasons | |
623d615c | 126 | # Get device ID if still empty first, otherwise log in right away |
db3f131d | 127 | elif [ -z "${SYNO_Device_ID:-}" ]; then |
623d615c MA |
128 | printf "Enter OTP code for user '%s': " "$SYNO_Username" |
129 | read -r otp_code | |
130 | if [ -z "${SYNO_Device_Name:-}" ]; then | |
131 | printf "Enter device name or leave empty for default (CertRenewal): " | |
132 | read -r SYNO_Device_Name | |
133 | [ -n "${SYNO_Device_Name}" ] || SYNO_Device_Name="CertRenewal" | |
5ae3a020 | 134 | fi |
4635dacf | 135 | |
623d615c MA |
136 | response=$(_get "$_base_url/webapi/entry.cgi?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&otp_code=$otp_code&enable_syno_token=yes&enable_device_token=yes&device_name=$SYNO_Device_Name") |
137 | _debug3 response "$response" | |
138 | SYNO_Device_ID=$(echo "$response" | grep "device_id" | sed -n 's/.*"device_id" *: *"\([^"]*\).*/\1/p') | |
139 | _secure_debug2 SYNO_Device_ID "$SYNO_Device_ID" | |
140 | else | |
141 | response=$(_get "$_base_url/webapi/entry.cgi?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_name=$SYNO_Device_Name&device_id=$SYNO_Device_ID") | |
142 | _debug3 response "$response" | |
cc692854 T |
143 | fi |
144 | ||
623d615c | 145 | sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p') |
cc692854 | 146 | token=$(echo "$response" | grep "synotoken" | sed -n 's/.*"synotoken" *: *"\([^"]*\).*/\1/p') |
0548ad2f | 147 | _debug "Session ID" "$sid" |
623d615c | 148 | _debug SynoToken "$token" |
0c9e4f67 | 149 | if [ -z "$SYNO_DID" ] && [ -z "$SYNO_Device_ID" ] || [ -z "$sid" ] || [ -z "$token" ]; then |
623d615c MA |
150 | _err "Unable to authenticate to $_base_url - check your username & password." |
151 | _err "If two-factor authentication is enabled for the user, set SYNO_Device_ID." | |
555e0de9 BH |
152 | return 1 |
153 | fi | |
154 | ||
cc692854 | 155 | _H1="X-SYNO-TOKEN: $token" |
52a168b9 | 156 | export _H1 |
5d3bc95a | 157 | _debug2 H1 "${_H1}" |
52a168b9 | 158 | |
623d615c | 159 | # Now that we know the username & password are good, save them |
52a168b9 BH |
160 | _savedeployconf SYNO_Username "$SYNO_Username" |
161 | _savedeployconf SYNO_Password "$SYNO_Password" | |
623d615c MA |
162 | _savedeployconf SYNO_Device_Name "$SYNO_Device_Name" |
163 | _savedeployconf SYNO_Device_ID "$SYNO_Device_ID" | |
555e0de9 | 164 | |
52a168b9 | 165 | _info "Getting certificates in Synology DSM" |
cc692854 | 166 | response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1&_sid=$sid" "$_base_url/webapi/entry.cgi") |
555e0de9 | 167 | _debug3 response "$response" |
74a4a788 BH |
168 | escaped_certificate="$(printf "%s" "$SYNO_Certificate" | sed 's/\([].*^$[]\)/\\\1/g;s/"/\\\\"/g')" |
169 | _debug escaped_certificate "$escaped_certificate" | |
170 | id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p") | |
52a168b9 | 171 | _debug2 id "$id" |
555e0de9 | 172 | |
3a7c7fe4 | 173 | if [ -z "$id" ] && [ -z "${SYNO_Create:-}" ]; then |
623d615c | 174 | _err "Unable to find certificate: $SYNO_Certificate & \$SYNO_Create is not set" |
555e0de9 BH |
175 | return 1 |
176 | fi | |
177 | ||
db3f131d | 178 | # We've verified this certificate description is a thing, so save it |
74a4a788 | 179 | _savedeployconf SYNO_Certificate "$SYNO_Certificate" "base64" |
555e0de9 | 180 | |
52a168b9 | 181 | _info "Generate form POST request" |
0deea539 | 182 | nl="\0015\0012" |
79637097 | 183 | delim="--------------------------$(_utc_date | tr -d -- '-: ')" |
0deea539 | 184 | content="--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")\0012" |
185 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_ccert")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ccert")\0012" | |
186 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"inter_cert\"; filename=\"$(basename "$_cca")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cca")\0012" | |
52a168b9 BH |
187 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id" |
188 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_Certificate}" | |
74a4a788 | 189 | if echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then |
db3f131d | 190 | _debug2 default "This is the default certificate" |
5ab9ca1c BH |
191 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}true" |
192 | else | |
db3f131d | 193 | _debug2 default "This is NOT the default certificate" |
5ab9ca1c | 194 | fi |
52a168b9 | 195 | content="$content${nl}--$delim--${nl}" |
95769de4 BH |
196 | content="$(printf "%b_" "$content")" |
197 | content="${content%_}" # protect trailing \n | |
52a168b9 BH |
198 | |
199 | _info "Upload certificate to the Synology DSM" | |
cc692854 | 200 | response=$(_post "$content" "$_base_url/webapi/entry.cgi?api=SYNO.Core.Certificate&method=import&version=1&SynoToken=$token&_sid=$sid" "" "POST" "multipart/form-data; boundary=${delim}") |
555e0de9 | 201 | _debug3 response "$response" |
555e0de9 | 202 | |
1b475cf9 BH |
203 | if ! echo "$response" | grep '"error":' >/dev/null; then |
204 | if echo "$response" | grep '"restart_httpd":true' >/dev/null; then | |
623d615c | 205 | _info "Restarting HTTP services succeeded" |
555e0de9 | 206 | else |
623d615c | 207 | _info "Restarting HTTP services failed" |
555e0de9 | 208 | fi |
623d615c MA |
209 | |
210 | _logout | |
6459ccb1 | 211 | return 0 |
555e0de9 | 212 | else |
52a168b9 | 213 | _err "Unable to update certificate, error code $response" |
623d615c | 214 | _logout |
555e0de9 BH |
215 | return 1 |
216 | fi | |
217 | } | |
623d615c MA |
218 | |
219 | #################### Private functions below ################################## | |
220 | _logout() { | |
db3f131d | 221 | # Logout to not occupy a permanent session, e.g. in DSM's "Connected Users" widget |
623d615c MA |
222 | response=$(_get "$_base_url/webapi/entry.cgi?api=SYNO.API.Auth&version=$api_version&method=logout") |
223 | _debug3 response "$response" | |
224 | } |