
GitHub Enterprise Cloudの監査ログをS3にストリーミングして永続化しAthenaで検索してみた
GitHub Enterprise Cloudの監査ログ(Audit Log)は180日まで保持され、Gitイベントに関しては7日まで保持されます。
監査やトラブルシュートのために監査ログをAmazon S3に保存して永続化し、Amazon AthenaでSQL検索できるようにする方法を紹介します。
一度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 連携を指定します。
- 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で検索する方法を紹介しました。
- 監査ログの永続化を設定
- イベントログをJSON文字列としてAthenaテーブル定義し、汎用的に探索
- 頻出する運用オペレーションに対応するイベント特化型のAthenaテーブルを定義
というような流れで段階的に運用を強化するとよいと思います。