Table of Contents

モチベーション

前準備

パッケージをアップデートする

  1. apt updateでパッケージ一覧を更新
  2. apt upgradeでパッケージを更新
  3. apt autoremoveで必要なくなったパッケージを削除
  4. apt autocleanで.debファイルを削除

    • /var/log/apt/history.logにaptコマンドの履歴がある
    • /var/log/apt/term.logにaptコマンドのログがある

パッケージの自動アップデートを設定する

セキュリティ対策のため,パッケージは常に最新になるようにする.

  1. 20-auto-upgradesを編集する
    vi /etc/apt/apt.conf.d/20auto-upgrades

    APT::Periodic::Update-Package-Lists "1";
    APT::Periodic::Download-Upgradeable-Packages "1";
    APT::Periodic::AutocleanInterval "7";
    APT::Periodic::Unattended-Upgrade "1";
    
    • Update-Package-Lists : アップデートの確認頻度[Day]
    • Download-Upgradeable-Packages : アップデートをダウンロードしておくか
    • AutocleanInterval : クリーンアップ頻度[Day]
    • Unattended-Upgrade : アップデートをインストールするか
  2. 50unattended-upgradesを編集する
    vi /etc/apt/apt.conf.d/50unattended-upgrades

    "${distro_id}:${distro_codename}-updates"; をコメントアウト
    
  3. 20-auto-upgradesを適用する
    dpkg-reconfigure -plow unattended-upgrades

  4. 自動アップデートのサービスがactiveかを確認
    systemctl status unattended-upgrades.service

  5. テストする
    unattended-upgrade --dry-run --debug

一般ユーザを新規追加してsudoが使えるようにしておく

  1. adduser <user>
  2. gpasswd -a <user> sudo

一般ユーザでサーバーにSSHできるようにする

ローカルで公開鍵を生成する

以下はmacbookをローカルとした場合の手順 1. cd ~/.ssh 2. ssh-keygen -t rsa -b 4096 3. chmod 600 <秘密鍵> 4. vi config

```
Host <nickname>
HostName <ip address>
Port <port for ssh>
User <username>
IdentityFile ~/.ssh/<秘密鍵>
```
  1. cat < ~/.ssh/<公開鍵> | pbcopy

サーバーに公開鍵を登録する

  1. su <user>
  2. mkdir ~/.ssh
  3. chmod 700 .ssh
  4. ~/.ssh/authorized_keysに公開鍵をコピペ
  5. chmod 600 authorized_keys
  6. cd /etc/ssh
  7. sudo cp sshd_config sshd_config.bk
  8. sshd_configを編集する

    Port <port for ssh> <== 22番以外にしたほうが良い
    PermitRootLogin no
    PasswordAuthentication no
    PubkeyAuthentication yes
    UsePAM no
    
  9. sudo systemctl restart ssh

  10. ローカルからサーバにsshできれば優勝

  11. rm <公開鍵>

Firewallを設定する

  1. sudo ufw reset
  2. sudo ufw default DENYで全パケットを拒否する設定にする
  3. sudo ufw allow <port for ssh>でSSHのポートは許可しておく
  4. sudo ufw enableでファイアウォールを有効化
  5. sudo ufw status verboseで状態を確認

Docker(rootless)をインストールする

  1. cat /etc/lsb-releaseでUbuntuのバージョンを確認する.ここではUbuntu 20.04を対象とした.
  2. uidmap必要なのでsudo apt install uidmapを実行する.
  3. Install Docker Engine
  4. Run the Docker daemon as a non-root user
    • systemctl周辺でエラーが生じる場合は,export XDG_RUNTIME_DIR=/run/user/$UIDして実行してみる.
  5. rootlessだと1024番未満のportをbindすることができない./etc/sysctl.confnet.ipv4.ip_unprivileged_port_start=0を追加してsudo sysctl --systemを実行することで,1024番未満のportをbindできるようになる.
  6. sudo ufw allow 80sudo ufw allow 443を実行して,80番portと443番portへの外部からのアクセスを許可する.
  7. Rootlessはデフォルトで,client ipをコンテナに伝搬しないので,(コンテナ内のnginxでclient ipを取得しようとしても,コンテナの接続しているdocker bridgeのgatewayになってしまう)以下を実行してclient ipをコンテナ内で受け取れるようにする.ただし,デフォルトの方がパフォーマンスが良いので,コンテナでclient ipを受け取らなくても良いのなら設定する必要は無い.
    • ~/.config/systemd/user/docker.service.d/override.confに以下を追加する [Service] Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns"
    • sudo systemctl daemon-reload
    • sudo systemctl restart docker
    • reboot

Nginxコンテナを立ち上げてHTTPでアクセスできることを確認する

今回は以下のようなフォルダ構成を仮定して説明する.

/web/
  ├ nginx/
  |  ├ letsencrypt/     <=== let's encrypt関連
  |  ├ logs/
  |  |  ├ access.log    <=== アクセスログ
  |  |  ├ error.log     <=== エラーログ
  |  |  └ certbot.log   <=== cronによる証明書の自動更新の際のログ
  |  ├ default.conf     <=== nginx.confからincludeされる設定
  |  ├ nginx.conf       <=== Nginxの設定
  |  └ Dockerfile
  └ docker-compose.yml 

まずは.Nginxコンテナを起動するためのDockerfileを書く.詳しくはDockerコンテナのライフサイクルを参照のこと.

FROM nginx:latest
RUN apt-get update && \
    apt-get install -y certbot python-certbot-nginx && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    ln -sf  /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
ADD nginx.conf /etc/nginx/
ADD default.conf /etc/nginx/conf.d/

次に,以下のようなNginxの設定ファイルを作成&編集する.パラメータの説明は今回は省く.

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
    multi_accept on;
    use epoll;
}


http {
    server_tokens off;
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;

    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf; <=== 別の.confファイルをincludeする
}

また,以下のようなnginx.confからincludeされるdefault.confを作成&編集する.パラメータの説明は今回は省く.

server {
    server_name  <hostname>;
    listen  80;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

そして,以下のようなdocker-compose.ymlを書く.詳しくはDockerコンテナのライフサイクルを参照のこと.

version: '3'
services:
  proxy:
    build: ./nginx <=== /web/nginx/Dockerfileへの相対パス
    tty: true
    container_name: proxy
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    ports:
      - "80:80"   <=== HTTPで外部からアクセスできるように
      - "443:443" <=== HTTPSで外部からアクセスできるように
    volumes:
      - '/web/nginx/logs:/var/log/nginx' <=== nginxのログはホスト側と共有する
      - '/web/nginx/letsencrypt:/etc/letsencrypt' <=== letsencryptのディレクトリもホストと共有する

最後に,ホストの/web/ディレクトリ内でdocker-compose up -dを実行すると,docker-compose.ymlから新たなDockerイメージがビルドされてNginxコンテナが立ち上がる.ブラウザからhttp://<hostname>でアクセスできれば,HTTP部分は開通.

$ docker ps
CONTAINER ID        IMAGE           COMMAND                  CREATED             STATUS              PORTS                                      NAMES
5320cf3e8eff        web_proxy       "/docker-entrypoint.…"   21 seconds ago      Up 20 seconds       0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   proxy

HTTPSに対応する

ホストでdocker exec -it proxy bashを実行することでNginxコンテナに入り,cerbotを実行することでLet’s encrypt証明書を発行する.発行された証明書はコンテナ内の/etc/letsencrypt/live/<hostname>/に保存される.

次に,default.confを以下のように編集する.80番ポートへのアクセスは443番ポート(https://)へリダイレクトするように設定し,443番ポートにはsslの設定を行う.

server {
    server_name  <hostname>;
    listen 80;
    return 301 https://$host$request_uri;
}

server {
    server_name  <hostname>;
    listen  443;
    ssl on;
    ssl_protocols  TLSv1.2 TLSv1.3;
    ssl_certificate     /etc/letsencrypt/live/<hostname>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<hostname>/privkey.pem;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

最後に,ホストの/web/ディレクトリ内でdocker-compose restartを実行して,Nginxコンテナを再起動することで,default.confを再読み込みさせる.ブラウザからhttps://<hostname>でアクセスできれば,HTTPSの対応が完了する.

Let’s encryptの証明書は90日で期限切れになるので,自動で更新するようにする

Let’s encryptの証明書は90日で期限切れになるので,定期的に更新する必要がある.証明書の更新を自動で行うため,cronにjobを登録する.

  1. cp /etc/crontab /etc/cron.d/certbot_dockerにて,crontabフォーマットファイルをコピーする
  2. certbot_dockerに以下のような設定を行う.以下は,毎日午前3時にdate; docker exec proxy certbot renewを実行し,結果を/web/nginx/log/certbot.logに出力するというジョブである.

    # m h dom mon dow user  command
    0 3    * * * <username> { date; docker exec proxy certbot renew; } >> /web/nginx/log/certbot.log 2>&1
    
  3. sudo service cron restartにより,certbot_dockerをcronに反映する.

  4. 毎日午前3時に以下のようなログが/web/nginx/log/certbot.logに出力される.証明書の期限に余裕がある場合は,証明書の更新はスキップされ,証明書の期限が近づくと更新される.

    Thu Jul 23 03:00:01 JST 2020
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Processing /etc/letsencrypt/renewal/<hostname>.conf
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Cert not yet due for renewal
    
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    The following certs are not due for renewal yet:
    /etc/letsencrypt/live/<hostname>/fullchain.pem expires on 20XX-XX-XX (skipped)
    No renewals were attempted.
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -