「もしもし、オレだよオレ、10.1.2.3だよ」
「あら、あなた10.1.2.3なの?なんか声が違うわね。」
「ちょっとカゼをひいちゃって。仕事でトラブってお金が必要なんだ。」
「おかしいわね、あなたlocaldomainさんちのlocalhost君でしょ」
「そ、そんなことないよ」

自宅鯖で遊び始めるとLAN内にいろんなサーバーが立ちあがります。アクセスするときhttpsなのはいいけど「保護されてない通信」と表示されたり、パスワードの自動入力が効かなかったりして地味に不便ですよね。しかしLet’s EncryptなどではIPアドレス形式の証明書は発行できません。それならいっそ自分で証明書を発行してしまいましょう。

今回やること

今回は自分専用のRoot CAを作って、プライベートIPアドレスに対応したサーバー証明書を発行して、それrぞれのサーバーにインストールしていきます。証明書の発行はWebベースで行い、なるべくコマンド入力を使わないで済むようにします。

証明書の発行は Local CA を使います。Local CAはDockerのコンテナとして動作するのでDockerが必要です。DockerもWebベースで操作できるようにするため、 Portainer CE を導入します。今回のインストール先のサーバーは Ubuntu 24 Serverで、Proxmox VE上でVMとして稼働しています。

サーバーのIPアドレスは 10.0.1.234 でセットアップを進めていきます。

Docker Engine のインストール

まずはアップデート

sudo apt update
sudo apt upgrade -y

必要パッケージやDocker の GPG キーを登録していきます。

sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

Docker リポジトリを追加します。

sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

Docker をインストールします。

sudo apt update
sudo apt install -y \
  docker-ce \
  docker-ce-cli \
  containerd.io \
  docker-buildx-plugin \
  docker-compose-plugin

Docker を有効化します。

sudo systemctl enable --now docker
sudo systemctl status docker

Active: active (running) と出ればOKです。ためしに動作確認してみましょう。

sudo docker run hello-world

いろいろと出てきますが、Hello from Docker!と出ればコンテナの実行も問題ありません。

Portainer CE のインストール

Portainer データ用 Volume を作成します。

sudo docker volume create portainer_data

念のため確認します。

sudo docker volume ls

Portainer を起動します。Portainerが使用するポート番号は 9443 です。

sudo docker run -d \
  -p 9443:9443 \
  --name portainer \
  --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:lts

起動できたか確認します。

sudo docker ps
CONTAINER ID   IMAGE                        COMMAND        CREATED          STATUS          PORTS                                                             NAMES
723aeb6b12cf   portainer/portainer-ce:lts   "/portainer"   45 seconds ago   Up 45 seconds   8000/tcp, 9000/tcp, 0.0.0.0:9443->9443/tcp, [::]:9443->9443/tcp   portainer

こんな風に出れば正常です。

Portainer のセットアップ

Portainerにログインしてセットアップをしていきます。その前にセットアップに必要な情報を取得します。

sudo docker logs portainer 2>&1 | grep 'setup_token='

出てきた値をコピーしたらブラウザで https://10.0.1.234:9443 にアクセスします。警告が出てきますが、これは後で対応します。そのためのオレオレ認証局ですからね!

先ほどコピーしたsetup_tokenを入力し、adminとパスワードを決めます。次の画面のEdge Computeはskipでかまいません。Homeを押して以下のようにlocalが1つだけ表示されている画面が出ればOKです。

Local CA のインストール

次はLocal CAをインストールします。PortinaerのHome → local → Stacks→ Add Stack と進みます。Nameは localca などと入力、Web editorを選択して以下のように入力します。

services:
  web:
    image: practicalsre/local-ca:latest
    container_name: localca-web
    restart: unless-stopped
    volumes:
      - db:/app/db
      - static_volume:/app/staticfiles
    environment:
      - TZ=Asia/Tokyo
      - CSRF_TRUSTED_ORIGINS=http://10.0.1.234:8080
    networks:
      - app_network
    command: >
      sh -c "
      ls -l &&
      python manage.py migrate &&
      python manage.py initadmin &&
      python manage.py collectstatic --noinput &&
      exec gunicorn localca_project.wsgi --bind 0.0.0.0:8000 --worker-class=gthread --threads=4"

  nginx:
    image: practicalsre/local-ca-nginx:latest
    container_name: localca-nginx
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - static_volume:/app/staticfiles:ro
    depends_on:
      - web
    networks:
      - app_network

volumes:
  static_volume:
  db:

networks:
  app_network:
    driver: bridge

この CSRF_TRUSTED_ORIGINS=http://10.0.1.234:8080 のIPアドレスははサーバーのIPアドレスで、ポート8080で公開します。Local CAのデフォルトポートは80ですが、将来Webサーバーを追加したときにかぶらないように nginx:ports:で”8080:80″ としています。

ここまで入力したら Deploy the stack を押してデプロイします。

Local CA にログイン

ブラウザで http://10.0.1.234:8080/ にアクセスします。右上のLoginを押して、admin, password でログインします。ログインしたら右上のChange Passwordでパスワードを変更しましょう。

ルート証明書の作成

まだ何も作成していないので中身は空です。上部のCreate CAを押します。

Create Root CAのRoot CA Nameに自分の認証局の名前を入れて、Validity Periodには有効期限です。別に短くする必要性がないので最大の7300(20年)にしてCreate Root CAを押して作成します。作成すると右側に表示されます。

中間証明書の作成

次に中間証明書を作成します。Create Intermediate CAのIntermediate CA Nameに自分の中間証明書の名前を入れて、Signing Root CAには先ほど作成したルート証明書を選択します。有効期限も最大の3650でいいでしょう。面倒なので有効期限とか無期限にしてほしいですね。

Create Intermediate CAを押すと中間証明書が作成されて右に表示されます。

Portainer 用のリーフ証明書を作成する

次はサーバーにインストールする証明書を作っていきます。Local CAのトップ画面に戻ると2つの証明書が作られているのがわかります。上部のCreate Leafを押します。

以下のように入力します。
・Common Name: portainer のように、ホスト名やサービス名など
・Subject Alternative Names: サーバーのIPアドレス
・Signing Intermediate CA: 先ほど作成した中間証明書
・Validity Period: 最大の825

入力したらCreate Leaf Certificateを押します。有効期限が825日なので2年ちょっとで交換しなくてはなりません。面倒なので無期限にしてもらえませんかね。一般のサーバー証明書の有効期限も昔は2年とかいけたのに、1年、200日、100日、47日のようにどんどん短くなってほんと迷惑ですw 自動更新にしないとやってられない世界です。

証明書のダウンロード

必要な証明書ができたのでLocal Caのトップ画面に戻ってダウンロードします。

ダウンロードするのは4つのファイルです。
・Oreore Root CA.pem = ルート証明書 公開鍵
・Oreore Intermediate CA.pem = 中間証明書 公開鍵
・portainer_chain.pem = サーバー証明書 公開鍵
・portainer_private.pem = サーバー証明書 秘密鍵

ルート証明書や中間証明書の秘密鍵はダウンロードしてバックアップしておいてもかまいませんが、セキュリティの根幹となる部分なので厳重に管理する必要があります。とはいってもLocal CAで作成した秘密鍵は暗号化されずにディスクに保存されているそうです。本格的にやるならディスクの暗号化をする手もありますが、そこまでガチでやる必要性も高くないと思うので、気が向いたらあとでやりましょう。

Windows 11にルート証明書をインストール

証明書ができたらインストールを行います。ルート証明書はPCに、サーバー証明書はサーバーにインストールします。Windows + Rで certlm.msc を起動します。

起動したら、信頼されたルート証明書→証明書を右クリック、すべてのタスク→インポートを開きます。

ウィザードが開くので最初は次へ、インポートするファイルはルート証明書(Oreore Root CA.pem)を選択して次へを押します。証明書の配置先はすでに選択されているので、信頼されたルート証明書が選択されていることを確認して次へ→完了。

これでPCにオレオレルート証明書がインストールされました。一覧からOreore Root CAを開けくと確認できます。

これでオレオレルート証明書が親の証明書はこのPCで認証されるようになったので、オレオレ中間証明書はインストールしなくても、オレオレサーバー証明書は正しく認証されます。

Portainer にサーバー証明書をインストール

いよいよ最後です。Portainerに収録されているサーバー証明書を、正しい(オレが正しいと言ってる)証明書と差し替えます。まずは以下の2つのファイルをSCPなどで Ubuntuサーバーに転送します。

・portainer_chain.pem = サーバー証明書 公開鍵
・portainer_private.pem = サーバー証明書 秘密鍵

念のため証明書が読めるか確認してみましょう。

openssl x509 -in portainer_chain.pem -noout -subject -issuer -ext subjectAltName
subject=CN = portainer, O = LocalCA
issuer=CN = Oreore Intermediate CA, O = LocalCA
X509v3 Subject Alternative Name:
    DNS:portainer, IP Address:10.0.1.234

作成した通りの内容になってますね。適切な場所にファイルをコピーします。

sudo mkdir -p /opt/portainer/certs
sudo cp portainer_chain.pem portainer_private.pem /opt/portainer/certs/
sudo chmod 644 /opt/portainer/certs/portainer_chain.pem
sudo chmod 600 /opt/portainer/certs/portainer_private.pem

現在稼働しているPortainerは停止して削除し、証明書付きで Portainer を再作成します。

sudo docker stop portainer
sudo docker rm portainer

sudo docker run -d \
  -p 9443:9443 \
  --name portainer \
  --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  -v /opt/portainer/certs:/certs:ro \
  portainer/portainer-ce:lts \
  --sslcert /certs/portainer_chain.pem \
  --sslkey /certs/portainer_private.pem

これでオレオレ証明書が組み込まれたPortainerになりました。

再度 Portainer にアクセス

再度ChromeでPortainerのURLにアクセスします。しかし前回のアクセス情報が残っているのでChromeを再起動する必要があるのですが、普通に終了しただけでは消えない場合があります。そこでChromeのアドレスバーに以下の内容を入れて再起動させます。

chrome://restart

これで再度 https://10.0.1.234:9443 にアクセスすると…

あの忌々しい「保護されていない通信」が消えました!!
証明書を表示してみると、ちゃんとオレオレルート証明書→オレオレ中間証明書という構成が確認できますね。

これでログインしたときにパスワードを保存できるようになり、次回ログイン時は自動入力されます。通信も自分で作った鍵で暗号化されているので今までより安全です。

Local CA をhttpsにしたい

秘密鍵をダウンロードするのにhttpでいいのかな…。ずっとそんな疑問がありました。そこでLocal CAをhttps化したいと思います。

Local CA 用の証明書を発行する

オレがオレのためにオレを証明書するだと!? 何を言っているかわからないと思うがオレも…、まぁとにかく先に進みましょう。Local CAにアクセスして、Local CA用の証明書を作成してダウンロードします。IPアドレスは同じなのでPortainer用の証明書でも使えますが、サーバーごとに分けるのが安全です。

作成方法は前回の説明と同じなので割愛します。localcaというCommon Nameで作ってます。作成したらUbuntuサーバーへsftpでアップロードします。

Local CA を再構築する

sudo mkdir -p /opt/localca/certs
sudo mkdir -p /opt/localca/nginx
sudo cp localca_chain.pem /opt/localca/certs/
sudo cp localca_private.pem /opt/localca/certs/
sudo chmod 644 /opt/localca/certs/localca_chain.pem
sudo chmod 600 /opt/localca/certs/localca_private.pem
sudo tee /opt/localca/nginx/default.conf > /dev/null <<'EOF'
# HTTP -> HTTPS
server {
    listen 80;
    server_name _;

    return 301 https://$host:8443$request_uri;
}

# HTTPS
server {
    listen 443 ssl;
    server_name _;

    ssl_certificate     /etc/nginx/certs/localca_chain.pem;
    ssl_certificate_key /etc/nginx/certs/localca_private.pem;

    ssl_protocols TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://web:8000;

        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /static/ {
        alias /app/staticfiles/;
        expires 30d;
    }
}
EOF

PortainerのLocal CA Stackも変更します。local → Stacks → localca → Editor を開き、以下のように編集します。

services:
  web:
    image: practicalsre/local-ca:latest
    container_name: localca-web
    restart: unless-stopped
    volumes:
      - db:/app/db
      - static_volume:/app/staticfiles
    environment:
      - TZ=Asia/Tokyo
      - CSRF_TRUSTED_ORIGINS=https://10.0.1.234:8443
    networks:
      - app_network
    command: >
      sh -c "
      ls -l &&
      python manage.py migrate &&
      python manage.py initadmin &&
      python manage.py collectstatic --noinput &&
      exec gunicorn localca_project.wsgi --bind 0.0.0.0:8000 --worker-class=gthread --threads=4"

  nginx:
    image: practicalsre/local-ca-nginx:latest
    container_name: localca-nginx
    restart: unless-stopped
    ports:
      - "8080:80"
      - "8443:443"
    volumes:
      - static_volume:/app/staticfiles:ro
      - /opt/localca/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - /opt/localca/certs:/etc/nginx/certs:ro
    depends_on:
      - web
    networks:
      - app_network

volumes:
  static_volume:
  db:

networks:
  app_network:
    driver: bridge

入力したらUpdate the stackで更新します。

Local CA にhttpsでアクセスしてみる

それではLocal CAにアクセスしてみましょう。新しいURLはこうなります。
https://10.0.1.234:8443/

Local CAもSSLになりました~!

Proxmox VE をhttpsにしたい

はい、できます。(なんかAIみたいな応答w)
ノードを選択して → システム → 証明書 を選択し、カスタム証明書をアップロードを押します。

Local CAで作成した2つのファイルをアップロードします。

これで完了です。アドレスバーに chrome://restart を入れてChromeを再起動してから再度Proxmox VEにアクセスすればhttpsで警告が表示されなくなるはずです。

TrueNAS Scale をhttpsにしたい

はい、できます。(しつこいw)
TrueNASにログインしたら認証保護 → 証明書を開き、証明書のインポートを押します。証明書と秘密鍵のテキストを貼り付けます。

システム → 一般的な設定 → GUI の設定を開きます。GUI SSL証明書にオレオレ証明書が追加されているので、これを選択して保存します。

これで chrome://restart をして再度アクセスすれば警告が消えているはずです。

おわりに

ということで長々とセットアップをしてきましたが、Local CAさえセットアップしてしまえば、あとは証明書を発行するだけです。httpsでアクセスするソフトウェアは様々ですが、たいていの場合は任意の証明書を設定できるようになっているので、簡単に対応できると思います。