HWRF  trunk@4391
rstprod.py
1 """!Handles data restriction classes.
2 
3 Implements access control mechanisms for NOAA data. Although this was
4 written for the NOAA Restricted Data (rstprod), it can be used for
5 general access control. It is also more general than NOAA, so long as
6 one correctly initializes the produtil.cluster module. The mechanism
7 used depends on the cluster, due to varying capabilities throughout.
8 Some do not implement access control mechanisms that are usable for
9 the restricted data (such as NOAA Jet). For those systems,
10 RstNoAccessControl is raised if one attempts to restrict a file."""
11 
12 ##@var __all__
13 # List of symbols exported by "from produtil.rstprod import *"
14 __all__= [ 'RestrictionClass', 'tag_rstprod', 'rstprod_tagger',
15  'make_rstprod_tagger' ]
16 
17 class RstprodError(Exception):
18  """!The base class of all exceptions specific to the rstprod module"""
20  """!Raised when the cluster has no access control mechanisms."""
22  """!Raised when a group's id or name could not be determined."""
23 
24 import os, stat, grp
26 
27 from produtil.acl import ACL, ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT
28 
29 ##@var okay_mode
30 # File permission bits (from the stat module) that are allowed to be
31 # set on restricted access data. When Access Control List (ACL) based
32 # access control is used, the group bits refer to the rstprod's
33 # permissions in the ACL, rather than the owning group.
34 okay_mode = stat.S_IRUSR|stat.S_IWUSR|stat.S_IXUSR | \
35  stat.S_IRGRP|stat.S_IWGRP|stat.S_IXGRP
36 
37 def acl_text_for_rstclass(groupname,mode):
38  """!Generates the access control list for the specified restriction
39  class (groupname) and nine bit access permissions (mode).
40  @param groupname the restricted file unix group
41  @param mode required access mode (world access will be removed even
42  if it is present in mode)"""
43  if not isinstance(mode,int):
44  raise TypeError(
45  'In acl_text_for_rstclass, the mode must be the integer access mode, not a %s %s'
46  %(type(groupname).__name__,repr(groupname)))
47  imode=int(mode)&0770
48  if not isinstance(groupname,basestring):
49  raise TypeError(
50  'In acl_text_for_rstclass, the groupname must be the string name of a unix group, not a %s %s'
51  %(type(groupname).__name__,repr(groupname)))
52  return "u::%c%c%c,g::---,g:%s:%c%c%c,o::---,m::rwx" % (
53  ( 'r' if 0!=imode&stat.S_IRUSR else '-' ),
54  ( 'w' if 0!=imode&stat.S_IWUSR else '-' ),
55  ( 'x' if 0!=imode&stat.S_IXUSR else '-' ),
56  groupname,
57  ( 'r' if 0!=imode&stat.S_IRGRP else '-' ),
58  ( 'w' if 0!=imode&stat.S_IWGRP else '-' ),
59  ( 'x' if 0!=imode&stat.S_IXGRP else '-' ) )
60 
61 class RestrictionClass(object):
62  """!This is a python class intended to be used to automate
63  restricting data to a specific restriction class using access
64  control lists or group ownership
65 
66  Example:
67  @code
68  rc=RestrictionClass("rstprod")
69  rc.restrict_file("/path/to/some/dangerous/file")
70  @endcode
71 
72  It can also set the Default Access Control List if supplied a directory:
73  @code
74  rc.restrict_file("/path/to/some/dangerous/directory/")
75  @endcode"""
76  def __init__(self,group,use_acl=None,logger=None):
77  """!Create a new RestrictionClass object for the specified
78  group.
79  @param group The group may be the string group name, or the numeric
80  group id.
81  @param use_acl If use_acl is unspecified, then
82  produtil.cluster.use_acl_for_rstdata() is used to decide.
83  @param logger a logging.Logger for log messages"""
84  assert(use_acl is None)
85  if use_acl is None:
86  # We are being asked to automatically decide what type of
87  # access control mechanism to use.
89  raise RstNoAccessControl(
90  "This cluster cannot be used for NOAA restricted data. It "
91  "uses group quotas, so I cannot control access through "
92  "group IDs. It does not have a functional access control "
93  "list (ACL) mechanism, so I cannot use ACLs.")
95  self.__use_acl=bool(use_acl)
96  if isinstance(group,basestring):
97  self.__groupname=group
98  try:
99  grent=grp.getgrnam(group)
100  self.__groupid=grent[2]
101  except (EnvironmentError,KeyError) as e:
102  raise RstBadGroup('%s: could not get group id for group: %s'
103  %(group,str(e)))
104  if not isinstance(self.__groupid,int):
105  raise RstBadGroup(
106  '%s: could not get group id for group. The grp.getgrnam'
107  '(...)[2] returned something that was not an int: a %s %s'
108  %(group,type(self.__groupid).__name__,repr(self.__groupid)))
109  elif isinstance(group,int):
110  try:
111  grent=grp.getgrgid(group)
112  self.__groupname=grent[0]
113  self.__groupid=group
114  except (EnvironmentError,KeyError) as e:
115  raise RstBadGroup('%s: could not get group id for group: %s'
116  %(group,str(e)))
117  if not isinstance(self.__groupname,basestring):
118  raise RstBadGroup(
119  '%d: could not get group name for group. The grp.getgrgid'
120  '(...)[0] returned something that was not an int: a %s %s'
121  %(group,type(self.__groupid).__name__,
122  repr(self.__groupid)))
123 
124  else:
125  raise TypeError(
126  "In produtil.rstprod.RestrictionClass.__init__, the group parameter must be the string group name or integer group id. You provided a %s %s"
127  %(type(group).__name__,repr(group)))
128  self.__allowed=stat.S_IRUSR|stat.S_IWUSR|stat.S_IXUSR | \
129  stat.S_IRGRP|stat.S_IWGRP|stat.S_IXGRP
130  if use_acl:
131  self.__acls=self.make_acl_dict()
132  def make_acl_dict(self):
133  """!Internal function that generates the ACL dictionary.
134 
135  @protected
136  This is part of the internal implementation of
137  RestrictionClass and should not be used directly. It returns
138  a dict() that maps from integer permission to an ACL object
139  that will set an access control list appropriate for that
140  permission. The user and restriction group will match the old
141  user and group permissions, but other groups will have no
142  permissions, and the "world" permissions will be 0."""
143  acls=dict()
144  mode=010
145  for IRUSR in ( 0, stat.S_IRUSR ):
146  for IWUSR in ( 0, stat.S_IWUSR ):
147  for IXUSR in ( 0, stat.S_IXUSR ):
148  for IRGRP in ( 0, stat.S_IRGRP ):
149  for IWGRP in ( 0, stat.S_IWGRP ):
150  for IXGRP in ( 0, stat.S_IXGRP ):
151  mode=IRUSR|IWUSR|IXUSR|IRGRP|IWGRP|IXGRP
152  txt=acl_text_for_rstclass(self.groupname,mode)
153  acl=ACL()
154  acl.from_text(txt)
155  acls[mode]=acl
156  return acls
157  @property
158  def groupname(self):
159  """!The name of the group used for the restriction class"""
160  return self.__groupname
161  @property
162  def groupid(self):
163  """!The numeric ID of the group used for the restriction class"""
164  return self.__groupid
165  @property
166  def use_acl(self):
167  """!True if ACLs are used for access permission, False if
168  setgid and chgrp are used."""
169  return self.__use_acl
170 
171  def acl_for(self,st_mode):
172  """!Returns an produtil.acl.ACL object for the specified access
173  mode. Will raise an exception if self.use_acl is False.
174  @param st_mode desired access mode"""
175  imode = stat.S_IMODE(st_mode)
176  amode = imode & self.__allowed # limit to allowed permissions
177  return self.__acls[amode]
178 
179  def chgrp_restrict(self,target,st_mode,chown,chmod,logger):
180  """!Internal function that uses chgrp to restrict a file's access.
181 
182  This is an internal implementation function that should not be
183  called directly. It handles the non-ACL (chgrp+setgid) case
184  of restrict_file and restrict_gid.
185  @param target the target file
186  @param st_mode the desired mode
187  @param chown chowning function
188  @param chmod chmodding function
189  @param logger a logging.Logger for log messages
190  @protected """
191  if logger is not None:
192  logger.info('%s: chgrp to %s'%(str(target),self.__groupname))
193  chown(target,-1,self.__groupid)
194  if stat.S_ISDIR(st_mode):
195  smode = stat.S_ISGID | (stat.S_IMODE(st_mode)&okay_mode)
196  if logger is not None:
197  logger.info('%s: set mode on directory to 0%o'
198  %(str(target),smode))
199  chmod( target, smode )
200  else:
201  smode = stat.S_IMODE(st_mode)&okay_mode
202  if logger is not None:
203  logger.info('%s: set mode on file to 0%o'%(str(target),smode))
204  chmod( target, smode )
205 
206  def acl_restrict_file(self,target,st_mode,set_acl,logger):
207  """!Internal function that restricts files using ACLs
208 
209  This is an internal implementation function that should not be
210  called directly. It handles the ACL case of restrict_file.
211  @protected
212  @param target the target file
213  @param st_mode the desired access
214  @param set_acl the acl-setting function
215  @param logger a logging.Logger for log messages """
216  if stat.S_ISDIR(st_mode):
217  if logger is not None:
218  logger.info('%s: use acl to restrict dir to group %s'
219  %(str(target),self.groupname))
220  set_acl(target,ACL_TYPE_ACCESS)
221  set_acl(target,ACL_TYPE_DEFAULT)
222  else:
223  if logger is not None:
224  logger.info('%s: use acl to restrict file to group %s'
225  %(str(target),self.groupname))
226  set_acl(target,ACL_TYPE_ACCESS)
227 
228  def restrict_file(self,filename,st_mode=None,logger=None):
229  """!Adds the requested restrictions to the specified file or
230  directory. This routine needs to stat the opened file to get
231  the stat.st_mode.
232  @param st_mode To avoid a stat call, send st_mode into the
233  optional argument.
234  @param filename the target file
235  @param logger a logging.Logger for log messages"""
236  if st_mode is None:
237  if logger is not None:
238  logger.info(filename+': stat file')
239  s=os.stat(filename)
240  st_mode=s.st_mode
241  if self.__use_acl:
242  acl=self.acl_for(stat.S_IMODE(st_mode))
243  self.acl_restrict_file(filename,st_mode,acl.to_file,logger)
244  else:
245  self.chgrp_restrict(filename,st_mode,os.chown,os.chmod,logger)
246 
247  def restrict_fd(self,fd,st_mode=None,logger=None):
248  """Adds the requested restrictions to an opened file. This
249  routine needs to stat the opened file to get the stat.st_mode.
250  @param st_mode To avoid a stat call, send st_mode into the optional argument.
251  @param fd the target file descriptor
252  @param logger a logging.Logger for log messages"""
253  if hasattr(fd,'fileno'):
254  fd=fd.fileno()
255  if st_mode is None:
256  if logger is not None:
257  logger.info(str(fd)+': stat fileno')
258  s=os.fstat(fd)
259  st_mode=s.st_mode
260  if self.__use_acl:
261  acl=self.acl_for(stat.S_IMODE(st_mode))
262  if logger is not None:
263  logger.info('%s: set acl of fileno to restrict to group %s'
264  %(str(fd),self.__groupname))
265  acl.to_fd(fd)
266  else:
267  self.chgrp_restrict(fd,st_mode,os.fchown,os.fchmod,logger)
268 
269 ##@var rstprod_tagger
270 #The RestrictionClass object used for tag_rstprod. Create this with
271 #make_rstprod_tagger
272 rstprod_tagger=None
273 
274 def make_rstprod_tagger(group='rstprod',use_acl=None,logger=None):
275  """!Creates the rstprod_tagger object for use by tag_rstprod"""
276  global rstprod_tagger
277  rstprod_tagger=RestrictionClass(group,use_acl,logger)
278 
279 def tag_rstprod(target,logger=None):
280  """!Places a file or directory under the rstprod restriction class.
281  This command will attempt to raise RstprodForbidden if it is run
282  on a cluster that is not supposed to have rstprod data (only
283  GAEA, Zeus and WCOSS are allowed).
284 
285  This routine uses the approved rstprod protection mechanisms on
286  each cluster:
287 
288  * Zeus --- place the file in the rstprod access control list, and
289  make it unreadable to anyone else.
290 
291  * WCOSS --- place the file in group rstprod and remove permissions
292  for others.
293 
294  * GAEA --- same as WCOSS
295 
296  Note that the NOAA Jet cluster is not allowed to contain
297  restricted data, so this routine will raise RstprodForbidden on
298  that cluster."""
299  if rstprod_tagger is None:
300  make_rstprod_tagger(logger=logger)
301  if isinstance(target,basestring):
302  rstprod_tagger.restrict_file(target,logger=logger)
303  elif isinstance(target,file) or isinstance(target,int):
304  rstprod_tagger.restrict_fd(target,logger=logger)
305  else:
306  raise TypeError('The tag_rstprod target argument must be an int, a file '
307  'or a basestring. You supplied a %s %s'
308  %(type(target).__name__,repr(target)))
309 
Manipulates Access Control Lists (ACL)
Definition: acl.py:1
def groupid(self)
The numeric ID of the group used for the restriction class.
Definition: rstprod.py:162
def no_access_control()
True if the cluster provides no means to control access to files.
Definition: cluster.py:127
def make_acl_dict(self)
Internal function that generates the ACL dictionary.
Definition: rstprod.py:132
def use_acl(self)
True if ACLs are used for access permission, False if setgid and chgrp are used.
Definition: rstprod.py:166
def use_acl_for_rstdata()
Synonym for here.use_acl_for_rstdata.
Definition: cluster.py:134
def acl_text_for_rstclass(groupname, mode)
Generates the access control list for the specified restriction class (groupname) and nine bit access...
Definition: rstprod.py:37
def acl_for(self, st_mode)
Returns an produtil.acl.ACL object for the specified access mode.
Definition: rstprod.py:171
Provides information about the cluster on which this job is running.
Definition: cluster.py:1
def restrict_file
Adds the requested restrictions to the specified file or directory.
Definition: rstprod.py:228
def acl_restrict_file(self, target, st_mode, set_acl, logger)
Internal function that restricts files using ACLs.
Definition: rstprod.py:206
The base class of all exceptions specific to the rstprod module.
Definition: rstprod.py:17
def __init__
Create a new RestrictionClass object for the specified group.
Definition: rstprod.py:76
This is a python class intended to be used to automate restricting data to a specific restriction cla...
Definition: rstprod.py:61
Raised when a group's id or name could not be determined.
Definition: rstprod.py:21
def groupname(self)
The name of the group used for the restriction class.
Definition: rstprod.py:158
def chgrp_restrict(self, target, st_mode, chown, chmod, logger)
Internal function that uses chgrp to restrict a file's access.
Definition: rstprod.py:179
Raised when the cluster has no access control mechanisms.
Definition: rstprod.py:19
def tag_rstprod
Places a file or directory under the rstprod restriction class.
Definition: rstprod.py:279
def make_rstprod_tagger
Creates the rstprod_tagger object for use by tag_rstprod.
Definition: rstprod.py:274
ACL class wrapped around the libacl library:
Definition: acl.py:139