前回の記事では LINE Messaging API を利用して、コマンドラインからプッシュメッセージを送信するツールを作成しました。

その発展形として Webhook を利用して bot サーバを作ろうとしたのですが、Webhook を利用するにはサーバの SSL/TLS 証明書が必要です。

というわけで、今回は自宅サーバに向けて SSL/TLS を無料で発行する手順についてメモっておきます。

※ 今回は LINE Messaging API シリーズに関連しているといえばしているのですが、独立した話題になりますので別記事として書きました。

この記事の想定

  • 最終目標:自宅サーバに SSL/TLS 証明書を登録し、HTTPS で通信できるようにする
  • 対象:HTTPS 用の自宅サーバを立てたい人、Messaging API の Webhook を利用して bot サーバを立てたい人

サーバの概要

  • Raspberry Pi 5
    • OS : Ubuntu 23.10 (Linux kernel ver.6.5.0, 64bit)
    • CPU : BCM2835
    • RAM : 8GB

Let’s Encrypt を使う

SSL/TLS 証明書を手に入れるには認証局での認証処理が必要です。認証には手数料が有料の場合が多いですが、Let’s Encrypt であれば無料で発行できます。

ドメインの入手

認証したいドメインを予め取得しておきます(サブドメインも可)。以降、[YOUR DOMAIN HERE] と示します。

手順

前準備:ポートを開放しておく

サーバ側で 80番ポートを開放し、ルータ側でポートマッピングを設定しておきます。

$ sudo ufw allow 80

前準備:サーバアプリケーション(apache2 など)を停止しておく

後ほど使う certbot は TCP 80 番ポートを認証時に利用します。

もしサーバ側で既に apache2(または nginx)などのサーバアプリケーションが実行されている場合は、予め停止しておいてください。

$ sudo systemctl stop apache2

certbot のインストール&認証

まずは証明書生成ツールである certbot をインストール。

$ sudo apt install certbot

インストールが完了したらツールを起動します。

$ sudo certbot certonly --standalone -d [YOUR DOMAIN HERE]

初回起動時はメールアドレスの入力、同意書への同意を求められます。

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): [YOUR E-MAIL ADDRESS HERE]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.4-April-3-2024.pdf. You must agree in
order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o:

次に証明書の発行完了後にメールアドレスの共有をしても良いか?と聞かれます。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o:

これにて初期設定は完了です。続いてドメインの認証処理が開始されます。

Account registered.
Requesting a certificate for [YOUR DOMAIN]

ここで DNS のチェック等が入ります。もしドメインからサーバまで 80 番ポートでアクセスできない場合、ここで失敗します。

Certbot failed to authenticate some domains (authenticator: standalone). The Certificate Authority reported these problems:
  Domain: [YOUR DOMAIN]
  Type:   connection
  Detail: xxx.xxx.xxx.xxx: Fetching http://[YOUR DOMAIN]/.well-known/acme-challenge/...: Timeout during connect (likely firewall problem)

成功したら証明書のファイルパスが表示されます。

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/[YOUR DOMAIN]/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/[YOUR DOMAIN]/privkey.pem
This certificate expires on 2025-01-14.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

以降、この証明書を使って SSL/TLS 通信が可能です。

例えば、Rust の Actix web の場合:

#[post("/")]
async fn index() -> impl Responder {
    ...
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    rustls::crypto::aws_lc_rs::default_provider()
        .install_default()
        .unwrap();
	
    // 証明書を読み込み(ここ!)
    let mut certs_file = BufReader::new(File::open("fullchain.pem").unwrap());
    let mut key_file = BufReader::new(File::open("privkey.pem").unwrap());
    
    // load TLS certs and key
    // to create a self-signed temporary cert for testing:
    // `openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'`
    let tls_certs = rustls_pemfile::certs(&mut certs_file)
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    let tls_key = rustls_pemfile::pkcs8_private_keys(&mut key_file)
        .next()
        .unwrap()
        .unwrap();
    
    // set up TLS config options
    let tls_config = rustls::ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(tls_certs, rustls::pki_types::PrivateKeyDer::Pkcs8(tls_key))
        .unwrap();

    HttpServer::new(|| {
            App::new()
                .service(index)
        })
        .bind_rustls_0_23(("0.0.0.0", 443), tls_config)?
        .run()
        .await
}

証明書の有効期限は3ヶ月間です。自動更新をさせたい場合はこちら ↓ の記事が参考になるかと思います。

おわりに

次回はこれを利用して、Rust で LINE Messaging API の Webhook サーバを作っていきます。