web_bonsaiの日記

web開発の学習日記です。誰に見せるためでもないただの日記です。

nginx.conf内でリクエストヘッダーとリファラをチェックして403を返す | Mac + Docker + Rails + Next.js その0048

はじめに

  • Next.jsでフロントエンドを作成して、そのドメインweb-bonsai.tech としています。
  • RailsAPIを作成して、そのドメインapi.web-bonsai.tech としています。

APIはweb-bonsai.techからのリクエストだけ受け付けたいので、リクエストヘッダーとリファラをチェックするようにしたときのメモです。

nginx.confはGitHubリポジトリにはコミットされないように、.gitignoreしています。

nginx.confを編集する

以下のようにしてみました。

  server {
    〜略〜

    # invalidなリクエストかどうか判定するための変数を定義
    set $invalid_request '';

    # $invalid_refererのとき$invalid_requestを1にする
    valid_referers server_names example.com;
    if ($invalid_referer) {
      set $invalid_request 1;
    }

    # $invalid_refererだったとしても、headers['X-API-PASSWORD']が一致したとき$invalid_request = ''に戻す
    if ($http_x_api_password = 'abcde') {
      set $invalid_request '';
    }

    if ($invalid_request) {
      return 403;
    }

    〜略〜
  }

nginx.confのコードについての説明

まず判定用の変数$invalid_requestを定義して、空文字列を入れておきます。

    # invalidなリクエストかどうか判定するための変数を定義
    set $invalid_request '';

リファラが無い場合や、リファラのサーバ名がexample.comでない場合は、以下のブロックで変数$invalid_requestが1になります。

    # $invalid_refererのとき$invalid_requestを1にする
    valid_referers server_names example.com;
    if ($invalid_referer) {
      set $invalid_request 1;
    }

Next.jsでSSGビルドをするとき、getStaticProps()内でfetch()すると、リファラが無いので上記のブロックで変数$invalid_requestが1になってしまいますが、リクエストヘッダーに正しい X-API-PASSWORD が存在する場合は、変数$invalid_requestを空文字列に戻します。
ここでは 'abcde' と比較していますが、実際には文字数や文字種の多いパスワードと比較します。

    # $invalid_refererだったとしても、headers['X-API-PASSWORD']が一致したとき$invalid_request = ''に戻す
    if ($http_x_api_password = 'abcde') {
      set $invalid_request '';
    }

$invalid_requestが空文字列でない場合は403を返します。

    if ($invalid_request) {
      return 403;
    }

Next.jsのgetStaticProps()の記述内容

以下の通り、headersを設定して送信すると、nginx.conf内では変数$http_x_api_passwordで参照できます。

export const getStaticProps = async () => {
  const response = await fetch(`${process.env.API_BASE_PATH}/api/tasks`, {
    headers: {
      'X-API-PASSWORD': process.env.X_API_PASSWORD ?? '',
    },
  });
  const props = await response.json();

  return {
    props,
  };
};

環境変数の定義

frontend/.env.localに以下の通り定義します。

X_API_PASSWORD=abcde

frontendサービスでyarn buildする

いつも通り以下のコマンドでビルドします。

$ docker-compose run --rm frontend yarn build

おわりに

nginx.conf内で2つの変数の文字列を結合して判定したり、正規表現を使って判定するともっと適切な書き方がありそうな気がします。

また、web-bonsai.techのフォーム入力などからリクエストする場合には、httponlyでsecureなcookieなどを使って判定した方がよさそうだなと思っていますが、nginx.confに慣れていなくてつらいので今のところはここまでにしておきます。

POSTしたときに422エラーでActionController::InvalidAuthenticityTokenになって失敗したので解決したい | Mac + Docker + Rails + Next.js その0048

参考にさせていただいたページ

はじめに

解決できませんでしたが色々試行錯誤した個人的なログとして残しています。

環境について

この記事はローカル環境での話です。

docker-composeで、railsアプリケーションには https://localhost でアクセスできるようにしていて、next.jsアプリケーションには https://localhost:10443 だったり、https://localhost:3001だったりでアクセスできるようにしています。

解決したいログ(ActionController::InvalidAuthenticityToken)

https://localhost:10443からhttps://localhostに、POSTリクエストしたときにrailsのログに以下のように表示されました。

| Started POST "/api/tasks" for 172.25.0.1 at 2022-09-11 00:48:57 +0000
| Cannot render console from 172.25.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1
| Processing by Api::TasksController#post as */*
|   Parameters: {"name"=>"aaaaaaaaaa", "description"=>"", "task"=>{"name"=>"aaaaaaaaaa", "description"=>""}}
| HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)
| Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms | Allocations: 444)
| 
| 
|   
| ActionController::InvalidAuthenticityToken (HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)):
| 
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token'
| activesupport (7.0.3.1) lib/active_support/callbacks.rb:400:in `block in make_lambda'

ログを読んでみる

以下の部分はデバッグ用のrails consoleに関するメッセージのため、今回は関係ありません。

| Cannot render console from 172.25.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1

送信したパラメーターのオブジェクト構造が間違っていますね。

| Parameters: {"name"=>"aaaaaaaaaa", "description"=>"", "task"=>{"name"=>"aaaaaaaaaa", "description"=>""}}

送信したNext.jsのアプリケーションのオリジン(https://localhost:10443)とRailsアプリケーションのオリジン(https://localhost)が違います。

| ActionController::InvalidAuthenticityToken (HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)):

ステータスコード422であることがわかります。

| Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms | Allocations: 444)

request_forgery_protection.rbの中で判定をしていそうです。

| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token'

パラメータが違う

まずパラメータが違いますね。

{
  name: 'aaaaaaaaaa',
  description: '',
}

を送っていますが、

{
  task: {
    name: 'aaaaaaaaaa',
    description: '',
  },
}

を送らないといけませんでした。今回の場合。

パラメータを修正して再送信してログを確認する

パラメータを修正して再度POSTしたら以下のようになりました。

パラメータに関するログが変化しましたが、ステータスコード422であることには変化がありません。

| Started POST "/api/tasks" for 172.24.0.1 at 2022-09-16 06:15:24 +0000
| Cannot render console from 172.24.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1
| Processing by Api::TasksController#post as */*
|   Parameters: {"task"=>{"name"=>"aaaaaaaaaa", "description"=>""}}
| HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)
| Completed 422 Unprocessable Entity in 3ms (ActiveRecord: 0.0ms | Allocations: 446)
| 
| 
|   
| ActionController::InvalidAuthenticityToken (HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)):
|   
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token'
| activesupport (7.0.3.1) lib/active_support/callbacks.rb:400:in `block in make_lambda'

オリジンのチェックをしないようにする

rails/config/environments/development.rbに以下の設定を追加します。

# クライアント側とドメインが異なるため、クロスオリジンの検証を無効にする
config.action_controller.forgery_protection_origin_check = false

docker-compose down と up をし直す

一度 docker-compose down して up し直します。

再送信してログを確認する

再度POSTしたら以下のようになりました。

HTTP Origin headerに関するログが2箇所変化しましたが、ステータスコード422であることには変化がありません。

| Started POST "/api/tasks" for 172.24.0.1 at 2022-09-16 06:31:36 +0000
| Cannot render console from 172.24.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1
|   ActiveRecord::SchemaMigration Pluck (4.1ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
| Processing by Api::TasksController#post as */*
|   Parameters: {"task"=>{"name"=>"aaaaaaaaaa", "description"=>""}}
| Can't verify CSRF token authenticity.
| Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms | Allocations: 583)
| 
| 
|   
| ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.):
|   
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request'
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token'
| activesupport (7.0.3.1) lib/active_support/callbacks.rb:400:in `block in make_lambda'

一旦AuthenticityTokenの確認を無効にする

あくまで一旦です。

セキュリティ面を犠牲にして、とにかくPOSTできるようにしたい。ということになってしまいます。

AuthenticityTokenの確認を無効にしたいcontrollerに以下の記述を追加します。

今回の私の場合は、rails/app/controllers/api/tasks_controller.rbを編集します。

class TasksController < ApplicationController
  skip_before_action :verify_authenticity_token

  〜略〜
end

再送信してログを確認する

再度POSTしたら以下のようになりました。

ステータスコード200になって、レコードが追加されました。

| Started POST "/api/tasks" for 172.24.0.1 at 2022-09-16 06:48:12 +0000
| Cannot render console from 172.24.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1
| Processing by Api::TasksController#post as */*
|   Parameters: {"task"=>{"name"=>"aaaaaaaaaa", "description"=>""}}
|   TRANSACTION (0.4ms)  BEGIN
|   ↳ app/controllers/api/tasks_controller.rb:18:in `post'
|   Task Exists? (0.7ms)  SELECT 1 AS one FROM "tasks" WHERE "tasks"."name" = $1 LIMIT $2  [["name", "aaaaaaaaaa"], ["LIMIT", 1]]
|   ↳ app/controllers/api/tasks_controller.rb:18:in `post'
|   Task Create (12.5ms)  INSERT INTO "tasks" ("name", "description", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["name", "aaaaaaaaaa"], ["description", ""], ["created_at", "2022-09-16 06:48:12.631905"], ["updated_at", "2022-09-16 06:48:12.631905"]]
|   ↳ app/controllers/api/tasks_controller.rb:18:in `post'
|   TRANSACTION (2.2ms)  COMMIT
|   ↳ app/controllers/api/tasks_controller.rb:18:in `post'
|   Task Load (0.3ms)  SELECT "tasks".* FROM "tasks" ORDER BY "tasks"."created_at" DESC
|   ↳ app/controllers/application_controller.rb:3:in `map'
| Completed 200 OK in 39ms (Views: 0.2ms | ActiveRecord: 16.1ms | Allocations: 5153)

AuthenticityTokenの確認を有効にする

先ほど追加した以下の行を削除します。

  skip_before_action :verify_authenticity_token

AuthenticityTokenをちゃんと確認してPOSTできるようにする

レスポンスヘッダーにAuthenticityTokenを入れるメソッドを定義する

レスポンスヘッダーにauthenticity_tokenを入れるためのset_csrf_token_headerメソッドをrails/app/controllers/application_controller.rbに定義します。

class ApplicationController < ActionController::Base
  def set_csrf_token_header
    response.set_header('X-CSRF-Token', form_authenticity_token)
  end
end

各アクションの後にset_csrf_token_headerを実行する

今回の私の場合は、rails/app/controllers/api/tasks_controller.rbを編集します。

各アクションの後にset_csrf_token_headerメソッドを実行します。

class TasksController < ApplicationController
  after_action :set_csrf_token_header

  〜略〜
end

X-CSRF-Tokenヘッダをexposeする

rack-corsの設定を変更します。

rails/config/initializers/cors.rbのを編集します。

expose: ['X-CSRF-Token'], を追記して以下のようにします。

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "*"
    resource "*",
             headers: :any,
             methods: [:get, :post, :put, :patch, :delete, :options, :head],
             expose: ['X-CSRF-Token']
  end
end

docker-compose down と up をし直す

一度 docker-compose down して up し直します。

ブラウザでレスポンスヘッダーを確認する

再度POSTして、ブラウザでレスポンスヘッダーを確認したら、以下のようにX-CSRF-Tokenが追加されていることが確認できました。

X-CSRF-Tokenを受け取るためのエンドポイントの作成

POSTする前に受け取りたいのでそのためのエンドポイントを作成します。

X-CSRF-Tokenを受け取る

以下のようにすると受け取れました。

const token = response.headers.get('X-CSRF-Token');

X-CSRF-Tokenをリクエストヘッダーに入れてPOSTしてみる

POSTしてみたものの以下の状態は解消されず、一旦諦めました...。

ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.)

おわりに

難しい。

本番環境のバックエンドのアクセス制限をする | Mac + Docker + Rails + Next.js その0047

参考にさせていただいたページ

はじめに

web-bonsai.techサーバーからのリクエスト以外は、api.web-bonsai.techで403エラーを返すことにします。

api.web-bonsai.techのnginx/nginx.confを編集する

以下のようにリファラでリクエスト元のサーバー名をチェックして403を返すようにしました。

  server {
    〜略〜

    valid_referers server_names web-bonsai.tech;
    if ($invalid_referer) {
      return 403;
    }

    〜略〜
  }

frontendサービスを別サーバーに移管する | Mac + Docker + Rails + Next.js その0046

frontendサービス用のサーバーを契約してセットアップする

以前と同じく契約して

  • サーバーのセキュリティ設定
  • vimのインストール
  • gitのインストール
  • dockerのインストール
  • docker-composeのインストール

などのセットアップをします。

frontendサービス用のリポジトリを作成してサーバーにcloneする

だいたい以前やった手順を参考に行います。

frontendサービスを本番環境でブラウザからアクセスできるようにする

だいたい以前やった手順を参考に行います。

ブラウザで確認してみる

ブラウザで以下のURLにアクセスしてみます。

とりあえずバックエンドもフロントエンドもそれぞれのサーバーで表示できるようになりました。

以下のドメインでそれぞれ運用していきます。

  • api.web-bonsai.tech
  • web-bonsai.tech

バックエンドとフロントエンドを別々のサーバーで運用したいのでバックエンドをサブドメインで公開する | Mac + Docker + Rails + Next.js その0045

やること

web-bonsai.techドメインで公開していたrailsアプリケーションのドメインapi.web-bonsai.techに切り替えていきます。

Next.jsで作っているfrontendサービスはこれを機に別サーバーに移管して運用します。

docker-compose.ymlを編集する

https-portalサービスのenviroment.DOMAINSを以下のようにします。

    environment:
      DOMAINS: 'api.web-bonsai.tech -> http://nginx:8000'

appサービスのenviroment.RAILS_ALLOW_HOST1を以下の通りにします。

    environment:
      - RAILS_ALLOW_HOST1=api.web-bonsai.tech

rails/config/environments/development.rbとproduction.rbの修正

この環境変数RAILS_ALLOW_HOST1は、

  • rails/config/environments/development.rb
  • rails/config/environments/production.rb

で以下のように使用されています。

config.hosts << ENV.fetch("RAILS_ALLOW_HOST1")

nginx/nginx.confを編集する

以下の、serverのserver_nameのところのweb-bonsai.techとなっていたところを以下のようにapi.web-bonsai.techとします。

 server {
  〜略〜
  server_name api.web-bonsai.tech
  〜略〜
}

docket-compose downとupする

downします。

$ docker-compose down

upします。

$ docker-compose up -d

ブラウザで確認してみる

ブラウザで

残タスク

  • frontendサービスを切り離して別のサーバーで運用する
  • api.web-bonsai.techはweb-bonsai.techからのリクエストだけ受け付けるようにアクセス制限する
  • frontendサービスに関する記述をapi.web-bonsai.techのリポジトリから削除する
    • docker-compose.yml
    • nginx/nginx.conf
    • .husky/pre-push

さくらのVPSとメールボックスを同ドメインで利用するネームサーバ設定をサブドメインでやる | Mac + Docker + Rails + Next.js その0044

はじめに

APIサブドメインで運用して、フロントエンドと分けたくなったので、 api.web-bonsai.tech のネームサーバ設定をしていきます。

ネームサーバ新規登録

以前やった「さくらのVPSとメールボックスを同ドメインで利用するネームサーバ設定 | Mac + Docker + Rails その0013 - web_bonsaiの日記」を参考にして、「ネームサーバ新規登録」をします。

さくらのVPSのコントロールパネルにログインし、サイドバーのメニューから「DNS登録」をクリックします。

「ネームサーバを登録」セクションのフィールドに、ドメイン api.web-bonsai.tech を入力して「ドメインを登録する」ボタンをクリックします。

「ネームサーバサービス」ページが表示され、 api.web-bonsai.techゾーン状態は「未設定」になっています。

さくらのレンタルサーバ独自ドメインを追加

同じページを参考に進めていきます。

さくらのレンタルサーバコントロールパネルにログインし、サイドバーのメニューから「ドメイン/SSL」の「ドメイン/SSL」をクリックします。

ドメイン新規追加」ボタンをクリックします。

他社でドメインを取得したので、「他社で取得したドメインを移管せずに使う」のセクションの「追加」ボタンをクリックします。

「他社で取得された独自ドメインサブドメインを追加」のセクションのフィールドを以下の通り入力し、「追加」ボタンをクリックしました。

api.web-bonsai.techドメインが追加されました。

ドメインコントロールパネルでネームサーバのMXレコード情報を書き換える

参考ページに重要事項として書きましたが、先にMXレコード情報を書き換えます。

サイドバーから「ネームサーバサービス」をクリックします。

「編集」ボタンをクリックして、MXの 10 @ となっている箇所を
10 ○○.sakura.ne.jp. に書き換えます。

○○の部分はさくらのメールボックスまたはさくらのレンタルサーバのアカウント名です。
また、ここでは最後の . を忘れないように注意してください。

ネームサーバのAレコード情報を書き換える

MXレコード情報が済んだので、次はAレコード情報を書き換えます。

Aレコード情報をVPSIPアドレスにします。

さっき書き換えたMXの上の「A」の項目です。

ドメインのネームサーバ設定

ムームードメインにログインして行う設定です。

ドメインを登録するときは必要ですが、サブドメインのときはたぶん不要だと思います。

たぶんですが。

メールユーザーの新規追加

さくらのレンタルサーバコントロールパネルにログインし、サイドバーから「メール」をクリックして、メールアドレス一覧ページに遷移します。

以下の画像の「新規追加」ボタンをクリックし、メールユーザーを必要に応じて新規追加します。

送信してみる

さくらのレンタルサーバWebメールのページから試しに送信してみると、ちゃんと送信できるはずです。

pre-commit時にhuskyでfrontendのtest,、eslint、robocop、rspecを実行する | Mac + Docker + Rails + Next.js その0043

参考にさせていただいたページ

はじめに

今回の手順はfrontendディレクトリではなく、リポジトリのrootディレクトリで行っていきます。

インストール

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

% yarn add -D husky

Enable Git hooks

Git hooksを有効化します。

% npx husky install

以下のように表示されました。

husky - Git hooks installed

package.jsonにscripts.prepareを追記

以下のようになりました。

{
  "scripts": {
    "prepare": "husky install"
  },
  "devDependencies": {
    "husky": "^8.0.1"
  }
}

Create a hook

.husky/pre-commitファイルを作成します。

% npx husky add .husky/pre-commit "yarn --cwd frontend lint"

このとき生成されるファイルは以下の記述内容です。

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn --cwd frontend lint

.husky/pre-commitの編集

一行追記して、以下の通りに編集しました。

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

echo 'frontend lint を実行します。'
yarn --cwd frontend lint

echo 'frontend test:ci を実行します。'
yarn --cwd frontend test:ci

echo 'rubocop を実行します。'
docker-compose run -T --rm app rubocop

echo 'rspec を実行します。'
docker-compose run -T --rm app rspec

git commitして動くか確認してみる

普通にaddして、commitしたら動きました。

git pushのときに実行するには

今回の手順で作成した.husky/pre-commitのファイル名を.husky/pre-pushに変更するだけでOKです。

commitする度に実行するのは時間がかかり過ぎるなと思ったらpre-pushで実行するのも良いと思います。