Add security to a file

Introduction

The requirement: to secure an existing file so that Everyone can read, a named user can read and write, and the local Administrators group has Full Control.

Background / Explanation

This can be one of the most frustrating areas of Windows to get into: the concept is simple, and it says something perhaps for the UI that it makes it look easy. But behind the scenes is a powerful and by-and-large underutilised security model which you have to cope with at some level when you're doing things programatically. ("You are in a maze of twisty structures, all alike...") Below are two sections giving the background to the code: the longer version gives a potted summary of the security model; the shorter version jumps straight into the minimum you'll need to know.

The Long Version

Skip to the short version if you understand the Windows security model, or don't want to try.

Under Windows, many objects -- and most particularly files -- can have a security descriptor attached. In fact, when you create an object, you can attach a security attributes structure, which is a security descriptor with a flag indicating whether or not to inherit. The security descriptor is composed of: the object owner, its group, its Discretionary Access Control List (DACL) and its System Access Control List (SACL).

The owner is occasionally useful, especially if you're an administrator dealing with stale or forgotten accounts. The group, intended to offer a Posix-like group concept, is almost never used. The DACL is what we're most interested in and we'll look at that below. The SACL is used only for auditing and so on, and even when it's used is usually only ephemeral.

The DACL (and the SACL) are in essence lists of entries, and represent what you see on the Security tab of an object's properties. Each Access Control Entry (ACE) is a mapping between a user/group and an access mask. The user/group is actually a SID, a domain-wide system identifier; the access mask may vary from one object to another as things which make sense for a file may not apply to a named pipe or a mutex; and each entry can be either an allow entry or a deny entry.

The Short Version

So, to add permissions to a new file, you need a Security Attributes structure which includes a Security Descriptor, which has a Discretionary Access Control List which is a list of Access Control Entries, each of which maps a user/group SID to an access mask. Important Note: you can nearly always pass None where Security Attributes is required; this will result in a useful default Security Descriptor. This is *not* the same as passing a Security Descriptor with a DACL of None, which will give Full Control to Everyone.

So, we're interested in adding ACEs to the DACL. Fortunately, all the structures and functions we'll need are in the win32security module from the pywin32 extensions.

For the purposes of illustration, we're using the CACLS utility which comes with Windows to show what permissions are in place. You could, of course, use this in any case to set permissions, but it's mildly awkward and anyway this is a Python How-To.

The Code

Since it's far easier to create a file using Python's usual open ("blah", "w") than to use Windows' CreateFile, we'll create the file first with whatever default permissions it inherits and then replace them with our own. Obviously if this file needed to be truly secure then this would be a security hole and you'd do better to create it secure.

import os, sys
import win32api
import win32security
import ntsecuritycon as con

FILENAME = "temp.txt"
os.remove (FILENAME)

def show_cacls (filename):
  print
  print
  for line in os.popen ("cacls %s" % filename).read ().splitlines ():
    print line

#
# Find the SIDs for Everyone, the Admin group and the current user
#
everyone, domain, type = win32security.LookupAccountName ("", "Everyone")
admins, domain, type = win32security.LookupAccountName ("", "Administrators")
user, domain, type = win32security.LookupAccountName ("", win32api.GetUserName ())

#
# Touch the file and use CACLS to show its default permissions
# (which will probably be: Admins->Full; Owner->Full; Everyone->Read)
#
open (FILENAME, "w").close ()
show_cacls (FILENAME)

#
# Find the DACL part of the Security Descriptor for the file
#
sd = win32security.GetFileSecurity (FILENAME, win32security.DACL_SECURITY_INFORMATION)

#
# Create a blank DACL and add the three ACEs we want
# We will completely replace the original DACL with
# this. Obviously you might want to alter the original
# instead.
#
dacl = win32security.ACL ()
dacl.AddAccessAllowedAce (win32security.ACL_REVISION, con.FILE_GENERIC_READ, everyone)
dacl.AddAccessAllowedAce (win32security.ACL_REVISION, con.FILE_GENERIC_READ | con.FILE_GENERIC_WRITE, user)
dacl.AddAccessAllowedAce (win32security.ACL_REVISION, con.FILE_ALL_ACCESS, admins)

#
# Put our new DACL into the Security Descriptor,
# update the file with the updated SD, and use
# CACLS to show what's what.
#
sd.SetSecurityDescriptorDacl (1, dacl, 0)
win32security.SetFileSecurity (FILENAME, win32security.DACL_SECURITY_INFORMATION, sd)
show_cacls (FILENAME)