fix: support multiple SSHPubKey records per user

There was one blazing issue with the previous implementation regardless
of the multiple records: we were generating fingerprints by storing
the key into a file and reading it with ssh-keygen. This is absolutely
terrible and was not meant to be left around (it was forgotten, my bad).

Took this opportunity to clean up a few things:
- simplify pubkey validation
- centralize things a bit better

Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2022-02-08 07:50:15 -08:00
parent 660d57340a
commit 4c14a10b91
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
11 changed files with 162 additions and 108 deletions

View file

@ -2,7 +2,8 @@ from typing import Any, Dict
from fastapi import Request
from aurweb import cookies, db, models, time
from aurweb import cookies, db, models, time, util
from aurweb.models import SSHPubKey
from aurweb.models.ssh_pub_key import get_fingerprint
from aurweb.util import strtobool
@ -52,32 +53,35 @@ def timezone(TZ: str = str(),
context["language"] = TZ
def ssh_pubkey(PK: str = str(),
user: models.User = None,
**kwargs) -> None:
# If a PK is given, compare it against the target user's PK.
if PK:
# Get the second token in the public key, which is the actual key.
pubkey = PK.strip().rstrip()
parts = pubkey.split(" ")
if len(parts) == 3:
# Remove the host part.
pubkey = parts[0] + " " + parts[1]
fingerprint = get_fingerprint(pubkey)
if not user.ssh_pub_key:
# No public key exists, create one.
with db.begin():
db.create(models.SSHPubKey, UserID=user.ID,
PubKey=pubkey, Fingerprint=fingerprint)
elif user.ssh_pub_key.PubKey != pubkey:
# A public key already exists, update it.
with db.begin():
user.ssh_pub_key.PubKey = pubkey
user.ssh_pub_key.Fingerprint = fingerprint
elif user.ssh_pub_key:
# Else, if the user has a public key already, delete it.
def ssh_pubkey(PK: str = str(), user: models.User = None, **kwargs) -> None:
if not PK:
# If no pubkey is provided, wipe out any pubkeys the user
# has and return out early.
with db.begin():
db.delete(user.ssh_pub_key)
db.delete_all(user.ssh_pub_keys)
return
# Otherwise, parse ssh keys and their fprints out of PK.
keys = util.parse_ssh_keys(PK.strip())
fprints = [get_fingerprint(" ".join(k)) for k in keys]
with db.begin():
# Delete any existing keys we can't find.
to_remove = user.ssh_pub_keys.filter(
~SSHPubKey.Fingerprint.in_(fprints))
db.delete_all(to_remove)
# For each key, if it does not yet exist, create it.
for i, full_key in enumerate(keys):
prefix, key = full_key
exists = user.ssh_pub_keys.filter(
SSHPubKey.Fingerprint == fprints[i]
).exists()
if not db.query(exists).scalar():
# No public key exists, create one.
db.create(models.SSHPubKey, UserID=user.ID,
PubKey=" ".join([prefix, key]),
Fingerprint=fprints[i])
def account_type(T: int = None,

View file

@ -107,14 +107,16 @@ def invalid_pgp_key(K: str = str(), **kwargs) -> None:
def invalid_ssh_pubkey(PK: str = str(), user: models.User = None,
_: l10n.Translator = None, **kwargs) -> None:
if PK:
invalid_exc = ValidationError(["The SSH public key is invalid."])
if not util.valid_ssh_pubkey(PK):
raise invalid_exc
if not PK:
return
fingerprint = get_fingerprint(PK.strip().rstrip())
if not fingerprint:
raise invalid_exc
try:
keys = util.parse_ssh_keys(PK.strip())
except ValueError as exc:
raise ValidationError([str(exc)])
for prefix, key in keys:
fingerprint = get_fingerprint(f"{prefix} {key}")
exists = db.query(models.SSHPubKey).filter(
and_(models.SSHPubKey.UserID != user.ID,