ガラシのパルプンテ頼み

地方産限界エンジニアのグローバル独り言

Railsのルーティングヘルパーについて

Railsのルーティングでは、リクエストを適切なコントローラのアクションにルーティングするための便利なヘルパーが用意されています。

それぞれのヘルパーの概要は以下の通りです。

1. resourceresources

resourceresourcesは、RailsでRESTfulなルーティングを設定するために使用されます。RESTfulなルーティングとは、リソースに対して標準的なCRUD操作(Create, Read, Update, Delete)を提供するルーティングです。

resource

  • 概要: 単数リソースに対するルーティングを設定します。単数リソースはIDを持たず、常に1つだけ存在するリソースです。
  • 生成されるルーティング例:
  resource :profile
  GET    /profile/new
  POST   /profile
  GET    /profile
  GET    /profile/edit
  PATCH  /profile
  PUT    /profile
  DELETE /profile

resources

  • 概要: 複数リソースに対するルーティングを設定します。複数リソースはIDを持ち、複数のエントリを持つことができます。
  • 生成されるルーティング例:
  resources :articles
  GET    /articles
  GET    /articles/new
  POST   /articles
  GET    /articles/:id
  GET    /articles/:id/edit
  PATCH  /articles/:id
  PUT    /articles/:id
  DELETE /articles/:id

2. module

  • 概要: コントローラをモジュールでネームスペース化します。特定のモジュール内にあるコントローラに対してルーティングします。
  • :
  namespace :admin do
    resources :articles, module: 'content'
  end

これは、app/controllers/admin/content/articles_controller.rbにマッピングされます。

3. namespace

  • 概要: URLのパスとコントローラの名前空間をネームスペース化します。管理パネルやAPIなどの独立したルーティングを設定する場合に便利です。
  • :
  namespace :admin do
    resources :articles
  end
  GET    /admin/articles
  GET    /admin/articles/new
  POST   /admin/articles
  GET    /admin/articles/:id
  GET    /admin/articles/:id/edit
  PATCH  /admin/articles/:id
  PUT    /admin/articles/:id
  DELETE /admin/articles/:id

これは、app/controllers/admin/articles_controller.rbにマッピングされます。

4. controller

  • 概要: ルーティングを特定のコントローラのアクションに直接マッピングします。URLの構造を自由にカスタマイズする場合に使用されます。
  • :
  get 'login', to: 'sessions#new'
  post 'login', to: 'sessions#create'
  delete 'logout', to: 'sessions#destroy'

これは、それぞれSessionsControllernewcreatedestroyアクションにマッピングされます。

まとめ

  • resource: 単数リソースのCRUD操作のルーティングを設定。
  • resources: 複数リソースのCRUD操作のルーティングを設定。
  • module: コントローラをモジュールでネームスペース化。
  • namespace: URLとコントローラの名前空間をネームスペース化。
  • controller: 特定のコントローラのアクションに直接ルーティング。

これらを組み合わせて、Railsアプリケーションのルーティングを柔軟かつ効率的に設定することができます。

配列形式のクエリパラメータの受け渡しについて

概要

RDB上のPrimaryKeyをクエリパラメータに指定し、複数のリソースを取得するエンドポイントを実装する際に、 クエリパラメータとして配列を受け渡し、コントローラー側でその配列をループしながらリソースを取得する方法を考えた。

しかし連携するリソースIDの数が最大で50個におよび、都度keyとなる文字列(&hoge_ids=1&hoge_ids=2...)を URLに含めてしまうと、最大文字数を超過してしまう可能性があった。

ひとつのkeyに対してカンマ区切りでリソースIDを付与する形(?hoge_ids=1,2,3...)でURLを構築できないかという思惑から、 クエリパラメータで同じkeyから複数のリソースを取得する実装パターンについて、 エンドポイントを実装する際のベターなプラクティスと、それに伴う実装について考えていく。

実装パターン

以下のパターンをベースに考えます。

【カンマ区切り】

取得したいパラメータのリストをカンマ区切りの文字列で表現する方法。

バックエンドでカンマをsplitして配列に加工し直す一手間が必要になる。

GET /user?id=1,2,3

【配列として受け渡し1】

取得したいパラメータを1つずつ & で繋げて配列形式を表現する方法。

GET /user?id=1&id=2&id=3

【配列として受け渡し2】

2 と似ているが取得したいパラメータが「配列である」ことを 表現するために [] のブラケットをつける方法。

GET /user?id[]=1&id[]=2&... 

yarnが原因でdocker-compose upが実行できないときの対処法

現象

Rails開発環境を立ち上げる際に、docker-compose upコマンドを実行すると以下のエラーが出るようになった。

========================================
 Your Yarn packages are out of date!
 Please run `yarn install --check-files` to update.
========================================


To disable this check, please change `check_yarn_integrity`
to `false` in your webpacker config file (config/webpacker.yml).


yarn check v1.22.21
info Visit https://yarnpkg.com/en/docs/cli/check for documentation about this command.


Exiting

一時的な回避方法

エラーメッセージに記載されているように、プロジェクトの config/webpacker.yml ファイルを開き、development環境のcheck_yarn_integrity の値を false に設定します。 これにより、Yarnパッケージの整合性チェックを無効にすることができ、上記エラーを一時的に回避することが可能です。 今回は別件で緊急の修正タスクがあり、久々にDocker環境を立ち上げた際に上記のエラーが発生したため、修正タスクを優先しエラーの根本解決に関しては見送った形です。 設定ファイルにおいてデフォルトでtrueになっている設定をfalseに書き換えている時点で何となくきな臭さは感じていると思いますが、一時的な対応という部分を強調している理由は以下の通り。

check_yarn_integrityとは?

check_yarn_integrity は、RailsのWebpacker設定ファイル(config/webpacker.yml)にあるオプションです。このオプションが true に設定されている場合、Railsサーバーを起動する際に、 Yarnパッケージがプロジェクトの依存関係と整合しているかどうかをチェックします。これにより、開発中に依存関係の問題がある場合に早期に警告が出され、エラーの原因を特定しやすくなります。

check_yarn_integrityをfalseに設定する弊害

上述の通りcheck_yarn_integrity を false に設定すると、Yarnパッケージの整合性チェックが行われなくなります。 では具体的にどんな弊害をもたらすかというと

  1. 依存関係の不一致が見過ごされる: パッケージのバージョンがプロジェクトの要件と一致していない場合、その不一致が発見されにくくなります。これにより、予期せぬ動作やエラーが発生する可能性があります。

  2. デバッグの困難化: 依存関係に関する問題が発生した場合、問題の原因を特定するのがより困難になります。整合性チェックがないため、問題が依存関係に起因するかどうかを判断するのに追加の作業が必要になる可能性があります。

  3. 開発環境と本番環境の乖離: 本番環境では整合性チェックが行われないため、開発環境で問題が発生しなかった不一致が本番環境で問題を引き起こす可能性があります。

これらの理由から一般的には、check_yarn_integrity を true のままにしておくことが推奨されます。 これにより、依存関係の問題を早期に特定し、安定した開発環境を維持することができるためです。 ただし、冒頭で示したような特定の状況や開発フローによっては、このチェックを無効にすることもあります。

根本的な解決方法

正直各々の環境によって問題が異なるため、一貫した解決方法があるわけではありません。 以下の方法を試して一個ずつ問題を潰していく必要があります。

  1. Yarnパッケージの更新: ターミナルで yarn install --check-files コマンドを実行してください。これにより、プロジェクトに必要なパッケージが更新されます。

  2. Dockerコンテナの再ビルド: 変更を適用した後、docker-compose up --build コマンドを実行して、Dockerコンテナを再ビルドします。これにより、新しい依存関係がコンテナに適用されます。

  3. 依存関係の不一致や他の設定の問題: プロジェクトの package.json や yarn.lock ファイルを確認し、依存関係が正しく設定されていることを確認してください。

【JavaScript初心者向け】Promiseとresolveによる非同期処理について

Promiseとは?

JavaScriptで非同期処理を行う際に使われるオブジェクトです。非同期処理とは、すぐに結果が得られない処理のことを指します。例えば、Webサーバーからデータを取得する際や、一定時間後に何かを実行する場合などがこれにあたります。Promiseは、このような非同期処理が完了した後に何が起こるかを管理するために使われます。

Promiseには主に3つの状態があります:

  1. Pending(保留中):
    • 非同期処理がまだ完了していない状態です。
  2. Fulfilled(履行済み):
    • 非同期処理が成功し、完了した状態です。
  3. Rejected(拒否された):
    • 非同期処理が何らかの理由で失敗した状態です。

resolveとは?

resolvePromise内で使用される関数の一つで、Promiseを「Fulfilled(履行済み)」の状態に移行させます。つまり、非同期処理が成功したときにresolveを呼び出すことで、「この処理はうまくいった」と伝えることができます。

Promiseの基本的な使い方

Promiseを生成する基本的な構文は以下の通りです:

let promise = new Promise(function(resolve, reject) {
  // 非同期処理をここに書く

  // 成功した場合
  resolve(result);

  // 失敗した場合
  reject(error);
});

この例では、new Promiseを使って新しいPromiseを作成しています。Promiseのコンストラクタには関数が渡され、この関数はresolverejectの2つの引数を受け取ります。

  • resolve(result): 非同期処理が成功した場合に呼び出されます。resultは非同期処理の結果です。
  • reject(error): 非同期処理が失敗した場合に呼び出されます。errorはエラーの内容です。

Promiseの使用例

Webサーバーからデータを取得する簡単な例を考えてみましょう:

let dataFetching = new Promise(function(resolve, reject) {
  // データ取得の非同期処理
  fetch("https://example.com/data")
    .then(response => response.json())
    .then(data => resolve(data))  // データ取得成功
    .catch(error => reject(error));  // データ取得失敗
});

dataFetching.then(data => {
  console.log("取得したデータ:", data);
}).catch(error => {
  console.error("エラー発生:", error);
});

この例では、fetchを使ってWebサーバーからデータを取得し、成功した場合にはresolveでデータを返し、失敗した場合にはrejectでエラーを返しています。そして、dataFetchingthenメソッドでデータが取得できた場合の処理を、catchメソッドでエラーが発生した場合の処理を記述しています。

このように、Promiseresolveは非同期処理の結果を扱うための非常に便利な仕組みです。

dockerビルド時にGPGエラーが出た場合の対処法

現象

docker-compose build --no-cacheを実行した際に以下のようなエラーが発生した。

W: GPG error: http://security.debian.org/debian-security buster/updates InRelease: At least one invalid signature was encountered.

対応

  1. 未使用のイメージ、コンテナ、ネットワークを削除する docker system prune
  2. ビルド実行 docker-compose build --no-cache

上記で解消されない場合

  1. 未使用のボリュームを削除する docker system prune --volumes
  2. ビルド実行 docker-compose build --no-cache

原因

原因としては以下のようなものが考えられる。Docker関連なら大抵1が原因であることが多い。 が、文字数稼ぎのため一応ChatGPTへの壁打ちで返ってきた確認点も追記しておく。

  1. Dockerのディスクイメージサイズ使用量が100%になっている(大抵これ
  2. 無効なGPG署名: Debianのパッケージリポジトリから取得したデータのGPG署名(データの正当性を保証するための暗号化署名)が無効、または損傷している場合にこのエラーが発生します。
  3. 古いキャッシュ: ローカルのAPTキャッシュ(パッケージマネージャの一時データ)が古いか、破損していることが原因で、正しい署名を検証できない場合があります。
  4. リポジトリの一時的な問題: リポジトリ自体が一時的な問題(たとえば、サーバーの問題やメンテナンス)により、正しい署名を提供できていない可能性があります。
  5. ネットワークの問題: ネットワーク接続の問題やプロキシ、ファイアウォールの設定が原因で、リポジトリからのデータ取得が適切に行われなかった場合も考えられます。
  6. キーの期限切れ: 使用しているGPGキーが期限切れになっている場合、正しく署名を検証できなくなります。

そもそもGPGキーとはなにか

ChatGPT参照

GPGキー(GNU Privacy Guardキー)は、デジタルデータの暗号化とデジタル署名に使用される暗号化キーのペアです。これらのキーは、データの安全な送受信、データの完全性と認証の確保、およびソフトウェアパッケージの正当性の検証に利用されます。GPGキーは公開鍵暗号方式を採用しており、公開キーは他者と共有され、秘密キーは個人が秘密に保持します。このキーのペアを使用することで、安全なデジタルコミュニケーションとデータの保護が可能になります。

参考記事

note.yu9824.com

qiita.com

【GitHub Actions】CircleCIからGitHubActionsに乗り換えてRailsプロジェクトのCIを回す(RSpec・rubocop)

これまでCI/CDツールとしてはCircleCIを使っていましたが、昨今の潮流やコスト面での比較検討から弊社のプロジェクトでもGitHub Actionsへの乗り換えを実施しました。

今回はRailsのプロジェクトでGitHub ActionsによるCI/CDを適用する方法をまとめていきます。

本記事の対象読者としては以下の通りです。

  • 現状CircleCIを使っていてGitHub Actionsへの乗り換えを検討している
  • Rails製のプロダクトにGithub Actionsを適用して、RSpecとrubocopを自動で実行したい

なぜ乗り換えるのか

  • 同じことができるならほぼ無料で済むGitHub Actionsの方がコスト面でお得だから
  • GitHubでコード管理を行なっているから
  • CIの専門家がいるわけではない弊社チームにおいては、何でもできるCircleCIよりも最低限やりたいことを実現できるGithub Actionsのほうが取り回しがしやすい

CircleCIでのテスト内容

  1. Dockerイメージから環境をビルド
  2. コードのチェックアウト
  3. キャッシュからgemを復元
  4. gem install(キャッシュがあればAleady Update)
  5. gemをキャッシュに保存(キャッシュがなければ)
  6. Node.js と npm を更新(Dockerイメージに内包されているNodeバージョンがアプリのNodeのバージョンと異なるため)
  7. yarnライブラリをキャッシュから復元
  8. yarn install(キャッシュがあればAleady Update)
  9. yarnライブラリをキャッシュに保存(キャッシュがなければ)
  10. データベースをセットアップ
  11. rubocop実行
  12. RSpec実行
version: 2.1

# ビルド
jobs:
  build:
    docker:
      - image: circleci/ruby:3.0.0-node
        environment:
          RAILS_ENV: test
          POSTGRES_HOST: 127.0.0.1
      - image: circleci/postgres:11.5
        environment:
          POSTGRES_USER: app_user
          POSTGRES_DB: app_test
          POSTGRES_PASSWORD: password
          POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C"

    working_directory: ~/app_name
        
       # コードのチェックアウト
    steps:
      - checkout

      # キャッシュからgemを復元
      - restore_cache:
          keys:
            - gem-cache-v1-{{ checksum "Gemfile.lock" }}
            - gem-cache-v1-

      # gem install(キャッシュがあればAleady Update)
      - run:
          command: |
            gem install bundler
            bundle config set path 'vendor/bundle'
            bundle install --jobs=4 --retry=3

           # gemをキャッシュに保存
      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}

      - run:
          name: "Node.js と npm を更新"
          command: |
            curl -sSL "https://nodejs.org/dist/v16.13.1/node-v16.13.1-linux-x64.tar.xz" | sudo tar --strip-components=2 -xJ -C /usr/local/bin/ node-v16.13.1-linux-x64/bin/node
            curl https://www.npmjs.com/install.sh | sudo bash

      # yarnライブラリをキャッシュから復元
      - restore_cache:
          keys:
            - yarn-packages-{{ checksum "yarn.lock" }}
            - yarn-packages-

      # yarn install(キャッシュがあればAleady Update)
      - run: yarn install

           # ライブラリをキャッシュに保存
      - save_cache:
          paths:
            - ~/.cache/yarn
          key: yarn-packages-{{ checksum "yarn.lock" }}

      # Database setup
      - run: bundle exec rails db:create
      - run: bundle exec rails db:migrate

      # Rubocop
      - run: bundle exec rubocop app
      # Rspec
      - run: bundle exec rspec

GitHub Actionsへの置き換え

1 .github/workflows/配下にワークフローを配置

GitHub Actionsを導入したいプロジェクトのリポジトリに移動したら、「Add file」から「Create new file」を選択して、ファイル作成画面に遷移します。

次にファイル作成画面で.github/workflowsディレクトリにYAMLファイルを配置します。

今回は「testing.yml」という名前でファイルを作成しました。

そのまま画面下までスクロールし、「Create new branch〜」のラジオボタンを選択して、新しく作成するブランチ名を入力。「Propose new file」を押下すると所定のディレクトリにtesting.ymlを配置したブランチが新たに作成されます。

作成後は自身のローカルマシンからたった今作成したブランチにチェックアウトしてワークフローファイルを編集していきます。

2 ワークフローファイルを編集する

GitHub Actionのワークフローとして書き換えたものが以下の通り。

コード内にコメントも付与していますが、以降これを題材としてGitHub Actionsで用いられる概念や用語簡単に説明していきます。

name: testing

on:
  # pushトリガーはmasterブランチへのpush時にのみ実行
  push:
    branches:
      - master
  # pull_requestはopen, synchronize, reopenのタイミングでの実行がデフォルト設定になっている
  # synchronizeはPRに変更があった場合という意味
  pull_request:

jobs:
  test:
    # ホストランナー(仮想マシン)のイメージを選択
    runs-on: ubuntu-latest
    timeout-minutes: 10
    # DB用のサービスコンテナを作成
    services:
      postgresql: # サービス名
        image: postgres:11.5 # DockerHubのイメージ参照
        ports: # 仮想マシン内で使用するポート番号
          - 5432:5432
        env: # 仮想マシン内でサービスから使用される環境変数を定義
          POSTGRES_USER: app_user
          POSTGRES_DB: app_test
          POSTGRES_PASSWORD: password
          POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C"

    # steps: コードのチェックアウトやRubyのインストールなど、特定の処理の単位
    steps:
      - name: Checkout code # ステップ名
        # uses: アクションを実行するキー。
        uses: actions/checkout@v3

      # GitHubの公式アクションとして「actions/setup-ruby」が存在するが最新バージョンの追随が遅いなどの理由からあまり使われていない模様
      # Railsのリポジトリを見ても「ruby/setup-ruby」を使用しているためこちらを採用
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with: # アクションに与える引数を指定
          bundler-cache: true
          ruby-version: 3.0.0

      # withに「cache: yarn」を指定しても、ホストランナー上のnode_modulesをキャッシュしない
      # https://dev.classmethod.jp/articles/caching-dependencies-in-workflow-execution-on-github-actions/
      - name: Set up Node.js
        # v2ではNVMを使って複数バージョンを管理可能。単一のバージョンを指定する場合はv3を利用
        uses: actions/setup-node@v3
        with:
          node-version: 16.13.1

      # node関連の依存関係をキャッシュ
      - name: Cache node modules
        uses: actions/cache@v2
        with:
          path: node_modules # キャッシュ対象のディレクトリ
          key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} # キャッシュ時のキー
          restore-keys: | # キャッシュの復元
            ${{ runner.os }}-node-

      - name: Bundler and gem install
        run: |
          gem install bundler
          bundle install --jobs 4 --retry 3 --path vendor/bundle

      - name: Yarn install
        run: yarn install

      - name: Database create and migrate
        run: |
          cp config/database.yml.ci config/database.yml
          bundle exec rails db:create RAILS_ENV=test
          bundle exec rails db:migrate RAILS_ENV=test

      - name: Run rubocop
        run: bundle exec rubocop app

      - name: Run rspec
        run: bundle exec rspec

3 config配下にdatabase.yml.ciを配置

database.yml.ciは、Railsアプリケーションのテスト環境で使用されるデータベース設定ファイルです。

通常、Railsアプリケーションはdatabase.ymlという名前のファイルを使用してデータベース接続情報を管理します。

ただし、CI/CDツールであるGitHub Actionsでは、通常のdatabase.ymlファイルではうまく動作しない場合があります。たとえば、CI/CD環境で使用するデータベースの接続情報が異なる場合があります。

そのため、database.yml.ciファイルを作成し、CI/CDツールで使用されるデータベース接続情報を定義することが推奨されています。

test:
  adapter: postgresql
  database: app_test
  username: app_user
  password: password
  host: localhost

GitHub Actions内で使用される基本概念、用語

イベント

冒頭のonキーでジョブの実行タイミングが制御している箇所。push、pull_request等が実行タイミングを決めるイベントキー。branchesはどのブランチにそれらが行われたらワークフローを実行するかを決めるキーです。

上述のコードでは「masterブランチにpushされた場合」「pull_requestがオープン・変更・再オープンされた場合」にワークフローを実行するよう指示しています。pull_requestイベントキーの初期値はオープン・変更・再オープン時となっていますが、typesキーでこれを変更することも可能です。

ワークフロー

GitHub Actionsでは、このファイル全体を指してワークフローという単位で数えます。

ワークフローは一つまたは複数のジョブから構成されます。

ジョブ

jobsキー配下に置かれている処理群が「ジョブ」

ジョブはその配下の一つまたは複数のステップから構成されます。

ステップ

ジョブを構成するアクションやrunに続くコマンドの集合を指します。

usesキーでアクションを、runキーでコマンドを実行します。

アクション

GitHub Actionsでは再利用製の高いコード(依存関係のキャッシュやツールチェーンのセットアップなど)を「アクション」として提供してくれています。

アクションとはGitHub、またはサードパーティ製のカスタムライブラリで、再利用製の高いコードを関数化したようなものです。

コマンド

ホストランナー上のOSで使用できるコマンドを指します。

平たく言えば普段ターミナル上で実行しているコマンド類と同様です。

ホストランナー

ワークフローが実行される仮想マシンのことです。

runs-onキーでGitHubが用意する所定のOSを指定して使用します。

まとめ

導入してみてメリットに感じたこと

  • 導入が簡単
    • 所定のディレクトリにymlを置くだけですぐに使えるのが楽
  • 当たり前だけどGitHubとの親和性が高い。
    • ワークフローの実行トリガーがイベント・ブランチに応じてかなり細かく指定できる
  • CIの所要時間が短縮された
    • これはspecのコード量や実行環境によりそうですが、弊社の環境では特に2回目以降のキャッシュ込みでの所要時間が2m程度は短縮されました

これから新たにプロジェクトをオープンし、コードのホスティングをGitHubで行う場合、GitHub Actionsを選択するのはごく自然な選択肢となりそうです。

【GitHub・SSH接続】WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!への対処法

ある日、Gitでリモートへのプッシュをかけた際にこんなエラーが発生しました。

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:RGRZ/*********************************************.
Please contact your system administrator.
Add correct host key in /Users/XXXX/.ssh/known_hosts to get rid of this message.
Offending RSA key in /Users/XXXX/.ssh/known_hosts:2
RSA host key for [git.xxxxx.com]:XXXX has changed and you have requested strict checking.
Host key verification failed.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.

数分前までは正常にGitコマンドが使用できていたところ、突然WARNINGなどと仰々しいプリントでエラーが出力されたのでビビり散らかしてしまいました。

今回はGitHubにSSHで接続しているユーザーが、稀にエンカウントするかもしれないタイトルのエラーについて、その原因と対処法をまとめました。

原因

GitHubはセキュリティ上の理由から、不定期でIPアドレスを変更することがあります。

これはGitHubがユーザーのリポジトリやコードなどの重要な情報を保護するために、様々なセキュリティ対策を講じているためです。

IPアドレスの変更は、主にDDoS攻撃などの攻撃からGitHubを保護するための一つの方法です。

IPアドレスを定期的に変更することにより、攻撃者が特定のIPアドレスに対して攻撃を仕掛けることを困難にし、攻撃の影響を軽減することができます。

また、GitHubはクラウドサービスプロバイダー(CSP)からサーバーを借りているおり、そのCSP側がIPアドレスの変更を実施することもあります。

CSP側のIPアドレス変更は、GitHub側から直接制御することができないため、そうした理由からも不定期なIPアドレスの変更につながる場合があります。

これらの前提を踏まえて、今回のエラーが発生した原因は何かというと、SSHで接続するリモートホストの接続先情報、つまりはGitHubのIPアドレスが変更されたことが原因でした。

過去にSSH接続を行った実績のあるリモートホストの接続先情報(公開鍵)はローカルマシンの~/.ssh/known_hosts に保存されています。

このknown_hostsファイルに保存されているGitHubのIPアドレスと、今接続しようとしているGitHubのIPアドレスとが食い違っているため、中間者攻撃ではありませんか??危険な接続先ではないですか??大丈夫ですか??システム管理者に確認して問題ないようであれば、/Users/XXXX/.ssh/known_hosts内の2行目の接続先に関する情報を見直してくださいねというような旨の警告を出してくれていたということです。

ChatGPT先生曰く

最近はググるよりもChatGPT先生に聞いた方が問題解決が早いので、脳死でエラー文をChatGPTにぶん投げてます。

今回も例に漏れず細かな原因を探る前に、大まかな問題や解決フローを探る意図でこんな感じの質問を投げてみました。

Gitでリモートリポジトリへのプッシュ操作を行った際に「WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!」
というエラーが発生しました。どのように対処すればいいですか?

余談ですが自分はAIチャットにも敬語で問いかけるタイプの人間。

ChatGPT先生から返ってきた返答は以下の通り。

このエラーメッセージは、SSH経由でリモートホストに接続したことがある場合に表示されます。
このエラーは、SSHで接続しようとするホストの公開鍵が以前と異なることを示しています。
これは、例えばリモートホストが更新された場合や、新しいホストに接続する場合に発生する可能性があります。
この問題を解決するには、以下の手順を実行してください。

対処法1(ChatGPT案)

そしてChatGPTが提案してくれた解決策がこちら。


  1. まず、警告メッセージに記載されているリモートホストのIPアドレスを確認します。
  2. 次に、リモートホストの公開鍵を削除します。これは、リモートホストに接続する前に行う必要があります。以下のコマンドを実行してください。

     ssh-keygen -R [リモートホストのIPアドレス]
    

    例えば、IPアドレスが「192.168.0.1」の場合は以下のようになります。

     ssh-keygen -R 192.168.0.1
    
  3. 再度接続を試みます。接続時にリモートホストの公開鍵を再び取得し、接続を許可するかどうかを確認するメッセージが表示されます。接続を許可する場合は、「yes」と入力してEnterキーを押します。

     ssh [ユーザー名]@[リモートホストのIPアドレス]
    
  4. 接続が成功したら、再度プッシュ操作を行ってください。

以上の手順を実行することで、SSH経由でリモートホストに接続してプッシュ操作を行うことができるようになるはずです。


このようにssh-keygen -R で指定したIPをキーに持つknowm_hostsファイル内の公開鍵を削除し、再度SSH接続を行うことで公開鍵が再登録されるまでの流れを出力してくれました。

ちなみに1の手順の「リモートホストのIPアドレス」は以下のコマンドで確認することが可能です。

$ sed -n '2p' ~/.ssh/known_hosts

これは第二引数で与えたファイルの第一引数で与えた行数を出力するコマンドで、2p(2行目)という数字がどこから出てきたかというと、冒頭のエラーメッセージの以下の箇所から参照した形です。

Offending RSA key in /Users/XXXX/.ssh/known_hosts:2

3の手順ではsshコマンドによる認証を行なったのちにプッシュ操作を〜と記載されていますが、sshコマンドの実行は省略可能です。

対処法2

今回は以下の方法で実際に解消まで漕ぎ着けました。やってることとしてはChatGPTが出力してくれた解決案を手動で実行しているに過ぎないので、詳細については割愛します。

  1. エラーメッセージに従い、~/.ssh/known_hostsの2行目を削除
vim ~/.ssh/known_hosts
  1. 展開されたknown_hostsの2行目にカーソルを移動して dd
  2. :wq で保存して終了
  3. 再度プッシュ操作を実行
  4. 以下のように対話式に接続の確認がとられるのでそれぞれにyes を入力
The authenticity of host 'github.com (xx.xx.xxx.xxx)' can't be established.
ECDSA key fingerprint is SHA256: xxxx
Are you sure you want to continue connecting 

(yes/no/[fingerprint])? yes

Warning: Permanently added 'github.com' (ECDSA) to the list of known hosts.
Warning: the ECDSA host key for 'github.com' differs from the key for the IP address ‘xx.xx.xxx.xxx'
Offending key for IP in /Users/xxxx/.ssh/known_hosts:10

Are you sure you want to continue connecting (yes/no)? yes

まとめ

  • GitHubはセキュリティ対策の一環として、不定期でIPアドレスの変更を行う
  • ローカルに保存されている接続先情報との不一致によりがWARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!が発生する
  • ~/.ssh/known_hosts のエラーメッセージに記載されている行(変更前の接続先情報)を削除し、再度SSH接続を行うことで、リモートホストの接続先情報が更新されてSSH接続が可能となる