Certificado SSL Lets Encrypt - Apache + Funtoo

De Wiki Hackstore
Lets-encrypt-logo.png

Crie um script para automatizar a geração de certificados Lets Encrypt, todo último dia do mês.

Requisitos

emerge app-crypt/acme-tiny www-servers/apache dev-libs/openssl
mkdir -p /var/lib/letsencrypt
mkdir -p /etc/ssl/letsencrypt
cd /var/lib/letsencrypt
openssl genrsa 4096 > account.key
openssl genrsa 4096 > domain.key


Adicione as seguintes linhas ao /etc/apache2/vhosts.d/00_default_vhost.conf

alias /.well-known/acme-challenge/ /var/www/localhost/htdocs/.well-known/acme-challenge/
<Directory /var/www/localhost/htdocs/.well-known/acme-challenge/>
     AllowOverride None
     Require all granted
</Directory>



Configuração

Script acme-tiny

Crie o script /hackstore/cert-letsencrypt.sh:

mkdir /hackstore ; vi /hackstore/cert-letsencrypt.sh

Com o conteúdo abaixo:

#!/bin/bash

########################################################
# Adicione ao /etc/crontab:
# 00 23 28-31 * * root /hackstore/cert-letsencrypt.sh
########################################################

VERSION="3.5"

########################################################

DATECERT="$(date +'%Y%m%d_%H%M%S')"
CERTDIR="/etc/ssl/letsencrypt"
CERTLOG="/var/log/cert-letsencrypt.log"
ERRORLOG="/var/log/cert-letsencrypt-error.log"
REPORTLOG="/var/log/cert-letsencrypt-report-${DATECERT}.log"
ERRORDNS="0"
ERROR="0"
LINE="################################################"

# Email
MAILFROM="cloud@hackstore.com.br"
MAILTO="raphaelbastos@hackstore.com.br"
MAILHOST="cloud.hackstore.com.br"

########################################################
# Lista de certificados
# OBS: inicie com 0 e siga a ordem numerica

# hackstore.com.br
DOMAIN[0]="hackstore.com.br"
ALTNAMEDOMAIN[0]="DNS:wiki.hackstore.com.br,DNS:hackstore.com.br,DNS:www.hackstore.com.br"

# ip.hackstore.com.br
DOMAIN[1]="ip.hackstore.com.br"
ALTNAMEDOMAIN[1]="DNS:ip.hackstore.com.br"

########################################################

echo -e "Report da renovação dos certificados lets encrypt - $(date)\n" > ${REPORTLOG}

if [ ${#DOMAIN[*]} -eq 0 ]; then
	echo "nenhum vhost configurado no script. Configure as variaveis DOMAIN[0], DOMAIN[1], DOMAIN[2]..."
	exit 1;
fi

# Executa todo último dia do mês
FLAGDAY="$(date +%d -d tomorrow)"
if [ "${FLAGDAY}" = "01" ]; then

	# Rotate log
	mv "${CERTLOG}" "${CERTLOG}-${DATECERT}"

	if [ ! -d "${CERTDIR}" ]; then
		mkdir -p "${CERTDIR}"
	fi

	# Backup files
	cp -rp "/var/lib/letsencrypt" "/var/lib/letsencrypt-${DATECERT}"
	cp -rp "/etc/ssl/letsencrypt" "/etc/ssl/letsencrypt-${DATECERT}"

	# Download chain.pem
	wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > /etc/ssl/letsencrypt/chain.pem.new 2> ${ERRORLOG}
	if [ $? -ne 0 ]; then
		cat ${ERRORLOG} |mail ${MAILTO} -r ${MAILFROM} -s "Erro ao efetuar o download do lets-encrypt-x3-cross-signed.pem - $(date)"
	else
		mv /etc/ssl/letsencrypt/chain.pem.new /etc/ssl/letsencrypt/chain.pem
	fi

##############################################################################################################

	# Renew certs
	###########################

	# inicio do log
	{

	o=0
	# inicio do loop
	while [ $o -lt ${#DOMAIN[*]} ]; do

		########################################
		# checagem de validade do dominio
		whois ${DOMAIN[$o]}
		if [ $? -ne 0 ]; then
			ERRORDNS="1"
		fi
		########################################
		# Checa status do dominio
		STATUS_DOMAIN=$(whois ${DOMAIN[$o]} |grep -w status|awk '{print $2}')
		if [ "${STATUS_DOMAIN}" = "published" ]; then
			if [ ! -d /etc/ssl/letsencrypt/${DOMAIN[$o]} ]; then
				mkdir -p /etc/ssl/letsencrypt/${DOMAIN[$o]} 2> ${ERRORLOG}
				if [ $? -ne 0 ]; then
					cat ${ERRORLOG} |mail ${MAILTO} -r ${MAILFROM} -s "Erro ao criar diretório /etc/ssl/letsencrypt/${DOMAIN[$o]} - $(date)"
				fi
			fi

			########################################
			# Generation CSR using PRIVATE KEY
			openssl req -new -sha256 -key /var/lib/letsencrypt/domain.key -subj "/C=US/O=Acme/CN=${DOMAIN[$o]}" \
                        -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=${ALTNAMEDOMAIN[$o]}")) \
                        -out /etc/ssl/letsencrypt/${DOMAIN[$o]}/domain.csr.new 2> ${ERRORLOG}
			if [ $? -ne 0 ]; then
				cat ${ERRORLOG} |mail ${MAILTO} -r ${MAILFROM} -s "Erro ao gerar CSR do certificado para ${DOMAIN[$o]} via openssl - $(date)"
				ERROR="1"
				ERRORDETAIL="$(cat ${ERRORLOG})"
			else
				mv /etc/ssl/letsencrypt/${DOMAIN[$o]}/domain.csr.new /etc/ssl/letsencrypt/${DOMAIN[$o]}/domain.csr 2> ${ERRORLOG}
				if [ $? -ne 0 ]; then
					cat ${ERRORLOG} |mail ${MAILTO} -r ${MAILFROM} -s "Erro ao criar arquivo /etc/ssl/letsencrypt/${DOMAIN[$o]}/domain.csr - $(date)"
					ERROR="2"
					ERRORDETAIL="$(cat ${ERRORLOG})"
				fi
			fi

			########################################
			# Assinatura via acme-tiny
			acme-tiny --account-key /var/lib/letsencrypt/account.key --csr /etc/ssl/letsencrypt/${DOMAIN[$o]}/domain.csr \
                        --acme-dir /var/www/localhost/htdocs/.well-known/acme-challenge/ > /etc/ssl/letsencrypt/${DOMAIN[$o]}/signed.crt.new 2> ${ERRORLOG}
			if [ $? -ne 0 ]; then
				cat ${ERRORLOG} |mail ${MAILTO} -r ${MAILFROM} -s "Erro ao assinar certificado via acme-tiny para ${DOMAIN[$o]} - $(date)"
				ERROR="3"
				ERRORDETAIL="$(cat ${ERRORLOG}|grep detail)"
			else
				mv /etc/ssl/letsencrypt/${DOMAIN[$o]}/signed.crt.new /etc/ssl/letsencrypt/${DOMAIN[$o]}/signed.crt 2> ${ERRORLOG}
				if [ $? -ne 0 ]; then
					cat ${ERRORLOG} |mail ${MAILTO} -r ${MAILFROM} -s "Erro ao criar arquivo /etc/ssl/letsencrypt/${DOMAIN[$o]}/signed.crt - $(date)"
					ERROR="4"
					ERRORDETAIL="$(cat ${ERRORLOG})"
				fi
			fi

			########################################
			# Arquivo de cadeia do certificado
			cat /etc/ssl/letsencrypt/${DOMAIN[$o]}/signed.crt /etc/ssl/letsencrypt/chain.pem > /etc/ssl/letsencrypt/${DOMAIN[$o]}/fullchain.pem 2> ${ERRORLOG}
			if [ $? -ne 0 ]; then
				cat ${ERRORLOG} |mail ${MAILTO} -r ${MAILFROM} -s "Erro ao criar arquivo /etc/ssl/letsencrypt/${DOMAIN[$o]}/fullchain.pem - $(date)"
				ERROR="5"
				ERRORDETAIL="$(cat ${ERRORLOG})"
			fi

			########################################
			# Carrega novo certificado
			/etc/init.d/apache2 reload

			########################################
			# Validade do certificado
			GERADO="$(echo | openssl s_client -servername ${DOMAIN[$o]} -connect ${DOMAIN[$o]}:443 2>/dev/null | openssl x509 -noout -dates|grep notBefore|cut -d= -f2)"
			VALIDADE="$(echo | openssl s_client -servername ${DOMAIN[$o]} -connect ${DOMAIN[$o]}:443 2>/dev/null | openssl x509 -noout -dates|grep notAfter|cut -d= -f2)"

			########################################
			# Report por email
			if [ ${ERROR} = "1" ];then
				echo -e "\n${LINE}\n${DOMAIN[$o]}: ERROR OPENSSL\n${ERRORDETAIL}\n\nDNS status: ${STATUS_DOMAIN}\nData assinatura:${GERADO}\nValidade: ${VALIDADE}" >> ${REPORTLOG}
			elif [ ${ERROR} = "2" ];then
				echo -e "\n${LINE}\n${DOMAIN[$o]}: ERROR OPENSSL\n${ERRORDETAIL}\n\nDNS status: ${STATUS_DOMAIN}\nData assinatura:${GERADO}\nValidade: ${VALIDADE}" >> ${REPORTLOG}
			elif [ ${ERROR} = "3" ];then
				echo -e "\n${LINE}\n${DOMAIN[$o]}: ERROR ACME-TINY\n${ERRORDETAIL}\n\nDNS status: ${STATUS_DOMAIN}\nData assinatura:${GERADO}\nValidade: ${VALIDADE}" >> ${REPORTLOG}
			elif [ ${ERROR} = "4" ];then
				echo -e "\n${LINE}\n${DOMAIN[$o]}: ERROR ACME-TINY\n${ERRORDETAIL}\n\nDNS status: ${STATUS_DOMAIN}\nData assinatura:${GERADO}\nValidade: ${VALIDADE}" >> ${REPORTLOG}
			elif [ ${ERROR} = "5" ];then
				echo -e "\n${LINE}\n${DOMAIN[$o]}: ERROR FULLCHAIN\n${ERRORDETAIL}\n\nDNS status: ${STATUS_DOMAIN}\nData assinatura:${GERADO}\nValidade: ${VALIDADE}" >> ${REPORTLOG}
			else
				echo -e "\n${LINE}\n${DOMAIN[$o]}: SUCCESS\n\nDNS status: ${STATUS_DOMAIN}\nData assinatura:${GERADO}\nValidade: ${VALIDADE}" >> ${REPORTLOG}
			fi

		###############################################################################################################################
		# se o status do DNS for diferente de published, gera report de erro
		else
			if [ ${ERRORDNS} = "1" ]; then
				echo -e "\n${LINE}\n${DOMAIN[$o]}: DNS ERROR\n$(whois ${DOMAIN[$o]})" >> ${REPORTLOG}
			else
				STATUS_DOMAIN_ERROR=$(whois ${DOMAIN[$o]}|grep -w status|awk '{print $2}')
				echo -e "\n${LINE}\n${DOMAIN[$o]}: DNS ERROR\nDNS status: ${STATUS_DOMAIN_ERROR}" >> ${REPORTLOG}
			fi
		fi

		# Limpa as variaveis
		ERRORDNS="0"
		ERROR="0"

		# Adiciona +1 ao loop
		let o=$o+1

	# Fim do loop
	done
##############################################################################################################

# end of renew
###########################

	# fim do log
	} > ${CERTLOG} 2>&1

echo -e "\n#################################################################################################\nFim do report. - $(date)" >> ${REPORTLOG}

# Envia report completo por email
cat ${REPORTLOG} | mutt -a "${CERTLOG}" -s "Renovação Letsencrypt - ${MAILHOST} $(date)" -- ${MAILTO}

fi

# end of script


Apache

ssl_hackstore.com.br.conf

# SSL Default Virtual Host
#
<IfDefine DEFAULT_VHOST>
    <VirtualHost *:443>
        ServerName hackstore.com.br
        ServerAlias wiki.hackstore.com.br www.hackstore.com.br

        UseCanonicalName Off

        # SSL Compression (CRIME attack)
        SSLCompression off

        # Headers
        Header always set X-Frame-Options "SAMEORIGIN"
        Header always set Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"
        Header always set X-XSS-Protection "1; mode=block"
        Header always set X-Content-Type-Options "nosniff"
        Header always set Referrer-Policy "strict-origin"

        SSLEngine on



        SSLCertificateKeyFile /etc/ssl/letsencrypt/area31.net.br.key
        SSLCertificateFile /etc/ssl/letsencrypt/hackstore.com.br/signed.crt
        SSLCACertificatePath /etc/ssl/letsencrypt/hackstore.com.br/
        SSLCertificateChainFile /etc/ssl/letsencrypt/chain.pem

        SSLProtocol TLSv1.2
        SSLHonorCipherOrder on

        SSLCipherSuite "ECDHE-RSA-AES128-GCM-SHA256 \
            ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 \
            ECDHE-ECDSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 \
            DHE-DSS-AES128-GCM-SHA256 kEDH+AESGCM ECDHE-RSA-AES128-SHA256 \
            ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA ECDHE-ECDSA-AES128-SHA \
            ECDHE-RSA-AES256-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES256-SHA \
            ECDHE-ECDSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA \
            DHE-DSS-AES128-SHA256 DHE-RSA-AES256-SHA256 DHE-DSS-AES256-SHA \
            DHE-RSA-AES256-SHA ECDHE-RSA-DES-CBC3-SHA ECDHE-ECDSA-DES-CBC3-SHA \
            AES128-GCM-SHA256 AES256-GCM-SHA384 AES128-SHA256 AES256-SHA256 \
            AES128-SHA AES256-SHA AES CAMELLIA DES-CBC3-SHA !aNULL !eNULL !EXPORT \
            !DES !RC4 !MD5 !PSK !aECDH !EDH-DSS-DES-CBC3-SHA !EDH-RSA-DES-CBC3-SHA \
            !KRB5-DES-CBC3-SHA"

        ScriptAlias /cgi-bin/ /var/www/wiki.hackstore.com.br/cgi-bin

        DocumentRoot "/var/www/wiki.hackstore.com.br/htdocs"

        <Directory "/var/www/wiki.hackstore.com.br/htdocs">
            Options SymLinksIfOwnerMatch
            AllowOverride All
            Require all granted
        </Directory>

        <Directory "/var/www/wiki.hackstore.com.br/htdocs/wiki/images">
            AllowOverride None
            AddType text/plain .html .htm .shtml .php .phtml .php5
            php_admin_flag engine off
        </Directory>

        CustomLog /var/log/apache2/ssl_hackstore.com.br-access_log combined
        ErrorLog /var/log/apache2/ssl_hackstore.com.br-error_log

    </VirtualHost>
</IfDefine>


ssl_ip.hackstore.com.br.conf

# SSL Default Virtual Host
#
<IfDefine DEFAULT_VHOST>
    <VirtualHost *:443>
        ServerName ip.hackstore.com.br

        UseCanonicalName Off

        # SSL Compression (CRIME attack)
        SSLCompression off

        # Headers
        Header always set X-Frame-Options "SAMEORIGIN"
        Header always set Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"
        Header always set X-XSS-Protection "1; mode=block"
        Header always set X-Content-Type-Options "nosniff"
        Header always set Referrer-Policy "strict-origin"

        SSLEngine on

         UseCanonicalName Off

        SSLCertificateKeyFile /etc/ssl/letsencrypt/area31.net.br.key
        SSLCertificateFile /etc/ssl/letsencrypt/ip.hackstore.com.br/signed.crt
        SSLCACertificatePath /etc/ssl/letsencrypt/ip.hackstore.com.br/
        SSLCertificateChainFile /etc/ssl/letsencrypt/chain.pem

        SSLProtocol TLSv1.2
        SSLHonorCipherOrder on

        SSLCipherSuite "ECDHE-RSA-AES128-GCM-SHA256 \
            ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 \
            ECDHE-ECDSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 \
            DHE-DSS-AES128-GCM-SHA256 kEDH+AESGCM ECDHE-RSA-AES128-SHA256 \
            ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA ECDHE-ECDSA-AES128-SHA \
            ECDHE-RSA-AES256-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES256-SHA \
            ECDHE-ECDSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA \
            DHE-DSS-AES128-SHA256 DHE-RSA-AES256-SHA256 DHE-DSS-AES256-SHA \
            DHE-RSA-AES256-SHA ECDHE-RSA-DES-CBC3-SHA ECDHE-ECDSA-DES-CBC3-SHA \
            AES128-GCM-SHA256 AES256-GCM-SHA384 AES128-SHA256 AES256-SHA256 \
            AES128-SHA AES256-SHA AES CAMELLIA DES-CBC3-SHA !aNULL !eNULL !EXPORT \
            !DES !RC4 !MD5 !PSK !aECDH !EDH-DSS-DES-CBC3-SHA !EDH-RSA-DES-CBC3-SHA \
            !KRB5-DES-CBC3-SHA"

        ScriptAlias /cgi-bin/ /var/www/ip.hackstore.com.br/cgi-bin

        DocumentRoot "/var/www/ip.hackstore.com.br/htdocs"

        <Directory "/var/www/ip.hackstore.com.br/htdocs">
            Options SymLinksIfOwnerMatch
            AllowOverride All
            Require all granted
        </Directory>

        <Directory "/var/www/ip.hackstore.com.br/htdocs/wiki/images">
            AllowOverride None
            AddType text/plain .html .htm .shtml .php .phtml .php5
            php_admin_flag engine off
        </Directory>

        CustomLog /var/log/apache2/ssl_ip.hackstore.com.br-access_log combined
        ErrorLog /var/log/apache2/ssl_ip.hackstore.com.br-error_log

    </VirtualHost>
</IfDefine>


Agendamento automático no crontab

Adicione ao /etc/crontab para que seja executado no último dia de todo mês:

00 23 28-31 * * root /hackstore/cert-letsencrypt.sh


Redirecionamento de 80 pra 443 (opcional)

Crie um arquivo dentro de /etc/apache2/vhosts.d com nome hackstore.com.br.conf

<IfDefine DEFAULT_VHOST>



<VirtualHost *:80>
        ServerName hackstore.com.br
        ServerAlias wiki.hackstore.com.br www.hackstore.com.br

        DocumentRoot "/var/www/hackstore.com.br/htdocs/web"
        UseCanonicalName Off
                RewriteEngine On
                RewriteCond %{HTTPS} off
                RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

        CustomLog /var/log/apache2/hackstore.com.br-access_log combined
        ErrorLog /var/log/apache2/hackstore.com.br-error_log
        ScriptAlias /cgi-bin/ /var/www/hackstore.com.br/cgi-bin
 <Directory "/var/www/hackstore.com.br/htdocs/web">
               #Options Indexes +FollowSymLinks +SymLinksIfOwnerMatch
               #AllowOverride All
               #Order allow,deny
               #Allow from all

               Options SymLinksIfOwnerMatch
               AllowOverride All
               #Order allow,deny
               #Allow from all
               Require all granted
       </Directory>
</VirtualHost>

</IfDefine>


Entrada CAA no DNS (opcional)

É recomendável a criação de uma entrada do tipo CAA no DNS do domínio. Exemplo:

0 iodef "mailto:raphaelbastos@hackstore.com.br"
0 issue "letsencrypt.org"

Para conferir seu domínio:

https://www.ssllabs.com/ssltest/analyze.html?d=area31.net.br

Caa.png Caa2.png Caa-tls.png