制御不能な自動化は「凶器」となる。Drive.SerialNumberによる環境ロックの極意
自動化エンジニアの諸君。
「動けばいい」という考えでコードを書くのは今日限りでやめてもらいたい。
業務効率化のために配布したVBScriptが、権限のないユーザーのPCで実行され、機密性の高いファイルが書き換えられたり、意図しないネットワークドライブにデータを流し込んだりする……。そんな事態を招いたとき、君たちは「仕様です」と胸を張って言えるか?
自動化ツールは、正しく制御されて初めて「道具」になる。制御を失ったスクリプトは、組織にとっての「凶器」だ。
今回は、`FileSystemObject`(FSO)の `Drive.SerialNumber` プロパティを使い、「許可された特定のPC、あるいは特定のストレージ(USBメモリ等)でのみ動作を許可する」という、実務におけるセキュリティ・ガードロジックの設計思想を伝授する。
1. なぜ「シリアルナンバー」なのか? ―― 識別子の選定における勘違い
まず、多くの初心者が陥る致命的な勘違いを正しておこう。
`Drive.SerialNumber` が取得するのは、物理的なハードウェア(SSDやHDD)に刻印された製造番号ではない。それは、そのボリューム(パーティション)がフォーマットされた際に割り当てられる「ボリューム・シリアルナンバー」だ。
「物理シリアルが取れないなら意味がないのでは?」と思うかもしれない。だが、業務自動化の現場においては、むしろこれで十分なケースが多い。
- PCの特定: 特定の端末(Cドライブ)でのみ実行させたい。
- メディアの特定: 支給した特定のUSBメモリ(Dドライブ等)が挿入されている時だけ、データの読み書きを許可したい。
この「環境の制約」をコードに組み込むことが、堅牢な設計の第一歩だ。
2. 凡庸なコードと、プロフェッショナルな設計の違い
私が現場で目にする「非効率で危険なコード」と、私が推奨する「堅牢な設計」を比較してみよう。
【Bad Practice】ハードコーディングと例外処理の欠如
' 典型的なダメな例
Set fso = CreateObject("Scripting.FileSystemObject")
' シリアルを直接書き込んでいる(保守性が皆無)
If fso.Drives("C:").SerialNumber <> "1234-ABCD" Then
MsgBox "許可されていないPCです。"
WScript.Quit
End If
なぜダメなのか?
1. 保守性の欠如: 許可するPCが増えるたびに、ソースコードを書き換えて再配布しなければならない。これはエンジニアの仕事ではない。
2. 脆弱なエラーハンドリング: もし `C:` ドライブが存在しない、あるいは権限がない場合、このスクリプトは即座にランタイムエラーでクラッシュする。
3. 論理の硬直化: 単一のIDしかチェックできないため、拡張性がない。
【Best Practice】ガード・ロジックの分離と外部設定
プロフェッショナルは、「実行ロジック」と「環境判定ロジック」を完全に分離する。 許可リストは外部ファイル(Configファイル)に持たせ、スクリプト本体は「許可されたか否か」の結果だけを受け取る設計にする。
3. プロダクション・クオリティのコード実装例
以下に、実務でそのまま使えるレベルの、堅牢な実装例を示す。
このコードは、外部のテキストファイル(`allowed_drives.txt`)に記載されたシリアルナンバーと照合する仕組みだ。
'==============================================================================
' Script Name: SecureExecutionGuard.vbs
' Description: Drive.SerialNumberを用いた実行環境制限ロジック
' Author: Chief Architect
'==============================================================================
Option Explicit
' --- 設定定数 ---
Const CONFIG_FILE = "allowed_ids.txt" ' 許可されたシリアルを記述したファイル
Const TARGET_DRIVE = "C:" ' 監視対象のドライブ
Dim fso, targetSerial, isAuthorized
Set fso = CreateObject("Scripting.FileSystemObject")
' 1. 環境チェックの実行
isAuthorized = CheckEnvironment(fso, TARGET_DRIVE, CONFIG_FILE)
' 2. ガード・ロジック:許可されていなければ即座に終了
If Not isAuthorized Then
' ログ出力などの実務的な処理をここに入れるべきだが、
' セキュリティの観点から、詳細な理由は伏せるのが定石。
MsgBox "実行権限がありません。管理者にお問い合わせください。", vbCritical, "Access Denied"
WScript.Quit
End If
' -----------------------------------------------------------------------------
' メイン処理 (ここから下に本来の業務ロジックを書く)
' -----------------------------------------------------------------------------
MsgBox "認証成功。業務プロセスを開始します。", vbInformation, "System Ready"
' =============================================================================
' 関数: CheckEnvironment
' 引数: fso (FileSystemObject), driveLetter (String), configFile (String)
' 戻り値: Boolean (許可されていればTrue)
' =============================================================================
Function CheckEnvironment(objFSO, driveLetter, cfgPath)
Dim driveSerial, configFile, ts, line, matched
matched = False
driveSerial = ""
On Error Resume Next ' エラーハンドリングの開始
' A. 対象ドライブのシリアル取得
If objFSO.DriveExists(driveLetter) Then
driveSerial = objFSO.Drives(driveLetter).SerialNumber
Else
Err.Raise 1, "CheckEnvironment", "Target drive not found."
End If
' B. 許可リストファイルの読み込みと照合
If objFSO.FileExists(cfgPath) Then
Set ts = objFSO.OpenTextFile(cfgPath, 1) ' 1 = ForReading
Do Until ts.AtEndOfStream
line = Trim(ts.ReadLine)
' 空行やコメント行(#で始まる)を無視する設計
If line <> "" And Left(line, 1) <> "#" Then
If line = driveSerial Then
matched = True
Exit Do
End If
End If
Loop
ts.Close
Else
Err.Raise 2, "CheckEnvironment", "Configuration file missing."
End If
If Err.Number <> 0 Then
' エラーが発生した場合は、安全のためFalseを返す
' 実務では、ここでシステムログへの書き出しを行う
Err.Clear
matched = False
End If
On Error GoTo 0 ' エラーハンドリングの解除
CheckEnvironment = matched
End Function
設計のポイント解説
1. ガード・クローズ(Guard Clause)の採用:
`CheckEnvironment` 関数の戻り値が `False` であれば、メインロジックに一切触れさせずに `WScript.Quit` で終了させる。これにより、メイン処理のネストが深くならず、読みやすい。
2. 堅牢なエラーハンドリング:
`On Error Resume Next` を関数内に閉じ込め、ドライブの不在や設定ファイルの欠落といった「外部要因によるエラー」を適切にキャッチしている。エラーが起きたときは「失敗(False)」とみなす。これが安全設計の鉄則だ。
3. 外部設定による高い保守性:
`allowed_ids.txt` を用意し、そこにシリアルを1行ずつ書くだけで、コードを一切触らずに許可端末を増やせる。
*例 (`allowed_ids.txt`):*
# 許可されたドライブシリアルリスト
1234-ABCD
5678-EFGH
4. セキュリティ・バイ・オブスキュリティ(隠蔽による安全):
エラーメッセージに「シリアル番号が一致しません」と具体的に書かない。攻撃者にヒントを与えないためだ。
4. 運用における注意点:データベース・ファイル連携への影響
このロジックを導入する場合、以下の点に留意せよ。
- ネットワークドライブの扱い:
`fso.Drives(“Z:”)` のようにネットワークドライブを指定した場合、環境によってはシリアルが取得できなかったり、接続の遅延によりタイムアウトが発生したりする。ネットワーク越しに実行させる場合は、必ず `DriveExists` メソッドで存在確認を行うこと。
- ログ管理の重要性:
「なぜ動かなかったのか」という問い合わせは必ず来る。認証失敗時に、「どのシリアルが取得されたか」を(ユーザーには見せず)隠しログファイルに書き出す設計にしておけ。これがなければ、デバッグの際に君の時間は無駄に奪われることになる。
- 構成管理:
`allowed_ids.txt` 自体の改ざんを防ぐ必要がある。このファイルを読み取り専用にするか、あるいはスクリプト本体と組み合わせて、設定ファイル自体も暗号化・チェックサム検証を行うのが、真のアーキテクトの仕事だ。
まとめ
`Drive.SerialNumber` を使った制御は、単なる「鍵」ではない。それは、君が作成した自動化ツールが、「意図した場所で、意図した通りに動く」ことを保証するためのプロフェッショナルな規律だ。
コードを書くときは、常に「もし、この環境が想定外だったら?」という問いを自分に投げかけろ。その思考の積み重ねが、バグのない、堅牢なシステムを生むのだ。
