TechTips

Categories

Latest comments

Ruby 4.0でrdocを追加インストールするとwarningが出るようになる

wakairo @wakairo

Ruby 4.0では、rdoc gemについて最新バージョンなどを追加インストールすると、gemコマンドの実行で大量のwarningが表示される状態に陥いります。

なお、開発サイドもこの状態を把握しているようですので、個人的には解決されるまで様子を見ようと思っています。

0
Raw
https://www.techtips.page/en/comments/1172

Docker Composeのサービス名にappやdevを使うとSeleniumのHTTP接続がSSLエラーになる原因と対策

wakairo @wakairo

要点

Docker Compose の compose.yaml でサービス名を付ける際は、HTTP での接続先となるコンテナのサービス名には app や dev といったトップレベルドメイン(TLD)として実在する文字列を使わないほうが安全です。

具体的な対策

HTTP で接続するサービス(コンテナ)には、ハイフン(-)を用いて複数の単語を組み合わせた名前をつけるのが、簡単な解決策です。

  • 避けるべきサービス名の例:app, dev
  • 推奨されるサービス名の例:rails-app, web-server

なぜエラーになるのか

Docker Compose のネットワーク内では、サービス名で名前解決をして他コンテナへアクセスできます。そのため、Selenium コンテナ内のブラウザから http://app/ のようにアクセスする設定にすることがあります。

しかしブラウザは、その app を Docker 内部の名前としてではなく、通常のホスト名として扱います。 app や dev は実在の TLD であり、しかも HSTS(HTTP Strict Transport Security)を強制する対象なので、ブラウザは http://app/ を https://app/ に書き換えます。 接続先が HTTPS 非対応なら、結果として ERR_SSL_PROTOCOL_ERROR や Are you trying to open an SSL connection to a non-SSL Puma? のようなエラーになります。

一方、rails-app のように、TLD と一致しない名前にすれば、HSTS 強制の対象から外れ、HTTPS に書き換えられないため、エラーを防ぐことができます。

背景:何が起きているか

Docker の compose.yaml の services で定義したサービス名は、Docker ネットワーク内で他コンテナへアクセスする際のホスト名として使えます。ここでのホスト名とは、http://example.com/ の example.com に当たります。

サービス名にドット(.)が含まれていない場合、単一ラベルのホスト名となり、ブラウザはその名前を TLD として扱います。例えば、app というサービス名は、app という TLD のみからなるホスト名として認識されます。

TLD のみのホスト名のうち、app や dev は HSTS 強制の対象となっており、HSTS プレロードリストなどの仕組みにより、HTTPS 接続が強制されます。そのため、http://app/ という HTTP での接続になるよう設定しても、ブラウザは強制的に https://app/ という HTTPS での接続へと書き換えて接続しようとします。

結果、app や dev のような HSTS が強制される名前を持つサービス(コンテナ)に対して、Selenium コンテナ内のブラウザは、HTTP で接続するよう設定されていても、HTTPS で接続してしまいます。 さらに、接続される側のコンテナが HTTPS に対応していない場合、接続がエラーとなります。

このように、E2E テストやシステムテストでは「ブラウザがどう解釈するか」は無視できない事項です。 コンテナ間疎通の都合だけで名前を決めるのではなく、ブラウザが特殊な扱いをする名前ではないか、という観点も持っておくとトラブルを減らせます。

注意点

  • TLD、及び、HSTS が強制されるホスト名は追加が続いています。そのため、将来のトラブルを防ぐ観点では、現状でトラブルになる名前だけでなく、今後トラブルになりそうな名前全般を避けたほうが無難だと考えられます。
  • HSTS はホスト名ベースで効き、ポート番号では回避できません。つまり http://app:3000/ のような URL でもエラーになり得ます。
  • ホスト名にドットを含めることは、本件の SSL エラーの確実な回避策とはなりません。なぜなら、HSTS 強制の対象となっているホスト名としては、TLD のみのホスト名だけではなく、facebook.com や www.amazon.com といったドットを含むホスト名も多数存在するためです。

接続エラーに遭遇した状況

compose.yaml において、selenium/standalone-chromium イメージの Selenium コンテナからサービス名が app のコンテナへ HTTP で接続する設定をしたところ、以下のエラーが発生しました。

2026-03-25 05:37:53 +0000 HTTP parse error, malformed request: #<Puma::HttpParserError: Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?>
[Screenshot Image]: /x/tmp/screenshots/failures_test_should_destroy_Article.png
E

Error:
ArticlesTest#test_should_destroy_Article:
Selenium::WebDriver::Error::UnknownError: unknown error: net::ERR_SSL_PROTOCOL_ERROR
  (Session info: chrome=145.0.7632.109)
    test/system/articles_test.rb:38:in 'block in <class:ArticlesTest>'

なお、サービス名を app から rails-app に変更したところ、無事意図したとおりに動くようになりました。 HSTS 強制の対象となる TLD(app)との一致を回避した結果、ブラウザが HTTPS へ強制的に書き換えなくなったためと考えられます。

参考資料

0
Raw
https://www.techtips.page/en/comments/1139
❤️1

PmRails v1.1.0 をリリースしました。

wakairo @wakairo

リリースの概要

  • 新コマンド pmrails-new-plus : アプリの作成・gem のインストール・.gitignore の更新を1ステップで自動化します。
  • プロジェクトローカルなruntimeディレクトリ .pmrails/var/ : gem・キャッシュ・設定ファイルなどをプロジェクトごとに隔離して管理するようになりました。
  • SELinux / Fedora Immutable サポート : 全コマンドに Podmanの --userns=keep-id オプションを追加しました。

アップデート時の注意:ランタイムディレクトリ変更のため、pmbundle installの再実行が必要です。

リリースノートと変更履歴はGitHubで確認できます: https://github.com/wakairo/pmrails/releases/tag/v1.1.0

0
Raw
https://www.techtips.page/en/comments/1123
🎉1
❤️1

Rails 8.0からRails 8.1への移行(アップデート、アップグレード)で必要な作業

wakairo @wakairo

Rails 8.1におけるconfig.content_security_policy_nonce_auto

config.content_security_policy_nonce_autoは、 Rails 8.1で追加された「CSPのnonceを自動で追加する機能」を有効にするかどうかの設定です。

rails newで生成されるconfig/initializers/content_security_policy.rbにおいて、 8.1からは以下のようにこの設定のコードが追加されました(この変更のPRとこの変更のCommit)。

+#   # Automatically add `nonce` to `javascript_tag`, `javascript_include_tag`, and `stylesheet_link_tag`
+#   # if the corresponding directives are specified in `content_security_policy_nonce_directives`.
+#   # config.content_security_policy_nonce_auto = true
+#

デフォルトではCSP設定自体がコメントアウトされており、この機能の設定もコメントアウトされています。

Railsガイドでは、CSPについての説明が「Content-Security-Policyヘッダー」の項目にあり、 このnonceを自動で追加する機能の説明が「nonceを追加する」の項目にあります。

0
Raw
https://www.techtips.page/en/comments/1122
😄1

vscodeのdevcontainerでCodexを使うのを今は見送りにした理由

wakairo @wakairo

Codex(OpenAIのコーディング・エージェント)をdevcontainer(開発用コンテナ)の中に閉じ込めて動かすのは、 セキュリティの確保もできて、なかなか良いアイデアなのではと思いました。 ところが、vscodeで実際に試してみたところ、Codexのセキュリティの仕組みと絡んで、不都合な挙動があったので、 このやり方は公式にサポートされるまでは様子見が良いかなと今は思っています。

確認したこと

まずvscodeでdevcontainerを起動しました。 設定ファイル.devcontainer/devcontainer.jsonの中身は以下の通りです。

{
        "name": "Ruby",
        "image": "mcr.microsoft.com/devcontainers/ruby:3-3.4"
}

次に、Codex拡張をインストールしました。 その次に、Codexにソースファイルの一部を変更する指示を出したところ、 apply_patchが上手くいかないのでsedなどを代わりに使うという返答で、 変更前後のdiffが見づらい形になりました。

なお、devcontainerの設定で指定するimageは以下の2つも試しましたが、 同じ現象に遭遇しましたので、特定のイメージでのみ発生する現象ではありません。

  • ruby:3.4(Rubyの公式イメージ)
  • ghcr.io/openai/codex-universal:latest(OpenAIが公開している参考Dockerイメージ)

apply_patchが失敗する背景

Codexが言うには、apply_patchはbwrapという一種のコンテナの中で動かす仕組みらしく、 devcontainerというコンテナの中でbwrapというコンテナを動かす、 つまり、コンテナの中でコンテナを動かすのに失敗しているらしいです。 具体的には、devcontainerの中でbwrapのコンテナのために新しいnamespaceを作ろうとして、 permissionの問題に遭遇しているとのこと。

Codexがbwrapを使うのはセキュリティを高めるためだと思いますので、 安易な設定変更などでapply_patchを出来るようにするのは、 セキュリティ上のリスクがあると見ています。

0
Raw
https://www.techtips.page/en/comments/1121

Rails 8.0からRails 8.1への移行(アップデート、アップグレード)で必要な作業

wakairo @wakairo

Rails 8.1におけるRails.configuration.action_view.remove_hidden_field_autocomplete

config.action_view.remove_hidden_field_autocompleteは、hiddenフィールドからautocomplete="off"属性を除去するかどうかの設定です。 (この機能のPRとこの機能のCommit) bin/rails app:updateコマンドが生成する config/initializers/new_framework_defaults_8_1.rbには、 以下のようにこの設定を有効にするコードがあります。

+  # Highlight code that triggered redirect in logs.
+  config.action_dispatch.verbose_redirect_logs = true
+

この機能が導入された背景

この機能は、HTML標準へ より準拠したHTMLをRailsが生成するように追加されました。

具体的には、この機能を有効にしない場合、Railsは以下のようなautocomplete="off"属性を付けたhiddenフィールドを生成していました。

<input type="hidden" name="authenticity_token" value="abc123..." autocomplete="off">

このautocomplete属性付きのhiddenフィールドはHTML標準に沿っておらず、 Nu Html Checkerは 以下のメッセージとともに「エラー」として指摘していました。

An “input” element with a “type” attribute whose value is “hidden” must not have an “autocomplete” attribute whose value is “on” or “off”.

0
Raw
https://www.techtips.page/en/comments/1120

Rails 8.0からRails 8.1への移行(アップデート、アップグレード)で必要な作業

wakairo @wakairo

Rails 8.1におけるconfig.action_dispatch.verbose_redirect_logs

config.action_dispatch.verbose_redirect_logsは、リダイレクトのソース位置を関連するログ行の下にログ出力するかどうかの設定です。

rails newで生成されるconfig/environments/development.rbにおいて、 8.1からは以下のようにこの設定を有効にするコードが追加されました(この変更のPRとこの変更のCommit)。

+  # Highlight code that triggered redirect in logs.
+  config.action_dispatch.verbose_redirect_logs = true
+

development環境でデバッグする際に使える情報が増える形になると思いますので、既存アプリでもこの設定を追加しても良いかもしれません。

0
Raw
https://www.techtips.page/en/comments/1119

Rails 8.0からRails 8.1への移行(アップデート、アップグレード)で必要な作業

wakairo @wakairo

基本的にはRailsガイドの手順に従えば良いと思います。

ガイドの手順にもありますが、ぜひbin/rails app:updateコマンドを活用しましょう。

Rails 8.1への移行で対応が必要そうな個別の作業について、以下のコメントでそれぞれ取り上げますので、ご参考になれば幸いです。

0
Raw
https://www.techtips.page/en/comments/1118
😄1

Active Recordのgenerates_token_for:DBカラム不要で一時的なトークンを扱う機能

wakairo @wakairo

generates_token_forとは

generates_token_forは、特定の目的を持つトークンを生成し、そのトークンからレコードを検索・検証するための機能です。 Rails 7.1で標準機能として導入されました。

特長

  • DBへのデータ保存不要:トークンを保存するためのデータベースのカラムやテーブルを追加せずに利用できます。
  • 目的別に分離:目的(purpose)ごとにトークンを分離できるため、別目的で生成されたトークンを使い回すことができません。
  • 失効条件を設定可能:有効期限とモデルのデータ変化による失効条件を柔軟に設定できます。
  • 改ざん耐性:署名付きのトークンのため、改ざん耐性があります。

使いどころ:一時的に有効なURLの送付

以下のような「一時的で目的が限定されたトークン付きURL」を送付する場面に適しています。

  • メールアドレス確認リンク
  • 招待リンク
  • マジックリンク(パスワードなしログイン)
  • 一時アクセスリンク

なお、APIの認証トークンのような永続的な用途には向きません。 Railsで永続的なトークンを扱いたい場合は、has_secure_tokenなどの利用を検討してください。

注意:パスワードリセットには専用APIあり

Rails 8.0以降でhas_secure_passwordを利用している場合、パスワードリセット用トークンを扱う専用APIが自動的に提供されます。そのため、パスワードリセットの用途に限っては、自分でgenerates_token_forを定義する必要はありません。(詳しくは後述の補足を参照してください)

使い方(メールアドレス確認の例)

ここでは「メールアドレス変更時の確認リンク」を題材に、基本的な使い方を説明します。

基本の流れ(定義・生成・検証)

まず、モデルにgenerates_token_forを用いて、トークンの用途名(ここでは:email_verification)を定義します。 なお、用途名が異なっていれば複数定義することも可能です。

class User < ApplicationRecord
  generates_token_for :email_verification
end

トークンを生成する際は、対象のインスタンスに対してgenerate_token_forを呼び出します。

注意:モデルで定義する際は複数形のgenerates_token_forですが、 インスタンスから呼び出す際は単数形のgenerate_token_forになる点に注意してください。

user = User.first # 対象のレコードを取得(ここでは`first`で代替)
token = user.generate_token_for(:email_verification)
# => "BAhJIi..." (生成されたトークン文字列)

生成したトークンは、例えば以下のようにURLパラメータに載せてメールなどで送付するのが典型的な流れです。

# 例:メイラー等でのURL生成
email_verifications_url(token: token)
# => "https://example.com/email_verifications?token=BAhJIi..."

受け取ったトークンからレコードを検索・検証するには、クラスメソッドのfind_by_token_forを使います。 有効なトークンならUserインスタンスが返り、無効なトークンや期限切れのトークンの場合はnilが返ります。

user = User.find_by_token_for(:email_verification, token)

有効期限による失効

定義時にexpires_inオプションを付与することで、トークンに有効期限(例:15分間)を設けることができます。

class User < ApplicationRecord
  generates_token_for :email_verification, expires_in: 15.minutes
end

モデルのデータ変化による失効

generates_token_forは、「一度状態が変わったら、古いトークンを使わせない」挙動を簡単に実装できます。 例えば、新しいメールアドレス確認用に生成されたトークンを、メールアドレスの変更が完了した後に無効化すれば、万が一流出しても悪用を防げます。

無効化の仕組みは「定義時のブロックの戻り値をトークンに埋め込み、検証時にその値が変わっていれば無効とみなす」というものです。

class User < ApplicationRecord
  generates_token_for :email_verification, expires_in: 15.minutes do
    # トークン生成時と検証時で`email`の値が異なれば(=メールアドレスが変更完了していれば)、
    # 有効期限内であっても無効になる
    email
  end
end
注意:ブロックの戻り値に機密情報を含めない

ブロック内で返した値は、改ざん耐性はあるものの、暗号化されるわけではなく、デコードすれば読める形でトークンに埋め込まれます。 そのため、ブロックの戻り値には機密情報を含めてはなりません。

補足:パスワードリセット専用API

Rails 8.0以降では、モデルでhas_secure_passwordを利用している場合、パスワードリセットトークン用の以下のメソッドがデフォルトで追加されます。

  • password_reset_token:トークン生成(インスタンスメソッド)
  • find_by_password_reset_token:トークンからの検索と検証(クラスメソッド)
  • find_by_password_reset_token!:同上(無効時に例外を発生させるバージョン)

Rails 8.1で追加されたトークン有効期限の機能

さらにRails 8.1以降では、トークンの有効期限を柔軟に扱うための機能が追加されています。

has_secure_passwordの呼び出し時にreset_tokenオプションを通して有効期限を変更できるようになりました。

class User < ApplicationRecord
  has_secure_password reset_token: { expires_in: 1.hour }
end

また、以下のメソッドが追加されました。

  • password_reset_token_expires_in:設定されている有効期限の取得(インスタンスメソッド)

参考情報

0
Raw
https://www.techtips.page/en/comments/1117

Bootstrap 5に由来するDart Sassの非推奨警告を抑制する:Railsアプリ(dartsass-rails)を例に

wakairo @wakairo

問題:Bootstrap 5に由来する大量の非推奨警告

Bootstrapは、 Sassの仕様変更に対して長期的に取り組みを進めている 最中であるため、Dart Sassでコンパイルすると以下のような非推奨(Deprecation)警告が大量に出力されます。

Deprecation Warning [import]: Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.

More info and automated migrator: https://sass-lang.com/d/import

  ╷
1 │ @import "bootstrap/mixins/banner";
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^
  ╵
    _bootstrap.scss 1:9                          @use
    app/assets/stylesheets/application.scss 1:1  root stylesheet

この大量の警告にまぎれてしまい、自分で書いたコードに対する警告を見つけにくくなるという問題があります。 その結果、自分のコードに警告が出ていないことを確認するだけでも、手間と時間がかかってしまいます。

なお、2024年にリリースされたDart Sass 1.80.0以降、@importなどの非推奨警告がより積極的に出力されるようになり、この問題がさらに目立つようになりました。

解決策:--quiet-depsで依存ライブラリへの非推奨警告を抑制する

この問題の解決策は、sassコマンドを実行する際に--quiet-depsオプションを付与することです。 これにより、Bootstrapを含む依存ライブラリ(厳密には、ロードパス経由でインポートされたコード)に対する非推奨警告が出力されなくなります。 なお、ロードパス経由でない自分のコードに対する警告は引き続き出力されます。

Railsアプリ(dartsass-rails)で--quiet-depsを付与する方法

dartsass-rails gemを利用するRailsアプリでは、 config/initializers/dartsass.rbを開いて(なければ新規作成して)、 以下のコードを記述します。

Rails.application.config.dartsass.build_options << "--quiet-deps"

参考情報

0
Raw
https://www.techtips.page/en/comments/1116