2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

政府OSS『源内(genai-web)』のセキュリティ設計を解読し、LLM推論もクローズドにしてみた

2
Posted at

はじめに

GMOコネクトの永田です。

「政府OSSの源内(genai-web)は堅牢そう。ただ社内ポリシー上、LLM推論をクラウドに通すのは避けたい」というニッチな相談を受けました。

genai-web本体は無改修のまま、ExAppプラグインモデルで社内DGX Sparkに推論だけ逃がす構成に落としどころを見つけたので、その記録です。Bedrock経由で十分な組織が大半ですが、本記事は「LLMプロバイダ委託だけは切り離したい」少数派向けです。

先にまとめ

  • genai-web本体は無改修。ExAppプラグイン仕様に従い、外部AIアプリとして社内LLMを登録
  • Bedrock Guardrails圏外を Microsoft Presidio + 自前 Vault で補完
  • 既存 WireGuard 資産を流用(前回記事の続編)
  • 社内利用は数名程度の規模感で月次運用中

第1部: genai-webのセキュリティ設計を読み解く

genai-webは2026年4月にデジタル庁がOSS化した政府向け生成AI基盤で、AWS社製のGenU(Generative AI Use Cases)をベースに堅牢化と機能追加を加えたものです。CDKで一通りの構成がコード化されており、設計を読むと「政府レベルのセキュリティ要件をAWS上でどう組むか」の実例として参考になります。

1.1 強みは2軸で整理できる

リポジトリを読み込んで、セキュリティ関連の主要な強み10個を「一般Web共通かLLM特化か」「AWSサービス機能かアプリ独自実装か」の2軸で分類しました。

一般 Web サービス共通 LLM 利用サービスならでは
AWS サービス機能 CMEK + 暗号消去(KMS)
VPC隔離 + VPC Endpoints
WAF + CloudFront + Geo制限
DynamoDB PITR + TTL + CMEK
AWS 機能 + 設定判断 Cognito 脅威検出FULL + SAML + IP DENY
監査ログ別アカウント日次転送 ※
Bedrock Guardrails の日本向けチューニング
アプリ独自実装 マルチテナント ExApp プラグインモデル
Cross-account Bedrock
IaC 運用パターン cdk-nag 抑制理由の明示化

※ 監査ログ転送は DDB Export + EventBridge + Cross-account sts:AssumeRole という AWS 機能の組み合わせで、Lambda コード自体は API を叩くだけの薄い接着剤レベル。

整理してみて見えてきたのは、強みの大半はAWSサービス機能の組み合わせ + 設定判断であり、「アプリ独自実装」と呼べる強みの中で一般Web共通の項目はゼロという構造でした。アプリ独自実装は LLM 特化の領域(ExApp プラグイン / Cross-account Bedrock)に集中しており、ここが genai-web の真の独自価値、と読み替えることができます。

1.2 一般Webサービス共通の対策(AWSサービス機能で完結)

第1部の主役ではないので表で要点だけ。各項目はAWSの推奨パターンそのものです。

項目 実装の要点 参考
CMEK + 暗号消去 DDB / S3 / Logs / SNS / SQS / Cognito を 同一CMK で暗号化、削除で復元不可 Well-Architected SEC08-BP02 Enforce encryption at rest
VPC + VPC Endpoints Private subnet + NAT egress only、DDB/Secrets Manager は AWSバックボーン経由 Well-Architected SEC 5: How do you protect your network resources?
WAF + CloudFront + Geo IPv4/v6/国コードの AND条件、ログ1年保持KMS暗号化 AWS Prescriptive Guidance: Restrict access based on IP address or geolocation by using AWS WAF
Cognito + SAML + IP DENY SAML有効時セルフサインアップ自動無効化、NotIpAddress でDENY Amazon Cognito: Advanced security with threat protection
監査ログ別アカウント転送 DDB日次エクスポート → Cross-account Lambda転送 DynamoDB Export to S3

「政府レベル」と聞くと身構えますが、要素技術はAWSの標準的な堅牢化パターンの集合です。同じ設定パターンは他のWebアプリにも横展開できます。

1.3 LLM特化の独自価値(genai-web ならではの領域)

genai-web独自の価値は次の2点に集約されます。

Bedrock Guardrails の日本向けPIIチューニング (guardrail.ts)

piiEntitiesConfig: [
  { action: 'BLOCK', type: 'AGE' },
  { action: 'BLOCK', type: 'AWS_ACCESS_KEY' },
  { action: 'BLOCK', type: 'CREDIT_DEBIT_CARD_NUMBER' },
  { action: 'BLOCK', type: 'EMAIL' },
  { action: 'BLOCK', type: 'IP_ADDRESS' },
  { action: 'BLOCK', type: 'PHONE' },
  // ...計17種、ただし NAME / DRIVER_ID / CA_* / US_* / UK_* は意図的に除外
]

NAMEDRIVER_ID は日本仕様で機能しないため誤検知抑制目的で意図的に除外、というコメントが付いています。マイナンバー・日本のパスポート等のBedrock非対応種別は別途regexで補完する設計思想ですが、現時点で genai-web OSS にはこの補完層は実装されておらず、ExApp 側で持つ前提になっています。第3部のハマり2でこの空白を埋める実装を入れます。

マルチテナント ExApp プラグインモデル

外部AIアプリをJSONスキーマ(text / select / file 等)で登録すると、genai-webが入力フォームを自動生成し、HTTP経由でAIアプリ側を呼び出します。チーム単位RBAC、Secrets Manager によるAPIキー管理、HMAC匿名化ユーザーID、202応答時のSQSポーリング等が組み込まれていて、外部AIアプリの追加・差し替えを安全に行えます。第3部で実物に乗ります。

第2部: 「LLM推論」の委託先だけを切り離す

2.1 委託先は二階建て

genai-webをAWSにデプロイすると、データを預ける委託先は2種類に分かれます。

委託先 内容
AWS(IaaS / PaaS) Lambda 実行、DDB保存、KMS、Cognito、CloudFront 等のWeb層全体
LLM プロバイダ Bedrock経由の Anthropic Claude / Amazon Nova / Mistral 等

契約主体・運用主体・セキュリティ評価が完全に別物で、FISC外部委託リスク管理基準でも個別に管理が想定されています(参考: FDUA生成AIガイドライン)。

2.2 Bedrockの契約は十分強い

Bedrockは AWS Service Terms により入出力が学習に使われず、通信もAWSバックボーン内で完結し、保存もゼロです。通常はここで十分です。本記事はこの先に進みたい少数派向けの内容です。

2.3 それでも切り離したい組織

実務上、次のような事情で「LLMプロバイダ委託だけは避けたい」となる組織があります。

  • 委託先管理コスト(契約レビュー・年次評価・監査対応)を減らしたい
  • 契約上のリスクではなく、契約外のリスクまで物理的に排除したい
  • 既存の社内規定が「LLM as a Service 利用禁止」と書かれており、規定改定が間に合わない

ポリシーの問題なので、技術的に正しいかという議論ではなく、組織の事情として存在するケースです。

2.4 本記事のスコープ

本記事は「AWS の IaaS / PaaS 委託は許容するが、LLMプロバイダ委託だけは切り離したい」組織向けです。クラウド全停止が要件の組織は対象外です。

第3部: ローカルLLMをExAppとして繋ぐ

3.1 全体アーキテクチャ

WG serverとDGX Sparkの接続は前回記事で構築済みなので、その上にgenai-webからの呼び出し経路を載せた形です。

3.2 ExAppプラグインモデルが綺麗に効く

genai-webのExApp仕様は「HTTPSエンドポイント + APIキー + 入力フォーム定義JSON」というシンプルな契約です。

ExAppが受け持つ範囲 genai-web側で受け持つ範囲
POST /invoke で推論 UI生成、認証、ログ保存、課金、監査
GET /status/{id} で状態確認 非同期ポーリング、タイムアウト管理
自分の業務範囲のガードレール Bedrock経由部分のGuardrails

genai-web本体には一切手を入れないため、git pullでバージョン追随できる状態を維持できます。これが今回の構成で一番大きい利点でした。

3.3 FastAPI最薄実装(VPC内 Lambda → WG server → DGX Spark)

genai-web の CDK は vpcIdForInvokeExApp コンテキストで InvokeExApp Lambda を 既存VPC に配置できる仕組みを持っています。前回記事で作った WireGuard 用 VPC をそのまま指定すれば、Lambda はインターネットを経由せずに WG server のプライベート IP へ直接到達します。

WG server 上の FastAPI(350行程度)は実質的に「プロトコル変換 + PIIガード + 非同期ジョブ管理」を兼ねた一枚仕立てのリバースプロキシです。

  • プロトコル変換: genai-web の ExApp 仕様(POST /invoke + 202 + status_url)↔ Ollama の OpenAI 互換 API(/v1/chat/completions
  • PIIガード: ハマり2 で扱う Presidio + 自前 Vault、または regex baseline
  • 非同期ジョブ管理: asyncio.create_task + dict ベースのジョブ状態保持
  • HTTPS 終端: ハマり3 で扱う自己署名 cert で 8443 を待ち受け

要点だけコードを抜粋します。

@app.post("/invoke")
async def invoke(req: InvokeRequest, x_api_key: str | None = Header(default=None, alias="x-api-key")):
    _require_api_key(x_api_key)
    request_id = str(uuid.uuid4())
    # 即座に202で返し、推論はasyncio.create_taskで裏で回す
    asyncio.create_task(_run_inference(request_id, req, x_user_id))
    return JSONResponse(
        status_code=202,
        content={"request_id": request_id, "status": "PENDING",
                 "status_url": f"/status/{request_id}"},
    )

裏側のワーカーは「入力ガードレール → Ollama呼出 → 出力ガードレール」の3段で、それぞれの段で問題が起きたら status を書き換えるだけのシンプルな構造です。プロセスを分けてマイクロサービス化する選択肢もありましたが、責務が小さく1ファイルで全体把握できる範囲に収まったため、一枚に寄せています

ハマり1: API Gateway 29秒制限

genai-webのInvokeExApp Lambdaは API Gateway経由で呼ばれるため、29秒の統合タイムアウトを超えるとクライアント側が打ち切ります。長文推論や大きいモデルだとローカルLLMでも余裕で超えます。

genai-webのExApp仕様は元から非同期モードを想定していて、POST /invoke202 + status_url を返すと、genai-web側のPollExAppStatus LambdaがSQS backoffで定期的に状態取得しに来てくれます。FastAPI側はfastapi-async-job のような既製品を使わず素朴に asyncio.create_task で実装しました。シングルプロセス前提(uvicorn --workers 1)でジョブ状態をdictで持つだけです。

ハマり2: 念のためのPIIフィルタをExAppに持たせる判断

InvokeExApp Lambdaはユーザー入力をそのままExAppにforwardするだけで、Bedrock Guardrailsを経由しません。genai-webの設計上、ExApp側にガードレールを持たせる前提になっているためです。ドキュメント上は明示されておらず、ソースコメント止まりでした。各クラウドのExAppテンプレート(genai-ai-api)も実装としてはガードレールが入っていません。

ただし正直に言うと、本構成ではLLM推論がローカル DGX Spark で完結しているため、委託先管理ポリシーの観点だけで言えば PII フィルタは必須ではありません。PIIが流れる先が社内資産だけなので、社内DBに流れるのと同じ位置付けです。

それでも次の副次的な理由で薄く入れることにしました。

  • DynamoDB ChatLogs への PII 永続化を抑える(運用者・監査者の閲覧時の最小権限)
  • LLM ハルシネーションで無関係な氏名・メアドが出力に混入するケースの保険
  • 将来 Bedrock 併用や別プラットフォーム移行時の保険

「ガードレール必須だから入れた」ではなく「念のため入れた」というスタンスです。ExApp境界のフィルタとして4ツールを比較しました。

ツール 主目的 可逆Masking 備考
Microsoft Presidio PII検出+匿名化(汎用) ✅ encrypt operator 他3ツールが内部で利用
LLM Guard LLM入出力スキャナ ✅ Vault パターン 内部でPresidio利用
NeMo Guardrails 会話フロー・トピック制限 ✗ BLOCK中心 用途違い
Guardrails AI LLM出力 validator ✗ 検証中心 Maskには不向き

4ツールいずれも内部でPresidioを呼んでおり、日本PII recognizer(マイナンバー / 運転免許 / 銀行口座等)はどのツールを選んでもPresidioのPattern recognizerを書く作業に帰結します。LLM Guardが機能要件にフィットし第一候補でしたが、transformers連鎖で2GB近い依存になるため、結局Presidio直接 + 自前Vaultで実装しました。Vaultは30行程度です。

@dataclass
class Vault:
    mapping: dict[str, str] = field(default_factory=dict)
    counter: dict[str, int] = field(default_factory=dict)

    def reserve(self, entity_type: str, original: str) -> str:
        self.counter[entity_type] = self.counter.get(entity_type, 0) + 1
        placeholder = f"[{entity_type}_{self.counter[entity_type]}]"
        self.mapping[placeholder] = original
        return placeholder

    def restore(self, text: str) -> str:
        out = text
        for placeholder in sorted(self.mapping.keys(), key=lambda s: -len(s)):
            out = out.replace(placeholder, self.mapping[placeholder])
        return out

実装中に踏んだPython正規表現の罠も書いておきます。\b は Unicode mode でひらがな・カタカナ・漢字も「word char」扱いになるため、連絡先は090-1234-5678です のような日本語直接隣接ケースでJP_PHONE推定が発火しません。(?<!\d)...(?!\d) のlookaroundに置き換えて解決しました。

ハマり3: 自己署名cert + Lambda Node.js TLS

genai-webのExApp登録UIは https:// 必須で、http:// は弾かれます。社内向け検証段階でACM管理の正規証明書を出すのも面倒だったので、自己署名証明書 + uvicornのTLS終端で対応しました。

ただしLambda Node.jsはデフォルトで自己署名を拒否するので、Lambda環境変数に NODE_TLS_REJECT_UNAUTHORIZED=0 を投入する必要があります。CDKデプロイのたびにドリフトで戻るので、post-deploy-tls-bypass.sh というスクリプトでLambda環境変数のpatchを毎回当て直す運用にしました。本番化するならACM正規cert + Route53が筋ですが、検証段階としてはこれで十分としました。

第4部: 結果と考察

4.1 ローカルLLM呼び出し化まとめ

観点 Bedrockのみ ローカルLLM統合後
LLM推論データの経路 AWSバックボーン内で完結 社内 WireGuard tunnel 内で完結
LLMプロバイダへの委託 あり(契約で管理) なし(経路上に存在せず)
Bedrock Guardrails 適用 圏外 → Presidio + 自前 Vault で補完
genai-web本体のセキュリティ 全機能維持 同左

4.2 トレードオフ

  • 運用負荷増: WG server + DGX Spark の死活監視、モデル更新作業が自社管理に
  • フェイルオーバーなし: DGX Spark が落ちたら停止
  • 最新モデル追随の遅れ: Bedrock側の新モデル即時反映は享受できない
  • ガードレールの自前管理: regex / Presidio Pattern を自分で更新する必要がある

これらを許容できる組織だけが選ぶ構成です。

4.3 結語

genai-webの設計を読み解いた限り、Bedrock経由で運用しても十分堅牢で、多くの組織はそのままで足ります。ただ「Web層は政府レベルOSSの堅牢性をそのまま借りつつ、LLM推論層だけ自社内に閉じる」という分岐が、ExAppプラグインモデルのおかげで本体無改修で実現できる、というのは想像以上に綺麗な設計でした。

委託先管理ポリシー上「LLMプロバイダ委託だけは避けたい」少数派の組織にとっては、現実的な選択肢になりうる構成だと思います。

まとめ

  • genai-webのセキュリティ設計の半分はAWSサービス機能の組み合わせで、AWS Best Practiceの集合体としても参考になる
  • LLM特化の独自価値は「日本向けGuardrails設定」と「ExAppプラグインモデル」の2点
  • ExAppプラグインを使えば本体無改修のままLLM推論層だけクローズドにできる
  • Bedrock Guardrails圏外はPresidio + 自前Vaultで念のため補完(ローカルLLMだけなら必須ではないが副次理由で薄く入れた)
  • Bedrockで足りる組織はそれが最適。本記事はあくまでニッチな委託先管理要件向け

最後に、GMOコネクトではサービス開発支援や技術支援をはじめ、幅広い支援を行っておりますので、何かありましたらお気軽にお問合せください。

お問合せ:https://gmo-connect.jp/contactus/

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?