[LINUX] Let's Encrypt and MyDNS issue a multi-domain (including subdomain) certificate with a wildcard and automatically renew it. apache too.

Introduction

I tried Let's Encrypt for the first time. If CN was used as the host name and only SAN was used, "CN does not match!" Was displayed at a certain place, so countermeasures were necessary. I was able to issue a reusable certificate with one sheet, so I will record it.

What you can do by reading this entry

be able to. Should be.

Screenshot from Gyazo

Environment etc.

Target device and environment

If you ever wanted a certificate like this

I'm building a validation environment with a domain called ʻexample.com. Under that there is a subdomain called sub.example.com`. I wanted to reuse one certificate for the servers in that environment. So, I want such a certificate.

item value
CN *.example.com
SAN *.example.com
SAN *.sub.example.com
SAN example.com

MyDNS account information

domain value
Domain name example.com
Master ID mydns123456
password mydnspassword
sub domain value
Subdomain name sub.example.com
Master ID mydns654321
password subdompassword

Implementation

Script for hooks

I have https://github.com/disco-v8/DirectEdit for MyDNS, but I can't manage it including subdomains, so With this as a reference, I created the following. I will explain using this. Let's Encrypt MyDNS Hook Script

Preparation

yum -y install epel-release
yum -y update
yum -y install php php-mbstring certbot python2-certbot-apache mod_ssl
cd /root/
git clone https://github.com/bashaway/le_mydns_hook

Put your MyDNS account information in ./le_mydns_hook/accounts.conf.

vi ./le_mydns_hook/accounts.conf
----------8<-----(snip)-----8<----------
$MYDNS_ID['Domain name']  = 'Master ID';
$MYDNS_PWD['Domain name'] = 'password';
----------8<-----(snip)-----8<----------

For example, in the above example, modify accounts.conf as follows.

----------8<-----(snip)-----8<----------
$MYDNS_ID['example.com']  = 'mydns123456';
$MYDNS_PWD['example.com'] = 'mydnspassword';
$MYDNS_ID['sub.example.com']  = 'mydns654321';
$MYDNS_PWD['sub.example.com'] = 'subdompassword';
----------8<-----(snip)-----8<----------

Certificate issuance

Once you work, it seems that certbot renwe will take over the arguments, so specify your own hook with the full path.

certbot certonly --manual \
 --server https://acme-v02.api.letsencrypt.org/directory \
 --preferred-challenges dns-01 \
 --agree-tos --no-eff-email \
 --manual-public-ip-logging-ok \
 --manual-auth-hook /root/le_mydns_hook/regist.php \
 --manual-cleanup-hook /root/le_mydns_hook/delete.php \
 -m [email protected] \
 -d *.example.com \
 -d *.sub.example.com \
 -d example.com 

Let's check (Issuer is Fake because it was issued by staging below)

# openssl x509 -in /etc/letsencrypt/archive/example.com/cert1.pem -text | egrep "CN|DNS"
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
        Subject: CN=*.example.com
                DNS:*.sub.example.com, DNS:*.example.com, DNS:example.com

apache settings

firewall settings

firewall-cmd --add-service https --zone=public --permanent
firewall-cmd --reload

The existing settings will be replaced with the certificate issued by Let's Encrypt.

/etc/httpd/conf.d/ssl.conf


#SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem

#SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

#SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem

Let httpd load the settings.

systemctrl reload httpd

Check for automatic updates

For checking, I will add --force-renewal.

certbot certonly --manual \
 --server https://acme-v02.api.letsencrypt.org/directory \
 --preferred-challenges dns-01 \
 --agree-tos --no-eff-email \
 --manual-public-ip-logging-ok \
 --manual-auth-hook /root//le_mydns_hook/regist.php \
 --manual-cleanup-hook /root/le_mydns_hook/delete.php \
 -m [email protected] \
 -d *.example.com \
 -d *.sub.example.com \
 -d example.com \
 --webroot-path /var/www/html/ \
 --post-hook "systemctl reload httpd" \
 --force-renewal

Probably, the updated one is published as below.

$ ls -1 /etc/letsencrypt/archive/example.com/cert*
/etc/letsencrypt/archive/example.com/cert1.pem <---First issued
/etc/letsencrypt/archive/example.com/cert2.pem <--- force-renewal

Automatic update by cron

Shortly --force-renewal for confirmation.

/etc/cron.d/letsencrypt


0/10 * * * * root /bin/certbot renew --webroot-path /var/www/html/ --post-hook "systemctl reload httpd" --force-renewal
$ ls -1 /etc/letsencrypt/archive/example.com/cert*
/etc/letsencrypt/archive/example.com/cert1.pem <---First issued
/etc/letsencrypt/archive/example.com/cert2.pem <---Manual force-renewal
/etc/letsencrypt/archive/example.com/cert3.pem <---force with cron-renewal

If it's updated without any problems, check it once a week. If you don't fix it, you'll get stuck in the rate limit.

/etc/cron.d/letsencrypt


0 1 * * 1 root /bin/certbot renew --webroot-path /var/www/html/ --post-hook "systemctl reload httpd"

at the end

script

This is a script on github. I'm adding a TXT record with regist and deleting a TXT record with delete.php. The only difference between the two scripts is $ CERTBOT_ENV ['EDIT_CMD'] ='REGIST'; or $ CERTBOT_ENV ['EDIT_CMD'] ='DELETE';.

regist.php


#!/usr/bin/php
<?php

// set environment
include(__DIR__.'/accounts.conf');
date_default_timezone_set(@date_default_timezone_get());
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');

// set certbot env
$CERTBOT_ENV['CERTBOT_DOMAIN']     = getenv('CERTBOT_DOMAIN');
$CERTBOT_ENV['CERTBOT_VALIDATION'] = getenv('CERTBOT_VALIDATION');

// txt record
$CERTBOT_ENV['EDIT_CMD'] = 'REGIST';

// mydns account
$MYDNS_ACCOUNT=$MYDNS_ID[$CERTBOT_ENV['CERTBOT_DOMAIN']].':'.$MYDNS_PWD[$CERTBOT_ENV['CERTBOT_DOMAIN']];

$MYDNS_HEADERS = array('Content-Type: application/x-www-form-urlencoded',
                       'Authorization: Basic '. base64_encode($MYDNS_ACCOUNT),);


//Set context resources
$POST_OPTIONS = array( 'http' => array('method' => 'POST',
                                       'header' => implode("\r\n", $MYDNS_HEADERS),
                                       'content' => http_build_query($CERTBOT_ENV)));

// get contents
$MYDNS_CONTENTS = file_get_contents($MYDNS_URL, false, stream_context_create($POST_OPTIONS));

sleep(2);

?>

delete.php


#!/usr/bin/php
<?php

// set environment
include(__DIR__.'/accounts.conf');
date_default_timezone_set(@date_default_timezone_get());
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');

// set certbot env
$CERTBOT_ENV['CERTBOT_DOMAIN']     = getenv('CERTBOT_DOMAIN');
$CERTBOT_ENV['CERTBOT_VALIDATION'] = getenv('CERTBOT_VALIDATION');

// txt record
$CERTBOT_ENV['EDIT_CMD'] = 'DELETE';

// mydns account
$MYDNS_ACCOUNT=$MYDNS_ID[$CERTBOT_ENV['CERTBOT_DOMAIN']].':'.$MYDNS_PWD[$CERTBOT_ENV['CERTBOT_DOMAIN']];

$MYDNS_HEADERS = array('Content-Type: application/x-www-form-urlencoded',
                       'Authorization: Basic '. base64_encode($MYDNS_ACCOUNT),);


//Set context resources
$POST_OPTIONS = array( 'http' => array('method' => 'POST',
                                       'header' => implode("\r\n", $MYDNS_HEADERS),
                                       'content' => http_build_query($CERTBOT_ENV)));

// get contents
$MYDNS_CONTENTS = file_get_contents($MYDNS_URL, false, stream_context_create($POST_OPTIONS));

?>

Source

https://letsencrypt.org/ja/ https://github.com/disco-v8/DirectEdit

Recommended Posts

Let's Encrypt and MyDNS issue a multi-domain (including subdomain) certificate with a wildcard and automatically renew it. apache too.
[Linux] Create a self-signed certificate with Docker and apache
HTTPS with Django and Let's Encrypt