You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by br...@apache.org on 2015/12/06 00:22:20 UTC

svn commit: r1718134 - in /subversion/trunk/tools/dist: README.advisory advisory.py security/mailinglist.py

Author: brane
Date: Sat Dec  5 23:22:20 2015
New Revision: 1718134

URL: http://svn.apache.org/viewvc?rev=1718134&view=rev
Log:
Finally wrote the script for sending advisory mails.

* tools/dist/advisory.py: New; the all-in-one script.
* tools/dist/README.advisory: Instructions for using said script.

* tools/dist/security/mailinglist.py:
   Tweak interface to match the other scripts in this module.

Added:
    subversion/trunk/tools/dist/README.advisory   (with props)
    subversion/trunk/tools/dist/advisory.py   (with props)
Modified:
    subversion/trunk/tools/dist/security/mailinglist.py

Added: subversion/trunk/tools/dist/README.advisory
URL: http://svn.apache.org/viewvc/subversion/trunk/tools/dist/README.advisory?rev=1718134&view=auto
==============================================================================
--- subversion/trunk/tools/dist/README.advisory (added)
+++ subversion/trunk/tools/dist/README.advisory Sat Dec  5 23:22:20 2015
@@ -0,0 +1,78 @@
+A guide to sending security advisory e-mails
+============================================
+
+--------------------------------------------------------
+Step 1: Prepare the advisory texts, patches and metadata
+--------------------------------------------------------
+
+[details are covered elsewhere]
+
+----------------------------------
+Step 2: Prepare the website update
+----------------------------------
+
+  $ cd ${PMC_AREA_WC}/security
+  $ ${TRUNK_WC}/tools/dist/advisory.py generate \
+        --destinatio=${SITE_WC}/publish/security \
+        CVE-2015-5259 CVE-2015-5343 ...
+
+This will generate a plain-text version of the advisories, including
+patches etc., suiatble for publishing on our web site. Once these
+are generated, make sure you add the links to the new files to:
+
+    ${SITE_WC}/publish/security/index.html
+
+
+-----------------------------------------------
+Step 3: Check the advisories and their metadata
+-----------------------------------------------
+
+  $ cd ${PMC_AREA_WC}/security
+  $ ${TRUNK_WC}/tools/dist/advisory.py test \
+        --username=someone \
+        --revision=22091347 \
+        --release-versions=1.8.15,1.9.3 \
+        --release-date=2015-12-15 \
+        CVE-2015-5259 CVE-2015-5343 ...
+
+Assuming all the required bits are in place, this will generate the
+complete text of a GPG-signed e-mail message, signed by and sent from
+someone@apache.org, for all the listed CVE numbers.
+
+Note the arguments:
+
+    --revision    is the revision on
+                  https://dist.apache.org/repos/dist/dev/subversion
+                  in which the tarballs are/will be available
+                  (see: notice-template.txt in ${PMC_AREA_WC}/security).
+
+    --release-versions   is a comma-separated list of version numbers
+                         in which fixes for the CVE numbers will be
+                         available.
+
+    --release-date       is the expected date of the release(s).
+
+
+----------------------
+Step 4: Send teh mails
+----------------------
+
+  $ cd ${PMC_AREA_WC}/security
+  $ ${TRUNK_WC}/tools/dist/advisory.py test \
+        (the rest of the arguments are as in step 3).
+
+The mails will be sent one at a time to each recipient separately.
+
+
+--------------------------------------------------
+Step 5: Wait for the release. Release.
+        Commit the site update prepared in step 1.
+--------------------------------------------------
+
+
+
+TODO: security/mailer.py does not calculate the micalg= PGP/MIME
+      parameter based on the properties of the actual PGP key
+      used. It's currently hard-coded as "pgp-sha512" which *should*
+      be correct for anyone signing these mails with their ASF release
+      signing key.

Propchange: subversion/trunk/tools/dist/README.advisory
------------------------------------------------------------------------------
    svn:eol-style = native

Added: subversion/trunk/tools/dist/advisory.py
URL: http://svn.apache.org/viewvc/subversion/trunk/tools/dist/advisory.py?rev=1718134&view=auto
==============================================================================
--- subversion/trunk/tools/dist/advisory.py (added)
+++ subversion/trunk/tools/dist/advisory.py Sat Dec  5 23:22:20 2015
@@ -0,0 +1,182 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+"""
+Send GPG-signed security advisory e-mayails from an @apache.org address
+to a known list of recipients, or write the advisory text in a
+form suitable for publishing on http://subversion.apache.org/.
+
+Usage: cd to the root directory of the advisory descriptions, then:
+
+    $ ${TRUNK_WC}/tools/dist/advisory.py send \
+          --username=<ASF-username> \
+          --revision=<dist-dev-revision-number>
+          --release-versions=<target-releases> \
+          --release-date=<expected-release-date> <CVE-number>...
+
+or
+
+    $ ${TRUNK_WC}/tools/dist/advisory.py test \
+          (... --username, etc. as above)
+
+or
+
+    $ ${TRUNK_WC}/tools/dist/advisory.py generate \
+          --destination=${SITE_WC}/publish/security \
+          <CVE-number>...
+"""
+
+from __future__ import absolute_import
+
+import os
+import sys
+import argparse
+import datetime
+import getpass
+import re
+
+import security.parser
+import security.adviser
+import security.mailer
+import security.mailinglist
+
+ROOTDIR = os.path.abspath(os.getcwd())
+NOTICE_TEMPLATE = 'notice-template.txt'
+MAILING_LIST = 'pre-notifications.txt'
+
+
+def parse_args(argv):
+    parser = argparse.ArgumentParser(
+        prog=os.path.basename(__file__), add_help=True,
+        description="""\
+Send GPG-signed security advisory e-mayails from an @apache.org address
+to a known list of recipients, or write the advisory text in a
+form suitable for publishing on http://subversion.apache.org/.
+""")
+    parser.add_argument(
+        'command', action='store',
+        choices=['send', 'test', 'generate'],
+        help=('send: send mail; '
+              'test: write the mail to standard output; '
+              'generate: write an advisory for the website'))
+    parser.add_argument(
+        '--username', action='store', required=False,
+        help='the @apache.org username of the sender')
+    parser.add_argument(
+        '--revision', action='store', required=False, type=int,
+        help=('revision on dist.a.o./repos/dist/dev/subversion '
+              'in which the patched tarballs are available'))
+    parser.add_argument(
+        '--release-versions', action='store', required=False,
+        help=('comma-separated list of future released versions '
+              'that will contain the fix(es)'))
+    parser.add_argument(
+        '--release-date', action='store', required=False,
+        help=('expected release date for the above mentioned'
+              ' versions (in ISO format, YYYY-MM-DD)'))
+    parser.add_argument(
+        '--destination', action='store', required=False,
+        help=('the directory where the website advisory should be '
+              'written; usually ${SITE_WC}/publish/security'))
+    parser.add_argument('cve', nargs='+')
+
+    return parser.parse_args(argv)
+
+
+def check_root():
+    if not os.path.isfile(os.path.join(ROOTDIR, NOTICE_TEMPLATE)):
+        sys.stderr.write('Missing file: ' + NOTICE_TEMPLATE + '\n')
+        sys.exit(1)
+    if not os.path.isfile(os.path.join(ROOTDIR, MAILING_LIST)):
+        sys.stderr.write('Missing file: ' + MAILING_LIST + '\n')
+        sys.exit(1)
+
+
+def check_sendmail(args):
+    if (not (args.username and args.revision
+             and args.release_versions
+             and args.release_date and args.cve)
+        or args.destination):
+        sys.stderr.write(
+            'The "' + args.command + '" command requires the '
+            'following options:\n'
+            '  --username, --revision, --release-versions, --release-date\n'
+            ' and a list of CVE numbers.\n')
+        sys.exit(1)
+    args.release_versions = re.split(r'\s*,\s*', args.release_versions)
+    args.release_date = datetime.datetime.strptime(args.release_date,
+                                                   '%Y-%m-%d')
+
+
+def sendmail(really_send, args):
+    notice_template = os.path.join(ROOTDIR, NOTICE_TEMPLATE)
+    mailing_list = os.path.join(ROOTDIR, MAILING_LIST)
+    sender = args.username + '@apache.org'
+    notification = security.parser.Notification(ROOTDIR, *args.cve)
+    mailer = security.mailer.Mailer(notification,
+                                    args.username + '@apache.org',
+                                    notice_template,
+                                    args.release_date,
+                                    args.revision,
+                                    *args.release_versions)
+    message = mailer.generate_message()
+    recipients = security.mailinglist.MailingList(mailing_list)
+    if (not really_send):
+        sys.stdout.write(message.as_string())
+        return
+
+    password = getpass.getpass('Password for ' + args.username
+                               + ' at mail-relay.apache.org: ')
+    mailer.send_mail(message, args.username, password,
+                     recipients=recipients)
+
+
+def check_generate(args):
+    if (not (args.destination and args.cve)
+        or args.username or args.revision
+        or args.release_versions
+        or args.release_date):
+        sys.stderr.write(
+            'The "generate" command requires the '
+            '--destination option '
+            'and a list of CVE numbers.\n')
+        sys.exit(1)
+    if not os.path.isdir(args.destination):
+        sys.stderr.write(args.destination + ' is not a directory')
+        sys.exit(1)
+
+def generate(args):
+    notification = security.parser.Notification(ROOTDIR, *args.cve)
+    security.adviser.generate(notification, args.destination);
+
+
+def main():
+    check_root()
+    args = parse_args(sys.argv[1:])
+    if args.command in ('send', 'test'):
+        check_sendmail(args)
+        sendmail(args.command == 'send', args)
+    elif args.command == 'generate':
+        check_generate(args)
+        generate(args)
+
+
+if __name__ == '__main__':
+    main()

Propchange: subversion/trunk/tools/dist/advisory.py
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: subversion/trunk/tools/dist/advisory.py
------------------------------------------------------------------------------
    svn:executable = *

Modified: subversion/trunk/tools/dist/security/mailinglist.py
URL: http://svn.apache.org/viewvc/subversion/trunk/tools/dist/security/mailinglist.py?rev=1718134&r1=1718133&r2=1718134&view=diff
==============================================================================
--- subversion/trunk/tools/dist/security/mailinglist.py (original)
+++ subversion/trunk/tools/dist/security/mailinglist.py Sat Dec  5 23:22:20 2015
@@ -34,12 +34,11 @@ class MailingList(object):
     Parses ^/pmc/subversion/security/pre-notifications.txt
     """
 
-    __PRE_NOTIFICATIONS = 'pre-notifications.txt'
     __ADDRESS_LINE = re.compile(r'^\s{6}(?:[^<]+)?<[^<>]+>\s*$')
 
-    def __init__(self, rootdir):
+    def __init__(self, mailing_list):
         self.__addresses = []
-        self.__parse_addresses(rootdir)
+        self.__parse_addresses(mailing_list)
 
     def __iter__(self):
         return self.__addresses.__iter__()
@@ -47,9 +46,8 @@ class MailingList(object):
     def __len__(self):
         return len(self.__addresses)
 
-    def __parse_addresses(self, rootdir):
-        pre_notifications = os.path.join(rootdir, self.__PRE_NOTIFICATIONS)
-        with open(pre_notifications, 'rt') as pn:
+    def __parse_addresses(self, mailing_list):
+        with open(mailing_list, 'rt') as pn:
             for line in pn:
                 m = self.__ADDRESS_LINE.match(line)
                 if not m: