11#! /bin/bash
2- # info: adding letsencrypt ssl cetificate for domain
3- # options: USER DOMAIN [ALIASES] [RESTART] [NOTIFY]
2+ # info: check letsencrypt domain
3+ # options: USER DOMAIN [ALIASES]
44#
5- # The function turns on SSL support for a domain. Parameter ssl_dir is a path
6- # to directory where 2 or 3 ssl files can be found. Certificate file
7- # domain.tld.crt and its key domain.tld.key are mandatory. Certificate
8- # authority domain.tld.ca file is optional. If home directory parameter
9- # (ssl_home) is not set, https domain uses public_shtml as separate
10- # documentroot directory.
5+ # The function check and validates domain with Let's Encrypt
116
127
138# ----------------------------------------------------------#
1813user=$1
1914domain=$2
2015aliases=$3
21- restart=$4
22- notify=$5
16+
17+ # LE API
18+ API=' https://acme-v02.api.letsencrypt.org'
2319
2420# Includes
2521source $HESTIA /func/main.sh
2622source $HESTIA /func/domain.sh
2723source $HESTIA /conf/hestia.conf
2824
29- # Additional argument formatting
30- format_domain_idn
25+ # encode base64
26+ encode_base64 () {
27+ cat | base64 | tr ' +/' ' -_' | tr -d ' \r\n='
28+ }
29+
30+ # Let's Encrypt v2 curl function
31+ query_le_v2 () {
32+
33+ protected=' {"nonce": "' $3 ' ",'
34+ protected=' ' $protected ' "url": "' $1 ' ",'
35+ protected=' ' $protected ' "alg": "RS256", "kid": "' $KID ' "}'
36+ content=" Content-Type: application/jose+json"
37+
38+ payload_=$( echo -n " $2 " | encode_base64)
39+ protected_=$( echo -n " $protected " | encode_base64)
40+ signature_=$( printf " %s" " $protected_ .$payload_ " | \
41+ openssl dgst -sha256 -binary -sign $USER_DATA /ssl/user.key | \
42+ encode_base64)
43+
44+ post_data=' {"protected":"' " $protected_ " ' ",'
45+ post_data=$post_data ' "payload":"' " $payload_ " ' ",'
46+ post_data=$post_data ' "signature":"' " $signature_ " ' "}'
47+
48+ curl -s -i -d " $post_data " " $1 " -H " $content "
49+ }
50+
3151
3252
3353# ----------------------------------------------------------#
3454# Verifications #
3555# ----------------------------------------------------------#
3656
37- check_args ' 2' " $# " ' USER DOMAIN [ALIASES] [RESTART] [NOTIFY] '
38- is_format_valid ' user' ' domain'
57+ check_args ' 2' " $# " ' USER DOMAIN [ALIASES]'
58+ is_format_valid ' user' ' domain' ' aliases '
3959is_system_enabled " $WEB_SYSTEM " ' WEB_SYSTEM'
40- is_system_enabled " $WEB_SSL " ' SSL_SUPPORT'
4160is_object_valid ' user' ' USER' " $user "
4261is_object_unsuspended ' user' ' USER' " $user "
4362is_object_valid ' web' ' DOMAIN' " $domain "
4463is_object_unsuspended ' web' ' DOMAIN' " $domain "
64+ get_domain_values ' web'
65+ for alias in $( echo " $aliases " | tr ' ,' ' \n' | sort -u) ; do
66+ check_alias=" $( echo $ALIAS | tr ' ,' ' \n' | grep ^$alias $) "
67+ if [ -z " $check_alias " ]; then
68+ check_result $E_NOTEXIST " domain alias $alias doesn't exist"
69+ fi
70+ done
4571
4672
4773# ----------------------------------------------------------#
4874# Action #
4975# ----------------------------------------------------------#
5076
51- # Parsing domain data
52- get_domain_values ' web'
53-
5477# Registering LetsEncrypt user account
5578$BIN /v-add-letsencrypt-user $user
5679if [ " $? " -ne 0 ]; then
6285
6386# Parsing LetsEncrypt account data
6487source $USER_DATA /ssl/le.conf
65- email=$EMAIL
66-
67- # Validating domain and aliases
68- i=1
69- for alias in $( echo $domain ,$aliases | tr ' ,' ' \n' | sort -u) ; do
70- $BIN /v-check-letsencrypt-domain $user $alias
71- if [ " $? " -ne 0 ]; then
72- touch $HESTIA /data/queue/letsencrypt.pipe
73- sed -i " / $domain /d" $HESTIA /data/queue/letsencrypt.pipe
74- send_notice " LETSENCRYPT" " $alias validation failed"
75- check_result $E_INVALID " LE domain validation" > /dev/null
88+
89+ # Checking wildcard alias
90+ if [ " $aliases " = " *.$domain " ]; then
91+ wildcard=' yes'
92+ proto=" dns-01"
93+ if [ ! -e " $HESTIA /data/users/$user /dns/$domain .conf" ]; then
94+ check_result $E_NOTEXIST " DNS domain $domain doesn't exist"
95+ fi
96+ else
97+ proto=" http-01"
98+ fi
99+
100+ # Requesting nonce / STEP 1
101+ answer=$( curl -s -I " $API /directory" )
102+ nonce=$( echo " $answer " | grep Nonce | cut -f2 -d \ | tr -d ' \r\n' )
103+ status=$( echo " $answer " | grep HTTP/1.1 | tail -n1 | cut -f 2 -d ' ' )
104+ if [[ " $status " -ne 200 ]]; then
105+ check_result $E_CONNECT " Let's Encrypt nonce request status $status "
106+ fi
107+
108+ # Placing new order / STEP 2
109+ url=" $API /acme/new-order"
110+ payload=' {"identifiers":['
111+ for identifier in $( echo $domain ,$aliases | tr ' ,' ' \n' | sort -u) ; do
112+ payload=$payload ' {"type":"dns","value":"' $identifier ' "},'
113+ done
114+ payload=$( echo " $payload " | sed " s/,$//" )
115+ payload=$payload ' ]}'
116+ answer=$( query_le_v2 " $url " " $payload " " $nonce " )
117+ nonce=$( echo " $answer " | grep Nonce | cut -f2 -d \ | tr -d ' \r\n' )
118+ authz=$( echo " $answer " | grep " acme/authz" | cut -f2 -d ' "' )
119+ finalize=$( echo " $answer " | grep ' finalize":' | cut -f4 -d ' "' )
120+ status=$( echo " $answer " | grep HTTP/1.1 | tail -n1 | cut -f2 -d ' ' )
121+ if [[ " $status " -ne 201 ]]; then
122+ check_result $E_CONNECT " Let's Encrypt new auth status $status "
123+ fi
124+
125+ # Requesting authorization token / STEP 3
126+ for auth in $authz ; do
127+ payload=' '
128+ answer=$( query_le_v2 " $auth " " $payload " " $nonce " )
129+ url=$( echo " $answer " | grep -A3 $proto | grep url | cut -f 4 -d \" )
130+ token=$( echo " $answer " | grep -A3 $proto | grep token | cut -f 4 -d \" )
131+ nonce=$( echo " $answer " | grep Nonce | cut -f2 -d \ | tr -d ' \r\n' )
132+ status=$( echo " $answer " | grep HTTP/1.1 | tail -n1 | cut -f 2 -d ' ' )
133+ if [[ " $status " -ne 200 ]]; then
134+ check_result $E_CONNECT " Let's Encrypt acme/authz bad status $status "
76135 fi
77136
78- # Checking LE limits per account
79- if [ " $i " -gt 100 ]; then
80- touch $HESTIA /data/queue/letsencrypt.pipe
81- sed -i " / $domain /d" $HESTIA /data/queue/letsencrypt.pipe
82- send_notice ' LETSENCRYPT' ' Limit of domains per account is reached'
83- check_result $E_LIMIT " LE can't sign more than 100 domains"
137+ # Accepting challenge / STEP 4
138+ if [ " $wildcard " = ' yes' ]; then
139+ record=$( printf " %s" " $token .$THUMB " | \
140+ openssl dgst -sha256 -binary | encode_base64)
141+ old_records=$( $BIN /v-list-dns-records $user $domain plain| grep ' TXT' )
142+ old_records=$( echo " $old_records " | grep _acme-challenge | cut -f 1)
143+ for old_record in $old_records ; do
144+ $BIN /v-delete-dns-record $user $domain $old_record
145+ done
146+ $BIN /v-add-dns-record $user $domain " _acme-challenge" " TXT" $record
147+ check_result $? " DNS _acme-challenge record wasn't created"
148+ else
149+ if [ " $WEB_SYSTEM " = ' nginx' ] || [ ! -z " $PROXY_SYSTEM " ]; then
150+ conf=" $HOMEDIR /$user /conf/web/nginx.$domain .conf_letsencrypt"
151+ sconf=" $HOMEDIR /$user /conf/web/snginx.$domain .conf_letsencrypt"
152+ if [ ! -e " $conf " ]; then
153+ echo ' location ~ "^/\.well-known/acme-challenge/(.*)$" {' \
154+ > $conf
155+ echo ' default_type text/plain;' >> $conf
156+ echo ' return 200 "$1.' $THUMB ' ";' >> $conf
157+ echo ' }' >> $conf
158+ fi
159+ if [ ! -e " $sconf " ]; then
160+ ln -s " $conf " " $sconf "
161+ fi
162+ $BIN /v-restart-proxy
163+ check_result $? " Proxy restart failed" > /dev/null
164+
165+ else
166+ well_known=" $HOMEDIR /$user /web/$rdomain /public_html/.well-known"
167+ acme_challenge=" $well_known /acme-challenge"
168+ mkdir -p $acme_challenge
169+ echo " $token .$THUMB " > $acme_challenge /$token
170+ chown -R $user :$user $well_known
171+ fi
172+ $BIN /v-restart-web
173+ check_result $? " Web restart failed" > /dev/null
174+ fi
175+
176+ # Requesting ACME validation / STEP 5
177+ validation_check=$( echo " $answer " | grep ' "valid"' )
178+ if [[ ! -z " $validation_check " ]]; then
179+ validation=' valid'
180+ else
181+ validation=' pending'
182+ fi
183+
184+ # Doing pol check on status
185+ i=1
186+ while [ " $validation " = ' pending' ]; do
187+ payload=' {}'
188+ answer=$( query_le_v2 " $url " " $payload " " $nonce " )
189+ validation=$( echo " $answer " | grep -A1 $proto | tail -n1| cut -f4 -d \" )
190+ nonce=$( echo " $answer " | grep Nonce | cut -f2 -d \ | tr -d ' \r\n' )
191+ status=$( echo " $answer " | grep HTTP/1.1 | tail -n1 | cut -f 2 -d ' ' )
192+ if [[ " $status " -ne 200 ]]; then
193+ check_result $E_CONNECT " Let's Encrypt vvalidation status $status "
194+ fi
195+
196+ i=$(( i + 1 ))
197+ if [ " $i " -gt 10 ]; then
198+ check_result $E_CONNECT " Let's Encrypt domain validation timeout"
199+ fi
200+ sleep 1
201+ done
202+ if [ " $validation " = ' invalid' ]; then
203+ check_result $E_CONNECT " Let's Encrypt domain verification failed"
84204 fi
85- i=$(( i++ ))
86205done
87206
88- # Generating CSR
89- ssl_dir=$( $BIN /v-generate-ssl-cert " $domain " " $email " " US" " California" \
207+
208+ # Generating new ssl certificate
209+ ssl_dir=$( $BIN /v-generate-ssl-cert " $domain " " info@$domain " " US" " California" \
90210 " San Francisco" " Hestia" " IT" " $aliases " | tail -n1 | awk ' {print $2}' )
91211
92- # Signing CSR
93- crt=$( $BIN /v-sign-letsencrypt-csr $user $domain $ssl_dir )
94- if [ " $? " -ne 0 ]; then
95- touch $HESTIA /data/queue/letsencrypt.pipe
96- sed -i " / $domain /d" $HESTIA /data/queue/letsencrypt.pipe
97- send_notice " LETSENCRYPT" " $alias validation failed"
98- check_result " $E_INVALID " " LE $domain validation"
99- fi
100- echo " $crt " > $ssl_dir /$domain .crt
101-
102- # Dowloading CA certificate
103- le_certs=' https://letsencrypt.org/certs'
104- x1=' lets-encrypt-x1-cross-signed.pem.txt'
105- x3=' lets-encrypt-x3-cross-signed.pem.txt'
106- issuer=$( openssl x509 -text -in $ssl_dir /$domain .crt | grep " Issuer:" )
107- if [ -z " $( echo $issuer | grep X3) " ]; then
108- curl -s $le_certs /$x1 > $ssl_dir /$domain .ca
109- else
110- curl -s $le_certs /$x3 > $ssl_dir /$domain .ca
212+ # Sedning CSR to finalize order / STEP 6
213+ csr=$( openssl req -in $ssl_dir /$domain .csr -outform DER | encode_base64)
214+ payload=' {"csr":"' $csr ' "}'
215+ answer=$( query_le_v2 " $finalize " " $payload " " $nonce " )
216+ nonce=$( echo " $answer " | grep Nonce | cut -f2 -d \ | tr -d ' \r\n' )
217+ status=$( echo " $answer " | grep HTTP/1.1 | tail -n1 | cut -f 2 -d ' ' )
218+ certificate=$( echo " $answer " | grep ' certificate":' | cut -f4 -d ' "' )
219+ if [[ " $status " -ne 200 ]]; then
220+ check_result $E_CONNECT " Let's Encrypt finalize bad status $status "
111221fi
112222
223+ # Downloading signed certificate / STEP 7
224+ curl -s " $certificate " -o $ssl_dir /$domain .pem
225+
226+ # Splitting up downloaded pem
227+ crt_end=$( grep -n END $ssl_dir /$domain .pem | head -n1 | cut -f1 -d:)
228+ head -n $crt_end $ssl_dir /$domain .pem > $ssl_dir /$domain .crt
229+
230+ pem_lines=$( wc -l $ssl_dir /$domain .pem | cut -f 1 -d ' ' )
231+ ca_end=$( grep -n " BEGIN" $ssl_dir /$domain .pem | tail -n1 | cut -f 1 -d :)
232+ ca_end=$(( pem_lines - crt_end + 1 ))
233+ tail -n $ca_end $ssl_dir /$domain .pem > $ssl_dir /$domain .ca
234+
113235# Adding SSL
114236ssl_home=$( search_objects ' web' ' LETSENCRYPT' ' yes' ' SSL_HOME' )
115237$BIN /v-delete-web-domain-ssl $user $domain > /dev/null 2>&1
@@ -137,23 +259,18 @@ update_object_value 'web' 'DOMAIN' "$domain" '$LETSENCRYPT' 'yes'
137259
138260
139261# ----------------------------------------------------------#
140- # Hestia #
262+ # Hestia #
141263# ----------------------------------------------------------#
142264
143- # Restarting web
144- $BIN /v-restart-web $restart
145- if [ " $? " -ne 0 ]; then
146- send_notice ' LETSENCRYPT' " web server needs to be restarted manually"
147- fi
265+ # Deleteing task from queue
266+ touch $HESTIA /data/queue/letsencrypt.pipe
267+ sed -i " / $domain /d" $HESTIA /data/queue/letsencrypt.pipe
148268
149269# Notifying user
150270send_notice ' LETSENCRYPT' " $domain SSL has been installed successfully"
151271
152- # Deleteing task from queue
153- touch $HESTIA /data/queue/letsencrypt.pipe
154- sed -i " / $domain /d" $HESTIA /data/queue/letsencrypt.pipe
155272
156273# Logging
157274log_event " $OK " " $ARGUMENTS "
158275
159- exit
276+ exit
0 commit comments