]>
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") | |
87a7bde6 | 94 | api_path=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"path" *: *"\([^"]*\)".*/\1/p') |
cc692854 T |
95 | api_version=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"maxVersion" *: *\([0-9]*\).*/\1/p') |
96 | _debug3 response "$response" | |
d52b3877 | 97 | _debug3 api_path "$api_path" |
cc692854 T |
98 | _debug3 api_version "$api_version" |
99 | ||
623d615c | 100 | # Login, get the session ID & SynoToken from JSON |
52a168b9 | 101 | _info "Logging into $SYNO_Hostname:$SYNO_Port" |
52b81608 BH |
102 | encoded_username="$(printf "%s" "$SYNO_Username" | _url_encode)" |
103 | encoded_password="$(printf "%s" "$SYNO_Password" | _url_encode)" | |
0d7b8316 MA |
104 | |
105 | otp_code="" | |
db3f131d MA |
106 | # START - DEPRECATED, only kept for legacy compatibility reasons |
107 | if [ -n "$SYNO_TOTP_SECRET" ]; then | |
108 | _info "WARNING: Usage of SYNO_TOTP_SECRET is deprecated!" | |
109 | _info " See synology_dsm.sh script or ACME.sh Wiki page for details:" | |
110 | _info " https://github.com/acmesh-official/acme.sh/wiki/Synology-NAS-Guide" | |
111 | DEPRECATED_otp_code="" | |
112 | if _exists oathtool; then | |
113 | DEPRECATED_otp_code="$(oathtool --base32 --totp "${SYNO_TOTP_SECRET}" 2>/dev/null)" | |
114 | else | |
115 | _err "oathtool could not be found, install oathtool to use SYNO_TOTP_SECRET" | |
116 | return 1 | |
117 | fi | |
cc692854 | 118 | |
db3f131d MA |
119 | if [ -n "$SYNO_DID" ]; then |
120 | _H1="Cookie: did=$SYNO_DID" | |
121 | export _H1 | |
122 | _debug3 H1 "${_H1}" | |
123 | fi | |
124 | ||
0d7b8316 | 125 | 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 |
126 | _debug3 response "$response" |
127 | # END - DEPRECATED, only kept for legacy compatibility reasons | |
623d615c | 128 | # Get device ID if still empty first, otherwise log in right away |
db3f131d | 129 | elif [ -z "${SYNO_Device_ID:-}" ]; then |
623d615c MA |
130 | printf "Enter OTP code for user '%s': " "$SYNO_Username" |
131 | read -r otp_code | |
132 | if [ -z "${SYNO_Device_Name:-}" ]; then | |
133 | printf "Enter device name or leave empty for default (CertRenewal): " | |
134 | read -r SYNO_Device_Name | |
135 | [ -n "${SYNO_Device_Name}" ] || SYNO_Device_Name="CertRenewal" | |
5ae3a020 | 136 | fi |
4635dacf | 137 | |
d52b3877 | 138 | response=$(_get "$_base_url/webapi/$api_path?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") |
b793dbf9 MA |
139 | _secure_debug3 response "$response" |
140 | ||
141 | id_property='device_id' | |
142 | [ "${api_version}" -gt '6' ] || id_property='did' | |
143 | SYNO_Device_ID=$(echo "$response" | grep "$id_property" | sed -n 's/.*"'$id_property'" *: *"\([^"]*\).*/\1/p') | |
623d615c MA |
144 | _secure_debug2 SYNO_Device_ID "$SYNO_Device_ID" |
145 | else | |
d52b3877 | 146 | response=$(_get "$_base_url/webapi/$api_path?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") |
623d615c | 147 | _debug3 response "$response" |
cc692854 T |
148 | fi |
149 | ||
623d615c | 150 | sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p') |
cc692854 | 151 | token=$(echo "$response" | grep "synotoken" | sed -n 's/.*"synotoken" *: *"\([^"]*\).*/\1/p') |
0548ad2f | 152 | _debug "Session ID" "$sid" |
623d615c | 153 | _debug SynoToken "$token" |
0c9e4f67 | 154 | if [ -z "$SYNO_DID" ] && [ -z "$SYNO_Device_ID" ] || [ -z "$sid" ] || [ -z "$token" ]; then |
623d615c MA |
155 | _err "Unable to authenticate to $_base_url - check your username & password." |
156 | _err "If two-factor authentication is enabled for the user, set SYNO_Device_ID." | |
555e0de9 BH |
157 | return 1 |
158 | fi | |
159 | ||
cc692854 | 160 | _H1="X-SYNO-TOKEN: $token" |
52a168b9 | 161 | export _H1 |
5d3bc95a | 162 | _debug2 H1 "${_H1}" |
52a168b9 | 163 | |
623d615c | 164 | # Now that we know the username & password are good, save them |
52a168b9 BH |
165 | _savedeployconf SYNO_Username "$SYNO_Username" |
166 | _savedeployconf SYNO_Password "$SYNO_Password" | |
623d615c MA |
167 | _savedeployconf SYNO_Device_Name "$SYNO_Device_Name" |
168 | _savedeployconf SYNO_Device_ID "$SYNO_Device_ID" | |
555e0de9 | 169 | |
52a168b9 | 170 | _info "Getting certificates in Synology DSM" |
cc692854 | 171 | response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1&_sid=$sid" "$_base_url/webapi/entry.cgi") |
555e0de9 | 172 | _debug3 response "$response" |
74a4a788 BH |
173 | escaped_certificate="$(printf "%s" "$SYNO_Certificate" | sed 's/\([].*^$[]\)/\\\1/g;s/"/\\\\"/g')" |
174 | _debug escaped_certificate "$escaped_certificate" | |
175 | id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p") | |
52a168b9 | 176 | _debug2 id "$id" |
555e0de9 | 177 | |
3a7c7fe4 | 178 | if [ -z "$id" ] && [ -z "${SYNO_Create:-}" ]; then |
623d615c | 179 | _err "Unable to find certificate: $SYNO_Certificate & \$SYNO_Create is not set" |
555e0de9 BH |
180 | return 1 |
181 | fi | |
182 | ||
db3f131d | 183 | # We've verified this certificate description is a thing, so save it |
74a4a788 | 184 | _savedeployconf SYNO_Certificate "$SYNO_Certificate" "base64" |
555e0de9 | 185 | |
52a168b9 | 186 | _info "Generate form POST request" |
0deea539 | 187 | nl="\0015\0012" |
79637097 | 188 | delim="--------------------------$(_utc_date | tr -d -- '-: ')" |
0deea539 | 189 | content="--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")\0012" |
190 | 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" | |
191 | 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 |
192 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id" |
193 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_Certificate}" | |
74a4a788 | 194 | if echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then |
db3f131d | 195 | _debug2 default "This is the default certificate" |
5ab9ca1c BH |
196 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}true" |
197 | else | |
db3f131d | 198 | _debug2 default "This is NOT the default certificate" |
5ab9ca1c | 199 | fi |
52a168b9 | 200 | content="$content${nl}--$delim--${nl}" |
95769de4 BH |
201 | content="$(printf "%b_" "$content")" |
202 | content="${content%_}" # protect trailing \n | |
52a168b9 BH |
203 | |
204 | _info "Upload certificate to the Synology DSM" | |
cc692854 | 205 | 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 | 206 | _debug3 response "$response" |
555e0de9 | 207 | |
1b475cf9 BH |
208 | if ! echo "$response" | grep '"error":' >/dev/null; then |
209 | if echo "$response" | grep '"restart_httpd":true' >/dev/null; then | |
623d615c | 210 | _info "Restarting HTTP services succeeded" |
555e0de9 | 211 | else |
623d615c | 212 | _info "Restarting HTTP services failed" |
555e0de9 | 213 | fi |
623d615c MA |
214 | |
215 | _logout | |
6459ccb1 | 216 | return 0 |
555e0de9 | 217 | else |
52a168b9 | 218 | _err "Unable to update certificate, error code $response" |
623d615c | 219 | _logout |
555e0de9 BH |
220 | return 1 |
221 | fi | |
222 | } | |
623d615c MA |
223 | |
224 | #################### Private functions below ################################## | |
225 | _logout() { | |
db3f131d | 226 | # Logout to not occupy a permanent session, e.g. in DSM's "Connected Users" widget |
623d615c MA |
227 | response=$(_get "$_base_url/webapi/entry.cgi?api=SYNO.API.Auth&version=$api_version&method=logout") |
228 | _debug3 response "$response" | |
229 | } |