[Security] 判別式存取控制表 (Discretionary Access Control List) 實作:原型概念

判別式存取控制表 Discretionary Access Control List (DACL),這個自由度頗高的存取控制方法,在 Windows 以及其他作業系統中已行之有年,它最大的特色就是使用者和群組可以擁有自己的存取控制設定,而系統物件也可以擁有自己的存取控制項目,也就是說系統物件不會只有 CRUD 四種權限,還可以因為物件的特性而定義額外的權限,例如存取詮釋資料 (metadata), 列印 (printing) 或是下載檔案 (download) 等權限,discretionary 這個字有 "任意的","無條件的" 的意思,而 DACL 也是讓消費者和生產者可以擁有自己的權限設定,只需要在存取時進行檢查即可。

判別式存取控制表 Discretionary Access Control List (DACL),這個自由度頗高的存取控制方法,在 Windows 以及其他作業系統中已行之有年,它最大的特色就是使用者和群組可以擁有自己的存取控制設定,而系統物件也可以擁有自己的存取控制項目,也就是說系統物件不會只有 CRUD 四種權限,還可以因為物件的特性而定義額外的權限,例如存取詮釋資料 (metadata), 列印 (printing) 或是下載檔案 (download) 等權限,discretionary 這個字有 "任意的","無條件的" 的意思,而 DACL 也是讓消費者和生產者可以擁有自己的權限設定,只需要在存取時進行檢查即可。

以 Windows 作業系統來說,每一個系統物件都擁有自己的 DACL 權限表,以 NTFS 檔案系統內的檔案來說好了,每個檔案都有六種基本權限-Full Control, Modify, Read & Execute, Read, Write 與特殊權限等:

image

不過當我們按 [進階] 時,能進一步的設定檔案的所有權限,而檔案擁有的延伸權限有 7 種,故檔案的權限設定共有 13 種 (資料夾則是 15 種左右):

image

但平常我們在設計系統時,通常都只有 CRUD 四種權限,了不起再加個 List 好了,而且多半是由資料庫保存這些設定,而每次使用者要進入系統時,都要和資料庫來一下親密接觸 (SQL),而且如果功能或權限表要擴充時,還要去動資料表的結構,或是在大量使用者的情況下,資料列會異常的多。這些都不利於系統的擴張與延展,我們會需要一種存取控制方式,能夠:

  1. 允許使用者,群組與系統功能擁有自己的存取控制機制。
  2. 系統功能可以自己設定存取權限的項目。
  3. 使用者和群組之間可以有繼承關係,而當權限有衝突時,可透過規則決定最終權限。
  4. 將資料庫的讀寫最小化。
  5. 資料量不可以太大。

 

因此,我最近藉由一些系統設計的工作,研究這類型的存取控制表的設計方式,首先,為了要將使用資料庫的量降到最低,勢必在系統中要有幾個資料結構可以保存這些資料,而且不能讓資料量過大,否則如果系統物件超過一定數量時,資料可能會存不下去,所以我的原型設計中,每個存取控制項目 (Access Control Entry; ACE) 要維持在64bits (8 bytes),而整個存取控制表,則是 16 bytes + 存取控制的項目數 x 8 bytes,而存取控制的項目中,基本權限有 9 個,可擴充的權限有 32 個,共計 41 種權限設定。為了要容納這些資料,我參考了 Windows ACL 以及 TCP/IP stack 的結構圖方式,以 8 bytes 為單位,切割出了一個存取控制項目的基本結構:

image

第一個 16 bits 是存放系統物件的代碼,因此系統物件最多可以擁有 65,536 個 (ushort),而有 1 個 bit 存放此存取項目是否是由其他設定繼承,Base Access Control共有 9 種,分別是Read, Create, Update, Delete, List, Search, Print, Download 與 Upload 等,Owner 則分兩個 bits,一個是 Owner To User,另一個是 Owner To Group,可用來判斷此 ACL 的 Owner 是屬於哪一類型的帳戶,GENERAL 則是泛用權限,目前有 FULL, READ, WRITE 與 GUEST 四種,而自第 33 個 bit 起算,共有 32bits 可供物件自行設定權限。

在 .NET Framework 中有一個類別可處理這樣的位元序列 (bit array),它就是 BitArray,它可以針對 bit 進行操作,而位元只有 0 和 1,因此用作控制旗標是再理想不過了,因此就有這樣的程式碼:

// this._entryRawData is a BitArray Object.
private void Initialize()
{
    ushort i = 16;

    this._isInherit = this._entryRawData.Get((i++));
    this._sys_canRead = this._entryRawData.Get((i++));
    this._sys_canCreate = this._entryRawData.Get((i++));
    this._sys_canUpdate = this._entryRawData.Get((i++));
    this._sys_canDelete = this._entryRawData.Get((i++));
    this._sys_canList = this._entryRawData.Get((i++));
    this._sys_canSearch = this._entryRawData.Get((i++));
    this._sys_canPrint = this._entryRawData.Get((i++));
    this._sys_canDownload = this._entryRawData.Get((i++));
    this._sys_canUpload = this._entryRawData.Get((i++));

    this._isOwnerUser = this._entryRawData.Get((i++));
    this._isOwnerGroup = this._entryRawData.Get((i++));
    this._sys_genericFull = this._entryRawData.Get((i++));
    this._sys_genericRead = this._entryRawData.Get((i++));
    this._sys_genericWrite = this._entryRawData.Get((i++));
    this._sys_genericGuest = this._entryRawData.Get((i++));

    this._sys_modifyACE = this._entryRawData.Get((i++));

    // object permissions.
    while (i < this._entryRawData.Length)
    {
        this._objectPermissions.Add((ushort)(i - 33), this._entryRawData.Get(i));
        i++;
    }
}

 

當然,我們也要能夠將這個項目還原成 byte 陣列以利儲存,所以:

public byte[] GetBinaryData()
{
    BitArray moduleIDArray = new BitArray(Convert.ToByte(this._moduleID));
    this._entryRawData = new BitArray(64, false);

    // set 0-15 bit for module ID.
    for (int i = 0; i < moduleIDArray.Length; i++)
        this._entryRawData.Set(i, moduleIDArray[i]);

    // set 16-25 bit
    this._entryRawData.Set(16, this._isInherit);
    this._entryRawData.Set(17, this._sys_canRead);
    this._entryRawData.Set(18, this._sys_canCreate);
    this._entryRawData.Set(19, this._sys_canUpdate);
    this._entryRawData.Set(20, this._sys_canDelete);
    this._entryRawData.Set(21, this._sys_canList);
    this._entryRawData.Set(22, this._sys_canSearch);
    this._entryRawData.Set(23, this._sys_canPrint);
    this._entryRawData.Set(24, this._sys_canDownload);
    this._entryRawData.Set(25, this._sys_canUpload);

    // set 26-27 bit.
    this._entryRawData.Set(26, this._isOwnerUser);
    this._entryRawData.Set(27, this._isOwnerGroup);

    // set 28-31 bit.
    this._entryRawData.Set(28, this._sys_genericFull);
    this._entryRawData.Set(29, this._sys_genericRead);
    this._entryRawData.Set(30, this._sys_genericWrite);
    this._entryRawData.Set(31, this._sys_genericGuest);

    // set 32 bit.
    this._entryRawData.Set(32, this._sys_modifyACE);

    // set 33-63 bit (object permission)
    foreach (KeyValuePair<ushort, bool> objectPerm in this._objectPermissions)
        this._entryRawData.Set(33 + objectPerm.Key, objectPerm.Value);

    byte[] data = new byte[(int)Math.Ceiling((double)this._entryRawData.Length / 8)];
    this._entryRawData.CopyTo(data, 0);
    return data;
}

 

而 DACL 的資料結構,則是以標頭,預設的 ACE 與物件的 ACE 所組成:

image

 

DACL 的初始化就比 ACE 要簡單多了:

private void Initialize(byte[] DACLBinaryData)
{
    MemoryStream ms = new MemoryStream(DACLBinaryData);
    BinaryReader reader = new BinaryReader(ms);

    // load metadata.
    this._length = BitConverter.ToUInt32(reader.ReadBytes(4), 0);
    this._version = BitConverter.ToUInt16(reader.ReadBytes(2), 0);
    this._entriesCount = BitConverter.ToUInt16(reader.ReadBytes(2), 0);

    // load default entry.
    this._defaultEntry = AccessControlEntry.CreateFromBinaryData(reader.ReadBytes(8));

    while (reader.BaseStream.Position < (reader.BaseStream.Length - 1))
    {
        AccessControlEntry entry = AccessControlEntry.CreateFromBinaryData(reader.ReadBytes(8));
        this._objectEntries.Add(entry.ModuleID, entry);
    }

    reader.Close();
}

 

本文就先介紹到這吧,後面還有不少東西呢 ...

 

Reference:

http://en.wikipedia.org/wiki/Discretionary_access_control

http://www.ntfs.com/ntfs-permissions-access-entries.htm

http://msdn.microsoft.com/en-us/library/aa446683(v=vs.85).aspx