Edgewall Software
Modify

Opened 6 years ago

Last modified 13 months ago

#8294 new enhancement

[PATCH] Add GnuPG encryption to email notifications

Reported by: richard@… Owned by: eblot
Priority: normal Milestone: unscheduled
Component: notification Version: 0.11.3
Severity: normal Keywords: gpg consider patch
Cc: andrew@…, hoff.st@…, n-roeser@…, thijstriemstra
Release Notes:
API Changes:

Description

Here's a modification to add GnuPG encryption to outgoing notifications in a mod_poython setup. It's my first python hack, so its pretty crude. There's no error checking, and constants appear magically. Here we go:

Add an import:

from subprocess import *

In the init section of class NotifyEmail replace lines 192-196 of the original source with:

        # ..and the gpg key if the user has one
        self.email_map = {}
        emails = []
        for username, name, email in self.env.get_known_users(self.db):
            if email:
                self.email_map[username] = email
                emails.append(email)
        # ..and the gpg key if the user has one
        self.gpg_map = {}
        output = Popen(["c:\Program Files\GNU\GnuPG\gpg.exe", "--homedir",
                "C:\data\gpg", "--batch", "--with-colons", "--list-keys"],
                stdout=PIPE).communicate()[0]
        lines = re.split("\n", output)
        skip = 0
        gpgkeyid = ""
        for line in lines:
            data = re.split(':', line)
            if data[0] == "pub":
                gpgkeyid = data[4]
                skip = 0
            if gpgkeyid and skip == 0 and data[0] == "uid":
                temp = data[9].rsplit('<', 1)
                tmail = temp[1].rstrip('>')
                for email in emails:
                    if email == tmail:
                        self.gpg_map[email] = gpgkeyid
                        skip = 1
                        break

In the send method replace line 397 of the original source with:

        # lets see if we need to encrypt body to one or multiple recipients
        gpgkeys = [];
        for recipient in recipients:
            if recipient in self.gpg_map:
                temp = '-r ' + self.gpg_map[recipient]
                gpgkeys.append(temp)
        value = body
        if len(gpgkeys):
            args = ["c:\Program Files\GNU\GnuPG\gpg.exe", "--homedir", "C:\data\gpg", '-e',
                                 '--batch', '--armor']
            args.extend(gpgkeys)
            proc = Popen(args, stdout=PIPE, stdin=PIPE)
            value = proc.communicate(body)[0]
        # msg = MIMEText(body, 'plain')
        msg = MIMEText(value, 'plain')

Now setup your public keys using "—homedir c:\data\gpg —import", assign ultimate trust to the keys. And that's that.

Attachments (8)

notification.diff (8.0 KB) - added by rgent <richard@…> 5 years ago.
notification.2.diff (9.1 KB) - added by rgent <richard@…> 5 years ago.
notification.0.13.diff (8.7 KB) - added by rgent <richard@…> 4 years ago.
notification.0.13.2.diff (8.7 KB) - added by rgent <richard@…> 4 years ago.
notification.0.13.3.diff (8.8 KB) - added by rgent <richard@…> 4 years ago.
performance improvement in gpg key file parse
notification.0.13.4.diff (8.8 KB) - added by rgent <richard@…> 4 years ago.
notification.0.13.patch (8.6 KB) - added by rgent <richard@…> 4 years ago.
Try this with patch -p0.
notification.1.0.1.py (25.4 KB) - added by rgent <richard@…> 13 months ago.
trac 1.0.1 patched replacement for notification.py

Download all attachments as: .zip

Change History (49)

comment:1 Changed 6 years ago by cboos

  • Keywords gpg consider added
  • Milestone set to 2.0

Now you may want to retry with a proper patch and factor out the constants to a few TracIni settings (e.g. [notification] gpg_path).

But I question a bit the usefulness of such a feature… what would be the use case?

(gpg signing, I can understand, but why encrypting?)

comment:2 Changed 6 years ago by anonymous

Well, lets say that an organization uses PGP encryption to secure all email because of the sensitive content of the email, or because of the hostile location of the email recipients. Or, just because the sysadmin hates the idea of dropping clear text readable postcards into the world wide email system. When such a person uses snail mail, he probably encloses it in an envelope with the idea that only the recipient will read it.

Speaking for myself, we've discovered that trac is really great for running our small company development and operations departments. In fact, trac has become the central focus of much of our daily activities. Thanks for a great product! Our organization is also not centralized, so trac notifications keep us all in touch. Our server is behind a VPN, and we use PGP for email. Since a lot of our IP has been transferred from email to trac, we want to protect it from both well meaning and hostile eyes.

Since trac is free, I also wanted to return something to the community. This is a pretty valuable hack since PGP Corp sells a server to do this for $$$$, and has deliberately crippled their $100+ desktop product to prevent the ability to generate encrypted notifications without a logged on user. I found it unacceptable to extort money for something that should be inexpensive, and only needed 40 lines of code to make free.

I'm not fully convinced that anything more needs to be done other than to have this particular hack-a-trac for trac 11.3 mod_python users located where it can be found, but, as you say, it would be fairly easy to get the constants from ini, and it would be a good exercise for me to do the patch.

Like everyone else, I'm pretty busy, so we'll see..

comment:3 Changed 6 years ago by ebray

Yeah, it's not a bad idea, but it won't of use to much anyone who doesn't have GPG installed, much less at c:\Program Files\GNU\GnuPG\gpg.exe. (My gpg, for example, is /usr/bin/gpg).

comment:4 follow-up: Changed 6 years ago by nkantrowitz

Seems like something that would make a very nice plugin once we have a notification system flexible enough to allow it.

comment:5 in reply to: ↑ 4 Changed 6 years ago by rblank

Replying to nkantrowitz:

Seems like something that would make a very nice plugin once we have a notification system flexible enough to allow it.

This could probably already be done on trunk by implementing an IEmailSender that pipes through GPG and delegates to SmtpEmailSender to actually send the message.

comment:6 Changed 5 years ago by andrew@…

  • Cc andrew@… added

comment:7 Changed 5 years ago by rgent <richard@…>

Had to upgrade the hack for 11.7 and that was no fun. The big change was moving mail server logon to happen after gpg encryption to prevent dropped connections due to mail server timeouts. Since 2.0 seems pretty far off, now looking into preparing a proper patch for 0.12 trunk as per comment:5.

comment:8 Changed 5 years ago by rgent <richard@…>

IEmailSender has Smtp and Sendmail implementations (and could have more), so implementing a GpgEmailSender sender isn't elegant. Better to have an IMsgEncrypt with a GPG implementation (potential others being PGPCorp, Pkware, etc). The encryption part of IMsgEncrypt could be added directly to NotifyEmail.send. 0.12 moves Notify.begin_send directly into SmtpEmailSender.send so server timeouts due to encryption processing are eliminated (and thanks for that!). NotifyEmail.__init__ would call through IMsgEncrypt.init to implement the key caching logic from the OP. Then add constants to trac.ini similar to IEmailSender/Sendmail implementation, which solves the problem raised in comment:3.

Only basic usability problem is that new users won't get into the key cache without a server restart, but that's also a problem for NotifyEmail.email_map, so if that is already resolved, or gets fixed eventually, IMsgEncrypt's keys will also be refreshed.

A fundamental restriction would be that only one type of encryption would be allowed per project. It would be complicated to implement GPG encryption for some users in a project and, say, Pkware encryption for others in the same project.

Should have a patch for GPG encryption soon.

comment:9 Changed 5 years ago by rblank

How about using SendmailEmailSender and using a wrapper script around sendmail that encrypts the message before passing it to sendmail?

The notification architecture is currently being redesigned (see TracDev/Proposals/Announcer), so you might want to join the discussion there (and on trac-dev).

comment:10 Changed 5 years ago by rgent <richard@…>

Sounds good, but I'm using SmtpEmailSender?, so I'd need to adjust the code anyway. Thanks for the suggestion about TracDev.

comment:11 Changed 5 years ago by rgent <richard@…>

Looking at the Announcer UML shows that the proper place for IMsgEncrypt would be in the decorator loop of Addl.Decorator of the preferred formatter loop, so the UML seems to handle the complexity of multiple encryption types in the same project.. and that is good. But looking at the trunk source shows IAnnouncementEmailDecorator requires weighted decorators since the order of decoration would be critical. Once encrypted, further decorations would be pointless.

Looking into the equivalent of the 0.12 Notifier.py, distributors/mail.py, shows that the same logic from comment:8 would allow encrypted emails without enforcing weighted decorators at the expense of multiple encryption types for the same project. Frankly, multiple encryption types for the same project seems presently irrelevant, so the proposal in comment:8 would apply to both 0.12 and the Announcer plugin. If a use case evolves where people using the Announcer plugin require multiple encryption types for the same project, weighted decorators could be explored.

comment:12 Changed 5 years ago by rgent <richard@…>

Oops.. looks like someone else is already working on MessageEncryption for Announcer (see ticket 6773 over at trac-hacks). Given the proposal at TracDev/Proposals/Announcer to replace the existing notification system with Announcer, my patch for 0.12 seems unrequired.

Changed 5 years ago by rgent <richard@…>

comment:13 Changed 5 years ago by rgent <richard@…>

Decided to complete the patch for 0.12 anyway. Diff is attached.

Here's my trac.ini settings from the notification section:

email_encrypt = true
email_encrypter = GpgMsgEncrypter
email_sender = SmtpEmailSender
gpg_binary = C:\Program Files\GNU\GnuPG\gpg.exe
gpg_home = C:\Data\GnuPG

Haven't tried with sendmail, but should work. Haven't tried on unix, but changing gpg_binary and gpg_home in the trac.ini should work. Couldn't find any unit tests for notification, but the patch worked for me with clear and encrypted text recipients.

Changed 5 years ago by rgent <richard@…>

comment:14 Changed 5 years ago by rgent <richard@…>

Found a bug.. a break in the GpgMsgEncrypter init method would have caused only one decryption key to be identified.

Also, patch was not unified diff.

comment:15 Changed 5 years ago by cboos

  • Milestone changed from 2.0 to unscheduled

Milestone 2.0 deleted

comment:16 Changed 4 years ago by rblank

  • Milestone changed from triaging to unscheduled

The patch looks nice. Is it really necessary to cache the key IDs, i.e. is the time taken by this operation really significant compared to e.g. the time taken to encrypt the message? If not, I'd rather leave the caching out.

I wonder if we could generalize the encryption process as an INotificationMessageProcessor "pipe" for messages, where a message is passed to each processor in a list, which can return a list of processed messages that are passed to the next processor (to handle users for which the encryption key is not known, as you do in your patch), and finally, all resulting messages are passed to the IEmailSender.

Also, considering there were talks about integrating th:AnnouncerPlugin, I'd like to hear from other developers: is it worth integrating this patch for 0.13? I'm +1, as a nice patch is available and announcer hasn't shown progress lately, and I volunteer for the integration.

comment:17 follow-up: Changed 4 years ago by rgent <richard@…>

Key ID cache is not required for the implementation. The cache was created to decrease resource use and prevent repetitive processing.

From the subprocess class documentation we get: "On Windows: the Popen class uses CreateProcess() to execute the child program," and CreateProcess is known to be resource intensive. While the parse of the key file is not singularly expensive, its a repetitious loop that returns the same result on each ticket change. Caching key IDs resolves both issues.

If key caching is a serious problem, perhaps an enable\disable switch could be added to the config file.

Concerning your suggestion for an

INotificationMessageProcessor "pipe" for messages, where a message is passed to each processor in a list, which can return a list of processed messages that are passed to the next processor..

TracDev/Proposals/Announcer suggests the term decorators in its UML proposal, and your processor loop would be a partial implementation of the UML's decorator loop. I agree with the UML (provided weighted decorators are included - see comment:11), and I agree with your design suggestion.

comment:18 in reply to: ↑ 17 ; follow-up: Changed 4 years ago by rblank

Replying to rgent <richard@…>:

While the parse of the key file is not singularly expensive, its a repetitious loop that returns the same result on each ticket change.

Except when it doesn't :) From comment:8:

Only basic usability problem is that new users won't get into the key cache without a server restart

(snip)

If key caching is a serious problem, perhaps an enable\disable switch could be added to the config file.

It's not a problem per se. It introduces an additional issue (having to restart the server when the key list changes) and more complexity in the code, and I just want to make sure the performance advantage is important enough to compensate for that. If it doesn't, we better leave it out.

comment:19 follow-up: Changed 4 years ago by hasienda <hoff.st@…>

I'm the author of GPG support for AnnouncerPlugin, so I'll certainly take a look at your patch. It's a pity, that I've not seen this before. I'd have certainly liked to hear from your effort earlier, but it's not too late. If you do care, get in contact with me, please (see my trac-hacks author page for details).

I'm about to revive the aforementioned proposal with some code to actually do the integration. I'd appreciate help in any form. Maybe you're interested to work with us?

comment:20 Changed 4 years ago by hasienda <hoff.st@…>

  • Cc hoff.st@… added

comment:21 in reply to: ↑ 18 ; follow-up: Changed 4 years ago by rgent <richard@…>

Replying to rblank:

Replying to rgent <richard@…>:

While the parse of the key file is not singularly expensive, its a repetitious loop that returns the same result on each ticket change.

Except when it doesn't :) From comment:8:

Only basic usability problem is that new users won't get into the key cache without a server restart

Continuing comment:8

..but that's also a problem for NotifyEmail.email_map

Which is still the same in 0.13. So if we're caching the email_map for known users, without manipulation somewhere, then new users won't get into the notification queue without a server restart either. So if email_map is adjusted someplace else, or NotifyEmail.__init__ gets re-called when a new user email becomes available, the key cache can\will be refilled. That restricts encryption to the user base, not all emails that might appear in the ticket - which makes sense since we'd have to add the user's key to a local key file, leaving anonymous encryption as a much bigger task.

(snip)

If key caching is a serious problem, perhaps an enable\disable switch could be added to the config file.

It's not a problem per se. It introduces an additional issue (having to restart the server when the key list changes) and more complexity in the code, and I just want to make sure the performance advantage is important enough to compensate for that. If it doesn't, we better leave it out.

Entirely your call.

comment:22 in reply to: ↑ 19 Changed 4 years ago by rgent <richard@…>

Replying to hasienda <hoff.st@…>:

I'm the author of GPG support..

<snip>

As mentioned in comment:11, the patch would need minor changes to be applied to distributors\mail.py in the Announcer plugin. The patch was mostly a learning exercise with the added benefit of saving me the trouble of manually editing source code on every release of trac.

If you've got something specific in mind that I could help with, I'll gladly take a look, but there's a lot going on, so can't promise that anything will get done.

comment:23 in reply to: ↑ 21 Changed 4 years ago by rblank

Replying to rgent <richard@…>:

..but that's also a problem for NotifyEmail.email_map

Which is still the same in 0.13. So if we're caching the email_map for known users, without manipulation somewhere, then new users won't get into the notification queue without a server restart either.

I must be missing something. Where do you see NotifyEmail.email_map cached? Sure, it's created in the constructor, but NotifyEmail is not a Component, it's a "plain" class, and it gets instantiated for every e-mail that is sent (see e.g. TicketModule._do_save()).

comment:24 follow-up: Changed 4 years ago by rgent <richard@…>

My bad. I moved encrypter init into a component, and forgot that NotifyEmail was not a singleton. Of course the cache can go. The read of the key file should probably stay in the c'tor, and the key file parse loop should be rewritten to be more efficient.

Is that something you would do at this stage, or do you want me to resubmit the patch?

comment:25 in reply to: ↑ 24 ; follow-up: Changed 4 years ago by rblank

Replying to rgent <richard@…>:

Is that something you would do at this stage, or do you want me to resubmit the patch?

It would be great if you could submit an updated patch. OTOH, I'm still waiting for opinions whether this should be integrated as-is, or if Announcer is still meant to be integrated soon-ish, so you may want to wait for that decision before investing more time into it.

Changed 4 years ago by rgent <richard@…>

comment:26 Changed 4 years ago by rgent <richard@…>

All done. Even better if patch does not get integrated because Announcer is integrated. I use the patch in production, so I'm committed either way.

Changed 4 years ago by rgent <richard@…>

comment:27 in reply to: ↑ 25 ; follow-up: Changed 4 years ago by hasienda <hoff.st@…>

Replying to rblank:

Replying to rgent <richard@…>:

Is that something you would do at this stage, or do you want me to resubmit the patch?

It would be great if you could submit an updated patch. OTOH, I'm still waiting for opinions whether this should be integrated as-is, or if Announcer is still meant to be integrated soon-ish, […]

Well, I'm already trying to contact the maintainer of Announcer about this. As the current implementation of GnuPG support for Announcer is out but not finished in my eyes (while already used in production as well), there is still much work to be done for both, actual integration of Announcer with Trac (replacing TracNotification) and improving the new crypto part. This is certainly not done within some days.

While the patch here certainly has anything needed to just do the mentioned use case, I'd certainly appreciate a more feature-full implementation of GnuPG support (managing multiple keys per user, allow for restriction to not send anything unencrypted, etc.). And as we're about to add an optional dependency (GnuPG binary) it can't hurt to have a ready-made wrapper as another one. I don't feel as reluctant as the OP to just interface to the GnuPG binary on my own but let a matured wrapper handle basic interaction including error checking (see related development documentation for more details).

I'm moving towards this stuff now, but given my Python skills and time both limiting the progress considerably I'll certainly need some help, especially with code review and testing. I.e. unit tests are something I've still to explore.

comment:28 Changed 4 years ago by n-roeser@…

  • Cc n-roeser@… added

Adding myself to Cc. Without a comment, this change is rejected as potential spam. :-(

comment:29 in reply to: ↑ 27 Changed 4 years ago by rgent <richard@…>

Replying to hasienda <hoff.st@…>:

<snip>

..but let a matured wrapper handle basic interaction including error checking (see related development documentation for more details).

I'm moving towards this stuff now, but given my Python skills and time both limiting the progress considerably I'll certainly need some help, especially with code review and testing. I.e. unit tests are something I've still to explore.

Most of the naming conventions and basic ideas were lifted from your encryption notes, so thanks for that. As for the help, I'll extend my development environment to include the Announcer plugin, and post Announcer specific message encryption notes in th:ticket:6773.

Changed 4 years ago by rgent <richard@…>

performance improvement in gpg key file parse

comment:30 Changed 4 years ago by rgent <richard@…>

Ok, attachment:notification.0.13.3.diff is the latest (probably final) version. Last few diffs were performance improvements to the key file parse. According to gpg cvs "DETAILS", the number of fields in rows is not fixed, but in the case of uid, the documentation says:

1. Field:  Type of record
    ...
    uid = user id (only field 10 is used).

Also, added "—fixed-list-mode" flag to separate first uid from others.

Changed 4 years ago by rgent <richard@…>

comment:31 Changed 4 years ago by rgent <richard@…>

Removed an unused var and replaced an if with elif. attachment:notification.0.13.4.diff is final candidate.

comment:32 Changed 4 years ago by Thijs Triemstra <lists@…>

  • Cc lists@… added
  • Keywords patch added

comment:33 Changed 4 years ago by Thijs Triemstra <lists@…>

I'm not able to apply this patch using patch -p0, can you attach a file with the output of svn diff?

Changed 4 years ago by rgent <richard@…>

Try this with patch -p0.

comment:34 Changed 4 years ago by thijstriemstra

  • Cc thijstriemstra added; lists@… removed
  • Summary changed from Add GnuPG encryption to email notifications to [PATCH] Add GnuPG encryption to email notifications

Thanks.

comment:35 Changed 4 years ago by srevill@…

Hi. I've created a Trac 0.12.2 install in a CentOS 5.5 VM to test this notification.0.13.patch. I've got GnuPG installed with the pub keys for some test users and it appears to be working apart from one small detail: the body text in the encrypted notification email is empty. There's no encrypted stuff, just one or maybe two linefeeds.

Here's the output that may be relevant in the log file and it all looks OK to me:

2011-03-24 18:17:15,105 Trac[notification] INFO: Mapping GnuPG keys to emails using /usr/bin/gpg with home /home/trac/.gnupg
2011-03-24 18:17:15,135 Trac[notification] INFO: Encrypting email to: [u'srevill@endurancetech.co.uk', u'amoyler@endurancetech.co.uk', u'rnicoll@endurancetech.co.uk']
2011-03-24 18:17:15,136 Trac[notification] INFO: Preparing clear text for: []
2011-03-24 18:17:15,157 Trac[notification] INFO: Sending notification through SMTP at localhost:25 to [u'srevill@endurancetech.co.uk', u'amoyler@endurancetech.co.uk', u'rnicoll@endurancetech.co.uk']

Notes: I'm srevill and I assigned the test ticket to myself. rnicoll and amoyler are people who are CC'd on the ticket. All three of us have public keys in the gpg keyring.

Any ideas?

comment:36 Changed 4 years ago by anonymous

Upon further investigation, I'm also seeing this in the log:

Trac[web_ui] ERROR: Failure sending notification on change to ticket #315: OSError: [Errno 32] Broken pipe

The email notifications were working before I added the extra stuff to the trac.ini to activate the patch:

email_encrypt = true
email_encrypter = GpgMsgEncrypter
email_sender = SmtpEmailSender
gpg_binary = /usr/bin/gpg
gpg_home = /home/trac/.gnupg

comment:37 Changed 4 years ago by srevill@…

One last post: it seems that the empty body text notification email is sent for the first update on the ticket since starting that Trac environment. All subsequent ticket updates fail with the "Broken pipe" error and don't send any notification email at all. Restarting Trac seems to get back into the initial state.

comment:38 Changed 4 years ago by rgent <richard@…>

Did you assign ultimate trust to the keys?

Like gpg --homedir /home/trac/.gnupg --edit-key srevill@endurancetech.co.uk trust.

comment:39 Changed 4 years ago by anonymous

No, I assigned "full" trust. I'll give that a go and see what happens. Thanks.

comment:40 Changed 4 years ago by srevill@…

Nice one! That was indeed the problem. I suppose gnupg was sitting there waiting for someone to press 'y' on the keyboard for it before! Oops.

Changed 13 months ago by rgent <richard@…>

trac 1.0.1 patched replacement for notification.py

comment:41 Changed 13 months ago by rgent <richard@…>

I've upgraded my trac installation from 0.12 to 1.0.1. This required a new notification patch for GPG.

attachment:notification.1.0.1.py is an already patched replacement for version 1.0.1 trac/notification.py, as in not a diff\patch. You should edit your sites notification.py and completely replace the contents with notification.1.0.1.py.

If required, I can attach a patch for 1.1.x trunk.

Modify Ticket

Change Properties
Set your email in Preferences
Action
as new The owner will remain eblot.
as The resolution will be set. Next status will be 'closed'.
The owner will be changed from eblot to anonymous. Next status will be 'assigned'.

Add Comment


E-mail address and name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.