Блог    Теги    RSS    Файлопомойка Мой Github

Блог №200 по адресу 0708:07C7 / https

1 февраля 2017, 15:34
В связи с наметившейся тенденцией всяческого ущемления (понижение позиции в выдаче поисковиков, предупреждения о небезопасности в браузерах) сайтов, доступных только по http, а так же по причине необходимости повышения безопасности передаваемых данных, передо мной возникла задача внедрения https с минимальными материально-техническими затратами на внедрение и сопровождение.

Итак, возможны несколько подходов при внедрении https.
1. Использование самоподписанных сертификатов. Этот подход хорошо подходит для небольших частных сайтов, различных админок и подобной мелочи.
Плюсы такого подхода:
+ Ни от кого не зависим - сертификат можно самостоятельно сделать на любой домен, на любой срок. Обычный центр сертификации по очевидным причинам не станет подписывать сертификат для внутренних доменов (*.lan и т.п.).
+ Всё довольно просто - получить самоподписанный сертификат можно буквально в одну команду.
Минусы:
- При первом посещении сайта пользователю будет выдано предупреждение о недоверенном сертификате и необходимо будет добавить сертификат в список доверенных.
Для некоторых утилит часто неясно как или вовсе невозможно это сделать.

howto:
openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365


2. Использование собственного центра сертификации. Оптимальный вариант для внутренних ресурсов организаций. К плюсам и минусам подхода №1 добавляется:
+ Опять же ни от кого не зависим - сертификат можно самостоятельно сделать на любой домен, на любой срок.
+ Централизованное управление выданными/отозванными сертификатами. Как бонусом - удобно мониторить истекающие сертификаты.
+ Для доверия к любому внутреннему ресурсу достаточно добавить всего один сертификат на всех клиентах. При системы центрального управления конфигурациями (active directory, ansible и т.п.) сделать это крайне просто.
- При компрометации сертификата центра сертификации автоматически компрометируются все серверы и клиенты, на которых он установлен. Т.к. x509 не предусматривает ограничений на выдаваемые центром сертификации сертификаты, то компрометируются в том числе и любые https соединения к любым серверам. Единственное что остаётся в безопасности - клиенты, использующие привязку к сертификату (certificate pinning).
- Собственный центр сертификации относительно сложно настроить. Нельзя просто скопировать первый рецепт с serverfault - нужно понимать что и зачем делать.

howto:
Не существует быстрого и универсального пути, но для себя я написал небольшой скрипт, обладающий минимальным функционалам по созданию ЦС, созданию и подписи ключей и сертификатов. Скрипт далеко не идеальный, но длительное время он служил мне верой и правдой.
#!/bin/bash -xe

export OPENSSL_CONF=cnf

a="$1"
shift || true
n="$1"
o="$2"

cd "`dirname $0`"
umask 0007

case "$a" in
gen)
test -n "$n"
openssl genrsa 2048 >private/$n

cp "$OPENSSL_CONF"{,.tmp}
export OPENSSL_CONF="$OPENSSL_CONF.tmp"
echo '[ alt_names ]' >> "$OPENSSL_CONF"
cnt=1
while (( "$#" )); do
echo "DNS.$cnt = $1" >> "$OPENSSL_CONF"
(( cnt++ ))
shift
done

openssl req -new -key private/$n -subj "/C=RU/ST=Belgorod/O=IVTBSU/OU=CA/CN=$n" >csr.$n
openssl req -text -noout <csr.$n
openssl ca -batch -notext -extensions server -in csr.$n >cert/$n
rm csr.$n
;;
genca)
mkdir ca cert private dh || true
openssl genrsa 4096 >ca/key
openssl req -new -key ca/key -subj "/C=RU/ST=Belgorod/O=IVTBSU/OU=CA/CN=ivt.su" >ca/csr
openssl x509 -req -days 10000 -in ca/csr -signkey ca/key >ca/crt
rm ca/csr
echo 100001 >ca/srl
echo 100001 >ca/crlnum
touch ca/index
openssl ca -gencrl >ca/crl
;;
gendh)
test -r cert/$n
openssl dhparam -in cert/$n 1024 >dh/$n
;;
revoke)
test -r cert/$n
openssl ca -revoke cert/$n
openssl ca -gencrl >ca/crl
rm cert/$n private/$n
;;
public)
openssl rsa -in cert/$n -pubout
;;
info)
openssl x509 -in cert/$n -text
;;
chown)
test -n "$o"
for i in dh cert private; do
test -r "$i/$n" || continue
chmod 0400 "$i/$n"
chown $o:wheel "$i/$n"
done
;;
*)
echo "Usage: $0 "
exit 1
;;
esac

Так же понадобится файл конфигурации openssl (cnf):
[ ca ]
default_ca = myca

[ crl_ext ]
# issuerAltName=issuer:copy #this would copy the issuer name to altname
authorityKeyIdentifier = keyid:always

[ myca ]
dir = ./ca
new_certs_dir = $dir
unique_subject = no
certificate = $dir/crt
database = $dir/index
private_key = $dir/key
serial = $dir/srl
default_days = 730
default_md = sha256
policy = myca_policy
x509_extensions = myca_extensions
crlnumber = $dir/crlnum
default_crl_days = 730

[ myca_policy ]
commonName = supplied
stateOrProvinceName = supplied
countryName = optional
emailAddress = optional
organizationName = supplied
organizationalUnitName = optional

[ myca_extensions ]
basicConstraints = CA:false
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
keyUsage = digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
crlDistributionPoints = URI:http://CHANGE.ME/root.crl
nsComment = "OpenSSL Generated Certificate"
subjectAltName = @alt_names


[ req ]
default_md = sha256
distinguished_name = req_distinguished_name
#attributes = req_attributes
x509_extensions = v3_ca
req_extensions = v3_req
[ v3_req ]
subjectAltName = @alt_names
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = CA:true

[ req_distinguished_name ]
countryName = Country?
countryName_default = RU
stateOrProvinceName = State?
stateOrProvinceName_default = Belgorod obl.
localityName = City?
localityName_default = Belgorod
0.organizationName = Organization?
0.organizationName_default = Home
commonName = Domain?
commonName_default = change.me
emailAddress = Email?
emailAddress_default = ca@change.me

[ server ]
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
extendedKeyUsage = serverAuth
keyUsage = digitalSignature, keyEncipherment
subjectAltName = @alt_names

[ client ]
basicConstraints = CA:FALSE
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
extendedKeyUsage = clientAuth
keyUsage = digitalSignature


3. Использование публичного центра сертификации. Фактически единственный приемлемый вариант для публичных сайтов.
+ Все клиенты по умолчанию будет доверять вашему сертификату.
- Нужно проходить процедуру верификации для подтверждения владения доменом, при чём домен, очевидно, должен быть публичным.
- Центр сертификации может накладывать собственные ограничения на параметры сертификата. Например, Let's Encrypt выдаёт сертификаты не более чем на ~3 месяца и не допускает использования widcard в доменном имени.
- Большинство центров сертификации хотят денег.

howto:
Я буду использовать let's encrypt (LE) т.к.
1. Он бесплатный;
2. Процедура получения и обновления сертификатов автоматизирована.
Всю грязную работу будет выполнять утилита acme-tiny:
 pkg install acme-tiny

Вряд ли кому-то доставит удовольствие поддерживать сертификаты для нескольких десятков доменов, особенно с учётом срока годности сертификатов LE (~3 месяца). Поэтому я набросал небольшой скрипт. Он автоматически создаёт ключи и сертификаты для указанных доменов и, при близости срока их истекания, проводит обновление.
Предварительная подготовка:
# mkdir -p /var/www/le
# mkdir /etc/ssl/le
# cd /etc/ssl/le
# openssl genrsa 4096 >LE.key

Собственно, сам скрипт:
#!/bin/sh -e

cd "$(dirname $0)"


isok() {
name="$1"; domains="$4"
test -r "$name.key" || return 1
test -r "$name.crt" || return 1
openssl x509 -in "$name.crt" -checkend 2592000 -noout || return 1
f1="`mktemp`"; echo "$domains" |tr : '\n' |sort >"$f1"
f2="`mktemp`"; openssl x509 -in "$name.crt" -text |egrep -m1 '^ +DNS:' |sed -e 's/ *DNS://g' |tr , '\n' |sort >"$f2"
set +e
diff -q "$f1" "$f2" >/dev/null
ret=$?
set -e
rm "$f1" "$f2"
test "$ret" = 0 || return 1
return 0
}

upd() {
name="$1"; group="$2"; service="$3"; domains="$4"
echo "Generating $name..." >&2
cnff="`mktemp`"
test -r "$name.key" || openssl genrsa 4096 >"$name.key"
cat /etc/ssl/openssl.cnf >"$cnff"
echo "$domains" | awk -vRS=: 'BEGIN{printf "[SAN]\nsubjectAltName="}notfirst==1{printf ","}{printf "DNS:"$0; notfirst=1} ' >>"$cnff"
openssl req -new -sha256 -key $name.key -subj "/" -reqexts SAN -config "$cnff" > "$name.csr"
rm "$cnff"
acme_tiny --account-key LE.key --csr "$name.csr" --acme-dir /var/www/le/ > "$name.crt"
rm "$name.csr"
curl -s https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem >> "$name.crt"
cat "$name.crt" "$name.key" >"$name.pem"
chgrp "$group" "$name.key" "$name.crt" "$name.pem"
chmod o-rwx "$name.key" "$name.crt" "$name.pem"
test "$service" = '-' || service "$service" reload
}

cat << EOF |grep -v -e^# -e^$ |while read i; do isok $i || upd $i; done

# name group service dns1.example.com:dns2.example.com
web www nginx err200.net:www.err200.net:ftp.err200.net
imap dovecot dovecot imap.err200.net:pop3.err200.net:pop.err200.net
irc ircd inspircd irc.err200.net
EOF


Добавляем в cron:
@monthly /etc/ssl/le/upd.sh


Самое время настроить веб-сервер. Я сделаю это на примере nginx. Let's encrypt верифицирует домен путём загрузки через http файла со случайно сгенерированным именем, нужно это учесть.
Создаём /etc/nginx/lessl.conf:
listen 443 ssl http2;

add_header Strict-Transport-Security "max-age=31536000";

location ^~ /.well-known/acme-challenge/ {
alias /var/www/le/;
try_files $uri =404;
auth_basic off;
}


Теперь в конфиг нужных server'ов просто пишем
include lessl.conf;

... делаем service nginx reload и запускаем скрипт. Если всё сделано правильно - скрипт отрапортует "Certificate signed!" столько раз, сколько в нём указано сертификатов.
Всё, можно прописывать в server'ах:
ssl_certificate /etc/ssl/le/web.crt;
ssl_certificate_key /etc/ssl/le/web.key;

... и сайт должен заработать по https.

При использовании реверс прокси может возникнуть проблема редиректов с https на http внутри сайта. Решается она очень просто:
proxy_redirect default;
proxy_redirect http:// $scheme://;

Или для haproxy:
http-response replace-value Location http://(.*) https://\1 if { dst_port 443 }


На этом всё.
Теги: #nix   #web   #https  

Комментарии RSS

Ваше имя:

E-mail (будет скрыт):

Текст: