Skip to content

Commit

Permalink
Introduce repo key handling
Browse files Browse the repository at this point in the history
Ask user before importing RPM GPG key
Ask to remove gpg key on repo remove
Ask to remove key after installing zoom

https://bugzilla.suse.com/show_bug.cgi?id=1207334
  • Loading branch information
asdil12 committed Jan 24, 2023
1 parent 9587598 commit 378c6e7
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 7 deletions.
89 changes: 84 additions & 5 deletions opi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,20 @@ def get_repos():
mainsec = cp.sections()[0]
if not bool(int(cp.get(mainsec, "enabled"))):
continue
yield (re.sub(r"\.repo$", "", repo_file), cp.get(mainsec, "baseurl"))
repo = {
"name": re.sub(r"\.repo$", "", repo_file),
"url": cp.get(mainsec, "baseurl"),
}
if cp.has_option(mainsec, "gpgkey"):
repo["gpgkey"] = cp.get(mainsec, "gpgkey")
yield repo
except Exception as e:
print("Error parsing '%s': %r" % (repo_file, e))

def get_enabled_repo_by_url(url):
for repo, repo_url in get_repos():
if url_normalize(repo_url) == url_normalize(url):
return repo
for repo in get_repos():
if url_normalize(repo['url']) == url_normalize(url):
return repo['name']

def add_repo(filename, name, url, enabled=True, gpgcheck=True, gpgkey=None, repo_type='rpm-md', auto_import_key=False, auto_refresh=False, priority=None):
tf = tempfile.NamedTemporaryFile('w')
Expand All @@ -142,7 +148,7 @@ def add_repo(filename, name, url, enabled=True, gpgcheck=True, gpgkey=None, repo
tf.file.write("type=%s\n" % repo_type)
tf.file.write("gpgcheck=%i\n" % gpgcheck)
if gpgkey:
subprocess.call(['sudo', 'rpm', '--import', gpgkey.replace('$releasever', get_version() or '$releasever')])
ask_import_key(gpgkey)
tf.file.write("gpgkey=%s\n" % gpgkey)
if auto_refresh:
tf.file.write("autorefresh=1\n")
Expand All @@ -162,6 +168,34 @@ def add_repo(filename, name, url, enabled=True, gpgcheck=True, gpgkey=None, repo
refresh_cmd = ['sudo', 'dnf', 'ref']
subprocess.call(refresh_cmd)

def normalize_key(pem):
new_lines = []
for line in pem.split("\n"):
line = line.strip()
if not line:
continue
if line.lower().startswith("version:"):
continue
new_lines.append(line)
new_lines.insert(1, '')
return "\n".join(new_lines)

def get_keys_from_rpmdb():
s = subprocess.check_output(["rpm", "-q", "gpg-pubkey", "--qf",
'%{NAME}-%{VERSION}-%{RELEASE}\n%{PACKAGER}\n%{DESCRIPTION}\nOPI-SPLIT-TOKEN-TO-TELL-KEY-PACKAGES-APART\n'])
keys = []
for raw_kpkg in s.decode().strip().split("OPI-SPLIT-TOKEN-TO-TELL-KEY-PACKAGES-APART"):
raw_kpkg = raw_kpkg.strip()
if not raw_kpkg:
continue
kid, name, pubkey = raw_kpkg.strip().split("\n", 2)
keys.append({
"kid": kid,
"name": name,
"pubkey": normalize_key(pubkey)
})
return keys

def install_packages(packages, from_repo=None, allow_vendor_change=False, allow_arch_change=False, allow_downgrade=False, allow_name_change=False):
if get_backend() == BackendConstants.zypp:
args = ['sudo', 'zypper', 'in']
Expand Down Expand Up @@ -409,12 +443,57 @@ def ask_for_option(options, question="Pick a number (0 to quit):", option_filter
else:
return options[num-1]

def ask_import_key(keyurl):
key = normalize_key(requests.get(keyurl.replace('$releasever', get_version() or '$releasever')).text)
for line in subprocess.check_output(["gpg", "--quiet", "--show-keys", "--with-colons", "-"], input=key.encode()).decode().strip().split("\n"):
if line.startswith("uid:"):
key_info = line.split(':')[9]
if [db_key for db_key in get_keys_from_rpmdb() if normalize_key(db_key['pubkey']) == key]:
print(f"Package signing key '{key_info}' is already present.")
else:
if ask_yes_or_no(f"Import package signing key '{key_info}'", 'y'):
tf = tempfile.NamedTemporaryFile('w')
tf.file.write(key)
tf.file.flush()
subprocess.call(['sudo', 'rpm', '--import', tf.name])
tf.file.close()

def ask_keep_key(keyurl, repo_name=None):
"""
Ask to remove the key given by url to key file.
Warns about all repos still using the key except the repo given by repo_name param.
"""
urlkey = normalize_key(requests.get(keyurl.replace('$releasever', get_version() or '$releasever')).text)
keys = [key for key in get_keys_from_rpmdb() if key['pubkey'] == urlkey]
for key in keys:
repos_using_this_key = []
for repo in get_repos():
if repo_name and repo['name'] == repo_name:
continue
if repo.get('gpgkey'):
repokey = normalize_key(requests.get(repo['gpgkey']).text)
if repokey == key['pubkey']:
repos_using_this_key.append(repo)
if repos_using_this_key:
default_answer = 'y'
print("This key is still in use by the following remaining repos - removal is NOT recommended:")
print(" - "+ "\n - ".join([repo['name'] for repo in repos_using_this_key]))
else:
default_answer = 'n'
print("This key is not in use by any remaining repos.")
print("Keeping the key will allow additional packages signed by this key to be installed in the future without further warning.")
if not ask_yes_or_no(f"Keep package signing key '{key['name']}'?", default_answer):
subprocess.call(['sudo', 'rpm', '-e', key['kid']])

def ask_keep_repo(repo):
if not ask_yes_or_no('Do you want to keep the repo "%s"?' % repo, 'y'):
repo_info = next((r for r in get_repos() if r['name'] == repo))
if get_backend() == BackendConstants.zypp:
subprocess.call(['sudo', 'zypper', 'rr', repo])
if get_backend() == BackendConstants.dnf:
subprocess.call(['sudo', 'rm', os.path.join(REPO_DIR, '%s.repo' % repo)])
if repo_info.get('gpgkey'):
ask_keep_key(repo_info['gpgkey'], repo)

def format_binary_option(binary, table=True):
if is_official_project(binary['project']):
Expand Down
4 changes: 3 additions & 1 deletion opi/plugins/zoom.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ def run(cls, query):
if not opi.ask_yes_or_no("Do you want to install Zoom from zoom.us?", 'y'):
return

subprocess.call(['sudo', 'rpm', '--import', 'https://zoom.us/linux/download/pubkey'])
key_url = "https://zoom.us/linux/download/pubkey"
opi.ask_import_key(key_url)
opi.install_packages(['https://zoom.us/client/latest/zoom_openSUSE_x86_64.rpm'])
opi.ask_keep_key(key_url)
9 changes: 8 additions & 1 deletion test/02_install_from_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,24 @@
print("PEXPECT: Found hardware entry id %r" % hwentryid)
c.sendline(hwentryid)

c.expect('Import package signing key', timeout=10)
c.sendline('y')

c.expect('new packages? to install', timeout=60)
c.expect('Continue', timeout=60)
c.sendline('y')

c.expect('Do you want to keep the repo', timeout=350)
c.sendline('n')

c.expect('Keep package signing key', timeout=10)
c.sendline('n')

c.interact()
c.wait()
c.close()
assert c.exitstatus == 0, 'Exit code: %i' % c.exitstatus

subprocess.check_call(['rpm', '-qi', 'rtl8812au'])
subprocess.check_call('zypper lr -u | grep -v hardware', shell=True)
subprocess.check_call('! zypper lr -u | grep hardware', shell=True)
subprocess.check_call('! rpm -q gpg-pubkey --qf "%{NAME}-%{VERSION}\t%{PACKAGER}\n" | grep hardware', shell=True)
3 changes: 3 additions & 0 deletions test/03_install_using_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
c.expect("Do you want to install")
c.sendline('y')

c.expect('Import package signing key', timeout=10)
c.sendline('y')

c.expect("Continue")
c.sendline('y')

Expand Down

0 comments on commit 378c6e7

Please sign in to comment.