OCI_WAF(CDN)を構成してみる

WAFのEdge PolicyはCDNとしての機能もあるらしいのでそれ目的で構成してみた

OCIリソース

  • Oracle Cloud Infrastructure Certificates:Cert_Blogs_Ash →結局Edge Policyでは使えないことがわかって削除
    • Reasion:US East(Ashburn)
    • Compartment: Blogs
  • Oracle Web Application Firewall(WAF)※Edge Policy: WaF_for_Blogs_Ash
    • Reasion:US East(Ashburn) →Edge Policyはグローバルっぽい

    • Compartment: Blogs

SAN証明書

ドメイン毎に証明書を分けようと思ったが、WAFが1つにつき1つの証明書しか含められない、かつWAFが初回インスタンスのみ無料っぽいので、ドメインすべて同じ証明書にくくる
→というかその方法 SAN証明書というやつ取得する必要があるらしく、Traefikの構成を変える必要があるとのこと。

↓下は証明書を分けて抜き出すためのコマンド。WAFのインスタンスポコポコ量産できないので使わない。

# jqインストール
sudo apt -y install jq

# Traefik v2ではA型とBg型の格納方法があるらしい。↓はB型(型によって以下のコマンドが変わってくる)
ubuntu@instance-blogs-ash:~/traefik/letsencrypt$ jq 'keys'  ~/traefik/letsencrypt/acme.json
[
  "le"
]
ubuntu@instance-blogs-ash:~/traefik/letsencrypt$

# どのドメインの証明書が含まれてるか確認
ubuntu@instance-blogs-ash:~/traefik/letsencrypt$ jq -r '.[].Certificates[].domain.main' ~/traefik/letsencrypt/acme.json
staging.fuku.tokyo
stagingsub.fuku.tokyo
ubuntu@instance-blogs-ash:~/traefik/letsencrypt$

# ドメイン毎に証明書ファイルを分けて出力する方法
jq -c '
  .[].Certificates[] |
  {dom: .domain.main, cert: .certificate, key: .key}
' acme.json |
while IFS= read -r obj; do
  dom=$(jq -r '.dom'  <<<"$obj")
  jq  -r '.cert' <<<"$obj" | base64 -d > "${dom}.crt"
  jq  -r '.key'  <<<"$obj" | base64 -d > "${dom}.key"
done

SAN証明書の構成

  1. traefik.ymlにdomains: セクションを追加

    1. 今のhttpChallenge:web(CAが80番ポートアクセスして存在を確認)という仕組みでdomainsはサポートしてないらしくdnsChallenge(DNSをチェックするっぽい)にする必要があるらしい
      1. dnsChallenge providerにociがサポートしているらしい(OCI DNSを使っているのでこれが使える)
      2. OCI APIキー取得
        1. 「user settings」> 「API Key」から生成して、各種設定とprivate key fileを保存
      3. traefik.ymlを変更:
        1. domainsを指定
            websecure:
              address: ":443"
              http:
                tls:
                  options: default
                  certResolver: le          # 1 つだけ ACME リゾルバを指定
                  domains:                  # ここにまとめたいドメインを列挙
                    - main: staging.fuku.tokyo
                      sans:
                        - stagingsub.fuku.tokyo
          
        2. 今のhttpChallengeをdnsChallengeに変更
              dnsChallenge:
                provider: oraclecloud
                delayBeforeCheck: 10
        
      4. docker-compose.yml修正
        1. secratesを登録して秘密鍵ファイルをコンテナに渡す、トップレベルでsecrets定義(リソースの定義)とサービス側にもsecrets定義(使うよって定義)が必要
        2. 各OCIのAPI KEY情報を設定。実際の設定は.envに書いてある。
        ubuntu@instance-blogs-ash:~/traefik$ cat docker-compose.yml
        services:
          traefik:
            image: traefik:latest
            container_name: traefik
            restart: unless-stopped
            ports:
              - "80:80"
              - "443:443"
            volumes:
              - ./traefik.yml:/traefik.yml:ro
              - ./dynamic:/dynamic:ro
              - ./letsencrypt:/letsencrypt
              - /etc/localtime:/etc/localtime:ro
              - /etc/timezone:/etc/timezone:ro
            secrets:
              - source: oci_api_key
                target: oci_api_key.pem
            environment:
              - TZ=${TIMEZONE}
              - OCI_REGION=${OCI_REGION}
              - OCI_COMPARTMENT_OCID=${OCI_COMPARTMENT_OCID}
              - OCI_TENANCY_OCID=${OCI_TENANCY_OCID}
              - OCI_USER_OCID=${OCI_USER_OCID}
              - OCI_PUBKEY_FINGERPRINT=${OCI_PUBKEY_FINGERPRINT}
              # ファイル参照 (_FILE) で秘密鍵を安全に渡す
              - OCI_PRIVKEY_FILE=/run/secrets/oci_api_key.pem
              - OCI_PRIVKEY_PASS=            # パスフレーズ無しなら空文字を明示
            depends_on:
              - socket-proxy
            networks:
              - traefik_net
        
          socket-proxy:
            image: tecnativa/docker-socket-proxy:latest
            container_name: socket-proxy
            networks:
              - traefik_net           # Traefik と同じネット
            environment:
              TZ: ${TIMEZONE}
              CONTAINERS: 1           # Traefik が必要とする最小限のみ有効化
            volumes:
              - /var/run/docker.sock:/var/run/docker.sock:ro
              - /etc/localtime:/etc/localtime:ro
              - /etc/timezone:/etc/timezone:ro
        
        networks:
          traefik_net:
            external: true
        
        secrets:
          oci_api_key:
            file: ./secrets/oci_api_key.pem
        ubuntu@instance-blogs-ash:~/traefik$
        
      5. private key fileを置く
        1. mkdier ~/traefik/secrets
        2. ファイルアップロード(./secrets/oci_api_key.pem)
        3. chmod ./secrets/oci_api_key.pem ```
  2. コンテナに定義してある証明書自動取得ラベルを外して代わりにtlsが有効っていうラベルのみにする

    ↓を消して
    - "traefik.http.routers.site1.tls.certresolver=le"
    ↓を入れる
    - "traefik.http.routers.site1.tls=true"
    
  3. treaefikを再起動とその他コンテナ再起動

    1. コンテナ止めて
    2. acme.jsonを初期化 cp /dev/nul acme.json
    3. コンテナ起動
    4. サイトアクセスできればとりあえず成功
    5. ダメだったら、docker compose logs |grep ERR でtraefikのエラーを見る。
  4. 一枚にまとまっている確認

#1 と表示される
ubuntu@instance-blogs-ash:~/traefik/letsencrypt$ jq '.le.Certificates | length'  acme.json
1
ubuntu@instance-blogs-ash:~/traefik/letsencrypt$

# 1つのエントリ(1行)に複数のサブドメインが含まれている。
ubuntu@instance-blogs-ash:~/traefik/letsencrypt$ jq -r '.le.Certificates[]
       | [.domain.main] + .domain.sans
       | @tsv' acme.json
staging.fuku.tokyo      stagingsub.fuku.tokyo
ubuntu@instance-blogs-ash:~/traefik/letsencrypt$

その他ナレッジ

yqインストール(yamalのパーサ、apt getでインストールもできるがそれ古い奴らしくて使えない。この新しい奴をインストールする)

sudo wget -O /usr/local/bin/yq \
  https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64
sudo chmod +x /usr/local/bin/yq

Oracle Cloud Infrastructure Certificates

→結局Edge WASでは使えないことが分かった。そのため最終的にはWAFに直接証明書をImportした。 
WAF はレガシー版(Edge WAF)とWAF v2(Reagional WAF)の2つがある。
v1はOCI外に配置されてるWAFサーバーっぽい。→CDN用途で使えるのはこっち。
v2はLBにアタッチする形で使うセキュリティーの拡張機能って感じ。

  1. 証明書のExport

    MAIN=staging.fuku.tokyo
    
    jq -r --arg MAIN "$MAIN" '
      .le.Certificates[]
      | select(.domain.main==$MAIN)
      | .certificate' acme.json | base64 -d > fullchain.pem
    
    jq -r --arg MAIN "$MAIN" '
      .le.Certificates[]
      | select(.domain.main==$MAIN)
      | .key' acme.json | base64 -d > privkey.pem
    
    # fullchain.pemとprivkey.pemが出来る。
    ubuntu@instance-blogs-ash:~/traefik/letsencrypt$ ls -ltr
    total 24
    -rw------- 1 ubuntu ubuntu 13462 May 14 23:07 acme.json
    -rw-rw-r-- 1 ubuntu ubuntu  4010 May 14 23:33 fullchain.pem
    -rw-rw-r-- 1 ubuntu ubuntu  3243 May 14 23:33 privkey.pem
    ubuntu@instance-blogs-ash:~/traefik/letsencrypt$
    
    # ドメインがすべて含まれていればOK
    ubuntu@instance-blogs-ash:~/traefik/letsencrypt$ openssl x509 -in  fullchain.pem -noout -text | grep DNS
                    DNS:staging.fuku.tokyo, DNS:stagingsub.fuku.tokyo
    ubuntu@instance-blogs-ash:~/traefik/letsencrypt$
    
    
    # OCI ではcertificatonとchain別で張らないといけないので、分けて出力
    awk '
      /-----BEGIN CERTIFICATE-----/ {i++}
      i==1 {print > "certificate.pem"}     # 1枚目だけ
      i>=2 {print > "chain.pem"}           # 2枚目以降をチェーンへ
    ' fullchain.pem
    
  2. OCI
    Certificates > Certificates > Create Certificate をクリック
    Importedを選択して、Certificateにcertificate.pemの内容を、Certificate Chainにfullchain.pemの内容を、Private Keyにprivkey.pemの内容を張る、そのまま進んで、最終的にActiveになればOK

    image-7.png

Oracle Web Application Firewall(WAF)

Identity & Security > Web application firewall > Create WAF policy

legacy workflow のリンクをクリックする必要がある。
これでつくるとEdge policy になるらしく、作成も削除もちょっと時間かかる。世界中のWAFサーバーに設定を伝搬しているらしい。
image-8.png

入力はこんな感じ

image-9.png

出来上がったら、Settingsでオプションを設定する。
証明書はOCI Cerficationsを使えないので直接アップロード↓のは一回アップロードした後の画面なのでCertificatieが入っているが、初回はUpload…のほウにチェックして貼り付ける。fullchain.pemとprivkey.pemが必要(fullchainの2件の登録間の空白行はダメらしいので、そこを削って張った

image-12.png

各オプションの意味

画面に表示される項目OCI CLI パラメータ何をするかいつ有効にする?
Enable response buffering--is-response-buffering-enabledオリジンから返ってくるレスポンスをいったん WAF エッジでバッファリングしてからクライアントへ送り出します。‐ ネットワークの瞬断や輻輳時に、途中で転送が切れるリスクを低減。‐ その代わり Time To First Byte (TTFB) がわずかに遅くなる可能性があります。 (Oracle クラウド インフラドキュメント)暗号化オブジェクトや大きいファイルを配信していて、回線品質が不安定なケース/WAF 側でレスポンス改変(圧縮・マスキングなど)を行うケース
Cache control respected--is-cache-control-respectedオリジン応答の Cache-Control ヘッダー値(例 max-age=120)をそのまま尊重し、WAF がエッジ・キャッシュとして働きます。独自のキャッシュルールを別途設定している場合はそちらが優先されます。 (Oracle クラウド インフラドキュメント)オリジン側で細かいキャッシュ制御をしている/既存 CDN から OCI WAF に移行しつつ設定を変えたくない場合
Behind CDN--is-behind-cdnWAF の前段に CDN がある前提で、CDN が付与するヘッダー(既定は X-Forwarded-For)から「本当のクライアント IP」を取得します。不正アクセスブロックやレート制限を正しい IP 単位で判定できます。ヘッダー名は Client address header で変更可能です。 (Oracle クラウド インフラドキュメント)Akamai/CloudFront など外部 CDN を経由して OCI WAF を使う場合
Enable SNI--is-sni-enabledTLS 拡張の Server Name Indication (SNI) を有効化します。WAF→オリジンへの TLS ハンドシェイク時に Host 名 を渡せるため、1 つの IP:Port で複数バーチャルホストを運用しているオリジンでも正しい証明書が返せます。 (Oracle クラウド インフラドキュメント)オリジンが SNI 必須の構成(例: SSL 証明書を vhost 単位で切り替えている Nginx/Apache)になっている場合

設定したら publish all てボタンが出るので押さなきゃダメ。→反映には10分~30分くらいかかるらしい。

DNSの書き換え

CNAME Targetが表示されるのでDNSに登録
image-13.png

Aレコード消して、CNAMEを登録
image-14.png

速度計測

いろいろ計測ツール(Webサービス)があるらしい

  • Google PageSpeed Insights
  • GTmetrix
  • Pingdom Website Speed Test
  • WebPageTest

その中で手軽そうなPingdom Website Speed Testで実施 

https://tools.pingdom.com/

from: Asia/Tokyoで計測

staging.fuku.tokyo(WAF使ってない)

image-17.png

staging.fuku.tokyo(WAF)

stagingsub.fuku.tokyo(WAF) →ページサイズが違くて確認ならなかったが遅いっちゃ遅い

image-18.png

www.fuku.tokyo(Tokyo region)

image-19.png

Chrome のDeveloper Toolsでもいい感じにできる。こっちのほうがいいかな。Shift + F5で計測
staging.fuku.tokyo(WAF使ってない)
image-20.png

staging.fuku.tokyo(WAF)

www.fuku.tokyo(Tokyo region)
image-21.png

結局速くはならなかった。WordPressみたいな動的生成ページにはCDN不向きっていう根本的な話もあるので。
とりあえずしばらくはこれで運用してみようかな。