Google Cloud生成AIセキュリティ対策: Sensitive Data Protection
こんにちは山下です。
以前に引き続き生成AIセキュリティ対策について記事を作成していきたいと思います。
Next'25以前からSecurity Command Center(SCC)の機能群にいくつかサービス追加が行われており、その内GAとなっている機能が生成AIのセキュリティ対策に有用なため触れていきます。
前回のModel Armorに加え、今回はSensitive Data Protection(以降SDP)についてみていきます。SCCのAI ProtectionシリーズのSDP Discoveryが該当のものになります。
SDPは文字通り機密情報や個人情報等、重要データを保護するためのサービスです。そのカバー対象としているものとカバーできないもの、特徴を今回見ていきたいと思います。
そもそも機密情報とは(infoType)
Google Cloudでは取り扱われるデータの種別をinfoTypeとして定義しています。その中でも機密情報にあたるものとして個人の公的な情報(パスポート番号、マイナンバー、運転免許証番号など)、金融に関わる情報(クレジットカード番号、銀行口座番号など)、個人を特定できる情報(住所、電話番号、メールアドレス)があります。
これらはすべて事前定義の種別として定義されており、その重要度もまちまちです。内容は多岐に及ぶためそれぞれ紹介はしません。以下のリンクにて列挙されています。
特徴的なのは、世界共通で個人情報や機密情報に当たるグローバルカテゴリーのものと各国の政府から発行される公的証明書/番号については国毎に定義されている点です。日本では以下が国別の機密情報として定義されいています。もちろん保険証やインボイス事業者番号など民間や自治体/省庁が出しているものもありますが、一様に定義されよく使われるものが対象となっております。
infoType | 説明 |
---|---|
JAPAN_BANK_ACCOUNT |
日本の銀行口座番号。 |
JAPAN_CORPORATE_NUMBER |
日本の法人番号。 |
JAPAN_DRIVERS_LICENSE_NUMBER |
日本の運転免許証番号。 |
JAPAN_INDIVIDUAL_NUMBER |
日本国民識別番号は、2016 年 1 月から始まった新しい国民 ID 番号であり、「マイナンバー」と呼ばれます。 |
JAPAN_PASSPORT |
日本のパスポート番号。パスポート番号はアルファベット 2 文字と 7 桁の数字で構成されています。 |
実機の”構成”、”INFOTYPE”からも確認することができます。
事前定義にはない個人を特定できる情報やサービスや企業で扱われる独自の機密情報についてはカスタムで定義する事ができます。単純な一致した文字列の検出から、正規表現を用いた検出も行えます。
ちなみに、日本の個人情報保護法では以下のように個人情報が定義されています。情報は個人を識別/特定できるものが対象のため、顔や声、動作や挙動といった非言語的なものも含まれているのが特徴です。つまり、テキストではないメディアやデータについても取り扱いを慎重に行う必要があります。
個人情報に当たるもの
-
特定の個人を識別できる情報
その情報に含まれる氏名、生年月日、その他の記述等(文書、図画もしくは電磁的記録に記載・記録され、または音声、動作その他の方法を用いて表された一切の事項)により、特定の個人を識別することができるもの。他の情報と容易に照合することができ、それにより特定の個人を識別することができることとなるものを含みます。 -
個人識別符号が含まれる情報
その情報単体で特定の個人を識別できる、法律で定められた文字、番号、記号その他の符号が含まれるもの。
また、私は法律のプロでは正直ないので、、Geminiの力も借りて個人情報以外で機密情報に当たるものも挙げてみました。
他に日本で機密情報に当たるもの
情報区分 | 主な根拠法 | 概要 | 具体例 |
---|---|---|---|
営業秘密 | 不正競争防止法 | 企業活動に有用で、秘密として管理されている技術・営業情報 | 顧客リスト、製造ノウハウ、設計図、販売マニュアル、事業計画 |
特定秘密 | 特定秘密の保護に関する法律 | 国の安全保障に関する特に重要な情報で、行政機関の長が指定したもの | 防衛計画、重要な外交交渉内容、諜報活動に関する情報、暗号 |
防衛秘密 | 自衛隊法 など | 日本の防衛に特化した秘密情報 | 部隊の運用計画、装備品の詳細な性能 |
職務上の秘密 | 国家公務員法、地方公務員法 | 公務員が職務上知り得た、まだ公開されていない情報全般 | 政策の内部検討資料、許認可の審査内容 |
- この他に、医師や弁護士などが負う**「職業上の守秘義務」(刑法第134条)の対象となる情報もありますが、これらは特定の職務倫理に基づくもので、上記の法律による定義とは少し性質が異なります。 また、法律で直接定義されていなくても、企業や組織が契約(秘密保持契約:NDA)**によって、特定の情報を「機密情報」と定義し、法的な拘束力を持たせることも一般的です。
余談:
Google Cloudでデータの分類/検出するための仕組みがあるとはいえ、正直なところ重要機密の情報をパブリッククラウド上で扱うことに抵抗があるケースがほとんどだと思います。
例えば、日銀が2024年に出している金融でのクラウドサービス利用率を見るとクラウドサービスを93.5%と多く利用しているのに比べ、勘定系などの基幹システムでの利用が5.8%と極端に低いことからも伺えます。
ただ、クラウド利用の懸念点として一番多く挙げているのは、人材/ノウハウの不足や体制確立といった人や運用に関わるところがメイン(上位3つ)でセキュリティに関わる機密性は次点かつ割合としては前者の半分程度となっています。
もちろん、上記で挙げた法的な問題もありますが、意外とSDPのようなマネージドサービスによって運用に関わる懸念が払拭できるとクラウド化の抵抗は少ないのかもしれません。
SDPがサポートするデータ範囲
文字通り機密情報保護を行うSDPの出来ることについて触れたいと思います。SDPはデータを格納するサービスに対してスキャンと検出を行うことができます。
[対象サービス]
- BigQuery
- CloudSQL
- Cloud Storage
- Vertex AI
- AWS S3
- Azure Blob(Storage Account)
[対象データ]
ファイル形式 | 説明 |
---|---|
構造化データ | BigQueryのテーブル、Cloud SQLのデータベース、Avro, CSV, TSV ファイル |
ドキュメント | Microsoft Word (.docx), Excel (.xlsx), PowerPoint (.pptx), PDF。これらのファイルは内部のテキストや構造を理解して解析(インテリジェントドキュメント解析)。 |
画像 | JPG, PNG, BMP, SVG など。OCR技術を用いて画像内に含まれるテキストを抽出し、そのテキストに対して機密情報のスキャンを実行。これにより、契約書の写真やスクリーンショット内の個人情報なども検出。 |
その他テキスト | JSON, XML, HTML、各種プログラミング言語のソースコード(.py, .java, .jsなど)、ログファイルなど、広範なテキストベースのファイルに対応。 |
一方で、以下はデータを格納するもののサポートしていないサービスになります。サポートしている形式ではあるため、今後のサポートに期待です。
[未サポートのサービス]
- Spanner
- AlloyDB
- BigTable
- Firestore
- Filestore
- BigLake
また、以下についてもデータ格納が考えられるものの一時的な格納であり最終保管場所でなかったり、バイナリーやブロックのようなセンシティブデータが直接含まれない or 解読が困難なものについても対象とはしていません。
- ArtifactRegistry
- PersistentDisk
- Pub/Sub
- ETL/ELT系(Dataflow,Dataproc,DataPrep,DataFusion)
SDPによるスキャン
SDPで実際にセンシティブデータをスキャンする際の設定、内容を見てみます。
以下の個人情報サンプルデータをテキストで、データベースのシークレットサンプルをGeminiで生成したPythonコードの2つで試してみます。
# 個人情報テキスト
My credit card number is 1234 5678 9012 3456, and my CVV is 789.
My gmail address is test@example.com.
My phone number is 090-0000-0000.
# 平文パスワード
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
app = Flask(__name__)
# WARNING: Hardcoding passwords is a significant security risk.
# This is for demonstration purposes ONLY.
DB_USER = "your_db_user"
DB_PASSWORD = "password" # The hardcoded password as requested
DB_HOST = "your_db_host" # e.g., 'localhost' or a remote host
DB_PORT = "your_db_port" # e.g., '5432' for PostgreSQL, '3306' for MySQL
DB_NAME = "your_db_name"
# Replace with your actual database URI string:
# For demonstration, let's assume PostgreSQL
if DB_HOST and DB_PORT and DB_NAME and DB_USER: # Check if variables are set to avoid errors if not configured
app.config['SQLALCHEMY_DATABASE_URI'] = f'postgresql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}'
else:
# Fallback to a local SQLite database if connection details are not fully set
# This avoids an error if the user just runs the script without setting DB_USER etc.
# but still demonstrates the password usage if those were set.
db_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'app.db')
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
print(f"Warning: Full database credentials not set. Falling back to SQLite at {db_path}")
print("If you intended to use a different DB, ensure DB_USER, DB_HOST, DB_PORT, and DB_NAME are set.")
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Example Model (Optional)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
def __repr__(self):
return f'<User {self.username}>'
@app.route('/')
def index():
return "Hello! This is a Flask app."
@app.route('/test_db')
def test_db_connection():
try:
# A simple query to test the connection
user_count = User.query.count()
return f"Successfully connected to the database. User count: {user_count}"
except Exception as e:
# Log the full error for debugging but return a generic message to the user
app.logger.error(f"Database connection failed: {e}")
return f"Database connection failed. Check server logs. Error: {str(e)}"
@app.route('/add_user/<username>')
def add_user(username):
try:
if User.query.filter_by(username=username).first():
return f"User '{username}' already exists."
new_user = User(username=username)
db.session.add(new_user)
db.session.commit()
return f"User '{username}' added successfully!"
except Exception as e:
db.session.rollback()
app.logger.error(f"Failed to add user: {e}")
return f"Failed to add user '{username}'. Error: {str(e)}"
if __name__ == '__main__':
# Create database tables if they don't exist
# In a real app, you'd likely use migrations (e.g., Flask-Migrate)
with app.app_context():
db.create_all()
app.run(debug=True)
検出ルール
機密情報を対象のリソース内で含んでいないかを検出し通知、保管する一連のアクションを定義したスキャン構成と実際に検出からアクションまでを実行する定義したジョブまたはジョブトリガーの2種に分かれます。
スキャン構成は設定なので組織ポリシーや階層ファイアウォールポリシーのように親元で定義したものを配下のフォルダ/プロジェクトに継承することも、各フォルダ/プロジェクトでオーバーライドして除外対応することも可能です。
スキャン構成の定義
検出するリソースの種別と適用範囲を組織/プロジェクト/リソースレベルで定義できます。
事前定義またはカスタムのInfoTypeを選択し、何がセンシティブか判定します。新しいテンプレートを作成とすると裏でテンプレートが生成され、自分でテンプレートを作成する場合は”構成”の”テンプレート”から作成を行うことができます。テンプレート指定でGlobalを選ぶと全リージョンにリージョンを指定すると各リージョン紐付けが可能です。
判定基準の閾値でレベル感を定義する事ができます。センシティブかどうかの可能性を確率で判定し、最小の可能性にかなり低いを設定すると全レベルで判定、かなり高いを選択するとかなり高いのみ検出となります。
ENUM | 説明 |
---|---|
VERY_UNLIKELY (かなり低い) |
次のような特徴があります。• シグナルが弱い。• コンテキストの手がかりがない。• 特定の infoType に対する否定的なシグナル。 |
UNLIKELY (低い) |
次のような特徴があります。• 1 つ以上の弱いシグナル。• 別の infoType の強いシグナル。 |
POSSIBLE (可能性あり) |
次のような特徴があります。• 特定の infoType に対する 1 つ以上のシグナル。シグナルには、チェックサムの合格が含まれる場合がある。• コンテキスト上の強い手がかりと、一意の特定の形式がない。 |
LIKELY (高い) |
特定の infoType に対する 1 つ以上の強いシグナルを特徴とします。シグナルには、チェックサムの合格、コンテキスト上の強い手がかり、一意の特定の形式が含まれます。 |
VERY_LIKELY (かなり高い) |
特定の infoType に対する多数の強いシグナルを特徴とします。シグナルには、チェックサムの合格、コンテキスト上の強い手がかり、一意の特定の形式が含まれます。 |
アクション定義によって、検出後の通知先、検出結果の保存設定を行うことができます。
検出までにとどめて連携まで行わなくてもOKです。
この構成を保存するロケーションを設定できます。
構成をこちらで設定されます。
検出結果
定義にもよりますが検出のプロフィールにて検出結果を確認することができます。
指定したロケーションまたはプロジェクトを選択することでセンシティブデータの検査結果を確認することが出来ます。わかりやすい個人情報を入れたのでエラーが機密性”高”で表示されました。
InfoTypeのクレジットカード番号、メールアドレス、電話番号で検出がされました。
ただ、Pythonコード内のデータベースパスワードも検出されていません。
ここで少し検出感度を上げてみます。最小の閾値を上げてみます。
以下のように今まで検出できていなかったPythonでのInfoType検出がされるようになりました。ただ、パスワード検出まで至れていないのでここは検出テンプレート、InfoTypeの精度をもっと上げる必要があるようです・・・。ちなみにソースコードスキャンはSASTで行うべきなのとバケットに保管されるケースもないと思うので、ここでやるべきでないのかもしれません。
検査ジョブを実行する
検出されたプロファイルからジョブの定義を行うことが出来ます。
ジョブでは検出だけにとどまらずマスキングを行えます。
匿名化テンプレートによってマスキング方法を定義することができます。
ジョブによりスキャンが行われます。ジョブは構成と同様に定期実行も1度限りの実行も可能です。
上記のジョブではエクスポート設定をしてGCSにマスキングした結果を出力するようにしています。
以下のようにジョブによって検出された結果がマスキングされています。ただ、、CVVがマスキングされていないため、こちらもInfoTypeでの調整がまだ必要そうです。
まとめ
InfoTypeの取り扱いやスキャン構成の定義など少し慣れが必要ではあるものの、機密情報の検出をからマスキングまで自動で行うことがSDPではできました。しかし、検出されきれないものがあるため、InfoTypeの調整や機密情報のサンプルデータを作成して試験を繰り返し実行する必要がありそうです。
AIエージェントがGoogleCloud内のデータを良きに計らってスキャンしてしまうのはリスクです。認証認可やVPC-SC、組織ポリシーなどで防げるものもありますが、意図せず読み込む危険性もあります。また、RAGとして取り込まれたり、ファインチューニングの学習材料に混入する危険もあります。ガードレールの一つとしてデータセキュリティに取り組んでみるのはいかがでしょうか?
Discussion