GitHub Enterprise Cloudの監査ログをS3にストリーミングして永続化しAthenaで検索してみた

GitHub Enterprise Cloudの監査ログをS3にストリーミングして永続化しAthenaで検索してみた

180日しか保持できないGitHubの監査ログをAmazon S3に保存して永続化し、Amazon Athenaで検索するまでの流れ
Clock Icon2025.03.05

GitHub Enterprise Cloudの監査ログ(Audit Log)は180日まで保持され、Gitイベントに関しては7日まで保持されます。

監査やトラブルシュートのために監査ログをAmazon S3に保存して永続化し、Amazon AthenaでSQL検索できるようにする方法を紹介します。

GitHub Audit

一度Amazon AthenaでS3監査ログのテーブル定義をしてしまえば、AWSにアクセスできるユーザーだれもが、SQLで監査ログを検索できるようになります。また、Athenaはフルマネージドのサーバーレスサービスのため、S3ストレージ以外に固定費も発生しません。

構築方法

1. AWS 側のデータ連携設定

データ連携のために、AWS側には以下の3リソースを作成します

  • ログ・ファイル保存先のS3バケット
  • GitHubとAWS間のOpenID Connect認証(AWS Identity Provider)
  • GitHubがS3に書き込むためにassumeするIAMロール

以下の CloudFormationテンプレートを実行すると、これらリソースが作成されます。

AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template for GitHub Enterprise Audit Log streaming to S3 with OIDC authentication'

Parameters:
  BucketName:
    Type: String
    Description: Name of the S3 bucket to store GitHub Enterprise audit logs
    Default: github-enterprise-audit-logs

  GitHubAccount:
    Type: String
    Default: https://github.com/YOUR-ENTERPRISE-ACCOUNT

  LogRetentionDays:
    Type: Number
    Description: Number of days to retain logs
    Default: 365
    MinValue: 1
    MaxValue: 3653

Resources:
  # OIDC プロバイダーの作成
  GitHubAuditLogOIDCProvider:
    Type: AWS::IAM::OIDCProvider
    Properties:
      Url: https://oidc-configuration.audit-log.githubusercontent.com
      ClientIdList: 
        - sts.amazonaws.com
      ThumbprintList:
        - 6938fd4d98bab03faadb97b34396831e3780aea1

  # S3 バケットの作成
  AuditLogBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref BucketName
      VersioningConfiguration:
        Status: Suspended
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
            BucketKeyEnabled: true
      LifecycleConfiguration:
        Rules:
          - Id: ExpireOldLogs
            Status: Enabled
            ExpirationInDays: !Ref LogRetentionDays

  # GitHub OIDC 用の IAM ロール
  GitHubAuditLogRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GitHubEnterpriseAuditLogRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Federated: !GetAtt GitHubAuditLogOIDCProvider.Arn
            Action: 'sts:AssumeRoleWithWebIdentity'
            Condition:
              StringEquals:
                "oidc-configuration.audit-log.githubusercontent.com:aud": "sts.amazonaws.com"
                "oidc-configuration.audit-log.githubusercontent.com:sub": !Ref GitHubAccount
      Policies:
        - PolicyName: GitHubAuditLogS3WritePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Sid: VisualEditor0
                Effect: Allow
                Action:
                  - 's3:PutObject'
                Resource: !Sub 'arn:aws:s3:::${AuditLogBucket}/*'

Outputs:
  AuditLogBucketName:
    Description: Name of the S3 bucket storing GitHub Enterprise audit logs
    Value: !Ref AuditLogBucket

  AuditLogBucketArn:
    Description: ARN of the S3 bucket storing GitHub Enterprise audit logs
    Value: !GetAtt AuditLogBucket.Arn

  GitHubOIDCRoleArn:
    Description: ARN of the IAM role for GitHub OIDC authentication
    Value: !GetAtt GitHubAuditLogRole.Arn

  OIDCProviderArn:
    Description: ARN of the GitHub Audit Log OIDC provider
    Value: !GetAtt GitHubAuditLogOIDCProvider.Arn

2. GitHub 側のデータ連携設定

GitHub のエンタープライズアカウントの Settings → Log streaming から Amazon S3 連携を指定します。

github-audit-log-streaming-aws

  • Authentication : OpenID Connect
  • Bucket : 先ほどのS3バケット
  • ARN Role : 先ほど作成したIAMロールのARN

特に、GitHubとAWS間はOpenID Connectでの認証し、永続的なアクセスキーは利用しません。

3. S3監査ログとAmazon Athenaの連携

本セクションでは、サイボウズ様の次の記事を大いに参考にしています。

GitHub の 監査ログを Amazon Athena でクエリできるようにした

正しく監査ログがストリーム連携されると、S3バケットのルートに _check ファイルが作成され、 yyyy/MM/dd/HH の階層で gzip ファイルが作成されます。

$ tree .
.
├── 2025
│   ├── 02
│   ├── 02
│   │   ├── 27
│   │   │   └── 23
│   │   │       ├── 26
│   │   │       │   ├── aaa-bbb-ccc-ddd-eee.json.log.gz
│   │   │       │   └── aaa-bbb-ccc-ddd-eee.json.log.gz
...
└── _check

スキャン範囲を限定することで検索時間、及び、利用費を削減できるように、'yyyy/MM/dd/HH' のディレクトリ階層でパーティション化して、テーブル作成します。

様々な操作に対するログが JSON 形式でストリーミングされるため、次のログイベントのドキュメントのように、フィールドも様々です

Audit log events for your enterprise - GitHub Enterprise Cloud Docs

そのため、以下の2種類のAthena用テーブルを定義することをおすすめします

  • ログ横断型汎用テーブル
  • 用途特化型テーブル

どちらも射影(projection)でパーティション化しています。

ログ横断型汎用テーブル

汎用型テーブルでは、レコード全体をJSON文字列(org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe)として1カラム(json_objects)で管理します。

任意のJSONに対応できる一方で、クエリー時に json_extract_scalar 等を使ったJSON文字列のパースが必要です。

CREATE EXTERNAL TABLE `default`.`github_audit_logs` (
  `json_objects` string
)
PARTITIONED BY (
  `date` string
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
LOCATION 's3://YOUR-BUCKET-NAME/'
TBLPROPERTIES (
  'projection.enabled' = 'true',
  'projection.date.type' = 'date',
  'projection.date.format' = 'yyyy/MM/dd/HH',
  'projection.date.range' = '2025/01/01/01,NOW',
  'projection.date.interval' = '1',
  'projection.date.interval.unit' = 'HOURS',
  'storage.location.template' = 's3://YOUR-BUCKET-NAME/${date}/'
);

用途特化型テーブル(ワークフローの例)

特化型テーブルでは、検索対象のログ(操作)に対応するJSONのキーをテーブルのカラムとして構造化して管理します。実際のデータ型は、実データを参考にしましょう。

テーブル定義時に一手間かける一方で、SQLで素直にクエリーできます。

CREATE EXTERNAL TABLE github_workflow_logs (
	`@timestamp` BIGINT,
	`_document_id` STRING,
	`action` STRING,
	`actor` STRING,
	`actor_id` BIGINT,
	`actor_is_bot` BOOLEAN,
	`business` STRING,
	`business_id` BIGINT,
	`created_at` BIGINT,
	`event` STRING,
	`hashed_token` STRING,
	`head_branch` STRING,
	`head_sha` STRING,
	`name` STRING,
	`operation_type` STRING,
	`org` STRING,
	`org_id` BIGINT,
	`programmatic_access_type` STRING,
	`public_repo` BOOLEAN,
	`repo` STRING,
	`repo_id` BIGINT,
	`request_access_security_header` STRING,
	`run_number` INT,
	`started_at` STRING,
	`token_id` BIGINT,
	`trigger_id` BIGINT,
	`user_agent` STRING,
	`workflow_id` BIGINT,
	`workflow_run_id` BIGINT,
	`completed_at` STRING,
	`conclusion` STRING,
	`run_attempt` INT,
	`topic` STRING
)
PARTITIONED BY (
  `date` string
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES (
	'ignore.malformed.json' = 'FALSE',
	'dots.in.keys' = 'FALSE',
	'case.insensitive' = 'TRUE',
	'mapping' = 'TRUE'
)
LOCATION 's3://YOUR-BUCKET-NAME/'
TBLPROPERTIES (
	'projection.enabled' = 'true',
	'projection.date.type' = 'date',
	'projection.date.format' = 'yyyy/MM/dd/HH',
	'projection.date.range' = '2025/01/01/01,NOW',
	'projection.date.interval' = '1',
	'projection.date.interval.unit' = 'HOURS',
	'storage.location.template' = 's3://YOUR-BUCKET-NAME//${date}/',
	'classification' = 'json'
);

検索

以降では

  • GitHub のSettings → Audit log → Events 検索画面
  • Athena 汎用テーブル
  • Athena 特化型テーブル

から具体的にログを検索してみます。

Athenaの場合、スキャン範囲を限定するために、明示的にフルスキャンするケースを除いて日付を指定しましょう。

action をざっと把握したい

操作(action)をトリガーにイベントログが生成されます。

Athena 汎用テーブル

どのような操作が存在するか、ざっと把握するためには、Athenaの汎用テーブルに対して、以下のクエリでaction x 件数の一覧を抽出します。

select json_extract_scalar(json_objects, '$.action') as action,
	count(*) as cnt
from default.github_audit_logs
where date between '2025/03/01' AND '2025/04/01'
group by json_extract_scalar(json_objects, '$.action')
order by cnt desc

リポジトリの削除ログを調査

リポジトリを削除すると、repo.destroy という action で記録されます。

GitHub

GitHubコンソールで、以下の条件で検索しましょう。

action:repo.destroy repo:ORG_NAME/REPO_NAME

Athena 汎用テーブル

select *
from default.github_audit_logs
where date between '2025/03/01' AND '2025/04/01'
	and json_extract_scalar(json_objects, '$.action') = 'repo.destroy'
	and json_extract_scalar(json_objects, '$.repo') = 'ORG_NAME/REPO_NAME'

GitHub Actionsの実行数を知りたい

GitHub Actionsのワークフローが実行完了すると、 workflows.completed_workflow_run というアクションが記録されます。

汎用型テーブルに対して json_extract_scalar を用いて複雑なクエリーを組み立てると、汎用的にデータ探索できる一方で、特化型テーブルはクエリーがシンプルになります。

Athena 汎用テーブル

select json_extract_scalar(json_objects, '$.repo') as repo,
	count(*) as cnt
from default.github_audit_logs
where date between '2025/03/01' AND '2025/03/05'
	and json_extract_scalar(json_objects, '$.action') = 'workflows.completed_workflow_run'
group by json_extract_scalar(json_objects, '$.repo')
order by cnt desc

Athena 特化テーブル

select repo,
	count(*) as cnt
from github_workflow_logs
where action = 'workflows.completed_workflow_run'
	and date between '2025/03/01' AND '2025/03/05'
group by repo
order by cnt desc

最後に

GitHub Enterprise Cloudで監査ログをAmazon S3にストリーミングし、Amazon Athenaで検索する方法を紹介しました。

  1. 監査ログの永続化を設定
  2. イベントログをJSON文字列としてAthenaテーブル定義し、汎用的に探索
  3. 頻出する運用オペレーションに対応するイベント特化型のAthenaテーブルを定義

というような流れで段階的に運用を強化するとよいと思います。

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.