У меня есть приложение на Go, в котором в одном из потоков работает простой HTTP сервер. К этому серверу обращаются по HTTPS. Запрос приходит на Pound - HTTP/HTTPS reverse-proxy and load-balancer и перенаправляется в приложение. TLS сертификат изготавливается и обновляется с помощью Let's Encrypt.

Простая и привычная схема. Правда, чаще в этой схеме бывает Nginx, но в этой статье мы не будем рассуждать, почему Pound, а не Nginx. Все очень хорошо, но меня последнее время начинает раздражать, когда к простому и понятному коду на Go нужно прикрутить небольшого динозаврика с пять-шестью скриптами на Ansible, закатать все это в деплой и радоваться тому, как это все славно улеглось в небольшой виртуалке.

HTTP на Go + Pound или Nginx + TLS with Let's Encrypt
HTTP на Go + Pound или Nginx + TLS with Let's Encrypt

Я где-то там, в своем начале длительное время писал на Assembler-е. Размер откомпилированной программы больше одного Кб считался огромным. Видимо с тех пор во мне застряло желание экономить ресурсы компьютера, несмотря на то, что разработчики железа предоставляют их все больше и больше. Так же я ни как не могу привыкнуть к этим жутким папкам под названием ‘node_modules’ и всегда хочу стереть их содержимое.

И я задумался...
Задумался над тем, как можно сократить количество участников в задаче «HTTP-сервер с сертификатом Let's Encrypt на Go». Год думал. Шучу. Мое приложение с динозавриками замечательно проработало год, и настало время сделать обновления.

И решение нашлось. Оказывается, все, что нужно для облегчения этой задачи, есть в библиотеке Go. Итак, вот что нужно написать в программе на Go для того, что бы работал HTTPS сервер и автоматически создавался и обновлялся TLS сертификат по технологии Let’s Encrypt:

mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, TLS user! Your config: %+v", r.TLS)
})
log.Fatal(http.Serve(autocert.NewListener(domain), mux))

И все! Да, и все - те же самые стандартные три строчки, которые присутствуют во всех примерах создания HTTP-сервера. Ищем отличия, а я посмотрю на секундомер...
Ну конечно! - ‘autocert.NewListener(domain)’ в ‘http.Serve’.

Приложение, использующее этот код, должно быть запущено с правами администратора, и на хосте должен быть белый IP адрес. Без него Let’s Encrypt не сможет создать сертификат.

Для того что бы работала привычная нам переадресация с HTTP на HTTPS, добавим еще пару строк кода перед предыдущим кодом:

go func() {
	if err := http.ListenAndServe(":80", http.HandlerFunc(redirectTLS)); err != nil {
		log.Fatalf("ListenAndServe error: %v", err)
	}
}()

Здесь мы создали еще один HTTP сервер, работающий на 80-м порту и пересылающий все на наш основной сервер с помощью функции ‘redirectTLS’:

func redirectTLS(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "https://"+domain+":443"+r.RequestURI, http.StatusMovedPermanently)
}

Приложение имеет параметр '–domain' и строка запуска выглядит так:

sudo go run . -domain=example.com

Конечно. Для запуска этого приложения у вас должен быть зарегистрированный домен и белый IP адрес.

Программа размещена на Github:
https://github.com/kirill-scherba/https-example

Вот ещё один адрес на Gitflic, пока он запасной:
https://gitflic.ru/project/kirill-scherba/https-example

Комментарии (2)


  1. vebeer
    26.04.2022 11:13

    А права администратора нужны, чтобы слушать на 80м и 443м порту или для чего-то другого? А можно же кастомные порты указать для этого, 8080 и 8443, например? Хотя, кажется, что тогда LE не сможет прийти на 80й порт и верифицировать домен


    1. kirill-scherba Автор
      26.04.2022 12:44

      Да, вы все правильно себе ответили, тогда Let's Encrypt не сможет получить или обновить сертификат.

      A в случае когда мы используем для этого Pound или Nginx , то в момент получения (или обновления) сертификата Pound/Nginx нужно выключать по той же причине - Let's Encrypt не сможет прийти на 80ый порт и верифицировать домен.