public inbox for cygwin-apps-cvs@sourceware.org
help / color / mirror / Atom feed
* [calm - Cygwin server-side packaging maintenance script] branch master, updated. 20230209-98-gf1ccc90
@ 2024-04-23 15:21 Jon Turney
  0 siblings, 0 replies; only message in thread
From: Jon Turney @ 2024-04-23 15:21 UTC (permalink / raw)
  To: cygwin-apps-cvs




https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=f1ccc9033618d24da93cd10529faea5a7eaa20be

commit f1ccc9033618d24da93cd10529faea5a7eaa20be
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Mon Apr 22 19:14:18 2024 +0100

    Ignore sighup when daemonized
    
    It seems like systemd sends this as well as SIGTERM, so make sure to
    ignore it so we shut down cleanly.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=3a18bba53957302246fc44c6dbbbd26520ed2080

commit 3a18bba53957302246fc44c6dbbbd26520ed2080
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sat Feb 17 19:26:03 2024 +0000

    Don't update setup.ini if we can't sign it
    
    Check with gpg-agent if the signing key(s) are available, and don't
    update setup.ini if we can't sign it.
    
    Also, since we want to do some logging about keygrips before we
    daemonize, move logging_setup earlier, and don't close the file
    descriptors it opens when we darmonize.


Diff:
---
 calm/calm.py             | 74 ++++++++++++++++++++++++++++++++++++++++++------
 calm/common_constants.py |  5 ++++
 calm/utils.py            |  2 ++
 3 files changed, 72 insertions(+), 9 deletions(-)

diff --git a/calm/calm.py b/calm/calm.py
index ac662a2..02e613b 100755
--- a/calm/calm.py
+++ b/calm/calm.py
@@ -549,10 +549,39 @@ def do_main(args, state):
     return 0
 
 
+#
+# verify signing key(s) are available in gpg-agent
+#
+def is_passphrase_cached(args):
+    passphrase_cached = set()
+
+    for k in args.keygrips:
+        logging.debug('Querying gpg-agent on keygrip %s' % (k))
+        key_details = utils.system("/usr/bin/gpg-connect-agent 'keyinfo %s' /bye" % k)
+        for l in key_details.splitlines():
+            if l.startswith('S'):
+                # check for either PROTECTION='P' and CACHED='1' (passphrase is
+                # cached) or PROTECTION='C' (no passphrase)
+                keyinfo = l.split()
+                if keyinfo[2] == k:
+                    if ((keyinfo[7] == 'P' and keyinfo[6] == '1') or
+                        keyinfo[7] == 'C'):
+                        passphrase_cached.add(k)
+                    else:
+                        logging.error("Signing key not available")
+                        # Provide some help on the necessary runes: start agent
+                        # with --allow-preset-passphrase so that the passphrase
+                        # preloaded with gpg-preset-passphrase doesn't expire.
+                        logging.error("Load it with '/usr/libexec/gpg-preset-passphrase --preset %s' then provide passphrase" % k)
+                    break
+
+    # return True if all keys are accessible
+    return passphrase_cached == set(args.keygrips)
+
+
 #
 #
 #
-
 def do_output(args, state):
     # update packages listings
     # XXX: perhaps we need a --[no]listing command line option to disable this from being run?
@@ -598,7 +627,13 @@ def do_output(args, state):
                     changed = True
 
             # then update setup.ini
-            if changed:
+            if not changed:
+                logging.debug("removing %s, unchanged %s" % (tmpfile.name, inifile))
+                os.remove(tmpfile.name)
+            elif not is_passphrase_cached(args):
+                logging.debug("removing %s, cannot sign" % (tmpfile.name))
+                os.remove(tmpfile.name)
+            else:
                 update_json = True
 
                 if args.dryrun:
@@ -633,10 +668,6 @@ def do_output(args, state):
                         keys = ' '.join(['-u' + k for k in args.keys])
                         utils.system('/usr/bin/gpg ' + keys + ' --batch --yes -b ' + extfile)
 
-            else:
-                logging.debug("removing %s, unchanged %s" % (tmpfile.name, inifile))
-                os.remove(tmpfile.name)
-
     # write packages.json
     jsonfile = os.path.join(args.htdocs, 'packages.json.xz')
     if update_json or not os.path.exists(jsonfile):
@@ -681,9 +712,19 @@ def do_daemon(args, state):
 
     logging.getLogger('inotify.adapters').propagate = False
 
+    def getLogFileDescriptors(logger):
+        """Get a list of fds from logger"""
+        handles = []
+        for handler in logger.handlers:
+            handles.append(handler.stream.fileno())
+        if logger.parent:
+            handles += getLogFileDescriptors(logger.parent)
+        return handles
+
     context = daemon.DaemonContext(
         stdout=sys.stdout,
         stderr=sys.stderr,
+        files_preserve=getLogFileDescriptors(logging.getLogger()),
         umask=0o002,
         pidfile=lockfile.pidlockfile.PIDLockFile(args.daemon))
 
@@ -699,12 +740,15 @@ def do_daemon(args, state):
         running = False
         raise InterruptedError
 
+    def sighup(signum, frame):
+        logging.debug("SIGHUP")
+
     context.signal_map = {
         signal.SIGTERM: sigterm,
+        signal.SIGHUP: sighup,
     }
 
     with context:
-        logging_setup(args)
         logging.info("calm daemon started, pid %d" % (os.getpid()))
         irk.irk("calm daemon started")
 
@@ -900,6 +944,7 @@ def main():
     setupdir_default = common_constants.HTDOCS
     vault_default = common_constants.VAULT
     logdir_default = '/sourceware/cygwin-staging/logs'
+    key_default = [common_constants.DEFAULT_GPG_KEY]
 
     parser = argparse.ArgumentParser(description='Upset replacement')
     parser.add_argument('-d', '--daemon', action='store', nargs='?', const=pidfile_default, help="daemonize (PIDFILE defaults to " + pidfile_default + ")", metavar='PIDFILE')
@@ -907,7 +952,7 @@ def main():
     parser.add_argument('--force', action='count', help="force regeneration of static htdocs content", default=0)
     parser.add_argument('--homedir', action='store', metavar='DIR', help="maintainer home directory (default: " + homedir_default + ")", default=homedir_default)
     parser.add_argument('--htdocs', action='store', metavar='DIR', help="htdocs output directory (default: " + htdocs_default + ")", default=htdocs_default)
-    parser.add_argument('--key', action='append', metavar='KEYID', help="key to use to sign setup.ini", default=[], dest='keys')
+    parser.add_argument('--key', action='append', metavar='KEYID', help="key to use to sign setup.ini", default=key_default, dest='keys')
     parser.add_argument('--logdir', action='store', metavar='DIR', help="log directory (default: '" + logdir_default + "')", default=logdir_default)
     parser.add_argument('--trustedmaint', action='store', metavar='NAMES', help="trusted package maintainers (default: '" + trustedmaint_default + "')", default=trustedmaint_default)
     parser.add_argument('--pkglist', action='store', metavar='FILE', help="package maintainer list (default: " + pkglist_default + ")", default=pkglist_default)
@@ -930,6 +975,18 @@ def main():
     if args.reports is None:
         args.reports = args.daemon
 
+    logging_setup(args)
+
+    # find matching keygrips for keys
+    args.keygrips = []
+    for k in args.keys:
+        details = utils.system('gpg2 --list-keys --with-keygrip --with-colons %s' % k)
+        for l in details.splitlines():
+            if l.startswith('grp'):
+                grip = l.split(':')[9]
+                args.keygrips.append(grip)
+                logging.debug('key ID %s has keygrip %s' % (k, grip))
+
     state = CalmState()
     state.args = args
 
@@ -944,7 +1001,6 @@ def main():
     if args.daemon:
         do_daemon(args, state)
     else:
-        logging_setup(args)
         status = do_main(args, state)
 
     return status
diff --git a/calm/common_constants.py b/calm/common_constants.py
index 29b719b..82d7801 100644
--- a/calm/common_constants.py
+++ b/calm/common_constants.py
@@ -82,11 +82,16 @@ DEFAULT_KEEP_COUNT = 3
 DEFAULT_KEEP_COUNT_TEST = 2
 DEFAULT_KEEP_DAYS = 0
 
+# getting gpg to accurately tell you the default key is apparently impossible,
+# so hardcode it here
+DEFAULT_GPG_KEY = '56405CF6FCC81574682A5D561A698DE9E2E56300'
+
 # different values to be used when we are not running on sourceware.org, but my
 # test system...
 if os.uname()[1] == 'tambora':
     EMAILS = 'debug'
     ALWAYS_BCC = ''
+    DEFAULT_GPG_KEY = '29E138393680DBA0'
 
 # package compressions
 PACKAGE_COMPRESSIONS = ['bz2', 'gz', 'lzma', 'xz', 'zst']
diff --git a/calm/utils.py b/calm/utils.py
index 26e3655..6fb93b9 100644
--- a/calm/utils.py
+++ b/calm/utils.py
@@ -124,9 +124,11 @@ def system(args):
         for l in e.output.decode().splitlines():
             logging.warning(l)
         logging.warning('%s exited %d' % (args.split()[0], e.returncode))
+        return e.output.decode()
     else:
         for l in output.decode().splitlines():
             logging.info(l)
+        return output.decode()
 
 
 #


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2024-04-23 15:21 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-04-23 15:21 [calm - Cygwin server-side packaging maintenance script] branch master, updated. 20230209-98-gf1ccc90 Jon Turney

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).