1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19   
 20   
 21   
 22   
 23   
 24   
 25   
 26   
 27   
 28   
 29   
 30   
 31   
 32   
 33   
 34   
 35   
 36   
 37   
 38  """ 
 39  Provides an extension to encrypt staging directories. 
 40   
 41  When this extension is executed, all backed-up files in the configured Cedar 
 42  Backup staging directory will be encrypted using gpg.  Any directory which has 
 43  already been encrypted (as indicated by the C{cback.encrypt} file) will be 
 44  ignored. 
 45   
 46  This extension requires a new configuration section <encrypt> and is intended 
 47  to be run immediately after the standard stage action or immediately before the 
 48  standard store action.  Aside from its own configuration, it requires the 
 49  options and staging configuration sections in the standard Cedar Backup 
 50  configuration file. 
 51   
 52  @author: Kenneth J. Pronovici <pronovic@ieee.org> 
 53  """ 
 54   
 55   
 56   
 57   
 58   
 59   
 60  import os 
 61  import logging 
 62   
 63   
 64  from CedarBackup2.util import resolveCommand, executeCommand, changeOwnership 
 65  from CedarBackup2.xmlutil import createInputDom, addContainerNode, addStringNode 
 66  from CedarBackup2.xmlutil import readFirstChild, readString 
 67  from CedarBackup2.actions.util import findDailyDirs, writeIndicatorFile, getBackupFiles 
 68   
 69   
 70   
 71   
 72   
 73   
 74  logger = logging.getLogger("CedarBackup2.log.extend.encrypt") 
 75   
 76  GPG_COMMAND = [ "gpg", ] 
 77  VALID_ENCRYPT_MODES = [ "gpg", ] 
 78  ENCRYPT_INDICATOR = "cback.encrypt" 
 86   
 87     """ 
 88     Class representing encrypt configuration. 
 89   
 90     Encrypt configuration is used for encrypting staging directories. 
 91   
 92     The following restrictions exist on data in this class: 
 93   
 94        - The encrypt mode must be one of the values in L{VALID_ENCRYPT_MODES} 
 95        - The encrypt target value must be a non-empty string 
 96   
 97     @sort: __init__, __repr__, __str__, __cmp__, encryptMode, encryptTarget 
 98     """ 
 99   
100 -   def __init__(self, encryptMode=None, encryptTarget=None): 
 101        """ 
102        Constructor for the C{EncryptConfig} class. 
103   
104        @param encryptMode: Encryption mode 
105        @param encryptTarget: Encryption target (for instance, GPG recipient) 
106   
107        @raise ValueError: If one of the values is invalid. 
108        """ 
109        self._encryptMode = None 
110        self._encryptTarget = None 
111        self.encryptMode = encryptMode 
112        self.encryptTarget = encryptTarget 
 113   
115        """ 
116        Official string representation for class instance. 
117        """ 
118        return "EncryptConfig(%s, %s)" % (self.encryptMode, self.encryptTarget) 
 119   
121        """ 
122        Informal string representation for class instance. 
123        """ 
124        return self.__repr__() 
 125   
127        """ 
128        Definition of equals operator for this class. 
129        Lists within this class are "unordered" for equality comparisons. 
130        @param other: Other object to compare to. 
131        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other. 
132        """ 
133        if other is None: 
134           return 1 
135        if self.encryptMode != other.encryptMode: 
136           if self.encryptMode < other.encryptMode: 
137              return -1 
138           else: 
139              return 1 
140        if self.encryptTarget != other.encryptTarget: 
141           if self.encryptTarget < other.encryptTarget: 
142              return -1 
143           else: 
144              return 1 
145        return 0 
 146   
148        """ 
149        Property target used to set the encrypt mode. 
150        If not C{None}, the mode must be one of the values in L{VALID_ENCRYPT_MODES}. 
151        @raise ValueError: If the value is not valid. 
152        """ 
153        if value is not None: 
154           if value not in VALID_ENCRYPT_MODES: 
155              raise ValueError("Encrypt mode must be one of %s." % VALID_ENCRYPT_MODES) 
156        self._encryptMode = value 
 157   
159        """ 
160        Property target used to get the encrypt mode. 
161        """ 
162        return self._encryptMode 
 163   
165        """ 
166        Property target used to set the encrypt target. 
167        """ 
168        if value is not None: 
169           if len(value) < 1: 
170              raise ValueError("Encrypt target must be non-empty string.") 
171        self._encryptTarget = value 
 172   
174        """ 
175        Property target used to get the encrypt target. 
176        """ 
177        return self._encryptTarget 
 178   
179     encryptMode = property(_getEncryptMode, _setEncryptMode, None, doc="Encrypt mode.") 
180     encryptTarget = property(_getEncryptTarget, _setEncryptTarget, None, doc="Encrypt target (i.e. GPG recipient).") 
 181   
188   
189     """ 
190     Class representing this extension's configuration document. 
191   
192     This is not a general-purpose configuration object like the main Cedar 
193     Backup configuration object.  Instead, it just knows how to parse and emit 
194     encrypt-specific configuration values.  Third parties who need to read and 
195     write configuration related to this extension should access it through the 
196     constructor, C{validate} and C{addConfig} methods. 
197   
198     @note: Lists within this class are "unordered" for equality comparisons. 
199   
200     @sort: __init__, __repr__, __str__, __cmp__, encrypt, validate, addConfig 
201     """ 
202   
203 -   def __init__(self, xmlData=None, xmlPath=None, validate=True): 
 204        """ 
205        Initializes a configuration object. 
206   
207        If you initialize the object without passing either C{xmlData} or 
208        C{xmlPath} then configuration will be empty and will be invalid until it 
209        is filled in properly. 
210   
211        No reference to the original XML data or original path is saved off by 
212        this class.  Once the data has been parsed (successfully or not) this 
213        original information is discarded. 
214   
215        Unless the C{validate} argument is C{False}, the L{LocalConfig.validate} 
216        method will be called (with its default arguments) against configuration 
217        after successfully parsing any passed-in XML.  Keep in mind that even if 
218        C{validate} is C{False}, it might not be possible to parse the passed-in 
219        XML document if lower-level validations fail. 
220   
221        @note: It is strongly suggested that the C{validate} option always be set 
222        to C{True} (the default) unless there is a specific need to read in 
223        invalid configuration from disk. 
224   
225        @param xmlData: XML data representing configuration. 
226        @type xmlData: String data. 
227   
228        @param xmlPath: Path to an XML file on disk. 
229        @type xmlPath: Absolute path to a file on disk. 
230   
231        @param validate: Validate the document after parsing it. 
232        @type validate: Boolean true/false. 
233   
234        @raise ValueError: If both C{xmlData} and C{xmlPath} are passed-in. 
235        @raise ValueError: If the XML data in C{xmlData} or C{xmlPath} cannot be parsed. 
236        @raise ValueError: If the parsed configuration document is not valid. 
237        """ 
238        self._encrypt = None 
239        self.encrypt = None 
240        if xmlData is not None and xmlPath is not None: 
241           raise ValueError("Use either xmlData or xmlPath, but not both.") 
242        if xmlData is not None: 
243           self._parseXmlData(xmlData) 
244           if validate: 
245              self.validate() 
246        elif xmlPath is not None: 
247           xmlData = open(xmlPath).read() 
248           self._parseXmlData(xmlData) 
249           if validate: 
250              self.validate() 
 251   
253        """ 
254        Official string representation for class instance. 
255        """ 
256        return "LocalConfig(%s)" % (self.encrypt) 
 257   
259        """ 
260        Informal string representation for class instance. 
261        """ 
262        return self.__repr__() 
 263   
265        """ 
266        Definition of equals operator for this class. 
267        Lists within this class are "unordered" for equality comparisons. 
268        @param other: Other object to compare to. 
269        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other. 
270        """ 
271        if other is None: 
272           return 1 
273        if self.encrypt != other.encrypt: 
274           if self.encrypt < other.encrypt: 
275              return -1 
276           else: 
277              return 1 
278        return 0 
 279   
281        """ 
282        Property target used to set the encrypt configuration value. 
283        If not C{None}, the value must be a C{EncryptConfig} object. 
284        @raise ValueError: If the value is not a C{EncryptConfig} 
285        """ 
286        if value is None: 
287           self._encrypt = None 
288        else: 
289           if not isinstance(value, EncryptConfig): 
290              raise ValueError("Value must be a C{EncryptConfig} object.") 
291           self._encrypt = value 
 292   
294        """ 
295        Property target used to get the encrypt configuration value. 
296        """ 
297        return self._encrypt 
 298   
299     encrypt = property(_getEncrypt, _setEncrypt, None, "Encrypt configuration in terms of a C{EncryptConfig} object.") 
300   
302        """ 
303        Validates configuration represented by the object. 
304   
305        Encrypt configuration must be filled in.  Within that, both the encrypt 
306        mode and encrypt target must be filled in. 
307   
308        @raise ValueError: If one of the validations fails. 
309        """ 
310        if self.encrypt is None: 
311           raise ValueError("Encrypt section is required.") 
312        if self.encrypt.encryptMode is None: 
313           raise ValueError("Encrypt mode must be set.") 
314        if self.encrypt.encryptTarget is None: 
315           raise ValueError("Encrypt target must be set.") 
 316   
318        """ 
319        Adds an <encrypt> configuration section as the next child of a parent. 
320   
321        Third parties should use this function to write configuration related to 
322        this extension. 
323   
324        We add the following fields to the document:: 
325   
326           encryptMode    //cb_config/encrypt/encrypt_mode 
327           encryptTarget  //cb_config/encrypt/encrypt_target 
328   
329        @param xmlDom: DOM tree as from C{impl.createDocument()}. 
330        @param parentNode: Parent that the section should be appended to. 
331        """ 
332        if self.encrypt is not None: 
333           sectionNode = addContainerNode(xmlDom, parentNode, "encrypt") 
334           addStringNode(xmlDom, sectionNode, "encrypt_mode", self.encrypt.encryptMode) 
335           addStringNode(xmlDom, sectionNode, "encrypt_target", self.encrypt.encryptTarget) 
 336   
338        """ 
339        Internal method to parse an XML string into the object. 
340   
341        This method parses the XML document into a DOM tree (C{xmlDom}) and then 
342        calls a static method to parse the encrypt configuration section. 
343   
344        @param xmlData: XML data to be parsed 
345        @type xmlData: String data 
346   
347        @raise ValueError: If the XML cannot be successfully parsed. 
348        """ 
349        (xmlDom, parentNode) = createInputDom(xmlData) 
350        self._encrypt = LocalConfig._parseEncrypt(parentNode) 
 351   
352     @staticmethod 
354        """ 
355        Parses an encrypt configuration section. 
356   
357        We read the following individual fields:: 
358   
359           encryptMode    //cb_config/encrypt/encrypt_mode 
360           encryptTarget  //cb_config/encrypt/encrypt_target 
361   
362        @param parent: Parent node to search beneath. 
363   
364        @return: C{EncryptConfig} object or C{None} if the section does not exist. 
365        @raise ValueError: If some filled-in value is invalid. 
366        """ 
367        encrypt = None 
368        section = readFirstChild(parent, "encrypt") 
369        if section is not None: 
370           encrypt = EncryptConfig() 
371           encrypt.encryptMode = readString(section, "encrypt_mode") 
372           encrypt.encryptTarget = readString(section, "encrypt_target") 
373        return encrypt 
  374   
375   
376   
377   
378   
379   
380   
381   
382   
383   
384   
385 -def executeAction(configPath, options, config): 
 415   
416   
417   
418   
419   
420   
421 -def _encryptDailyDir(dailyDir, encryptMode, encryptTarget, backupUser, backupGroup): 
 422     """ 
423     Encrypts the contents of a daily staging directory. 
424   
425     Indicator files are ignored.  All other files are encrypted.  The only valid 
426     encrypt mode is C{"gpg"}. 
427   
428     @param dailyDir: Daily directory to encrypt 
429     @param encryptMode: Encryption mode (only "gpg" is allowed) 
430     @param encryptTarget: Encryption target (GPG recipient for "gpg" mode) 
431     @param backupUser: User that target files should be owned by 
432     @param backupGroup: Group that target files should be owned by 
433   
434     @raise ValueError: If the encrypt mode is not supported. 
435     @raise ValueError: If the daily staging directory does not exist. 
436     """ 
437     logger.debug("Begin encrypting contents of [%s].", dailyDir) 
438     fileList = getBackupFiles(dailyDir)  
439     for path in fileList: 
440        _encryptFile(path, encryptMode, encryptTarget, backupUser, backupGroup, removeSource=True) 
441     logger.debug("Completed encrypting contents of [%s].", dailyDir) 
 442   
443   
444   
445   
446   
447   
448 -def _encryptFile(sourcePath, encryptMode, encryptTarget, backupUser, backupGroup, removeSource=False): 
 449     """ 
450     Encrypts the source file using the indicated mode. 
451   
452     The encrypted file will be owned by the indicated backup user and group.  If 
453     C{removeSource} is C{True}, then the source file will be removed after it is 
454     successfully encrypted. 
455   
456     Currently, only the C{"gpg"} encrypt mode is supported. 
457   
458     @param sourcePath: Absolute path of the source file to encrypt 
459     @param encryptMode: Encryption mode (only "gpg" is allowed) 
460     @param encryptTarget: Encryption target (GPG recipient) 
461     @param backupUser: User that target files should be owned by 
462     @param backupGroup: Group that target files should be owned by 
463     @param removeSource: Indicates whether to remove the source file 
464   
465     @return: Path to the newly-created encrypted file. 
466   
467     @raise ValueError: If an invalid encrypt mode is passed in. 
468     @raise IOError: If there is a problem accessing, encrypting or removing the source file. 
469     """ 
470     if not os.path.exists(sourcePath): 
471        raise ValueError("Source path [%s] does not exist." % sourcePath) 
472     if encryptMode == 'gpg': 
473        encryptedPath = _encryptFileWithGpg(sourcePath, recipient=encryptTarget) 
474     else: 
475        raise ValueError("Unknown encrypt mode [%s]" % encryptMode) 
476     changeOwnership(encryptedPath, backupUser, backupGroup) 
477     if removeSource: 
478        if os.path.exists(sourcePath): 
479           try: 
480              os.remove(sourcePath) 
481              logger.debug("Completed removing old file [%s].", sourcePath) 
482           except: 
483              raise IOError("Failed to remove file [%s] after encrypting it." % (sourcePath)) 
484     return encryptedPath 
 485   
492     """ 
493     Encrypts the indicated source file using GPG. 
494   
495     The encrypted file will be in GPG's binary output format and will have the 
496     same name as the source file plus a C{".gpg"} extension.  The source file 
497     will not be modified or removed by this function call. 
498   
499     @param sourcePath: Absolute path of file to be encrypted. 
500     @param recipient: Recipient name to be passed to GPG's C{"-r"} option 
501   
502     @return: Path to the newly-created encrypted file. 
503   
504     @raise IOError: If there is a problem encrypting the file. 
505     """ 
506     encryptedPath = "%s.gpg" % sourcePath 
507     command = resolveCommand(GPG_COMMAND) 
508     args = [ "--batch", "--yes", "-e", "-r", recipient, "-o", encryptedPath, sourcePath, ] 
509     result = executeCommand(command, args)[0] 
510     if result != 0: 
511        raise IOError("Error [%d] calling gpg to encrypt [%s]." % (result, sourcePath)) 
512     if not os.path.exists(encryptedPath): 
513        raise IOError("After call to [%s], encrypted file [%s] does not exist." % (command, encryptedPath)) 
514     logger.debug("Completed encrypting file [%s] to [%s].", sourcePath, encryptedPath) 
515     return encryptedPath 
 516   
523     """ 
524     Confirms that a recipient's public key is known to GPG. 
525     Throws an exception if there is a problem, or returns normally otherwise. 
526     @param recipient: Recipient name 
527     @raise IOError: If the recipient's public key is not known to GPG. 
528     """ 
529     command = resolveCommand(GPG_COMMAND) 
530     args = [ "--batch", "-k", recipient, ]   
531     result = executeCommand(command, args)[0] 
532     if result != 0: 
533        raise IOError("GPG unable to find public key for [%s]." % recipient) 
 534