はじめに - まさかの事態発生
2025年8月のある金曜日、午後3時。私の携帯に緊急の電話が入りました。
「緊急です!開発環境からプライベートリポジトリへの不正アクセスが検知されました。すぐに対応をお願いします!」
セキュリティ部門からの一報でした。調査の結果、Cloud Workstationsを利用していた開発者が、業務で使用すべきでないプライベートなGitHubリポジトリにアクセスしていたことが判明。幸い実害はありませんでしたが、この事件をきっかけに「開発環境のアクセス制御を根本から見直す」プロジェクトが立ち上がりました。
これは、その時の失敗と学びの記録です。
第一章:楽観的な設計の落とし穴
事故前の環境
事故発生前の私たちの開発環境は、一見合理的に見えました:
1
2
3
4
5
|
# 事故前の設定(問題あり)
allowed_urls:
- "github.com/mycompany/*" # 会社のリポジトリ全体
- "*.npmjs.org" # npm packages
- "pypi.org" # Python packages
|
何が問題だったのか?
- 組織レベルの許可:
github.com/mycompany/*
という設定により、社員が個人的に作成したリポジトリにもアクセス可能
- 監査の不備: どのリポジトリにアクセスしているかの詳細な記録がない
- TLS検査なし: HTTPS通信の中身が見えず、具体的なAPIコールが追跡できない
実際に起きた事故
ある開発者が、業務時間中に個人のサイドプロジェクトのコードを会社のワークステーションにクローン。そのリポジトリには、他社のAPIキーが含まれていました。
1
2
3
|
# 実際に実行されたコマンド(問題のあるもの)
git clone https://github.com/mycompany/developer-personal-project.git
# ↑ このリポジトリに他社のAPIキーが含まれていた
|
結果:
- 会社のネットワーク経由で外部のAPIキーが通信
- セキュリティ監視システムがアラートを発報
- コンプライアンス違反の疑いで緊急調査開始
第二章:応急処置の実装と新たな問題
緊急対応:とりあえずブロック
まず、緊急対応として全てのGitHubアクセスを一時的にブロックしました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 緊急対応(全面ブロック)
resource "google_network_security_gateway_security_policy" "emergency" {
name = "emergency-block-all"
location = var.region
description = "Emergency: Block all GitHub access"
}
resource "google_network_security_gateway_security_policy_rule" "block_github" {
name = "block-github"
location = var.region
gateway_security_policy = google_network_security_gateway_security_policy.emergency.name
priority = 100
session_matcher = "host() == 'github.com'"
basic_profile = "DENY"
}
|
結果:開発が完全にストップ
当然ですが、開発チームから猛烈な反発が。「仕事にならない」「デプロイできない」という声が次々と。
第一回目の失敗:ホワイトリスト方式
次に試したのが、厳選されたリポジトリのみを許可するホワイトリスト方式でした。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 第一回目の実装(失敗)
resource "google_network_security_gateway_security_policy_rule" "allow_specific_repos" {
name = "allow-specific-repos"
location = var.region
gateway_security_policy = google_network_security_gateway_security_policy.main.name
priority = 100
session_matcher = <<-EOF
host() == 'github.com' &&
(inUrlPath('/mycompany/project-a') ||
inUrlPath('/mycompany/project-b') ||
inUrlPath('/mycompany/project-c'))
EOF
basic_profile = "ALLOW"
}
|
問題発生:git cloneが通らない!
土曜日の朝8時、緊急の電話が再び。
「git cloneができません!エラーが出ます!」
1
2
3
|
$ git clone https://github.com/mycompany/project-a.git
fatal: unable to access 'https://github.com/mycompany/project-a.git/':
The requested URL returned error: 403 Forbidden
|
デバッグ地獄の始まり
問題の原因を特定するため、深夜までログを解析しました。
1
2
3
4
5
6
7
8
|
# ログ解析で判明した事実
curl -v https://github.com/mycompany/project-a.git/info/refs?service=git-upload-pack
# 実際のgitアクセスパターン:
# 1. https://github.com/mycompany/project-a.git/info/refs
# 2. https://github.com/mycompany/project-a/git-upload-pack
# 3. https://api.github.com/repos/mycompany/project-a
# 4. https://objects.githubusercontent.com/...
|
判明した事実:gitの内部動作の複雑さ
単純に /mycompany/project-a
を許可するだけでは不十分でした。Gitは内部的に:
.git
サフィックスを付けてアクセス
api.github.com
のAPIを呼び出し
objects.githubusercontent.com
からオブジェクトをダウンロード
- リダイレクト先のURLにアクセス
第三章:TLS Inspection導入での新たな壁
TLS Inspectionの実装決断
URLパスレベルでの制御を実現するため、TLS Inspectionを導入することに。
1
2
3
4
5
6
7
8
|
resource "google_network_security_tls_inspection_policy" "main" {
name = "${var.prefix}-tls-inspection"
location = var.region
ca_pool = google_privateca_ca_pool.main.id
exclude_public_ca_set = false
description = "TLS inspection for detailed URL filtering"
}
|
新たな問題:証明書エラーの嵐
TLS Inspectionを有効化した瞬間、開発環境は再び使用不可能に。
1
2
3
|
# 大量の証明書エラー
curl: (60) SSL certificate problem: unable to get local issuer certificate
git: fatal: unable to access 'https://github.com/': SSL certificate verify failed
|
証明書配布の戦い
各ワークステーションに証明書を配布する必要がありました。
1
2
3
4
5
6
|
# 各ワークステーションで実行が必要(手動配布の悪夢)
sudo cp /path/to/inspection-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# Git用の設定
git config --global http.sslCAInfo /usr/local/share/ca-certificates/inspection-ca.crt
|
配布作業の現実:
- 開発者50名 × 平均3台のワークステーション = 150台の設定作業
- 手動配布による設定ミス多発
- 開発者からの「面倒くさい」という不満の声
第四章:自動化とスクリプト化による解決
自動設定スクリプトの開発
証明書配布を自動化するスクリプトを作成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#!/bin/bash
# setup-secure-proxy.sh
set -euo pipefail
# 証明書ダウンロードと設定
echo "Setting up secure proxy certificates..."
# GCS から証明書をダウンロード
gsutil cp gs://company-security-certs/inspection-ca.crt /tmp/
# システムに証明書を追加
sudo cp /tmp/inspection-ca.crt /usr/local/share/ca-certificates/company-inspection-ca.crt
sudo update-ca-certificates
# Git設定
git config --global http.sslCAInfo /usr/local/share/ca-certificates/company-inspection-ca.crt
# Python/pip設定
pip config set global.cert /usr/local/share/ca-certificates/company-inspection-ca.crt
# Node.js/npm設定
npm config set cafile /usr/local/share/ca-certificates/company-inspection-ca.crt
echo "Setup completed successfully!"
|
ワークステーション起動時の自動実行
Cloud Workstationsの起動スクリプト機能を活用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
resource "google_workstations_workstation_config" "main" {
workstation_config_id = "${var.prefix}-config"
location = var.region
workstation_cluster_id = google_workstations_workstation_cluster.main.workstation_cluster_id
container {
image = "us-central1-docker.pkg.dev/cloud-workstations-images/predefined/code-oss:latest"
# 起動時に証明書設定を自動実行
run_as_user = 1000
working_dir = "/home/user"
env = {
"SETUP_SCRIPT_URL" = "gs://company-scripts/setup-secure-proxy.sh"
}
}
# 起動時に自動実行
persistent_directories {
mount_path = "/home/user"
gce_pd {
size_gb = 100
}
}
}
|
第五章:最終的な解決策の実装
完成したSecure Web Proxyルール
数週間の試行錯誤の末、完成したルールセット:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
# 最終的な解決策
resource "google_network_security_gateway_security_policy_rule" "allow_approved_repos" {
name = "allow-approved-repos"
location = var.region
gateway_security_policy = google_network_security_gateway_security_policy.main.name
priority = 100
enabled = true
# 承認されたリポジトリのみ許可(正規表現使用)
session_matcher = <<-EOF
host() == 'github.com' &&
(inUrlPath('/mycompany/project-backend') ||
inUrlPath('/mycompany/project-frontend') ||
inUrlPath('/mycompany/shared-utils') ||
inUrlPath('/mycompany/infrastructure'))
EOF
basic_profile = "ALLOW"
}
# GitHub API アクセス用
resource "google_network_security_gateway_security_policy_rule" "allow_github_api" {
name = "allow-github-api"
location = var.region
gateway_security_policy = google_network_security_gateway_security_policy.main.name
priority = 101
enabled = true
session_matcher = <<-EOF
host() == 'api.github.com' &&
(inUrlPath('/repos/mycompany/project-backend') ||
inUrlPath('/repos/mycompany/project-frontend') ||
inUrlPath('/repos/mycompany/shared-utils') ||
inUrlPath('/repos/mycompany/infrastructure'))
EOF
basic_profile = "ALLOW"
}
# Git オブジェクト配信用
resource "google_network_security_gateway_security_policy_rule" "allow_git_objects" {
name = "allow-git-objects"
location = var.region
gateway_security_policy = google_network_security_gateway_security_policy.main.name
priority = 102
enabled = true
session_matcher = 'host() == "objects.githubusercontent.com"'
basic_profile = "ALLOW"
}
|
監査とロギングの強化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 詳細なロギング設定
resource "google_logging_log_sink" "proxy_logs" {
name = "${var.prefix}-proxy-logs"
destination = "bigquery.googleapis.com/projects/${var.project_id}/datasets/security_logs"
filter = <<-EOF
resource.type="gce_network"
AND protoPayload.serviceName="networksecurity.googleapis.com"
AND protoPayload.methodName="google.cloud.networksecurity.v1.NetworkSecurity.EvaluatePolicy"
EOF
bigquery_options {
use_partitioned_tables = true
}
}
|
リポジトリ追加のプロセス自動化
開発チームがストレスなく新しいリポジトリを追加できるよう、セルフサービスポータルも構築。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# repository_approval_bot.py
import json
from google.cloud import storage
from github import Github
def approve_repository_request(request):
"""GitHub Issueからリポジトリ許可申請を処理"""
# GitHub Issueから申請情報を解析
issue_body = request.json['issue']['body']
repo_name = extract_repo_name(issue_body)
business_justification = extract_justification(issue_body)
# 承認ワークフロー(簡略化)
if validate_business_case(business_justification):
# Terraformコードを自動更新
update_terraform_config(repo_name)
# PR作成
create_infrastructure_pr(repo_name)
# Slack通知
notify_security_team(repo_name, business_justification)
return {'status': 'processed'}
|
第六章:運用開始後の現実
成功指標
実装から3ヶ月後の結果:
1
2
3
4
5
|
# セキュリティ監査結果
- 不正アクセス事案: 0件
- 許可されていないリポジトリへのアクセス試行: 127件(全てブロック済)
- 開発者満足度: 8.2/10(初期3.1から大幅改善)
- リポジトリ追加申請の平均処理時間: 2.3時間(以前は2-3日)
|
想定外だった問題
1. パフォーマンスの劣化
1
2
3
4
|
# TLS Inspectionによるレイテンシ増加
git clone性能比較:
- 直接アクセス: 15秒
- Secure Web Proxy経由: 23秒(約50%増)
|
対策:
- 地域別Proxyインスタンスの配置
- キャッシュ機能の活用
2. 外部依存関係の問題
npmパッケージのインストールで予期しないエラーが多発。
1
2
3
|
# 実際に起きた問題
npm install axios
# ERROR: Unable to verify SSL certificate for registry.npmjs.org
|
対策:
パッケージレジストリ用の別ルールを追加。
現在進行形の課題
1. 運用コスト
- TLS Inspectionの処理によるコスト増(月額約40万円増)
- 専任管理者の配置が必要
2. 新技術への対応
- GitHub Codespaces使用時の制約
- Docker Hub等、新しいレジストリへの対応
第七章:得られた教訓と今後の展望
5つの重要な教訓
1. 完璧を求めすぎない
初期は100%セキュアな環境を目指しましたが、実用性とのバランスが重要。
2. 開発者体験を軽視しない
セキュリティを理由に開発効率を著しく下げると、結果的に抜け道を作られる。
3. 段階的な導入
一度に全てを変更せず、段階的にルールを厳格化。
4. 自動化は必須
手動作業が多いと、必ず運用が破綻する。
5. 透明性の確保
なぜそのルールが必要なのか、開発者に理解してもらう。
今後の計画
短期(3ヶ月以内):
- より細かい粒度でのアクセス制御(ブランチレベル)
- リアルタイムのアクセス監視ダッシュボード
中期(6ヶ月以内):
-機械学習による異常検知
長期(1年以内):
- ゼロトラストネットワークモデルの完全実装
- 他社事例の共有によるコミュニティ貢献
まとめ:失敗から学んだ本当に大切なこと
この3ヶ月間の経験で最も学んだことは、**「セキュリティと生産性は対立するものではなく、適切に設計すればお互いを高め合える」**ということです。
最初の事故は確かに大きなインパクトでしたが、それをきっかけに組織全体でセキュリティ意識が向上し、結果的には以前より安全で効率的な開発環境を構築できました。
重要なのは、失敗を恐れずに試行錯誤を続けること。そして、技術的な解決策だけでなく、人とプロセスの改善も同時に進めることでした。
同じような課題に直面している方の参考になれば幸いです。何か質問があれば、お気軽にコメントをお残しください。
実装詳細資料
今回の実装で使用したTerraformコードやスクリプトは、こちらのGitHubリポジトリで公開しています(社内限定)。
参考文献