Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Create accounts with a key and find the instance config

c36d507302c0f7fb24af6eba390c4121d67318c3 · Keenan Nemetz · 16d ago · parent 3abbe749

Create accounts with a key and find the instance config

An account is now written with its first key in one transaction, so a name cannot be claimed without a credential. add-key remains for additional keys, and remote user create forwards the key on stdin.

Local commands now resolve the config from --config, VOLE_CONFIG, ./vole.toml, or ~/.vole/vole.toml before falling back to defaults. init prints --config in its next steps when the instance will not be discovered automatically.

forge/identity/identity.go +62−17

@@ -496,22 +496,33 @@ func (s *Store) mutate(action, actor, account, detail string, fn func(tx *sql.Tx
496 496 return nil
497 497 }
498 498
499 // Create adds an account with a fresh id and no keys.
500 func (s *Store) Create(name, actor string) (*Account, error) {
499 // Create adds an account with a fresh id and its first key. The account, the
500 // name reservation, and the key are written in one transaction, so a name is
501 // never claimed by an account that cannot authenticate.
502 func (s *Store) Create(name, authorizedKey, actor string) (*Account, error) {
501 503 if err := ValidateName(name); err != nil {
502 504 return nil, err
503 505 }
506 key, err := parseKey(authorizedKey)
507 if err != nil {
508 return nil, err
509 }
504 510 now := time.Now().UTC()
505 511 accountID, err := id.New(now)
506 512 if err != nil {
507 513 return nil, err
508 514 }
509 err = s.mutate("identity.create", actor, accountID, name, func(tx *sql.Tx) error {
515 err = s.mutate("identity.create", actor, accountID, name+" "+key.Fingerprint, func(tx *sql.Tx) error {
510 516 if owner, err := reservedBy(tx, name); err != nil {
511 517 return err
512 518 } else if owner != "" {
513 519 return fmt.Errorf("identity: the name %q belongs to %s and cannot be reused; a retired or former name is never handed out twice", name, owner)
514 520 }
521 if owner, err := keyOwner(tx, key.Fingerprint); err != nil {
522 return err
523 } else if owner != "" {
524 return fmt.Errorf("identity: that key is already registered to %s", owner)
525 }
515 526 if _, err := tx.Exec(`INSERT INTO users(id, name, status, created) VALUES(?,?, 'active', ?)`,
516 527 accountID, name, now.Unix()); err != nil {
517 528 return fmt.Errorf("identity: create %s: %w", name, err)
@@ -519,6 +530,10 @@ func (s *Store) Create(name, actor string) (*Account, error) {
519 530 if _, err := tx.Exec(`INSERT INTO user_names(name, user_id) VALUES(?,?)`, name, accountID); err != nil {
520 531 return fmt.Errorf("identity: create %s: %w", name, err)
521 532 }
533 if _, err := tx.Exec(`INSERT INTO user_keys(fingerprint, user_id, type, line, comment) VALUES(?,?,?,?,?)`,
534 key.Fingerprint, accountID, key.Type, key.Line, key.Comment); err != nil {
535 return fmt.Errorf("identity: create %s: %w", name, err)
536 }
522 537 return nil
523 538 })
524 539 if err != nil {
@@ -598,32 +613,62 @@ func (s *Store) Retire(idOrName, actor string) (*Account, error) {
598 613 return s.Find(accountID)
599 614 }
600 615
601 // AddKey attaches an authorized_keys line to an account and returns its
602 // fingerprint.
603 func (s *Store) AddKey(idOrName, authorizedKey, actor string) (string, error) {
616 // parseKey validates one authorized_keys line and extracts what the roster stores.
617 func parseKey(authorizedKey string) (Key, error) {
604 618 line := strings.TrimSpace(authorizedKey)
619 if line == "" {
620 return Key{}, errors.New("identity: no public key given")
621 }
605 622 pub, comment, _, _, err := ssh.ParseAuthorizedKey([]byte(line))
606 623 if err != nil {
607 return "", fmt.Errorf("identity: not an SSH public key: %w", err)
624 return Key{}, fmt.Errorf("identity: not an SSH public key: %w", err)
625 }
626 return Key{
627 Fingerprint: ssh.FingerprintSHA256(pub),
628 Type: pub.Type(),
629 Line: line,
630 Comment: comment,
631 }, nil
632 }
633
634 // keyOwner returns the account name already holding a fingerprint, or "" when
635 // the key is unclaimed.
636 func keyOwner(tx *sql.Tx, fingerprint string) (string, error) {
637 var owner string
638 err := tx.QueryRow(`SELECT u.name FROM user_keys k JOIN users u ON u.id=k.user_id WHERE k.fingerprint=?`, fingerprint).Scan(&owner)
639 if errors.Is(err, sql.ErrNoRows) {
640 return "", nil
641 }
642 if err != nil {
643 return "", fmt.Errorf("identity: read keys: %w", err)
644 }
645 return owner, nil
646 }
647
648 // AddKey attaches an authorized_keys line to an account and returns its
649 // fingerprint. Creation requires a key; this is for adding another one.
650 func (s *Store) AddKey(idOrName, authorizedKey, actor string) (string, error) {
651 key, err := parseKey(authorizedKey)
652 if err != nil {
653 return "", err
608 654 }
609 fp := ssh.FingerprintSHA256(pub)
610 err = s.mutate("identity.add-key", actor, idOrName, fp+" "+comment, func(tx *sql.Tx) error {
655 err = s.mutate("identity.add-key", actor, idOrName, key.Fingerprint+" "+key.Comment, func(tx *sql.Tx) error {
611 656 a, err := findTx(tx, idOrName)
612 657 if err != nil {
613 658 return err
614 659 }
615 var owner string
616 err = tx.QueryRow(`SELECT u.name FROM user_keys k JOIN users u ON u.id=k.user_id WHERE k.fingerprint=?`, fp).Scan(&owner)
660 owner, err := keyOwner(tx, key.Fingerprint)
661 if err != nil {
662 return err
663 }
617 664 switch {
618 case err == nil && owner == a.Name:
665 case owner == a.Name:
619 666 return fmt.Errorf("identity: %s already has that key", a.Name)
620 case err == nil:
667 case owner != "":
621 668 return fmt.Errorf("identity: that key is already registered to %s", owner)
622 case !errors.Is(err, sql.ErrNoRows):
623 return fmt.Errorf("identity: read keys: %w", err)
624 669 }
625 670 if _, err := tx.Exec(`INSERT INTO user_keys(fingerprint, user_id, type, line, comment) VALUES(?,?,?,?,?)`,
626 fp, a.ID, pub.Type(), line, comment); err != nil {
671 key.Fingerprint, a.ID, key.Type, key.Line, key.Comment); err != nil {
627 672 return fmt.Errorf("identity: add key: %w", err)
628 673 }
629 674 return nil
@@ -631,7 +676,7 @@ func (s *Store) AddKey(idOrName, authorizedKey, actor string) (string, error) {
631 676 if err != nil {
632 677 return "", err
633 678 }
634 return fp, nil
679 return key.Fingerprint, nil
635 680 }
636 681
637 682 // RemoveKey revokes one key. An exact fingerprint or a unique prefix is accepted.