icaclsでシンプルにアクセス権制御をする。(に至るまでの天使の苦悩)


この記事でお伝えしたいこと

組織でアクセス権に関する自動処理を組みたかったら、素直にicaclsを使え。 PowerShellはやめとけ。

事の経緯

ActiveDirectory環境下でNASがある環境で、特別アクセス権を設けずに運用しているサーバーで以下の様なリクエストがあった。

仕様Rev1

  • Zさんは全社員からの書類提出をまとめる役
  • 各書類は社員本人からは見れるが、他の社員から見られてはいけない。
  • 社員は結構な頻度で増減する。フォルダの作成になるべく手間はかけたくない。
  • 基本的に ファイルの流れは 社員→Zさんの一方通行。

つまり制御の考え方としては以下の様なものということだった。

そこで、元締めのフォルダにNTFSアクセス権に以下の様な設定を施した。

ユーザ/グループ 権限 適用先
Everyone 読取りと書き込み このフォルダーのみ
Zさん フルコントロール このフォルダー、サブフォルダーおよびファイル
CREATOR OWNER (ほぼ)読取りと書き込み サブフォルダーとファイルのみ

上記の設定をした事によって、例えば Cさん がフォルダを作ると以下のNTFS権がついたフォルダが自動的にできるようになった。

ユーザ/グループ 権限 適用先
Cさん (ほぼ)読取りと書き込み このフォルダーのみ
CREATOR OWNER (ほぼ)読取りと書き込み サブフォルダーとファイルのみ
Zさん フルコントロール このフォルダー、サブフォルダーおよびファイル

めでたしめでたし

仕様Rev2

数日後 以下の問い合わせが舞い込む。

  • Zさんが作成したファイルが社員から見えない。
  • 社員が作成したファイルをZさんが編集すると見えなくなる。

つまりこういう事だ

このフォルダでは Zさんは全知全能の神である(以下Zさんは神と表記する)。Cさんはあくまで自分が作ったファイル(CREATOR OWNERであるファイル)しか触れることが許されない。神が作ったファイルは見ることも触れることも出来ないのだ。

当初の仕様では神は不可視で、供え物を渡すが、対価は求めないというストイックな仕様だったのだが、お茶目な神は下々に奇跡を与えたいという事だそうだ。

このCさんフォルダのみにおいて、Cさんに神の力を与えるには、Cさんが作成したフォルダから継承を抜いて、以下のNTFS権限設定をすればよい。

ユーザ/グループ 権限 適用先
Cさん (ほぼ)読取りと書き込み このフォルダー、サブフォルダーおよびファイル
CREATOR OWNER (ほぼ)読取りと書き込み サブフォルダーとファイルのみ
Zさん フルコントロール このフォルダー、サブフォルダーおよびファイル

Cさんの適用先を変えるだけの簡単な変更に見えるが・・・

時既にに遅し

この無数の信徒が所有するフォルダすべてに手作業をするのは、至難の業である。
また、信徒は今後も増え続ける事が予想されると。。。

かくして今日も 神に使える天使への使命(情シスへの無茶振り) が与えられたのである。*

どうにかする

Plan1. PowerShell でどうにかする。

はじめに言っておく。この方法ではどうにかできない。

PowerShell NTFSアクセス権 などと検索するとわりと情報がでてくる。
基本的には Get-Acl でアクセス権のオブジェクトを取得し、System.Security.AccessControl.FileSystemAccessRule を作成して、 Set-Acl でアクセス権を追加、変更するという手順だ。
それらを組み合わせてこんな実装をしてみた。(なおこのソースは仕様はみたせていない)

mission1.ps1
$tgtDir = "C:\TESTDIR\"
$tgtAcl = Get-ChildItem $tgtDir | Get-Acl #ターゲットフォルダ内のNTFS情報を取得する。
foreach($a in $tgtAcl){ #NTFS情報を走査する。
    foreach($ar in $a.Access){ # アクセス権の一覧を走査する。 
            if($ar.IsInherited -eq $false){ #継承が無効化されているユーザーがいる≒処理済みのフォルダと判断
                break # アクセス権変更を行わない
            }
            # 継承が無効化されたアクセス権(表示順序最上)が存在しないので処理を行う
            if($a.Owner -eq $ar.IdentityReference.Value){ # フォルダの所有者と同じユーザーが存在した時だけ処理をする。
                #所有者の権限を強くする。
                $newPermission = ($a.Owner,
                    [System.Security.AccessControl.FileSystemRights]::FullControl,
                    "ContainerInherit,ObjectInherit",
                    $ar.PropagationFlags,
                    $ar.AccessControlType
                    )
                # フォルダに対しての権限を設定する。
                $accessRule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule $newPermission
                $a.SetAccessRule($accessRule)
                $a | Set-Acl $i.PSPath
            }
        }
}

ローカルで実行するとそれらしい動きもして、神が作成したファイルが見えるようになった。
ただしこの実装では以下の点を、許容する必要があった。

  • これまで神が作成したファイルのアクセス権は変更されない。

まだ事案が発生した直後だったので、許容範囲かと思われた。
が、この実装には別の問題が隠れていたのだ。

Set-Aclは所有権の異なるファイル、フォルダには実行できない。

サーバー上でPowerShellスクリプトを実行すると セキュリティ識別子をこのオブジェクトのオーナーにすることはできません。 というエラーが吐き出された。
検索をしても国内で殆ど情報が見つからないエラーだったが、なんとか海外のTechNetから以下のフォーラムを見つけた。

Powershell Set-Acl use without changing owner?

MVPモデレータ曰く、これは仕様である

つまり、実行端末で管理者権限があろうと、その フォルダへのアクセス権変更権限があるユーザー であろうと Set-ACLでは所有権の無いフォルダ/ファイルへの操作は上記のエラーが出る のだそうだ。

きっともう少し掘り起こせば何かが・・・
と、思う気持ちをグッと押し込めて、実装を切り替える。

大人は切り替えが肝心なのだ。

Plan2. icacls でどうにかする。

PowerShellでどうにかしようと調べていた時に視界の片隅にいた icacls 。彼を使えばどうなるか。試してみたら、こうなった。

mission2.ps1

$tgtDir = "C:\TESTDIR\"
$tgtAcl = Get-ChildItem $tgtDir | Get-Acl #ターゲットフォルダ内のNTFS情報を取得する。
foreach($a in $tgtAcl){ #NTFS情報を走査する。
     foreach($ar in $a.Access){ # アクセス権の一覧を走査する。 
        if($ar.IsInherited -eq $false){ #継承が無効化されているユーザーがいる≒処理済みのフォルダ
            break # アクセス権変更を行わない
        }
        # 継承が無効化されたアクセス権(表示順序最上)が存在しないので処理を行う
        if($a.Owner -eq $ar.IdentityReference.Value){ # フォルダの所有者と同じユーザーが存在したらのアクセス権を追加する。
            $u = $ar.IdentityReference.Value # ユーザー名文字列を取得
            $p = Convert-Path($a.Path) # 対象のフォルダを文字列で保存
            $a.Owner # オーナーをコンソールに表示
            #icacls で権限を追加
            icacls ${p} /grant:r ${u}:`(OI`)`(CI`)F /t /c /l /q
        }
    }
}

上記で完了です。
今日費やした時間はなんだったんだ。ってくらい余裕の内容でした。

強いて言うなら、Convert-Pathでパスを変換しないとicaclsが受け取ってくれないよとか、エスケープ入れないと引数が通らないよとか、ぬるい内容で少し書き直しをしたぐらいです。

そして

日に1回このバッチを回して、新たに現れた信徒と神を繋ぐ…。
かくして神の仕様は満たされたのである。

改めてまとめ

アクセス権を日常的に定形で何かするというのはあまり無いケースだとは思います。
それだけにネット上にあるハウトゥはあまり組織での使用を踏まえたものがありません。
きっとそれぞれのローカルなノウハウがあるかとは思うのですが、今日私が知ったまとめを書き置きます。

  1. アクセス権を確認するなら Get-ACL は便利。
  2. ただし、Set-ACLは使ってはならない(ここに書き置いていない内容でも結構ハマった)
  3. アクセス権の追加、編集をしたければicaclsを使うこと。

以上3点でした。

私のように悩める天使が一人でもこの記事で救われれば何よりです・・・