【緊急事態】Cloud Workstationsのセキュリティ事故から学んだSecure Web Proxy実装の全記録

実際に発生したセキュリティ事故を受けて、Cloud WorkstationsとSecure Web Proxyを組み合わせた厳格なアクセス制御システムを構築した体験談。失敗から学んだ実装のポイントを包み隠さず共有します。

はじめに - まさかの事態発生

2025年8月のある金曜日、午後3時。私の携帯に緊急の電話が入りました。

「緊急です!開発環境からプライベートリポジトリへの不正アクセスが検知されました。すぐに対応をお願いします!」

セキュリティ部門からの一報でした。調査の結果、Cloud Workstationsを利用していた開発者が、業務で使用すべきでないプライベートなGitHubリポジトリにアクセスしていたことが判明。幸い実害はありませんでしたが、この事件をきっかけに「開発環境のアクセス制御を根本から見直す」プロジェクトが立ち上がりました。

これは、その時の失敗と学びの記録です。

第一章:楽観的な設計の落とし穴

事故前の環境

事故発生前の私たちの開発環境は、一見合理的に見えました:

1
2
3
4
5
# 事故前の設定(問題あり)
allowed_urls:
  - "github.com/mycompany/*"    # 会社のリポジトリ全体
  - "*.npmjs.org"               # npm packages
  - "pypi.org"                  # Python packages

何が問題だったのか?

  1. 組織レベルの許可: github.com/mycompany/* という設定により、社員が個人的に作成したリポジトリにもアクセス可能
  2. 監査の不備: どのリポジトリにアクセスしているかの詳細な記録がない
  3. 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は内部的に:

  1. .gitサフィックスを付けてアクセス
  2. api.github.com のAPIを呼び出し
  3. objects.githubusercontent.com からオブジェクトをダウンロード
  4. リダイレクト先の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リポジトリで公開しています(社内限定)。

参考文献

技術ネタ、趣味や備忘録などを書いているブログです
Hugo で構築されています。
テーマ StackJimmy によって設計されています。