4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事はユニークビジョン株式会社 Advent Calendar 2024の11日目です。

はじめに

最近、APIリクエストを受けた際に自動的にGitHubブランチを作成する機能を実装しました。

単純にGitHub APIを直接呼び出すことも可能ですが、そのアプローチには以下のような課題があります:

  • APIトークンの安全な管理
     特定のユーザーにトークンを紐づけておくと、そのユーザーが異動や退職した際にトークンの管理が困難になります。
  • 大規模システムにおける権限管理の複雑さ
    複数の作業を自動化する場合、APIトークンの実装だとスコープが広くなり、権限管理が難しくなります。

これらの課題に対応するため、今回はGitHub Appを活用した実装方法を選択しました。本記事では、その実装と得られた知見について紹介します。

Github Appとは

GitHub Appは、GitHubの機能を拡張するツールです。GitHub上での操作(Issue作成、PRへのコメント、プロジェクト管理など)が可能であったり、GitHub上のイベントに基づいて外部サービスと連携可能(例:IssueがオープンされたらSlackに投稿)です。

以下のような特徴があります。

  • 細かい権限設定が可能
    • リポジトリ単位で必要最小限の権限を付与できます
    • 例:イシューの作成のみ、PRの作成のみなど
  • webhook対応
    • GitHubのイベントをリアルタイムで検知し、自動処理を実行可能
  • JWT認証による安全な通信
    • 一時的なトークンを使用するため、セキュリティリスクを軽減

GitHub Marketplaceから、様々なGitHub Appをインストールできます。自分でGitHub Appを作成することも可能です。どちらの場合も、使いたいリポジトリに対してインストールする必要があります。

GitHub Appのしくみ

大きく分けてWebhookをトリガーとして実行する方法と、APIを直接呼び出す方法、Github Actionsをトリガーとして実行する方法があります。今回はAPIを直接呼び出す方法を紹介します。

シーケンス図は以下のようになっています。

この図は、クライアントからのリクエストを起点に、以下の流れでGitHub操作を実行します:

  1. JWT認証によりGitHub Appの認証
  2. 認証後、一時的なアクセストークンを取得
  3. トークンを使用してGitHub APIを呼び出し
  4. 結果をクライアントに返却

GitHub Appの作成

  1. GitHubのGitHub Appsにアクセスします。
  2. 「New GitHub App」をクリックします。
  3. GitHub App nameを入力します。ここは全世界でユニークである必要があるので、個人で使う限りはmisty1999-github-appのようにprefixとして自分のIDを入れると良いと思います。Homepage URLはなんでもいいです。Webhook URLは今回は設定する必要はありません。
  4. 「Create GitHub App」をクリックします。このような画面になります。
    image.png
  5. 下のほうにある「Generate a private key」をクリックします。「Generate」をクリックします。秘密鍵がダウンロードされます。
  6. 権限を設定します。左側の「Permissions&events」をクリックします。Repository permissionsのところで、Repository permissions>issuesをRead & writeを選択します。

以上でGitHub Appの作成は完了です。作成したGitHub AppのClient IDと秘密鍵をバックエンドAPIで使います。

続いて、作成したAppを自分のリポジトリにインストールします。作成したAppのページの左側にある「Install App」をクリックします。自分のユーザー名の右側の「Install」を選択します。only select repositoriesから自分のリポジトリを選択して「Install」を押せば、リポジトリへのインストールが完了します。

Octocrabについて

octocrabは、GitHub APIをラップしたRustのクレートです。GitHub AppやGitHub Actionsに関する操作を直感的に実装することができます。先ほどの図でバックエンドからGitHubにアクセスする処理がありましたが、今回の実装ではバックエンドからAppを呼び出す代わりにoctocrabを使ってクライアントを作成し、そこから呼び出しています。クライアントを使用することで、認証情報やレートリミットの機能を一元的に管理できるメリットがあります。

バックエンドAPIの実装

https://github.com/misty1999/my-git-apps
こちらにGitHub Appを使ったAPIの実装をしています。メインの処理はcreate_issue.rsで行っています。今回はissueを自動作成できるようにしてみました。

use jsonwebtoken::EncodingKey;
use octocrab::Octocrab;
use std::error::Error as StdError;

const REPO_OWNER: &str = "misty1999";
const REPO_NAME: &str = "my-git-apps";

pub async fn create_issue(title: &str, body: &str) -> Result<String, String> {
    // 設定を取得
    let config = crate::config::Config::from_env()
        .map_err(|e| format!("設定の読み込みに失敗: {}", e))?;
    
    let app = config.github_app
        .ok_or("GitHub App設定が見つかりません")?;

ここまでで、GitHub Appの設定を読み込んでいます。REPO_OWNERREPO_NAMEは自分のリポジトリを指定しています。config.github_appには、GitHub AppのIDと秘密鍵が入っています。config.rsで、.envprivate-key.pemを読み込んでいます。Github AppのIDと秘密鍵をそれぞれのファイルに入れています。

    // GitHub Appクライアントの作成
    let octocrab = Octocrab::builder()
        .app(
            octocrab::models::AppId(
                app.id.parse::<u64>()
                    .map_err(|e| format!("GitHub App IDのパースに失敗: {}", e))?
            ),
            EncodingKey::from_rsa_pem(app.secret.trim().as_bytes())
                .map_err(|e| format!("RSA秘密鍵のパースに失敗: {}", e))?
        )
        .build()
        .map_err(|e| format!("Octocrabインスタンスの作成に失敗: {}", e))?;

    // インストールトークンの取得
    let installation = octocrab
        .apps()
        .get_repository_installation(REPO_OWNER, REPO_NAME)
        .await
        .map_err(|e| format!("インストレーション情報の取得に失敗: {}\nステータスコード: {}", 
            e.to_string(),
            e.source().map_or("不明".to_string(), |s| s.to_string())
        ))?;

    let access_token: octocrab::models::InstallationToken = octocrab
        .post(
            format!("/app/installations/{}/access_tokens", installation.id),
            None::<&()>
        )
        .await
        .map_err(|e| e.to_string())?;

この部分は特に重要で、GitHub Appの認証フローの核となる処理です:

  1. 秘密鍵をセットしたクライアントを作成
  2. 秘密鍵から作成したjwtによる認証を行う
  3. 認証が完了したので、取得したインストールIDを使用してアクセストークンを発行
    // Issueの作成
    let issue = Octocrab::builder()
        .personal_token(access_token.token)
        .build()
        .map_err(|e| e.to_string())?
        .issues(REPO_OWNER, REPO_NAME)
        .create(title)
        .body(body)
        .send()
        .await
        .map_err(|e| e.to_string())?;

    Ok(format!("Issue '{}' を作成しました (#{})", title, issue.number))
}

認証が完了したので、GitHub APIを呼び出してIssueを作成しています。

まとめ

・GitHub Appを使うことで、属人化を防ぎつつ、よりセキュリティに配慮した実装が可能
・GitHub Appをクライアントから使う場合、jwtを使って認証を行い、それを使ってアクセストークンを取得するという多段階の認証を行う必要がある
・octocrabを使うと、この認証を直感的に実装できる

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?