Web / Security / Infrastructure
edencode.jp は、最初はただの名刺代わりの静的ページとして立ち上げた。Home があって、Works があって、連絡先は mailto: でメーラーが開くだけ。動いてはいるけれど、「運用している」と胸を張れる状態ではなかった。
これから仕事の依頼も受けるし、記事もどんどん増えていく。そうなった時に困るのは、見た目のクオリティよりも見えないところの作り込みだ。問い合わせフォームに bot が押し寄せたら、スパムメールに埋もれる。メールの送信元認証が無ければ、迷惑メールに振り分けられる。シークレットキーをコードに直書きしていたら、うっかり GitHub に公開した瞬間に乗っ取られる。
この日は「見た目を増やさない日」にした。代わりに、簡易ページから本番品質のサイトへ昇格させるための裏側の工事を一気通貫でやっていく。同じ手順で自分のサイトも固めたい人のために、順を追って書いていく。
セキュリティの前に、まず構造から。理由は単純で、構造が散らかっていると、セキュリティの穴を塞いだつもりでも別のページだけ抜けている、という事故が起きるから。
① 共通スタイルを 1 ファイルに集約する
カラー変数やリセット CSS、アクセシビリティ関連のような「全ページが使うもの」を css/site-common.css にまとめた。各ページでは <link> 1 行読み込むだけ。テーマカラーを変えたい時にこの 1 ファイルを直せば、サイト全体に反映される。
② 記事メタデータをデータファイルに分離する
これまで、トップページのブログカードは index.html の中に直書きしていた。新記事を追加するたびに HTML を触る構造だ。これを js/posts-data.js に切り出し、「配列の先頭に 1 件足すだけで新記事が反映される」形に変えた。
構造整理の指針は 「未来の自分を憎まない設計にする」。半年後にデザイン変えたい時、200 ファイル直すか、1 ファイル直すかの差はここで決まる。
本番サイトの中で一番攻撃対象になりやすいのがフォームだ。ここを mailto: から PHP の送信フォームに切り替えて、防衛ラインを複数重ねていく。1 つだけ強い壁を作るより、薄い壁を 5 枚重ねるほうが実戦では堅い。
① CSRF トークン — フォーム表示時にランダムな使い捨てトークンを発行してセッションに保存し、送信時に一致するかチェックする。これで他サイトから偽装リクエストを投げる攻撃(CSRF)を防げる。
② ハニーポット — 人間には見えない隠しフィールドを用意する。フォームを自動で全部埋めてくる bot はここにも値を入れるので、そこに値が入っていたら即 bot 判定。見えないから人間が間違って埋める心配はない。
③ 最低滞在時間チェック — フォームを表示した時刻を hidden で送り、受信側で「1 秒未満の送信は bot」として弾く。人間はどんなに早くても 2〜3 秒はかかる。
④ Cloudflare Turnstile — 「このチェックを押してください」の新しい形。画像を選ばせる CAPTCHA と違って、裏側でブラウザの挙動を見て人間かどうかを判定する。ユーザーは何も押さなくていいのが強み。
⑤ IP ベースのレート制限 — 同じ IP から 60 秒に 3 回以上送信されたら拒否する、というのをファイルベースで記録する。DB を用意しなくても動く簡易版。
おまけとして、メールヘッダインジェクション対策(入力値から改行コードを除去)、入力長の上限、カテゴリの許可リスト検証もセットで入れる。このへんは「やっていないと普通に狙われる」基本装備。
.env に追い出すTurnstile を動かすには、シークレットキーを PHP 側に持たせる必要がある。ここで絶対にやってはいけないのがコードに直書きすること。理由は単純で、コードは GitHub に push するから。公開リポジトリでも、プライベートリポジトリでも、共同作業者が増えた瞬間に漏れる。
解決策が .env ファイル。シークレットは全部ここに書き、コード側は環境変数として読む。
PHP 側には軽量ローダを自前で実装した。標準ライブラリで .env を読む仕組みは無いが、file() で 1 行ずつ読んで KEY=VALUE を putenv() に流し込むだけで十分動く。
配置場所が重要で、Xサーバーなど一般的な共有サーバーでは、public_html の一つ上のディレクトリに置く。/home/<user>/edencode.jp/.env のような位置だ。こうすると Web 経由で URL を叩いても物理的に取得できないし、PHP からは相対パスで読めるので運用に支障はない。
そして .gitignore に以下を追加して、うっかりコミット事故を防ぐ。
.env.example だけは残してリポジトリに入れておく。「どのキーを設定すればいいか」のサンプルになり、新しい環境にデプロイする時の手順書代わりになる。
.htaccess で「見せないものは見せない」万が一、.env や内部メモの .md ファイルを間違ってアップロードしてしまっても、Web 経由で覗けないようにしておく。これは Apache サーバー(Xサーバー含む)なら .htaccess で指定できる。
これで、https://edencode.jp/.env を叩かれても 404 が返るし、UPGRADE_NOTES.md のような内部用メモも外からは見えない。「置いてはいけないものは置かない」が大原則だけど、二段目の保険として必ず入れておく。
独自ドメインでメールを出すなら、絶対にやるのがこの 3 つ。設定していないと、自分が送ったメールが相手の迷惑メールフォルダに直行するし、第三者が「edencode.jp を名乗って」スパムを撒くことも防げない。
SPF(Sender Policy Framework)
DNS に「このドメインから送っていい送信サーバーはこれ」というリストを書いておく。受信側は、メールの送信元サーバーがリストに載っているかを照合する。
DKIM(DomainKeys Identified Mail)
送信時にメール本文へ電子署名を付ける。受信側は DNS から公開鍵を取ってきて、署名を検証する。これで途中で改ざんされていないことが保証できる。
DMARC(Domain-based Message Authentication)
「SPF や DKIM の検証に失敗したメールをどうするか」の方針を DNS に書いておく仕組み。3 段階ある:
p=none — 監視のみ(何もしないで、どれが失敗したかだけレポートを集める)p=quarantine — 迷惑メールフォルダに振り分けさせるp=reject — 受信側に拒否させるここで一つ罠がある。いきなり p=reject を設定してはいけない。SPF/DKIM の設定が完全でないうちにこれをやると、本物のメールも弾かれて誰にも届かなくなる。
正しい手順は:
p=none で 1〜2 週間様子を見る(レポートが届くので、誤判定がないか確認)p=quarantine に引き上げるp=reject にするXサーバーなら管理画面の「メール認証」から SPF と DKIM をワンクリックで有効化できて、DMARC は DNS の TXT レコードに 1 行追加するだけ。
サイトを作っただけでは Google は勝手には見にこない(来ても時間がかかる)。最短で検索に乗せるには、Google Search Console への登録と sitemap.xml の提出が必要になる。
手順はシンプル:
edencode.jp を登録(URL プレフィックスではなくドメイン推奨。サブドメインも全部カバーできる)google-site-verification=... の文字列を渡される)sitemap.xml の URL を送信するここでも 1 つ罠がある。DNS の管理画面で「追加」と「編集」を取り違えないこと。既存の A レコード(サーバーの IP を指している大事なレコード)を編集してしまうと、サイト本体が引き当たらなくなる。必ず「TXT レコードを新規追加」するモードで作業する。
sitemap.xml は、サイト内の全ページの URL をまとめた XML ファイル。手書きでも作れるが、記事が増えてきたら自動生成スクリプトを仕込んでおくと楽。
この日やった作業は、表の画面には何一つ新しく出てこない。記事が増えたわけでも、デザインが変わったわけでもない。増えたのは、壊れにくさ・なりすまされにくさ・運用で事故らない仕組みの方だ。
プロとアマの差って、たぶんここの積み重ねにある。見える機能は誰でも作れる。でも見えない防衛ラインをどこまで引くかは、作り手の思想がそのまま出る。
そして、この工事が終わったからといって「完璧」にはならない。攻撃手法は日々進化するし、サービスのベストプラクティスも更新される。本番サイトは作って終わりじゃなく、育てていくもの。今日はその育て方を覚えた日でもあった。
site-common.css に集約 / 記事メタデータを posts-data.js に分離.env を public_html の外に配置 / .gitignore で除外 / .env.example はサンプルとしてコミット.htaccess でドットファイル・.md・.log・.sh・.env・.bak を 404 にp=none から段階運用sitemap.xml 送信見た目は変わらない。でも、ここから先に乗せる記事や作品は、この土台の上で安心して発信できる。土台ができた、という事実そのものがこの日いちばんの成果だった。