From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2201) id 9CF35385840D; Tue, 23 Apr 2024 15:21:10 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 9CF35385840D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1713885670; bh=83H/IDLOfnUwHEDwE9a0kBJ9vXGExQShwP/RKnMCkVY=; h=To:Subject:Date:From:From; b=IWi0KWqjTcBkQjiIrfbUDLW1XWGhU5YNK++Zqp0cZdIx3chHDxcqDrb/4fa9zQgRy 3rQELK8YVYNB5Kwhhgmt6sYVl4wnpS27wAKROKcVIu8BZnq01+NB8Z1vYDwI2B36me fDe10EcchMfYsByJBg1N3aoPSUnMtcIQQcowHweY= To: cygwin-apps-cvs@sourceware.org Subject: [calm - Cygwin server-side packaging maintenance script] branch master, updated. 20230209-98-gf1ccc90 X-Git-Refname: refs/heads/master X-Git-Reftype: branch X-Git-Oldrev: 693426a56fcf26a85a1aeb202fccace35469e344 X-Git-Newrev: f1ccc9033618d24da93cd10529faea5a7eaa20be Message-Id: <20240423152110.9CF35385840D@sourceware.org> Date: Tue, 23 Apr 2024 15:21:09 +0000 (GMT) From: Jon Turney List-Id: https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=f1ccc9033618d24da93cd10529faea5a7eaa20be commit f1ccc9033618d24da93cd10529faea5a7eaa20be Author: Jon Turney 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 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() #